Choosing multicast network interface in Python - python

I have a server with two separate Ethernet connections. When I bind a socket in python it defaults to one of the two networks. How do I pull a multicast stream from the second network in Python? I have tried calling bind using the server's IP address on the second network, but that hasn't worked.

I recommend you don't use INADDR_ANY. In production multicast environments you want to be very specific with your multicast sockets and don't want to be doing things like sending igmp joins out all interfaces. This leads to hack-job workarounds when things aren't working like "route add -host 239.1.1.1 dev eth3" to get multicast joins going correctly depending on the system in question. Use this instead:
def joinMcast(mcast_addr,port,if_ip):
"""
Returns a live multicast socket
mcast_addr is a dotted string format of the multicast group
port is an integer of the UDP port you want to receive
if_ip is a dotted string format of the interface you will use
"""
#create a UDP socket
mcastsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#allow other sockets to bind this port too
mcastsock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#explicitly join the multicast group on the interface specified
mcastsock.setsockopt(socket.SOL_IP,socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(mcast_addr)+socket.inet_aton(if_ip))
#finally bind the socket to start getting data into your socket
mcastsock.bind((mcast_addr,port))
return mcastsock
In the mcastsock.bind you can also use '' instead of the address string, but I advise against this. With '', if you have another socket using the same port, both sockets will get eachothers data.

When binding your socket, try the values mentioned here:
For IPv4 addresses, two special forms
are accepted instead of a host
address: the empty string represents
INADDR_ANY, and the string
'' represents
INADDR_BROADCAST.
INADDR_ANY is also known as the wildcard address:
Sockets with wildcarded local addresses may receive messages directed to the specified port number and addressed to any of the possible addresses assigned to a host`
More here.

I figured it out. It turns out that the piece I was missing was adding the interface to the mreq structure that is used in adding membership to a multicast group.

For IPv4, the index of the network interface is the IP address; for IPv6 the index of the network interface is returned by the method socket.getaddrinfo.
The code below shows how to listen to multicast on all network interfaces:
from socket import AF_INET6, AF_INET
import socket
import struct
# Bugfix for Python 3.6 for Windows ... missing IPPROTO_IPV6 constant
if not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41
multicast_address = {
AF_INET: ["224.0.1.187"],
AF_INET6: ["FF00::FD"]
}
multicast_port = 5683
addr_info = socket.getaddrinfo('', None) # get all ip
for addr in addr_info:
family = addr[0]
local_address = addr[4][0]
sock = socket.socket(family, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((local_address, multicast_port))
if family == AF_INET:
for multicast_group in multicast_address[family]:
sock.setsockopt(
socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(multicast_group) + socket.inet_aton(local_address)
)
elif family == AF_INET6:
for multicast_group in multicast_address[family]:
ipv6mr_interface = struct.pack('i', addr[4][3])
ipv6_mreq = socket.inet_pton(socket.AF_INET6, multicast_group) + ipv6mr_interface
sock.setsockopt(
socket.IPPROTO_IPV6,
socket.IPV6_JOIN_GROUP,
ipv6_mreq
)
# _transport, _protocol = await loop.create_datagram_endpoint(
# lambda: protocol_factory(), sock=sock)

Related

All interfaces not being used despite multiple sockets

Despite binding two UDP sockets to 2 different addresses on different interfaces of a host, traffic flows through a single interface.
The topology of the network is as follows:
2 links between h2 and s1
1 link between h1 & s1
________
| |
h2 s1_______h1
|________|
I'm emulating 2 hosts and one switch on mininet. h1 is running a UDP server at 10.0.0.1:4243. The other host has 2 interfaces, with ips 10.0.0.2 & 10.0.0.4 . I'm making 2 sockets on h2, binding one to (10.0.0.2,9999) & the other socket to (10.0.0.4,8888). I'm running the code below, which should send packets on both interfaces alternately.
Instead, the first packets are sent on both the interfaces. All subsequent packets are sent over a single interface.
Client Code (Running on h2)
def client():
sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock1.bind(("10.0.0.2",9999))
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock2.bind(("10.0.0.4",8888))
while True:
text = 'The time is {}'.format(datetime.now())
data = text.encode('ascii')
sock1.sendto(data, ('10.0.0.1', 4243))
data, address = sock1.recvfrom(MAX_BYTES)
sock2.sendto(data, ('10.0.0.1', 4243))
data, address = sock2.recvfrom(MAX_BYTES)
Server Code (Running on h1)
def server():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('10.0.0.1', 4243))
print('Listening at {}'.format(sock.getsockname()))
while True:
data, address = sock.recvfrom(MAX_BYTES)
text = data.decode('ascii')
print('The client at {} says {!r}'.format(address, text))
text = 'Your data was {} bytes long'.format(len(data))
data = text.encode('ascii')
sock.sendto(data, address)
One packet is sent over both interfaces of h2. Subsequent packets are all sent and received only via interface with IP 10.0.0.2. On wiresharking, half the packets have src/dest IPs set to 10.0.0.4
From your comments, here is what I think is happening:
You have conflicting default routes
You have ip_forwarding enabled allowing internal routing
The bind call is essentially being negated due to #1 and #2
Conflicting default routes
From your comments, both interfaces probably have a /8. This means for h2 the kernel adds the same route for both something like:
10.0.0.0/8 dev h2-eth0 proto kernel scope link src 10.0.0.2
10.0.0.0/8 dev h2-eth1 proto kernel scope link src 10.0.0.4
This means the kernel wants to pick only one of these to send all 10.0.0.0/8 traffic to/from. You are trying to get around this with a bind call, but I don't believe Bind will overwrite the routeing table behavior.
ip_forwarding
If this flag is set, it also complicates matters because it may be allowing eth1 traffic via eth0.
bind
So bringing this all together, you bind your sockets to the individual IPs which are associated with individual interfaces, but the kernel only wants to route traffic destined for 10.0.0.1 out one interface and it chooses eth0. I think normally this would prevent eth1 traffic, but due to the routes and the ip_forward flag, eth1 traffic is routed through eth0 (after an initial ARP packet, the one packet you see).
Solution
Give h1's interface two IP addresses 10.1.0.1/16 and 10.0.0.1/16. Then give h2-eth0 ip address 10.0.0.2/16 and h2-eth1 ip address 10.1.0.2/16. Then have your program send data to 10.1.0.1 from 10.1.0.2, and 10.0.0.1 from 10.0.0.2.
By isolating the subnets you will prevent the root cause of the confusion, which was the conflicting routes.

How to make setsockopt IP_ADD_MEMBERSHIP honor the local interface address to receive multicast packets on only one specific interface?

Question
How to make setsockopt IP_ADD_MEMBERSHIP honor the local interface address to receive multicast packets on only one specific interface?
Summary
In a single Python process, I create three receive sockets (sockets S1, S2, and S3).
Each socket joins the same multicast group (same multicast address, same port) using setsockopt IP_ADD_MEMBERSHIP.
However, each socket joins that multicast group on a different local interface by setting imr_address in struct ip_mreq (see http://man7.org/linux/man-pages/man7/ip.7.html) to the address of particular local interface: socket S1 joins on interface I1, socket S2 joins on interface I2, and socket S3 joins on interface I3.
Here is the code for creating a receive socket:
def create_rx_socket(interface_name):
local_address = interface_ipv4_address(interface_name)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except AttributeError:
pass
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except AttributeError:
pass
sock.bind((MULTICAST_ADDR, MULTICAST_PORT))
report("join group {} on {} for local address {}".format(MULTICAST_ADDR, interface_name,
local_address))
req = struct.pack("=4s4s", socket.inet_aton(MULTICAST_ADDR), socket.inet_aton(local_address))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, req)
return sock
where the IP address of the local interface is determined as follows (I used debug prints to verify it obtains the correct address):
def interface_ipv4_address(interface_name):
interface_addresses = netifaces.interfaces()
if not interface_name in netifaces.interfaces():
fatal_error("Interface " + interface_name + " not present.")
interface_addresses = netifaces.ifaddresses(interface_name)
if not netifaces.AF_INET in interface_addresses:
fatal_error("Interface " + interface_name + " has no IPv4 address.")
return interface_addresses[netifaces.AF_INET][0]['addr']
On the sending socket side, I use setsockopt IP_MULTICAST_IF to choose the outgoing local interface on which outgoing multicast packets are to be sent. This works fine.
def create_tx_socket(interface_name):
local_address = interface_ipv4_address(interface_name)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except AttributeError:
pass
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except AttributeError:
pass
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(local_address))
# Disable the loopback of sent multicast packets to listening sockets on the same host. We don't
# want this to happen because each beacon is listening on the same port and the same multicast
# address on potentially multiple interfaces. Each receive socket should only receive packets
# that were sent by the host on the other side of the interface, and not packet that were sent
# from the same host on a different interface. IP_MULTICAST_IF is enabled by default, so we have
# to explicitly disable it)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0)
sock.bind((local_address, MULTICAST_PORT))
sock.connect((MULTICAST_ADDR, MULTICAST_PORT))
return sock
When the Python process receives a single individual multicast packet on (say) interface I1, all three socket (S1, S2, and S3) report a received UDP packet.
The expected behavior is that only socket S1 reports a received packet.
Here is some output from my script, along with some output from tcpdump, to illustrate the problem (<<< are manually added annotations):
beacon3: send beacon3-message-1-to-veth-3-1a on veth-3-1a from ('99.1.3.3', 911) to ('224.0.0.120', 911) <<< [1]
TX veth-3-1a: 15:49:13.355519 IP 99.1.3.3.911 > 224.0.0.120.911: UDP, length 30 <<< [2]
RX veth-1-3a: 15:49:13.355558 IP 99.1.3.3.911 > 224.0.0.120.911: UDP, length 30 <<< [3]
beacon1: received beacon3-message-1-to-veth-3-1a on veth-1-2 from 99.1.3.3:911 <<< [4a]
beacon1: received beacon3-message-1-to-veth-3-1a on veth-1-3a from 99.1.3.3:911 <<< [4b]
beacon1: received beacon3-message-1-to-veth-3-1a on veth-1-3b from 99.1.3.3:911 <<< [4c]
A single call to socket send [1] causes a single packet to be sent on the wire [2] which is received on the wire once [3] but which causes three sockets to wake up and report the received packet [4abc].
The expected behavior is that only one of the receive sockets would wake up and report the received packet, namely the one socket that joined the multicast group on the interface
Topology details
I have three Python scripts (1, 2, and 3) that are connected in the following network topology:
veth-1-2 veth-2-1
(1)------------------------(2)
| | |
veth-1-3a | | veth-1-3b | veth-2-3
| | |
| | |
veth-3-1a | | veth-3-1b |
| | |
(3)-------------------------+
veth-3-2
Each Python script is running in a network namespace (netns-1, netns-2, netns-3).
The network namespaces are connected to each other using veth interface pairs (veth-x-y).
Each Python process periodically sends a UDP multicast packet on each of its directly connected interfaces.
All UDP packets sent by any Python process on any interface always uses the same destination multicast address (224.0.0.120) and the same destination port (911).
Update:
I noticed the following comment in function ospf_rx_hook in file packet.c in BIRD (https://github.com/BIRD/bird):
int
ospf_rx_hook(sock *sk, uint len)
{
/* We want just packets from sk->iface. Unfortunately, on BSD we cannot filter
out other packets at kernel level and we receive all packets on all sockets */
if (sk->lifindex != sk->iface->index)
return 1;
DBG("OSPF: RX hook called (iface %s, src %I, dst %I)\n",
sk->iface->name, sk->faddr, sk->laddr);
Apparently BIRD has the same issue (at least on BSD) and solves it by checking which interface a received packet arrived on, and ignoring it when it is not the expected interface.
Update:
If the BIRD comment is true, then the question becomes: how can I determine which interface a packet was received on for a received UDP packet?
Some options:
Use IP_PKTINFO (http://man7.org/linux/man-pages/man7/ip.7.html and comments in Python socket bind 0.0.0.0, inspect incoming message). It appears that this may not be supported on all operating systems and/or in Python.
Check whether the source address is on the same subnet as the interface. This only works if the packets are sent over one hop which is indeed the case in my scenario. But does it work for IPv6 (yes, I think so) and on unnumbered interfaces (clearly not, but I wont support those)?
Some interesting observations on BIRD:
BIRD uses raw sockets for OSPF (because OSPF runs directly over IP, not over UDP) and in function sk_setup (file io.c) it calls sk_request_cmsg4_pktinfo which on Linux set the IP_PKTINFO socket option and on BSD sets the IP_RCVIF socket option.
BIRD has a macro INIT_MREQ4 to initialize the request for IP_ADD_MEMBERSHIP. Interestingly, for Linux it sets the multicast address (imr_multiaddr) and the local interface index (imr_ifindex) whereas for BSD it sets the multicast address (imr_multiaddr) and the local interface address (imr_interface)
I am "answering" my own question by documenting the work-around that I ended up using.
Note that this is only a work-around. In this work-around we detect packets that are received on the "wrong" socket and ignore them. If someone has a "real" solution (i.e. one that avoids the packets being received on the wrong socket in the first place), I would love to hear it.
Let's say that we join a multicast group when we create a receive socket as follows:
req = struct.pack("=4s4s", socket.inet_aton(MULTICAST_ADDR), socket.inet_aton(local_address))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, req)
Here, MULTICAST_ADDR is the multicast address and local_address is the IP address of the local interface on which the group is joined.
Apparently, what this really means is that IP multicast packets received on that interface will be accepted and passed on to the higher layer, which is UDP in this case.
Note, however, that if there are multiple UDP sockets S1, S2, S3, ... that have joined the same multicast group (same multicast address and same port) on different interfaces I1, I2, I3, ... then if we receive a multicast packet on ANY of the interfaces I1, I2, I3... then ALL the sockets S1, S2, S3, ... will be notified that a packet has been received.
My work-around works as follows:
(1) For each socket on which we join the mulitcast group, keep track of the if_index for the interface on which we did the join, and
interface_index = socket.if_nametoindex(interface_name)
(2) Use the IP_PKTINFO socket option to determine on which interface index the packet was actually received,
To set the option:
sock.setsockopt(socket.SOL_IP, socket.IP_PKTINFO, 1)
To retrieve the if_index from the PKTINFO ancillary data when the packet is received:
rx_interface_index = None
for anc in ancillary_messages:
if anc[0] == socket.SOL_IP and anc[1] == socket.IP_PKTINFO:
packet_info = in_pktinfo.from_buffer_copy(anc[2])
rx_interface_index = packet_info.ipi_ifindex
(3) Ignore the received packet if the if_index of the socket does not match the if_index on which the packet was received.
A complicating factor is that neither IP_PKTINFO nor in_pktinfo are part of the standard Python socket module, so both must be manually defined:
SYMBOLS = {
'IP_PKTINFO': 8,
'IP_TRANSPARENT': 19,
'SOL_IPV6': 41,
'IPV6_RECVPKTINFO': 49,
'IPV6_PKTINFO': 50
}
uint32_t = ctypes.c_uint32
in_addr_t = uint32_t
class in_addr(ctypes.Structure):
_fields_ = [('s_addr', in_addr_t)]
class in6_addr_U(ctypes.Union):
_fields_ = [
('__u6_addr8', ctypes.c_uint8 * 16),
('__u6_addr16', ctypes.c_uint16 * 8),
('__u6_addr32', ctypes.c_uint32 * 4),
]
class in6_addr(ctypes.Structure):
_fields_ = [
('__in6_u', in6_addr_U),
]
class in_pktinfo(ctypes.Structure):
_fields_ = [
('ipi_ifindex', ctypes.c_int),
('ipi_spec_dst', in_addr),
('ipi_addr', in_addr),
]
for symbol in SYMBOLS:
if not hasattr(socket, symbol):
setattr(socket, symbol, SYMBOLS[symbol])
For a complete working example and for important information about the copyright and 2-clause BSD-license for the source of this code see file https://github.com/brunorijsman/python-multicast-experiments/blob/master/beacon.py

Invalid argument when trying to bind python socket to IPv6 address

I'm trying from my virtual machine to bind my socket in python to the address of my other virtual machine so that I could sniff the frames that are exchanged by the two. So here's my code
import socket
UDP_IP = "fe80:849b:21f4:624c:d70b"
UDP_PORT = 61627
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
sock.bind((UDP_IP, UDP_PORT))
while True:
data, addr = sock.recvfrom(1024)
print(data)
When I try to execute my python, I get an error message :
sock.bind((UDP_IP, UDP_PORT))
socket.error: [Error 22] Invalid argument
What am I doing wrong, thanks in advance !
I bet, you had found the answer already, but nevertheless on the first glance I see two issues with your code:
In IPv6 fe80::/10 address block is reserved for Link-local addresses. These addresses are valid for local subnet only, therefore they are used with scope suffixes like %eth0 to specify proper scope. Where eth0 is a name of the network interface for which the IPv6 address is valid.
So for my MacOS laptop UDP_IP address could look like:
UDP_IP = "fe80:849b:21f4:624c:d70b%en0"
Next, specifying (UDP_IP, UDP_PORT) tuple is absolutely valid for IPv4 but for IPv6 this tuple should also contain flow_info and scope_id fields. Sometimes, flow_info and scope_id could be omitted or missed: (UDP_IP, UDP_PORT, 0, 0). It may work sometimes, but it's not the right way to use in the code.
So, these fields can be fetched via socket.getaddrinfo(IP, PORT) which returns a list of tuples for each address family and socket_kind. Filter them with something like that:
for ainfo in socket.getaddrinfo(UDP_IP, UDP_PORT):
if ainfo[0].name == 'AF_INET6' and ainfo[1].name == 'SOCK_DGRAM':
target_address = ainfo[4]
break
sock.bind(target_address)
I'm not sure, if I wrote the sample code right, as I didn't test it, check it before usage.

UDP Hole Punching fails on non-symmetric NATs

For a little while now, I got a serious issue on my UDP hole punching system which may involve things that are out of my reach now. I'll try to explain as precisely as possible because this is really bothering me :
UDP Hole punching method involves a third host S, which basically is public (not behind any kind of NAT or persistent firewall), that will be used by two clients A and B to get to know each other. Client A sends a udp packet to S, such that S can know its public IP and Port (which were mapped by A's NAT). Client B does the same. Then S matches A and B by sending to A B's public address, and A's public address to B. Therefore, they each know each other's address and can proceed to communicate.
So far so good, but here is the catch : it works only on special cases.
I am aware of the problem caused by Symmetric NATs (NATs that map your private IP and Port to a specific different address for each destination address you want to reach, so let's say I send a packet to S1 and S2, S1 will see a public address with port 50263 whereas S2 will see 50264). I am also aware that some Non-Symmetric NAT require you to send a UDP packet to a specific address before allowing to receive one from the same address.
So what I've understood so far is that as long as your NAT is not Symmetric, when opening a new socket, and send whatever byte array, your NAT will allocate a public IP and port to it. The public server tells your peer this public address such that it can communicate with you. You then have to send a packet to that peer in order to allow him to reply.
But it actually doesn't work everytime. I've tried at my university appartment, and managed to connect to myself using my public IP address, even by being behind a NAT. I also managed to send a UDP packet to a friend's, but it worked only once in the hundred of tests I made. I am now trying at home and it doesn't work at all.
I've setup 2 public VPS on the internet, and sent one packet to each one of them to see if my NAT was Symmetric, but no, the port that gets displayed on both of them is the same. I just can reach myself only from those public servers. Using Wireshark, I observe the packets leaving my computer but it seems that they get dropped beofre going back.
I've written simple python scripts to try out sending simply to myself :
Server
import socket
import sys
from helper import *
# This script is used to allow a client to know which external address it has.
def main():
# Port used to receive client requests
port = 6666
# We create a UDP socket on this port
sockfd = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
sockfd.bind (("", port))
print ("Address discoverer started on port %d" % port)
# Server loop
while True:
# We wait for any client to request for his address.
data, addr = sockfd.recvfrom(1)
# Once a client has sent a request, we send him back his external address, which we obtained using the packet he sent us.
sockfd.sendto (addr2bytes(addr), addr)
print ("Client %s:%d requested his address" % addr)
if __name__ == "__main__":
main()
Client
import sys
import socket
from helper import *
def main():
master = ("xxx.xxx.xxx.xxx", 6666)
me = ("yyy.yyy.yyy.yyy", 6666)
sockfd = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
sockfd.bind(('',6666))
sockfd.sendto (bytearray([0]), master)
data, addr = sockfd.recvfrom (6)
a = bytes2addr(data)
print ('My address is %s:%d' % a)
for i in range (0, 10):
sockfd.sendto ('hi myself'.encode('utf-8'), me)
data, addr = sockfd.recvfrom (1024)
print ("Received : " + data.decode('utf-8'))
if __name__ == "__main__":
main()
Executed on one of my public VPS, it works. Executed at home, I don't receive anything. It seems that sending from a public host grants all accesses, but behind a NAT is completely different.
Am I missing something fundamental ? I think I did pretty much every possible test but no success.
Depending on what you want to achieve, you may want to use IGD (spec) to retrieve your public IP and/or configure port forwarding automatically. Most home routers support UPnP IGD, from what I can tell.
Here's an example script that uses miniupnpc, a minimal UPnP IGD client, and the ipaddress and socket modules from python's standard library:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
IGDTestScript.py
================
Description: UPnP IGD example script
Author: shaggyrogers
Creation Date: 2018-03-28
License: None / Public domain
"""
import ipaddress
import socket
import sys
import miniupnpc
def main(args):
# Get IP address for default interface
allhosts = ipaddress.ip_address(socket.INADDR_ALLHOSTS_GROUP)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect((allhosts.compressed, 0))
localIP = ipaddress.ip_address(sock.getsockname()[0])
if localIP.is_global:
print(f'Public IP: {localIP} (no NAT)')
return 0
print(f'Local IP: {localIP}')
# Discover and connect to IGD device
igd = miniupnpc.UPnP()
assert igd.discover() > 0
assert igd.selectigd() != ''
# Get public IP
publicIP = ipaddress.ip_address(igd.externalipaddress())
assert publicIP.is_global
print(f'Public IP: {publicIP}')
# Dump port mappings
i, mapping = 0, igd.getgenericportmapping(0)
while mapping:
print(f'Port mapping {i}: {mapping}')
i += 1
mapping = igd.getgenericportmapping(i)
# Create/update port mapping
externalPort, internalPort, protocol = 1234, 4321, 'TCP'
assert igd.addportmapping(externalPort, protocol, localIP.compressed,
internalPort, 'test mapping', '')
print((f'Added port mapping: {protocol} {publicIP}:{externalPort}'
f' --> {localIP.compressed}:{internalPort}'))
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
Sample output:
Local IP: 192.168.1.123
Public IP: 203.0.113.1
Port mapping 0: (8908, 'TCP', ('192.168.1.123', 9100), 'tcp_map', '1', '', 0)
Port mapping 1: (22222, 'UDP', ('192.168.1.123', 22222), 'udp_map', '1', '', 0)
Added port mapping: TCP 203.0.113.1:1234 -> 192.168.1.123:4321

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