12
votes

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.

3
The TPROXY redirection you attempted is done at the ingress, do you need it at the ingress or egress? In addition, I'm not clear if you removed the NAT entries as well, with them you will loose the source address (acceptable?). - EdwardH
I need them at the egress (all UDP packets going from the local machine to the rest of the world should be caught); did I do something wrong with the TPROXY rule? It is bound on 0.0.0.0 to bind on all interfaces, but perhaps 127.0.0.1 would be sufficient to bind on the local interface. I didn't really understand the second part of your comment... you mean if I remove the iptables rules defined at the top? Then UDP packets would be allowed out, and I don't want that. The TPROXY part of the question is separate (I didn't have those iptables rules set when doing the TPROXY things). - Etienne Perot
After re-reading your comment, I think I understand the second part now. No, I didn't have any iptables entries when doing the TPROXY part, so the source/destination addresses shouldn't be lost (and I need them, so it is not acceptable to lose them) - Etienne Perot
The TRPOXY entry you used cannot do the trick because it is in the ingress, see here: linuxhomenetworking.com/wiki/images/f/f0/Iptables.gif. - EdwardH
Ok, so here it goes (in a comment because I didn't tested it): If you have access to the UDP traffic generator code, you could mark the packets (fwmark) and add an entry in the "ip rule" to handle this traffic in a separated routing table (100) "ip rule add fwmark 1 lookup 100". If you do not have access to that code, you could add something like this: "ip rule add unicast iif lo table 100". In Table 100, route the traffic locally: "ip route add local 0.0.0.0/0 dev lo table 100". The last one has a problem to distinguish UDP traffic, so I prefer the first one. - EdwardH

3 Answers

10
votes

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.

4
votes

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
1
votes

Do you control the host? If so, you can use Open vSwitch to write a rule that spans just the flow in question, preserving all IP information to the tap port (and then just bind a listener to the tap port).

(OVS can do all manner of much more complicated things, but this is a relatively simple task)