My understanding is when a DNAT rule is applied to change the destination IP of an outgoing packet, conntrack automatically deNATs the reply packet's source IP back to the original destination. However, I've noticed this only works when the the DNAT destination IP is not on the same subnet. Why is this happening?
Here's a worked example with tcpdump to demonstrate (br0 has subnet 192.168.1.0/24).
On my router, I've added a DNAT rule to forward any DNS requests from any IP on port 53 to another another server like this.
iptables -t nat -A PREROUTING -i br0 ! -s 8.8.4.4 ! -d 8.8.4.4 -p udp --dport 53 -j DNAT --to 8.8.4.4
Doing a DNS request to 1.1.1.1 ("dig @1.1.1.1 google.com") from a client connected to the router, and running a tcpdump on all interfaces on the router shows that the source IP of reply packets is changed back (client is 192.168.1.2, XXX.XXX.XXX.XXX is public IP):
12:23:36.603761 IP 192.168.1.2.56892 > 1.1.1.1.53: 28134+ [1au] A? google.com. (39) 12:23:36.603761 IP 192.168.1.2.56892 > 1.1.1.1.53: 28134+ [1au] A? google.com. (39) 12:23:36.603849 IP XXX.XXX.XXX.XXX.56892 > 8.8.4.4.53: 28134+ [1au] A? google.com. (39) 12:23:36.630553 IP 8.8.4.4.53 > XXX.XXX.XXX.XXX.56892: 28134 1/0/1 A 172.217.14.206 (55) 12:23:36.630613 IP 1.1.1.1.53 > 192.168.1.2.56892: 28134 1/0/1 A 172.217.14.206 (55) 12:23:36.630617 IP 1.1.1.1.53 > 192.168.1.2.56892: 28134 1/0/1 A 172.217.14.206 (55)
We can see above that DNAT rule changed the destination from 1.1.1.1 to 8.8.4.4 correctly. Then when the packet came back from 8.8.4.4, the return packet's source IP was changed backed to 1.1.1.1 before getting sent back to the client. Also looking at conntrack entries, we can see the entry:
udp 17 18 src=192.168.1.2 dst=1.1.1.1 sport=58664 dport=53 packets=1 bytes=67 src=8.8.4.4 dst=XXX.XXX.XXX.XXX sport=53 dport=58664 packets=1 bytes=83 mark=0 use=1
This also works when I forward to another server on the same system as the router but in a different network namespace+subnet. Example with this rule (10.0.5.3 is the IP of a DNS server on the router that is in a separate net namespace and subnet than br0):
iptables -t nat -A PREROUTING -i br0 ! -s 10.0.5.3 ! -d 10.0.5.3 -p udp --dport 53 -j DNAT --to 10.0.5.3
Doing a tcpdump test with a DNS request from a client, we see the source IP of reply packets gets changed back:
12:36:17.577910 IP 192.168.1.2.64194 > 1.1.1.1.53: 55319+ [1au] A? google.com. (39) 12:36:17.577910 IP 192.168.1.2.64194 > 1.1.1.1.53: 55319+ [1au] A? google.com. (39) 12:36:17.578019 IP 192.168.1.2.64194 > 10.0.5.3.53: 55319+ [1au] A? google.com. (39) 12:36:17.578022 IP 192.168.1.2.64194 > 10.0.5.3.53: 55319+ [1au] A? google.com. (39) 12:36:17.578829 IP 10.0.5.3.53 > 192.168.1.2.64194: 55319 1/0/1 A 172.217.14.206 (55) 12:36:17.578829 IP 10.0.5.3.53 > 192.168.1.2.64194: 55319 1/0/1 A 172.217.14.206 (55) 12:36:17.578895 IP 1.1.1.1.53 > 192.168.1.2.64194: 55319 1/0/1 A 172.217.14.206 (55) 12:36:17.578899 IP 1.1.1.1.53 > 192.168.1.2.64194: 55319 1/0/1 A 172.217.14.206 (55)
So again, the DNAT changed the destionation to 10.0.5.3, which replies back, and then reply packets get deNATed correctly changing their source IP back before sending it back to the client. Again, conntrack shows the entry
udp 17 26 src=192.168.1.2 dst=1.1.1.1 sport=64194 dport=53 packets=1 bytes=67 src=10.0.5.3 dst=192.168.1.2 sport=53 dport=64194 packets=1 bytes=83 mark=0 use=1
Now, here's where the problem happens. If instead I choose to redirect to a server on the subnet of br0 (like 192.168.1.1, which is an external server connected to the router on br0), like so:
iptables -t nat -A PREROUTING -i br0 ! -s 192.168.1.1 ! -d 192.168.1.1 -p udp --dport 53 -j DNAT --to 192.168.1.1
Now, doing a DNS request from a client results in an "reply from unexpected source 192.168.1.1" error, and tcpdump on the router shows that the source IP of the reply packets never gets translated back:
12:53:45.406527 IP 192.168.1.2.63500 > 1.1.1.1.53: 46258+ [1au] A? google.com. (39) 12:53:45.406527 IP 192.168.1.2.63500 > 1.1.1.1.53: 46258+ [1au] A? google.com. (39) 12:53:45.406647 IP 192.168.1.2.63500 > 192.168.1.1.53: 46258+ [1au] A? google.com. (39) 12:53:45.406651 IP 192.168.1.2.63500 > 192.168.1.1.53: 46258+ [1au] A? google.com. (39) ... 12:53:45.428014 IP 192.168.1.1.53 > 192.168.1.2.63500: 46258 1/0/1 A 172.217.14.206 (55) 12:53:45.428017 IP 192.168.1.1.53 > 192.168.1.2.63500: 46258 1/0/1 A 172.217.14.206 (55) 12:53:45.428014 IP 192.168.1.1.53 > 192.168.1.2.63500: 46258 1/0/1 A 172.217.14.206 (55)
We can see that the DNAT changed the destination IP correctly to 192.168.1.1, but then the reply packets from 192.168.1.1 never had their source IP changed back to 1.1.1.1. This results in the client (192.168.1.2) seeing the packet as coming from 192.168.1.1 instead of 1.1.1.1, and spits out the unexpected source error since it sent the request to 1.1.1.1. Looking at conntrack we see the following entry,
udp 17 23 src=192.168.1.2 dst=1.1.1.1 sport=63500 dport=53 packets=1 bytes=67 **[UNREPLIED]** src=192.168.1.1 dst=192.168.1.2 sport=53 dport=63500 packets=0 bytes=0 mark=0 use=1
Note how the above conntrack entry says UNREPLIED, while the other ones didn't. But the conntrack entry looks correct (the src/dst/port), so I don't understand why it's not deNATing the reply packets correctly and changing the source IP back like the other examples above.
Can anyone illuminate why the source IP is not changing back when the destination DNAT is on the same subnet, and why the conntrack entry shows UNREPLIED even though the src/dst/sport tuple matches the reply packet? Is there anything I can do to fix this so it works properly like for the first two examples with external IPs to the subnet?
Thank you!
No comments:
Post a Comment