diff --git a/packages/bun-usockets/src/bsd.c b/packages/bun-usockets/src/bsd.c index 732d79559a..6ecd0f144f 100644 --- a/packages/bun-usockets/src/bsd.c +++ b/packages/bun-usockets/src/bsd.c @@ -1231,6 +1231,18 @@ LIBUS_SOCKET_DESCRIPTOR bsd_create_udp_socket(const char *host, int port, int op return LIBUS_SOCKET_ERROR; } +#ifdef _WIN32 + /* Disable WSAECONNRESET errors from ICMP "Port Unreachable" messages. + * Without this, sending to a closed port causes subsequent recv calls to fail, + * which would close the entire UDP socket. This matches Node.js/libuv behavior. */ + { + BOOL bNewBehavior = FALSE; + DWORD dwBytesReturned = 0; + WSAIoctl(listenFd, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), + NULL, 0, &dwBytesReturned, NULL, NULL); + } +#endif + if (port != 0) { /* Should this also go for UDP? */ int enabled = 1; diff --git a/test/regression/issue/26166.test.ts b/test/regression/issue/26166.test.ts new file mode 100644 index 0000000000..f1edcb299e --- /dev/null +++ b/test/regression/issue/26166.test.ts @@ -0,0 +1,59 @@ +import { udpSocket } from "bun"; +import { expect, test } from "bun:test"; + +// https://github.com/oven-sh/bun/issues/26166 +// On Windows, ICMP "Port Unreachable" messages cause WSAECONNRESET errors which +// would close the UDP socket entirely. This test verifies the socket stays alive. +test("UDP socket survives sending to unreachable port", async () => { + let receivedCount = 0; + const receivedData: string[] = []; + const { resolve, reject, promise } = Promise.withResolvers(); + + // Create a server socket + const server = await udpSocket({ + socket: { + data(socket, data, port, address) { + receivedCount++; + receivedData.push(new TextDecoder().decode(data)); + if (receivedCount === 2) { + resolve(); + } + }, + }, + }); + + // Create a client + const client = await udpSocket({}); + + // Send first message to the server + client.send(Buffer.from("message1"), server.port, "127.0.0.1"); + + // Wait a bit for message to be received + await Bun.sleep(50); + + // Now send to an unreachable port (a port that's definitely not listening) + // This would trigger an ICMP Port Unreachable on Windows + const unreachablePort = 59999; + server.send(Buffer.from("this will fail"), unreachablePort, "127.0.0.1"); + + // Wait a bit for the ICMP error to potentially be processed + await Bun.sleep(100); + + // Send second message - this should still work if the socket survived + client.send(Buffer.from("message2"), server.port, "127.0.0.1"); + + // Wait for messages with timeout + const timeout = setTimeout(() => reject(new Error("Timeout waiting for messages")), 2000); + + try { + await promise; + clearTimeout(timeout); + } finally { + server.close(); + client.close(); + } + + expect(receivedCount).toBe(2); + expect(receivedData).toContain("message1"); + expect(receivedData).toContain("message2"); +});