如何通过 localhost 使用多播限制流量

Posted

技术标签:

【中文标题】如何通过 localhost 使用多播限制流量【英文标题】:How to limit traffic using multicast over localhost 【发布时间】:2011-02-22 04:05:42 【问题描述】:

我在 localhost 上使用多播 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 网络的多播数据包,还是我需要测试每个接收到的数据包的地址?

【问题讨论】:

【参考方案1】:

您可以在多播套接字上使用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.

【讨论】:

一个好主意,但它不适用于多播数据包。第二次连接时出现套接字错误:socket.error: [Errno 48] Address already in use 感谢扩展答案!我仍然在 Mac OSX 上收到地址使用错误,但你的回答确实引发了另一个想法。我只在绑定到该多播地址的最后一个套接字上获得欺骗数据包。所以我认为一个简单的解决方案是在地址上绑定另一个接收套接字。但是,结果也没有成功。【参考方案2】:

与此处其他答案中所述相反,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

(也支持Administratively Scoped Multicast。)

来源:W.R. Stevens,Unix 网络编程,第 2nd 版,第一卷,第 19.2 节,更正以匹配 RFC 2365。

【讨论】:

我认为基于 TTL 的范围仅限制发送方。它如何用于过滤不受信任网络上的传入数据包?【参考方案3】:

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

【讨论】:

【参考方案4】:

不幸的是,多播 IP 没有任何此类“按子网过滤”功能 - 因此,除非您想使用 IPTables(在 Linux 上)或系统/网络的等效“防火墙”软件/硬件来尝试和“掉在地上”每个你不喜欢的多播数据包,我认为你必须在应用程序级别进行(例如,在你的内部循环中对fromAddr 进行测试)。来自其他主机的 IP 流量是否会降低您的性能...?

【讨论】:

流量不多,更多的是数据包注入安全漏洞的机会。我宁愿在结构上处理它,也不愿在每个站点的使用情况下处理它。但是有愿望,就有工作。 @Shane,如果 Mordred 向您伪造 127.0.0.1 数据包,您可以绝对使用 IPTables 之类的东西将它们拒之门外——无论是否多播,任何声称是“从 127.0.0.1" 但实际上通过任何实际接口进入显然是虚假的 - 所以它绝对很容易“结构化”处理(但这完全是服务器故障问题,不是堆栈溢出问题,因为您编写的代码与问题无关,都是关于系统和网络的正确管理)。

以上是关于如何通过 localhost 使用多播限制流量的主要内容,如果未能解决你的问题,请参考以下文章

java * Windows 10,版本1703 * over * lo上的Java多播消息示例(软件环回接口1)*

如何分析多播消息流?

将多播流量转发到 RedHat 6 上的单个 NIC 的路由配置

如何在charles代理中阻止localhost流量?

如何让 Fiddler 停止忽略到 localhost 的流量?

网络小常识