From cdf1f6842072cbfdfd3815394f0ca2835238d13f Mon Sep 17 00:00:00 2001 From: Will Young Date: Sat, 23 Jul 2016 22:30:58 +0200 Subject: [PATCH 01/11] dgram: setMulticastInterface() outgoing interface selection 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().) --- doc/api/dgram.md | 17 +++++++++++ lib/dgram.js | 13 +++++++++ src/udp_wrap.cc | 20 +++++++++++++ src/udp_wrap.h | 2 ++ test/parallel/test-dgram-multicast-setIf.js | 31 +++++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 test/parallel/test-dgram-multicast-setIf.js diff --git a/doc/api/dgram.md b/doc/api/dgram.md index c3fcfb0528ffa4..ffa1d6ec1e8151 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -370,6 +370,23 @@ 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) + + +* `multicastInterface` {String} + +Sets the outgoing multicast interface using the provided IP. For IPv6, the addresses should use explicit scope to indicate the interface, as in the following example: + +```js +const s = dgram.createSocket('udp6'); +s.bind(1234, () => { + s.setMulticastInterface('::%eth1'); +}); + +``` + ### socket.setMulticastLoopback(flag) * `multicastInterface` {String} @@ -380,9 +380,9 @@ added: v7.0.0 Sets the outgoing multicast interface using the provided IP. For IPv6, the addresses should use explicit scope to indicate the interface, as in the following example: ```js -const s = dgram.createSocket('udp6'); -s.bind(1234, () => { - s.setMulticastInterface('::%eth1'); +const socket = dgram.createSocket('udp6'); +socket.bind(1234, () => { + socket.setMulticastInterface('::%eth1'); }); ``` diff --git a/lib/dgram.js b/lib/dgram.js index 0ebd3ec38e5dec..0e7e3405f127db 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -570,7 +570,7 @@ Socket.prototype.setMulticastInterface = function(interfaceAddress) { throw new Error('interface address must be specified'); } - var err = this._handle.setMulticastInterface(interfaceAddress); + const err = this._handle.setMulticastInterface(interfaceAddress); if (err) { throw errnoException(err, 'setMulticastInterface'); } diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index fecad9ada97c86..0124944384ea24 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -251,7 +251,7 @@ void UDPWrap::SetMulticastInterface(const FunctionCallbackInfo& args) { const char* iface_cstr = *iface; if (args[0]->IsUndefined() || args[0]->IsNull()) { - iface_cstr = nullptr; + iface_cstr = nullptr; } int err = uv_udp_set_multicast_interface(&wrap->handle_, diff --git a/test/internet/test-dgram-multicast-multi-process.js b/test/internet/test-dgram-multicast-multi-process.js index a7854652eab49f..e1017e2a26e5d8 100644 --- a/test/internet/test-dgram-multicast-multi-process.js +++ b/test/internet/test-dgram-multicast-multi-process.js @@ -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'), @@ -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() { @@ -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, diff --git a/test/parallel/test-dgram-multicast-setIf.js b/test/parallel/test-dgram-multicast-setIf.js index cca688cbf73449..10798a43610ef6 100644 --- a/test/parallel/test-dgram-multicast-setIf.js +++ b/test/parallel/test-dgram-multicast-setIf.js @@ -2,25 +2,31 @@ const common = require('../common'); const assert = require('assert'); const dgram = require('dgram'); -const socket = dgram.createSocket('udp4'); -const socket2 = dgram.createSocket('udp4'); -socket.bind(0); -socket.on('listening', common.mustCall(() => { - socket.setMulticastInterface('0.0.0.0'); +{ + const socket = dgram.createSocket('udp4'); - //close the socket - socket.close(); -})); + socket.bind(0); + socket.on('listening', common.mustCall(() => { + socket.setMulticastInterface('0.0.0.0'); -socket2.bind(0); -socket2.on('listening', common.mustCall(() => { - - //Try to set with an invalid Interface address (wrong address class) - assert.throws(common.mustCall(() => { - socket2.setMulticastInterface('::'); + // close the socket + socket.close(); })); +} + +{ + const socket = dgram.createSocket('udp4'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { - //close the socket - socket2.close(); -})); + // Try to set with an invalid interface address (wrong address class) + assert.throws(common.mustCall(() => { + socket.setMulticastInterface('::'); + })); + + // close the socket + socket.close(); + })); +} From d3a7c2e216e0851a027a25b44555fec9519aa905 Mon Sep 17 00:00:00 2001 From: Will Young Date: Fri, 5 Aug 2016 15:36:41 +0200 Subject: [PATCH 04/11] dgram: setMulticastInterface() outgoing interface selection (review 3) --- doc/api/dgram.md | 41 +++++- lib/dgram.js | 4 +- src/udp_wrap.cc | 6 +- .../test-dgram-multicast-set-interface.js | 122 ++++++++++++++++++ test/parallel/test-dgram-multicast-setIf.js | 32 ----- 5 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 test/parallel/test-dgram-multicast-set-interface.js delete mode 100644 test/parallel/test-dgram-multicast-setIf.js diff --git a/doc/api/dgram.md b/doc/api/dgram.md index c2922c6fc078c7..2bfc706950a190 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -377,16 +377,55 @@ added: REPLACEME * `multicastInterface` {String} -Sets the outgoing multicast interface using the provided IP. For IPv6, the addresses should use explicit scope to indicate the interface, as in the following example: +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 ("IP%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 cause a *Not running* Error Exception. + +If `multicastInterface` can not be parsed into an IP then an *EINVAL* Error Exception 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 system provided errors such as *EADDRNOTAVAIL* or *EPROTONOSUP* are indicated in the Error Exception. + +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 Families' 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)