Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IPv6 multicast with Node.js

I am experimenting with IPv6 UDP multicast over a VPN. I have tried the following code:

const dgram = require('dgram');

let sock = dgram.createSocket('udp6', {
  reuseAddr: true
});

sock.on('message', (data, source) => {
  console.log('on message', arguments);
});

sock.bind('36912', '2620:9b::1944:e598', () => {
  sock.addMembership('ff02::1:3', '2620:9b::1944:e598');
});


setInterval(() => {
  let buf = Buffer.from((new Date()).toString());
  sock.send(buf, 0, buf.length, 36912, 'ff02::1:3');
}, 500);

The script runs, and I see packets being sent/received with Wireshark, but neither end shows them in the console.

Wireshark UDP Capture

What am I doing wrong? What's a way to send and receive basic multicast with IPv6?

like image 276
Brad Avatar asked Jul 19 '16 01:07

Brad


People also ask

How do I multicast IPv6?

IPv6 multicast addresses use the prefix ff00::/8, shown in Table 4-10, which is equivalent to the IPv4 multicast address 224.0. 0.0/4. A packet sent to a multicast group always has a unicast source address. A multicast address can never be the source address.

Is multicast supported by IPv6?

IPv6 defines three types of addresses: unicast, anycast, and multicast.

Which one is a IPv6 multicast address?

IPv6 multicast addresses are distinguished from unicast addresses by the value of the high-order octet of the addresses: a value of 0xFF (binary 11111111) identifies an address as a multicast address; any other value identifies an address as a unicast address.


2 Answers

Scope id -> interface number

In IPv6, there is a concept of a scope_id of an address that is supposed to indicate a context for the IP address, and generally just means what interface it is reachable on. While scopes have OS specific names, each just translates to an interface number, with 0 usually meaning the system's default.

In IPv6 multicast, this scope_id is provided directly to IP_ADD_MEMBERSHIP and IP_MULTICAST_IF instead of providing an ip associated with the interface as IPv4 does.

wrapper smoothing of v6's differences

node (via libuv) hides this difference for you in addMembership by looking up the scope_id from the "interface address" you provide.

Unfortunately, starting from just an IP and getting a scope doesn't make a lot of sense (the whole point of the scope is that the IP could have different uses in different scopes.) So libuv is only able to fill in a scope if you explicitly provide it at the end of the address, using the %[scope] format.

Using Addresses with Explicit Scopes

The way around this seems to be:

 sock.bind('36912', '::', () => {
   sock.addMembership('ff02::1:3', '::%eth2');
 ...
 sock.send(buf, 0, buf.length, 36912, 'ff02::1:3%eth2');

Where:

  • using :: (or no address) in bind is necessary since you are combining receive which will filter the multicast address on this with send which needs a normal address.

  • using %[iface#] forces the scope of this interface #.

  • The second argument of addMembership could really start with any address since we are forcing the scope and the rest is discarded.

  • Usually the send socket is separated and given a different port or an anonymous port as you are either limited in what you can configure or in danger of getting EADDRINUSE errors for having sockets that are too similar.

like image 95
lossleader Avatar answered Sep 25 '22 17:09

lossleader


I followed the steps in this answer but still couldn't get IPv6 multicast to work. It turned out I was setting socket.setMulticastLoopback(false) to filter out messages coming from the node itself, which worked well for IPv4, but was blocking all messages for IPv6. Removing this fixed the issue and messages started appearing correctly, with no need to filter.

like image 38
Adam Reis Avatar answered Sep 22 '22 17:09

Adam Reis