Python/iptables: Capturing all UDP packets and their original destination

Etienne Perot picture Etienne Perot · Apr 6, 2012 · Viewed 7.7k times · Source

I am trying to write an iptables rule that will redirect all outgoing UDP packets to a local socket, but I also need the destination information. I started out with

sudo iptables -t nat -A sshuttle-12300 -j RETURN   --dest 127.0.0.0/8 -p udp
sudo iptables -t nat -A sshuttle-12300 -j REDIRECT --dest 0.0.0.0/0   -p udp --to-ports 15000

And that's great, now I can get all outgoing UDP packets by using a socket on port 15000.

Now, I need the destination information (target host and port number) so a simple UDP socket isn't enough; need a raw socket so that it gets the full IP header.

However, as it turns out, the packets received seem to be addressed for localhost:15000. This makes sense because that's where the socket is, but that's not what I want; I want the host/port before the packet was redirected by iptables.

Googling led to this question, with the answer suggesting two approaches: TPROXY and SO_ORIGINAL_DST, recommending the former, so that's what I tried to go with.

Added the iptables rule for TPROXY:

sudo iptables -t mangle -A PREROUTING -j TPROXY --dest 0.0.0.0/0 -p udp --on-port 15000

Reading from tproxy.txt, we need to create a listening socket with the IP_TRANSPARENT option (this is done as root):

from socket import *
s = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)
# The IP_TRANSPARENT option isn't defined in the socket module.
# Took the value (19) from the patch in http://bugs.python.org/issue12809
s.setsockopt(SOL_IP, 19, 1)
s.bind(('0.0.0.0', 15000))
s.recv(4096) # Will hang until it receives a packet

Alright, now let's write another script to generate a test packet to see if anything happens:

from socket import *
s = socket(AF_INET, SOCK_DGRAM)
s.connect(('192.168.1.1', 9001))
s.send('hello')

But then nothing happens on the receiving side. The recv call seems to hang, not receiving any data.

So, the overall question is either:

  • Is there something wrong in the code to receive the data from the TPROXY rule?

or

  • Is there another way to achieve this (redirect all outgoing UDP packets to a local socket with a way to get the destination information)?

Edit: I should insist that I'd like to redirect (therefore intercept) the packets, not just inspect them as they go through.

Answer

EdwardH picture EdwardH · Apr 10, 2012

I found your question interesting.

The following solution is based on marking the UDP traffic generated by the host and re-routing it back to the local host application. At the application, a UDP socket should be used to read the data, even one that is not destined for the host itself (see below how).

Networking settings:

  • Mark the UDP traffic that exits the host
  • Traffic that is marked with 1, pass to routing table 100 for processing
  • Route traffic to the application
iptables -A OUTPUT -t mangle -p udp -j MARK --set-mark 1
ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

Socket settings:

  • Create UDP socket (regular)
  • Enable binding/reading for non local addresses
#ifndef IP_TRANSPARENT
#define IP_TRANSPARENT 19
#endif

int val = 1; 
setsockopt(sockfd, SOL_IP, IP_TRANSPARENT, &val, sizeof(val));

You should be able now to read from the socket. Tip form Etienne Perot: For accepting all UDP traffic, bind to 0.0.0.0.

What I found here very interesting, is that locally generated traffic (and not routed one) may be classified and re-routed using iptables and route rules.

Hope this helps.