Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 974
Etienne Perot Avatar asked Apr 06 '12 03:04

Etienne Perot


2 Answers

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.

like image 103
EdwardH Avatar answered Nov 05 '22 04:11

EdwardH


you can use a tun/tap device, can simply read it from python, for example:

tunnel.py

import os
from fcntl import ioctl
from select import select
import struct
import subprocess

TUNSETIFF = 0x400454ca
TUNMODE = 0x0001

tunFile = os.open("/dev/net/tun", os.O_RDWR)
ifs = ioctl(f, TUNSETIFF, struct.pack("16sH", "tun%d", TUNMODE))
ifname = ifs[:16].strip("\x00")
print "Allocated interface %s. Configure it and use it" % ifname
subprocess.call("ifconfig %s 192.168.13.1" % ifname,shell=True)
# Reading
def read():
    r = select([tunFile], [], [])[0][0]
    if r == tunFile:
        return os.read(tunFile, 1600)
    return None

# Writing
def write(buf):
    os.write(tunFile, buf)

the full example can be found here

and route your packets to interface "tun0" or printed inetrface name.

linux distributions does not require to install anything but if you are on windows use this, and for mac os use this

EDIT 1 Note from here:

The difference between a tap interface and a tun interface is that a tap interface outputs (and must be given) full ethernet frames, while a tun interface outputs (and must be given) raw IP packets (and no ethernet headers are added by the kernel). Whether an interface functions like a tun interface or like a tap interface is specified with a flag when the interface is created.

for finding packet header information(like src, dst & etc ...) you may use dpkt

from dpkt import ip 
from tunnel import read,write # tunnel.py
while 1:
    data = read()
    if data:
        ipObj = ip.IP(data[4:])
        print ipObj.src, ipObj.dst
like image 4
pylover Avatar answered Nov 05 '22 04:11

pylover