Setting up UDP packets to two different destinations using iptables and PREROUTING

I am trying to send a UDP on a packet to two different external/remote IPs using iptables.

Currently I am running a command that looks like this:

iptables -t nat -A PREROUTING -i eth0 -p udp --dport 3070 -j DNAT --to-destination 192.111.111.111:5640
iptables -t nat -A PREROUTING -i eth0 -p udp --dport 3070 -j DNAT --to-destination 167.111.111.111:5640

However, this does not work, because the UDP packet looks at the iptables’ rules, and goes to 192.111.111.111:5640. Putting the other rule first makes the UDP packet go to that second destination. I tried combining the actual to-destinations using their range functionality, but the packet does not go anywhere, as these ranges are not internal IPs:

iptables -t nat -A PREROUTING -i eth0 -p udp --dport 3070 -j DNAT --to-destination 192.111.111.111-167.111.111.111:5640-5640

### note: I also tried
iptables -t nat -A PREROUTING -i eth0 -p udp --dport 3070 -j DNAT --to-destination 192.111.111.111-167.111.111.111:5640-5641

where the port is increased by one on the secondary server

I can confirm that both commands work separately, and that only one works when both are in the same rule hierarchy.

I have also tried to use TEE to make this work, pointing to one of the IPs as another gateway, but that doesn’t work either. Is there any way to do this in iptables, or am I missing the mark in creating a multi-destination general UDP forwarder?

The UDP packets should both be distributed at the same time/simultaneously and duplicated to each respective server. The use case at the moment is that the UDP packet is needed for a process on Server A as well as in Server B, but from the source side that is sending the packet, it can only point to one server. ie: Server C that sends udp packets -> Server D -> duplicate and send packet to Server B and Server A.

Thanks for any help or suggestions.

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

Simple userland tools

First I would recommend using a dedicated tool running on server D that will listen for UDP packets, and will send a copy twice, one for A and one for B. This could be done for example with bash process substitution+tee+socat (example loosely adapted from udp-multi-socat.sh with its handy reversed order, to have tee immediately running) , but I don’t know if some buffering might not happen in some cases (better create a dedicated application):

socat -U - udp4-recv:3070 | tee >(socat -u - udp4-datagram:167.111.111.111:5640) | socat -u - udp4-datagram:192.111.111.111:5640

Using kernel (iptables … -j TEE)

Now that is said, for a “kernel-assisted” method, there’s iptables’s TEE target which can duplicate packets. Because the duplication has to bypass the normal routing handling, it requires to “evacuate” the duplicated packet to an other host directly reachable (the –gateway parameter). Without this, it appears there’s no good way to handle the copied packet with TEE.

Fair enough, we can just create that host using a network namespace. Then reinject right back the duplicated traffic on host D, through the routing stack and iptables which can now DNAT it differently (because it came from an other interface). Because of the assymetric routing and the duplicate flow some adjustements have to be done (loose rp_filter on the virtual interface, and a different conntrack (origin) zone to differentiate the flows, because conntrack knows only about IPs, not interfaces)

Here’s the configuration to use on Server D (some duplication could probably be avoided, but it would be less readable):

ip netns del dup 2>/dev/null || : # to remove previous instance, if needed
ip netns add dup

ip link add name dup1 type veth peer netns dup name eth0
ip link set dup1 up
ip -n dup link set eth0 up
ip address add 10.10.10.1/24 dev dup1
ip -n dup address add 10.10.10.2/24 dev eth0
ip -n dup route add default via 10.10.10.1
sysctl -q -w net.ipv4.conf.dup1.rp_filter=2
ip netns exec dup sysctl -q -w net.ipv4.conf.eth0.forwarding=1
iptables -t mangle -A PREROUTING -i eth0 -p udp --dport 3070 -j TEE --gateway 10.10.10.2
iptables -t raw -A PREROUTING -i dup1 -p udp --dport 3070 -j CT --zone-orig 1
iptables -t nat -A PREROUTING -i eth0 -p udp --dport 3070 -j DNAT --to-destination 192.111.111.111:5640
iptables -t nat -A PREROUTING -i dup1 -p udp --dport 3070 -j DNAT --to-destination 167.111.111.111:5640

With this method even replies from A and B can both be sent back to C (which will think they both came from the very same initial destination), if this makes sense anyway. So if nothing is listening on A or B, an ICMP destination port unreachable will be sent to source C, which might choose to abort: additional firewalling to avoid this should probably be added somewhere.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply