I have a device in the network at 192.168.123.204
which broadcasts UDP datagrams(Artnet) as 2.168.123.204
to 2.255.255.255:6454
. (The network address is 192.168.123.204
but the datagram is sent with 2.168.123.204
as source.) The address 2.255.255.255
can’t be changed (no setting for that).
My Python script runs on the device 192.168.123.148
. I can receive the datagrams there with wireshark: but a Python socket bound to 0.0.0.0:6454
can’t receive them. Binding it to 2.168.123.204
or 2.255.255.255
does not work. The script is working, since I can receive Packets from 127.0.0.1
.
If it can’t be solved with Python can I redirect the UDP broadcast with iptables (linux)?
Network:
Router 192.168.123.1
/
Broadcaster: 192.168.123.204 Script: 192.168.123.148
basic script:
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()
full script:
#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()
interfaces:
$ 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
Advertisement
Answer
I misread your question, you are dealing with broadcast not multicast addresses. Your problem is that you do not have the SO_BROADCAST flag sent
IPv4 addresses are divided into unicast, broadcast and multicast addresses. Unicast addresses specify a single interface of a host, broadcast addresses specify all hosts on a network and multicast addresses address all hosts in a multicast group. Datagrams to broadcast addresses can be only sent or received when the SO_BROADCAST socket flag is set. In the current implementation, connection-oriented sockets are only allowed to use unicast addresses.
There is an explicit example here. The most important part being:
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()
You will also need to add an address on that subnet to the interface. This is very simple just enter:
ip addr add 2.255.255.254/8 dev eth1
This assumes that the broadcast is on 2.0.0.0/8, the interface name is eth1
and that 2.255.255.254 is not taken by another host.