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.
What am I doing wrong? What's a way to send and receive basic multicast with 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.
IPv6 defines three types of addresses: unicast, anycast, and multicast.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With