到目前为止,我使用 ipv4 进行多播并且它有效;所有涉及的计算机都运行Linux。我在两台机器上收听并在其中一台机器上发送(在单独的终端中)。在下面的示例中,发送计算机 (strawberry) 和远程计算机 (ero) 上收到“Hello 1”。
ero:~$ sudo ip addr add 224.4.19.42 dev enp4s0 autojoin
ero:~$ netcat -l -k -u -p 9988
strawberry:~ $ sudo ip addr add 224.4.19.42 dev wlan0 autojoin
strawberry:~ $ netcat -l -k -u -p 9988
strawberry:~ $ echo "Hello 1" | netcat -s 192.168.178.109 -w 0 -u 224.4.19.42 9988
对于 ipv6,只要只有远程机器侦听,它就可以工作;下面示例中的“Hello 2”由 ero 接收。一旦发送者(草莓)也加入了多播组,发送者(草莓)和远程机器(ero)都不会收到“Hello 3”:
ero:~$ sudo ip addr add ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 dev enp4s0 autojoin
ero:~$ netcat -l -k -u -p 9988
strawberry:~ $ echo "Hello 2" | netcat -w 0 -s 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:76d0 -u ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 9988
strawberry:~ $ sudo ip addr add ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 dev wlan0 autojoin
strawberry:~ $ netcat -l -k -u -p 9988
strawberry:~ $ echo "Hello 3" | netcat -w 0 -s 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:76d0 -u ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 9988
也许有趣的是:当我不提供发件人地址时,即没有 -s 选项,则 ipv4 示例显示与 ipv6 相同的行为:仅在草莓未加入多播组时才收到消息。因此,我使用 ipv6 尝试了不同的发送地址:示例中显示的全局地址 (2001:...)、唯一的本地地址 (ULA; fd00:...) 和链接本地地址 (LLA; fe80:...) .)。两者都没有帮助。
有任何提示我做错了什么吗?
答案1
介绍
对于主机系统,虽然发送到多播 IP 地址与发送到单播地址非常相似,但接收多播是不同的,并且使用额外的 API:主机不会将多播地址分配给接口,而是加入当应用程序(使用套接字)请求时,有兴趣接收选择的多播流量。这是RFC 1112 中描述。 RFC 的加入主机组和离开主机组函数通过使用以下方式转置到大多数 *nix 使用的 BSD 套接字 API 上:setsockopt(2)
加入和离开多播组的选项。 IPv6 与 IPv4 一样遵循这一点,但存在差异(包括路由行为)。 POSIX 甚至在很少 地方应该如何实现 IPv6 API(它似乎没有过多地描述 IPv4 的多播,可能是因为它在操作系统之间更加分散,因此不值得)。
在 Linux 上,使用的主要套接字选项是IP_MULTICAST_IF
加IP_ADD_MEMBERSHIP
对于 IPv4 和IPV6_MULTICAST_IF
加IPV6_ADD_MEMBERSHIP
(遵循 POSIX 的别名IPV6_JOIN_GROUP
)用于 IPv6。 OP的问题也很有趣,但默认启用:IP_MULTICAST_LOOP
和IPV6_MULTICAST_LOOP
。
了解线路上发生的情况以使其在网络中工作(组播组管理协议对于 IPv4 或MLD对于 IPv6...)或跨网络(例如使用PIM-SM)在应用程序级别不需要(但可能需要解决问题,特别是涉及交换机和多播监听的问题)。
剩下的答案预计系统上不会对网络配置进行特殊修改:没有向任何接口添加多播地址,也没有autojoin
选项。另请注意,224.4.19.42 属于AD-HOC Block II 区块分配给伦敦证券交易所。私人的组织本地范围块可以挑选239.0.0.0/8内。
网猫
网猫(其所有变体) 不处理多播。因此,通过调整网络设置(自动加入通常旨在与涉及隧道的配置一起使用)OP 设法将 netcat 与 IPv4 多播地址一起使用,但无论如何不使用 IPv6,任何实际使用多播的应用程序都遵循标准并使用附加 API 来获得适当的多播支持。诊断工具,例如网猫用于替换一个更复杂的软件,但是这个替换应该能够做到同样的事情:网猫惯于。
索卡特
IPv4
索卡特已经(几乎)完成了IPv4 组播支持。
发送到这样的多播组的方法是通过说明应该通过哪个启用多播的接口发送它(实际上索卡特貌似只支持该API的地址变体,所以使用时无法提供接口索卡特虽然 API 支持它,除非使用大兰原始setsockopt
socat 选项,请参见下文),并且可以选择声明是否需要多播环回(默认情况下启用它,因此在这里毫无用处,因此我将仅将其放在一个示例中):
在草莓上,如果存在默认路由或至少存在使用预期接口到多播 224.4.19.42 的路由,那么这就足够了(第一个命令不需要任何多播相关功能,因此这是唯一的情况nc
也可用于):
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,ip-multicast-loop=1
否则接口必须由其上的地址指定:
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,ip-multicast-if=192.168.178.109
使用记录的大兰格式一可以使用任意套接字选项,将socat
其setsockopt
选项用于简介段落中描述的 API 不支持的部分。它是操作系统并且可以依赖于体系结构。这里将介绍 amd64 (x86_64) 架构上的 Linux (>= 3.5)。
SOL_IP
= 0
IP_MULTICAST_IF
= 32
in_mreq
API 变体的预期替代结构:一个 IPv4 多播地址(大端格式 IPv4 地址的十六进制 4 个字节,由前导 引入x
)加上一个本地地址(另外 4 个字节)。对于一种in_mreqn
变体:同样加上一个由前导 引入的整数大小索引i
。并非所有参数都必须填写。工作示例(使用 JSON 格式和jq
用于计算接口索引的命令wlan0
):
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,setsockopt=0:32:x00000000xc0a8b26d
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,setsockopt=0:32:x00000000x00000000i$(ip -j link show enp4s0 | jq '.[].ifindex')
如果使用ip-multicast-loop=0
,则草莓将不会接收自己发出的多播流量。
接收部分必须用于IP_ADD_MEMBERSHIP
指定要加入的多播 IP 地址组,如果不想依赖路由堆栈来选择正确的选择,则还可以选择本地地址或接口(如果需要,则再次需要这些选项之一)没有到多播地址的正确路由,例如,如果没有默认路由或者默认路由错误)。当套接字绑定到其他地址时,也不需要声明此本地地址,否则INADDR_ANY
操作系统将遵循此选择。接收部分通常可以使用UDP4-RECV
(合并接收到的内容)或UDP4-RECVFROM
(拆分每个数据报)选项fork
。例如:
如果存在 224.4.19.42 的路由(包括默认),则在两台主机上:
socat -u UDP4-RECV:9988,ip-add-membership=224.4.19.42:0.0.0.0 -
否则(此处选择接口名称语法),
为了草莓:
socat -u UDP4-RECV:9988,ip-add-membership=224.4.19.42:wlan0 -
为了爱罗:
socat -u UDP4-RECV:9988,ip-add-membership=224.4.19.42:enp4s0 -
IPv6
看来 IPv6 支持已部分实现:与ip-add-membership
IPv4 类似,等效选项ipv6-add-membership
确实存在于源代码和工作中,但没有在任何地方记录。所有测试均采用索卡特7.4.1所以展示这个版本。这选项存在:
#ifdef IPV6_JOIN_GROUP
IF_IP6 ("ipv6-add-membership", &opt_ipv6_join_group)
#endif
或别名:
#ifdef IPV6_JOIN_GROUP
IF_IP6 ("ipv6-join-group", &opt_ipv6_join_group)
#endif
与格式声明供setsockopt(2)
以后使用:
#ifdef IPV6_JOIN_GROUP
const struct optdesc opt_ipv6_join_group = { "ipv6-join-group", "join-group", OPT_IPV6_JOIN_GROUP, GROUP_SOCK_IP6, PH_PASTSOCKET, TYPE_IP_MREQN, OFUNC_SOCKOPT, SOL_IPV6, IPV6_JOIN_GROUP };
#endif
所以实际上支持基本的多播 IPv6 侦听(否则这将需要 dalansetsockopt-listen
索卡特需要最新版本的选项)。选择不使用默认路由堆栈的选择来发送本身是不可能的,因为没有ipv6-multicast-if
选项(还?)。仍然可以使用 dalan 格式。用于的结构IPV6_MULTICAST_IF
只需要一个接口索引。IPV6_MULTICAST_IF
如果 IPv6 多播范围低于站点本地(例如:ff01::/16 或 ff02::/16),则可能始终需要使用,但其他情况也通常需要使用(见下文)。同样,该参数ipv6-multicast-loop
尚未实现,因此还需要一个 dalan 格式的 setsockopt 选项。
IPv6 路由行为在此与 IPv4 略有不同。它可能不遵循默认路由,并且在使用未显式定义路由的多个接口的系统上可能具有未定义的行为,具体取决于(重新)配置接口的顺序:路由表可以从以下位置切换:
# ip -6 route show table all type multicast
multicast ff00::/8 dev wlan0 table local proto kernel metric 256 pref medium
multicast ff00::/8 dev dummy0 table local proto kernel metric 256 pref medium
到
# ip -6 route show table all type multicast
multicast ff00::/8 dev dummy0 table local proto kernel metric 256 pref medium
multicast ff00::/8 dev wlan0 table local proto kernel metric 256 pref medium
只是因为接口先关闭然后再启动,从而更改了要使用的默认接口。运行任何类型的容器、虚拟机或动态接口都可能导致这种情况。
最后,只是盲目地发送信任系统的路由堆栈选择(或者如果已知路由配置正确)草莓(注意引号以避免 IPv6 所需的 shell 解释括号):
echo "Hello 1" | socat -u - UDP6-DATAGRAM:'[ff05::4141]':9988
指定要发送的接口并明确说明使用多播环回需要 dalan 格式setsockopt
选项:
SOL_IPV6
= 41
IPV6_MULTICAST_LOOP
= 19
IPV6_MULTICAST_IF
= 17的布尔值:实际上是整数(因此在 dalan 数据格式中使用的
IPV6_MULTICAST_LOOP
字母)的索引:整数i
IPV6_MULTICAST_IF
echo "Hello 1" | socat -u - UDP6-DATAGRAM:'[ff05::4141]':9988,setsockopt=41:19:i1,setsockopt=41:17:i$(ip -j link show wlan0 | jq '.[].ifindex')
接收于草莓:
socat -u UDP6-RECV:9988,ipv6-add-membership='[ff05::4141]':wlan0 -
同样接收到爱罗:
socat -u UDP6-RECV:9988,ipv6-add-membership='[ff05::4141]':enp4s0 -
任意数量的对等点之间的 IPv6 多播通信是可能的。下面是一个示例,同时禁用接收其自己的环回多播流量并指定接口:
草莓:
socat UDP6-DATAGRAM:'[ff05::4141]':9988,bind=:9988,ipv6-add-membership='[ff05::4141]':wlan0,setsockopt=41:19:i0,setsockopt=41:17:i$(ip -j link show wlan0 | jq '.[].ifindex') -
爱罗(相同,只是接口名称有机会):
socat UDP6-DATAGRAM:'[ff05::4141]':9988,bind=:9988,ipv6-add-membership='[ff05::4141]':enp4s0,setsockopt=41:19:i0,setsockopt=41:17:i$(ip -j link show enp4s0 | jq '.[].ifindex') -
其他系统也可以运行相同的命令,只是接口名称可能不同。
每个socat
将向所有其他多播对等体发送数据(在终端上键入),并且还将读回从它们发送的流量(但不是从其自身发送的流量)。