Python - Twisted - Simple UDP forwarder. Preserve source IP? - python

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.

Related

why socket binding in server needs its ip address >

For a connection is to be made, a bounded socket should be listening for clients. Client needs to know both ip address and port. For bounding a socket why we need an ip address of the server itself when the program(which listens for clients) itself is running on the server?
Simply because a server has multiple addresses, at least the loopback one at 127.0.0.1 (IP v4) and one per physical network interfaces. For example a corporate proxy has commonly two interfaces, one on the internal network and one on the public one. Most have a third one for the DMZ. Being member of different networks, those interfaces must have different addresses. And it make sense to open some services on only one interface.
But you also can use the ANY address (0.0.0.0 in IPv4) that means to accept connections on any interface.

Capture destination IP in TCP Python SocketServer

I have a Python script that is running on a Linux server that has a dozen IP addresses associated with it. I implemented a TCPSServer from Python's socketserver library and had it listen on all network interfaces.
Several devices will be connecting to this server, and we need to be able to somehow capture the ip address of the destination (not the IP address of the client, but the IP address of the server that the client thinks it is connecting to). Right now, I can receive client connections, I can see the client IP, but I cannot figure out a method for obtaining the destination IP.
Does anyone know a method for capturing the destination IP on the socketserver class? It would seem if I can listen to multiple interfaces, there would be a way to tell which interface was hit.
This will be installed on several servers eventually, each with an unknown number of network interfaces. However, we do know that this will only exist on Linux bases systems. So if there was an OS specific way to do this, I would be fine with that as well.
If you have a socket object, you can use socket.getsockname() to obtain the IP address it's bound to. So something along the lines of:
# IPv4
client = listening_socket.accept()
(ipv4,port) = client.getsockname()
# IPv6
client = listening_socket.accept()
(address, port, flow_info, scope_id) = client.getsockname()
Never tested it on a multihomed server with a socket bound to all interfaces though - might return IPv4 0.0.0.0 or the IPv6 equivalent, for all I know, which wouldn't be all that useful.

Python dhcp client

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.

Python twisted framework multicast bind on specific interface

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

Twisted: source IP address for outbound connections

I'm in the process of implementing a service -- written in Python with the Twisted framework, running on Debian GNU/Linux -- that checks the availability of SIP servers. For this I use the OPTIONS method (a SIP protocol feature), as this seems to be a commonplace practice. In order to construct correct and RFC compliant headers, I need to know the source IP address and the source port for the connection that is going to be established. [How] can this be done with Twisted?
This is what I tried:
I subclassed protocol.DatagramProtocol and within startProtocol(self) I used self.transport.getHost().host and self.transport.getHost().port. The latter is indeed the port that's going to be used, whereas the former only yields 0.0.0.0.
I guess that at this point Twisted doesn't [yet?] know which interface and as such which source IP address will be used. Does Twisted provide a facility that could help me with this or do I need to interface with the OS (routing) in a different way? Or did I just use self.transport.getHost().host incorrectly?
For the sake of completeness I answer my own question:
Make sure you use connect() on the transport before trying to determine the host's source IP address. The following excerpt shows the relevant part of a protocol implementation:
class FooBarProtocol(protocol.DatagramProtocol):
def startProtocol(self):
self.transport.getHost().host # => 0.0.0.0
self.transport.connect(self.dstHost, self.dstPort)
self.transport.getHost().host # => 192.168.1.102
If you are using UDP then the endpoint is determined by either:
calling bind() on the socket and explicitly giving it an address
sending a packet
If you want a few more details, check this response.
The problem is that I'm not that familiar with twisted. From what I can tell by a quick perusal of the source, it looks like you might want to use a reactor like t.i.d.SelectReactor instead. This is what t.n.d.DNSDatagramProtocol does under the hood.
If you take twisted out of the picture, then the following snippet shows what is going on:
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
<socket._socketobject object at 0x10025d670>
>>> s.getsockname() # this is an unbound or unnamed socket
('0.0.0.0', 0)
>>> s.bind( ('0.0.0.0', 0) ) # 0.0.0.0 is INADDR_ANY, 0 means pick a port
>>> s.getsockname() # IP is still zero, but it has picked a port
('0.0.0.0', 56814)
Get the host name is a little trickier if you need to support multiple network interfaces or IPv4 and IPv6. If you can make the interface used configurable, then pass it in as the first member of the tuple to socket.bind() and you are set.
Now the hard part is doing this within the confines of the abstractions that twisted provides. Unfortunately, I can't help a whole lot there. I would recommend looking for examples on how you can get access to the underlying socket or find a way to pass the socket information into the framework.
Good luck.
Did you see if that you want to do is possible with the SIP implementation that is part of Twisted?
In any case, how you set the source address and port for UDP in Twisted is quite similar to how you set them without Twisted. In Twisted, reactor.listenUDP(port, protocol, interface) binds an UDP socket to a specific port and interface and handles the received datagrams to your protocol. Inside the protocol, self.transport.write(msg, addr) sends a datagram to addr using the address that the protocol is bound to as source address.
Reading your question again, I think the only part you were missing was passing interface to reactor.listenUDP(...).

Categories