Skip to content

Commit 8f2e52a

Browse files
lostnetMylesBorins
authored andcommitted
dgram: added setMulticastInterface()
Add wrapper for uv's uv_udp_set_multicast_interface which provides the sender side mechanism to explicitly select an interface. The equivalent receiver side mechanism is the optional 2nd argument of addMembership(). PR-URL: #7855 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent ce13639 commit 8f2e52a

7 files changed

+528
-1
lines changed

doc/api/dgram.md

+81
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,84 @@ added: v0.6.9
336336
Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP
337337
packets may be sent to a local interface's broadcast address.
338338

339+
### socket.setMulticastInterface(multicastInterface)
340+
<!-- YAML
341+
added: REPLACEME
342+
-->
343+
344+
* `multicastInterface` {String}
345+
346+
*Note: All references to scope in this section are refering to
347+
[IPv6 Zone Indices][], which are defined by [RFC 4007][]. In string form, an IP
348+
with a scope index is written as `'IP%scope'` where scope is an interface name or
349+
interface number.*
350+
351+
Sets the default outgoing multicast interface of the socket to a chosen
352+
interface or back to system interface selection. The `multicastInterface` must
353+
be a valid string representation of an IP from the socket's family.
354+
355+
For IPv4 sockets, this should be the IP configured for the desired physical
356+
interface. All packets sent to multicast on the socket will be sent on the
357+
interface determined by the most recent successful use of this call.
358+
359+
For IPv6 sockets, `multicastInterface` should include a scope to indicate the
360+
interface as in the examples that follow. In IPv6, individual `send` calls can
361+
also use explicit scope in addresses, so only packets sent to a multicast
362+
address without specifying an explicit scope are affected by the most recent
363+
successful use of this call.
364+
365+
#### Examples: IPv6 Outgoing Multicast Interface
366+
367+
On most systems, where scope format uses the interface name:
368+
369+
```js
370+
const socket = dgram.createSocket('udp6');
371+
372+
socket.bind(1234, () => {
373+
socket.setMulticastInterface('::%eth1');
374+
});
375+
```
376+
377+
On Windows, where scope format uses an interface number:
378+
379+
```js
380+
const socket = dgram.createSocket('udp6');
381+
382+
socket.bind(1234, () => {
383+
socket.setMulticastInterface('::%2');
384+
});
385+
```
386+
387+
#### Example: IPv4 Outgoing Multicast Interface
388+
All systems use an IP of the host on the desired physical interface:
389+
```js
390+
const socket = dgram.createSocket('udp4');
391+
392+
socket.bind(1234, () => {
393+
socket.setMulticastInterface('10.0.0.2');
394+
});
395+
```
396+
397+
#### Call Results
398+
399+
A call on a socket that is not ready to send or no longer open may throw a *Not
400+
running* [`Error`][].
401+
402+
If `multicastInterface` can not be parsed into an IP then an *EINVAL*
403+
[`System Error`][] is thrown.
404+
405+
On IPv4, if `multicastInterface` is a valid address but does not match any
406+
interface, or if the address does not match the family then
407+
a [`System Error`][] such as `EADDRNOTAVAIL` or `EPROTONOSUP` is thrown.
408+
409+
On IPv6, most errors with specifying or omiting scope will result in the socket
410+
continuing to use (or returning to) the system's default interface selection.
411+
412+
A socket's address family's ANY address (IPv4 `'0.0.0.0'` or IPv6 `'::'`) can be
413+
used to return control of the sockets default outgoing interface to the system
414+
for future multicast packets.
415+
416+
339417
### socket.setMulticastLoopback(flag)
340418
<!-- YAML
341419
added: v0.3.8
@@ -490,4 +568,7 @@ and `udp6` sockets). The bound address and port can be retrieved using
490568
[`socket.address().address`]: #dgram_socket_address
491569
[`socket.address().port`]: #dgram_socket_address
492570
[`socket.bind()`]: #dgram_socket_bind_port_address_callback
571+
[`System Error`]: errors.html#errors_class_system_error
493572
[byte length]: buffer.html#buffer_class_method_buffer_bytelength_string_encoding
573+
[IPv6 Zone Indices]: https://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices
574+
[RFC 4007]: https://tools.ietf.org/html/rfc4007

lib/dgram.js

+13
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,19 @@ Socket.prototype.setMulticastLoopback = function(arg) {
493493
};
494494

495495

496+
Socket.prototype.setMulticastInterface = function(interfaceAddress) {
497+
this._healthCheck();
498+
499+
if (typeof interfaceAddress !== 'string') {
500+
throw new TypeError('"interfaceAddress" argument must be a string');
501+
}
502+
503+
const err = this._handle.setMulticastInterface(interfaceAddress);
504+
if (err) {
505+
throw errnoException(err, 'setMulticastInterface');
506+
}
507+
};
508+
496509
Socket.prototype.addMembership = function(multicastAddress,
497510
interfaceAddress) {
498511
this._healthCheck();

src/udp_wrap.cc

+17
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ void UDPWrap::Initialize(Local<Object> target,
9797
GetSockOrPeerName<UDPWrap, uv_udp_getsockname>);
9898
env->SetProtoMethod(t, "addMembership", AddMembership);
9999
env->SetProtoMethod(t, "dropMembership", DropMembership);
100+
env->SetProtoMethod(t, "setMulticastInterface", SetMulticastInterface);
100101
env->SetProtoMethod(t, "setMulticastTTL", SetMulticastTTL);
101102
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
102103
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
@@ -208,6 +209,22 @@ X(SetMulticastLoopback, uv_udp_set_multicast_loop)
208209

209210
#undef X
210211

212+
void UDPWrap::SetMulticastInterface(const FunctionCallbackInfo<Value>& args) {
213+
UDPWrap* wrap;
214+
ASSIGN_OR_RETURN_UNWRAP(&wrap,
215+
args.Holder(),
216+
args.GetReturnValue().Set(UV_EBADF));
217+
218+
CHECK_EQ(args.Length(), 1);
219+
CHECK(args[0]->IsString());
220+
221+
Utf8Value iface(args.GetIsolate(), args[0]);
222+
223+
const char* iface_cstr = *iface;
224+
225+
int err = uv_udp_set_multicast_interface(&wrap->handle_, iface_cstr);
226+
args.GetReturnValue().Set(err);
227+
}
211228

212229
void UDPWrap::SetMembership(const FunctionCallbackInfo<Value>& args,
213230
uv_membership membership) {

src/udp_wrap.h

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class UDPWrap: public HandleWrap {
2828
static void RecvStop(const v8::FunctionCallbackInfo<v8::Value>& args);
2929
static void AddMembership(const v8::FunctionCallbackInfo<v8::Value>& args);
3030
static void DropMembership(const v8::FunctionCallbackInfo<v8::Value>& args);
31+
static void SetMulticastInterface(
32+
const v8::FunctionCallbackInfo<v8::Value>& args);
3133
static void SetMulticastTTL(const v8::FunctionCallbackInfo<v8::Value>& args);
3234
static void SetMulticastLoopback(
3335
const v8::FunctionCallbackInfo<v8::Value>& args);

test/internet/test-dgram-multicast-multi-process.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const assert = require('assert');
88
const dgram = require('dgram');
99
const fork = require('child_process').fork;
1010
const LOCAL_BROADCAST_HOST = '224.0.0.114';
11+
const LOCAL_HOST_IFADDR = '0.0.0.0';
1112
const TIMEOUT = common.platformTimeout(5000);
1213
const messages = [
1314
Buffer.from('First message to send'),
@@ -136,6 +137,7 @@ if (process.argv[2] !== 'child') {
136137
sendSocket.setBroadcast(true);
137138
sendSocket.setMulticastTTL(1);
138139
sendSocket.setMulticastLoopback(true);
140+
sendSocket.setMulticastInterface(LOCAL_HOST_IFADDR);
139141
});
140142

141143
sendSocket.on('close', function() {
@@ -175,7 +177,7 @@ if (process.argv[2] === 'child') {
175177
});
176178

177179
listenSocket.on('listening', function() {
178-
listenSocket.addMembership(LOCAL_BROADCAST_HOST);
180+
listenSocket.addMembership(LOCAL_BROADCAST_HOST, LOCAL_HOST_IFADDR);
179181

180182
listenSocket.on('message', function(buf, rinfo) {
181183
console.error('[CHILD] %s received "%s" from %j', process.pid,

0 commit comments

Comments
 (0)