由于有两个 IGMPv2 加入消息,每个发送的 UDP 多播消息都会被接收两次。如何避免?

Every sent UDP multicast message is received twice because of two IGMPv2 join messages. How to avoid?

我有一个 Python 程序,它使用套接字在多播 IP 地址 224.0.1.1 和 UDP 端口 20001 上发送和接收 UDP 多播消息。

在接收端,我创建了一个接收套接字,并使用套接字选项 IP_ADD_MEMBERSHIP 调用 socket.setsockopt 一次以加入 IP 多播组。

但是,Wireshark 报告说,对 setsockopt 的单次调用会导致发送两个单独的加入(IGMPv2 成员报告)消息:

在发送端,我创建了一个发送套接字,调用 socket.connect 将套接字与多播 IP 地址 224.0.1.1 和 UDP 端口 20001 相关联。

然后我调用 socket.send 一次以发送一条测试消息。由于有两个单独的加入消息,发送的测试消息在网络上出现两次,一次带有目标以太网地址 01:00:52:00:01:01,一次带有目标以太网地址 a8:66:7f:3a:2b:1a.

在接收方,两条消息是分开接收的。因此,每条发送的消息都会收到两次。

问题是:如何防止这种情况发生?

重现该行为的最小示例如下:

import socket
import struct
import time

mcast_ipv4_address = "224.0.1.1"
port = 20001
group = (mcast_ipv4_address, port)

txsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
txsock.connect(group) 

rxsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
req = struct.pack("=4sl", socket.inet_aton(mcast_ipv4_address), socket.INADDR_ANY)
rxsock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, req)
rxsock.bind(group)

time.sleep(1.0)

print("Sending single message...")
msg = b"test"
txsock.send(msg)
print("Sent {}".format(msg))

print("Receiving first message...")
msg = rxsock.recv(65535)
print("Received {}".format(msg))

print("Receiving second message...")
msg = rxsock.recv(65535)
print("Received {}".format(msg))

time.sleep(0.1)

其他详细信息:

1) 操作系统为macOS High Sierra 10.13.5

2) Python版本为3.5.1

3)第一觉必不可少;没有它,问题就不会发生,因为发送加入消息需要一些时间

4) 第二次睡眠不是必须的;它用于确保在程序终止和发送离开消息之前,wireshark 可以看到两条测试消息。

5) 我尝试在请求结构中使用传出接口的真实 IP 地址而不是 INADDR_ANY,但没有任何区别。

我找到了我自己问题的答案:

如果在发送套接字上禁用 IP_MULTICAST_LOOP 选项,则:

1) Wireshark 仍将报告两个 IGMPv2 加入消息,与之前相同

2) Wireshark 仍将报告两个 UDP 多播消息,与之前相同

3) 但是,接收套接字只会接收到单个 UDP 多播消息(示例程序将阻塞 "Receiving second message...")

这是 macOS 的更新代码:

txsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 
socket.IPPROTO_UDP)
txsock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0)  # <<< FIX
txsock.connect(group)

令人讨厌的是,Linux 上的行为恰恰相反:

  • 如果您将 IP_MULTICAST_LOOP 保留为默认值启用,就像在原始示例程序中一样,您将收到一份已发送数据包的副本。

  • 如果像 "fixed" 示例程序那样禁用 IP_MULTICAST_LOOP,您将不会收到已发送数据包的任何副本(至少在 AWS 上不会)。

经过进一步调查,我发现该行为不取决于代码运行的平台(macOS vs Linux),而是取决于平台所连接的路由器。