【问题标题】:Receive foreign UDP Broadcast with Python使用 Python 接收外国 UDP 广播
【发布时间】:2021-06-11 05:00:43
【问题描述】:

我在192.168.123.204 的网络中有一个设备,它以2.168.123.2042.255.255.255:6454 广播UDP 数据报(Artnet)。 (网络地址是192.168.123.204,但是数据报是以2.168.123.204作为源发送的。)地址2.255.255.255不能改变(没有设置)。

我的 Python 脚本在设备 192.168.123.148 上运行。我可以使用wireshark 接收数据报:但是绑定到0.0.0.0:6454 的Python 套接字无法接收它们。将其绑定到 2.168.123.2042.255.255.255 不起作用。该脚本正在运行,因为我可以接收来自127.0.0.1 的数据包。

如果不能用 Python 解决,我可以用 iptables (linux) 重定向 UDP 广播吗?

网络:
路由器 192.168.123.1
/   \
广播公司:192.168.123.204脚本:192.168.123.148

基本脚本:

import socket
import asyncio


HOST, PORT = 'localhost', 6454


class SyslogProtocol(asyncio.DatagramProtocol):
    def __init__(self):
        super().__init__()

    def connection_made(self, transport) -> "Used by asyncio":
        self.transport = transport

    def datagram_received(self, data, addr) -> "Main entrypoint for processing message":
        # Here is where you would push message to whatever methods/classes you want.
      
        print(data)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    t = loop.create_datagram_endpoint(SyslogProtocol, local_addr=('0.0.0.0', PORT))
    loop.run_until_complete(t) # Server starts listening
    loop.run_forever()

完整脚本:

#import pygame
from ctypes import *
import socket
import asyncio
import os, random

class ArtNetPackage(LittleEndianStructure):
    PORT = 0x1936
    _fields_ = [("id", c_char * 8),
                ("opcode", c_ushort),
                ("protverh", c_ubyte),
                ("protver", c_ubyte),
                ("sequence", c_ubyte),
                ("physical", c_ubyte),         
                ("universe", c_ushort),
                ("lengthhi", c_ubyte),
                ("length", c_ubyte),
                ("payload", c_ubyte * 512)]
    
    def get_length(self):
        return self.lengthhi*256+self.length
        
    def __init__(self,data=b''):
        if len(data) == 0:
            self.id = b"Art-Net"
            self.opcode = 0x5000
            self.protver = 14
            self.universe = 0
            self.lengthhi = 2
        else:
            self.id = data[:8]
            self.opcode = data[8]+data[9]*256
            if self.opcode == 0x5000:
                self.protverh = data[10]
                self.protver = data[11]
                self.sequence = data[12]
                self.physical = data[13]
                self.universe = data[14]+data[15]*256
                self.lengthhi = data[16]
                self.length = data[17]
                self.payload = (c_ubyte * 512).from_buffer_copy(
                        data[18:530])#.ljust(512,b'\x00'))

#pygame.init()

HOST, PORT = 'localhost', 6454


class SyslogProtocol(asyncio.DatagramProtocol):
    def __init__(self):
        super().__init__()
        
    def connection_made(self, transport) -> "Used by asyncio":
        self.transport = transport

    def datagram_received(self, data, addr) -> "Main entrypoint for processing message":
        # Here is where you would push message to whatever methods/classes you want.
        try:
            dmx = ArtNetPackage(data)
            if not dmx.opcode == 0x5000:
                return
            print(dmx.payload[0])
        except:
            print("error")


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    t = loop.create_datagram_endpoint(SyslogProtocol, local_addr=('0.0.0.0', PORT))
    loop.run_until_complete(t) # Server starts listening
    loop.run_forever()

接口:

$ ip a
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
    inet 192.168.123.148/24 brd 192.168.123.255 scope global dynamic wlan0
       valid_lft 86393sec preferred_lft 86393sec
    inet6 XXXXXXXX/64 scope link 
       valid_lft forever preferred_lft forever

【问题讨论】:

  • 我不是网络专家,但我觉得你的做法不正确。您当前正在收听针对您的数据包。您需要的是收听原始数据包。为此,您可以使用 github.com/KimiNewt/pyshark LiveCapture 或者您可以使用此示例 uv.mx/personal/angelperez/files/2018/10/sniffers_texto.pdf。让我知道这是否适合你
  • 下定决心。 '[it] 将 UDP 数据报 ... 广播到 2.255.255.255:6454' '数据报发送到 2.168.123.204'。不能同时进行。
  • 你能发一张你的wireshark数据包的截图吗?

标签: python python-3.x networking udp udpclient


【解决方案1】:

使用原始套接字。

import struct

SRC_IPv4 = slice(26, 30)
DST_IPv4 = slice(30, 34)

SRC_PORTv4 = slice(34, 36)
DST_PORTv4 = slice(36, 38)

sock = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(3))
while 1:
    raw, (nic, ptype, *x) = sock.recvfrom(2048)
    if ptype == 2048 and raw[23] == 17:
        src_host = socket.inet_ntoa(raw[SRC_IPv4])
        src_port, = struct.Struct("!H").unpack(raw[SRC_PORTv4])
        dst_host = socket.inet_ntoa(raw[DST_IPv4])
        dst_port, = struct.Struct("!H").unpack(raw[DST_PORTv4])
        print(f'{src_host}:{src_port} => {dst_host}:{dst_port}')```

【讨论】:

  • 我感觉这将是一种有效的方法,因为他的数据包可能已损坏。
【解决方案2】:

我误读了您的问题,您处理的是广播而不是多播地址。您的问题是您没有发送SO_BROADCAST 标志

IPv4地址分为单播、广播和多播 地址。单播地址指定主机的单个接口, 广播地址指定网络上的所有主机和多播 地址寻址多播组中的所有主机。 数据报到 广播地址只能在 SO_BROADCAST 时发送或接收 套接字标志已设置。 在当前实现中,面向连接 套接字只允许使用单播地址。

有一个明确的例子here。最重要的部分是:

    def connection_made(self, transport):
        print('started')
        self.transport = transport
        sock = transport.get_extra_info("socket")
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        self.broadcast()

您还需要将该子网上的地址添加到接口。这很简单,只需输入:

ip addr add 2.255.255.254/8 dev eth1

这假设广播在 2.0.0.0/8,接口名称是eth1,并且 2.255.255.254 没有被其他主机占用。

【讨论】:

  • 如果他已经在wireshark中接收数据包,他应该不需要运行“ip addr”配置命令。 SO_BROADCAST 仅在发送数据包而不接收它们时是必需的。我在答案的代码中展示了无需在代码中设置该标志即可接收的能力。你从哪里得到你的报价?您的 SO_BROADCAST 链接中的引用如下:“SO_BROADCAST 设置或获取广播标志。启用后,允许数据报套接字将数据包发送到广播地址。此选项对面向流的套接字没有影响。 "
  • @MarkH 取决于交换机的智能程度,您可以看到内核/网卡将丢弃的许多数据包,因为它们不是发给您的(首先检查 dst MAC 的第 2 层,然后检查 dst第 3 层的 IP)。忽略 MAC/ARP,您的内核将不会接受子网广播数据包,除非它来自具有该子网 IP 的接口。
  • 仍然有人断言他在 Wireshark 的同一个盒子上接收它们,这表明它们正在进入盒子。
  • @MarkH 那是因为 Wireshark 将 NIC 放入 Promiscuous Mode,这使您可以查看线路上的数据包,否则这些数据包会被忽略/丢弃。
  • @MarkH 在第 2 层是和第 3 层没有。这里实际上是强制执行此操作的line in the Linux kernel code
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多