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
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.