Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disable SIO_UDP_CONNRESET behaviour for UDP #67

Merged
merged 2 commits into from
Jul 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions network.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,28 @@ const windows = struct {
ws2_32.WSA_FLAG_OVERLAPPED,
);

// Disable SIO_UDP_CONNRESET behaviour for UDP
//
// This resolves an issue where "recvfrom" can return a WSAECONNRESET error if a previous send
// call failed and resulted in an ICMP Port Unreachable message.
// https://github.com/MasterQ32/zig-network/issues/66
if (socket_type == std.os.SOCK.DGRAM) {
// This was based off the following Go code:
// https://github.com/golang/go/blob/5c154986094bcc2fb28909cc5f01c9ba1dd9ddd4/src/internal/poll/fd_windows.go#L338
const IOC_IN = 0x80000000;
const IOC_VENDOR = 0x18000000;
const SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
var flag = &[_]u8{ 0, 0, 0, 0 };
var ret: u32 = 0;
switch (ws2_32.WSAIoctl(sock, SIO_UDP_CONNRESET, flag.ptr, flag.len, null, 0, &ret, null, null)) {
0 => {},
ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) {
else => |err| return unexpectedWSAError(err),
},
else => unreachable,
}
}

if (std.io.is_async and std.event.Loop.instance != null) {
const loop = std.event.Loop.instance.?;
_ = try CreateIoCompletionPort(sock, loop.os_data.io_port, undefined, undefined);
Expand Down
63 changes: 60 additions & 3 deletions testsuite.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const network = @import("network");
const expect = std.testing.expect;

Expand Down Expand Up @@ -35,9 +36,7 @@ test "UDP timeout" {
var sock = try network.Socket.create(.ipv4, .udp);
defer sock.close();
try sock.connect(.{
.address = .{
.ipv4 = network.Address.IPv4.init(1, 1, 1, 1)
},
.address = .{ .ipv4 = network.Address.IPv4.init(1, 1, 1, 1) },
.port = 53,
});
try sock.setReadTimeout(3000000); // 3 seconds
Expand All @@ -63,3 +62,61 @@ test "IPv4 parse" {
try std.testing.expectEqual(make(127, 33, 10, 20), try parse("127.33.2580"));
try std.testing.expectEqual(make(255, 255, 255, 255), try parse("255.255.255.255"));
}

// https://github.com/MasterQ32/zig-network/issues/66
test "Windows-only, fix UDP WSAECONNRESET error when calling recvfrom after send failure" {
if (builtin.os.tag != .windows) {
// Skip if not Windows
return;
}
try network.init();
defer network.deinit();

// setup sockets
var server_sock = try network.Socket.create(.ipv4, .udp);
defer server_sock.close();
try server_sock.setReadTimeout(25 * std.time.us_per_ms);
try server_sock.bind(.{
.address = network.Address{ .ipv4 = network.Address.IPv4.any },
.port = 1234,
});
var client_sock = try network.Socket.create(.ipv4, .udp);
var client_sock_closed = false;
defer {
if (!client_sock_closed) {
client_sock.close();
client_sock_closed = true;
}
}
try client_sock.connect(.{
.address = .{ .ipv4 = network.Address.IPv4.init(127, 0, 0, 1) },
.port = 1234,
});
try client_sock.setReadTimeout(25 * std.time.us_per_ms);

// setup buffer
const buflen = 32;
var msg: [buflen]u8 = undefined;

// send and read data back and forth
_ = try client_sock.send("connect_info");
const recvFrom = try server_sock.receiveFrom(msg[0..buflen]);
// close the socket to force the WSAECONNRESET error when we send below
client_sock.close();
client_sock_closed = true;
// If we do not disable SIO_UDP_CONNRESET then a failed "send" will be caught when the
// next "recvfrom" function is called.
//
// MDN: https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom
// WSAECONNRESET: "On a UDP-datagram socket this error indicates a previous send operation resulted
// in an ICMP Port Unreachable message."
_ = try server_sock.sendTo(recvFrom.sender, "data");
_ = server_sock.receiveFrom(msg[0..buflen]) catch |err| switch (err) {
error.WouldBlock => {
// fallthrough, expect this to timeout
},
else => {
return err;
},
};
}