Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9170388
  • 博文数量: 1669
  • 博客积分: 16831
  • 博客等级: 上将
  • 技术积分: 12594
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-25 07:23
个人简介

柔中带刚,刚中带柔,淫荡中富含柔和,刚猛中荡漾风骚,无坚不摧,无孔不入!

文章分类

全部博文(1669)

文章存档

2023年(4)

2022年(1)

2021年(10)

2020年(24)

2019年(4)

2018年(19)

2017年(66)

2016年(60)

2015年(49)

2014年(201)

2013年(221)

2012年(638)

2011年(372)

分类: 系统运维

2013-11-27 14:43:34

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


阅读(547) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~