原文地址 (http://www.study-area.org/tips/multipath.htm) version 0.04 date 2004-09-27 一、前言 由于ADSL 及Cable Modem 的普及,越来越多企业或个人所拥有的对外连线早已超过一条了, 不管是固接还是拨接,相信不少人都在思考如何用最有效的方式将多条连线作整合, 将所有连线的使用发挥至最大极限及最佳的使用率。 除了起到负载的分摊之外,还能达到断线备援目的,以提供更灵活的连线整合方案。 本文将以实作的方式试图在Linux 上做到上述要求。 二、环境 我先说明一下我的测试环境: 2.1 系统方面: 我目前测试的系统是RedHat 9.0 Linux ,采用"伺服器"类型安装,并没提供X 界面。 透过APT 更新至最新修补,并没安装distro 之外的套件。 事实上,只要实作所需的套件满足的话,并不需要安装任何伺服器套件。 甚至,核心版本也不需作任何修改,只是某些连线特征会有些差异(后文[7.1]再详述)。 2.2 网路方面: 原本的seednet adsl 是五个IP 拨接的, 除了之前透过ip share 来使用外,我再起了另外一个ppp0 界面。 此外另外再牵了一条hinet 固一adsl 。 如此环境,基本上能够分别测试到如下这几种连线方式: * 固接(固定IP) * 拨接(非固定IP) * ip share(非固定IP) 见图: /\__/\__/\ ,--| internet |--. / \/--\/--\/ \ | | | | +--[seednet ADSL] [hinet ADSL] | (非固定) (固一) | | 220.130.96.254 | [ ip share ] | | 192.168.100.1 | | | | | 192.168.100.2 220.130.96.21 | +----------------------------------+ +---| (eth1) (eth0) | ppp0| kernel 2.4.23 | +----------------------------------+ (my linux box RH9.0) 2.3 测试方式 虽然,拨接adsl 都是同一设备,或许还不十分理想,目前也只能如此了... 我采用的是拔线的方式来测断线的,暂还没想到其他方式,或许大家可以帮忙想想的... 我的测试基本上是用ping 来做: * 若是ppp 或不指定测试目标,我会用next hop 来测。 * 否则,我会用traceroute 找出ISP 端机房的router ip , 而不用next hop ,是因为怕不准,比方说断线是外部线路之类的。 若大家有更好提议,也欢迎提供.... 三、设计目标 实作方案基本上要做到如下这些要求: 3.1 出向负载分流 所有连线在正常情况下,将共同分摊由内对外产生的连线流量。 对于固定连线,可指定权重(weight),否则使用相同权重( weight 1)。 3.2 断线侦测 若有连线断掉,需自动从路由中移除。 当连线恢复时,则自动增加路由。 3.3 进向负载分流(额外需求) 外部连线进来,尽可能的将流量分摊在每一条连线上。 四、设计构思 4.1 出向负载分流 利用Linux iproute 程式,将每一条连线的gateway 及其权重增加至路由表的default route 中。 其中的固接adsl 及ip share 连线,需静态指定其gateway 位址及权重。 其余拨接或非固接连线,则用iproute 程式抓出当时的gateway ,权重分配为1 。 4.2 断线侦测 不管是固接还是非固接连线,用手工方式(如traceroute)抓出ISP 端的机房router 位址, 并确定用ping 可以获得回应。 然后用静态路由指定通向router 的nexthop gateway ,定期使用ping 来侦察连线。 每次侦测后都重跑iproute 程式,并重新设定路由表。 若ping 不成功,则抓出连线界面,且在重设路由表时忽略该界面连线。 4.3 进向负载分流(额外需求) 使用动态dns(ddns) 将为每一界面位址分配一个A 记录,以达到轮询回应结果, 从而让不同的外部连线请求轮流使用每一条连线进入。 本实作方案中,ddns server 建议部署于外部的稳定连线的主机上。 关于动态dns 的server 设定,不含在本次实作方案之内。可请参考: http://www.study-area.org/tips/ddns.htm 设计难点在于ipshare : 其中ipshare 部份需另行设定nat 转线。 否则需从清单中移除。 若nat 设定正确,接下来的难点是获得ipshare 的IP (因为也是非固接的), 这需要在外部的web server 另行开发php 程式来获取(可置于ddns server)。 关于这部份设计,请参考: http://phorum.study-area.org/viewtopic.php?p=108638 或使用如下代码: ----------------------- cut here ---------------------- <?php //Get the real client IP ("bullet-proof"???) function GetProxyIP() { if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown")) $ip = getenv("REMOTE_ADDR"); else $ip = "unknown"; return($ip); }/*-------GetIP()-------*/ function GetClientIP() { if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown")) $ip = getenv("HTTP_CLIENT_IP"); else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) $ip = getenv("HTTP_X_FORWARDED_FOR"); else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown")) $ip = getenv("REMOTE_ADDR"); else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")) $ip = $_SERVER['REMOTE_ADDR']; else $ip = "unknown"; return($ip); } printf("proxy IP: "); print_r(GetProxyIP()); printf("<br>\n"); printf("client IP: "); print_r(GetClientIP()); ?> ----------------------- cut here ---------------------- 五、实作指令 5.1 获取当前各界面之ip : # ip address show 1: lo: <LOOPBACK,UP> mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 brd 127.255.255.255 scope host lo 2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:02:44:84:26:4f brd ff:ff:ff:ff:ff:ff inet 220.130.96.21/24 brd 220.130.96.255 scope global eth0 3: eth1: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:20:ed:36:f9:74 brd ff:ff:ff:ff:ff:ff inet 192.168.100.2/24 brd 192.168.100.255 scope global eth1 4: eth2: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:02:b3:4b:69:49 brd ff:ff:ff:ff:ff:ff inet 10.1.2.3/24 brd 10.1.2.255 scope global eth2 15: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP> mtu 1492 qdisc pfifo_fast qlen 3 link/ppp inet 210.64.33.27 peer 210.64.33.1/32 scope global ppp0 5.2 设定ip rule : # ip rule add pref 10 from 220.130.96.21 table 10 # ip rule add pref 20 from 192.168.100.2 table 20 # ip rule add pref 30 from 210.64.33.27 table 30 5.3 设定ip route 各table : # ip route replace default via 220.130.96.254 dev eth0 table 10 # ip route replace default via 192.168.100.1 dev eth1 table 20 # ip route replace default via 210.64.33.1 dev ppp0 table 30 5.4 设定ip route main table: # ip route replace default \ > nexthop via 220.130.96.254 dev eth0 weight 4 \ > nexthop via 192.168.100.1 dev eth1 weight 1 \ > nexthop via 210.64.33.1 dev ppp0 weight 1 5.5 检视main table 规则: # ip route show 210.64.33.1 dev ppp0 proto kernel scope link src 210.64.33.27 192.168.100.0/24 dev eth1 scope link 220.130.96.0/24 dev eth0 scope link 10.1.2.0/24 dev eth2 scope link 169.254.0.0/16 dev eth2 scope link 127.0.0.0/8 dev lo scope link default nexthop via 220.130.96.254 dev eth0 weight 4 nexthop via 192.168.100.1 dev eth1 weight 1 nexthop via 210.64.33.1 dev ppp0 weight 1 5.6 刷新route cache: # ip route flush cache 5.7 测试及确认连线生效: 基本上,若在输入上述命令中没遇到error ,那设定就已完成。 接下来可起用多个对外连线(或用ping), 然后使用tcpdump -i any 来查看封包是否能分摊在每一条连线上。 六、撰写script : 若前述设定经测试成功后,接下来就是撰写script 以让工作自动进行。 6.1 configs * 说明: 提供给其它scripts 所需的共同变数值。 * 代码: ----------------------- cut here ---------------------- BASH=/bin/bash MAIL=root SH=/bin/bash LS=/bin/ls GREP=/bin/grep AWK=/bin/awk SED=/bin/sed HEAD=/usr/bin/head TAIL=/usr/bin/tail CUT=/bin/cut CAT=/bin/cat WC=/usr/bin/wc TR=/usr/bin/tr SEQ=/usr/bin/seq SORT=/bin/sort LSMOD=/sbin/lsmod IP=/sbin/ip PING=/bin/ping IPTABLES=/sbin/iptables ROUTE=/sbin/route DATE=/bin/date DHCP_DIR=/var/lib/dhcp STATIC=./statics INTERVAL=60 # check time interval in seconds RUN_SCRIPT=./run_ip.sh # script to run ip route DDNS_SCRIPT=./ddns/ddns.sh # script to run ddns update DDNS=yes # enable/disable ddns update #-- define public destination for ping --# fixed_dest="168.95.1.1" #-- define fixed interface & gateway --# #-- format: "if:gw:weight [if:gw:weight]..." --# fixed_gw="eth0:220.130.96.254:4 eth1:192.168.100.1:1" fixed_if="$(echo -e "${fixed_gw// /\n}" | cut -d: -f1)" #-- define internal interface, or comment out for none --# int_if="eth0" #-- statict routing dest & order --# #-- format: "static_xxx=value; static_xxxx_order=value" static_seed_net="139.175.0.0/16" static_seed_net_order="eth1 eth0" static_studyarea=140.113.27.184 static_studyarea_order="eth0 eth1" ----------------------- cut here ---------------------- * 变数设定说明: fixed_dest= 用来作ping 测试的外部IP。 fixed_gw= 固定界面之闸道及权重,格式为"界面名称:闸道位址:权重"。 int_if= 内部界面名称,可设定多个。该界面将不会用来设定default route 。 INTERVAL= 侦测动作之时间间隔,以秒数作单位。 RUN_SCRIPT= 执行路由设定script 之路径。执行时可使用-d 选项排除不必要之界面。 DDNS_SCRIPT= 执行动态DNS 更新script 之路径。执行时可使用-e 选项排除不必要之界面。 DDNS= 启用或关闭ddns 更新(yes|no)。 static_xxxxxx= 静态路由之目的地,可为单一IP 或net_ID/mask 。xxxxxx 为任意名称。 static_xxxxxx_order= 静态路由选取的界面次序。左边为最优,若该界面失效,则选下一个。 6.2 run_ip.sh * 说明: 此script 用来抓出系统全部界面及nexthop gateway , 并完成路由规则及路由表设定。 * 代码: ----------------------- cut here ---------------------- #!/bin/bash # # script name: run_ip.sh # purpose: changing route table. # author: netman(netman@study-area.org) # license: GPL(http://www.gnu.org/licenses/gpl.html) # # date: 2004-09-23 # version: v0.05 # # caveate: # 1) tested on redhat 9.0 linux only # 2) iproute program is required # #------------------------------------------------- ----------- # change log: # 1) 2004-03-23 v0.01 # * first beta release # 2) 2004-03-24 v0.02 # * BUGFIXED: # - Add IPIF and GWIP checking before change route. # 3) 2004-04-15 v0.03 # * Add function: # - Restart network if dead device detected. # 4) 2004-07-07 v0.04 # * IMPROVEMENT # - Create central config file # * Add function: # - Enable static routing # 5) 2004-09-23 v0.05 # * IMPROVEMENT # - More accurate on deleting old rules # - More accurate on deleting dead interface # - Add gw determining for dhcp interface # - Add deletion for dead route # * BugFix # - Use central conf file # - Filter out IPv6 address # #------------------------------------------------- ----------- WD=${0%/*} CONFIG=$WD/configs [ -f $CONFIG ] && . $CONFIG || { echo "${0#*/}: ERROR: can not load $CONFIG." exit 1 } #-- route command prefix --# DEFGW='/sbin/ip route replace default' #-- define dead interface --# while getopts "d:" opt; do case $opt in d) dead_dev=$OPTARG ;; esac done #-- determine active interfaces --# interfaces=$($IP address | $GREP -E 'ppp|eth' | \ $AWK -F': ' '/^[0-9][0-9]*:/{print $2}' | $SORT -u \ | grep -Ev "${int_if// /|}") #-- remove old rule table --# for i in $interfaces; do $IP address | $GREP -A2 $i | $AWK '/inet[^6]/{print $2}' | $CUT -d'/' -f1 done | xargs -n1 echo from | while read line; do $IP rule | grep "$line" done | $SED -e "s/^/ip rule del pref /;s/://;s/lookup/table/" | $BASH unset i #-- remove dead interfaces --# if [ -n "$dead_dev" ]; then interfaces=$(echo "$interfaces" | grep -Ev "${dead_dev// /|}") for dev in $dead_dev; do DEF_ROUTE=$($IP route list | grep '^default' | grep "dev $dev") echo "$DEF_ROUTE" | while read line; do $IP route del $line done done fi #-- exit while all dead --# [ "$interfaces" ] || { $IP route del default exit 2 } #-- define table_id --# init_num=10 offset=`echo -e "${interfaces// /\n}" | $WC -l` last_num=$(($init_num + $((offset * 10)) )) tb_num=`$SEQ -s ' ' $init_num 10 $last_num` #-- FUNCTION: determine DHCP assigning gw --# dhgw() { get_lastest () { OIFS="$IFS" IFS=' ' for line in $RnT1 $RNT2; do l="$(echo $line | grep -o 'lease-time[^;]*' | cut -d' ' -f2)" r="$(echo $line | grep -o 'routers[^;]*' | cut -d' ' -f2)" e="$(echo $line | grep -o 'expire[^;]*' | cut -d' ' -f3-)" EX_TIME=$($DATE -d "$e" +%s) if [ "$((EX_TIME - l))" -gt "${P_TIME-0}" ]; then P_TIME=$((EX_TIME-l)) D_TIME=$EX_TIME ROUTER=$r fi done IFS="$OIFS" echo $D_TIME $ROUTER } DH_IF=$1 shift 1 RnT1=$($CAT $1 2>/dev/null | xargs echo | tr '}{' '\n' | $GREP $DH_IF | $TAIL -n 2) RnT2=$($CAT $1 2>/dev/null | xargs echo | tr '}{' '\n' | $GREP $DH_IF | $TAIL -n 2) [ "$RnT1" -o "$RnT2" ] || return GnT=$(get_lastest) C_TIME=$($DATE +%s) [ "${GnT% *}" -gt $C_TIME ] && GWIP=${GnT#* } } #-- FUNCTION: determine gw --# dgw() { unset GWIP #-- for fixed if --# if echo $fixed_if | $GREP -wq $1 ; then GWIP=`echo -e "${fixed_gw// /\n}" | $GREP "^$1:" | \ $CUT -d: -f2` WEIGHT=`echo -e "${fixed_gw// /\n}" | $GREP "^$1:" | \ $CUT -d: -f3` #-- for ppp if --# elif echo $1 | $GREP -q ppp ; then GWIP="`$IP address show dev $1 | $AWK '/inet[^6]/{print $4}' | \ $CUT -d'/' -f1 | $HEAD -n 1`" else #-- try current gw --# GWIP=$($ROUTE -n | $GREP $1'$' | $AWK '/^0.0.0.0/{print $2}' | $HEAD -n 1) fi #-- for dhcp if --# [ "$GWIP" ] || dhgw $1 $DHCP_DIR/dhclient.leases $DHCP_DIR/dhclient-$i.leases } #-- config rule & routing --# for i in $interfaces ; do unset IFIP GWIP WEIGHT IFIP="`$IP address show dev $i | $AWK '/inet[^6]/{print $2}' | \ $CUT -d'/' -f1`" dgw $i [ "$tb_num" ] && tb_id=${tb_num%% *} [ "$IFIP" -a "$GWIP" ] || continue # check gw $IP route replace $fixed_dest via $GWIP dev $i &>/dev/null && \ sleep 2; $PING -c1 -w2 $fixed_dest &>/dev/null && GWOK=true $IP route del $fixed_dest via $GWIP dev $i &>/dev/null if [ "$GWOK" = true ]; then unset GWOK else continue fi # set rule $IP rule add pref $tb_id from $IFIP table $tb_id # set route to gw $IP route replace default via $GWIP dev $i table $tb_id DEFGW=$DEFGW" nexthop via $GWIP dev $i weight ${WEIGHT:=1}" SETGW=true tb_num=${tb_num#* } done #-- apply routing --# [ "$SETGW" = true ] && { #-- remove old default route --# DEF_NU=$($IP route | $GREP -c '^default') for ((i=1;i<=$DEF_NU;i++)); do $IP route del default done #-- set new default route --# $DEFGW } #-- apply static routes --# [ -f $STATIC ] && . $STATIC #-- flush cache --# $IP route flush cache #-- show routing $IP route list #-- FINISH --# exit 0 ----------------------- cut here ---------------------- * 参数说明: -d "<interface...>" 指定失效界面,可设定多个。 6.3 statics * 说明: 此script 用来设定静态路由,封包将只会从单一连线送出,而不会分摊至其他连线。 * 代码: ----------------------- cut here ---------------------- #!/bin/bash # # script name: statics # purpose: set static routes. # author: netman(netman@study-area.org) # license: GPL(http://www.gnu.org/licenses/gpl.html) # # date: 2004-09-23 # version: v0.02 # # caveate: # 1) tested on redhat 9.0 linux only # 2) iproute program is required # #------------------------------------------------- ----------- # change log: # 2004-07-08 v0.01 # * first beta release2 # 2004-09-23 v0.02 # * Improvment: # - using flexible static_xxxx variable WD=${0%/*} CONFIG=$WD/configs [ -f $CONFIG ] && . $CONFIG || { echo "${0#*/}: ERROR: can not load $CONFIG." exit 1 } set_static () { while [ "$2" ]; do eval s_dest=\$$1 eval s_order=\$$2 all_gw="$($IP route show | $SED -n '/^default/,$p')" for if in $s_order; do if echo "$all_gw" | $GREP -q "$if" ; then sn_gw=$(echo "$all_gw" | $GREP $if | awk '{print $3}') sn_gw=${sn_gw%/*} break fi done [ "$sn_gw" ] && { $IP route replace "$s_dest" via $sn_gw } shift 2 done } set_static ${!static_*} ----------------------- cut here ---------------------- * 注意要点: 此script 可由run_ip.sh 来呼叫,也可以独立使用。 6.4 chk_line.sh * 说明: 此script 用来侦测连线是否正常,并定期执行run_ip.sh 及ddns.sh(选用)。 若抓出失效界面,再用-d 参数传给run_ip.sh ,该界面将被忽略。 * 代码: ----------------------- cut here ---------------------- #!/bin/bash # # script name: chk_ip.sh # purpose: checking outbound link and change route and dns. # author: netman(netman@study-area.org) # license: GPL(http://www.gnu.org/licenses/gpl.html) # # date: 2004-09-23 # version: v0.04 # # caveate: # 1) tested on redhat 9.0 linux only # 2) using PING for link detection # 3) iproute program is required # 4) scripts run_ip.sh & ddns.sh are required too # #------------------------------------------------- ----------- # change log: # 1) 2004-03-23 v0.01 # * first beta release # 2) 2004-03-24 v0.02 # * add script path detection # 4) 2004-07-07 v0.03 # * IMPROVEMENT # - Create central config file # * Add function: # - Add email notification # 5) 2004-09-23 v0.04 # * IMPROVEMENT # - Rewrite the method of getting dead interface # #------------------------------------------------- ----------- WD=${0%/*} CONFIG=$WD/configs [ -f $CONFIG ] && . $CONFIG || { echo "${0#*/}: ERROR: can not load $CONFIG." exit 1 } # change working directory cd $WD #-- determine gateways --# get_gw () { defaults=$($IP route show | $SED -n '/default/,$p' | $GREP via) if_n_gw=$(echo -en "$defaults" | $AWK '{print $5":"$3":'$fixed_dest'"}') if [ -n "$fixed_gw" ]; then fixed_pair=$(echo -e "${fixed_gw// /\n}" | awk -F: '{print $1":"$2":'$fixed_dest'"}') dyn_pair=$(echo "$if_n_gw" | $GREP -Ev "$(echo $fixed_if | $TR ' ' '|')") all_pair=$(echo $fixed_pair $dyn_pair) else all_pair=$(echo "$if_n_gw") fi } #-- configure static route --# get_dev () { for i in "$@"; do if=${i%%:*} gw=$(echo $i | $CUT -d: -f2) dest=${i##*:} $IP route replace $dest via $gw dev $if && \ sleep 1; $PING -c1 -w2 $dest &>/dev/null || echo $if $IP route del $dest via $gw dev $if done unset i } #-- run loop --# while : ; do get_gw # have gw d_dev=$(echo $(get_dev $all_pair)) # have dead link [ "$d_dev" ] && { # send mail if dead link found echo "dead link found: $d_dev" | \ mail -s "$(hostname): dead link" $MAIL } $SH $RUN_SCRIPT -d "$d_dev" # to run ip script [ -f "$DDNS_SCRIPT" -a "$DDNS" = yes ] && $SH $DDNS_SCRIPT unset all_pair sleep $INTERVAL done ----------------------- cut here ---------------------- 6.5 ddns/ddns.sh (选用项目) * 说明: 此script 用来动态侦测连线位址,并动态更新dns 位址记录。 * 代码: ----------------------- cut here ---------------------- #!/bin/bash # # script name: ddns.sh # purpose: updating dns record # author: netman(netman@study-area.org) # license: GPL(http://www.gnu.org/licenses/gpl.html) # # date: 2004-09-25 # version: v0.04 # # caveate: # 1) tested on redhat 9.0 linux only. # 2) iproute program is required. # 3) a php webpage to show ip, and the lynx program are required. # 4) ddns server and ddns key are required. # #------------------------------------------------- ----------- # change log: # 1) 2004-03-23 v0.01 # * first beta release # 2) 2004-03-23 v0.02 # * add EXCL_IF list for exception # 3) 2004-03-23 v0.03 # * add -e option for appending EXCL_IF # 4) 2004-09-25 v0.04 # * BugFix: detect gw interface list # #------------------------------------------------- ----------- # set commands HOST=/usr/bin/host GREP=/bin/grep CAT=/bin/cat IP=/sbin/ip SED=/bin/sed AWK=/bin/awk LYNX=/usr/bin/lynx TOUCH=/bin/touch DIFF=/usr/bin/diff NSUPDATE=/usr/bin/nsupdate MV=/bin/mv # set variables if echo $0 | $GREP '^/' ; then w_dir=${0%/*} else w_dir=$PWD/${0%/*} fi KEY_FILE=$w_dir/Ktestddns.+157+14615.key UPDATE_DATA=$w_dir/nsupdate.data UPDATE_OLD=$UPDATE_DATA.old HOST_NAME=testddns.study-area.org NS_SERVER=dns.study-area.org # don't use IP address IP_SERVER=dns.study-area.org # server running ip.php program IP_SERVER_IP=$(echo $($HOST $IP_SERVER) | tail -n 1 | $AWK '{print $NF}') IP_URL="http://$IP_SERVER/ip.php" CLIENT_TYPE="client" # no proxy or using ISP's proxy #CLIENT_TYPE="proxy" # use local proxy MASQ_IF="eth1" # which masqueraded behine NAT #EXCL_IF="eth1" # note: also use -e options to append list # have exception while getopts "e:" opt; do case $opt in e) EXCL_IF=$(echo $EXCL_IF $OPTARG) ;; esac done # ensure key files for file in $KEY_FILE ${KEY_FILE%key}private do if [ ! -r $file ]; then echo "$(basename $0): ERROR: $file is not readable." exit 1 fi done # ensure the server is connectable $HOST $NS_SERVER $NS_SERVER | $GREP -q "^$NS_SERVER has address" || { echo "$(basename $0): ERROR: could not contact nameserver $NS_SERVER." exit 2 } # prepare initial script $CAT >| $UPDATE_DATA <<end server="" $ns_server="" update="" delete="" $host_name="" a="" end="" test="" "$?"="0" ||="" {="" echo="" "$(basename="" $0):="" error:="" could="" not="" create="" $update_data."="" exit="" 3="" }="" #="" get="" interfaces="" all_if="$($IP" route="" show="" |="" $sed="" -n="" '="" default="" ,$p'="" $awk="" via="" {print="" $5}')="" [="" "$excl_if"="" ]="" &&="" "$all_if"="" $grep="" -ev="" "${excl_if="" |}")="" masqueraded="" ip="" get_mip="" ()="" for="" m_if="" in="" $masq_if;="" do="" $all_if="" -qw="" "$m_if"="" continue="" m_gw="$($IP" |\="" '$m_if'="" $3}')="" $ip="" replace="" $ip_server_ip="" $m_gw="" sleep="" 2="" m_ip="$($LYNX" --dump="" $ip_url="" "$client_type"="" -eo="" '([0-9]{1,3}\.){3}[0-9]+')="" del="" -z="" "$m_ip"="" "${0##*="" }:="" can't="" determine="" $m_if."="" 4="" $m_if:$m_ip="" unset="" done="" current="" if="" $all_if;="" "$masq_if"="" -wq="" "$if";="" then="" new_ip="$(get_mip" -f:="" '$if'="" $2}')="" else="" address="" dev="" $if="" inet="" fi="" $new_ip="" "$new_ip"="" "update="" add="" 0="" $new_ip"="">> $UPDATE_DATA echo "send" >> $UPDATE_DATA # do a test then update $TOUCH $UPDATE_OLD if ! $DIFF $UPDATE_OLD $UPDATE_DATA; then $NSUPDATE -k $KEY_FILE -v $UPDATE_DATA && { $MV $UPDATE_DATA $UPDATE_OLD } fi #-- end of script --# ----------------------- cut here ---------------------- * 变数设定说明: KEY_FILE= 用以更新dns 记录的key 。 UPDATE_DATA= 储存nsupdate 所需的命令稿。 UPDATE_OLD= 上一次nsupdate 所需的命令稿。 script 会比对前一份命令稿内容,若相同,则不做update ,否则才做。 HOST_NAME= 主机名称。需与ddns 所设定的记录一致。 NS_SERVER= ddns server 之主机名称,不能使用IP 位址。 IP_SERVER= 侦测IP 用的主机名称。 IP_SERVER_IP= 侦测IP 用的主机位址。 IP_URL= 侦测IP 用的网页URL 。 CLIENT_TYPE= 主机连线类型。其值为"client" 或"proxy" 二者之一。 若主机没有使用proxy 或使用ISP 提供的proxy,请设为"client"。 若主机使用local LAN 的proxy ,则请设为"proxy"。 MASQ_IF= 连线将会经过NAT 的界面,但其伪装位址需作ddns 更新。 (注意:NAT 必须要有相应的port mapping 设定。) EXCL_IF= 排除在ddns 更新之外的界面。 通常是连线将会经过NAT 的界面,但其伪装位址不作ddns 更新。 * 参数说明: -e "<interface...>" 排除在ddns 更新之外的界面,可设定多个。 若为多个界面,其值必须至于双引号(" ")或单引号(' ')之中,如: -e "eth0 eth1" 参数值将会扩充至$ESCL_IF 变数里面。 * 注意要点: 1) 关于动态ddns 的server 及client 端设定,请另行参考: http://www.study-area.org/tips/ddns.htm 2) 请定期检测及确认ddns server 能够连线及正常运作。 3) 当前实作范例,是将ddns.sh 与相关的keys 置于ddns 子目录中。 6.6 软件包 我将本实作所用到的script 及其它相关档案包装为tarball ,其内容如下: multipath/ 工作目录 multipath/run_ip.sh 路由更新script multipath/chk_line.sh 连线检测script multipath/ddns/ ddns 子目录 multipath/ddns/ddns.sh ddns 更新script multipath/ddns/Ktestddns.+157+14615.key ddns key multipath/ddns/Ktestddns.+157+14615.private ddns private key multipath/configs 共用变数 multipath/kernel-patch/ kernel-patch 子目录 multipath/kernel-patch/2.4.25-mp01 kernel 2.4.25 config 档 multipath/kernel-patch/patch-2.4.25-ja2.diff kernel 2.4.25 修补档 multipath/statics 静态路由script 大家可以从如下位址下载整个软件包: http://www.study-area.org/linux/src/multipath.tgz 6.7 执行script 以上script 均可独立执行, 其种最为主要的script 是chk_line.sh , 可用多种方式执行, 因为是无穷回圈, 可用backupground 方式启动: ./chk_line.sh & 执行killall chk_line.sh 时结束. 另一种方式是以init 用respawn 方式来执行. 修改/etc/inittab, 增加: ip:35:respawn:/path/to/chk_line.sh 然后执行: init q 要结束的话, 需修改inittab, 在句子前加# 注解, 再重跑init q 即可. 七、关于kernel patch 7.1 关于patch-2.4.25-ja2.diff 本实作方案不一定需要进行kernel path ,经测试是可用的。 只是因为routing cache 的关系,通往同一个destination 的routing 可能在某一段时间内是不变的。 参考http://www.lartc.org/howto/lartc.rpdb.multiple-links.html 最后一段有提到: Note that balancing will not be perfect, as it is route based, and routes are cached. This means that routes to often-used sites will always be over the same provider. 这对于某些需要作session 记忆的连线来说(如连接phpBB 讨论版),是不错的。 但倘若多条session 在某一特定时间内均为同一目标位址,其流量并不会分摊到其它线路上。 如果要做到将不同的session 分摊开来,而不论其目标位址是否相同,则须要下载patch 修补。 我在RedHat 9.0 上另行下载kernel 2.4.25 源始码及patch-2.4.25-ja2.diff 这支patch 。 大家可从前面6.4 章节提供的tarball 获取这次实作所用的config 及patch 。 或至官方网站下载: http://www.ssi.bg/~ja/patch-2.4.25-ja2.diff 7.2 关于equailze 虽然前述patch 能够将不同的session 分摊至不同连线,但这仍然是by session 的分摊, 并非做到by packet 层级的分摊,也就是所谓的"均衡负载"(equailze)。 要做到这点,似乎还要下载另一支patch: http://www.teatime.com.tw/~tommy/linux/equalize.patch 不过,遗憾的是,这部份我并没有测试成功。 且后来也没在kernel 2.4.25 上与另一支patch 混合使用。 关于equailze 的探讨及设定,大家可参考study-area 的讨论: http://phorum.study-area.org/viewtopic.php?t=10085 若有人成功测试起来,欢迎回报分享。谢谢﹗ 八、展望 当前实作并没提及频宽控管。若日后有时间,再来探讨QOS 及cbq 相关的题目。 在这之前,大家不妨先参考: http://peterkim.cgucccc.org/document/MPath.html 九、参考资料 http://phorum.study-area.org/viewtopic.php?t=10085 http://phorum.study-area.org/viewtopic.php?t=9057 http://www.teatime.com.tw/~tommy/doc/multipath.txt http://www.study-area.org/tips/m_routing.htm http://www.lartc.org/howto/lartc.rpdb.multiple-links.html http://peterkim.cgucccc.org/document/MPath.html ------- 本文结束------ |
Linux 上多条对外连线(Multi-Path)实作------带宽负载平衡
最新推荐文章于 2023-11-23 14:27:25 发布