Получение внешней трансляции UDP с помощью Python

#python #python-3.x #networking #udp #udpclient

#питон #python-3.x #создание сетей #udp #udpclient

Вопрос:

У меня есть устройство в сети, на 192.168.123.204 которое передаются UDP-дейтаграммы (Artnet) как 2.168.123.204 2.255.255.255:6454 . (Сетевой адрес 192.168.123.204 указан, но дейтаграмма отправляется в 2.168.123.204 качестве источника.) Адрес 2.255.255.255 не может быть изменен (для этого нет настроек).

Мой скрипт Python запускается на устройстве 192.168.123.148 . Я могу получать дейтаграммы там с помощью wireshark: но связанный с Python сокет 0.0.0.0:6454 не может их получить. Привязка его к 2.168.123.204 or 2.255.255.255 не работает. Скрипт работает, так как я могу получать пакеты от 127.0.0.1 .

Если это невозможно решить с помощью Python, могу ли я перенаправить трансляцию UDP с помощью iptables (linux)?

Сеть:
Router 192.168.123.1
/
Broadcaster: 192.168.123.204Script: 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
 

Протокол Wireshark

Комментарии:

1.Я не специалист по сетевому взаимодействию, но то, что, по моему мнению, вы делаете, неправильно. В данный момент вы прослушиваете пакеты, предназначенные для вас. Что вам нужно, так это прослушивать необработанные пакеты. Для этого вы можете использовать github.com/KimiNewt/pyshark LiveCapture или вы можете использовать этот пример uv.mx/personal/angelperez/files/2018/10/sniffers_texto.pdf . Дайте мне знать, если это сработает для вас

2. Решайтесь. Либо ‘[он] транслирует UDP-дейтаграммы… на 2.255.255.255: 6454″ или «дейтаграмма отправляется на 2.168.123.204». Не то и другое одновременно.

3. Не могли бы вы опубликовать скриншот ваших пакетов wireshark?

Ответ №1:

Я неправильно истолковал ваш вопрос, вы имеете дело с широковещательными, а не многоадресными адресами. Ваша проблема в том, что у вас нет отправленного флага SO_BROADCAST

Адреса IPv4 делятся на одноадресные, широковещательные и многоадресные адреса. Адреса одноадресной рассылки определяют единый интерфейс хоста, широковещательные адреса определяют все хосты в сети, а адреса многоадресной рассылки адресуют все хосты в группе многоадресной рассылки. Дейтаграммы на широковещательные адреса могут быть отправлены или получены только тогда, когда установлен флаг сокета SO_BROADCAST. В текущей реализации сокетам, ориентированным на подключение, разрешено использовать только одноадресные адреса.

Здесь есть явный пример. Наиболее важной частью является:

     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 не принимается другим хостом.

Комментарии:

1. Ему не нужно запускать команду настройки «ip addr», если он уже получает пакеты в wireshark. SO_BROADCAST необходим только при отправке пакетов, а не при их получении. Я продемонстрировал возможность получения без установки этого флага в коде в моем ответе. Откуда вы взяли свое предложение? Цитата из вашей ссылки SO_BROADCAST гласит: «SO_BROADCAST Устанавливает или получает флаг широковещательной передачи. При включении сокетам дейтаграмм разрешено отправлять пакеты на широковещательный адрес . Эта опция не влияет на потоковые сокеты «.

2. @MarkH в зависимости от того, насколько умен коммутатор, вы можете увидеть много пакетов, которые ваше ядро / сетевой адаптер отбросит, потому что они не предназначены для вас (сначала проверяется dst MAC для уровня 2, а затем dst IP для уровня 3). Игнорируя MAC / ARP, ваше ядро не будет принимать широковещательный пакет подсети, если он не поступает на интерфейс с IP-адресом в этой подсети.

3. Тем не менее, по-прежнему существует утверждение, что он получает их на тот же ящик в wireshark, что предполагает, что они попадают в ящик.

4. @MarkH это потому, что Wireshark переводит сетевой адаптер в неразборчивый режим , это позволяет вам видеть пакеты на проводе, которые в противном случае были бы проигнорированы / отброшены.

5. @MarkH на уровне 2 — да, а на уровне 3 — нет. Вот буквально строка в коде ядра Linux , которая обеспечивает это.

Ответ №2:

Используйте необработанный сокет.

 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}')```
 

Комментарии:

1. У меня есть предчувствие, что это будет правильный подход, поскольку его пакеты, скорее всего, повреждены.