一台Linux頂多個SOHO路由器 (iptables, iproute2, netfilter)
Saturday, October 18th, 2008
一台Linux代替一個SOHO路由器很容易,大致可以用6句command概括
dhclient3 … $WANIF # Get the IP on WAN side
ip addr add 192.168.0.0/24 broadcast + dev $LANIF # Get the IP on LAN side
dhcpd3 … $LANIF # Enable the DHCPD on LAN side
sys.net.ipv4.ip_forward = 1 # Enable forwarding
iptables -t nat -A POSTROUTING -o $WANIF -j MASQUERADE # Enable SNAT
dnsmasq # Enable DNS proxy
目的:一台Linux代替N個SOHO路由器。即是說一台能夠建立多個獨立NAT,大家都用看似同似同一樣但實際不一樣的私有IP網址地段。
始終1台用5個command不代表2台只需10個command,主要問題有七:
- WAN Interface要拿很多個WAN IP,但怎樣才可以有多個dhclient到很多IP?
- 怎樣MASQUERADE到不同的WAN IP……?
- 我想N個SOHO Router的LAN Interface都是192.168.0.0/24,就真的有如N個SOHO Router一樣,後面都是叫192.168.0.0/24,但實際此上192不同那192。若每個$LANIF[1..n]都是用192.168.0.0/24,傳統按Destination做Routing的Route Table會出問題,也就是說Kernel不知道192.168.0.0/24應從哪個Interface走的問題。
- 從LAN訪問Router機……?
- DMZ和Port Forwarding…?
- N NAT subnet互相通過大家的WAN Side IP做Connection也……?。
- Router機訪問Client Side……?
解答
首先ebtables這個圖很有用的:
在測試時即管多用tcpdump -i $LANxIF icmp -vv -n看看測試用的ping traffic。用iptables -vnL則可看到各個rule hit到的次數和流量。
從WAN Side拿多個IP
當然大前提是upstream DHCP Server會提供多個IP給你,就有如放N個Router也會拿到N個IP。
不是說建了eth0:1、eth0:2等就會拿到多個IP這麼簡單,因為DHCP Client是按Mac Address來提出request的。
但原來dhcp request新的IP,得出來的的租約並不完全是綁定於Mac Address上,而是綁在client-identifier,只不過default的identifier都是Mac Address就是了。
dhclient3通過dhclient.conf加一句”send dhcp-client-identifier…”就能拿到另一個IP。udhcpc的-c argument也有同樣效果。不過udhcpc好像不會存儲租約記錄,沒有”REBOOT” request,即是說client restart前後不會嘗試取得同一個IP,所以還是dhclient3好。
注意在一個DHCP Client取得IP前,同一時間只能有一個DHCP Client去發出申請,否則某些DHCP Server會用同一個租約去回應同一時間內的申請,即是說會拿到同一個IP。這大概是我面對的DHCP Server的bug吧。
而兩個dhcp client都不會把network interface config好的,這一步是通過client script來完成。對dhclient3而言,Ubuntu上就是/sbin/dhclient-script搞的 (從/usr/lib/call-dhclient-script setuid成root再call過去)。不過Ubuntu上的dhclient-script不支持ethX:alias form,所以還是要自己寫一套script來攪定config file和dhclient-script的問題。
MASQUERADE到不同的WAN IP
直觀來說就是按在nat-POSTROUTING加N條MASQUERADE rule,每條對應一個incoming interface,即:
iptables -t nat -A POSTROUTING -i $LANxIF -j SNAT –to-source $WANxIP
但一試就知問題是在POSTROUTING是不可以按incoming interface (-i …)做篩選的。
答案是在mangle-PREROUTING先用-j MARK把packet mark一下,再在nat-POSTROUTING用mark做篩選。
iptables -t mangle -A PREROUTING -i $LANxIF -j MARK –or-mark $x
iptables -t nat -A POSTROUTING -m mark –mark $x/0xff -j SNAT –to-source $WANxIP
POSTROUTING要用/0xff mask的原因稍後會提及。
大家都是192.168.0.0/24……
首先第一件事是要把sys.net.ipv4.conf.all.rp_filter變成0。rp_filter是防source address spoofing,但檢查方法是拿source IP在routing table上對,然後看看對出來的interface是不是等同packet incoming interface。但我們有N張interface都是192.168.0.0/24,結果所有192.168.0.0/24都只會解釋到其中一張排最前的interface,從其他interface來的192.168.0.0/24 packet都會被掉。若kernel是看相關interface的route table則無此問題,但事實並非如此。
rp_filter檢測是發生在很前的位置,比ebtables BROUTING還要早。所以這個問題出現時,tcpdump -i $LANxIF就會發現LAN下面的client連”who has 192.168.0.1, tell 192.168.0.x”的arp request都得不到reply,mangle-PREROUTING的rule也不會被hit。
第二是從回來的packet,SNAT反譯過來只知道是從192.168.0.0/24走出來,但實際上哪個192.168.0.0/24才對?
辦法是結合ip rule的Policy Based Routing和iptables的fwmark,將所有到$WANxIP的packet都打個mark ($x*0×100) (要乘上0×100是因為不要與SNAT的marking衝突),在ip rule將$x*0×100的都packet都指向一個獨特建立的routing table。即是說……
iptables -t mangle -A PREROUTING -d $WANxIP -j MARK –or-mark ($x*0×100)
ip rule add fwmark ($x*0×100)/0xff00 table $x prio $x
ip route add table $x to 192.168.0.0/24 nexthop dev $LANxIF
ip route add table $x to unreachable 0/0
由於之前的SNAT反轉譯是在PREROUTING後FORWARD前進行的,所以我們才可以在PREROUTING用之後SNAT出去的$WANxIP做判定,而不是$CLIENTxIP。否則的話就需要用connmark等方法。
從LAN訪問Router機……
訪問WAN還是ok,到這一步之前都應該可以從裏面ping到外面的IP。測試時建議先從ip route table main拿掉所有192.168.0.0/24的route,以免因為剛巧你試的Router組是第一條route而false positive。
不過到這刻,從client side要ping 192.168.0.1還是會timeout,所以用FQDN ping外面也不成,因為192.168.1.1的DNS Proxy用樣也不會回應Client的query。原因是,從本機走出來的packet只會經過OUTPUT和POSTROUTING,而不會經過PREROUTING的。
最後還是要用上CONNMARK……每n組router建n條INPUT rule
iptables -t mangle -A INPUT -i $LANxIF -j CONNMARK –set-mark ($x*0×100)
整個系統建一條OUTPUT rule
iptables -t mangle -A OUTPUT -m mark –mark 0 -j CONNMARK –restore-mark
原理是因為connmark和mark的分別是在於connmark的marking是按整個”logical connection” (tcp/udp/icmp等都可以)來存取的,而mark只是當前的packet。所以我們先在INPUT chain set個connmark,之後在OUTPUT chain下,將connmark恢原到mark,那就能使用到「大家都是192.168.0.0/24……」中的ip rule和routing table。
完成後就能從client side正式用IP訪問到Internet了。也能用192.168.0.1來跟這個Router機聯系。
DMZ and Port Forwarding…
這也沒難度了,只要在nat-PREROUTING按–destination或在mangle配下來的0x**00 mark做篩選,再在-j DNAT到client side IP即可。跟一般的配置方法無異。
iptables -t nat -A PREROUTING -m mark –mark ($x*0×100)/0xff00 -j DNAT –to-destination $SOME_SERVER
Client side通過WAN IP訪問另一組Router下DMZ或Port Forward了的機器
這個過程需要同時做SNAT (將來源組Client Side IP轉寫成$WANsourceIP)和DNAT (將目標的$WANtargetIP轉寫成$SOME_SERVER),並需用上目標組所用的Routing table。之前的DNAT、SNAT、ip rule都要加mask,set mark時是用–or-mark,就時為了兩組設定都可以同時生效。除此要注意之外,並不需要其他配置即可使用。
Router機主動訪問某組Router下DMZ或Port Forward了的機器
目標是Router上通過$WANxIP可以訪問到已過DMZ/Port Forward了程幾器。直接用192.168.0.0/24的IP就不用想,除非所用的software特別支持指定outgoing interface (SO_BINDTODEVICE)。
問題跟從LAN訪問Router機一樣,因為Router機走出來的packet只會經過OUTPUT和POSTROUTING……。解決辦法是將之前配到mangle-PREROUTING的marking rule和nat-PREROUTING的DNAT rule照抄一次到mangle/nat-OUTPUT rule下即可。
還記得之前的的CONNMARK restore加了–mark 0的檢查嘛?就是想已detect到要DNAT的packets不要做connmark restore (因client box reply的packet會經過INPUT的connmark save)。但現在好像因為DNAT不對稱,所以不加–mark 0的檢查也會因connmark對不上口而沒問題。
結語
現在所有被forward的packet都應該有些marking,所以再安全點可以加一句:
iptables -t mangle -A FORWARD -m mark –mark 0 -j DROP
掌握了netfilter的流程後,以及COMP361/362 (科大的Networking Course)有用功讀的話,要實現起來還是不難的。































































中文
粵語
October 19th, 2008 at 23:22
thank you!
November 25th, 2008 at 8:59
Cool info, thanks for explaining and letting me learn a little more about IP’s.