13

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.

Community
  • 1
  • 1
Etienne Perot
  • 4,764
  • 7
  • 40
  • 50
  • 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 Apr 09 '12 at 06:07
  • 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 Apr 09 '12 at 07:08
  • 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 Apr 09 '12 at 07:40
  • The TRPOXY entry you used cannot do the trick because it is in the ingress, see here: http://www.linuxhomenetworking.com/wiki/images/f/f0/Iptables.gif. – EdwardH Apr 09 '12 at 07:58
  • 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 Apr 09 '12 at 08:09
  • I don't have access to the UDP-sending code; the goal is to catch all UDP packets that the machine sends, so I guess only the second solution applies. This causes all packets ever to go to the localhost interface. But then, how would it be possible to catch UDP packets, while letting TCP packets go through? – Etienne Perot Apr 09 '12 at 19:37
  • Did you try ULOG target (-j ULOG) user space logging facility? I think it's perfect for you. – dAm2K Apr 10 '12 at 11:13

3 Answers3

10

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.

Community
  • 1
  • 1
EdwardH
  • 1,523
  • 2
  • 12
  • 20
  • Looks promising, but indeed I don't know the port of the UDP traffic (the goal is to catch everything from any application). The `TPROXY` rule given doesn't work, because as you noted it only works with PREROUTING (`ip_tables: TPROXY target: used from hooks OUTPUT, but only usable from PREROUTING`). However, without using this `iptables` rule, I have been able to listen to all outgoing packets by binding to `0.0.0.0` with the above socket option; it indeed catches all the UDP packets as desired, on any port, and even some noise on the network that is not addressed to the local machine. Thanks! – Etienne Perot Apr 10 '12 at 22:17
  • Thanks :) You almost doubled my reputation. I didn't do so much to worth it, hope I can repay. – EdwardH Apr 15 '12 at 05:41
  • @EtiennePerot I was also working on similar lines and was stuck on the same problem as you. I tried the above networking rule specified by EdwardH but I am unable to get the original destination address of the UDP packet (instead I get my equivalent address of your "localhost:15000"). I am using a raw socket to capture the traffic. Were you able to extract the original destination address with the above approach? It'll be great if you could point me in the right direction. – sultan.of.swing Jun 11 '12 at 06:13
  • @sultan.of.swing Yes, I was able to extract the original source and destination of the packet using a raw socket. You can see the relevant source code at https://github.com/EtiennePerot/sshuttle/blob/master/firewall.py in the functions `replay_udp` (receiving the UDP packets) and `do_iptables` (setting up `iptables` and `ip`). Note that sometimes the source address was 0.0.0.0, in which case I just assumed it was the external IP address of the machine. – Etienne Perot Jul 02 '12 at 19:05
4

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
pylover
  • 7,670
  • 8
  • 51
  • 73
  • This looks good. Is there a way to set `iptables` to make all UDP packets go to that tun device, without affecting TCP packets? Playing with `ip` routes alone probably wouldn't be able to tell the difference between TCP and UDP packets, which means I'd have to parse all TCP packets in Python, which would make things slower than they really have to do. – Etienne Perot Apr 09 '12 at 05:02
  • i wrote a vpn server with tun and dpkt , and performance is so good, dpkt just uses ip header and do not parse the packet's body, you can route only UDP packets with ip tables, or determine udp packets by dpkt.IP object, i thing this is a regular way to process packets – pylover Apr 09 '12 at 05:53
  • EdwardH's answer was closer to what I was expecting, but this looks like a fine solution too. +1 for dpkt, wish I had heard of it before I did my own packet parsing. – Etienne Perot Apr 10 '12 at 22:20
1

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)

Nick Bastin
  • 30,415
  • 7
  • 59
  • 78
  • Had a look but looks very complex to accomplish a task that (to me, at least) seems pretty simple. That, and this question is not to implement something from scratch; it is meant to get integrated in another project in which I wouldn't want to introduce many big additional dependencies. – Etienne Perot Apr 06 '12 at 04:58
  • @EtiennePerot: If you want to integrate into an existing situation, you're going to need to sit on the interface and filter every packet yourself. – Nick Bastin Apr 06 '12 at 05:13
  • No, I just want to integrate it in an existing project. The project already captures TCP traffic with `iptables`; I just want to extend that to UDP as well. – Etienne Perot Apr 06 '12 at 05:46
  • Just for completeness, there is a small catch with OpenVSwitch. It does not support fragmented packets. If UDP datagram or TCP segment happened to be fragmented, OpenVSwitch will have no access to UDP/TCP headers at all, even for the first packet. – abb Jun 08 '12 at 10:17
  • 1
    @abb: this is a configuration option - setting `frag_mode` to `nx-match` will often get you what you want. – Nick Bastin Jun 08 '12 at 22:00
  • @NickBastin: Thanks for the hint. I didn't know there is a partial implementation of fragment handling, nice to know. Is it the same feature as described here http://openvswitch.org/pipermail/git/2011-October/001923.html? At a first glance it looks insufficient for proper connection redirection anyway -- there is still no way to differentiate between TCP fragments belonging to connections to different ports. – abb Jun 14 '12 at 11:40