全部博文(685)
分类: LINUX
2014-11-20 19:39:41
此文分为两部分
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}