How can I duplicate incoming DNS packets and send them to a different nameserver with iptables?

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.


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

The documentation says

The TEE target will clone a packet and redirect this clone to another machine on the local network segment. In other words, the nexthop must be the target, or you will have to configure the nexthop to forward it further if so desired.

I don't know about the reason for this restriction, but it seems it can't be overcome with iptables NAT, which makes sense, because if the underlying implementation supports external targets, then why restrict the target range of TEE. I suspect that the TEE is done below the NAT level.

It wouldn't work anyway because the response would arrive with the wrong source address.


As TEE can only be used in the same subnet, you might be able to use the tool of Daemonlogger, also available on github (where it seems fresher?).

Daemonlogger is a packet logger and soft tap developed by Martin Roesch. The libpcap-based program has two runtime modes:

  1. It sniffs packets and spools them straight to the disk and can daemonize itself for background packet logging. By default the file rolls over when 2 GB of data is logged.
  2. It sniffs packets and rewrites them to a second interface, essentially acting as a soft tap. It can also do this in daemon mode. These two runtime modes are mutually exclusive, if the program is placed in tap mode (using the -I switch) then logging to disk is disabled.

Some writeups :

  • Daemonlogger in Ring Buffer Mode
  • Daemonlogger: let's log