Skip to content

Commit e6dc8e7

Browse files
committed
setMulticastInterface
1 parent 95654de commit e6dc8e7

File tree

8 files changed

+188
-9
lines changed

8 files changed

+188
-9
lines changed

packages/bun-usockets/src/bsd.c

+18
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,24 @@ int bsd_socket_multicast_loopback(LIBUS_SOCKET_DESCRIPTOR fd, int enabled) {
353353
return setsockopt_6_or_4(fd, IP_MULTICAST_LOOP, IPV6_MULTICAST_LOOP, &enabled, sizeof(enabled));
354354
}
355355

356+
int bsd_socket_multicast_interface(LIBUS_SOCKET_DESCRIPTOR fd, const struct sockaddr_storage *addr) {
357+
if (addr->ss_family == AF_INET) {
358+
const struct sockaddr_in *addr4 = (const struct sockaddr_in*) addr;
359+
return setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &addr4->sin_addr, sizeof(addr4->sin_addr));
360+
}
361+
362+
if (addr->ss_family == AF_INET6) {
363+
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*) addr;
364+
return setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &addr6->sin6_scope_id, sizeof(addr6->sin6_scope_id));
365+
}
366+
367+
#ifdef _WIN32
368+
WSASetLastError(WSAEINVAL);
369+
#endif
370+
errno = EINVAL;
371+
return -1;
372+
}
373+
356374
static int bsd_socket_ttl_any(LIBUS_SOCKET_DESCRIPTOR fd, int ttl, int ipv4, int ipv6) {
357375
if (ttl < 1 || ttl > 255) {
358376
#ifdef _WIN32

packages/bun-usockets/src/internal/networking/bsd.h

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ int bsd_socket_broadcast(LIBUS_SOCKET_DESCRIPTOR fd, int enabled);
182182
int bsd_socket_ttl_unicast(LIBUS_SOCKET_DESCRIPTOR fd, int ttl);
183183
int bsd_socket_ttl_multicast(LIBUS_SOCKET_DESCRIPTOR fd, int ttl);
184184
int bsd_socket_multicast_loopback(LIBUS_SOCKET_DESCRIPTOR fd, int enabled);
185+
int bsd_socket_multicast_interface(LIBUS_SOCKET_DESCRIPTOR fd, const struct sockaddr_storage *addr);
185186
int bsd_socket_keepalive(LIBUS_SOCKET_DESCRIPTOR fd, int on, unsigned int delay);
186187
void bsd_socket_flush(LIBUS_SOCKET_DESCRIPTOR fd);
187188
LIBUS_SOCKET_DESCRIPTOR bsd_create_socket(int domain, int type, int protocol, int *err);

packages/bun-usockets/src/udp.c

+4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ int us_udp_socket_set_multicast_loopback(struct us_udp_socket_t *s, int enabled)
130130
return bsd_socket_multicast_loopback(us_poll_fd(&s->p), enabled);
131131
}
132132

133+
int us_udp_socket_set_multicast_interface(struct us_udp_socket_t *s, const struct sockaddr_storage *addr) {
134+
return bsd_socket_multicast_interface(us_poll_fd(&s->p), addr);
135+
}
136+
133137
struct us_udp_socket_t *us_create_udp_socket(
134138
struct us_loop_t *loop,
135139
void (*data_cb)(struct us_udp_socket_t *, void *, int),

src/bun.js/api/bun/udp_socket.zig

+26-1
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,31 @@ pub const UDPSocket = struct {
437437
return arguments[0];
438438
}
439439

440+
pub fn setMulticastInterface(this: *This, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
441+
if (this.closed) {
442+
return globalThis.throwValue(bun.JSC.Maybe(void).errnoSys(@as(i32, @intCast(@intFromEnum(std.posix.E.BADF))), .setsockopt).?.toJS(globalThis));
443+
}
444+
445+
const arguments = callframe.arguments();
446+
if (arguments.len < 1) {
447+
return globalThis.throwInvalidArguments("Expected 1 argument, got {}", .{arguments.len});
448+
}
449+
450+
var addr: std.posix.sockaddr.storage = undefined;
451+
452+
if (!parseAddr(this, globalThis, JSC.jsNumber(0), arguments[0], &addr)) {
453+
return .false;
454+
}
455+
456+
const res = this.socket.setMulticastInterface(&addr);
457+
458+
if (getUSError(res, .setsockopt, true)) |err| {
459+
return globalThis.throwValue(err.toJS(globalThis));
460+
}
461+
462+
return .true;
463+
}
464+
440465
pub fn setTTL(this: *This, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
441466
return setAnyTTL(this, globalThis, callframe, uws.udp.Socket.setUnicastTTL);
442467
}
@@ -645,6 +670,7 @@ pub const UDPSocket = struct {
645670
addr4.family = std.posix.AF.INET;
646671
} else {
647672
var addr6: *std.posix.sockaddr.in6 = @ptrCast(storage);
673+
addr6.scope_id = 0;
648674

649675
if (str.indexOfAsciiChar('%')) |percent| {
650676
if (percent + 1 < str.length()) {
@@ -676,7 +702,6 @@ pub const UDPSocket = struct {
676702
if (inet_pton(std.posix.AF.INET6, address_slice.ptr, &addr6.addr) == 1) {
677703
addr6.port = htons(@truncate(port));
678704
addr6.family = std.posix.AF.INET6;
679-
addr6.scope_id = 0;
680705
} else {
681706
return false;
682707
}

src/bun.js/api/sockets.classes.ts

+4
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,10 @@ export default [
335335
fn: "setMulticastLoopback",
336336
length: 1,
337337
},
338+
setMulticastInterface: {
339+
fn: "setMulticastInterface",
340+
length: 1,
341+
},
338342
},
339343
klass: {},
340344
}),

src/deps/uws.zig

+5
Original file line numberDiff line numberDiff line change
@@ -4551,6 +4551,10 @@ pub const udp = struct {
45514551
pub fn setMulticastLoopback(this: *This, enabled: bool) c_int {
45524552
return us_udp_socket_set_multicast_loopback(this, @intCast(@intFromBool(enabled)));
45534553
}
4554+
4555+
pub fn setMulticastInterface(this: *This, iface: *const std.posix.sockaddr.storage) c_int {
4556+
return us_udp_socket_set_multicast_interface(this, iface);
4557+
}
45544558
};
45554559

45564560
extern fn us_create_udp_socket(loop: ?*Loop, data_cb: *const fn (*udp.Socket, *PacketBuffer, c_int) callconv(.C) void, drain_cb: *const fn (*udp.Socket) callconv(.C) void, close_cb: *const fn (*udp.Socket) callconv(.C) void, host: [*c]const u8, port: c_ushort, options: c_int, err: ?*c_int, user_data: ?*anyopaque) ?*udp.Socket;
@@ -4567,6 +4571,7 @@ pub const udp = struct {
45674571
extern fn us_udp_socket_set_ttl_unicast(socket: ?*udp.Socket, ttl: c_int) c_int;
45684572
extern fn us_udp_socket_set_ttl_multicast(socket: ?*udp.Socket, ttl: c_int) c_int;
45694573
extern fn us_udp_socket_set_multicast_loopback(socket: ?*udp.Socket, enabled: c_int) c_int;
4574+
extern fn us_udp_socket_set_multicast_interface(socket: ?*udp.Socket, iface: *const std.posix.sockaddr.storage) c_int;
45704575

45714576
pub const PacketBuffer = opaque {
45724577
const This = @This();

src/js/node/dgram.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -784,15 +784,14 @@ Socket.prototype.setMulticastLoopback = function (arg) {
784784
};
785785

786786
Socket.prototype.setMulticastInterface = function (interfaceAddress) {
787-
throwNotImplemented("setMulticastInterface", 10381);
788-
/*
789-
validateString(interfaceAddress, 'interfaceAddress');
790-
791-
const err = this[kStateSymbol].handle.setMulticastInterface(interfaceAddress);
792-
if (err) {
793-
throw new ErrnoException(err, 'setMulticastInterface');
787+
validateString(interfaceAddress, "interfaceAddress");
788+
const handle = this[kStateSymbol].handle;
789+
if (!handle?.socket) {
790+
throw $ERR_SOCKET_DGRAM_NOT_RUNNING();
791+
}
792+
if (!handle.socket.setMulticastInterface(interfaceAddress)) {
793+
throw new Error("setMulticastInterface EINVAL");
794794
}
795-
*/
796795
};
797796

798797
Socket.prototype.addMembership = function (multicastAddress, interfaceAddress) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const dgram = require('dgram');
5+
6+
{
7+
const socket = dgram.createSocket('udp4');
8+
9+
socket.bind(0);
10+
socket.on('listening', common.mustCall(() => {
11+
// Explicitly request default system selection
12+
socket.setMulticastInterface('0.0.0.0');
13+
14+
socket.close();
15+
}));
16+
}
17+
18+
{
19+
const socket = dgram.createSocket('udp4');
20+
21+
socket.bind(0);
22+
socket.on('listening', common.mustCall(() => {
23+
socket.close(common.mustCall(() => {
24+
assert.throws(() => { socket.setMulticastInterface('0.0.0.0'); },
25+
/Not running/i);
26+
}));
27+
}));
28+
}
29+
30+
{
31+
const socket = dgram.createSocket('udp4');
32+
33+
socket.bind(0);
34+
socket.on('listening', common.mustCall(() => {
35+
// Try to set with an invalid interfaceAddress (wrong address class)
36+
//
37+
// This operation succeeds on some platforms, throws `EINVAL` on some
38+
// platforms, and throws `ENOPROTOOPT` on others. This is unpleasant, but
39+
// we should at least test for it.
40+
try {
41+
socket.setMulticastInterface('::');
42+
} catch (e) {
43+
assert(e.code === 'EINVAL' || e.code === 'ENOPROTOOPT');
44+
}
45+
46+
socket.close();
47+
}));
48+
}
49+
50+
{
51+
const socket = dgram.createSocket('udp4');
52+
53+
socket.bind(0);
54+
socket.on('listening', common.mustCall(() => {
55+
// Try to set with an invalid interfaceAddress (wrong Type)
56+
assert.throws(() => {
57+
socket.setMulticastInterface(1);
58+
}, /TypeError/);
59+
60+
socket.close();
61+
}));
62+
}
63+
64+
{
65+
const socket = dgram.createSocket('udp4');
66+
67+
socket.bind(0);
68+
socket.on('listening', common.mustCall(() => {
69+
// Try to set with an invalid interfaceAddress (non-unicast)
70+
assert.throws(() => {
71+
socket.setMulticastInterface('224.0.0.2');
72+
}, /Error/);
73+
74+
socket.close();
75+
}));
76+
}
77+
78+
// If IPv6 is not supported, skip the rest of the test. However, don't call
79+
// common.skip(), which calls process.exit() while there is outstanding
80+
// common.mustCall() activity.
81+
if (!common.hasIPv6)
82+
return;
83+
84+
{
85+
const socket = dgram.createSocket('udp6');
86+
87+
socket.bind(0);
88+
socket.on('listening', common.mustCall(() => {
89+
// Try to set with an invalid interfaceAddress ('undefined')
90+
assert.throws(() => {
91+
socket.setMulticastInterface(String(undefined));
92+
}, /EINVAL/);
93+
94+
socket.close();
95+
}));
96+
}
97+
98+
{
99+
const socket = dgram.createSocket('udp6');
100+
101+
socket.bind(0);
102+
socket.on('listening', common.mustCall(() => {
103+
// Try to set with an invalid interfaceAddress ('')
104+
assert.throws(() => {
105+
socket.setMulticastInterface('');
106+
}, /EINVAL/);
107+
108+
socket.close();
109+
}));
110+
}
111+
112+
{
113+
const socket = dgram.createSocket('udp6');
114+
115+
socket.bind(0);
116+
socket.on('listening', common.mustCall(() => {
117+
// Using lo0 for OsX, on all other OSes, an invalid Scope gets
118+
// turned into #0 (default selection) which is also acceptable.
119+
socket.setMulticastInterface('::%lo0');
120+
121+
socket.close();
122+
}));
123+
}

0 commit comments

Comments
 (0)