Why does this send-IPv6-local-node-multicast program work under MacOS/X but not under Linux?

0

Below is a minimal example program that compiles under both MacOS/X and Linux. Its purpose is to send a single node-scoped UDP/IPv6 multicast packet over the machine's loopback-device. I believe it is correct, and it does work as expected under MacOS/X, but under Linux it fails with this output:

Attempting to send an IPv6/UDP packet to multicast address [ff11:0:1:0:94a4:2318:6300:4d51] on interface at index 1 (aka lo)
FAILURE!  sendto() returned -1, errno=101 aka [Network is unreachable]

... however if I modify line 26 to set the constant interfaceIdx = 0 rather than interfaceIdx = if_nametoindex(ifaceName), it successfully sends the packet under Linux:

Attempting to send an IPv6/UDP packet to multicast address [ff11:0:1:0:94a4:2318:6300:4d51] on interface at index 0 (aka lo)
SUCCESS!  sendto() returned 18

... so I could just put in an #ifdef __linux__ to force the interfaceIdx variable to 0, but I feel like that isn't the correct fix, since AFAIK setting the interface-index correctly is a requirement for IPv6-scoped-multicast. Does anyone know why setting the interfaceIdx to its correct value breaks the program under Linux?

Here is the program:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

// This function is here solely to make sure I pass in the right pointer to inet_pton's third argument
static int Inet_PtoN(int af, const char * src, struct in6_addr * dst)
{
   return inet_pton(af, src, dst);
}

int main(void)
{
   const char * dest = "ff11:0:1:0:94a4:2318:6300:4d51";  // my local-node-scoped IPv6 multicast address
#ifdef __linux__
   const char * ifaceName = "lo";   // name of loopback device under Linux
#else
   const char * ifaceName = "lo0";  // name of loopback device under MacOS/X
#endif
   const int interfaceIdx = if_nametoindex(ifaceName);

   printf("Attempting to send an IPv6/UDP packet to multicast address [%s] on interface at index %i (aka %s)\n", dest, interfaceIdx, ifaceName);

   int s = socket(AF_INET6, SOCK_DGRAM, 0);
   if (s<0) {perror("socket()"); return 10;}

   if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &interfaceIdx, sizeof(interfaceIdx)) != 0) {perror("setsockopt(IPV6_MULTICAST_IF)"); return -1;}

   struct sockaddr_in6 toAddr; memset(&toAddr, 0, sizeof(toAddr));
   toAddr.sin6_family = AF_INET6;
   toAddr.sin6_port   = htons(12345);
   if (Inet_PtoN(AF_INET6, dest, &toAddr.sin6_addr) <= 0) {perror("inet_pton()"); return -1;}
   toAddr.sin6_scope_id = interfaceIdx;

   char buf[] = "dummy payload text";
   const int r = sendto(s, buf, strlen(buf), 0, (struct sockaddr *)&toAddr, sizeof(toAddr));
   if (r >= 0) printf("SUCCESS!  sendto() returned %i\n", r);
          else printf("FAILURE!  sendto() returned %i, errno=%i aka [%s]\n", r, errno, strerror(errno));

   close(s);
   return 0;
}

... and here, just for reference, is the output of ifconfig -a in my Linux VM (Ubuntu 18.0.5):

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.158.154  netmask 255.255.255.0  broadcast 172.16.158.255
        inet6 fe80::bcf8:ae62:d420:b850  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:d5:92:67  txqueuelen 1000  (Ethernet)
        RX packets 41456  bytes 60446720 (60.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 15899  bytes 1058502 (1.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 5442  bytes 983303 (983.3 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5442  bytes 983303 (983.3 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

.... and here is the output of ifconfig -a on my Mac (which hosts the Linux Ubuntu VM via VMWare Fusion):

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
            options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
    inet 127.0.0.1 netmask 0xff000000
    inet6 ::1 prefixlen 128
    inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
    inet 127.94.0.2 netmask 0xff000000
    inet 127.94.0.1 netmask 0xff000000
    nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
XHC0: flags=0<> mtu 0
XHC1: flags=0<> mtu 0
XHC20: flags=0<> mtu 0
VHC128: flags=0<> mtu 0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=50b<RXCSUM,TXCSUM,VLAN_HWTAGGING,AV,CHANNEL_IO>
    ether f0:18:98:e8:e4:81
    inet6 fe80::4ef:a7f5:734c:5a82%en0 prefixlen 64 secured scopeid 0x8
    inet 10.0.1.26 netmask 0xffffff00 broadcast 10.0.1.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (<unknown type>)
    status: active
en6: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    ether ac:de:48:00:11:22
    inet6 fe80::aede:48ff:fe00:1122%en6 prefixlen 64 scopeid 0x9
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (100baseTX <full-duplex>)
    status: active
en8: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=400<CHANNEL_IO>
    ether 52:de:06:3a:29:9f
    inet6 fe80::1438:7fb7:7145:e06c%en8 prefixlen 64 secured scopeid 0xa
    inet 169.254.73.131 netmask 0xffff0000 broadcast 169.254.255.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (100baseTX <full-duplex>)
    status: active
ap1: flags=8802<BROADCAST,SIMPLEX,MULTICAST> mtu 1500
    options=400<CHANNEL_IO>
    ether f2:18:98:a8:a3:b4
    media: autoselect
    status: inactive
en1: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=400<CHANNEL_IO>
    ether f0:18:98:a8:a3:b4
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (<unknown type>)
    status: inactive
p2p0: flags=8802<BROADCAST,SIMPLEX,MULTICAST> mtu 2304
    options=400<CHANNEL_IO>
    ether 02:18:98:a8:a3:b4
    media: autoselect
    status: inactive
awdl0: flags=8902<BROADCAST,PROMISC,SIMPLEX,MULTICAST> mtu 1484
    options=400<CHANNEL_IO>
    ether 2a:72:ad:a2:71:2b
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: inactive
llw0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=400<CHANNEL_IO>
    ether 2a:72:ad:a2:71:2b
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: inactive
en5: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
    options=460<TSO4,TSO6,CHANNEL_IO>
    ether 82:32:b3:81:34:04
    media: autoselect <full-duplex>
    status: inactive
en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
    options=460<TSO4,TSO6,CHANNEL_IO>
    ether 82:32:b3:81:34:01
    media: autoselect <full-duplex>
    status: inactive
en3: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
    options=460<TSO4,TSO6,CHANNEL_IO>
    ether 82:32:b3:81:34:00
    media: autoselect <full-duplex>
    status: inactive
en4: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
    options=460<TSO4,TSO6,CHANNEL_IO>
    ether 82:32:b3:81:34:05
    media: autoselect <full-duplex>
    status: inactive
bridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=63<RXCSUM,TXCSUM,TSO4,TSO6>
    ether 82:32:b3:81:34:01
    Configuration:
            id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
            maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
            root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
            ipfilter disabled flags 0x0
    member: en2 flags=3<LEARNING,DISCOVER>
            ifmaxaddr 0 port 17 priority 0 path cost 0
    member: en3 flags=3<LEARNING,DISCOVER>
            ifmaxaddr 0 port 18 priority 0 path cost 0
    member: en4 flags=3<LEARNING,DISCOVER>
            ifmaxaddr 0 port 19 priority 0 path cost 0
    member: en5 flags=3<LEARNING,DISCOVER>
            ifmaxaddr 0 port 16 priority 0 path cost 0
    nd6 options=201<PERFORMNUD,DAD>
    media: <unknown type>
    status: inactive
utun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1380
    inet6 fe80::d1a3:48e0:11d1:fc27%utun0 prefixlen 64 scopeid 0x15
    nd6 options=201<PERFORMNUD,DAD>
utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000
    inet6 fe80::ee71:43c3:4648:31b0%utun1 prefixlen 64 scopeid 0x16
    nd6 options=201<PERFORMNUD,DAD>
utun2: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
    inet 172.27.224.165 --> 172.27.224.165 netmask 0xffffffc0
vmnet1: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    ether 00:50:56:c0:00:08
    inet 172.16.158.1 netmask 0xffffff00 broadcast 172.16.158.255
c
linux
macos
ipv6
multicast
asked on Stack Overflow Dec 22, 2020 by Jeremy Friesner • edited Dec 29, 2020 by dbush

1 Answer

1

Since ffx1: should not go out of the system, it should normally be fine to rendevouz on the system default interface (unless maybe interfaces go up and down between when client and servers setup.) One could add firewall rules, if the OS might accidentally send or accept these addresses on the wire.

If you want to make the actual lo interface act as a valid multicast to explicitly rendevouz on the private link, it needs the same configuration as a normal interface. How to make this persistent on boot would vary but most I think most modern Linuxes would understand these commands:

ifconfig lo multicast
ip -6 route show table local
# find an interface route and copy it for lo and create it with a lower metric:
ip -6 route add table local ff00::/8 dev lo metric 255 pref medium
answered on Stack Overflow Dec 28, 2020 by lossleader

User contributions licensed under CC BY-SA 3.0