I am trying to work with multicast in python using the example script below which is also available at
this location.
MYPORT = 8123
MYGROUP_4 = '225.0.0.250'
MYGROUP_6 = 'ff15:7079:7468:6f6e:6465:6d6f:6d63:6173'
MYTTL = 1 # Increase to reach other networks
import time
import struct
import socket
import sys
def main():
group = MYGROUP_6 if "-6" in sys.argv[1:] else MYGROUP_4
if "-s" in sys.argv[1:]:
sender(group)
else:
receiver(group)
def sender(group):
addrinfo = socket.getaddrinfo(group, None)[0]
s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
# Set Time-to-live (optional)
ttl_bin = struct.pack('#i', MYTTL)
if addrinfo[0] == socket.AF_INET: # IPv4
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl_bin)
else:
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl_bin)
while True:
data = repr(time.time())
s.sendto(data + '\0', (addrinfo[4][0], MYPORT))
time.sleep(1)
def receiver(group):
# Look up multicast group address in name server and find out IP version
addrinfo = socket.getaddrinfo(group, None)[0]
# Create a socket
s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
# Allow multiple copies of this program on one machine
# (not strictly needed)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind it to the port
s.bind(('', MYPORT))
group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0])
# Join group
if addrinfo[0] == socket.AF_INET: # IPv4
mreq = group_bin + struct.pack('=I', socket.INADDR_ANY)
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
else:
mreq = group_bin + struct.pack('#I', 0)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
# Loop, printing any data we receive
while True:
data, sender = s.recvfrom(1500)
while data[-1:] == '\0': data = data[:-1] # Strip trailing \0's
print (str(sender) + ' ' + repr(data))
if __name__ == '__main__':
main()
The script however does not function when run on MAC OSX 10.8 using python 2.7 as expected and throws the following error:
[Errno 49] Can't assign requested address
Anyone got any idea what the problem is? I have tried to run it on an Ubuntu 13.04 virtual machine and it works just fine so it seems the problem is with MAC OSX
Try changing the following:
MYGROUP_6 = 'ff15:7079:7468:6f6e:6465:6d6f:6d63:6173'
to
MYGROUP6 = 'ff0n::nnn:nnnn:nnn:nnnn'
^ you will need to use netstat to find your IPv6 Multicast Group address(es):
$ netstat -gsv | grep ff0
Related
I'm trying to run the following IPv6 multicast example on my MacOS:
#!/usr/bin/env python
#
# Send/receive UDP multicast packets.
# Requires that your OS kernel supports IP multicast.
#
# Usage:
# mcast -s (sender, IPv4)
# mcast -s -6 (sender, IPv6)
# mcast (receivers, IPv4)
# mcast -6 (receivers, IPv6)
MYPORT = 8123
MYGROUP_4 = '225.0.0.250'
MYGROUP_6 = 'ff15:7079:7468:6f6e:6465:6d6f:6d63:6173'
MYTTL = 1 # Increase to reach other networks
import time
import struct
import socket
import sys
def main():
group = MYGROUP_6 if "-6" in sys.argv[1:] else MYGROUP_4
if "-s" in sys.argv[1:]:
sender(group)
else:
receiver(group)
def sender(group):
addrinfo = socket.getaddrinfo(group, None)[0]
s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
# Set Time-to-live (optional)
ttl_bin = struct.pack('#i', MYTTL)
if addrinfo[0] == socket.AF_INET: # IPv4
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl_bin)
else:
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl_bin)
while True:
data = repr(time.time())
s.sendto((data + '\0').encode(), (addrinfo[4][0], MYPORT))
time.sleep(1)
def receiver(group):
# Look up multicast group address in name server and find out IP version
addrinfo = socket.getaddrinfo(group, None)[0]
# Create a socket
s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
# Allow multiple copies of this program on one machine
# (not strictly needed)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind it to the port
s.bind(('', MYPORT))
group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0])
# Join group
if addrinfo[0] == socket.AF_INET: # IPv4
mreq = group_bin + struct.pack('=I', socket.INADDR_ANY)
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
else:
mreq = group_bin + struct.pack('#I', 0)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
# Loop, printing any data we receive
while True:
data, sender = s.recvfrom(1500)
while data[-1:] == '\0': data = data[:-1] # Strip trailing \0's
print (str(sender) + ' ' + repr(data))
if __name__ == '__main__':
main()
when I'm trying to run it in multicast receiver option [python3 test.py -6], I'm getting the following error:
Traceback (most recent call last):
File "test.py", line 80, in <module>
main()
File "test.py", line 28, in main
receiver(group)
File "test.py", line 70, in receiver
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
OSError: [Errno 49] Can't assign requested address
I have tried the same code on my Windows machines on the same network it works fine. But on MacOS I have also tried different multicast group addresses, for example: ff02::abcd:98 but didn't work. Is it because my MacOS kernel doesn't yet support IPv6 multicast? Is there any workaround?
MacOS kernel: Darwin Kernel Version 21.6.0
I'm trying to send packets using sockets, and was able to do so just fine until this morning. I'm not sure what's going on. The packets are showing up in tcpdump but the server and the client cannot connect to each other.
netcat.py
import socket
import argparse
import sys
import os
import re
import threading
def convertContent(content: str = "") -> bytes:
byteContent = []
# grab the hex from the content
for i in range(len(content)):
if content[i] == "\\" and content[i+1] == "x":
byteContent.append(f"{content[i+2]}{content[i+3]}")
# grab the non hex from the content, split it on the hex
stringContent = re.split(r"\\x.{2}", content)
byteIndex = 0
newContent = b""
# Re add the non-hex content, and the hex content
for word in stringContent:
newContent += word.encode()
if byteIndex < len(byteContent):
newContent += bytes.fromhex(byteContent[byteIndex])
byteIndex += 1
newContent = newContent.replace(b"\\n", b"\n").replace(b"\\r", b"\r")
return newContent
class Netcat():
'''
Netcat class that can be used to send/receive TCP packets
'''
BUFFER_SIZE = 1024
def __init__(self):
pass
#classmethod
def createSocket(cls):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Address might be in a TIME_WAIT status, ignore this
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Port might be in a TIME_WAIT status, ignore this
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
return sock
#classmethod
def send(cls, hostname: str = "127.0.0.1", srcPort: int = 0, destPort: int = 9999, content: str = "", buffer_size: int = 1024):
srcPort = int(srcPort)
destPort = int(destPort)
try:
content = convertContent(content=content)
except:
pass
sock = cls.createSocket()
# Set the source port before sending
sock.connect((hostname, destPort))
sock.sendall(content)
# shutdown might be redundant/unnecessary (tells connected host that we're done sending data)
sock.shutdown(socket.SHUT_WR)
while True:
data = sock.recv(buffer_size)
if len(data) == 0:
break
sock.close()
#classmethod
def receive(cls, port: int = 9999, buffer_size: int = 1024):
if port <= 1024 and os.geteuid() != 0:
print(f"Listening on port {port} requires superuser privileges!")
return
host = ""
sock = cls.createSocket()
sock.bind((host, port))
sock.listen(10)
conn, addr = sock.accept()
while True:
data = conn.recv(buffer_size)
if not data:
break
conn.close()
threading.Thread(target=Netcat.receive,daemon=True).start()
Netcat.send(content="test")
Note: I am sending the packets from one VM to another, rather than sending to myself, but it would be a lot to ask people to spin up a bunch of VMs to reproduce this. The hostname param in the send method should be the actual IP of the receiving machine
I've thrown some print statements, and the server stops on sock.accept(), while the client hangs on sock.connect((hostname, destPort))
I checked the hostname for the server, and it's listening on (0.0.0.0, 8888) (assuming 8888 is the port param), which means its listening on all interfaces on that port, so I dont know why its refusing to connect
I tcpdumped on the server, and its getting the packets, it gets a SYN, then sends out a SYN, ACK, but the rest of the packets are marked as re-transmissions.
I've tried looping the accept & connect lines, thinking maybe some sort of race condition was occurring, but no matter what I do the client can't connect to the server.
Edit: This works on my local machine, but still breaks when I try to send packets over the network. The first 2 steps of the handshake go through SYN & SYN, ACK, but not the third ACK
Don't bind in the client. Working example below, but minor changes to make a standalone script:
import socket
import threading
def receive(port: int = 9999, buffer_size: int = 1024):
host = ""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Address might be in a TIME_WAIT status, ignore this
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen()
conn, addr = sock.accept()
while True:
data = conn.recv(buffer_size)
if not data:
break
print(data)
conn.close()
def send(hostname: str = "127.0.0.1", destPort: int = 9999, content: str = b"test", buffer_size: int = 1024):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Address might be in a TIME_WAIT status, ignore this
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Removed bind
sock.connect((hostname, destPort))
sock.sendall(content)
# shutdown might be redundant/unnecessary (tells connected host that we're done sending data)
sock.shutdown(socket.SHUT_WR)
while True:
data = sock.recv(buffer_size)
if len(data) == 0:
break
sock.close()
threading.Thread(target=receive,daemon=True).start()
send()
Output:
b'test'
I'm wondering if it's possible to bind more than one multicast address without using multithreading(creating few sockets), just by creating one socket that can obtain data from two addresses (which is my current way of doing this).
My code looks like:
import socket
import struct
import time
MCAST_GRP2 = '239.0.1.105'
MCAST_GRP = '239.0.1.104'
MCAST_PORT = 12345
IS_ALL_GROUPS = True
#scan time in seconds
SCAN_TIME = 10
#sampling time in seconds
SAMPLING_TIME = 1
bufferUDP = 2048
totalSize = 0
bitrateList = []
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
# on this port, receives ALL multicast groups
sock.bind(("", MCAST_PORT))
sock2.bind(("", MCAST_PORT))
else:
# on this port, listen ONLY to MCAST_GRP
sock.bind((MCAST_GRP, MCAST_PORT))
sock2.bind((MCAST_GRP2, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
mreq2 = struct.pack("4sl", socket.inet_aton(MCAST_GRP2), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
sock2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq2)
print("_____.:| Starting analysis of multicasts! |:._____\n")
for x in range(SCAN_TIME):
stop = time.time() + SAMPLING_TIME
while (time.time()<stop):
totalSize += len(sock.recv(bufferUDP)) + len(sock2.recv(bufferUDP))
bitrateList.append(totalSize)
print(bitrateList[x]*8/(1000000))
totalSize = 0
bitrateList.pop(0)
txtfile = open("Bitrate_history_ip_{}.txt".format("TESTTT"),"w+")
for x in range(SCAN_TIME-1):
bitrateList[x] = bitrateList[x]*8/(1000000)
txtfile.write("{}.Bitrate was equal to: {} Mbps\n".format(x+1,bitrateList[x]))
txtfile.write("Maximum bitrate value was: {} Mbps\n".format(max(bitrateList)))
txtfile.write("Minimum bitrate value was: {} Mbps\n".format(min(bitrateList)))
print('End of test')
time.sleep(5)
And is based on:
How do you UDP multicast in Python?
.. just by creating one socket that can obtain data from two addresses
One cannot bind a socket to multiple IP addresses.
One can still handle multiple sockets in parallel without needing multiple threads or multiple processes. This is done with an event-based architecture and non-blocking sockets, see Non-blocking Sockets in the Python documentation for more details.
Linux Kernel offers a few ways to get timestamps for received (SO_TIMESTAMP, SO_TIMESTAMPNS, SO_TIMESTAMPING) or sent (SO_TIMESTAMPING) packets.
Kernel Doc: https://www.kernel.org/doc/Documentation/networking/timestamping.txt
Is there a way I can use that with Python? I don't see any SO_TIMESTAMP constant inside the Python sources. Tried 3.6.2 and GitHub master branch.
Right now, I can only use SIOCGSTAMP that gives me the timestamp of the last received packet and nothing seems available for sent packet timestamp.
Finally, I have been able to get the SO_TIMESTAMPNS value like this:
SO_TIMESTAMPNS = 35
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3))
s.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
raw_data, ancdata, flags, address = s.recvmsg(65535, 1024)
ancdata[0][2] is the hardware timestamp as a timespec(ulong, ulong).
Does work on Linux but not on Mac OS X. Not tested on Windows.
complete code, send and receive using python3
import struct
import time
import select
import socket
import sys
if(len(sys.argv)!=2):
print("usage: ",sys.argv[0]," <send|receive>")
sys.exit()
print(sys.argv[1]);
if(sys.argv[1]=='send'):
MESSAGE = "Hello, World!"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
s.connect(('localhost', 10000))
s.send(MESSAGE)
time.sleep(0.0001)
#time.sleep(5)
s.send(MESSAGE)
s.close()
else:
SO_TIMESTAMPNS = 35
#s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3))
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)
server.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
server.bind(('localhost', 10000))
server.listen(5)
inputs = [ server ]
message_queues = {}
outputs = []
while inputs:
print('\nwaiting for the next event')
readable, writable, exceptional = select.select(inputs, outputs, inputs)
for s in readable:
if s is server:
connection, client_address = s.accept()
print('new connection from', client_address)
connection.setblocking(0)
inputs.append(connection)
else:
raw_data, ancdata, flags, address = s.recvmsg(65535, 1024)
print('received ', raw_data, '-',ancdata,'-',flags,'-',address)
if(len(ancdata)>0):
#print(len(ancdata),len(ancdata[0]),ancdata[0][0],ancdata[0][1],ancdata[0][2])
#print('ancdata[0][2]:',type(ancdata[0][2])," - ",ancdata[0][2], " - ",len(ancdata[0][2]));
for i in ancdata:
print('ancdata: (cmsg_level, cmsg_type, cmsg_data)=(',i[0],",",i[1],", (",len(i[2]),") ",i[2],")");
if(i[0]!=socket.SOL_SOCKET or i[1]!=SO_TIMESTAMPNS):
continue
tmp=(struct.unpack("iiii",i[2]))
timestamp = tmp[0] + tmp[2]*1e-10
print("SCM_TIMESTAMPNS,", tmp, ", timestamp=",timestamp)
if(not raw_data):
print('closing after reading no data')
# Stop listening for input on the connection
if s in outputs:
outputs.remove(s)
inputs.remove(s)
s.close()
i receive data from multicast for my UDP sniffer, but only in IPv4.
My code looks like this,
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
except socket.error as msg:
print('Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1])
sys.exit()
mreq = struct.pack("4sl", socket.inet_aton('239.255.11.3'), socket.INADDR_ANY)
# receive a packet
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
packet = s.recvfrom(65000)
But i am receiving data only when i set IPv4 address, and i want also receive from IPv6 multicast address.
I will be really grateful for any ideas and sorry for my english. ;-)
this example gets a multicast on FF02::158 (IoTivity UDP CoAP) in Windows
import socket
import struct
address = ('', 5683)
interface_index = 0 # default
sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM)
sock.bind(address)
for group in ['ff02::158']: # multiple addresses can be specified
sock.setsockopt(
41, # socket.IPPROTO_IPV6 = 41 - not found in windows 10, bug python
socket.IPV6_JOIN_GROUP,
struct.pack(
'16si',
socket.inet_pton(socket.AF_INET6, group),
interface_index
)
)
while True:
data, sender = sock.recvfrom(1500)
while data[-1:] == '\0': data = data[:-1]
print(str(sender) + ' ' + repr(data))
fuller answer
https://stackoverflow.com/a/66943594/8625835
You need to use the sockopt IPV6_ADD_MEMBERSHIP, as the API between IPv6 and IPv4 is slightly different. This is a good example.
This is what I'm doing in my code:
mc_address = ipaddress.IPv6Address('ff02::1:2')
listen_port = 547
interface_index = socket.if_nametoindex('eth0')
mc_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
mc_sock.bind((str(mc_address), listen_port, 0, interface_index))
mc_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP,
struct.pack('16sI', mc_address.packed, interface_index))
This is for a DHCPv6 server, but you'll get the idea.
If you also want to get multicast packets transmitted by yourself you have to add:
mc_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, 1)