How to use the options to socket() for IPv6 multicast UDP? - python

I have a linux network interface that IPv6 multicast traffic is arriving on. There are both ICMPv6 packets and UDP packets arriving for the same multicast group.
I'm trying to receive the UDP traffic. The code below is in Python but I don't believe this is important; the Python library here is a pretty thin wrapper around the BSD sockets API. This is what I'm doing:
import socket
import struct
import select
import ipaddress
# .packet is a byte array
mcast_group = ipaddress.IPv6Address("ff22:5eea:675c:d1fc:17c::1").packed
address = ipaddress.IPv6Address("...ipv6 global scope address of interface...")
sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
group = struct.pack("16s i",
mcast_group,
3 #Interface ID
)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
sock.bind((address.compressed, 0, 0, 0))
recv_socks, _, _ = select.select([sock], [], [], 10)
if recv_socks:
print("Received packet")
With the code above, I appear to be receiving both the UDP and ICMPv6 packets. If I change the second parameter to socket.socket() from socket.SOCK_RAW to socket.SOCK_DGRAM then I receive no packets at all.
I've confirmed that both packet types are arriving on that interface, addressed to that multicast group, using wireshark.
There is something here that I've fundamentally not understood about socket(). Shouldn't specifying IPPROTO_UDP mean that I only get UDP packets? Shouldn't specifying SOCK_DGRAM still allow UDP packets through?
The Linux kernel is 4.9 on aarch64, in case that's important.

Eventually figured this out. Here's the correct way of doing this:
import socket
import struct
import select
import ipaddress
# .packet is a byte array
mcast_group = ipaddress.IPv6Address("ff22:5eea:675c:d1fc:17c::1")
address = ipaddress.IPv6Address("...ipv6 global scope address of interface...")
port_no = 5555
interface_idx = 3
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
group = struct.pack("16s i",
mcast_group.packed,
3 #Interface ID
)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
sock.bind((str(mcast_group), port_no, 0, interface_idx))
recv_socks, _, _ = select.select([sock], [], [], 10)
if recv_socks:
print("Received packet")
Important differences from the code in the question:
UDP (SOCK_DGRAM) sockets need to be bound to a port number. AFAICT this is why I was getting ICMP traffic as well as UDP with SOCK_RAW and no traffic at all with SOCK_DGRAM.
Instead of binding the socket to a unicast address on the interface of interest, bind it to the multicast group but use the interface index as the scope_id (the fourth in the address tuple passed to socket.bind()).

Related

Get Multicast Dest from Python Socket Recv

For my project, I need some way to know which multicast group a packet was originally sent to when it's received by my client. I've considered maintaining a map of sockets to multicast groups and identifying them this way, but surely there's some way to get the address from the datagram?
To listen, I'm currently using:
# Initialise socket for IPv6 datagrams
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# Allows address to be reused
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Binds to all interfaces on the given port
sock.bind(('', 8080))
# Allow messages from this socket to loop back for development
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, True)
# Construct message for joining multicast group
mreq = struct.pack("16s15s".encode('utf-8'), socket.inet_pton(socket.AF_INET6, "ff02::abcd:1"), (chr(0) * 16).encode('utf-8'))
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
data, addr = sock.recvfrom(1024)
and to send:
# Create ipv6 datagram socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
# Allow own messages to be sent back (for local testing)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, True)
sock.sendto("hello world".encode('utf-8'), ("ff02::abcd:1", 8080))
Which works, but the address when received is that of the sending machine. How can I see the multicast group it was sent to?
Thanks!
You only joined multicast group address "ff02::abcd:1". Therefore, any packet that is received on the socket must have been sent to that multicast address.

Sniff Multicast IPV6 UDP Datagrams using Python, need to send?

I wrote a piece of code largely inspired by mcast.py to sniff UDP datagrams on ipv6. However, something weird happens.
I can only see packets on the network if i first start the send() method
(i.e file.py -s in one window, and file.py in another), this allows me to see other UDP packets on the specified port. But if I dont start file.py -s , nd I only run file.py, No packets are sniffed. What gives?
Specifically, it seems that the call to sendto(...) is what makes the receive() be able to sniff. Is there a solution to this? I only intend to listen to specific UDP IPV6 packets, not really send. Is it because I need to send a join message to the IPV6 multicast group first, or? (I cant see any such packets in wireshark..)
import sys
import socket
import time
import struct
import win_inet_pton #https://pypi.python.org/pypi/win_inet_pton
SOURCE_PORT = 16000
DEST_PORT = 26000
MULTICAST_ADDR = 'ff02::1' #IPV6 Multicast Address
socket.IPPROTO_IPV6 = 41 #Workaround for missing constants on Win32 systems.
def send(): #Send packets
MY_IP6 = "fe80::8d96:ab99:7447:1c5c%11" #Link local IPV6 address..
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
sock.bind((MY_IP6, SOURCE_PORT))
while True:
sock.sendto("foo", (MULTICAST_ADDR, DEST_PORT))
#Needed to be able to receive packets..?
time.sleep(0.2)
def receive(): #Receive packets
addrinfo = socket.getaddrinfo(MULTICAST_ADDR, None)[0]
sock = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', DEST_PORT))
#Join Multicast grp.
group = socket.inet_pton(addrinfo[0], addrinfo[4][0])
mreq = group + struct.pack('#I', 0) # Magic?
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
while True:
d , s = sock.recvfrom(4000)
print str(s) + " " + repr(d)
if __name__ == "__main__":
if len(sys.argv) >= 2:
if sys.argv[1] == "-s":
send()
else:
receive()

Why are multicast messages on the same port but from different groups combined? [duplicate]

This question already has an answer here:
Duplicate packets in Python multicast receiver
(1 answer)
Closed 7 years ago.
On an Ubuntu 14.04 server I have two processes, each listening for multicast messages on the same port, but from different groups. I would not have expected this, but each sees traffic from both the group they want, and the other group.
As far as I can tell, this is known behavior (though I would call it a problem). I found this SO question, which provides some techniques for determining the multicast group that data is being received from, but it does not answer the question of why this is even happening in the first place. I would have thought that the underlying system network code would have filtered out messages on multicast groups I am not attempting to receive.
While I am mostly working in C++, I can provide some simple Python code to demonstrate the problem. In one terminal window I listen on multicast group 239.1.1.1, port 12345. In another terminal window on the same server I listen to multicast group 239.2.2.2, also port 12345. On a second machine, I transmit one multicast message on 239.1.1.1:12345, and a different message on 239.2.2.2:12345.
On svr3, the transmitter:
rcook#svr3:~$ mcast_snd 239.1.1.1 12345 "from 1.1.1"
multicasting from 1.1.1 to 239.1.1.1 port 12345
rcook#svr3:~$ mcast_snd 239.2.2.2 12345 "from 2.2.2"
multicasting from 2.2.2 to 239.2.2.2 port 12345
On svr2, window 1, the first receiver:
rcook#svr2:~$ mcast_rcv 239.1.1.1 12345
listening for multicast data on 239.1.1.1 port 12345
received 10 bytes: from 1.1.1
received 10 bytes: from 2.2.2
On svr2, terminal 2, the other receiver:
rcook#svr2:~$ mcast_rcv 239.2.2.2 12345
listening for multicast data on 239.2.2.2 port 12345
received 10 bytes: from 1.1.1
received 10 bytes: from 2.2.2
As you can see, both receivers receive both messages. Can someone explain why this is? And if there is a better way to configure the receivers to not receive messages from other groups, please share that too.
For reference, here is the code for mcast_rcv:
#!/usr/bin/python
import socket
import struct
import sys
if len(sys.argv) != 3:
print 'usage:', sys.argv[0], '<multicast group>', '<multicast port>'
sys.exit(0)
mcast_group = sys.argv[1]
mcast_port = int(sys.argv[2])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', mcast_port))
mreq = struct.pack('4sl', socket.inet_aton(mcast_group), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
print 'listening for multicast data on', mcast_group, 'port', mcast_port
while True:
msg = sock.recv(10240)
print 'received', len(msg), 'bytes:', msg
And here is the code for mcast_snd:
#!/usr/bin/python
import socket
import sys
if len(sys.argv) != 4:
print 'usage:', sys.argv[0], '<multicast group>', '<multicast port>', '<mess
age>'
sys.exit(0)
mcast_group = sys.argv[1]
mcast_port = int(sys.argv[2])
message = sys.argv[3]
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
print 'multicasting', message, 'to', mcast_group, 'port', mcast_port
sock.sendto(message, (mcast_group, mcast_port))
The main difference with the Receving multiple multicast feeds on the same port - C, Linux is you are using python.
In mcast_rcv you can enable the group filter disabling the IP_MULTICAST_ALL option, letting the INADDR_ANY like this :
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', mcast_port))
mreq = struct.pack('4sl', socket.inet_aton(mcast_group), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
# disable mc_all
if hasattr(socket,'IP_MULTICAST_ALL') != True:
socket.IP_MULTICAST_ALL = 49
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_ALL, 0)
As the python I used doesnot define socket.IP_MULTICAST_ALL, it could be needed to define it.
Problem solved. I need to specify the multicast group to bind to in the receiver, not just the port. This SO question clued me in. By leaving the address as '' in the Python code, or INADDR_ANY in my C++, I am basically telling the OS that I want all messages on that port, regardless of group. The system is giving me exactly what I am asking for, like it always does.
If I replace the line sock.bind(('', mcast_port)) with sock.bind((mcast_group, mcast_port)) in mcast_rcv, the behavior is as I expect and want. Merely joining the multicast group (sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)) is not enough to filter out non-group messages on that port.

python multicast with windows 2012 server receiver fail

Python multicast receiver running on windows 2012 server is not getting udp traffic. I inspected traffic and I see the udp traffic is coming across. I have a firewall udp rule for the multicast group with edge traversal allowed. I get no firewall block events. This same code works with windows 2008 server client.
On server (linux ubuntu):
PORT = 12345
import sys, os, time, socket, struct
from socket import gethostname
mc_ttl = 1
mc_group = '224.7.7.7'
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(0.2)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL,
struct.pack('b', mc_ttl))
d = "my data"
while True:
sock.sendto(d, (mc_group, PORT))
on Client (windows 2012 server):
PORT = 12345
import sys, os, time, socket, struct
from socket import gethostname
mc_group = '224.7.7.7'
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', PORT))
mreq = struct.pack("4sl", socket.inet_aton(mc_group), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
print sock.recv(10240)
Note the above code works on windows 2008 server. After doing some searching I saw that theres some issues with INADDR_ANY binding to wrong address (and the requisite advice against binding to ''). Inspecting incoming udp traffic on windows client I see the multicast traffic but reciever doesn't get it.
I tried the following after reading this post. Which also did not work.
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
grp = socket.inet_aton(mc_group)
ip = socket.gethostbyname(socket.gethostname())
iface = socket.inet_aton(ip)
mreq = grp + iface
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
sock.bind((ip, PORT))
Anyone successfully implemented receiver on windows 2012 server?
I updated the response on the other post to be a bit more explicit. Using socket.gethostbyname() is still getting the default interface, so you'll have to use socket.gethostbyname_ex() to get the extended list then select which interface you want.
Take a look at the difference:
socket.gethostbyname(socket.gethostname())
# "169.254.80.80"
socket.gethostbyname_ex(socket.gethostname())
# ("PCName", [], ["169.254.80.80", "192.168.1.10"])
In the above example, we would want to skip over the first 169.254 link-local address and select the desired 192.168.1.10 address. If socket.gethostbyname() was used in the above example, it would have been the link-local (169.254.*) address that would have been selected and your code wouldn't be doing much.
socket.gethostbyname_ex(socket.gethostname())[2][1]
# "192.168.1.10"

Send and receive IPv6 link-local multicast UDP datagrams in Python?

This following is a straightforward IPv4 UDP broadcast, followed by listening on all interfaces.
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
sock.bind(("", 1337))
sock.sendto("hello world", ("255.255.255.255", 1337))
while True:
data, addr = sock.recvfrom(0x100)
print "received from {0}: {1!r}".format(addr, data)
I want to adjust this to send and receive both IPv4 and IPv6.
I've poked around and read as much as I can and believe the following is roughly the route I need to take:
Create an IPv6 socket.
Add the socket to a link or site local multicast group.
Send the UDP packets to the multicast address for the group in use.
Further info I have is that I may need to bind to several interfaces, and tell the socket using setsockopt() that it should also receive multicast packets. Lastly getaddrinfo() can be used across the board to gracefully "drop back" to IPv4 where IPv6 isn't available.
I have much of this implemented, but stumble primarily on the multicasting parts. A complete code example in Python, or vivid description of the constants and addresses needed are preferred.
Here's a link to python mcast demo, does both IPv4 and IPv6.
I'm currently asking a question over here that involves getting the multicast address of a received message, but the source code answers your question!
To listen:
# Initialise socket for IPv6 datagrams
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# Allows address to be reused
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Binds to all interfaces on the given port
sock.bind(('', 8080))
# Allow messages from this socket to loop back for development
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, True)
# Construct message for joining multicast group
mreq = struct.pack("16s15s".encode('utf-8'), socket.inet_pton(socket.AF_INET6, "ff02::abcd:1"), (chr(0) * 16).encode('utf-8'))
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
data, addr = sock.recvfrom(1024)
and to send:
# Create ipv6 datagram socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
# Allow own messages to be sent back (for local testing)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, True)
sock.sendto("hello world".encode('utf-8'), ("ff02::abcd:1", 8080))
This is for python3.6, with python 2.7 I don't think the encodes are necessary. Also in the struct.pack line, I've seen variations of "16s15s" such as "4s", but I don't know what it is and what I have worked for me!

Categories

Resources