Iptables – Need iptables port forwarding for BIDIRECTIONAL UDP

iptablesport-forwardingudp

IN SHORT: I need a port redirection on a connection-oriented protocol, just like it would be for TCP – but I need that it work for UDP. I have a custom connection-oriented protocol using UDP for transport.

In particular:

  1. The machine to be configured is listening on port 5005 UDP.
  2. UDP packets are expected to come from, say, 10.1.2.10.
  3. This machine should forward this packet to, say, 10.1.2.20 port 5010.
  4. … and the other way around!

For 4, more precisely: when a packet comes from 10.1.2.10, it is assigned a random port, say, 52000. Then, THIS machine receives packet on UDP port 5005 and forwards it to 10.1.2.20:5010 having also some randomly-selected source port, say, 52001. Now, when there's coming a packet from 10.1.2.20 port 5010 to THIS machine port 52001, it should redirect it also to 10.1.2.10 port 52000.

Moreover, if there's coming a packet from another port on 10.1.2.10, the port assignment and according rule for reverse-direction packet redirection should apply as well. It may be set 10s timeout for any traffic, after which the backward track rule is removed.

If this cannot be done using iptables, I'm fine with it – I'll fall back to a userspace solution. But with iptables it will simplify the deployment.

Best Answer

Your proxy server is thus doing a double NAT, masquerading (by hiding the other real peer) both ways.

  • It has to be configured as a router on its (single) interface or nothing will be forwarded. Let's say the interface name is eth0, and its IP is 10.1.2.50:

    echo 1 > /proc/sys/net/ipv4/conf/eth0/forwarding
    
  • Now because of the double NAT, the transformations in PREROUTING and/or POSTROUTING prevent matching the flow just using again the same filter. So in PREROUTING a CONNMARK is used to mark the flow which can then be reused immediately in PREROUTING (and avoiding duplicating the specific test for easier management) and later in POSTROUTING. Note -p udp is still needed because we ask SNAT and/or DNAT to alter UDP ports. After that conntrack will handle all future exchange, as long as it doesn't time out (usually 30s for first reply, then 3mn)

    iptables -t nat -A PREROUTING -s 10.1.2.10 -p udp --dport 5005 -j CONNMARK --set-mark 0x1
    iptables -t nat -A PREROUTING -p udp -m connmark --mark 0x1 -j DNAT --to-destination 10.1.2.20:5010
    iptables -t nat -A POSTROUTING -m connmark --mark 0x1 -j SNAT --to-source 10.1.2.50
    

    If it's really a requirement that you also alter the source port, instead of having conntrack do it only when needed, change the 3rd rule into:

    iptables -t nat -A POSTROUTING -p udp -m connmark --mark 0x1 -j SNAT --random-fully --to-source 10.1.2.50:32768-60999
    

    (usual range taken from /proc/sys/net/ipv4/ip_local_port_range, and --random-fully is needed else nothing is done because the original port is already in range).
    You could also change SNAT to MASQUERADE (and not have to state server IP) but only --random is available.

UPDATE: while the above rules work as intended, usually the connmark match and the CONNMARK target are intended for interaction with mark and MARK for a more complex usage as described in this blog. They are not needed for the simple usage here. Simply replace them all with resp. mark and MARK. Here netfilter's connection tracking already takes care of the flow once the first packet is handled (creating a conntrack entry in NEW state) without having to ask it to mark it in the conntrack entry. A mark is just put in the (first) packet (the nat table sees only packets in NEW state):

iptables -t nat -A PREROUTING -s 10.1.2.10 -p udp --dport 5005 -j MARK --set-mark 0x1
iptables -t nat -A PREROUTING -p udp -m mark --mark 0x1 -j DNAT --to-destination 10.1.2.20:5010
iptables -t nat -A POSTROUTING -m mark --mark 0x1 -j SNAT --to-source 10.1.2.50

Example captures, including errors (service not running etc.):

00:28:36.476453 IP 10.1.2.10.56955 > 10.1.2.50.5005: UDP, length 5
00:28:36.476487 IP 10.1.2.50.35172 > 10.1.2.20.5010: UDP, length 5
00:28:36.476516 IP 10.1.2.20 > 10.1.2.50: ICMP 10.1.2.20 udp port 5010 unreachable, length 41
00:28:36.476522 IP 10.1.2.50 > 10.1.2.10: ICMP 10.1.2.50 udp port 5005 unreachable, length 41

00:32:28.597050 IP 10.1.2.10.35443 > 10.1.2.50.5005: UDP, length 5
00:32:28.597084 IP 10.1.2.50.36842 > 10.1.2.20.5010: UDP, length 5
00:32:32.503709 IP 10.1.2.20.5010 > 10.1.2.50.36842: UDP, length 7
00:32:32.503745 IP 10.1.2.50.5005 > 10.1.2.10.35443: UDP, length 7
00:32:41.704371 IP 10.1.2.20.5010 > 10.1.2.50.36842: UDP, length 4
00:32:41.704404 IP 10.1.2.50.5005 > 10.1.2.10.35443: UDP, length 4
00:32:41.704427 IP 10.1.2.10 > 10.1.2.50: ICMP 10.1.2.10 udp port 35443 unreachable, length 40
00:32:41.704433 IP 10.1.2.50 > 10.1.2.20: ICMP 10.1.2.50 udp port 36842 unreachable, length 40

Please note that, you can replace all of this with this simple socat line (and you don't get any 30s/3mn timeout but you lose ICMP handling):

socat UDP4-LISTEN:5005,range=10.1.2.10/32,fork UDP4:10.1.2.20:5010
Related Topic