Skip to content

Commit b24ee68

Browse files
lostnetjasnell
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 606da2b commit b24ee68

7 files changed

+530
-1
lines changed

doc/api/dgram.md

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

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

lib/dgram.js

+15
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,21 @@ Socket.prototype.setMulticastLoopback = function(arg) {
562562
};
563563

564564

565+
Socket.prototype.setMulticastInterface = function(interfaceAddress) {
566+
this._healthCheck();
567+
568+
if (typeof interfaceAddress !== 'string') {
569+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
570+
'interfaceAddress',
571+
'string');
572+
}
573+
574+
const err = this._handle.setMulticastInterface(interfaceAddress);
575+
if (err) {
576+
throw errnoException(err, 'setMulticastInterface');
577+
}
578+
};
579+
565580
Socket.prototype.addMembership = function(multicastAddress,
566581
interfaceAddress) {
567582
this._healthCheck();

src/udp_wrap.cc

+17
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ void UDPWrap::Initialize(Local<Object> target,
130130
GetSockOrPeerName<UDPWrap, uv_udp_getsockname>);
131131
env->SetProtoMethod(t, "addMembership", AddMembership);
132132
env->SetProtoMethod(t, "dropMembership", DropMembership);
133+
env->SetProtoMethod(t, "setMulticastInterface", SetMulticastInterface);
133134
env->SetProtoMethod(t, "setMulticastTTL", SetMulticastTTL);
134135
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
135136
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
@@ -238,6 +239,22 @@ X(SetMulticastLoopback, uv_udp_set_multicast_loop)
238239

239240
#undef X
240241

242+
void UDPWrap::SetMulticastInterface(const FunctionCallbackInfo<Value>& args) {
243+
UDPWrap* wrap;
244+
ASSIGN_OR_RETURN_UNWRAP(&wrap,
245+
args.Holder(),
246+
args.GetReturnValue().Set(UV_EBADF));
247+
248+
CHECK_EQ(args.Length(), 1);
249+
CHECK(args[0]->IsString());
250+
251+
Utf8Value iface(args.GetIsolate(), args[0]);
252+
253+
const char* iface_cstr = *iface;
254+
255+
int err = uv_udp_set_multicast_interface(&wrap->handle_, iface_cstr);
256+
args.GetReturnValue().Set(err);
257+
}
241258

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

src/udp_wrap.h

+2
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

+3-1
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)