如何使用本地组播限制流量

7

我正在使用本地组播UDP实现在单台计算机上运行的合作程序的松散集合。以下代码在Mac OSX、Windows和Linux上都能正常工作。缺陷是,该代码也会接收来自localhost网络以外的UDP数据包。例如,当从我的网络中的另一台计算机发送时,sendSock.sendto(pkt, ('192.168.0.25', 1600))会被我的测试机器接收。

import platform, time, socket, select

addr = ("239.255.2.9", 1600)

sendSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 24)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, 
    socket.inet_aton("127.0.0.1"))

recvSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
if hasattr(socket, 'SO_REUSEPORT'):
    recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)

recvSock.bind(("0.0.0.0", addr[1]))
status = recvSock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 
    socket.inet_aton(addr[0]) + socket.inet_aton("127.0.0.1"));

while 1:
    pkt = "Hello host: {1} time: {0}".format(time.ctime(), platform.node())
    print "SEND to: {0} data: {1}".format(addr, pkt)
    r = sendSock.sendto(pkt, addr)

    while select.select([recvSock], [], [], 0)[0]:
        data, fromAddr = recvSock.recvfrom(1024)
        print "RECV from: {0} data: {1}".format(fromAddr, data)

    time.sleep(2)

我尝试过recvSock.bind(("127.0.0.1", addr[1])),但这会阻止套接字接收任何多播流量。是否有一种正确的方法来配置 recvSock 仅接受来自 127/24 网络的多播数据包,或者需要测试每个接收到的数据包的地址?

4个回答

6
与其他答案所述相反,IPv4 支持基于 TTL 的多播范围,具体如下:
0: node-local (not forwarded outside the current host)
1: link-local (not forwarded outside the current subnet)
< 32: site-local
< 64: region-local
< 128: continent-local
< 255: global

(它还支持行政范围组播。)

来源:W.R. Stevens,《Unix网络编程》,第二版,第I卷,第19.2节,修正以匹配RFC 2365


我原以为基于TTL的作用范围只限制在发送方。它怎么能够用来过滤不可信网络上的进入数据包呢? - Shane Holloway

2

很遗憾,多播IP没有任何“按子网过滤”的功能——因此,除非您想在Linux上操作IPTables或等效的系统/网络“防火墙”软件/硬件来尝试“丢弃”您不喜欢的每个多播数据包,否则我认为您将不得不在应用程序级别上进行处理(例如,在您的内部循环中对fromAddr进行测试)。其他主机的IP流量是否会降低您的性能...?


不是很拥挤,更多的是一个数据包注入安全漏洞的机会。我宁愿从结构上解决它,而不是在每个站点的使用中处理它。但是,希望和工作是两回事。 - Shane Holloway
1
@Shane,如果Mordred向您伪造127.0.0.1数据包,您可以使用IPTables等工具轻松阻止它们 - 无论是组播还是单播,任何声称来自127.0.0.1但实际上通过任何实际接口进入的数据包都是虚假的 - 因此在“结构上”处理这个问题绝对是容易的(但这完全是一个服务器故障问题,与代码无关,而是关于系统和网络的适当管理)。 - Alex Martelli

1
如果您的主机支持IPv6,您可以使用多播地址的范围组件(这是多播前缀FF0x:中的“x”)来隐式限制本地主机的入站和出站数据包,通过指定范围为1(例如,仅在本地主机上使用IPv6多播地址FF01::107作为“名称服务服务器”)。不幸的是,IPv4多播机制没有明确的范围,并且RFC 2365(http://tools.ietf.org/html/rfc2365)定义了管理范围内的IPv4多播范围,但未定义节点本地范围地址,只有链路本地范围范围。

0

你可以使用connect()到多播套接字的127.0.0.1,然后IP堆栈就可以为你过滤。

更新了包含源代码以演示

你可以在一个主机上运行此脚本的多个实例,并查看多播数据包的分发:

#!/usr/bin/python

import platform, time, socket, select

addr = ("239.255.2.9", 1600)

sendSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 24)
sendSock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
    socket.inet_aton("127.0.0.1"))

recvSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
if hasattr(socket, 'SO_REUSEPORT'):
    recvSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)

recvSock.bind(("0.0.0.0", addr[1]))
status = recvSock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
    socket.inet_aton(addr[0]) + socket.inet_aton("127.0.0.1"));

sendSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
if hasattr(socket, 'SO_REUSEPORT'):
    sendSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
sendSock.bind(("127.0.0.1", addr[1]));
recvSock.connect(("127.0.0.1", addr[1]));

while 1:
    pkt = "Hello host: {1} time: {0}".format(time.ctime(), platform.node())
    print "SEND to: {0} data: {1}".format(addr, pkt)
    r = sendSock.sendto(pkt, addr)

    while select.select([recvSock], [], [], 0)[0]:
        data, fromAddr = recvSock.recvfrom(1024)
        print "RECV from: {0} data: {1}".format(fromAddr, data)

    time.sleep(2)

从不同的接口发起数据包:

#!/usr/bin/python

import platform, time, socket, select

sendSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

pkt = "Hello from network.";
sendSock.sendto(pkt, ('10.65.42.129', 1600))

我在Windows XP上的Cygwin上运行了所有三个程序,并验证结果符合要求。

示例输出

SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:15 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:14 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:15 2013
SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:17 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:16 2013
RECV from: ('127.0.0.1', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:15:17 2013

以前的输出会显示外部数据包,例如:

SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:07:05 2013
RECV from: ('10.65.42.129', 4711) data: Hello from network.
RECV from: ('127.0.0.1', 4710) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:07:05 2013
SEND to: ('239.255.2.9', 1600) data: Hello host: NYCMDV8X5R1 time: Thu Feb 21 13:07:11 2013
RECV from: ('10.65.42.129', 4712) data: Hello from network.

1
一个好的想法,但它不能处理多播数据包。并且在第二次连接时会出现套接字错误:socket.error: [Errno 48] Address already in use - Shane Holloway
感谢您提供详细的回答!在Mac OSX上,我仍然遇到了地址已被使用的错误,但是您的回答引发了我的另一个想法。我只在最后一个绑定到该多播地址的套接字上收到欺骗数据包。因此,我认为一个简单的解决方案就是在该地址上绑定另一个接收套接字。然而,结果证明这也行不通。 - Shane Holloway

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接