Expose API to trigger WSAStartup

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.

Tbh, these seem like bugs in the standard library that should be fixed regardless of any new API that's added. It should not panic if winsock is already initialized outside of std. And the child program suggests std should be making sure winsock is initialized when using from_raw_socket. That just seems like an oversight, no?

That said, I'm not against exposing an initialization function but I think it's important to fix any bugs first.

Though looking at it, I'm not entirely clear why mio thinks it can't call WSAStartup itself. It should be fine to call it multiple times (so long as it's paired with a call to WSACleanup).

Good point. I've filed issue #115683.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.