In Python when we want to make a script that listens for multicast traffic we set the IP_ADD_MEMBERSHIP option of the socket with a value that consists of the multicast group address and the address of a local interface on which it will listen for the group's traffic.
Many examples on the Internet pass to IP_ADD_MEMBERSHIP the INADDR_ANY wildcard address as the local interface, and some of them state that this will make the socket to listen on all interfaces for multicast packets. However the Linux ip(7) man page states that when using INADDR_ANY
"an appropriate interface is chosen by the system"
and the freebsd man page says that it will choose the "default interface".
So either some answers online are wrong, or there's something that I'm missing here. I believe there's a confusion with INADDR_ANY when used as a parameter in IP_ADD_MEMBERSHIP, and INADDR_ANY when used as a parameter in bind() (represented by an empty string) but I'm not really sure. Can someone please clarify what is happening with INADDR_ANY or 0.0.0.0 when used in IP_ADD_MEMBERSHIP (i.e. it chooses the default interface or all interfaces) and if it behaves differently when used with bind?
When INADDR_ANY is given as the address in a bind call, this causes the socket to listen on the given port for any network interface.
After calling bind in this way, you'll see an entry like this in the output of the netstat command:
udp 0 0 0.0.0.0:46162 0.0.0.0:*
This is a UDP socket that was bound to INADDR_ANY with port 46162.
When used as the interface address when setting the IP_ADD_MEMBERSHIP option, INADDR_ANY indicates that the OS will chose an interface to join the given multicast group on, i.e. the "default" network interface.
It does not mean that it will join the group on all interfaces. To do that, you would need to itereate over all network interfaces and set the IP_ADD_MEMBERSHIP option on each one.
I've worked with Linux, Solaris, FreeBSD, and Windows, and none of them joins a multicast group on all interfaces when using INADDR_ANY.
Related
I've set up a VM and am trying to use a Python script to send IPv6 messages to my computer so I can analyze it using WireShark. The host computer is Windows, and I want to see messages from a Linux VM. However, when I try to send messages, socket.bind() returns with an invalid argument error. What IPv6 address should I use as the local IP for binding sockets? I'm 99% sure that the error is coming from binding to an invalid IP. So what should I use as the IP? Here is the output of nmcli dev show, hopefully this is enough information to help me figure this out. If it's not, let me know and I'll add more info.
[eng#peter test_scripts]$ nmcli dev show
GENERAL.DEVICE: enp0s3
GENERAL.TYPE: ethernet
GENERAL.HWADDR: 08:00:27:F7:9A:17
GENERAL.MTU: 1500
GENERAL.STATE: 100 (connected)
GENERAL.CONNECTION: System enp0s3
GENERAL.CON-PATH: /org/freedesktop/NetworkManager/ActiveConnection/0
WIRED-PROPERTIES.CARRIER: on
IP4.ADDRESS[1]: 10.0.2.15/24
IP4.GATEWAY: 10.0.2.2
IP4.DNS[1]: 10.0.2.3
IP4.DOMAIN[1]: stc.syrres.com
IP6.ADDRESS[1]: fe80::a00:27ff:fef7:9a17/64
IP6.GATEWAY:
GENERAL.DEVICE: lo
GENERAL.TYPE: loopback
GENERAL.HWADDR: 00:00:00:00:00:00
GENERAL.MTU: 65536
GENERAL.STATE: 10 (unmanaged)
GENERAL.CONNECTION: --
GENERAL.CON-PATH: --
IP4.ADDRESS[1]: 127.0.0.1/8
IP4.GATEWAY:
IP6.ADDRESS[1]: ::1/128
IP6.GATEWAY:
I've tested 'fe80::a00:27ff:fef7:9a17/64', 'fe80::a00:27ff:fef7:9a17' and others, but still can't get it to bind. What IPv6 address should I use?
If you want to listen, your best bet is to bind to :: which is the equivalent of binding to 0.0.0.0.
If you want to connect to that server, keep in mind you are using link-local addresses, which require a scope ID in order to function properly.
For example, on Linux, to connect to host fe80::1 on interface eth0 you would connect to fe80::1%eth0. If you're dealing with the socket module, don't forget to either use getaddrinfo() or be very careful to populate scopeid.
I have this basic UDP forward script in Python 3.
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
class Forward(DatagramProtocol):
def __init__(self, targetTuples):
print ('in init, targetTuples are ', targetTuples)
self._targetTuples = targetTuples
def datagramReceived(self, data, hostAndPort):
print ('self._targetTuples is ', self._targetTuples)
for (targetHost, targetPort) in self._targetTuples:
self.transport.write(data, (targetHost, targetPort))
reactor.listenUDP(5005, Forward([('10.35.203.24', 5000), ('10.35.200.251', 5005)]))
reactor.run()
So I'm listening on port 5005 UDP, and forwarding those packets to the two IP addresses and different ports.
My question is this -
How do I preserve the original IP address that twisted gets while listening on port 5005?
Source IP (10.1.1.1) --> Twisted (10.30.1.1) --> Multiple Destinations
How can I get Multiple Destinations to see the packet source preserved from the Source IP of (10.1.1.1) ?
When sending UDP datagrams using the BSD socket API (around which, as a first approximation, Twisted is a wrapper), the source address is set to the address the socket is bound to. You can specify the IP of the bind address for a UDP socket in Twisted by passing a value for the interface argument to reactor.listenTCP. However, you are typically restricted in what addresses you are allowed to bind to. Typically the only values allowed are addresses which are assigned to a local network interface. If you are forwarding traffic for 10.1.1.1 but you are on host 10.30.1.1 then you probably cannot set the source address of the UDP packets you send to 10.1.1.1 because that address isn't assigned to a network interface on the host doing the forwarding. If you assigned it to one, routing on your network would probably break in weird ways because then two different hosts would have the same IP address.
This doesn't mean it's not possible to do what you want - but it does mean you probably cannot do it using Twisted's basic UDP support. There are a number of other approaches you could take. For example, you can rewrite source addresses using iptables on Linux. Or you can operate on the IP level and parse and generate full UDP datagrams yourself letting you specify any source address you want (you can do this with Twisted on Linux, too, using twisted.pair.tuntap). There are probably a number of other ways. The best solution for you may depend on which platforms you're targeting and the particular reasons you want to do this kind of forwarding.
I came across a zeromq example code
subscriber = ctx.socket(zmq.XSUB)
subscriber.connect("tcp://localhost:6000")
publisher = ctx.socket(zmq.XPUB)
publisher.bind("tcp://*:6001")
The subscriber (client) is connecting to local host port 6000. But the publisher (the server) is binding to *:6001
What does this mean?
It means "all interfaces, port 6001" - a given computer can have more than one network interface (a trivial example would be that the average computer's LAN IP and it's localhost address are two different interfaces. The * means to accept connections from any of them.
What does it mean to .bind() at port 6000 while .connect() aims at port 6001?
Simply put, these two peers will not meet at this attempt to setup a link to communicate.
While wildcard does work for all <localhost> interfaces, it does not for port#-s.
A .bind() side can open it's receiving policy to accept a connection from any interface "behind" the * wild card, but the port#-s must match.
No exceptions, no excuse.
I'm trying to learn how to directly (no libraries) send DHCP request from python on multi-homed machine (multiple interfaces).
I've looked at pydhcplib, but still do not get it.
This code send DHCP packet on specific interface (eth3 in my case - no IP assigned), but it sends with eth0 IP address. How to change my src IP to 0.0.0.0?
dhcp-message is truncated in this example
LOCAL_PORT=68
SERVER_PORT=67
LOCAL_IP="0.0.0.0"
BCAST_IP="255.255.255.255"
LISTEN_DEV="eth3"
MSG_SIZE=2048
Conn=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Conn.settimeout(5)
Conn.setsockopt(socket.SOL_SOCKET,IN.SO_BINDTODEVICE,LISTEN_DEV+'\0')
Conn.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
Conn.bind((LOCAL_IP, LOCAL_PORT))
# Create DHCP-Discovery
msg="010106003f7d1664......"
Conn.sendto(msg.decode("hex"),(BCAST_IP,SERVER_PORT))
received = Conn.recv(MSG_SIZE)
Conn.close()
I assume you already know about the Advanced Interprocess Communication Tutorial.
Spoiler Alert: If you want to jump straight to the bottom line, have a look at the DHCP Query recipe.
Edit:
The special value INADDR_ANY (0.0.0.0, or the empty string '' in a python socket) is not an IP address.
"When an address is specified as INADDR_ANY (a manifest constant
defined in < netinet/in.h >), the system interprets the address as 'any
valid address'."
From RFC 2131:
In the case of a client using DHCP for initial configuration
(before the client's TCP/IP software has been completely
configured), DHCP requires creative use of the client's TCP/IP
software and liberal interpretation of RFC 1122. The TCP/IP
software SHOULD accept and forward to the IP layer any IP packets
delivered to the client's hardware address before the IP address is
configured; DHCP servers and BOOTP relay agents may not be able to
deliver DHCP messages to clients that cannot accept hardware
unicast datagrams before the TCP/IP software is configured.
Presumably you're running this program on a system where the ethernet interfaces have already been configured and have valid IP addresses. I'm not sure why you'd want the source IP address to be 0.0.0.0, but perhaps you could set the interface IP to 0.0.0.0 with ifconfig to get the effect you want.
Or you could use a RAW socket and build the IP and UDP headers yourself to contain anything.
After searching high and low across the google i have not found the definitive answer to the following question:
by roughly following the following guide:
http://twistedmatrix.com/documents/10.2.0/core/howto/udp.html#auto3
How is it possible to bind twisted Multicast listener to ONLY the multicast address and on specific or all interfaces.
While looking at reactor.listenMulticast it does not provide abstraction for hardware interface only an pseudo interface represented by an IP address. I cant find a method to bind only on the multicast address e.g. 224.0.0.1 of a specific interface or all interfaces.
Can anyone provide further information on this?
reactor.listenMulticast returns a twisted.internet.udp.MulticastPort object. That object owns the socket you're listening on. So hang on to the result of reactor.listenMulticast and set the appropriate socket option (in this case it looks like SO.BINDTODEVICE) along with a null terminated device string.
import IN, socket, struct
udp_port = reactor.listenMulticast(8005, MulticastServerUDP())
dev = "eth0" + "\0"
udp_port.socket.setsockopt(socket.SOL_SOCKET, IN.SO_BINDTODEVICE, dev)
reactor.run()
It would be nice if this were exposed directly through the listenMulticast call but assuming this works it would be a pretty easy patch. Let me know if this solves your problem.
The other answers may solve the problem, but there is a "more twisted" (and probably easier) way to listen to a multicast group on multiple interfaces.
Listening to a multicast group on one or more interfaces
To listen to a multicast group on one or more interfaces, provide the IP of each desired interface in multiple calls to the protocol's transport.joinGroup() method as the 'interface' argument.
Below is an example that works on my Linux box, and will make your system listen to multicasts on specific interfaces. Replace the IP addresses with ones belonging to your system.
#!/usr/bin/env python
from twisted.internet import protocol
from twisted.internet import reactor
class MyProtocol(protocol.DatagramProtocol):
def startProtocol(self):
# Join a multicast group, 224.0.0.9, on the interface belonging
# to each of these IPs.
# XXX Replace the interface_ips with one or more IP addresses belonging
# to your system.
for interface_ip in ["192.168.2.2", "10.99.1.100"]:
self.transport.joinGroup("224.0.0.9", interface_ip)
if __name__ == "__main__":
reactor.listenMulticast(1520, MyProtocol())
reactor.run()
You can check that the interface is listening to the new multicast group using the /sbin/ip maddr show command. Find the desired interface name in the command output, and verify that the multicast group shows up beneath it.
The UDP Server example linked in the original post should be able to do the same thing by changing the call to joinGroup() to include the second IP address argument, as above.
Sending multicasts from a particular IP
If you're receiving multicast data on a socket, chances are you will want to send multicast data too -- possibly out of multiple interfaces. Since it's closely related and there are very few examples around I'll throw it in here. Inside a twisted.internet.protocol.DatagramProtocol object you can use the self.transport.setOutgoingInterface() method to control the source IP that will be used for subsequent calls to self.transport.write(). Example showing sending a message from multiple IPs/interfaces:
class MyProtocol(protocol.DatagramProtocol):
# ...
def send_stuff(self, msg):
for src_ip in ["10.0.0.1", "192.168.1.1"]:
self.transport.setOutgoingInterface(src_ip)
self.transport.write(msg, ("224.0.0.9", 1520))
Suppose these IPs were assigned to two different interfaces. Sniffing with Wireshark you would see the message sent out of the first interface, then the second interface, using each IP as a source IP address for the respective transmission.
A simple, if heavy handed approach could be to just specify a route via something like:
sudo route add 239.255.0.0 -interface en0 -netmask 255.255.0.0