Skip to content

Commit

Permalink
dgram: added setMulticastInterface()
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
lostnet authored and mcollina committed Sep 14, 2017
1 parent cba206f commit 4ae0afb
Show file tree
Hide file tree
Showing 7 changed files with 530 additions and 1 deletion.
81 changes: 81 additions & 0 deletions doc/api/dgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,84 @@ added: v0.6.9
Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP
packets may be sent to a local interface's broadcast address.

### socket.setMulticastInterface(multicastInterface)
<!-- YAML
added: REPLACEME
-->

* `multicastInterface` {String}

*Note: All references to scope in this section are refering to
[IPv6 Zone Indices][], which are defined by [RFC 4007][]. In string form, an IP
with a scope index is written as `'IP%scope'` where scope is an interface name or
interface number.*

Sets the default outgoing multicast interface of the socket to a chosen
interface or back to system interface selection. The `multicastInterface` must
be a valid string representation of an IP from the socket's family.

For IPv4 sockets, this should be the IP configured for the desired physical
interface. All packets sent to multicast on the socket will be sent on the
interface determined by the most recent successful use of this call.

For IPv6 sockets, `multicastInterface` should include a scope to indicate the
interface as in the examples that follow. In IPv6, individual `send` calls can
also use explicit scope in addresses, so only packets sent to a multicast
address without specifying an explicit scope are affected by the most recent
successful use of this call.

#### Examples: IPv6 Outgoing Multicast Interface

On most systems, where scope format uses the interface name:

```js
const socket = dgram.createSocket('udp6');

socket.bind(1234, () => {
socket.setMulticastInterface('::%eth1');
});
```

On Windows, where scope format uses an interface number:

```js
const socket = dgram.createSocket('udp6');

socket.bind(1234, () => {
socket.setMulticastInterface('::%2');
});
```

#### Example: IPv4 Outgoing Multicast Interface
All systems use an IP of the host on the desired physical interface:
```js
const socket = dgram.createSocket('udp4');

socket.bind(1234, () => {
socket.setMulticastInterface('10.0.0.2');
});
```

#### Call Results

A call on a socket that is not ready to send or no longer open may throw a *Not
running* [`Error`][].

If `multicastInterface` can not be parsed into an IP then an *EINVAL*
[`System Error`][] is thrown.

On IPv4, if `multicastInterface` is a valid address but does not match any
interface, or if the address does not match the family then
a [`System Error`][] such as `EADDRNOTAVAIL` or `EPROTONOSUP` is thrown.

On IPv6, most errors with specifying or omiting scope will result in the socket
continuing to use (or returning to) the system's default interface selection.

A socket's address family's ANY address (IPv4 `'0.0.0.0'` or IPv6 `'::'`) can be
used to return control of the sockets default outgoing interface to the system
for future multicast packets.


### socket.setMulticastLoopback(flag)
<!-- YAML
added: v0.3.8
Expand Down Expand Up @@ -553,4 +631,7 @@ and `udp6` sockets). The bound address and port can be retrieved using
[`socket.address().address`]: #dgram_socket_address
[`socket.address().port`]: #dgram_socket_address
[`socket.bind()`]: #dgram_socket_bind_port_address_callback
[`System Error`]: errors.html#errors_class_system_error
[byte length]: buffer.html#buffer_class_method_buffer_bytelength_string_encoding
[IPv6 Zone Indices]: https://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices
[RFC 4007]: https://tools.ietf.org/html/rfc4007
15 changes: 15 additions & 0 deletions lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,21 @@ Socket.prototype.setMulticastLoopback = function(arg) {
};


Socket.prototype.setMulticastInterface = function(interfaceAddress) {
this._healthCheck();

if (typeof interfaceAddress !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'interfaceAddress',
'string');
}

const err = this._handle.setMulticastInterface(interfaceAddress);
if (err) {
throw errnoException(err, 'setMulticastInterface');
}
};

Socket.prototype.addMembership = function(multicastAddress,
interfaceAddress) {
this._healthCheck();
Expand Down
17 changes: 17 additions & 0 deletions src/udp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ void UDPWrap::Initialize(Local<Object> target,
GetSockOrPeerName<UDPWrap, uv_udp_getsockname>);
env->SetProtoMethod(t, "addMembership", AddMembership);
env->SetProtoMethod(t, "dropMembership", DropMembership);
env->SetProtoMethod(t, "setMulticastInterface", SetMulticastInterface);
env->SetProtoMethod(t, "setMulticastTTL", SetMulticastTTL);
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
Expand Down Expand Up @@ -277,6 +278,22 @@ X(SetMulticastLoopback, uv_udp_set_multicast_loop)

#undef X

void UDPWrap::SetMulticastInterface(const FunctionCallbackInfo<Value>& args) {
UDPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap,
args.Holder(),
args.GetReturnValue().Set(UV_EBADF));

CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());

Utf8Value iface(args.GetIsolate(), args[0]);

const char* iface_cstr = *iface;

int err = uv_udp_set_multicast_interface(&wrap->handle_, iface_cstr);
args.GetReturnValue().Set(err);
}

void UDPWrap::SetMembership(const FunctionCallbackInfo<Value>& args,
uv_membership membership) {
Expand Down
2 changes: 2 additions & 0 deletions src/udp_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class UDPWrap: public HandleWrap {
static void RecvStop(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddMembership(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DropMembership(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMulticastInterface(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMulticastTTL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMulticastLoopback(
const v8::FunctionCallbackInfo<v8::Value>& args);
Expand Down
4 changes: 3 additions & 1 deletion test/internet/test-dgram-multicast-multi-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const assert = require('assert');
const dgram = require('dgram');
const fork = require('child_process').fork;
const LOCAL_BROADCAST_HOST = '224.0.0.114';
const LOCAL_HOST_IFADDR = '0.0.0.0';
const TIMEOUT = common.platformTimeout(5000);
const messages = [
Buffer.from('First message to send'),
Expand Down Expand Up @@ -159,6 +160,7 @@ if (process.argv[2] !== 'child') {
sendSocket.setBroadcast(true);
sendSocket.setMulticastTTL(1);
sendSocket.setMulticastLoopback(true);
sendSocket.setMulticastInterface(LOCAL_HOST_IFADDR);
});

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

listenSocket.on('listening', function() {
listenSocket.addMembership(LOCAL_BROADCAST_HOST);
listenSocket.addMembership(LOCAL_BROADCAST_HOST, LOCAL_HOST_IFADDR);

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

0 comments on commit 4ae0afb

Please sign in to comment.