Skip to content

Commit 4ae0afb

Browse files
lostnetmcollina
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 <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent cba206f commit 4ae0afb

7 files changed

Lines changed: 530 additions & 1 deletion

File tree

doc/api/dgram.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,84 @@ added: v0.6.9
386386
Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP
387387
packets may be sent to a local interface's broadcast address.
388388

389+
### socket.setMulticastInterface(multicastInterface)
390+
<!-- YAML
391+
added: REPLACEME
392+
-->
393+
394+
* `multicastInterface` {String}
395+
396+
*Note: All references to scope in this section are refering to
397+
[IPv6 Zone Indices][], which are defined by [RFC 4007][]. In string form, an IP
398+
with a scope index is written as `'IP%scope'` where scope is an interface name or
399+
interface number.*
400+
401+
Sets the default outgoing multicast interface of the socket to a chosen
402+
interface or back to system interface selection. The `multicastInterface` must
403+
be a valid string representation of an IP from the socket's family.
404+
405+
For IPv4 sockets, this should be the IP configured for the desired physical
406+
interface. All packets sent to multicast on the socket will be sent on the
407+
interface determined by the most recent successful use of this call.
408+
409+
For IPv6 sockets, `multicastInterface` should include a scope to indicate the
410+
interface as in the examples that follow. In IPv6, individual `send` calls can
411+
also use explicit scope in addresses, so only packets sent to a multicast
412+
address without specifying an explicit scope are affected by the most recent
413+
successful use of this call.
414+
415+
#### Examples: IPv6 Outgoing Multicast Interface
416+
417+
On most systems, where scope format uses the interface name:
418+
419+
```js
420+
const socket = dgram.createSocket('udp6');
421+
422+
socket.bind(1234, () => {
423+
socket.setMulticastInterface('::%eth1');
424+
});
425+
```
426+
427+
On Windows, where scope format uses an interface number:
428+
429+
```js
430+
const socket = dgram.createSocket('udp6');
431+
432+
socket.bind(1234, () => {
433+
socket.setMulticastInterface('::%2');
434+
});
435+
```
436+
437+
#### Example: IPv4 Outgoing Multicast Interface
438+
All systems use an IP of the host on the desired physical interface:
439+
```js
440+
const socket = dgram.createSocket('udp4');
441+
442+
socket.bind(1234, () => {
443+
socket.setMulticastInterface('10.0.0.2');
444+
});
445+
```
446+
447+
#### Call Results
448+
449+
A call on a socket that is not ready to send or no longer open may throw a *Not
450+
running* [`Error`][].
451+
452+
If `multicastInterface` can not be parsed into an IP then an *EINVAL*
453+
[`System Error`][] is thrown.
454+
455+
On IPv4, if `multicastInterface` is a valid address but does not match any
456+
interface, or if the address does not match the family then
457+
a [`System Error`][] such as `EADDRNOTAVAIL` or `EPROTONOSUP` is thrown.
458+
459+
On IPv6, most errors with specifying or omiting scope will result in the socket
460+
continuing to use (or returning to) the system's default interface selection.
461+
462+
A socket's address family's ANY address (IPv4 `'0.0.0.0'` or IPv6 `'::'`) can be
463+
used to return control of the sockets default outgoing interface to the system
464+
for future multicast packets.
465+
466+
389467
### socket.setMulticastLoopback(flag)
390468
<!-- YAML
391469
added: v0.3.8
@@ -553,4 +631,7 @@ and `udp6` sockets). The bound address and port can be retrieved using
553631
[`socket.address().address`]: #dgram_socket_address
554632
[`socket.address().port`]: #dgram_socket_address
555633
[`socket.bind()`]: #dgram_socket_bind_port_address_callback
634+
[`System Error`]: errors.html#errors_class_system_error
556635
[byte length]: buffer.html#buffer_class_method_buffer_bytelength_string_encoding
636+
[IPv6 Zone Indices]: https://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices
637+
[RFC 4007]: https://tools.ietf.org/html/rfc4007

lib/dgram.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,21 @@ Socket.prototype.setMulticastLoopback = function(arg) {
587587
};
588588

589589

590+
Socket.prototype.setMulticastInterface = function(interfaceAddress) {
591+
this._healthCheck();
592+
593+
if (typeof interfaceAddress !== 'string') {
594+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
595+
'interfaceAddress',
596+
'string');
597+
}
598+
599+
const err = this._handle.setMulticastInterface(interfaceAddress);
600+
if (err) {
601+
throw errnoException(err, 'setMulticastInterface');
602+
}
603+
};
604+
590605
Socket.prototype.addMembership = function(multicastAddress,
591606
interfaceAddress) {
592607
this._healthCheck();

src/udp_wrap.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ void UDPWrap::Initialize(Local<Object> target,
131131
GetSockOrPeerName<UDPWrap, uv_udp_getsockname>);
132132
env->SetProtoMethod(t, "addMembership", AddMembership);
133133
env->SetProtoMethod(t, "dropMembership", DropMembership);
134+
env->SetProtoMethod(t, "setMulticastInterface", SetMulticastInterface);
134135
env->SetProtoMethod(t, "setMulticastTTL", SetMulticastTTL);
135136
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
136137
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
@@ -277,6 +278,22 @@ X(SetMulticastLoopback, uv_udp_set_multicast_loop)
277278

278279
#undef X
279280

281+
void UDPWrap::SetMulticastInterface(const FunctionCallbackInfo<Value>& args) {
282+
UDPWrap* wrap;
283+
ASSIGN_OR_RETURN_UNWRAP(&wrap,
284+
args.Holder(),
285+
args.GetReturnValue().Set(UV_EBADF));
286+
287+
CHECK_EQ(args.Length(), 1);
288+
CHECK(args[0]->IsString());
289+
290+
Utf8Value iface(args.GetIsolate(), args[0]);
291+
292+
const char* iface_cstr = *iface;
293+
294+
int err = uv_udp_set_multicast_interface(&wrap->handle_, iface_cstr);
295+
args.GetReturnValue().Set(err);
296+
}
280297

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

src/udp_wrap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class UDPWrap: public HandleWrap {
5050
static void RecvStop(const v8::FunctionCallbackInfo<v8::Value>& args);
5151
static void AddMembership(const v8::FunctionCallbackInfo<v8::Value>& args);
5252
static void DropMembership(const v8::FunctionCallbackInfo<v8::Value>& args);
53+
static void SetMulticastInterface(
54+
const v8::FunctionCallbackInfo<v8::Value>& args);
5355
static void SetMulticastTTL(const v8::FunctionCallbackInfo<v8::Value>& args);
5456
static void SetMulticastLoopback(
5557
const v8::FunctionCallbackInfo<v8::Value>& args);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const assert = require('assert');
2929
const dgram = require('dgram');
3030
const fork = require('child_process').fork;
3131
const LOCAL_BROADCAST_HOST = '224.0.0.114';
32+
const LOCAL_HOST_IFADDR = '0.0.0.0';
3233
const TIMEOUT = common.platformTimeout(5000);
3334
const messages = [
3435
Buffer.from('First message to send'),
@@ -159,6 +160,7 @@ if (process.argv[2] !== 'child') {
159160
sendSocket.setBroadcast(true);
160161
sendSocket.setMulticastTTL(1);
161162
sendSocket.setMulticastLoopback(true);
163+
sendSocket.setMulticastInterface(LOCAL_HOST_IFADDR);
162164
});
163165

164166
sendSocket.on('close', function() {
@@ -198,7 +200,7 @@ if (process.argv[2] === 'child') {
198200
});
199201

200202
listenSocket.on('listening', function() {
201-
listenSocket.addMembership(LOCAL_BROADCAST_HOST);
203+
listenSocket.addMembership(LOCAL_BROADCAST_HOST, LOCAL_HOST_IFADDR);
202204

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

0 commit comments

Comments
 (0)