I notice that, on Windows, several libraries, including socket2 and mio, use a hack to persuade the standard library to call WSAStartup
, which must happen before any socket API calls may be done.
I have another use case: child processes inheriting sockets from their parents. People who used inetd on Unix-like operating systems will be familiar with this concept, but Windows also supports it! Trouble is, the socket API still has to be initialized before using any inherited sockets, so if you inherit a socket and do something like TcpListener::from_raw_socket
, you're going to get an error: “Either the application has not called WSAStartup, or WSAStartup failed.”
Should the standard library expose a function (perhaps under std::os::windows
) to force a call to WSAStartup
? That seems preferable to the current proliferation of dummy-socket-opening code in every library or application that needs to work with raw sockets.
Here's a pair of example programs that demonstrate socket inheritance on Windows. First is the parent program:
use std::{
env,
io::{self, Read},
net::{Ipv4Addr, TcpListener, TcpStream},
os::windows::io::{AsRawSocket, OwnedSocket},
process::Command,
};
use windows_sys::Win32::Foundation::{SetHandleInformation, HANDLE_FLAG_INHERIT};
fn main() {
// Get the child program's name from the command line.
let inheritor_program = env::args().skip(1).next().unwrap();
// Open a TCP listening socket.
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0u16)).unwrap();
let address = listener.local_addr().unwrap();
let listener: OwnedSocket = listener.into();
// Make the TCP listening socket inheritable.
let made_inheritable = unsafe {
SetHandleInformation(
listener.as_raw_socket() as _,
HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT,
)
};
if made_inheritable == 0 {
panic!("{}", io::Error::last_os_error());
}
// Start the child process.
Command::new(inheritor_program)
.arg(format!("{}", listener.as_raw_socket()))
.spawn()
.unwrap();
// Connect to the child process and read some bytes.
let mut message = Vec::<u8>::new();
{
let mut connection = TcpStream::connect(address).unwrap();
connection.read_to_end(&mut message).unwrap();
}
// Write them to standard output.
let message = String::from_utf8(message).unwrap();
print!("{message}");
}
Then there's the child program:
use std::{
env,
io::Write,
net::{Ipv4Addr, TcpListener},
os::windows::io::{FromRawSocket, RawSocket},
};
fn main() {
drop(TcpListener::bind((Ipv4Addr::LOCALHOST, 0u16)));
let socket_handle: RawSocket = env::args().skip(1).next().unwrap().parse().unwrap();
let socket = unsafe { TcpListener::from_raw_socket(socket_handle) };
let (mut connection, _) = socket.accept().unwrap();
connection.write_all(b"Hello, world!\n").unwrap();
connection.flush().unwrap();
}
If that first drop
statement is commented out, the program will fail because WSAStartup
has not been called.