Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3125537
  • 博文数量: 685
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5303
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-19 14:17
个人简介

文章分类

全部博文(685)

文章存档

2015年(116)

2014年(569)

分类: LINUX

2014-11-20 19:39:41

原文地址:http://dngood.blog.51cto.com/446195/673728

此文分为两部分

1 脚本与函数

2 tc 与 htb (cbq 版这里

一 脚本与

#创建函数

注意必须先定义好函数,后才能调用,所以通常将函数定义在shell脚本头部!!!

此函数定义了两个变量clasid_vlan与classid_ip 用于接受传递参数值

	
  1. #classid 
  2. tc_classid() { 
  3. classid_vlan=$1 
  4. classid_ip=$2 
  5. if [[ "$classid_vlan" -ge "1" && "$classid_vlan" -le "9" ]];then 
  6. echo $(((classid_vlan-1)*lie+(classid_ip-1))) 
  7. fi 
  8.  
  9. if [[ "$classid_vlan" -ge "10" && "$classid_vlan" -le "49" ]];then 
  10. echo "ip $classid_vlan error" 
  11. fi 
  12.  
  13. if [[ "$classid_vlan" -ge "50" && "$classid_vlan" -le "59" ]];then 
  14. echo $((($classid_vlan-41)*lie+(classid_ip-1))) 
  15. fi 


#调用函数

下面将在另一个函数tc_show()中调用了先前定义的tc_classid()函数

	
  1. #show 
  2. tc_show() { 
  3. local tmp_1=$1 
  4. local tmp_2=$2 
  5. local tmp_3=$3 
  6. local tmp_4=$4 
  7. show=`tc_classid $tmp_3 $tmp_4` 
  8. if ["$tmp_2"=down];then 
  9. tmp_2=eth1 
  10. else 
  11. tmp_2=eth0 
  12. fi 
  13. tc class show dev $tmp_2|grep ":${show}[]" | awk'{print "speed:" $11}' 
  14. echo"$tmp_1 $tmp_2 $tmp_3 $tmp_4" 
  15. 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 函数库 

../点符号符来使得脚本能以调用库文件的函数,同理我们也可以定义自己的函数库文件

#完整脚本

	
  1. #!/bin/bash 
  2. #tc htb  
  3. #20110927 by dongnan ver0.2 
  4.  
  5. #variables 
  6. vlan=59 
  7. ip=254 
  8. ip_duan=10.0 
  9. num=0 
  10. down_dev=eth1 
  11. up_dev=eth0 
  12. lie=253 
  13. vip_download=1600Kbit 
  14. vip_upload=1600Kbit 
  15. iptables=/sbin/iptables
  16. tc=/sbin/tc
  17.  
  18. #function 
  19.  
  20. #classid 
  21. tc_classid() { 
  22. classid_vlan=$1 
  23. classid_ip=$2 
  24. if [[ "$classid_vlan" -ge "1" && "$classid_vlan" -le "9" ]];then 
  25.    echo $(((classid_vlan-1)*lie+(classid_ip-1))) 
  26. fi 
  27.  
  28. if [[ "$classid_vlan" -ge "10" && "$classid_vlan" -le "49" ]];then 
  29.    echo "ip $classid_vlan error" 
  30. fi 
  31.  
  32. if [[ "$classid_vlan" -ge "50" && "$classid_vlan" -le "59" ]];then 
  33.    echo $((($classid_vlan-41)*lie+(classid_ip-1))) 
  34. fi 
  35.  
  36. #show 
  37. tc_show() { 
  38. local tmp_1=$1 
  39. local tmp_2=$2 
  40. local tmp_3=$3 
  41. local tmp_4=$4 
  42. show=`tc_classid $tmp_3 $tmp_4`   
  43. if [ "$tmp_2" = down ];then 
  44.     tmp_2=eth1 
  45. else 
  46.     tmp_2=eth0 
  47. fi 
  48. tc class show dev $tmp_2 | grep ":${show}[ ]" | awk '{print "speed:" $11}' 
  49. echo "$tmp_1 $tmp_2 $tmp_3 $tmp_4" 
  50. echo "classid:$show" 
  51.  
  52. #change 
  53. tc_change() { 
  54. local tmp_1=$1 
  55. local tmp_2=$2 
  56. local tmp_3=$3 
  57. local tmp_4=$4 
  58. local tmp_5=$5 
  59. show=`tc_classid $tmp_3 $tmp_4`   
  60. echo "$tmp_1 $tmp_2 $tmp_3 $tmp_4 $tmp_5" 
  61. echo $show 
  62. if [ "$tmp_2" = "dual" ];then 
  63.     tc class change dev $down_dev parent 1:9999 classid 1:$show htb rate $vip_download ceil $vip_downlad burst 20kb 
  64.     tc class change dev $up_dev parent 2:9999 classid 2:$show htb rate $vip_upload ceil $vip_upload burst 20kb 
  65. fi 
  66.  
  67. if [ "$tmp_2" = "down" ];then 
  68.     tc class change dev $down_dev parent 1:9999 classid 1:$show htb rate $tmp_5 ceil $tmp_5 burst 20kb 
  69. fi 
  70.  
  71. if [ "$tmp_2" = "up" ];then 
  72.     tc class change dev $up_dev parent 2:9999 classid 2:$show htb rate $tmp_5 ceil $tmp_5 burst 20kb 
  73. fi 
  74.  
  75.  
  76. #clear 
  77. tc_clear() { 
  78.     tc qdisc del dev $down_dev root handle 1: 
  79.     tc qdisc del dev $up_dev root handle 2: 
  80.  
  81. #start 
  82. tc_start() { 
  83. #root handle|parent class  
  84. tc qdisc add dev $down_dev root handle 1: htb 
  85. tc class add dev $down_dev parent 1: classid 1:9999 htb rate 25Mbit 
  86. tc qdisc add dev $up_dev root handle 2: htb 
  87. tc class add dev $up_dev parent 2: classid 2:9999 htb rate 25Mbit 
  88.  
  89. #class 
  90. for((i=1;i<=$vlan;i++));do 
  91.     if [ "$i" -ge "10" -a "$i" -le "49" ];then 
  92.         continue 
  93.     fi 
  94.     #echo $i 
  95.     for((k=2;k<=ip;++k));do 
  96.     ((num++)) 
  97.     echo "\$classid is $num" 
  98.     #echo "\$vlan is $vlan" 
  99.     #echo "\$ip is 10.0.$i.$k" 
  100.  
  101. #down-eth1 
  102. tc class add dev $down_dev parent 1:9999 classid 1:$num htb rate 800Kbit ceil 900Kbit burst 15k 
  103. tc qdisc add dev $down_dev parent 1:$num sfq quantum 1514b perturb 10 
  104. tc filter add dev $down_dev protocol ip parent 1: prio 1 u32 match ip dst "$ip_duan.$i.$k" flowid 1:$num 
  105. #up-eth0 
  106. tc class add dev $up_dev parent 2:9999 classid 2:$num htb rate 800Kbit ceil 1600Kbit burst 10k 
  107. tc qdisc add dev $up_dev parent 2:$num sfq quantum 1514b perturb 10 
  108. tc filter add dev $up_dev parent 2: protocol ip prio 10 handle $num fw classid 2:$num 
  109. #up-eth0-iptables_mark 
  110. iptables -t mangle -A PREROUTING -i $down_dev -s "$ip_duan.$i.$k" -j MARK --set-mark $num 
  111.  
  112.     done 
  113. done 
  114.  
  115. #main 
  116.  
  117. case "$1" in 
  118.    start) 
  119.        echo "start!" 
  120.        tc_start 
  121.        exit 0 
  122.        ;; 
  123.    clear) 
  124.        echo "clear!" 
  125.        tc_clear 
  126.        exit 0 
  127.        ;; 
  128.    change) 
  129.        echo "change!" 
  130.        if [ $# -eq 1 -o $# -lt 5 ];then 
  131.           echo $? 
  132.       echo "Usage: $0 change arg1(type) arg2(vlan) arg3(ip_end) arg4(bandwidth)" 
  133.           echo "Usage: $0 change down 1 100 800Kbit" 
  134.           echo "Usage: $0 change up 1 199 800Kbit" 
  135.           exit 1 
  136.       else 
  137.           tc_change $1 $2 $3 $4 $5 
  138.           exit 0 
  139.        fi 
  140.        ;; 
  141.    show) 
  142.       echo "show!" 
  143.       if [ $# -eq 1 -o $# -lt 4 ];then 
  144.       echo "Usage: $0 show arg1(type) arg2(vlan) arg3(ip_end)" 
  145.           echo "Usage: $0 show down 1 100 || show up 1 199" 
  146.           exit 1 
  147.       else 
  148.           tc_show $1 $2 $3 $4 
  149.       fi 
  150.       ;; 
  151.     *) 
  152.        echo "Usage: $0 {start|clear|change|show}" 
  153.        exit 1 
  154. esac 

 

二 tc 与 htb

使用tc之前,请记住以下几条!

0 队列分为有类队列和无类队列

1 每个网卡只能有一种队列,比如cbq htb 或者pfifo_fast, tbf,sfq 前两个为有类队列,后三个为无类队列

2 队列里必须要有一个父类

3 队列里可以有多个子类,每个子类可以制定相应队列规则

4 无论是队列,还是classfilter都有ID队列最大Classid 数量9999个超过则报错,这其中包含父类与子类

5 利用队列我们只能对对网卡发送的数据进行控制,这也是为什么要在两个网卡上做流量控制,外网卡(eth0)做上行控制,内网卡(eth1)做下行控制!

6 如果要根据ip地址控制内网带宽,则必须能够在瓶颈处控制,也就是数据包必须经过网关,而且要使用过滤器比如u32!

 

TCTrafficControl的缩写,我们可以通过配置TC HTB规则来实现流量控制.
TC规则涉及到队列(queue),分类器(class)和过滤器(filter)三个概念.

队列(queue)用来实现控制网络的收发速度.通过队列,linux可以将网络数据包缓存起来,然后根据用户的设置,在尽量不中断连接(TCP)的前提下来平滑网络流量.需要注意的是,linux对接收队列的控制不够好,所以我们一般只用发送队列,即控发不控收

分类器(class)用来表示控制策略.很显然,很多时候,我们很可能要对不同的IP实行不同的流量控制策略,这时候我们就得用不同的class来表示不同的控制策略了.

过滤器(filter)用来将用户划入到具体的控制策略中(即不同的class).正如前述,我们要对A,B两个IP实行不同的控制策略(C,D),这时,我们可 filterA划入到控制策略C,将B划入到控制策略Dfilter划分的标志位可用u32打标功能或IPtablesset-mark功能来实现。


应用场景

1 网关上控制内网带宽,上面的脚本其实就是以下几句tc命令为基础的!

eth0 外网卡

	
  1. #创建根队列

  2. tc qdisc add dev eth1 root handle 1: htb 

  3. #创建父类

  4. tc class add dev eth1 parent 1: classid 1:9999 htb rate 30Mbit

  5. #创建子类

  6. tc class add dev eth1 parent 1:9999 classid 1:1 htb rate 512Kbit ceil 400Kbit 

  7. #创建过滤器

  8. tc filter add dev eth1 protocol ip parent 1: prio 1 u32 match ip dst 10.0.5.82 flowid 1:1 

  9. #更改子类规则

  10. tc class change dev eth1 parent 1:9999 classid 1346 htb rate 1200Kbit ceil 1200Kbit 

eth1 内网卡

	
  1. #与上边基本相同,不同处为dev eth0,fiter规则不同

  2. tc qdisc add dev eth0 root handle 2: htb 
  3. tc class add dev eth0 parent 2: classid 2:9999 htb rate 30Mbit 
  4. tc class add dev eth0 parent 2:9999 classid 2:1 htb rate 512Kbit ceil 400Kbit 
  5. tc filter add dev eth0 parent 2: protocol ip prio 10 handle 2:1 fw classid 2:1
  6. iptables -t mangle -A PREROUTING -i eth1 -s 10.0.5.82 -j MARK --set-mark $num 

 

2 服务器上限制某个服务使用的带宽,比如http 80 端口

	
  1. tc qdisc add dev eth1 root handle 1: htb default 30 
  2. tc class add dev eth1 parent 1: classid 1:1 htb rate 6mbit burst 15k 
  3. tc class add dev eth1 parent 1:1 classid 1:10 htb rate 5mbit burst 15k 
  4. tc class add dev eth1 parent 1:1 classid 1:20 htb rate 1mbit ceil 6mbit burst 15k 
  5. tc class add dev eth1 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k 
  6. tc qdisc add dev eth1 parent 1:10 handle 10: sfq perturb 10 
  7. tc qdisc add dev eth1 parent 1:20 handle 20: sfq perturb 10 
  8. tc qdisc add dev eth1 parent 1:30 handle 30: sfq perturb 10 
  9.  
  10. tc filter add dev eth1 protocol ip parent 1:0 prio 1 u32 match ip sport 80 0xffff flowid 1:10 
  11. 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

	
  1. Usage: ... qdisc add ... htb [default N] [r2q N] 
  2.  default  minor id of class to which unclassified packets are sent {0}  
  3. r2q      DRR quantums are computed as rate in Bps/r2q {10} 
  4. debug    string of 16 numbers each 0-3 {0}  
  5.  
  6. ... class add ... htb rate R1 [burst B1] [mpu B] [overhead O] 
  7. [prio P] [slot S] [pslot PS]  
  8. [ceil R2] [cburst B2] [mtu MTU] [quantum Q] 
  9. rate     rate allocated to this class (class can still borrow) 
  10.  burst    max bytes burst which can be accumulated during idle period {computed} 
  11. mpu      minimum packet size used in rate computations 
  12. overhead per-packet size overhead used in rate computations 
  13. ceil     definite upper class rate (no borrows) {rate} 
  14.  cburst   burst but for ceil {computed} 
  15. mtu      max packet size we create rate map for {1600} 
  16. prio     priority of leaf; lower are served first {0}  
  17. quantum  how much bytes to serve from leaf at once {use r2q} 

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