林銘賢
8 min readMar 21, 2017

packet 沒有跑到 iptables 的 nat table 花了我一天半

最近案子要做 DHCP relay 的功能,所以我們用 docker + ovs + dhcp server 在 SDN controller 內組一個 dhcp service。不過這有個限制,dhcp server 只能綁在一個 interface 上,問題就出在 dhcp server 綁的 interface 是用內部 IP,它沒有對外。外部的 packet 都是透過 controller + ovs 來幫忙轉發。所以需要設定 iptables 做 DNAT 把外部 dhcp packet 轉給內部的 dhcp server。

工作

  1. dhcp server 的 ip address 為 192.168.10.254,virtual interface 為 dhcp_int
  2. 當 SDN controller 透過 eth0 收到 dhcp packet 時,需要把它轉給內部的 dhcp server 192.168.10.254
  3. 使用 iptables 的 nat table 做 DNAT ,把 destination ip address 改成 192.168.10.254

想說這好像沒什麼難,隨便找個 DNAT 的 sample 就上機玩看看

sudo iptables -t nat -I PREROUTING -i eth2 -p udp -s 192.168.10.254 --j DNAT --to 192.168.10.254:67後記:這條是錯的,但也忘了當初怎麼寫的了

rule 下下去後,見鬼了,什麼 packet 都沒轉到 dhcp_int 上,開 wireshark 出來看也是空空的。結果又把 iptables 翻出來好好 K 一遍,原本忘光光的東西又都記回來了 (大概明天又會忘光了,只好寫個 blog 記錄一下)。

iptables 的 packet 架構

  1. 有 raw/mangle/nat/filter 等 table
  2. 每個 table 在 PREROUTING/POSTROUTING/INPUT/OUTPUT/FORWARD 階段都可以下 rule match packet

圖片來源 http://xkr47.outerspace.dyndns.org/netfilter/packet_flow/

馬上整理一下策略,看看發生什麼事

  1. 加 log 到 mangle/nat/filter 來觀察 packet 跑哪去了
sudo iptables -t mangle -A PREROUTING -i eth0 -p udp --dport 67 -j LOG --log-level 4 --log-prefix MANGLE_DHCP:sudo iptables -t nat -A PREROUTING -i eth0 -p udp --dport 67 -j LOG --log-level 4 --log-prefix NAT_DHCP:sudo iptables -A INPUT -i eth0 -p udp --dport 67 -j LOG --log-level 4 --log-prefix INPUT_DHCP:

2. dhcp client 一開著開,不斷地送 packet 給 server

3. 看 dmesg 丟出來什麼東西

[ 1622.834091] MANGLE_DHCP:IN=eth0 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 PROTO=UDP SPT=67 DPT=67 LEN=308 [ 1622.834110] INPUT_DHCP:IN=eth0 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 PROTO=UDP SPT=67 DPT=67 LEN=308

媽呀!log 跟我說 packet 根本沒有過 nat table 呀。進到 mangle prerouting 後,就轉到 filter input 了,我下的 DNAT rule 根本沒做用呀!

Google 找半天,也只有找到這一遍 https://lists.gt.net/iptables/user/55934 有碰到類似的問題,不過提配了我們幾件事:

  1. 他沒有解決問題
  2. nat table 只有 第一個 packet 會通過而已,剩下的不會
  3. conntrack 會記錄每個 connection 狀態,tcp/udp 都會

馬上去抓 contrack 來玩,dump 一下目前的 connection。

coming@c43:~$ sudo conntrack -L
tcp 6 431992 ESTABLISHED src=192.168.10.43 dst=104.16.121.127 sport=57332 dport=443 src=104.16.121.127 dst=192.168.10.43 sport=443 dport=57332 [ASSURED] mark=0 use=1
tcp 6 431996 ESTABLISHED src=192.168.10.43 dst=172.217.27.142 sport=34016 dport=443 src=172.217.27.142 dst=192.168.10.43 sport=443 dport=34016 [ASSURED] mark=0 use=1
udp 17 13 src=192.168.30.43 dst=192.168.30.255 sport=17500 dport=17500 [UNREPLIED] src=192.168.30.255 dst=192.168.30.43 sport=17500 dport=17500 mark=0 use=1
tcp 6 431978 ESTABLISHED src=192.168.10.43 dst=172.217.27.131 sport=41792 dport=443 src=172.217.27.131 dst=192.168.10.43 sport=443 dport=41792 [ASSURED] mark=0 use=1

耶!因為 dhcp client 一直在送 dhcp packet,所以 connection 已經被 conntrack 記錄了,所以後面的 packet 當然不會過 nat table 呀。那當然不管我再怎麼改 nat table try and error 一定都不會生效。哇咧×%&×%&×%&……。好吧,那就下 conntrack -F 把 connection 清掉,再看一下 log

[ 1622.834091] MANGLE_DHCP:IN=eth2 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 P[ 1622.834110] NAT_DHCP:IN=eth2 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 PROTO=UDP SPT=67 DPT=67 LEN=308[ 1622.834110] INPUT_DHCP:IN=eth2 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 PROTO=UDP SPT=67 DPT=67 LEN=308[ 1622.834091] MANGLE_DHCP:IN=eth0 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 PROTO=UDP SPT=67 DPT=67 LEN=308 [ 1622.834110] INPUT_DHCP:IN=eth0 OUT= MAC=00:cc:00:00:00:43:e8:e7:32:fe:37:be:08:00 SRC=192.168.30.119 DST=192.168.30.43 LEN=328 TOS=0x00 PREC=0x00 TTL=64 ID=49257 PROTO=UDP SPT=67 DPT=67 LEN=308

好的,果然 nat table 有 match 了,而且只有一第個 packet 有而已,後面其它的 packet 就不會跑到 NAT 去了。

學到的東西

  1. 要送 udp packet 給 server 的話,只要把資料導到 /dev/udp/<server ip>/<port> 就好了
  2. iptables nat table 只有第一個 packet 會過去,其它的 packet 不會過
  3. 只要 DNAT rule 生效後,就算砍掉該條 rule,同一個 connection 的 packet 還是會持續做 DNAT 轉換
  4. conntrack 可以查看目前 connection 狀態
  5. iptables 可以把 packet 轉到與 ovs 連接的 port
  6. DNAT 轉到 host machine 上的 packet 沒辦法透過 wireshark 觀查到
  7. nc 有點難用