Browse Source

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.
v0.7.4-release
Joe Walnes 14 years ago
committed by Ryan Dahl
parent
commit
df6e497793
  1. 31
      doc/api/dgram.markdown
  2. 42
      lib/dgram.js
  3. 109
      src/node_net.cc
  4. 4
      test/simple/test-dgram-multicast.js

31
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.

42
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;

109
src/node_net.cc

@ -1372,14 +1372,19 @@ static Handle<Value> SetTTL(const Arguments& args) {
}
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<void*>(&newttl),
sizeof(newttl));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
}
@ -1394,6 +1399,102 @@ static Handle<Value> SetTTL(const Arguments& args) {
return scope.Close(Integer::New(newttl));
}
static Handle<Value> 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<void*>(&newttl), sizeof(newttl));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
} else {
return scope.Close(Integer::New(newttl));
}
}
static Handle<Value> 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<void*>(&flags), sizeof(flags));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
} else {
return scope.Close(Integer::New(flags));
}
}
static Handle<Value> 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<void*>(&mreq), sizeof(mreq));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
} else {
return Undefined();
}
}
static Handle<Value> AddMembership(const Arguments& args) {
return SetMembership(args, IP_ADD_MEMBERSHIP);
}
static Handle<Value> 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<Object> 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);

4
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();
}
});

Loading…
Cancel
Save