From df6e497793381cbeca3e3bc09c007d8fe4473b74 Mon Sep 17 00:00:00 2001 From: Joe Walnes Date: Mon, 24 Jan 2011 07:39:54 +0000 Subject: [PATCH] dgram: setMulticastTTL, setMulticastLoopback and addMembership. These are options needed for real-world multicasting. Implementation notes: - POSIX only. - IPv4 only (IPv6 multicast is a tricky beast). - Didn't update tests, because it can't effectively be demonstrated on localhost only. --- doc/api/dgram.markdown | 31 ++++++++ lib/dgram.js | 42 +++++++++++ src/node_net.cc | 111 +++++++++++++++++++++++++++- test/simple/test-dgram-multicast.js | 4 + 4 files changed, 185 insertions(+), 3 deletions(-) diff --git a/doc/api/dgram.markdown b/doc/api/dgram.markdown index ef7aaa2c9a..a1c1997515 100644 --- a/doc/api/dgram.markdown +++ b/doc/api/dgram.markdown @@ -168,3 +168,34 @@ probes or when multicasting. The argument to `setTTL()` is a number of hops between 1 and 255. The default on most systems is 64. +### dgram.setMulticastTTL(ttl) + +Sets the `IP_MULTICAST_TTL` socket option. TTL stands for "Time to Live," but in this +context it specifies the number of IP hops that a packet is allowed to go through, +specifically for multicast traffic. Each router or gateway that forwards a packet +decrements the TTL. If the TTL is decremented to 0 by a router, it will not be forwarded. + +The argument to `setMulticastTTL()` is a number of hops between 0 and 255. The default on most +systems is 64. + +### dgram.setMulticastLoopback(flag) + +Sets or clears the `IP_MULTICAST_LOOP` socket option. When this option is set, multicast +packets will also be received on the local interface. + +### dgram.addMembership(multicastAddress, [multicastInterface]) + +Tells the kernel to join a multicast group with `IP_ADD_MEMBERSHIP` socket option. + +If `multicastAddress` is not specified, the OS will try to add membership to all valid +interfaces. + +### dgram.dropMembership(multicastAddress, [multicastInterface]) + +Opposite of `dropMembership` - tells the kernel to leave a multicast group with +`IP_DROP_MEMBERSHIP` socket option. This is automatically called by the kernel +when the socket is closed or process terminates, so most apps will never need to call +this. + +If `multicastAddress` is not specified, the OS will try to add membership to all valid +interfaces. diff --git a/lib/dgram.js b/lib/dgram.js index 510827cc91..89c47f8d5c 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -165,6 +165,48 @@ Socket.prototype.setTTL = function(arg) { } }; +Socket.prototype.setMulticastTTL = function(arg) { + var newttl = parseInt(arg); + + if (newttl >= 0 && newttl < 256) { + return binding.setMulticastTTL(this.fd, newttl); + } else { + throw new Error('New MulticastTTL must be between 0 and 255'); + } +}; + +Socket.prototype.setMulticastLoopback = function(arg) { + if (arg) { + return binding.setMulticastLoopback(this.fd, 1); + } else { + return binding.setMulticastLoopback(this.fd, 0); + } +}; + +Socket.prototype.addMembership = function(multicastAddress, + multicastInterface) { + var self = this; + dnsLookup(this.type, multicastAddress, function(err, ip, addressFamily) { + if (err) { // DNS error + self.emit('error', err); + return; + } + binding.addMembership(self.fd, multicastAddress, multicastInterface); + }); +}; + +Socket.prototype.dropMembership = function(multicastAddress, + multicastInterface) { + var self = this; + dnsLookup(this.type, multicastAddress, function(err, ip, addressFamily) { + if (err) { // DNS error + self.emit('error', err); + return; + } + binding.dropMembership(self.fd, multicastAddress, multicastInterface); + }); +}; + // translate arguments from JS API into C++ API, possibly after DNS lookup Socket.prototype.send = function(buffer, offset, length) { var self = this; diff --git a/src/node_net.cc b/src/node_net.cc index 7bb0528292..e7a24ff0e5 100644 --- a/src/node_net.cc +++ b/src/node_net.cc @@ -1366,20 +1366,25 @@ static Handle SetTTL(const Arguments& args) { FD_ARG(args[0]); - if (! args[1]->IsInt32()) { + if (!args[1]->IsInt32()) { return ThrowException(Exception::TypeError( String::New("Argument must be a number"))); } newttl = args[1]->Int32Value(); + if (newttl < 1 || newttl > 255) { return ThrowException(Exception::TypeError( String::New("new TTL must be between 1 and 255"))); } #ifdef __POSIX__ - if (0 > setsockopt(fd, IPPROTO_IP, IP_TTL, (void *)&newttl, - sizeof(newttl))) { + int r = setsockopt(fd, + IPPROTO_IP, + IP_TTL, + reinterpret_cast(&newttl), + sizeof(newttl)); + if (r < 0) { return ThrowException(ErrnoException(errno, "setsockopt")); } @@ -1394,6 +1399,102 @@ static Handle SetTTL(const Arguments& args) { return scope.Close(Integer::New(newttl)); } +static Handle SetMulticastTTL(const Arguments& args) { + HandleScope scope; + + if (args.Length() != 2) { + return ThrowException(Exception::TypeError( + String::New("Takes exactly two arguments: fd, new MulticastTTL"))); + } + + FD_ARG(args[0]); + + if (!args[1]->IsInt32()) { + return ThrowException(Exception::TypeError( + String::New("Argument must be a number"))); + } + + int newttl = args[1]->Int32Value(); + if (newttl < 0 || newttl > 255) { + return ThrowException(Exception::TypeError( + String::New("new MulticastTTL must be between 0 and 255"))); + } + + int r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, + reinterpret_cast(&newttl), sizeof(newttl)); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "setsockopt")); + } else { + return scope.Close(Integer::New(newttl)); + } +} + +static Handle SetMulticastLoopback(const Arguments& args) { + int flags, r; + HandleScope scope; + + FD_ARG(args[0]) + + flags = args[1]->IsFalse() ? 0 : 1; + r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, + reinterpret_cast(&flags), sizeof(flags)); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "setsockopt")); + } else { + return scope.Close(Integer::New(flags)); + } +} + +static Handle SetMembership(const Arguments& args, int socketOption) { + HandleScope scope; + + if (args.Length() < 2 || args.Length() > 3) { + return ThrowException(Exception::TypeError( + String::New("Takes arguments: fd, multicast group, multicast address"))); + } + + FD_ARG(args[0]); + + struct ip_mreq mreq; + memset(&mreq, 0, sizeof(mreq)); + + // Multicast address (arg[1]) + String::Utf8Value multicast_address(args[1]->ToString()); + if (inet_pton( + AF_INET, *multicast_address, &(mreq.imr_multiaddr.s_addr)) <= 0) { + return ErrnoException(errno, "inet_pton", "Invalid multicast address"); + } + + // Interface address (arg[2] - optional, default:INADDR_ANY) + if (args.Length() < 3 || !args[2]->IsString()) { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } else { + String::Utf8Value multicast_interface(args[2]->ToString()); + if (inet_pton( + AF_INET, *multicast_interface, &(mreq.imr_interface.s_addr)) <= 0) { + return ErrnoException(errno, "inet_pton", "Invalid multicast interface"); + } + } + + int r = setsockopt(fd, IPPROTO_IP, socketOption, + reinterpret_cast(&mreq), sizeof(mreq)); + + if (r < 0) { + return ThrowException(ErrnoException(errno, "setsockopt")); + } else { + return Undefined(); + } +} + +static Handle AddMembership(const Arguments& args) { + return SetMembership(args, IP_ADD_MEMBERSHIP); +} + +static Handle DropMembership(const Arguments& args) { + return SetMembership(args, IP_DROP_MEMBERSHIP); +} // // G E T A D D R I N F O @@ -1625,6 +1726,10 @@ void InitNet(Handle target) { NODE_SET_METHOD(target, "setBroadcast", SetBroadcast); NODE_SET_METHOD(target, "setTTL", SetTTL); NODE_SET_METHOD(target, "setKeepAlive", SetKeepAlive); + NODE_SET_METHOD(target, "setMulticastTTL", SetMulticastTTL); + NODE_SET_METHOD(target, "setMulticastLoopback", SetMulticastLoopback); + NODE_SET_METHOD(target, "addMembership", AddMembership); + NODE_SET_METHOD(target, "dropMembership", DropMembership); NODE_SET_METHOD(target, "getsockname", GetSockName); NODE_SET_METHOD(target, "getpeername", GetPeerName); NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); diff --git a/test/simple/test-dgram-multicast.js b/test/simple/test-dgram-multicast.js index fd691b4599..2246ccf0ac 100644 --- a/test/simple/test-dgram-multicast.js +++ b/test/simple/test-dgram-multicast.js @@ -22,6 +22,8 @@ sendSocket.on('close', function() { }); sendSocket.setBroadcast(true); +sendSocket.setMulticastTTL(1); +sendSocket.setMulticastLoopback(true); var i = 0; @@ -47,12 +49,14 @@ var listener_count = 0; function mkListener() { var receivedMessages = []; var listenSocket = dgram.createSocket('udp4'); + listenSocket.addMembership(LOCAL_BROADCAST_HOST); listenSocket.on('message', function(buf, rinfo) { console.error('received %s from %j', util.inspect(buf.toString()), rinfo); receivedMessages.push(buf); if (receivedMessages.length == sendMessages.length) { + listenSocket.dropMembership(LOCAL_BROADCAST_HOST); listenSocket.close(); } });