Linux – How to Duplicate Incoming DNS Packets with Iptables

domain-name-systemiptableslinuxnetworkingrouter

EDIT: the tldr: I want to use TEE and NAT to clone UDP traffic. I'm not worried about handling responses. I ideally don't want to install new software. I am using TEE to send to local segment (127.0.0.2) successfully, then I want to NAT that traffic out to the WAN.

I have bind9 listening on port 53 on my Debian server. I have an external client making DNS requests to said server. All of this is working fine. I would like to copy the incoming DNS requests on port 53 and send them off to, say, 8.8.8.8. Note the word copy.

From much googling and reading on SU, I have found the most often advised method is to use iptables TEE and NAT. I have TEE working pretty well, here's my command:

iptables -t mangle -A POSTROUTING -p udp -d 127.0.0.1 --dport 53 -j TEE --gateway 127.0.0.2

I verified I receive a copy of the DNS request at 127.0.0.2:53 with netcat. So far so good.

Now, I need to change the destination IP. I attempt to accomplish this with:

iptables -t nat -A PREROUTING -p udp -d 127.0.0.2 --dport 53 -j DNAT --to 8.8.8.8

I used tcpdump to monitor for outgoing traffic to 8.8.8.8. Nothing. I wondered: maybe I need to change the source IP address so that the kernel doesn't drop this packet, since it's arriving at 127.0.0.2, but the source IP is set to that of my external DNS client. Why not?

iptables -t nat -A POSTROUTING -p udp -d 127.0.0.2 --dport 53 -j SNAT --to DNS_SERVERS_PUBLIC_IP

Still, tcpdump shows nothing.

I have IP forwarding on:

$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

I am pretty much out of ideas and would appreciate any and all help. Thank you.

Best Answer

Nothing is impossible, with iptables.

# iptables -t nat -L -v --line-numbers -n
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        5   281 MARK       udp  --  docker0 *       0.0.0.0/0            172.17.0.1           udp dpt:53 MARK set 0xc0fe
2        5   281 TEE        udp  --  docker0 *       0.0.0.0/0            172.17.0.1           udp dpt:53 TEE gw:127.1.2.3
3        3   167 DNAT       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0xc0fe to:1.1.1.1:53

I was testing this with docker, since its easier to play with its interface. Notice I placed these rules before any other rules in nat prerouting table.

What you do is, mark the packet you are interested in, then TEE (copy) that packet, and now you can DNAT that packet to 1.1.1.1.

Mark them up, here you should use your eth0 or eno1, not docker0.

iptables -t nat -I PREROUTING 1 -i docker0 -p udp -d 172.17.0.1 --dport 53 -j MARK --set-mark 0xc0fe

Copy/tee it, over to localhost, the rule can be like this

iptables -t nat -I PREROUTING 2 -i docker0 -p udp --dst 172.17.0.1 --dport 53 -j TEE --gateway 127.1.2.3

But it can also be like this iptables -t nat -I PREROUTING 3 -p udp -m mark --mark 0xc0fe -j TEE --gateway 127.1.2.3

Finally

iptables -t nat -I PREROUTING 3 -p udp  -m mark --mark 0xc0fe -j DNAT --to-destination 1.1.1.1:53

So you were were much on the right path, except using TEE in the mangle table, here I am using both TEE and DNAT on nat table, and the MARK is not needed except for easier debugging, and handling rule-changes. Also another mistake you had was letting the DNAT rule act on the dst of what you believed the TEE should go to, but instead TEE and DNAT should act on same conditions. But I'll leave the mark rules here, just to have fun with cargo-cultists.

In shorter form:

iptables -t nat -I PREROUTING 1 -i eth0 -p udp --dst 172.17.0.1 --dport 53 -j TEE --gateway 127.3.3.3
iptables -t nat -I PREROUTING 2 -i eth0 -p udp --dst 172.17.0.1 --dport 53 -j DNAT --to-destination 1.1.1.1:53
Related Topic