Skip to content

Commit

Permalink
dgram: setMulticastTTL, setMulticastLoopback and addMembership.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
joewalnes authored and ry committed Feb 1, 2011
1 parent 3e5b568 commit df6e497
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 3 deletions.
31 changes: 31 additions & 0 deletions doc/api/dgram.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 42 additions & 0 deletions lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
111 changes: 108 additions & 3 deletions src/node_net.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1366,20 +1366,25 @@ static Handle<Value> 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<void*>(&newttl),
sizeof(newttl));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
}

Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions test/simple/test-dgram-multicast.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ sendSocket.on('close', function() {
});

sendSocket.setBroadcast(true);
sendSocket.setMulticastTTL(1);
sendSocket.setMulticastLoopback(true);

var i = 0;

Expand All @@ -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();
}
});
Expand Down

0 comments on commit df6e497

Please sign in to comment.