Subscribing to UDP multicast with Python - python

This topic has been covered in a good amount of detail here, unfortunately, I'm still running into some trouble.
I'm trying to subscribe to a stream of motion-capture data from a windows box on my network. The mo-cap server is broadcasting over my network, and I can pick up the data with wireshark on my OS X machine ('the target'). I see that a message originating from the origin ip 204.102.224.2 (windows) broadcasting on 239.255.42.99 via UDP, to port 1511 is going out, as desired.
My python code is as follows:
PORT = 1511
MULTICAST_ADDRESS = '239.255.42.99'
SOCKET_BUFSIZE = 1024
datasock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
datasock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
datasock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
datasock.bind((MULTICAST_ADDRESS, PORT))
mreq = struct.pack("=4sl", socket.inet_aton(MULTICAST_ADDRESS), socket.INADDR_ANY)
datasock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
datasock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
datasock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, SOCKET_BUFSIZE)
datasock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while 1:
data = datasock.recv(rx.MAX_PACKETSIZE)
packet = rx.unpack(data, version=version)
if type(packet) is rx.SenderData:
version = packet.natnet_version
#print("NatNet version received:", version)
if type(packet) in [rx.SenderData, rx.ModelDefs, rx.FrameOfData]:
packet_dict = packet._asdict()
all_bodies = packet_dict['rigid_bodies']
for body in all_bodies:
contortion = body._asdict()['orientation']
euler = Quat([elem for elem in contortion]).equatorial
I think for my current issue it is safe to ignore some of the code in the loop, as some of it derives from a helper library I'm using (optirx). Note that I didn't start out with all the options, but decided to include all of them to see what should stay and what can go. I've tried various combinations and permutations. I'm on OS X 10.10.3

The problem is here:
datasock.bind((MULTICAST_ADDRESS, PORT))
You shouldn't bind the socket to the multicast address. You should bind it to '0.0.0.0'. The setsockopt call with IP_ADD_MEMBERSHIP takes care of joining the multicast group.

Related

How to properly send a broadcast message using python socket?

This is the configuration I am using.
Now I want the nodes to send packets in broadcast, but only to nodes directly connected.
sender.py:
import socket
import time
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
server.settimeout(0.2)
message = b"mymessage"
while True:
server.sendto(message, ('<broadcast>', 37020))
print("message sent!")
time.sleep(1)
receiver.py:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # UDP
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
client.bind(("", 37020))
while True:
data, addr = client.recvfrom(1024)
print(f"received: {data}, from: {addr}\n")
Let's say this is the configuration:
If I run receiver.py on r1,r2,r3, and I run sender.py on r4, i would expect to receive the messages only on the directly connected machines, since I set TTL=1. Instead I receive the message in ALL machines. The output of the receiving machines is received b"mymessage" from 172.17.0.4 (which is the interface I use to manage the virtual nodes, as seen in the picture. Since I don't want the nodes to use that interface, I tried to replace the <broadcast> parameter in sender.py with a more specific one. I first replaced it with 10.255.255.255, but I couldn't receive anything. 0.0.0.0 is not working either. I have a similar issue using multicast, but that would go out of topic. I am probably doing something very wrong here but i can't figure out what.
The final aim, as partially stated above, is to give each nodes the ability to connect ONLY to the adjacent ones, skipping anything that requires more than 1 hop.
Thank you for your time.

Retreiving multicast with python

I am working on a test tool for an existing piece of SW that outputs messages on multicast ip of 240.240.240.1 port 2349. The IP interface on which I am receiving the multicast messages is 192.168.0.4. I can observe these messages being pushed out onto the network via wireshark. Even netstat -ng shows the subscriptions. However, the receiver I wrote in python is not picking them up. I did borrow the code below from another thread, with the attempt to modify it to work in my situation.
import socket
import struct
import sys
multicast_group = '240.240.240.1'
server_address = (multicast_group, 2345)
# Create the socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Bind to the server address
sock.bind(server_address)
# Tell the operating system to add the socket to
# the multicast group on all interfaces.
group = socket.inet_aton(multicast_group)
src = bytearray(socket.inet_aton("192.168.0.4"))
mreq = bytearray(group)
mreq.extend(src)
sock.setsockopt(
socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP,
mreq)
# Receive/respond loop
while True:
print('\nwaiting to receive message')
data, address = sock.recvfrom(1024)
print('received {} bytes from {}'.format(
len(data), address))
print(data)
print('sending acknowledgement to', address)
sock.sendto(b'ack', address)
Any help would be appreciated in figuring out what I am doing wrong.
So I figured out my own problem. I had checked iptables as being an issue early on, with no luck. But I fixed/modified several things along the way to address the issues being seen. Turns out the code above works just fine, it was firewalld/iptables rules blocking the receipt of multicast.

Are my conclusions reasonable about this code receiving multicast using socket?

Basing on MC receiver from stack:
How do you UDP multicast in Python?
I would like to completely understand what is going on. Here is what I understand and what not:
As far as I understand: socket_name = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) => creating socket with IP proto ver 4, that will receive MC datagrams using UDP.
socket_name.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) this is responsible for setting parameter that socket could use the same address
(and in this case, port, because SO_REUSEADDR = SO_REUSEPORT for Multicasts).
(Paragraph multicast ~ How do SO_REUSEADDR and SO_REUSEPORT differ?)
if IS_ALL_GROUPS: socket_name.bind(('', MCAST_PORT)) means that if IS_ALL_GROUPS is true, bind socket to any addres, and else: socket_name.bind((MCAST_GRP, MCAST_PORT)) means bind socket to specific given IP address.
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) means convert IP address to binary form and socket_name.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) means that we add membership of group and after this line packets starting to arrive.
However I don't understand some things.
Why when I used IS_ALL_GROUPS = true where MCAST_GRP = '239.0.1.104' program could start and when the parameter was false, it didn't bound to specific multicast address? My logic is that when the parameter is true it binds to any MCast addres he gets from IGMP join messages and when parameter is false, it binds to specific given address. Am I correct?
I have multithread program that analyses bitrate to which i provide more than one address in form of list. When I set IS_ALL_GROUPS to false, program works correctly printing out e.g. 10.5, 4.5, 5.0 where each result is bitrate of one stream from unique address, however all of addresses share the same port 12345. When I set IS_ALL_GROUPS to true, program sums up results giving 20.0, 20.0, 20.0, do you know what may be the cause?
import socket
import struct
MCAST_GRP = '239.0.1.104'
MCAST_PORT = 12345
IS_ALL_GROUPS = True
socket_name = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
# on this port, receives ALL multicast groups
socket_name.bind(('', MCAST_PORT))
else:
# on this port, listen ONLY to MCAST_GRP
socket_name.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
print socket_name.recv(10240)
I think there are a few things going on:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
should probably be:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
the only "datagram" based protocol for IPv4 is UDP, the protocol field is just for "raw sockets" which are used by programs like tcpdump. this shouldn't matter though.
next:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
just allows multiple sockets to be bound to this same (addr, port) combination at once, i.e. you can run multiple copies of your program at once, or in quick succession. which I think is what you're saying
next:
sock.bind((host, port))
says you want to receive packets destined for the a given address and port. the special address '' (i.e. INADDR_ANY) means that the kernel shouldn't do any filtering on address. you probably don't want to bind to any specific address at this stage, see What does it mean to bind a multicast (UDP) socket?
next:
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
I'd suggest using "=4sl" for the format string as this gives me 8 bytes, rather than 16, and is consistent with a C struct ip_mreq on my (Linux) system. this difference in padding might just be a 32/64bit issue, and doesn't seem to hurt
to summarise: 1. the setsockopt(IP_ADD_MEMBERSHIP) call has asked your kernel's network stack to arrange (via IGMP) for multicast packets sent to MCAST_GRP to be delivered to a network interface attached to your host. 2. the bind() call has arranged for packets received via the network interface to make it to the socket in your process. it might be important that the bind() specifies INADDR_ANY so that all multicast packets that arrive are delivered to your process rather than being filtered
a couple of command lines that might be useful are:
tcpdump -i eth0 -vv 'ip proto 2'
which dumps IGMP packets, and:
tcpdump -i eth0 -vv 'net 224.0.0.0/4'
which dumps multicast packets
I'm not sure what your "multithread analysis program" is doing, as you've not given any relevant info. your posted code is also wrong, presumably socket_name and sock are the same? I'd also suggest using Python 3 as Python 2 is dying

Windows doesn't receive multicast IPv6 packets from all interfaces

I am trying to receive IPv6 multicast packets (sent to the ff02::1 address) on Windows using this python 2.7 code-
import socket
import win_inet_pton
import struct
socket.IPPROTO_IPV6=41 #because using python 2.7 on wondows
PORT = 1234
UDP_BROADCAST_IPv6 = "ff02::1"
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("",PORT)) # not working with "::" either
# Join multicast group
addrinfo = socket.getaddrinfo(UDP_BROADCAST_IPv6, None)[0]
group = socket.inet_pton(addrinfo[0], addrinfo[4][0])
mreq = group + struct.pack('#I', 0)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
while True:
msg=sock.recv(1024)
print msg
I send packets from another computer that is connected to my computer via Ethernet; in addition, my computer also has a WiFi interface. Although I'm able to see the relevant packets when sniffing the Ethernet connection with Wireshark, the packets are not received by this code.
However, when I disable the WiFi network card, the packets are received.
This makes me think that while the WiFi interface is enabled the code listens only to packets from that interface.
I read that binding to "" should enable receiving packets from all network interfaces, but for some reason it doesn't work for me.
Does anyone have any idea to something that I have forgotten to do? or a different way to solve this?
Thanks!
Solved it :)
So apparently IPv6 doesn't listen to multicast from all interfaces. This syntax
mreq = group + struct.pack('#I', 0)
was wrong. According to this, mreq is composed of the group id and the interface id, where 0 is the default interface (in my case- WiFi). In order to listen to multicast from other interfaces, the network interface index should be specified.
The network interface index is the number thet appears after the % in the ipv6 address when running ipconfig, and can also be found running "route print" in cmd.
I used this code to find it on python:
import netifaces as ni
import _winreg as wr # use "winreg" in python3
def get_ethernet_ipv6_ifindex():
x=ni.interfaces()
con_names=get_connection_name_from_guid(x)
ethernet_index= con_names.index('Ethernet')
addresses= ni.ifaddresses(x[ethernet_index])
brod_addr=addresses[socket.AF_INET6][-1]["broadcast"]
return int(brod_addr[brod_addr.find("%")+1:])
"""
Taken from the very helpful https://stackoverflow.com/questions/29913516/how-to-get-meaningful-network-interface-names-instead-of-guids-with-netifaces-un
"""
def get_connection_name_from_guid(iface_guids):
iface_names = ['(unknown)' for i in range(len(iface_guids))]
reg = wr.ConnectRegistry(None, wr.HKEY_LOCAL_MACHINE)
reg_key = wr.OpenKey(reg, r'SYSTEM\CurrentControlSet\Control\Network\{4d36e972-e325-11ce-bfc1-08002be10318}')
for i in range(len(iface_guids)):
try:
reg_subkey = wr.OpenKey(reg_key, iface_guids[i] + r'\Connection')
iface_names[i] = wr.QueryValueEx(reg_subkey, 'Name')[0]
except WindowsError:
pass
return iface_names
And then-
mreq = group + struct.pack('#I', get_ethernet_ipv6_ifindex())

Python setsockopt invalid argument with IP_ADD_MEMBERSHIP

I am trying to create socket that can receive multicast UDP packages on my Ubuntu using Python 2.7.3.
I have based my code on https://stackoverflow.com/a/1794373/1444854
Unfortunately I keep getting the same error:
self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
File "/usr/lib/python2.7/socket.py", line 224, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 22] Invalid argument
This is the code I have used
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(("", 12345))
self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
Where for 'mreq' I have tried various stuff, of which I show a few here. For the format in the struct examples I have used both with and without the network byte order indicator ('!'). The native one seems to double the size (from 8 to 16). I have also tried both signed and unsigned longs ('l' and 'L').
group = "127.0.0.1" # Or any other ipv4 address...
mreq = socket.inet_aton(group) + socket.inet_aton("0.0.0.0")
mreq = struct.pack("4sL", socket.inet_aton(self.group), socket.inet_aton("0.0.0.0"))
mreq = struct.pack("4sL", socket.inet_aton(self.group), socket.htonl(socket.INADDR_ANY))
mreq = struct.pack("4sL", socket.inet_aton(self.group), socket.INADDR_ANY)
At this point I do not know what the problem is, and could use some help with this.
I thought that the wrong 'mreq´ would provide an issue, but after so many tries I feel there must be something else I am missing.
Any help is appreciated. If more information is needed, I will gladly provide it.
EDIT:
The problem, which I totally overlooked, was the fact that I needed a proper multicast interface. Using 'ifconfig wlan0' or any other interface, you can check that MultiCast is in fact enabled. Furthermore I think any address between 224.3.* and 224.250.* has not been assigned for other uses. The 'mreq' that works for me is
mreq = struct.pack("!4sl", socket.inet_aton(self.group), socket.INADDR_ANY)
This thread is old but it might help to know that IPV4 Multicast addresses use reserved class D address range: 224.0.0.0 through 239.255.255.255
If IP address is out of this range, setsockopt will throw 'Invalid argument' error.
cheers

Categories