shell-9-函数(tc与限速实例)
2011-09-25 20:48:45
标签:linux 限速 shell tc function
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://dngood.blog.51cto.com/446195/673728
此文分为两部分
1 脚本与函数
2 tc 与 htb (cbq 版这里)
一 脚本与函数
#创建函数
注意必须先定义好函数,后才能调用,所以通常将函数定义在shell脚本头部!!!
此函数定义了两个变量clasid_vlan与classid_ip 用于接受传递参数值
#classid
tc_classid() {
classid_vlan=$1
classid_ip=$2
if [[ "$classid_vlan" -ge "1" && "$classid_vlan" -le "9" ]];then
echo $(((classid_vlan-1)*lie+(classid_ip-1)))
fi
if [[ "$classid_vlan" -ge "10" && "$classid_vlan" -le "49" ]];then
echo "ip $classid_vlan error"
fi
if [[ "$classid_vlan" -ge "50" && "$classid_vlan" -le "59" ]];then
echo $((($classid_vlan-41)*lie+(classid_ip-1)))
fi
}
#调用函数
下面将在另一个函数tc_show()中调用了先前定义的tc_classid()函数
#show
tc_show() {
local tmp_1=$1
local tmp_2=$2
local tmp_3=$3
local tmp_4=$4
show=`tc_classid $tmp_3 $tmp_4`
if ["$tmp_2"=down];then
tmp_2=eth1
else
tmp_2=eth0
fi
tc class show dev $tmp_2|grep ":${show}[]" | awk'{print "speed:" $11}'
echo"$tmp_1 $tmp_2 $tmp_3 $tmp_4"
echo"classid:$show"
}
向函数传递参数
bash shell将函数作为小型脚本处理,函数可以使用标准参数环境变量来表示命令行传递给函数的参数!!注意,因为函数为自己的参数值使用专用的参数环境变量,所以函数无法从脚本命令行直接访问脚本参数值,也就是说函数不识别运行脚本时后面附带的参数值。如果要调用 则可以将脚本的参数赋值给函数中的局部变量,例如
local tmp_1=$1
local tmp_2=$2
local tmp_3=$3
local tmp_4=$4
#tc classid 传递两个参数 $tmp_3 与 $tmp_4 ,如下
show=`tc_classid $tmp_3 $tmp_4`
函数返回值
bash shell将函数看成小型脚本,并以退出状态结束,我们可以通过$?查看三种不同的退出状态,如果不使用return 来指定返回值,则以最后一个执行的命令退出状态为返回值,从这点我们发现函数的默认退出状态是不可靠的!所以我们可以
使用return 来返回函数值;还可以使用函数输出,对于命令输出可以捕获并存放到shell变量中,函数的输出也可以捕获并放到shell变量中!
这里使用反引号show=`tc_classid $tmp_3 $tmp_4`来包围函数及其传递的参数,是为了将函数返回值赋值给变量 $show
函数中的变量
这里要提及一个作用域的概念,表示说变量的可见区域。函数内定义的变量和普通变量有不同的作用域,脚本外部定义的变量要覆盖函数内定义的变量。函数使用两个类型的变量:
全局变量:是shell脚本内处处有效的变量,默认情况下脚本中定义的变量都是全局变量,而默认定义的变量在函数内部也可以正常访问的
局部变量:使用local定义作用与函数内部的,比如上边的local tmp_1=$1
#函数库共享函数
cat /etc/init.d/network | grep -i 'functions'
. ./network-function
脚本 /etc/init.d/network 通过 . ./network-function 函数库
../点符号符来使得脚本能以调用库文件的函数,同理我们也可以定义自己的函数库文件
#完整脚本
#!/bin/bash
#tc htb
#20110927 by dongnan ver0.2
#variables
vlan=59
ip=254
ip_duan=10.0
num=0
#
down_dev=eth1
up_dev=eth0
#
lie=253
vip_download=1600Kbit
vip_upload=1600Kbit
#
iptables=/sbin/iptables
tc=/sbin/tc
#function
#classid
tc_classid() {
classid_vlan=$1
classid_ip=$2
if [[ "$classid_vlan" -ge "1" && "$classid_vlan" -le "9" ]];then
echo $(((classid_vlan-1)*lie+(classid_ip-1)))
fi
if [[ "$classid_vlan" -ge "10" && "$classid_vlan" -le "49" ]];then
echo "ip $classid_vlan error"
fi
if [[ "$classid_vlan" -ge "50" && "$classid_vlan" -le "59" ]];then
echo $((($classid_vlan-41)*lie+(classid_ip-1)))
fi
}
#show
tc_show() {
local tmp_1=$1
local tmp_2=$2
local tmp_3=$3
local tmp_4=$4
show=`tc_classid $tmp_3 $tmp_4`
if [ "$tmp_2" = down ];then
tmp_2=eth1
else
tmp_2=eth0
fi
tc class show dev $tmp_2 | grep ":${show}[ ]" | awk '{print "speed:" $11}'
echo "$tmp_1 $tmp_2 $tmp_3 $tmp_4"
echo "classid:$show"
}
#change
tc_change() {
local tmp_1=$1
local tmp_2=$2
local tmp_3=$3
local tmp_4=$4
local tmp_5=$5
show=`tc_classid $tmp_3 $tmp_4`
echo "$tmp_1 $tmp_2 $tmp_3 $tmp_4 $tmp_5"
echo $show
#
if [ "$tmp_2" = "dual" ];then
tc class change dev $down_dev parent 1:9999 classid 1:$show htb rate $vip_download ceil $vip_downlad burst 20kb
tc class change dev $up_dev parent 2:9999 classid 2:$show htb rate $vip_upload ceil $vip_upload burst 20kb
fi
if [ "$tmp_2" = "down" ];then
tc class change dev $down_dev parent 1:9999 classid 1:$show htb rate $tmp_5 ceil $tmp_5 burst 20kb
fi
if [ "$tmp_2" = "up" ];then
tc class change dev $up_dev parent 2:9999 classid 2:$show htb rate $tmp_5 ceil $tmp_5 burst 20kb
fi
}
#clear
tc_clear() {
tc qdisc del dev $down_dev root handle 1:
tc qdisc del dev $up_dev root handle 2:
}
#start
tc_start() {
#root handle|parent class
tc qdisc add dev $down_dev root handle 1: htb
tc class add dev $down_dev parent 1: classid 1:9999 htb rate 25Mbit
tc qdisc add dev $up_dev root handle 2: htb
tc class add dev $up_dev parent 2: classid 2:9999 htb rate 25Mbit
#class
for((i=1;i<=$vlan;i++));do
if [ "$i" -ge "10" -a "$i" -le "49" ];then
continue
fi
#echo $i
for((k=2;k<=ip;++k));do
((num++))
echo "\$classid is $num"
#echo "\$vlan is $vlan"
#echo "\$ip is 10.0.$i.$k"
#down-eth1
tc class add dev $down_dev parent 1:9999 classid 1:$num htb rate 800Kbit ceil 900Kbit burst 15k
tc qdisc add dev $down_dev parent 1:$num sfq quantum 1514b perturb 10
tc filter add dev $down_dev protocol ip parent 1: prio 1 u32 match ip dst "$ip_duan.$i.$k" flowid 1:$num
#up-eth0
tc class add dev $up_dev parent 2:9999 classid 2:$num htb rate 800Kbit ceil 1600Kbit burst 10k
tc qdisc add dev $up_dev parent 2:$num sfq quantum 1514b perturb 10
tc filter add dev $up_dev parent 2: protocol ip prio 10 handle $num fw classid 2:$num
#up-eth0-iptables_mark
iptables -t mangle -A PREROUTING -i $down_dev -s "$ip_duan.$i.$k" -j MARK --set-mark $num
done
done
}
#main
case "$1" in
start)
echo "start!"
tc_start
exit 0
;;
clear)
echo "clear!"
tc_clear
exit 0
;;
change)
echo "change!"
if [ $# -eq 1 -o $# -lt 5 ];then
echo $?
echo "Usage: $0 change arg1(type) arg2(vlan) arg3(ip_end) arg4(bandwidth)"
echo "Usage: $0 change down 1 100 800Kbit"
echo "Usage: $0 change up 1 199 800Kbit"
exit 1
else
tc_change $1 $2 $3 $4 $5
exit 0
fi
;;
show)
echo "show!"
if [ $# -eq 1 -o $# -lt 4 ];then
echo "Usage: $0 show arg1(type) arg2(vlan) arg3(ip_end)"
echo "Usage: $0 show down 1 100 || show up 1 199"
exit 1
else
tc_show $1 $2 $3 $4
fi
;;
*)
echo "Usage: $0 {start|clear|change|show}"
exit 1
esac
二 tc 与 htb
使用tc之前,请记住以下几条!
0 队列分为有类队列和无类队列
1 每个网卡只能有一种队列,比如cbq htb 或者pfifo_fast, tbf,sfq 前两个为有类队列,后三个为无类队列
2 队列里必须要有一个父类
3 队列里可以有多个子类,每个子类可以制定相应队列规则
4 无论是队列,还是class和filter都有ID,队列最大Classid 数量9999个超过则报错,这其中包含父类与子类
5 利用队列我们只能对对网卡发送的数据进行控制,这也是为什么要在两个网卡上做流量控制,外网卡(eth0)做上行控制,内网卡(eth1)做下行控制!
6 如果要根据ip地址控制内网带宽,则必须能够在瓶颈处控制,也就是数据包必须经过网关,而且要使用过滤器比如u32!
概念
TC为TrafficControl的缩写,我们可以通过配置TC HTB规则来实现流量控制.
TC规则涉及到队列(queue),分类器(class)和过滤器(filter)三个概念.
队列(queue)用来实现控制网络的收发速度.通过队列,linux可以将网络数据包缓存起来,然后根据用户的设置,在尽量不中断连接(如TCP)的前提下来平滑网络流量.需要注意的是,linux对接收队列的控制不够好,所以我们一般只用发送队列,即“控发不控收”。
分类器(class)用来表示控制策略.很显然,很多时候,我们很可能要对不同的IP实行不同的流量控制策略,这时候我们就得用不同的class来表示不同的控制策略了.
过滤器(filter)用来将用户划入到具体的控制策略中(即不同的class中).正如前述,我们要对A,B两个IP实行不同的控制策略(C,D),这时,我们可 用filter将A划入到控制策略C,将B划入到控制策略D,filter划分的标志位可用u32打标功能或IPtables的set-mark功能来实现。
应用场景
1 网关上控制内网带宽,上面的脚本其实就是以下几句tc命令为基础的!
eth0 外网卡
#创建根队列
tc qdisc add dev eth1 root handle 1: htb
#创建父类
tc class add dev eth1 parent 1: classid 1:9999 htb rate 30Mbit
#创建子类
tc class add dev eth1 parent 1:9999 classid 1:1 htb rate 512Kbit ceil 400Kbit
#创建过滤器
tc filter add dev eth1 protocol ip parent 1: prio 1 u32 match ip dst 10.0.5.82 flowid 1:1
#更改子类规则
tc class change dev eth1 parent 1:9999 classid 1346 htb rate 1200Kbit ceil 1200Kbit
eth1 内网卡
#与上边基本相同,不同处为dev eth0,fiter规则不同
tc qdisc add dev eth0 root handle 2: htb
tc class add dev eth0 parent 2: classid 2:9999 htb rate 30Mbit
tc class add dev eth0 parent 2:9999 classid 2:1 htb rate 512Kbit ceil 400Kbit
tc filter add dev eth0 parent 2: protocol ip prio 10 handle 2:1 fw classid 2:1
iptables -t mangle -A PREROUTING -i eth1 -s 10.0.5.82 -j MARK --set-mark $num
2 服务器上限制某个服务使用的带宽,比如http 80 端口
tc qdisc add dev eth1 root handle 1: htb default 30
tc class add dev eth1 parent 1: classid 1:1 htb rate 6mbit burst 15k
tc class add dev eth1 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
tc class add dev eth1 parent 1:1 classid 1:20 htb rate 1mbit ceil 6mbit burst 15k
tc class add dev eth1 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k
tc qdisc add dev eth1 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth1 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev eth1 parent 1:30 handle 30: sfq perturb 10
tc filter add dev eth1 protocol ip parent 1:0 prio 1 u32 match ip sport 80 0xffff flowid 1:10
tc filter add dev eth1 protocol ip parent 1:0 prio 1 u32 match ip sport 22 0xffff flowid 1:20
队列规定:根、句柄、父辈和兄弟
每块网卡都有一个出口“根队列规定”(root),缺省情况下是 pfifo_fast 队列
队列规定。每个队列规定都指定一个句柄(handle),以便以后的配置语句能够引用这个队列规定。
队列规定的句柄有两个部分:一个主号码和一个次号码。习惯上把根队列规定称为“1:”,等价于“1:0”。队列规定的次号码永远是 0。类的主号码必须与它们父辈的主号码一致。
(default 30)表示当某个ip流不满足任何已设定的filter规则时,将自动归入class 30中.
HTB(Hierarchical Token Bucket, 分层的令牌桶)
HTB 就象 CBQ 一样工作,但是并不靠计算闲置时间来整形。它是一个分类的令
牌桶过滤器。它只有很少的参数
SFQ(Stochastic Fairness Queueing,随机公平队列)是公平队列算法家族中的一个
简单实现。它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的
计算量却很少。
perturb
多少秒后重新配置一次散列算法。如果取消设置,散列算法将永远不会重新配置(不建议这样做);10 秒应该是一个合适的值
quantum
一个流至少要传输多少字节后才切换到下一个队列。却省设置为一个最大包的长度(MTU 的大小)。不要设置这个数值低于 MTU!
rate是指在带宽紧张的情况下的最大网络速度,当带宽空闲时,class可通过向其兄弟借用带宽而达到ceil大的网络速度,注意,借用表兄弟之间也可借用带框.prio用来指示借用带宽时的竞争力,prio越小,优先级越高,竞争力越强.
man tc
Usage: ... qdisc add ... htb [default N] [r2q N]
default minor id of class to which unclassified packets are sent {0}
r2q DRR quantums are computed as rate in Bps/r2q {10}
debug string of 16 numbers each 0-3 {0}
... class add ... htb rate R1 [burst B1] [mpu B] [overhead O]
[prio P] [slot S] [pslot PS]
[ceil R2] [cburst B2] [mtu MTU] [quantum Q]
rate rate allocated to this class (class can still borrow)
burst max bytes burst which can be accumulated during idle period {computed}
mpu minimum packet size used in rate computations
overhead per-packet size overhead used in rate computations
ceil definite upper class rate (no borrows) {rate}
cburst burst but for ceil {computed}
mtu max packet size we create rate map for {1600}
prio priority of leaf; lower are served first {0}
quantum how much bytes to serve from leaf at once {use r2q}
#update 20121008
脚本
本文出自 “dongnan” 博客,请务必保留此出处http://dngood.blog.51cto.com/446195/673728