Linux ADSL Multipath Routing Solution By Tommy Wu 由於前些日子, 我所服務的單位, 決定將原本使用的專線退租, 改用 ADSL 來提供 上網的服務, 為了降低 ADSL 斷線無法連線的情形, 所以由 SeedNet, Hinet 各申 請了一條單機型的 T1/384 ADSL 來使用. 但是一般 ISP 業者似乎沒有提供兩條線路頻寬合用的服務, 更何況是使用兩家不同 ISP 的線路, 所以決定自己利用 Linux 的 Equal-Cost MultiPath Routing (ECMP) 功能來解決這個問題. 關於 ECMP 的設定說明, 可以參考 http://www.study-area.org/tips/m_routing.htm 初期, 我利用上述的功能來處理, 但是發現效率不怎麼好, 由於路由的決定, 並不 是依據 packet 來傳送, 所以出去的時候, 同一個 session 只會使用一條線路, 仍然會常常造成, 明明另一條線路沒有什麼流量, 但是大家都擠在同一條線路的情 形發生. 在 iproute2 的說明中有提到 equalize 參數, 會將 packet 分散到不同的線路上 面, 但是 kernel 需要另外的 patch 才能運作, 上網查了一下, 有找到下面這個 patch: http://www.van-dijk.net/linuxkernel/200212/att-0980/01-equalize_2.4.18.patch 測試後發現的確可以同時使用不同的線路傳送 packet. 現在說明一下, 如何做到這 個功能: 1. 你必須要有 iproute2 套件. 另外由於要對 kernel 加上另外的 patch, 所以相關 的工具也是必要的. 2. 由 www.kernel.org 抓下 linux-2.4.18.tar.bz2 與上述的 patch. 上述的 patch, 我在 2.4.18 之後的版本都裝過, 都可以正常使用, 之前的 kernel 沒有試過, 並不清楚... 不過 2.4.18 應該算是 2.4 版本中最穩定的一個版本, 如 果你還在用舊的 kernel, 建議升級到這個版本. tommywu@fw:/usr/src$ tar jxvf linux-2.4.18.tar.bz2 ... ... tommywu@fw:/usr/src$ cd linux tommywu@fw:/usr/src/linux$ patch -p1 < ../01-equalize_2.4.18.patch patching file Documentation/networking/load-balancing.txt patching file include/linux/in_route.h patching file net/ipv4/fib_semantics.c patching file net/ipv4/ip_output.c patching file net/ipv4/route.c patching file net/ipv4/udp.c 這個 patch 並不會新增任何的設定, 所以你可以參考上述 ECMP 文件中的設定選 項來設定你的 kernel. 然後重新 make 一個新的 kernel 來使用. 我通常會選取 下面這幾個功能: CONFIG_IP_MULTICAST=y CONFIG_IP_ADVANCED_ROUTER=y CONFIG_IP_MULTIPLE_TABLES=y CONFIG_IP_ROUTE_FWMARK=y CONFIG_IP_ROUTE_NAT=y CONFIG_IP_ROUTE_MULTIPATH=y CONFIG_IP_ROUTE_TOS=y CONFIG_IP_ROUTE_VERBOSE=y CONFIG_IP_ROUTE_LARGE_TABLES=y 3. 利用上面的 kernel 重新開機之後, 就應該可以使用 equalize 參數了. 多數的設 定都與上述的 ECMP 文件相同, 只是多了一個 equalize 參數. 舉例來說: # 對外網卡 EXT_IF="eth0" # HiNet IP EXT_IP1="111.111.111.111" EXT_MASK1="24" GW1="111.111.111.1" # SeedNet IP EXT_IP2="222.222.222.222" EXT_MASK2="24" GW2="222.222.222.1" # 設定 ip ip addr add $EXT_IP1/$EXT_MASK1 dev $EXT_IF ip addr add $EXT_IP2/$EXT_MASK2 dev $EXT_IF # 設定 HiNet routing ip rule add to $EXT_IP1/$EXT_MASK1 lookup 201 ip route add default via $GW1 dev $EXT_IF table 201 # 設定 SeedNet routing ip rule add to $EXT_IP2/$EXT_MASK2 lookup 202 ip route add default via $GW2 dev $EXT_IF table 202 # 設定 Default route ip route replace default equalize \ nexthop via $GW1 dev $EXT_IF \ nexthop via $GW2 dev $EXT_IF # 清除 route cache ip route flush cache 利用上面的設定, 我們就可以將兩條線路合併使用. 以 T1/384 的 ADSL 來說, 一般 上傳的頻寬約可到 40KB 上下, 現在利用這個 patch, 我們上傳一個檔案試看看: tommywu@hisstby:/usr/src$ ftp ftp.teatime.com.tw Connected to www.teatime.com.tw. 220 ProFTPD 1.2.5rc1 Server (Debian) [211.23.144.122] Name (ftp.teatime.com.tw:tommywu): tommy 331 Password required for tommy. Password: 230 User tommy logged in. Remote system type is UNIX. Using binary mode to transfer files. ftp> bin 200 Type set to I. ftp> put patch-2.4.19-pre6.bz2 local: patch-2.4.19-pre6.bz2 remote: patch-2.4.19-pre6.bz2 200 PORT command successful. 150 Opening BINARY mode data connection for patch-2.4.19-pre6.bz2. 226 Transfer complete. 3858685 bytes sent in 49.92 secs (75.5 kB/s) ftp> quit 221 Goodbye. tommywu@hisstby:/usr/src$ 可以超過 40KB, 也就是的確會同時使用到兩條線路來傳送. 如果有興趣, 可以到下列的網址查看流量: http://fw1.tahsda.org.tw/stats/mrtg/ http://fw2.tahsda.org.tw/stats/mrtg/ 要注意這只有出去的 packet 是我們這端所能控制的, 回來的 packet 就不是我們 可以控制了, 所以出去的流量在不同的線路上看起來是類似的, 但是回來的流量就 不一定了. 如果要控制進來的流量, 可能要利用 DNS 的方式來控制了. 接下來, 要考慮一條線路斷線時, 要改變 routing table 的設定. 由於 ADSL router 應該都有支援 SNMP 的功能, 所以我們可以利用 SNMP 來判斷是否斷線. 你要先確定你 的 linux 中有 snmpd, snmp 套件. 我們有兩種方式來處理, 第一種是利用 snmp traps. 以 arcatel 340 來說, 內定的 password 應該是 12345. telnet 192.168.1.1 之後, 打入密碼, 在 > 符號打入 snmp, 就會進入 snmp> 設定 目錄, 打入 snmp trap help 有說明如下: snmp trap add [] - add a trap destination snmp trap delete [] - delete a trap destination snmp trap flush - delete all trap destinations snmp trap list - list trap destinations 假定你要收 trap 的 ip 是 192.168.1.254, 就打入 snmp trap add public 192.168.1.254 就可以了. 這樣就應該在該機器可以收到相對的 snmp traps. 以上面 Hinet/SeedNet 兩個線路來說, 如果要在同一網段上, 要先改變其中一個 ATU-R 的 ip 才可以, 內定都是 192.168.1.1, 我們把其中一個改成 192.168.1.2 然後在 192.168.1.254 機器上安裝 snmptrapd, 修改 /etc/snmp/snmptrapd.conf 加上下面兩行: traphandle .1.3.6.1.6.3.1.1.5.2 /usr/local/bin/adsl_up traphandle .1.3.6.1.6.3.1.1.5.3 /usr/local/bin/adsl_down 然後寫 adsl_up, adsl_down 兩個 script 來更改 route table. 內容大約是: #!/bin/bash read DUMMY_HOST read ROUTE_IP case "$ROUTE_IP" in 192.168.1.1) ip route replace default .... ;; *) echo "snmp traps from unknown ip?" esac 這樣子就會在每次斷線或恢復連線時收到 ATU-R 的通知了. 另外, 如果不想使用 snmp trap, 要使用 polling 的方式, 主動去查詢線路情形, 可以使用 snmpwalk 來處理, 用上面的例子, 可以先執行 snmpwalk 192.168.1.1 public interfaces.ifTable.ifEntry 會出現一堆資料, 看一下 adsl 在的 index 是 20. interfaces.ifTable.ifEntry.ifDescr.20 = ADSL physical interface interfaces.ifTable.ifEntry.ifType.20 = adsl(94) 線路狀態在 interfaces.ifTable.ifEntry.ifOperStatus.20 = up(1) up(1) 表示連線中, 所以執行 snmpwalk 192.168.1.1 public interfaces.ifTable.ifEntry.ifOperStatus.20 會得到 interfaces.ifTable.ifEntry.ifOperStatus.20 = up(1) 當斷線時, 上面就不會在 up(1) 的狀態. 所以可以寫個 adsl_test 的 script snmpwalk 192.168.1.1 public interfaces.ifTable.ifEntry.ifOperStatus.20 | grep "up(1)" | wc -l 如果得到 0 就是斷線. 上面就是使用 snmpwalk 來查詢線路的方法. 這個我在 arcatel 340 上面使用, 可以正確得知線路的情形. 另外, 在我家的 cisco 677 上, 在 bridge mode 下, 使用 snmpwalk 查詢線路都是 up(1), 無法知道線路情形... 不過如有異動, 仍可 以收到對應的 snmp traps. 這兩種方式各有利弊, 第一種方式無法得知一開始的狀態, 第二種方式無法即時 得知狀態的改變, 所以通常會同時利用兩種方式來處理, 以上述的例子來說, 我 們可以使用下列的 script 來處理: #!/bin/bash # chk_adsl: check the status of ADSL and change routing table # SNMP tree SNMP_KEY="interfaces.ifTable.ifEntry.ifOperStatus.20" # 對外網卡 EXT_IF="eth0" # HiNet IP EXT_ATUR1="192.168.1.1" EXT_IP1="111.111.111.111" EXT_MASK1="24" GW1="111.111.111.1" # SeedNet IP EXT_ATUR2="192.168.1.2" EXT_IP2="222.222.222.222" EXT_MASK2="24" GW2="222.222.222.1" STATUS1=`snmpwalk $EXT_ATUR1 public $SNMP_KEY | grep "up(1)" | wc -l` STATUS2=`snmpwalk $EXT_ATUR2 public $SNMP_KEY | grep "up(1)" | wc -l` if [ "$STATUS1" = "0" ]; then if [ "$STATUS2" = "0" ]; then # ALL line down echo -e "\n\ ALL ADSL DOWN!\n\ Date: $(date)\n\ Host: $(hostname)\n\ " | /bin/mail -s "$(date) ALL ADSL DOWN!" root else # ATUR1 down, ATUR2 up echo -e "\n\ ADSL $EXT_ATUR1 DOWN!\n\ Date: $(date)\n\ Host: $(hostname)\n\ " | /bin/mail -s "$(date) ADSL $EXT_ATUR1 DOWN!" root ip route relpace default via $GW2 dev $EXT_IF ip route flush cache fi else if [ "$STATUS2" = "0" ]; then # ATUR1 up, ATUR2 down echo -e "\n\ ADSL $EXT_ATUR2 DOWN!\n\ Date: $(date)\n\ Host: $(hostname)\n\ " | /bin/mail -s "$(date) ADSL $EXT_ATUR2 DOWN!" root ip route relpace default via $GW1 dev $EXT_IF ip route flush cache else # All line up echo -e "\n\ ALL ADSL UP!\n\ Date: $(date)\n\ Host: $(hostname)\n\ " | /bin/mail -s "$(date) ALL ADSL UP!" root ip route replace default equalize \ nexthop via $GW1 dev $EXT_IF \ nexthop via $GW2 dev $EXT_IF ip route flush cache fi fi 利用上面的 script, 我們只要在 snmptrapd.conf 中將相關的 traphandle 指到這一個 script, 就可以依照線路的狀態來做對應的處理. 當然, 如果你 的線路不只兩條, 我想應該也可以照上面的作法自己試試看. 本文的最新版本可以由下列的網頁取得: http://www.teatime.com.tw/~tommy/doc/multipath.txt