gpt4 book ai didi

python - 如何使setsockopt IP_ADD_MEMBERSHIP接受本地接口(interface)地址以仅在一个特定接口(interface)上接收多播数据包?

转载 作者:太空狗 更新时间:2023-10-29 12:39:18 25 4
gpt4 key购买 nike

问题

如何使setsockopt IP_ADD_MEMBERSHIP接受本地接口(interface)地址以仅在一个特定接口(interface)上接收多播数据包?

摘要

在一个Python过程中,我创建了三个接收套接字(套接字S1,S2和S3)。

每个套接字使用setsockopt IP_ADD_MEMBERSHIP加入相同的多播组(相同的多播地址,相同的端口)。

但是,每个套接字通过将struct ip_mreq中的imr_address(请参见http://man7.org/linux/man-pages/man7/ip.7.html)设置为特定本地接口(interface)的地址,从而在不同的本地接口(interface)上加入该多播组:套接字S1在接口(interface)I1上加入,套接字S2在接口(interface)I2上加入,套接字S3在接口(interface)I3。

这是用于创建接收套接字的代码:

def create_rx_socket(interface_name):
local_address = interface_ipv4_address(interface_name)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except AttributeError:
pass
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except AttributeError:
pass
sock.bind((MULTICAST_ADDR, MULTICAST_PORT))
report("join group {} on {} for local address {}".format(MULTICAST_ADDR, interface_name,
local_address))
req = struct.pack("=4s4s", socket.inet_aton(MULTICAST_ADDR), socket.inet_aton(local_address))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, req)
return sock

本地接口(interface)的IP地址是按以下方式确定的(我使用了调试打印来验证其是否获得了正确的地址):
def interface_ipv4_address(interface_name):
interface_addresses = netifaces.interfaces()
if not interface_name in netifaces.interfaces():
fatal_error("Interface " + interface_name + " not present.")
interface_addresses = netifaces.ifaddresses(interface_name)
if not netifaces.AF_INET in interface_addresses:
fatal_error("Interface " + interface_name + " has no IPv4 address.")
return interface_addresses[netifaces.AF_INET][0]['addr']

在发送套接字一侧,我使用setsockopt IP_MULTICAST_IF选择要在其上发送传出多播数据包的传出本地接口(interface)。这很好。
def create_tx_socket(interface_name):
local_address = interface_ipv4_address(interface_name)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except AttributeError:
pass
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except AttributeError:
pass
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(local_address))
# Disable the loopback of sent multicast packets to listening sockets on the same host. We don't
# want this to happen because each beacon is listening on the same port and the same multicast
# address on potentially multiple interfaces. Each receive socket should only receive packets
# that were sent by the host on the other side of the interface, and not packet that were sent
# from the same host on a different interface. IP_MULTICAST_IF is enabled by default, so we have
# to explicitly disable it)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0)
sock.bind((local_address, MULTICAST_PORT))
sock.connect((MULTICAST_ADDR, MULTICAST_PORT))
return sock

当Python进程在(例如)接口(interface)I1上接收到单个单独的多播数据包时,所有三个套接字(S1,S2和S3)都报告收到的UDP数据包。

预期的行为是只有套接字S1报告收到的数据包。

这是我的脚本的一些输出,以及tcpdump的一些输出,以说明问题(<<<是手动添加的注释):
beacon3: send beacon3-message-1-to-veth-3-1a on veth-3-1a from ('99.1.3.3', 911) to ('224.0.0.120', 911)     <<< [1]
TX veth-3-1a: 15:49:13.355519 IP 99.1.3.3.911 > 224.0.0.120.911: UDP, length 30 <<< [2]
RX veth-1-3a: 15:49:13.355558 IP 99.1.3.3.911 > 224.0.0.120.911: UDP, length 30 <<< [3]
beacon1: received beacon3-message-1-to-veth-3-1a on veth-1-2 from 99.1.3.3:911 <<< [4a]
beacon1: received beacon3-message-1-to-veth-3-1a on veth-1-3a from 99.1.3.3:911 <<< [4b]
beacon1: received beacon3-message-1-to-veth-3-1a on veth-1-3b from 99.1.3.3:911 <<< [4c]

一次对套接字发送[1]的调用会导致在导线[2]上发送单个数据包,该数据包在导线[3]上被接收一次,但会导致三个套接字唤醒并报告接收到的数据包[4abc]。

预期的行为是,只有一个接收套接字会唤醒并报告接收到的数据包,即在接口(interface)上加入多播组的一个套接字

拓扑详细信息

我有以下三个网络拓扑中连接的三个Python脚本(1、2和3):
              veth-1-2      veth-2-1
(1)------------------------(2)
| | |
veth-1-3a | | veth-1-3b | veth-2-3
| | |
| | |
veth-3-1a | | veth-3-1b |
| | |
(3)-------------------------+
veth-3-2

每个Python脚本都在网络 namespace (netns-1,netns-2,netns-3)中运行。

网络 namespace 使用veth接口(interface)对(veth-x-y)相互连接。

每个Python进程都会在其直接连接的每个接口(interface)上定期发送UDP多播数据包。

任何Python进程在任何接口(interface)上发送的所有UDP数据包始终使用相同的目标多播地址(224.0.0.120)和相同的目标端口(911)。

更新:

我在BIRD( https://github.com/BIRD/bird)的 ospf_rx_hook文件中的函数 packet.c中注意到以下注释:
int
ospf_rx_hook(sock *sk, uint len)
{
/* We want just packets from sk->iface. Unfortunately, on BSD we cannot filter
out other packets at kernel level and we receive all packets on all sockets */
if (sk->lifindex != sk->iface->index)
return 1;

DBG("OSPF: RX hook called (iface %s, src %I, dst %I)\n",
sk->iface->name, sk->faddr, sk->laddr);

显然,BIRD存在相同的问题(至少在BSD上),并通过检查接收到的数据包到达哪个接口(interface)来解决该问题,并在它不是预期的接口(interface)时忽略它。

更新:

如果BIRD注释是正确的,那么问题就变成了:如何确定接收到的UDP数据包在哪个接口(interface)上接收?

一些选项:
  • 使用IP_PKTINFO(http://man7.org/linux/man-pages/man7/ip.7.htmlPython socket bind 0.0.0.0, inspect incoming message中的注释)。似乎并非所有操作系统和/或Python都支持此功能。
  • 检查源地址是否与接口(interface)在同一子网中。仅当数据包通过一跳发送时才有效(在我的情况下确实如此)。但是,它是否适用于IPv6(是的,我认为是这样)和未编号的接口(interface)(显然不是,但是我不支持这些接口(interface))?

  • 关于BIRD的一些有趣的观察:
  • BIRD对OSPF使用原始套接字(因为OSPF直接通过IP运行,而不是通过UDP运行),并且在函数sk_setup(文件io.c)中,它调用sk_request_cmsg4_pktinfo,该函数在Linux上设置IP_PKTINFO套接字选项,在BSD上设置IP_RCVIF套接字选项。
  • BIRD具有一个宏INIT_MREQ4来初始化对IP_ADD_MEMBERSHIP的请求。有趣的是,对于Linux,它设置多播地址(imr_multiaddr)和本地接口(interface)索引(imr_ifindex),而对于BSD,则设置多播地址(imr_multiaddr)和本地接口(interface)地址(imr_interface)
  • 最佳答案

    我通过记录最终使用的变通方法来“回答”自己的问题。

    请注意,这只是一个变通办法。在这种解决方法中,我们检测到“错误”套接字上接收到的数据包,并忽略它们。如果有人有一个“真正的”解决方案(即首先避免从错误的套接字上接收到数据包的解决方案),我很想听听它。

    假设我们如下创建接收套接字时加入了一个多播组:

    req = struct.pack("=4s4s", socket.inet_aton(MULTICAST_ADDR), socket.inet_aton(local_address))
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, req)

    此处,MULTICAST_ADDR是多播地址,而local_address是加入该组的本地接口(interface)的IP地址。

    显然,这实际上意味着在该接口(interface)上接收到的IP多播数据包将被接受并传递到更高的层,在本例中为UDP。

    但是请注意,如果有多个UDP套接字S1,S2,S3,...在不同的接口(interface)I1,I2,I3,...上加入了相同的多播组(相同的多播地址和相同的端口),则如果在接口(interface)I1,I2,I3的任何一个上接收到多播数据包,则将通知所有套接字S1,S2,S3,...已收到数据包。

    我的解决方法如下:

    (1)对于我们加入了多播组的每个套接字,请跟踪我们进行加入的接口(interface)的if_index,以及
    interface_index = socket.if_nametoindex(interface_name)

    (2)使用IP_PKTINFO套接字选项确定实际在哪个接口(interface)索引上接收到数据包,

    设置选项:
    sock.setsockopt(socket.SOL_IP, socket.IP_PKTINFO, 1)

    要在收到数据包时从PKTINFO辅助数据中检索if_index,请执行以下操作:
    rx_interface_index = None
    for anc in ancillary_messages:
    if anc[0] == socket.SOL_IP and anc[1] == socket.IP_PKTINFO:
    packet_info = in_pktinfo.from_buffer_copy(anc[2])
    rx_interface_index = packet_info.ipi_ifindex

    (3)如果套接字的if_index与接收数据包的if_index不匹配,则忽略接收的数据包。

    一个复杂的因素是IP_PKTINFO和in_pktinfo都不是标准Python套接字模块的一部分,因此必须手动定义它们:
    SYMBOLS = {
    'IP_PKTINFO': 8,
    'IP_TRANSPARENT': 19,
    'SOL_IPV6': 41,
    'IPV6_RECVPKTINFO': 49,
    'IPV6_PKTINFO': 50
    }

    uint32_t = ctypes.c_uint32

    in_addr_t = uint32_t

    class in_addr(ctypes.Structure):
    _fields_ = [('s_addr', in_addr_t)]

    class in6_addr_U(ctypes.Union):
    _fields_ = [
    ('__u6_addr8', ctypes.c_uint8 * 16),
    ('__u6_addr16', ctypes.c_uint16 * 8),
    ('__u6_addr32', ctypes.c_uint32 * 4),
    ]

    class in6_addr(ctypes.Structure):
    _fields_ = [
    ('__in6_u', in6_addr_U),
    ]

    class in_pktinfo(ctypes.Structure):
    _fields_ = [
    ('ipi_ifindex', ctypes.c_int),
    ('ipi_spec_dst', in_addr),
    ('ipi_addr', in_addr),
    ]

    for symbol in SYMBOLS:
    if not hasattr(socket, symbol):
    setattr(socket, symbol, SYMBOLS[symbol])

    有关此代码源的完整工作示例以及有关版权和2条款BSD许可证的重要信息,请参见文件 https://github.com/brunorijsman/python-multicast-experiments/blob/master/beacon.py

    关于python - 如何使setsockopt IP_ADD_MEMBERSHIP接受本地接口(interface)地址以仅在一个特定接口(interface)上接收多播数据包?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54078643/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com