为什么这个 send-IPv6-local-node-multicast 程序在 MacOS/X 下工作但在 Linux 下不工作?
Why does this send-IPv6-local-node-multicast program work under MacOS/X but not under Linux?
下面是在 MacOS/X 和 Linux 下编译的最小示例程序。其目的是通过机器的环回设备发送单个节点范围的 UDP/IPv6 多播数据包。我相信它是正确的,并且它在 MacOS/X 下确实按预期工作,但在 Linux 下它失败并显示以下输出:
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]
... 但是,如果我修改第 26 行以设置常量 interfaceIdx = 0
而不是 interfaceIdx = if_nametoindex(ifaceName)
,它会成功发送 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
...所以我可以输入一个 #ifdef __linux__
来强制 interfaceIdx
变量为 0,但我觉得这不是正确的解决方法,因为 AFAIK 设置了接口-正确索引是 IPv6 范围多播的要求。有谁知道为什么将 interfaceIdx
设置为正确的值会破坏 Linux 下的程序?
程序如下:
#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;
}
...这里是我的 Linux VM (Ubuntu 18.0.5):
中 ifconfig -a
的输出,仅供参考
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
... 这是 ifconfig -a
在我的 Mac 上的输出(通过 VMWare Fusion 托管 Linux Ubuntu 虚拟机):
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
因为 ffx1:
不应该离开系统,通常在系统默认界面上 rendevouz 应该没问题(除非在客户端和服务器设置之间界面可能会上下移动。)可以添加防火墙规则,如果 OS 可能会在网络上意外发送或接受这些地址。
如果要使实际的 lo 接口充当有效的多播以在私有 link 上显式 rendevouz,它需要与普通接口相同的配置。如何使这个在启动时持久化会有所不同,但我认为大多数现代 Linux 都会理解这些命令:
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
下面是在 MacOS/X 和 Linux 下编译的最小示例程序。其目的是通过机器的环回设备发送单个节点范围的 UDP/IPv6 多播数据包。我相信它是正确的,并且它在 MacOS/X 下确实按预期工作,但在 Linux 下它失败并显示以下输出:
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]
... 但是,如果我修改第 26 行以设置常量 interfaceIdx = 0
而不是 interfaceIdx = if_nametoindex(ifaceName)
,它会成功发送 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
...所以我可以输入一个 #ifdef __linux__
来强制 interfaceIdx
变量为 0,但我觉得这不是正确的解决方法,因为 AFAIK 设置了接口-正确索引是 IPv6 范围多播的要求。有谁知道为什么将 interfaceIdx
设置为正确的值会破坏 Linux 下的程序?
程序如下:
#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;
}
...这里是我的 Linux VM (Ubuntu 18.0.5):
中ifconfig -a
的输出,仅供参考
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
... 这是 ifconfig -a
在我的 Mac 上的输出(通过 VMWare Fusion 托管 Linux Ubuntu 虚拟机):
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
因为 ffx1:
不应该离开系统,通常在系统默认界面上 rendevouz 应该没问题(除非在客户端和服务器设置之间界面可能会上下移动。)可以添加防火墙规则,如果 OS 可能会在网络上意外发送或接受这些地址。
如果要使实际的 lo 接口充当有效的多播以在私有 link 上显式 rendevouz,它需要与普通接口相同的配置。如何使这个在启动时持久化会有所不同,但我认为大多数现代 Linux 都会理解这些命令:
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