1 use std::ffi::c_void; 2 use std::fmt; 3 use std::fs::File; 4 use std::io; 5 use std::mem::size_of; 6 use std::os::windows::io::AsRawHandle; 7 8 use windows_sys::Win32::Foundation::{ 9 RtlNtStatusToDosError, HANDLE, NTSTATUS, STATUS_NOT_FOUND, STATUS_PENDING, STATUS_SUCCESS, 10 }; 11 use windows_sys::Win32::System::WindowsProgramming::{ 12 NtDeviceIoControlFile, IO_STATUS_BLOCK, IO_STATUS_BLOCK_0, 13 }; 14 15 const IOCTL_AFD_POLL: u32 = 0x00012024; 16 17 #[link(name = "ntdll")] 18 extern "system" { 19 /// See <https://processhacker.sourceforge.io/doc/ntioapi_8h.html#a0d4d550cad4d62d75b76961e25f6550c> 20 /// 21 /// This is an undocumented API and as such not part of <https://github.com/microsoft/win32metadata> 22 /// from which `windows-sys` is generated, and also unlikely to be added, so 23 /// we manually declare it here NtCancelIoFileEx( FileHandle: HANDLE, IoRequestToCancel: *mut IO_STATUS_BLOCK, IoStatusBlock: *mut IO_STATUS_BLOCK, ) -> NTSTATUS24 fn NtCancelIoFileEx( 25 FileHandle: HANDLE, 26 IoRequestToCancel: *mut IO_STATUS_BLOCK, 27 IoStatusBlock: *mut IO_STATUS_BLOCK, 28 ) -> NTSTATUS; 29 } 30 /// Winsock2 AFD driver instance. 31 /// 32 /// All operations are unsafe due to IO_STATUS_BLOCK parameter are being used by Afd driver during STATUS_PENDING before I/O Completion Port returns its result. 33 #[derive(Debug)] 34 pub struct Afd { 35 fd: File, 36 } 37 38 #[repr(C)] 39 #[derive(Debug)] 40 pub struct AfdPollHandleInfo { 41 pub handle: HANDLE, 42 pub events: u32, 43 pub status: NTSTATUS, 44 } 45 46 unsafe impl Send for AfdPollHandleInfo {} 47 48 #[repr(C)] 49 pub struct AfdPollInfo { 50 pub timeout: i64, 51 // Can have only value 1. 52 pub number_of_handles: u32, 53 pub exclusive: u32, 54 pub handles: [AfdPollHandleInfo; 1], 55 } 56 57 impl fmt::Debug for AfdPollInfo { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 f.debug_struct("AfdPollInfo").finish() 60 } 61 } 62 63 impl Afd { 64 /// Poll `Afd` instance with `AfdPollInfo`. 65 /// 66 /// # Unsafety 67 /// 68 /// This function is unsafe due to memory of `IO_STATUS_BLOCK` still being used by `Afd` instance while `Ok(false)` (`STATUS_PENDING`). 69 /// `iosb` needs to be untouched after the call while operation is in effective at ALL TIME except for `cancel` method. 70 /// So be careful not to `poll` twice while polling. 71 /// User should deallocate there overlapped value when error to prevent memory leak. poll( &self, info: &mut AfdPollInfo, iosb: *mut IO_STATUS_BLOCK, overlapped: *mut c_void, ) -> io::Result<bool>72 pub unsafe fn poll( 73 &self, 74 info: &mut AfdPollInfo, 75 iosb: *mut IO_STATUS_BLOCK, 76 overlapped: *mut c_void, 77 ) -> io::Result<bool> { 78 let info_ptr = info as *mut _ as *mut c_void; 79 (*iosb).Anonymous.Status = STATUS_PENDING; 80 let status = NtDeviceIoControlFile( 81 self.fd.as_raw_handle() as HANDLE, 82 0, 83 None, 84 overlapped, 85 iosb, 86 IOCTL_AFD_POLL, 87 info_ptr, 88 size_of::<AfdPollInfo>() as u32, 89 info_ptr, 90 size_of::<AfdPollInfo>() as u32, 91 ); 92 match status { 93 STATUS_SUCCESS => Ok(true), 94 STATUS_PENDING => Ok(false), 95 _ => Err(io::Error::from_raw_os_error( 96 RtlNtStatusToDosError(status) as i32 97 )), 98 } 99 } 100 101 /// Cancel previous polled request of `Afd`. 102 /// 103 /// iosb needs to be used by `poll` first for valid `cancel`. 104 /// 105 /// # Unsafety 106 /// 107 /// This function is unsafe due to memory of `IO_STATUS_BLOCK` still being used by `Afd` instance while `Ok(false)` (`STATUS_PENDING`). 108 /// Use it only with request is still being polled so that you have valid `IO_STATUS_BLOCK` to use. 109 /// User should NOT deallocate there overlapped value after the `cancel` to prevent double free. cancel(&self, iosb: *mut IO_STATUS_BLOCK) -> io::Result<()>110 pub unsafe fn cancel(&self, iosb: *mut IO_STATUS_BLOCK) -> io::Result<()> { 111 if (*iosb).Anonymous.Status != STATUS_PENDING { 112 return Ok(()); 113 } 114 115 let mut cancel_iosb = IO_STATUS_BLOCK { 116 Anonymous: IO_STATUS_BLOCK_0 { Status: 0 }, 117 Information: 0, 118 }; 119 let status = NtCancelIoFileEx(self.fd.as_raw_handle() as HANDLE, iosb, &mut cancel_iosb); 120 if status == STATUS_SUCCESS || status == STATUS_NOT_FOUND { 121 return Ok(()); 122 } 123 Err(io::Error::from_raw_os_error( 124 RtlNtStatusToDosError(status) as i32 125 )) 126 } 127 } 128 129 cfg_io_source! { 130 use std::mem::zeroed; 131 use std::os::windows::io::{FromRawHandle, RawHandle}; 132 use std::ptr::null_mut; 133 use std::sync::atomic::{AtomicUsize, Ordering}; 134 135 use super::iocp::CompletionPort; 136 use windows_sys::Win32::{ 137 Foundation::{UNICODE_STRING, INVALID_HANDLE_VALUE}, 138 System::WindowsProgramming::{ 139 OBJECT_ATTRIBUTES, FILE_SKIP_SET_EVENT_ON_HANDLE, 140 }, 141 Storage::FileSystem::{FILE_OPEN, NtCreateFile, SetFileCompletionNotificationModes, SYNCHRONIZE, FILE_SHARE_READ, FILE_SHARE_WRITE}, 142 }; 143 144 const AFD_HELPER_ATTRIBUTES: OBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES { 145 Length: size_of::<OBJECT_ATTRIBUTES>() as u32, 146 RootDirectory: 0, 147 ObjectName: &AFD_OBJ_NAME as *const _ as *mut _, 148 Attributes: 0, 149 SecurityDescriptor: null_mut(), 150 SecurityQualityOfService: null_mut(), 151 }; 152 153 const AFD_OBJ_NAME: UNICODE_STRING = UNICODE_STRING { 154 Length: (AFD_HELPER_NAME.len() * size_of::<u16>()) as u16, 155 MaximumLength: (AFD_HELPER_NAME.len() * size_of::<u16>()) as u16, 156 Buffer: AFD_HELPER_NAME.as_ptr() as *mut _, 157 }; 158 159 const AFD_HELPER_NAME: &[u16] = &[ 160 '\\' as _, 161 'D' as _, 162 'e' as _, 163 'v' as _, 164 'i' as _, 165 'c' as _, 166 'e' as _, 167 '\\' as _, 168 'A' as _, 169 'f' as _, 170 'd' as _, 171 '\\' as _, 172 'M' as _, 173 'i' as _, 174 'o' as _ 175 ]; 176 177 static NEXT_TOKEN: AtomicUsize = AtomicUsize::new(0); 178 179 impl AfdPollInfo { 180 pub fn zeroed() -> AfdPollInfo { 181 unsafe { zeroed() } 182 } 183 } 184 185 impl Afd { 186 /// Create new Afd instance. 187 pub(crate) fn new(cp: &CompletionPort) -> io::Result<Afd> { 188 let mut afd_helper_handle: HANDLE = INVALID_HANDLE_VALUE; 189 let mut iosb = IO_STATUS_BLOCK { 190 Anonymous: IO_STATUS_BLOCK_0 { Status: 0 }, 191 Information: 0, 192 }; 193 194 unsafe { 195 let status = NtCreateFile( 196 &mut afd_helper_handle as *mut _, 197 SYNCHRONIZE, 198 &AFD_HELPER_ATTRIBUTES as *const _ as *mut _, 199 &mut iosb, 200 null_mut(), 201 0, 202 FILE_SHARE_READ | FILE_SHARE_WRITE, 203 FILE_OPEN, 204 0, 205 null_mut(), 206 0, 207 ); 208 if status != STATUS_SUCCESS { 209 let raw_err = io::Error::from_raw_os_error( 210 RtlNtStatusToDosError(status) as i32 211 ); 212 let msg = format!("Failed to open \\Device\\Afd\\Mio: {}", raw_err); 213 return Err(io::Error::new(raw_err.kind(), msg)); 214 } 215 let fd = File::from_raw_handle(afd_helper_handle as RawHandle); 216 // Increment by 2 to reserve space for other types of handles. 217 // Non-AFD types (currently only NamedPipe), use odd numbered 218 // tokens. This allows the selector to differentate between them 219 // and dispatch events accordingly. 220 let token = NEXT_TOKEN.fetch_add(2, Ordering::Relaxed) + 2; 221 let afd = Afd { fd }; 222 cp.add_handle(token, &afd.fd)?; 223 match SetFileCompletionNotificationModes( 224 afd_helper_handle, 225 FILE_SKIP_SET_EVENT_ON_HANDLE as u8 // This is just 2, so fits in u8 226 ) { 227 0 => Err(io::Error::last_os_error()), 228 _ => Ok(afd), 229 } 230 } 231 } 232 } 233 } 234 235 pub const POLL_RECEIVE: u32 = 0b0_0000_0001; 236 pub const POLL_RECEIVE_EXPEDITED: u32 = 0b0_0000_0010; 237 pub const POLL_SEND: u32 = 0b0_0000_0100; 238 pub const POLL_DISCONNECT: u32 = 0b0_0000_1000; 239 pub const POLL_ABORT: u32 = 0b0_0001_0000; 240 pub const POLL_LOCAL_CLOSE: u32 = 0b0_0010_0000; 241 // Not used as it indicated in each event where a connection is connected, not 242 // just the first time a connection is established. 243 // Also see https://github.com/piscisaureus/wepoll/commit/8b7b340610f88af3d83f40fb728e7b850b090ece. 244 pub const POLL_CONNECT: u32 = 0b0_0100_0000; 245 pub const POLL_ACCEPT: u32 = 0b0_1000_0000; 246 pub const POLL_CONNECT_FAIL: u32 = 0b1_0000_0000; 247 248 pub const KNOWN_EVENTS: u32 = POLL_RECEIVE 249 | POLL_RECEIVE_EXPEDITED 250 | POLL_SEND 251 | POLL_DISCONNECT 252 | POLL_ABORT 253 | POLL_LOCAL_CLOSE 254 | POLL_ACCEPT 255 | POLL_CONNECT_FAIL; 256