confium 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,784 @@
1
+ //! Windows winsock diagnostics for the extension listener bind failure
2
+ //! (WSAENOTSOCK 10038 inside the Ruby process; passes in a plain Rust
3
+ //! process on the same runner — see ext/socket-smoke).
4
+ //!
5
+ //! Two probes run once at extension load, before any other winsock
6
+ //! consumer in the process can interfere:
7
+ //!
8
+ //! 1. WSAStartup(2.2) — and the reference is deliberately NEVER
9
+ //! released. Ruby's own exit path and other native extensions call
10
+ //! WSACleanup; if the process-wide startup count ever drains to
11
+ //! zero, every existing socket dies and new ones fail with
12
+ //! WSAENOTSOCK/WSANOTINITIALISED. Holding one reference pins the
13
+ //! winsock instance for the extension's lifetime.
14
+ //! 2. A raw std TcpListener bind on 127.0.0.1:0 — logged to stderr so
15
+ //! the state of winsock AT LOAD TIME is visible in every CI log.
16
+
17
+ #![cfg(windows)]
18
+
19
+ use std::net::TcpListener;
20
+ use std::sync::atomic::AtomicBool;
21
+ use std::sync::atomic::Ordering;
22
+
23
+ static PROBED: AtomicBool = AtomicBool::new(false);
24
+
25
+ unsafe extern "system" {
26
+ fn GetModuleHandleA(name: *const u8) -> *mut core::ffi::c_void;
27
+ fn GetProcAddress(module: *mut core::ffi::c_void, name: *const u8) -> *mut core::ffi::c_void;
28
+ }
29
+
30
+ unsafe extern "C" {
31
+ fn WSAStartup(wVersionRequested: u16, lpWSAData: *mut WsaData) -> i32;
32
+ fn socket(af: i32, ty: i32, protocol: i32) -> usize;
33
+ fn WSASocketW(
34
+ af: i32,
35
+ ty: i32,
36
+ protocol: i32,
37
+ lpProtocolInfo: *const u8,
38
+ g: i32,
39
+ dwFlags: u32,
40
+ ) -> usize;
41
+ fn bind(s: usize, name: *const u8, namelen: i32) -> i32;
42
+ fn connect(s: usize, name: *const u8, namelen: i32) -> i32;
43
+ fn send(s: usize, buf: *const u8, len: i32, flags: i32) -> i32;
44
+ fn setsockopt(s: usize, level: i32, name: i32, value: *const u8, len: i32) -> i32;
45
+ fn recv(s: usize, buf: *mut u8, len: i32, flags: i32) -> i32;
46
+ fn listen(s: usize, backlog: i32) -> i32;
47
+ fn getsockname(s: usize, name: *mut u8, namelen: *mut i32) -> i32;
48
+ fn accept(s: usize, addr: *mut u8, addrlen: *mut i32) -> usize;
49
+ fn closesocket(s: usize) -> i32;
50
+ fn WSAGetLastError() -> i32;
51
+ }
52
+
53
+ #[repr(C)]
54
+ struct WsaData {
55
+ w_version: u16,
56
+ w_high_version: u16,
57
+ i_max_sockets: u16,
58
+ i_max_u_dp_dg: u16,
59
+ lp_vendor_info: *mut u8,
60
+ sz_description: [u8; 257],
61
+ sz_system_status: [u8; 129],
62
+ }
63
+
64
+ /// Run the winsock probes once. Idempotent; safe to call from init.
65
+ pub fn probe() {
66
+ if PROBED.swap(true, Ordering::SeqCst) {
67
+ return;
68
+ }
69
+
70
+ let mut data = WsaData {
71
+ w_version: 0,
72
+ w_high_version: 0,
73
+ i_max_sockets: 0,
74
+ i_max_u_dp_dg: 0,
75
+ lp_vendor_info: std::ptr::null_mut(),
76
+ sz_description: [0; 257],
77
+ sz_system_status: [0; 129],
78
+ };
79
+ // SAFETY: WSAData is a plain C struct; the pointer is valid for the
80
+ // call duration. The startup reference is intentionally leaked.
81
+ let rc = unsafe { WSAStartup(0x0202, &mut data) };
82
+ let w_hi = data.w_version >> 8;
83
+ let w_lo = data.w_version & 0xff;
84
+ let h_hi = data.w_high_version >> 8;
85
+ let h_lo = data.w_high_version & 0xff;
86
+ eprintln!("confium-winsock: WSAStartup(2.2) rc={rc} ver={w_hi}.{w_lo} high={h_hi}.{h_lo}");
87
+
88
+ probe_raw_winsock();
89
+ probe_wsa_socket_variants();
90
+ probe_address_resolution_split();
91
+
92
+ match TcpListener::bind("127.0.0.1:0") {
93
+ Ok(listener) => {
94
+ let addr = listener
95
+ .local_addr()
96
+ .map(|a| a.to_string())
97
+ .unwrap_or_else(|e| format!("<local_addr failed: {e}>"));
98
+ eprintln!("confium-winsock: init-time bind OK ({addr})");
99
+ }
100
+ Err(e) => {
101
+ eprintln!("confium-winsock: init-time bind FAILED: {e}");
102
+ }
103
+ }
104
+ }
105
+
106
+ /// Step-level raw winsock discrimination: create a socket via FFI,
107
+ /// bind it via FFI, close it. If these succeed where the std bind
108
+ /// fails, the fault is in the std windows-gnu socket path inside the
109
+ /// Ruby process; if the FFI socket() itself fails, the process-level
110
+ /// winsock state is poisoned (no library code of ours involved).
111
+ fn probe_raw_winsock() {
112
+ const AF_INET: i32 = 2;
113
+ const SOCK_STREAM: i32 = 1;
114
+ const IPPROTO_TCP: i32 = 6;
115
+ const INVALID: usize = usize::MAX;
116
+
117
+ // sockaddr_in: family=AF_INET, port=0 (ephemeral), addr=127.0.0.1
118
+ let mut addr = [0u8; 16];
119
+ addr[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
120
+ addr[4..8].copy_from_slice(&[127, 0, 0, 1]);
121
+
122
+ // SAFETY: plain winsock calls; the sockaddr outlives both calls.
123
+ let handle = unsafe { socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) };
124
+ if handle == INVALID {
125
+ let err = unsafe { WSAGetLastError() };
126
+ eprintln!("confium-winsock: raw socket() FAILED wsagetlasterror={err}");
127
+ return;
128
+ }
129
+ eprintln!("confium-winsock: raw socket() handle={handle:#x}");
130
+
131
+ let rc = unsafe { bind(handle, addr.as_ptr(), 16) };
132
+ if rc != 0 {
133
+ let err = unsafe { WSAGetLastError() };
134
+ eprintln!("confium-winsock: raw bind() rc={rc} wsagetlasterror={err}");
135
+ } else {
136
+ eprintln!("confium-winsock: raw bind() OK");
137
+ }
138
+ unsafe { closesocket(handle) };
139
+ }
140
+
141
+ /// Probe (e): Rust std creates sockets with `WSASocketW` (not the
142
+ /// plain `socket()` above). Round (d) showed raw `socket()`+`bind()`
143
+ /// succeed while std's bind fails — so call `WSASocketW` exactly as
144
+ /// std does, across its flag variants, and bind each result. Whichever
145
+ /// variant reproduces the 10038 identifies the failing entry point.
146
+ fn probe_wsa_socket_variants() {
147
+ const AF_INET: i32 = 2;
148
+ const SOCK_STREAM: i32 = 1;
149
+ const IPPROTO_TCP: i32 = 6;
150
+ const INVALID: usize = usize::MAX;
151
+ const WSA_FLAG_OVERLAPPED: u32 = 0x01;
152
+ const WSA_FLAG_NO_HANDLE_INHERIT: u32 = 0x80;
153
+
154
+ let variants: [(&str, u32); 3] = [
155
+ ("overlapped", WSA_FLAG_OVERLAPPED),
156
+ ("overlapped|no_handle_inherit", WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT),
157
+ ("flags=0", 0),
158
+ ];
159
+
160
+ for (label, flags) in variants {
161
+ let mut addr = [0u8; 16];
162
+ addr[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
163
+ addr[4..8].copy_from_slice(&[127, 0, 0, 1]);
164
+
165
+ // SAFETY: plain winsock calls; the sockaddr outlives both.
166
+ let s = unsafe { WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, std::ptr::null(), 0, flags) };
167
+ if s == INVALID {
168
+ let err = unsafe { WSAGetLastError() };
169
+ eprintln!("confium-winsock: WSASocketW[{label}] FAILED wsagetlasterror={err}");
170
+ continue;
171
+ }
172
+ let rc = unsafe { bind(s, addr.as_ptr(), 16) };
173
+ if rc != 0 {
174
+ let err = unsafe { WSAGetLastError() };
175
+ eprintln!("confium-winsock: WSASocketW[{label}] handle={s:#x} bind rc={rc} wsagetlasterror={err}");
176
+ } else {
177
+ eprintln!("confium-winsock: WSASocketW[{label}] handle={s:#x} bind OK");
178
+ }
179
+ unsafe { closesocket(s) };
180
+ }
181
+ }
182
+
183
+ /// Probe (f): split std's bind path into its two halves. std's
184
+ /// `TcpListener::bind("host:port")` resolves the string (GetAddrInfoW)
185
+ /// before creating the socket; binding a pre-parsed `SocketAddr`
186
+ /// skips resolution entirely. If the SocketAddr form works while the
187
+ /// string form fails, the fault is std's address resolution inside
188
+ /// the Ruby process, and the fix is for confium-net-tcp to parse
189
+ /// hosts itself. Also exercised: a complete raw listener (bind +
190
+ /// listen + getsockname) and a std CLIENT connect to it, to see
191
+ /// whether any std net path works in-process.
192
+ fn probe_address_resolution_split() {
193
+ use std::net::IpAddr;
194
+ use std::net::Ipv4Addr;
195
+ use std::net::SocketAddr;
196
+ use std::net::TcpStream;
197
+
198
+ // 1) std bind via pre-parsed SocketAddr (no getaddrinfo).
199
+ let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
200
+ match TcpListener::bind(addr) {
201
+ Ok(l) => eprintln!("confium-winsock: std bind[SocketAddr] OK ({})", l.local_addr().map(|a| a.to_string()).unwrap_or_default()),
202
+ Err(e) => eprintln!("confium-winsock: std bind[SocketAddr] FAILED: {e}"),
203
+ }
204
+
205
+ // 2) std bind via &str (exercises GetAddrInfoW).
206
+ match TcpListener::bind("127.0.0.1:0") {
207
+ Ok(l) => eprintln!("confium-winsock: std bind[str] OK ({})", l.local_addr().map(|a| a.to_string()).unwrap_or_default()),
208
+ Err(e) => eprintln!("confium-winsock: std bind[str] FAILED: {e}"),
209
+ }
210
+
211
+ // 3) Full raw listener + std client connect to it.
212
+ const AF_INET: i32 = 2;
213
+ const INVALID: usize = usize::MAX;
214
+ let mut saddr = [0u8; 16];
215
+ saddr[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
216
+ saddr[4..8].copy_from_slice(&[127, 0, 0, 1]);
217
+ // SAFETY: plain winsock calls; buffers outlive the calls.
218
+ let s = unsafe { socket(AF_INET, 1, 6) };
219
+ if s == INVALID {
220
+ eprintln!("confium-winsock: probe-f socket FAILED {}", unsafe { WSAGetLastError() });
221
+ return;
222
+ }
223
+ if unsafe { bind(s, saddr.as_ptr(), 16) } != 0 {
224
+ eprintln!("confium-winsock: probe-f bind FAILED {}", unsafe { WSAGetLastError() });
225
+ unsafe { closesocket(s) };
226
+ return;
227
+ }
228
+ if unsafe { listen(s, 16) } != 0 {
229
+ eprintln!("confium-winsock: probe-f listen FAILED {}", unsafe { WSAGetLastError() });
230
+ unsafe { closesocket(s) };
231
+ return;
232
+ }
233
+ let mut got = [0u8; 16];
234
+ let mut gotlen: i32 = 16;
235
+ let mut port: u16 = 0;
236
+ if unsafe { getsockname(s, got.as_mut_ptr(), &mut gotlen) } == 0 {
237
+ port = u16::from_be_bytes([got[2], got[3]]);
238
+ eprintln!("confium-winsock: probe-f raw listener up on port {port}");
239
+ }
240
+ if port != 0 {
241
+ match TcpStream::connect(("127.0.0.1", port)) {
242
+ Ok(_) => eprintln!("confium-winsock: std client connect OK"),
243
+ Err(e) => eprintln!("confium-winsock: std client connect FAILED: {e}"),
244
+ }
245
+ // Drain the accepted connection so the raw socket closes clean.
246
+ let mut pa = [0u8; 16];
247
+ let mut palen: i32 = 16;
248
+ let acc = unsafe { accept(s, pa.as_mut_ptr(), &mut palen) };
249
+ if acc != INVALID {
250
+ unsafe { closesocket(acc) };
251
+ }
252
+ }
253
+ unsafe { closesocket(s) };
254
+ }
255
+
256
+ /// Probe (g): the timeline bisect. Exposed to Ruby as
257
+ /// `Confium::Native.winsock_probe(tag)` so the spec harness can run
258
+ /// the full diagnostic sequence at chosen points (suite start, right
259
+ /// before a ceremony) and the CI log shows exactly WHEN each std net
260
+ /// operation degrades. Adds the comparator probe (f) lacked: a raw
261
+ /// FFI connect against a live listener, step by step, next to the
262
+ /// std connect — plus a UDP bind for type coverage.
263
+ ///
264
+ /// Non-Windows builds get a no-op with the same signature so spec
265
+ /// code can call it unconditionally.
266
+ #[cfg(windows)]
267
+ pub fn probe_on_demand(tag: &str) {
268
+ use std::io::Write;
269
+ use std::net::IpAddr;
270
+ use std::net::Ipv4Addr;
271
+ use std::net::SocketAddr;
272
+ use std::net::TcpStream;
273
+ use std::net::UdpSocket;
274
+
275
+ let log = |what: &str, r: Result<String, String>| match r {
276
+ Ok(s) => eprintln!("confium-winsock[{tag}]: {what} OK ({s})"),
277
+ Err(e) => eprintln!("confium-winsock[{tag}]: {what} FAILED: {e}"),
278
+ };
279
+
280
+ let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
281
+ log("std bind[SocketAddr]", TcpListener::bind(addr).and_then(|l| l.local_addr().map(|a| a.to_string())).map_err(|e| e.to_string()));
282
+ log("std bind[str]", TcpListener::bind("127.0.0.1:0").and_then(|l| l.local_addr().map(|a| a.to_string())).map_err(|e| e.to_string()));
283
+ log("std udp bind", UdpSocket::bind("127.0.0.1:0").and_then(|s| s.local_addr().map(|a| a.to_string())).map_err(|e| e.to_string()));
284
+
285
+ // Raw listener for the connect comparisons.
286
+ let listener = raw_listener();
287
+ let Some((ls, port)) = listener else {
288
+ eprintln!("confium-winsock[{tag}]: raw listener setup FAILED");
289
+ return;
290
+ };
291
+
292
+ // FFI connect with a plain socket().
293
+ let c1 = ffi_connect(false, port);
294
+ match c1 {
295
+ Ok(()) => eprintln!("confium-winsock[{tag}]: FFI connect[socket()] OK"),
296
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI connect[socket()] FAILED: {e}"),
297
+ }
298
+ // FFI connect with WSASocketW (overlapped), as std does.
299
+ match ffi_connect(true, port) {
300
+ Ok(()) => eprintln!("confium-winsock[{tag}]: FFI connect[WSASocketW] OK"),
301
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI connect[WSASocketW] FAILED: {e}"),
302
+ }
303
+ // std connect, tuple form (resolution + socket + connect).
304
+ match TcpStream::connect(("127.0.0.1", port)) {
305
+ Ok(_) => eprintln!("confium-winsock[{tag}]: std connect[tuple] OK"),
306
+ Err(e) => eprintln!("confium-winsock[{tag}]: std connect[tuple] FAILED: {e}"),
307
+ }
308
+ // std connect, pre-parsed SocketAddr (no resolution).
309
+ match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) {
310
+ Ok(_) => eprintln!("confium-winsock[{tag}]: std connect[SocketAddr] OK"),
311
+ Err(e) => eprintln!("confium-winsock[{tag}]: std connect[SocketAddr] FAILED: {e}"),
312
+ }
313
+ drain_accepted(ls);
314
+
315
+ // Probe (h): the ceremonies showed std connect OK followed by
316
+ // transport send failing 10038. Exercise the write paths next to
317
+ // the reads: a std blocking write, and a raw FFI send.
318
+ if let Some((ls2, port2)) = raw_listener() {
319
+ match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port2)) {
320
+ Ok(mut s) => match s.write_all(b"confium-probe") {
321
+ Ok(()) => eprintln!("confium-winsock[{tag}]: std write_all OK"),
322
+ Err(e) => eprintln!("confium-winsock[{tag}]: std write_all FAILED: {e}"),
323
+ },
324
+ Err(e) => eprintln!("confium-winsock[{tag}]: send-probe connect FAILED: {e}"),
325
+ }
326
+ drain_accepted(ls2);
327
+ }
328
+ if let Some((_ls3, port3)) = raw_listener() {
329
+ match ffi_send_probe(port3) {
330
+ Ok(()) => eprintln!("confium-winsock[{tag}]: FFI send OK"),
331
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI send FAILED: {e}"),
332
+ }
333
+ drain_accepted(_ls3);
334
+ }
335
+
336
+ // Probe (i): every FAILING path (noise connect, coordinator
337
+ // send/recv) calls set_read_timeout; no passing path does, and
338
+ // none of the probes above ever set a timeout. Test std
339
+ // set_read_timeout and the raw FFI setsockopt(SO_RCVTIMEO) next
340
+ // to each other on a connected socket.
341
+ if let Some((ls4, port4)) = raw_listener() {
342
+ match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port4)) {
343
+ Ok(mut s) => {
344
+ match s.set_read_timeout(Some(std::time::Duration::from_secs(5))) {
345
+ Ok(()) => eprintln!("confium-winsock[{tag}]: std set_read_timeout OK"),
346
+ Err(e) => eprintln!("confium-winsock[{tag}]: std set_read_timeout FAILED: {e}"),
347
+ }
348
+ match s.write_all(b"probe-i") {
349
+ Ok(()) => eprintln!("confium-winsock[{tag}]: post-timeout write OK"),
350
+ Err(e) => eprintln!("confium-winsock[{tag}]: post-timeout write FAILED: {e}"),
351
+ }
352
+ let mut one = [0u8; 1];
353
+ use std::io::Read;
354
+ match s.read(&mut one) {
355
+ Ok(_) => eprintln!("confium-winsock[{tag}]: post-timeout read OK"),
356
+ Err(e) => eprintln!("confium-winsock[{tag}]: post-timeout read FAILED: {e}"),
357
+ }
358
+ }
359
+ Err(e) => eprintln!("confium-winsock[{tag}]: probe-i connect FAILED: {e}"),
360
+ }
361
+ drain_accepted(ls4);
362
+ }
363
+ if let Some((ls5, port5)) = raw_listener() {
364
+ match ffi_rcvtimeo_probe(port5) {
365
+ Ok(()) => eprintln!("confium-winsock[{tag}]: FFI setsockopt[SO_RCVTIMEO] OK"),
366
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI setsockopt[SO_RCVTIMEO] FAILED: {e}"),
367
+ }
368
+ drain_accepted(ls5);
369
+ }
370
+
371
+ // Probe (j): 0.8.5 moved deadlines to non-blocking polling, and the
372
+ // tcp ceremony now gets PAST the send (the fix worked for writes)
373
+ // but fails on recv — reads appear broken in-process regardless of
374
+ // socket mode. Hypothesis: the statically-linked `recv` import
375
+ // binds to an exported wrapper in the Ruby DLL instead of ws2_32.
376
+ // Compare: recv through GetProcAddress("ws2_32.dll", "recv") vs
377
+ // the statically-bound recv, against a send-only server so only
378
+ // the client's recv is under test.
379
+ let echo_j = push_listener();
380
+ match ffi_recv_probe(false) {
381
+ Ok(()) => eprintln!("confium-winsock[{tag}]: FFI recv[static] OK"),
382
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI recv[static] FAILED: {e}"),
383
+ }
384
+ if let Some(h) = echo_j {
385
+ let _ = h.join();
386
+ }
387
+ let echo_j2 = push_listener();
388
+ match ffi_recv_probe(true) {
389
+ Ok(()) => eprintln!("confium-winsock[{tag}]: FFI recv[GetProcAddress] OK"),
390
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI recv[GetProcAddress] FAILED: {e}"),
391
+ }
392
+ if let Some(h) = echo_j2 {
393
+ let _ = h.join();
394
+ }
395
+
396
+ // Probe (k): every FAILED std read so far had a socket-state call
397
+ // before it (set_read_timeout or set_nonblocking); every WORKING
398
+ // read was raw FFI on a socket()-created socket. Test the last
399
+ // untried combination: a std read with ZERO options — plain
400
+ // blocking std connect + read against the push server — plus the
401
+ // same after set_nodelay only, and after set_nonblocking only.
402
+ if let Some(h) = push_listener() {
403
+ match TcpStream::connect(("127.0.0.1", PUSH_PORT.with(|p| p.get()))) {
404
+ Ok(mut s) => {
405
+ use std::io::Read;
406
+ let mut b = [0u8; 1];
407
+ match s.read(&mut b) {
408
+ Ok(_) => eprintln!("confium-winsock[{tag}]: std read[no options] OK"),
409
+ Err(e) => eprintln!("confium-winsock[{tag}]: std read[no options] FAILED: {e}"),
410
+ }
411
+ }
412
+ Err(e) => eprintln!("confium-winsock[{tag}]: probe-k connect FAILED: {e}"),
413
+ }
414
+ let _ = h.join();
415
+ }
416
+ if let Some(h) = push_listener() {
417
+ match TcpStream::connect(("127.0.0.1", PUSH_PORT.with(|p| p.get()))) {
418
+ Ok(mut s) => {
419
+ let _ = s.set_nodelay(true);
420
+ use std::io::Read;
421
+ let mut b = [0u8; 1];
422
+ match s.read(&mut b) {
423
+ Ok(_) => eprintln!("confium-winsock[{tag}]: std read[after nodelay] OK"),
424
+ Err(e) => eprintln!("confium-winsock[{tag}]: std read[after nodelay] FAILED: {e}"),
425
+ }
426
+ }
427
+ Err(e) => eprintln!("confium-winsock[{tag}]: probe-k connect FAILED: {e}"),
428
+ }
429
+ let _ = h.join();
430
+ }
431
+ if let Some(h) = push_listener() {
432
+ match TcpStream::connect(("127.0.0.1", PUSH_PORT.with(|p| p.get()))) {
433
+ Ok(mut s) => {
434
+ let _ = s.set_nonblocking(true);
435
+ let _ = s.set_nonblocking(false);
436
+ use std::io::Read;
437
+ let mut b = [0u8; 1];
438
+ match s.read(&mut b) {
439
+ Ok(_) => eprintln!("confium-winsock[{tag}]: std read[after nonblocking toggle] OK"),
440
+ Err(e) => eprintln!("confium-winsock[{tag}]: std read[after nonblocking toggle] FAILED: {e}"),
441
+ }
442
+ }
443
+ Err(e) => eprintln!("confium-winsock[{tag}]: probe-k connect FAILED: {e}"),
444
+ }
445
+ let _ = h.join();
446
+ }
447
+
448
+ // Probe (l): the ONE cell never exercised — a read WHILE the
449
+ // socket is nonblocking with NO data pending. That is exactly the
450
+ // first iteration of the 0.8.5 polling deadline loops, and every
451
+ // other cell in the matrix works. Normally such a read returns
452
+ // WouldBlock (WSAEWOULDBLOCK 10035); if it returns WSAENOTSOCK
453
+ // (10038) in-process, every remaining failure is explained: the
454
+ // poll loops pass the error through (WouldBlock-only filter).
455
+ // Server here ACCEPTS and stays silent, so no data is ever ready.
456
+ if let Some((ls, port)) = raw_listener() {
457
+ let silent = std::thread::spawn(move || {
458
+ let mut pa = [0u8; 16];
459
+ let mut palen: i32 = 16;
460
+ // SAFETY: plain winsock accept.
461
+ let _acc = unsafe { accept(ls, pa.as_mut_ptr(), &mut palen) };
462
+ std::thread::sleep(std::time::Duration::from_millis(1200));
463
+ // SAFETY: cleanup.
464
+ unsafe { closesocket(ls) };
465
+ });
466
+ match TcpStream::connect(("127.0.0.1", port)) {
467
+ Ok(mut s) => {
468
+ let _ = s.set_nonblocking(true);
469
+ use std::io::Read;
470
+ let mut b = [0u8; 1];
471
+ match s.read(&mut b) {
472
+ Ok(n) => eprintln!("confium-winsock[{tag}]: nonblocking idle read returned Ok({n})?!"),
473
+ Err(e) => eprintln!(
474
+ "confium-winsock[{tag}]: nonblocking idle read kind={:?} raw={:?} ({e})",
475
+ e.kind(),
476
+ e.raw_os_error()
477
+ ),
478
+ }
479
+ let _ = s.set_nonblocking(false);
480
+ }
481
+ Err(e) => eprintln!("confium-winsock[{tag}]: probe-l connect FAILED: {e}"),
482
+ }
483
+ let _ = silent.join();
484
+ }
485
+ // Same cell via raw FFI: socket() + FIONBIO + recv, silent peer.
486
+ if let Some((ls2, port2)) = raw_listener() {
487
+ let silent = std::thread::spawn(move || {
488
+ let mut pa = [0u8; 16];
489
+ let mut palen: i32 = 16;
490
+ // SAFETY: plain winsock accept.
491
+ let acc = unsafe { accept(ls2, pa.as_mut_ptr(), &mut palen) };
492
+ if acc != usize::MAX {
493
+ std::thread::sleep(std::time::Duration::from_millis(1200));
494
+ // SAFETY: cleanup.
495
+ unsafe { closesocket(acc) };
496
+ }
497
+ // SAFETY: cleanup.
498
+ unsafe { closesocket(ls2) };
499
+ });
500
+ match ffi_nonblocking_idle_recv(port2) {
501
+ Ok(code) => eprintln!("confium-winsock[{tag}]: FFI nonblocking idle recv rc={code} (0 pending, expect -1/10035)"),
502
+ Err(e) => eprintln!("confium-winsock[{tag}]: FFI nonblocking idle recv FAILED: {e}"),
503
+ }
504
+ let _ = silent.join();
505
+ }
506
+ }
507
+
508
+ /// FFI socket + connect + ioctlsocket(FIONBIO,1) + one recv against a
509
+ /// silent peer. Returns the recv return code (negative means
510
+ /// WSAGetLastError carries the reason).
511
+ #[cfg(windows)]
512
+ fn ffi_nonblocking_idle_recv(port: u16) -> Result<i32, String> {
513
+ const AF_INET: i32 = 2;
514
+ const INVALID: usize = usize::MAX;
515
+ const FIONBIO: i32 = -2147195266; // 0x8004667E as i32
516
+ // SAFETY: plain winsock calls below.
517
+ let s = unsafe { socket(AF_INET, 1, 6) };
518
+ if s == INVALID {
519
+ return Err(format!("create {}", unsafe { WSAGetLastError() }));
520
+ }
521
+ let mut peer = [0u8; 16];
522
+ peer[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
523
+ peer[2..4].copy_from_slice(&port.to_be_bytes());
524
+ peer[4..8].copy_from_slice(&[127, 0, 0, 1]);
525
+ if unsafe { connect(s, peer.as_ptr(), 16) } != 0 {
526
+ let err = unsafe { WSAGetLastError() };
527
+ unsafe { closesocket(s) };
528
+ return Err(format!("connect wsagetlasterror={err}"));
529
+ }
530
+ let on: u32 = 1;
531
+ let rc = unsafe {
532
+ #[link(name = "ws2_32")]
533
+ unsafe extern "C" {
534
+ fn ioctlsocket(s: usize, cmd: i32, argp: *mut u32) -> i32;
535
+ }
536
+ ioctlsocket(s, FIONBIO, &on as *const u32 as *mut u32)
537
+ };
538
+ if rc != 0 {
539
+ let err = unsafe { WSAGetLastError() };
540
+ unsafe { closesocket(s) };
541
+ return Err(format!("ioctlsocket wsagetlasterror={err}"));
542
+ }
543
+ // Give the silent peer a moment so no data is ever pending.
544
+ std::thread::sleep(std::time::Duration::from_millis(200));
545
+ let mut b = [0u8; 1];
546
+ let got = unsafe { recv(s, b.as_mut_ptr(), 1, 0) };
547
+ let err = if got < 0 { unsafe { WSAGetLastError() } } else { 0 };
548
+ unsafe { closesocket(s) };
549
+ if got < 0 {
550
+ Err(format!("recv rc={got} wsagetlasterror={err}"))
551
+ } else {
552
+ Ok(got)
553
+ }
554
+ }
555
+
556
+ fn ffi_rcvtimeo_probe(port: u16) -> Result<(), String> {
557
+ const AF_INET: i32 = 2;
558
+ const SOL_SOCKET: i32 = 0xffff;
559
+ const SO_RCVTIMEO: i32 = 0x1006;
560
+ const INVALID: usize = usize::MAX;
561
+ // SAFETY: plain winsock calls.
562
+ let s = unsafe { socket(AF_INET, 1, 6) };
563
+ if s == INVALID {
564
+ return Err(format!("create {}", unsafe { WSAGetLastError() }));
565
+ }
566
+ let mut peer = [0u8; 16];
567
+ peer[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
568
+ peer[2..4].copy_from_slice(&port.to_be_bytes());
569
+ peer[4..8].copy_from_slice(&[127, 0, 0, 1]);
570
+ let rc = unsafe { connect(s, peer.as_ptr(), 16) };
571
+ if rc != 0 {
572
+ let err = unsafe { WSAGetLastError() };
573
+ unsafe { closesocket(s) };
574
+ return Err(format!("connect wsagetlasterror={err}"));
575
+ }
576
+ let ms: u32 = 5000;
577
+ let rc = unsafe { setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &ms as *const u32 as *const u8, 4) };
578
+ if rc != 0 {
579
+ let err = unsafe { WSAGetLastError() };
580
+ unsafe { closesocket(s) };
581
+ return Err(format!("setsockopt wsagetlasterror={err}"));
582
+ }
583
+ let payload = b"probe-i";
584
+ let sent = unsafe { send(s, payload.as_ptr(), payload.len() as i32, 0) };
585
+ unsafe { closesocket(s) };
586
+ if sent < 0 {
587
+ Err(format!("send wsagetlasterror={}", unsafe { WSAGetLastError() }))
588
+ } else {
589
+ Ok(())
590
+ }
591
+ }
592
+
593
+ /// FFI socket + connect + send, step by step.
594
+ #[cfg(windows)]
595
+ fn ffi_send_probe(port: u16) -> Result<(), String> {
596
+ const AF_INET: i32 = 2;
597
+ const INVALID: usize = usize::MAX;
598
+ // SAFETY: plain winsock calls.
599
+ let s = unsafe { socket(AF_INET, 1, 6) };
600
+ if s == INVALID {
601
+ return Err(format!("create {}", unsafe { WSAGetLastError() }));
602
+ }
603
+ let mut peer = [0u8; 16];
604
+ peer[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
605
+ peer[2..4].copy_from_slice(&port.to_be_bytes());
606
+ peer[4..8].copy_from_slice(&[127, 0, 0, 1]);
607
+ let rc = unsafe { connect(s, peer.as_ptr(), 16) };
608
+ if rc != 0 {
609
+ let err = unsafe { WSAGetLastError() };
610
+ unsafe { closesocket(s) };
611
+ return Err(format!("connect wsagetlasterror={err}"));
612
+ }
613
+ let payload = b"confium-probe";
614
+ let sent = unsafe { send(s, payload.as_ptr(), payload.len() as i32, 0) };
615
+ let err = if sent < 0 { unsafe { WSAGetLastError() } } else { 0 };
616
+ unsafe { closesocket(s) };
617
+ if sent < 0 {
618
+ Err(format!("send wsagetlasterror={err}"))
619
+ } else {
620
+ Ok(())
621
+ }
622
+ }
623
+
624
+ #[cfg(windows)]
625
+ fn raw_listener() -> Option<(usize, u16)> {
626
+ const AF_INET: i32 = 2;
627
+ const INVALID: usize = usize::MAX;
628
+ let mut saddr = [0u8; 16];
629
+ saddr[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
630
+ saddr[4..8].copy_from_slice(&[127, 0, 0, 1]);
631
+ // SAFETY: plain winsock calls; buffers outlive them.
632
+ let s = unsafe { socket(AF_INET, 1, 6) };
633
+ if s == INVALID {
634
+ eprintln!("confium-winsock: raw_listener socket FAILED {}", unsafe { WSAGetLastError() });
635
+ return None;
636
+ }
637
+ if unsafe { bind(s, saddr.as_ptr(), 16) } != 0 {
638
+ eprintln!("confium-winsock: raw_listener bind FAILED {}", unsafe { WSAGetLastError() });
639
+ unsafe { closesocket(s) };
640
+ return None;
641
+ }
642
+ if unsafe { listen(s, 16) } != 0 {
643
+ eprintln!("confium-winsock: raw_listener listen FAILED {}", unsafe { WSAGetLastError() });
644
+ unsafe { closesocket(s) };
645
+ return None;
646
+ }
647
+ let mut got = [0u8; 16];
648
+ let mut gotlen: i32 = 16;
649
+ if unsafe { getsockname(s, got.as_mut_ptr(), &mut gotlen) } != 0 {
650
+ eprintln!("confium-winsock: raw_listener getsockname FAILED {}", unsafe { WSAGetLastError() });
651
+ unsafe { closesocket(s) };
652
+ return None;
653
+ }
654
+ let port = u16::from_be_bytes([got[2], got[3]]);
655
+ Some((s, port))
656
+ }
657
+
658
+ #[cfg(windows)]
659
+ fn ffi_connect(wsa: bool, port: u16) -> Result<(), String> {
660
+ const AF_INET: i32 = 2;
661
+ const INVALID: usize = usize::MAX;
662
+ // SAFETY: plain winsock calls.
663
+ let s = if wsa {
664
+ unsafe { WSASocketW(AF_INET, 1, 6, std::ptr::null(), 0, 0x01) }
665
+ } else {
666
+ unsafe { socket(AF_INET, 1, 6) }
667
+ };
668
+ if s == INVALID {
669
+ return Err(format!("create {}", unsafe { WSAGetLastError() }));
670
+ }
671
+ let mut peer = [0u8; 16];
672
+ peer[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
673
+ peer[2..4].copy_from_slice(&port.to_be_bytes());
674
+ peer[4..8].copy_from_slice(&[127, 0, 0, 1]);
675
+ let rc = unsafe { connect(s, peer.as_ptr(), 16) };
676
+ let err = if rc != 0 { unsafe { WSAGetLastError() } } else { 0 };
677
+ unsafe { closesocket(s) };
678
+ if rc != 0 {
679
+ Err(format!("connect wsagetlasterror={err}"))
680
+ } else {
681
+ Ok(())
682
+ }
683
+ }
684
+
685
+ #[cfg(windows)]
686
+ fn drain_accepted(listener: usize) {
687
+ const INVALID: usize = usize::MAX;
688
+ let mut pa = [0u8; 16];
689
+ let mut palen: i32 = 16;
690
+ // SAFETY: plain winsock calls.
691
+ let acc = unsafe { accept(listener, pa.as_mut_ptr(), &mut palen) };
692
+ if acc != INVALID {
693
+ unsafe { closesocket(acc) };
694
+ }
695
+ unsafe { closesocket(listener) };
696
+ }
697
+
698
+
699
+ /// A listener whose peer sends one byte on accept and closes — no
700
+ /// server-side recv, so a client-side recv probe is uncontaminated.
701
+ #[cfg(windows)]
702
+ fn push_listener() -> Option<std::thread::JoinHandle<()>> {
703
+ let (ls, port) = raw_listener()?;
704
+ PUSH_PORT.with(|p| p.set(port));
705
+ Some(std::thread::spawn(move || {
706
+ const INVALID: usize = usize::MAX;
707
+ let mut pa = [0u8; 16];
708
+ let mut palen: i32 = 16;
709
+ // SAFETY: plain winsock calls.
710
+ let acc = unsafe { accept(ls, pa.as_mut_ptr(), &mut palen) };
711
+ if acc != INVALID {
712
+ let one = b"x";
713
+ unsafe { send(acc, one.as_ptr(), 1, 0) };
714
+ unsafe { closesocket(acc) };
715
+ }
716
+ unsafe { closesocket(ls) };
717
+ }))
718
+ }
719
+
720
+ /// Probe (j): connect to a push server, then recv one byte — either
721
+ /// through the statically-linked `recv` or through ws2_32's, resolved
722
+ /// at runtime via GetProcAddress.
723
+ #[cfg(windows)]
724
+ fn ffi_recv_probe(dynamic: bool) -> Result<(), String> {
725
+ const AF_INET: i32 = 2;
726
+ const INVALID: usize = usize::MAX;
727
+ type RecvFn = unsafe extern "system" fn(usize, *mut u8, i32, i32) -> i32;
728
+
729
+ let recv_fn: RecvFn = if dynamic {
730
+ // SAFETY: GetModuleHandleA/GetProcAddress on a system DLL.
731
+ let ws2 = unsafe { GetModuleHandleA(b"ws2_32.dll\0".as_ptr()) };
732
+ if ws2.is_null() {
733
+ return Err("GetModuleHandleA(ws2_32) null".into());
734
+ }
735
+ let sym = unsafe { GetProcAddress(ws2, b"recv\0".as_ptr()) };
736
+ if sym.is_null() {
737
+ return Err("GetProcAddress(recv) null".into());
738
+ }
739
+ // SAFETY: the symbol is ws2_32's recv with the winsock ABI.
740
+ unsafe { std::mem::transmute::<*mut core::ffi::c_void, RecvFn>(sym) }
741
+ } else {
742
+ // SAFETY: the statically-linked recv declaration.
743
+ unsafe { std::mem::transmute::<unsafe extern "C" fn(usize, *mut u8, i32, i32) -> i32, RecvFn>(recv) }
744
+ };
745
+
746
+ let port = PUSH_PORT.with(|p| p.get());
747
+ if port == 0 {
748
+ return Err("no push listener port".into());
749
+ }
750
+
751
+ // SAFETY: plain winsock calls below.
752
+ let s = unsafe { socket(AF_INET, 1, 6) };
753
+ if s == INVALID {
754
+ return Err(format!("create {}", unsafe { WSAGetLastError() }));
755
+ }
756
+ let mut peer = [0u8; 16];
757
+ peer[0..2].copy_from_slice(&(AF_INET as u16).to_ne_bytes());
758
+ peer[2..4].copy_from_slice(&port.to_be_bytes());
759
+ peer[4..8].copy_from_slice(&[127, 0, 0, 1]);
760
+ if unsafe { connect(s, peer.as_ptr(), 16) } != 0 {
761
+ let err = unsafe { WSAGetLastError() };
762
+ unsafe { closesocket(s) };
763
+ return Err(format!("connect wsagetlasterror={err}"));
764
+ }
765
+ let mut buf = [0u8; 1];
766
+ let n = unsafe { recv_fn(s, buf.as_mut_ptr(), 1, 0) };
767
+ let err = if n < 0 { unsafe { WSAGetLastError() } } else { 0 };
768
+ unsafe { closesocket(s) };
769
+ if n < 0 {
770
+ Err(format!("recv wsagetlasterror={err}"))
771
+ } else {
772
+ Ok(())
773
+ }
774
+ }
775
+
776
+ #[cfg(windows)]
777
+ thread_local! {
778
+ static PUSH_PORT: std::cell::Cell<u16> = const { std::cell::Cell::new(0) };
779
+ }
780
+
781
+ /// No-op on non-Windows targets (the Ruby method exists everywhere so
782
+ /// spec code need not branch).
783
+ #[cfg(not(windows))]
784
+ pub fn probe_on_demand(_tag: &str) {}