Skip to content
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
27 changes: 13 additions & 14 deletions packages/bun-usockets/src/bsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,7 @@ static int bsd_set_reuseaddr(LIBUS_SOCKET_DESCRIPTOR listenFd) {
}

static int bsd_set_reuseport(LIBUS_SOCKET_DESCRIPTOR listenFd) {
#if defined(__linux__)
// Among Bun's supported platforms, only Linux does load balancing with SO_REUSEPORT.
#if defined(SO_REUSEPORT) && !defined(_WIN32)
const int one = 1;
return setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
#else
Expand Down Expand Up @@ -1232,6 +1231,14 @@ LIBUS_SOCKET_DESCRIPTOR bsd_create_udp_socket(const char *host, int port, int op
}

if (bsd_set_reuse(listenFd, options) != 0) {
if (err != NULL) {
#ifdef _WIN32
*err = WSAGetLastError();
#else
*err = errno;
#endif
}
bsd_close_socket(listenFd);
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
Comment thread
robobun marked this conversation as resolved.
}
Comment thread
robobun marked this conversation as resolved.
Expand All @@ -1254,23 +1261,15 @@ LIBUS_SOCKET_DESCRIPTOR bsd_create_udp_socket(const char *host, int port, int op

int enabled = 1;
if (setsockopt(listenFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enabled, sizeof(enabled)) == -1) {
if (errno == 92) {
if (setsockopt(listenFd, IPPROTO_IP, IP_PKTINFO, &enabled, sizeof(enabled)) != 0) {
//printf("Error setting IPv4 pktinfo!\n");
}
} else {
//printf("Error setting IPv6 pktinfo!\n");
if (errno == ENOPROTOOPT || errno == EINVAL) {
setsockopt(listenFd, IPPROTO_IP, IP_PKTINFO, &enabled, sizeof(enabled));
}
}

/* These are used for getting the ECN */
if (setsockopt(listenFd, IPPROTO_IPV6, IPV6_RECVTCLASS, &enabled, sizeof(enabled)) == -1) {
if (errno == 92) {
if (setsockopt(listenFd, IPPROTO_IP, IP_RECVTOS, &enabled, sizeof(enabled)) != 0) {
//printf("Error setting IPv4 ECN!\n");
}
} else {
//printf("Error setting IPv6 ECN!\n");
if (errno == ENOPROTOOPT || errno == EINVAL) {
setsockopt(listenFd, IPPROTO_IP, IP_RECVTOS, &enabled, sizeof(enabled));
}
}

Expand Down
1 change: 1 addition & 0 deletions src/errno/windows_errno.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@ pub const SystemErrno = enum(u16) {
Win32Error.DIR_NOT_EMPTY => SystemErrno.ENOTEMPTY,
Win32Error.WSAENOTSOCK => SystemErrno.ENOTSOCK,
Win32Error.NOT_SUPPORTED => SystemErrno.ENOTSUP,
Win32Error.WSAEOPNOTSUPP => SystemErrno.ENOTSUP,
Win32Error.BROKEN_PIPE => SystemErrno.EPIPE,
Win32Error.ACCESS_DENIED => SystemErrno.EPERM,
Win32Error.PRIVILEGE_NOT_HELD => SystemErrno.EPERM,
Expand Down
268 changes: 268 additions & 0 deletions test/regression/issue/28083.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows } from "harness";

describe("dgram implicit bind on send", () => {
Comment thread
robobun marked this conversation as resolved.
test("reusePort option works with dgram sockets", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const dgram = require("dgram");
const isWin = process.platform === "win32";

// Create two sockets with reusePort: true bound to the same port
const s1 = dgram.createSocket({ type: "udp4", reusePort: !isWin });
const s2 = dgram.createSocket({ type: "udp4", reusePort: !isWin });

s1.bind(0, "127.0.0.1", () => {
const port = s1.address().port;
process.stdout.write("s1:" + port + "\\n");

if (isWin) {
// On Windows, reusePort is not supported - just verify s1 works
process.stdout.write("ok\\n");
s1.close();
s2.close();
return;
}

// On non-Windows, second socket should be able to bind to the same port
s2.bind(port, "127.0.0.1", () => {
const port2 = s2.address().port;
process.stdout.write("s2:" + port2 + "\\n");
process.stdout.write("same:" + String(port === port2) + "\\n");
s1.close();
s2.close();
});

s2.on("error", (err) => {
process.stdout.write("s2error:" + err.message + "\\n");
s1.close();
s2.close();
process.exit(1);
});
});

s1.on("error", (err) => {
process.stdout.write("s1error:" + err.message + "\\n");
s1.close();
process.exit(1);
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);

if (isWindows) {
expect(stdout).toContain("ok\n");
} else {
expect(stdout).toContain("same:true\n");
}
expect(exitCode).toBe(0);
});

test("send() without bind() implicitly binds and delivers the message", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const dgram = require("dgram");
const receiver = dgram.createSocket("udp4");
const sender = dgram.createSocket("udp4");

receiver.bind(0, "127.0.0.1", () => {
const port = receiver.address().port;

receiver.on("message", (msg, rinfo) => {
process.stdout.write(msg.toString() + "\\n");
process.stdout.write(String(rinfo.port > 0) + "\\n");
sender.close();
receiver.close();
});

sender.send(Buffer.from("hello"), 0, 5, port, "127.0.0.1", (err) => {
if (err) {
process.stdout.write("ERROR:" + err.message + "\\n");
process.exit(1);
}
const addr = sender.address();
process.stdout.write(addr.address + "\\n");
process.stdout.write(String(addr.port > 0) + "\\n");
});
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);

expect(stdout).toBe("0.0.0.0\ntrue\nhello\ntrue\n");
expect(exitCode).toBe(0);
});

test("listening event fires after implicit bind", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const dgram = require("dgram");
const socket = dgram.createSocket("udp4");
const target = dgram.createSocket("udp4");
let listeningFired = false;

socket.on("listening", () => {
listeningFired = true;
});

target.bind(0, "127.0.0.1", () => {
const port = target.address().port;
socket.send(Buffer.from("test"), 0, 4, port, "127.0.0.1", (err) => {
process.nextTick(() => {
process.stdout.write(String(listeningFired) + "\\n");
socket.close();
target.close();
});
});
Comment thread
robobun marked this conversation as resolved.
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);

expect(stdout).toBe("true\n");
expect(exitCode).toBe(0);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test("multiple sends without bind() are all delivered", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const dgram = require("dgram");
const receiver = dgram.createSocket("udp4");
const sender = dgram.createSocket("udp4");
const messages = [];

receiver.bind(0, "127.0.0.1", () => {
const port = receiver.address().port;

receiver.on("message", (msg) => {
messages.push(msg.toString());
if (messages.length === 3) {
messages.sort();
process.stdout.write(messages.join(",") + "\\n");
sender.close();
receiver.close();
}
});

sender.send(Buffer.from("aaa"), 0, 3, port, "127.0.0.1");
sender.send(Buffer.from("bbb"), 0, 3, port, "127.0.0.1");
sender.send(Buffer.from("ccc"), 0, 3, port, "127.0.0.1");
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);

expect(stdout).toBe("aaa,bbb,ccc\n");
expect(exitCode).toBe(0);
});

test("send(buffer, port, address, callback) short form works without bind", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const dgram = require("dgram");
const receiver = dgram.createSocket("udp4");
const sender = dgram.createSocket("udp4");

receiver.bind(0, "127.0.0.1", () => {
const port = receiver.address().port;

receiver.on("message", (msg) => {
process.stdout.write(msg.toString() + "\\n");
sender.close();
receiver.close();
});

sender.send(Buffer.from("short-form"), port, "127.0.0.1", (err) => {
if (err) {
process.stdout.write("ERROR:" + err.message + "\\n");
process.exit(1);
}
});
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);

expect(stdout).toBe("short-form\n");
expect(exitCode).toBe(0);
});

test("bidirectional communication works with implicit bind (k-rpc pattern)", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const dgram = require("dgram");
const receiver = dgram.createSocket("udp4");
const sender = dgram.createSocket("udp4");

sender.on("message", (msg) => {
process.stdout.write("reply:" + msg.toString() + "\\n");
sender.close();
receiver.close();
});

receiver.bind(0, "127.0.0.1", () => {
const port = receiver.address().port;

receiver.on("message", (msg, rinfo) => {
process.stdout.write("request:" + msg.toString() + "\\n");
receiver.send(Buffer.from("pong"), 0, 4, rinfo.port, rinfo.address);
});

sender.send(Buffer.from("ping"), 0, 4, port, "127.0.0.1");
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]);

expect(stdout).toBe("request:ping\nreply:pong\n");
expect(exitCode).toBe(0);
});
});
Loading