Created
April 22, 2026 16:34
-
-
Save marler8997/5ab75f9e90f68edef1cecaa1d1979060 to your computer and use it in GitHub Desktop.
Zig 0.16.0 Missing Windows Network Errors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! Minimal repro for three NTSTATUS codes that stdlib's AFD-based socket | |
| //! functions should map but currently fall through to `windows.unexpectedStatus`: | |
| //! | |
| //! netConnectIpWindows / AFD.CONNECT: CONNECTION_REFUSED (0xc0000236) | |
| //! netReadWindows / AFD.RECEIVE: CONNECTION_RESET (0xc000020d) | |
| //! netWriteWindows / AFD.SEND: CONNECTION_RESET (0xc000020d) | |
| //! | |
| //! On an unpatched stdlib each scenario prints the raw NTSTATUS via | |
| //! std.options.unexpected_error_tracing and surfaces `error.Unexpected`. | |
| //! On a patched stdlib each scenario surfaces a specific Zig error | |
| //! (ConnectionRefused / ConnectionResetByPeer). | |
| const std = @import("std"); | |
| pub fn main(init: std.process.Init) !void { | |
| const io = init.io; | |
| try scenario1_connectRefused(io); | |
| try scenario2_readAfterReset(io); | |
| try scenario3_writeAfterReset(io); | |
| } | |
| fn banner(comptime name: []const u8) void { | |
| std.log.info("", .{}); | |
| std.log.info("=== {s} ===", .{name}); | |
| } | |
| // AFD.CONNECT to an unused localhost port -> CONNECTION_REFUSED. | |
| fn scenario1_connectRefused(io: std.Io) !void { | |
| banner("connect to unused localhost port (expect CONNECTION_REFUSED)"); | |
| // Bind and immediately close a socket to discover a port nothing listens on. | |
| const port = blk: { | |
| var addr: std.Io.net.IpAddress = .{ .ip4 = .loopback(0) }; | |
| var probe = try std.Io.net.IpAddress.listen(&addr, io, .{}); | |
| defer probe.deinit(io); | |
| break :blk probe.socket.address.ip4.port; | |
| }; | |
| var target: std.Io.net.IpAddress = .{ .ip4 = .loopback(port) }; | |
| if (std.Io.net.IpAddress.connect(&target, io, .{ .mode = .stream })) |stream| { | |
| stream.close(io); | |
| std.log.err(" connect unexpectedly succeeded", .{}); | |
| } else |err| { | |
| std.log.info(" connect -> error.{s}", .{@errorName(err)}); | |
| } | |
| } | |
| // AFD.RECEIVE on a socket whose peer sent RST -> CONNECTION_RESET. | |
| // Trick: server writes more than the client reads; when client closes with | |
| // unread data in its recv buffer, Windows' TCP stack sends RST. (SO_LINGER=0 | |
| // would be simpler but AFD doesn't expose it.) | |
| fn scenario2_readAfterReset(io: std.Io) !void { | |
| banner("read after peer RST (expect CONNECTION_RESET)"); | |
| var listen_addr: std.Io.net.IpAddress = .{ .ip4 = .loopback(0) }; | |
| var server = try std.Io.net.IpAddress.listen(&listen_addr, io, .{ .reuse_address = true }); | |
| defer server.deinit(io); | |
| const thread = try std.Thread.spawn(.{}, rstClient, .{ io, server.socket.address.ip4.port }); | |
| var stream = try server.accept(io); | |
| defer stream.close(io); | |
| var write_buf: [64]u8 = undefined; | |
| var writer = stream.writer(io, &write_buf); | |
| try writer.interface.writeAll(&[_]u8{0xaa} ** 1024); | |
| try writer.interface.flush(); | |
| thread.join(); | |
| var buf: [100]u8 = undefined; | |
| var bufs: [1][]u8 = .{&buf}; | |
| if (io.vtable.netRead(io.userdata, stream.socket.handle, &bufs)) |n| { | |
| std.log.info(" read -> n={}", .{n}); | |
| } else |err| { | |
| std.log.info(" read -> error.{s}", .{@errorName(err)}); | |
| } | |
| } | |
| // AFD.SEND on a socket whose peer sent RST -> CONNECTION_RESET. | |
| fn scenario3_writeAfterReset(io: std.Io) !void { | |
| banner("write after peer RST (expect CONNECTION_RESET)"); | |
| var listen_addr: std.Io.net.IpAddress = .{ .ip4 = .loopback(0) }; | |
| var server = try std.Io.net.IpAddress.listen(&listen_addr, io, .{ .reuse_address = true }); | |
| defer server.deinit(io); | |
| const thread = try std.Thread.spawn(.{}, rstClient, .{ io, server.socket.address.ip4.port }); | |
| var stream = try server.accept(io); | |
| defer stream.close(io); | |
| var write_buf: [64]u8 = undefined; | |
| var writer = stream.writer(io, &write_buf); | |
| try writer.interface.writeAll(&[_]u8{0xaa} ** 1024); | |
| try writer.interface.flush(); | |
| thread.join(); | |
| // Drain any pending RST so the NEXT write clearly sees the reset connection. | |
| var drain: [4]u8 = undefined; | |
| var drain_bufs: [1][]u8 = .{&drain}; | |
| _ = io.vtable.netRead(io.userdata, stream.socket.handle, &drain_bufs) catch {}; | |
| const payload = &[_]u8{0xbb} ** 64; | |
| const data: []const []const u8 = &.{payload}; | |
| if (io.vtable.netWrite(io.userdata, stream.socket.handle, &.{}, data, 1)) |n| { | |
| std.log.info(" netWrite -> n={}", .{n}); | |
| } else |err| { | |
| std.log.info(" netWrite -> error.{s}", .{@errorName(err)}); | |
| } | |
| } | |
| // Peer that connects, reads a single byte (proving the server's data arrived | |
| // in its kernel recv buffer), then closes — leaving the remaining bytes | |
| // unread. Windows sends RST from this side on close. | |
| fn rstClient(io: std.Io, port: u16) void { | |
| rstClientInner(io, port) catch |e| std.log.err("rstClient: {t}", .{e}); | |
| } | |
| fn rstClientInner(io: std.Io, port: u16) !void { | |
| var addr: std.Io.net.IpAddress = .{ .ip4 = .loopback(port) }; | |
| var stream = try std.Io.net.IpAddress.connect(&addr, io, .{ .mode = .stream }); | |
| defer stream.close(io); | |
| var read_buf: [1]u8 = undefined; | |
| var reader = stream.reader(io, &read_buf); | |
| _ = reader.interface.takeByte() catch return reader.err.?; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment