Chinaunix首页 | 论坛 | 博客
  • 博客访问: 790057
  • 博文数量: 264
  • 博客积分: 592
  • 博客等级: 中士
  • 技术积分: 1574
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-24 22:02
文章分类

全部博文(264)

文章存档

2019年(2)

2018年(1)

2017年(1)

2016年(4)

2015年(14)

2014年(57)

2013年(88)

2012年(97)

分类: LINUX

2013-05-17 22:36:59

目录
第1章 贡献 1
第2章 简介 2
2.1. 除外责任与许可 2
2.2. 预备知识 2
2.3. LINUX能为你做什么 3
2.4. 内务声明 3
2.5. 访问,CVS和提交更新 4
2.6. 邮件列表 4
2.7. 本文档的布局 4
第3章 介绍 IPROUTE2 6
3.1 为什么使用 IPROUTE2 6
3.2 IPROUTE2 概览 6
3.3 先决条件 6
3.4 浏览你的当前配置 7
3.4.1. 让ip显示我们的链路 7
3.4.2. 让ip显示我们的 IP 地址 7
3.4.3. 让ip显示路由 8
3.5. ARP 9
第4章 规则——路由策略数据库 11
4.1. 简单的源策略路由 11
4.2. 多重上连ISP的路由 12
4.2.1. 流量分割 13
4.2.2. 负载均衡 14
第5章 GRE 和其他隧道 15
5.1. 关于隧道的几点注释 15
5.2. IP-IN-IP 隧道 15
5.3. GRE 隧道 16
4
5.3.1. IPv4隧道 16
5.3.2. IPv6隧道 18
5.4. 用户级隧道 18
第6章 用CISCO和6BONE实现IPV6隧道 19
6.1. IPV6隧道 19
第7章 IPSEC:INTERNET上安全的IP 22
7.1. 从手动密钥管理开始 22
7.2. 自动密钥管理 25
7.2.1. 理论 26
7.2.2. 举例 26
7.2.3. 使用X.509证书进行自动密钥管理 29
7.3. IPSEC隧道 32
7.4. 其它IPSEC软件 33
7.5. IPSEC与其它系统的互操作 33
7.5.1. Windows 33
第8章 多播路由 34
第9章 带宽管理的队列规定 36
9.1. 解释队列和队列规定 36
9.2. 简单的无类队列规定 37
9.2.1. pfifo_fast 37
9.2.2. 令牌桶过滤器(TBF) 39
9.2.3. 随机公平队列(SFQ) 41
9.3. 关于什么时候用哪种队列的建议 42
9.4. 术语 43
9.5. 分类的队列规定 45
9.5.1. 分类的队列规定及其类中的数据流向 45
9.5.2. 队列规定家族:根,句柄,兄弟和父辈 45
9.5.3. PRIO队列规定 46
9.5.4. 著名的CBQ队列规定 48
9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶) 54
5
9.6. 使用过滤器对数据包进行分类 55
9.6.1. 过滤器的一些简单范例 56
9.6.2. 常用到的过滤命令一览 57
9.7. IMQ(INTERMEDIATE QUEUEING DEVICE,中介队列设备) 58
9.7.1. 配置范例 58
第10章 多网卡的负载均衡 60
10.1. 告诫 61
10.2. 其它可能性 61
第11章 NETFILTER和IPROUTE——给数据包作标记 62
第12章 对包进行分类的高级过滤器 64
12.1. U32分类器 65
12.1.1. U32选择器 65
12.1.2. 普通选择器 66
12.1.3. 特殊选择器 67
12.2. 路由分类器 67
12.3. 管制分类器 68
12.3.1. 管制的方式 68
12.3.2. 越限动作 69
12.3.3. 范例 70
12.4. 当过滤器很多时如何使用散列表 70
第13章 内核网络参数 72
13.1. 反向路径过滤 72
13.2. 深层设置 73
13.2.1. ipv4一般设置 73
13.2.2. 网卡的分别设置 78
13.2.3. 邻居策略 79
13.2.4. 路由设置 80
第14章 不经常使用的高级队列规定 82
14.1. BFIFO/PFIFO 82
14.1.1. 参数与使用 82
6
14.2. CLARK-SHENKER-ZHANG算法 (CSZ) 82
14.3. DSMARK 83
14.3.1. 介绍 83
14.3.2. Dsmark与什么相关? 83
14.3.3. Differentiated Services指导 84
14.3.4. 使用Dsmark 84
14.3.5. SCH_DSMARK如何工作 84
14.3.6. TC_INDEX过滤器 85
14.4. 入口队列规定 87
14.4.1. 参数与使用 87
14.5. RED(RANDOM EARLY DETECTION,随机提前检测) 87
14.6. GRED(GENERIC RANDOM EARLY DETECTION,一般的随机提前检测) 88
14.7. VC/ATM模拟 89
14.8. WRR(WEIGHTED ROUND ROBIN,加权轮转) 89
第15章 方便菜谱 90
15.1. 用不同的SLA运行多个网站. 90
15.2. 防护SYN洪水攻击 90
15.3. 为防止DDOS而对ICMP限速 91
15.4. 为交互流量设置优先权 92
15.5. 使用NETFILTER,IPROUTE2和SQUID实现WEB透明代理 93
15.5.1. 实现之后的数据流图 96
15.6. 与PMTU发现有关的"基于路由的MTU设置" 96
15.6.1. 解决方案 97
15.7. 与PMTU发现有关的MSS箝位(给ADSL,CABLE,PPPOE和PPTP用户) 98
15.8. 终极的流量控制:低延迟,高速上/下载 98
15.8.1. 为什么缺省设置不让人满意 99
15.8.2. 实际的脚本(CBQ) 100
15.8.3. 实际的脚本(HTB) 102
15.9. 为单个主机或子网限速 103
15.10. 一个完全NAT和QOS的范例 104
7
15.10.1. 开始优化那不多的带宽 104
15.10.2. 对数据包分类 106
15.10.3. 改进设置 107
15.10.4. 让上面的设置开机时自动执行 108
第16章 构建网桥以及用ARP代理构建伪网桥 109
16.1. 桥接与IPTABLES的关系 109
16.2. 桥接与流量整形 109
16.3. 用ARP代理实现伪网桥 109
16.3.1. ARP和ARP代理 110
16.3.2. 实现 110
第17章 动态路由——OSPF和BGP 112
17.1. 用ZEBRA设置OSPF 112
17.1.1. 必要条件 113
17.1.2. 配置Zebra 113
17.1.3. 运行Zebra 115
第18章 其它可能性 117
第19章 进一步学习 119
第20章 鸣谢 120
8
第1章 贡献
本文档的成形得益于很多人的贡献,我希望能够回报他们.列出其中几个:
Rusty Russell
Alexey N. Kuznetsov
来自Google的一些好心人
Casema Internet的工作人员
1
第2章 简介
欢迎,亲爱的读者.
希望这篇文档能对你更好地理解Linxs2.2/2.4的路由有所帮助和启发.不被大
多数使用者所知道的是,你所使用工具,其实能够完成相当规模工作.比如route
和ifconfig,实际上暗中调用了非常强大的iproute 2的底层基本功能.
我希望这个HOWTO能够象Rusty Russell的作品那样通俗易懂.
你可以随时给HOWTO工作组发电子邮件来找到我们.但是如果您的问题并不
直接与这个HOWTO文档相关,请首先考虑发给邮件列表(参考相关章节).我们
可不是免费的帮助平台,但我们经常会在邮件列表上回答问题.
在钻研这个HOWTO之前,如果您想做的只是一点简单的流量整形,不妨直接
去看看其它可能性这一章里面的CBQ.init.
2.1. 除外责任与许可
这个文档依着对公众有利用价值的目的而发布,但不提供任何担保,即使是在经
销或者使用在特定场合时的潜在担保.
简单地说,如果您的STM-64骨干网瘫痪,并向您尊敬的客户们散布黄色图片,
对不起,那绝对不关我的事.
Copyright (c) 2002 所有:bert hubert,Gregory Maxwell,Martijn van Oosterhout,
Remco van Mook,Paul B. Schroeder等等.这份材料可以在遵从Open Publication
License, v1.0(或更新版)各项条款的前提下发布.Open Publication License的最新
版可以在 得到.
请随意复制并发布(出售或者赠送)本文档,格式不限.只是请求将纠正和/或注解
转发给文档的维护者.
还希望如果你出版本HOWTO的硬拷贝,请给作者们发一份以备复习之用.
2.2. 预备知识
就像标题所暗示的,这是一个"高级"HOWTO.虽然它不是终极的航天科技,
但还是要求一定的基础知识.
这里是一些可能对你有帮助的参考文献:
2
Rusty Russell的networking-concepts-HOWTO
非常精彩的介绍,解释了什么是网络以及一个网络如何与其它网络互联.
Linux Networking-HOWTO (以前叫做Net-3 HOWTO)
好东西,虽然非常冗长.它讲授的内容就是你连接到Internet所需的的配置内
容.应该在/usr/doc/HOWTO/NET3-4-HOWTO.txt中,也可以在线阅读.
2.3. Linux能为你做什么
一个小列表:
管制某台计算机的带宽
管制通向某台计算机的带宽
帮助你公平地共享带宽
保护你的网络不受DoS攻击
保护Internet不受到你的客户的攻击
把多台服务器虚拟成一台,进行负载均衡或者提高可用性
限制对你的计算机的访问
限制你的用户访问某些主机
基于用户账号(没错!),MAC地址,源IP地址,端口,服务类型,时间
或者内容等条件进行路由.
现在,很多人都没有用到这些高级功能.这有很多原因.比如提供的文档过于冗
长而且不容易上手,而且流量控制甚至根本就没有归档.
2.4. 内务声明
关于这个文档有些事情要指出.当我写完这个文档的绝大部分的时候,我真的不
希望它永远就是那个样子.我是一个坚信开放源代码的人,所以我希望你能够给
我发回反馈,更新,补丁等等.所以你应该尽可以告知我你的手稿或者指出一些
哪怕是无关紧要的错误,不必犹豫.如果我的英语有些晦涩,请原谅那不是我的
母语,尽可以给我建议.
如果你认为自己更有资格维护某个章节,或者认为自己可以写作并维护一个新的
章节,请您一定不要客气.这个HOWTO的SGML可以通过CVS得到,我估计
肯定有很多人还在为它出力.
作为请求援助,你会在文档中发现很多"求助"的字样.我们永远欢迎您的补丁!
无论您在哪里发现"求助",都应该明白您正在踏入一个未知的领域.这并不是
说在别的地方就没有错误,但您应该倍加小心.如果您确认了某些事情,请您一
3
定通知我们,以便我们能够把"求助"的标记去掉.
关于这个HOWTO,I will take some liberties along the road. For example, I postulate
a 10Mbit Internet connection, while I know full well that those are not very common.
2.5. 访问,CVS和提交更新
本HOWTO的规范位置在这里.
我们现在向全球开放了匿名CVS访问.从各个角度来说这都是一件好事.你可
以轻松地升级到本HOWTO的最新版本,而且提交补丁也不再成为问题.
另外的好处是,这可以让作者在源码上独立地继续工作.
$ export CVSROOT=:pserver:anon@outpost.ds9a.nl:/var/cvsroot
$ cvs login
CVS password: [enter 'cvs' (without 's)]
$ cvs co 2.4routing
cvs server: Updating 2.4routing
U 2.4routing/lartc.db
如果您做了修改并希望投稿,运行:
cvs -z3 diff -uBb
然后把输出用电子邮件发给,我们就可以很轻松地把它集成进去
了.谢谢!请确认你修改的是.db文件,其它文件都是通过它生成的.
提供了一个Makefile帮助您生成postscript,dvi,pdf,html和纯文本格式的文件.
你可能需要安装docbook,docboot-utils,ghostscript和tetex等等支持软件才能生
成各种格式的文本.
注意,不要更改2.4routing.sgml!那里面有旧版本的HOWTO.正确的文件是
lartc.db.
2.6. 邮件列表
作者已经开始收到关于这个HOWTO越来越多的邮件了.为了把大家的兴趣条
理化,已经决定启动一个邮件列表,让大家在那里互相探讨有关高级路由和流量
控制的话题.你可以在这里进行订阅.
需要指出的是,作者们对于列表中没有问及的问题不可能及时回答.我们愿意让
列表的归档成为一个知识库.如果你有问题,请搜索归档,然后在post到邮件
列表里.
2.7. 本文档的布局
我们几乎马上就要做一些有趣的实验,也就意味着最开始部分的基本概念解释并
不完整或者不完善,请您不必管它,后面会一点点说清楚.
4
路由和包过滤是完全不同的概念.关于过滤的问题,Rusty的文档说得很清楚,
你可以在这里找到:
Rusty出色的不可靠指南
我们则将致力于netfilter与iproute2相结合后能做什么.
5
第3章 介绍 iproute2
3.1 为什么使用 iproute2
现在,绝大多数 Linux 发行版和绝大多数 UNIX都使用古老的arp, ifconfig和
route命令.虽然这些工具能够工作,但它们在Linux2.2和更高版本的内核上显
得有一些落伍.比如,现在GRE隧道已经成为了路由的一个主要概念,但却不
能通过上述工具来配置.
使用了iproute2,隧道的配置与其他部分完全集成了.
2.2 和更高版本的Linux 内核包含了一个经过彻底重新设计的网络子系统.这些
新的代码让Linux在操作系统的竞争中取得了功能和性能上的优势.实际上,
Linux新的路由,过滤和分类代码,从功能和性能上都不弱于现有的那些专业的
路由器,防火墙和流量整形产品.
随着新的网络概念的提出,人们在现有操作系统的现有体系上修修补补来实现他
们.这种固执的行为导致了网络代码中充斥着怪异的行为,这有点像人类的语言.
过去,Linux模仿了SunOS的许多处理方式,并不理想.
这个新的体系则有可能比以往任何一个版本的Linux都更善于清晰地进行功能
表达.
3.2 iproute2 概览
Linux有一个成熟的带宽供给系统,称为Traffic Control(流量控制).这个系统
支持各种方式进行分类,排序,共享和限制出入流量.
我们将从 iproute2 各种可能性的一个简要概览开始.
3.3 先决条件
你应该确认已经安装了用户级配置工具.这个包的名字在RedHat和Debian中都
叫作"iproute",也可以在这个地方找到:
ftp://ftp.inr.ac.ru/ip-routing/iproute2-2.2.4-now-ss .tar.gz
你也可以试试在这里找找最新版本.
iproute 的某些部分需要你打开一些特定的内核选项.应该指出的是,RedHat6.2
及其以前的所有发行版中所带的缺省内核都不带有流量控制所需要的绝大多数
功能.
6
而RedHat 7.2在缺省情况下能满足所有要求.
另外,确认一下你的内核支持netlink ,Iproute2需要它.
3.4 浏览你的当前配置
这听上去确实让人惊喜:iproute2已经配置好了!当前的ifconfig和route命令已
经正在使用新的系统调用,但通常使用了缺省参数(真无聊).
新的工具ip成为中心,我们会让它来显示我们的网卡配置.
3.4.1. 让ip显示我们的链路
[ahu@home ahu]$ ip link list
1: lo: mtu 3924 qdisc noqueue
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: dummy: mtu 1500 qdisc noop
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
3: eth0: mtu 1400 qdisc pfifo_fast qlen 100
link/ether 48:54:e8:2a:47:16 brd ff:ff:ff:ff:ff:ff
4: eth1: mtu 1500 qdisc pfifo_fast qlen 100
link/ether 00:e0:4c:39:24:78 brd ff:ff:ff:ff:ff:ff
3764: ppp0: mtu 1492 qdisc pfifo_fast qlen 10
link/ppp
你的结果可能有所区别,但上述显示了我家里NAT路由器的情况.我将只解释
输出中并非全部直接相关的部分.因为并不是所有部分都与我们的话题有关,所
以我只会解释输出的一部分.
我们首先看到了 loopback 接口. While your computer may function somewhat
without one, I'd advise against it. MTU (最大传输单元)尺寸为 3924 字节,并且不
应该参与队列.这是因为 loopback 接口完全是内核想象出来的,并不存在的接
口.
现在我们跳过这个无关的接口,它应该并不实际存在于你的机器上.然后就是两
个物理网络接口,一个接在我的 cable modem 上,另一个接到我家里的以太网
端上.再下面,我们看见了一个 ppp0 接口.
应该指出,我们没有看到 IP 地址.iproute 切断了"链路"和"IP 地址"两个
概念的直接联系.当使用 IP 别名的时候,IP地址的概念显得更加不相关了.
尽管如此,还是显示出了标识以太网卡硬件的 MAC 地址.
3.4.2. 让ip显示我们的 IP 地址
[ahu@home ahu]$ ip address show
1: lo: mtu 3924 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: dummy: mtu 1500 qdisc noop
7
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
3: eth0: mtu 1400 qdisc pfifo_fast qlen 100
link/ether 48:54:e8:2a:47:16 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
4: eth1: mtu 1500 qdisc pfifo_fast qlen 100
link/ether 00:e0:4c:39:24:78 brd ff:ff:ff:ff:ff:ff
3764: ppp0: mtu 1492 qdisc pfifo_fast qlen 10
link/ppp
inet 212.64.94.251 peer 212.64.94.1/32 scope global ppp0
这里包含了更多信息.显示了我们所有的地址,以及这些地址属于哪些网卡.
"inet"表示Internet (IPv4).还有很多其它的地址类型,但现在还没有涉及到.
让我们先就近看看eth0.上面说它与IP地址10.0.0.1/8相关联.这是什么意思呢?
"/8"表示IP地址表示网络地址的位数.因为一共是32个bit,所以我们的这个
网络有了24 bit的主机空间. 10.0.0.1 的开始8bit是10.0.0.0,也就是我们的网络
地址,我们的子网掩码是255.0.0.0.
其它的bit直接连接在这个网卡上,所以10.250.3.13可以直接通过eth0联络到,
就象10.0.0.1一样.
对于ppp0,仍是相同的概念,虽然数字看上去有所不同.它的地址是
212.64.94.251,不带子网掩码.这意味着这是一个点到点的连接,而且除了
212.64.94.251之外的地址是对端的.当然,还有很多信息.它还告诉我们这个链
路的另一端只有一个地址:212.64.94.1./32意思是说没有表示网络的bit.
掌握这些概念是绝对重要的.如果有问题,不妨先参考以下这个HOWTO文件
开头曾经提到的那些文档.
你应该注意到了"qdisc",它是基于对列规范的一个概念.它在后面会变得很重
要.
3.4.3. 让ip显示路由
好的,现在我们已经知道如何找到10.x.y.z了,然后我们就可以到达212.64.94.1.
但这还不够,我们还得说明如何找到全世界.可以通过我们的ppp连接找到
Internet,212.64.94.1愿意把我们的数据包发给全世界,并把回应的数据包传回给
我们.
[ahu@home ahu]$ ip route show
212.64.94.1 dev ppp0 proto kernel scope link src 212.64.94.251
10.0.0.0/8 dev eth0 proto kernel scope link src 10.0.0.1
127.0.0.0/8 dev lo scope link
default via 212.64.94.1 dev ppp0
字面的意思相当清楚.前4行的输出明确地说明了ip address show的意思,最
后一行说明了世界的其它部分可以通过我们的缺省网关212.64.94.1找到.我们
通过"via"这个词断定这是一个网关,我们要把数据包交给它.这就是我们要
留心的问题
下面列出以前route 命令的输出作为参考:
[ahu@home ahu]$ route -n
8
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use
Iface
212.64.94.1 0.0.0.0 255.255.255.255 UH 0 0 0 ppp0
10.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 eth0
127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
0.0.0.0 212.64.94.1 0.0.0.0 UG 0 0 0 ppp0
3.5. ARP
ARP 是由 RFC 826 所描述的"地址解析协议".ARP是网络上的计算机在居域
网中用来解析另一台机器的硬件地址/位置的时候使用的.互联网上的机器一般
都是通过机器名解析成IP地址来互相找到的.这就能够解决foo.com网络能够
与bar.net网络通讯.但是,仅仅依靠IP地址,却无法得到一台计算机在一个网
络中的物理位置.这时候就需要ARP.
让我们举一个非常简单的例子.假定我有一个网络,里面有几台机器.其中的两
台在我的子网上,一台叫foo,IP地址是10.0.0.1,另一台叫bar,IP地址是10.0.0.2.
现在,foo想ping一下bar看看是不是正常,但是呢,foo只知道bar的IP地址,
却并不知道bar的硬件(MAC)地址.所以foo在ping bar之前就会先发出ARP询
问.这个ARP询问就像在喊:"Bar(10.0.0.2)!你在哪里(你的MAC地址是多少)?!"
结果这个广播域中的每台机器都能听到foo的喊话,但是只有bar(10.0.0.2)会回
应.Bar会直接给foo发送一个ARP回应,告诉它"Foo (10.0.0.1),我的Mac地
址是00:60:94:E9:08:12".经过这种简单的交谈,机器就能够在局域网中定位它
要通话的对象.Foo会一直使用这个结果,直到它的ARP缓冲忘掉这个结果(在
Unix系统上通常是15分钟之后).
现在我们来看一看具体的工作过程.你可以这样察看你的ARP表(缓冲):
[root@espa041 /home/src/iputils]# ip neigh show
9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud reachable
你可以看到,我的机器 espa041 (9.3.76.41) 知道如何找到 espa042 (9.3.76.42) 和
espagate (9.3.76.1).现在让我们往缓冲中添加另一台机器.
[root@espa041 /home/paulsch/.gnome-desktop]# ping -c 1 espa043
PING espa043.austin.ibm.com (9.3.76.43) from 9.3.76.41 : 56(84) bytes of data.
64 bytes from 9.3.76.43: icmp_seq=0 ttl=255 time=0.9 ms
--- espa043.austin.ibm.com ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.9/0.9/0.9 ms
[root@espa041 /home/src/iputils]# ip neigh show
9.3.76.43 dev eth0 lladdr 00:06:29:21:80:20 nud reachable
9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud reachable
由于espa041试图联络espa043,espa043的硬件地址已经添加到ARP缓冲里了.
所以直到espa043的记录失效以前(也就是两个机器间长时间没有通讯),espa041
知道如何找到espa043,也就不必频繁地进行ARP询问了.
9
现在让我们来删除 espa043 的ARP缓冲:
[root@espa041 /home/src/iputils]# ip neigh delete 9.3.76.43 dev eth0
[root@espa041 /home/src/iputils]# ip neigh show
9.3.76.43 dev eth0 nud failed
9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud stale
现在espa041 已经忘记了espa043 的MAC地址,如果下次它要与espa043 通讯,
需要再次发送 ARP询问.你在espagate (9.3.76.1) 上也会发现以上输出已经变成
了"stale"状态.这意味着MAC地址仍然是在册,但是接下来第一次通讯的时候
需要确认一下.
10
第4章 规则——路由策略数据库
如果你有一个大规模的路由器,你可能不得不同时满足不同用户对于路由的不同
需求.路由策略数据库可以帮助你通过多路由表技术来实现.
如果你想使用这个特性,请确认你的内核配置中带有 "IP: advanced router" 和
"IP: policy routing" 两项.
当内核需要做出路由选择时,它会找出应该参考哪一张路由表.除了 "ip" 命令
之外,以前的 "route" 命令也能修改 main 和 local 表.
缺省规则:
[ahu@home ahu]$ ip rule list
0:
f
rom all lookup local
32766:
f
rom all lookup main
32767:
f
rom all lookup default
上面列出了规则的优先顺序.我们看到,所有的规则都应用到了所有的包上
("from all").我们前面已经看到了 "main" 表,就是"ip route ls"命令的输出,
但是"local"和"default"是初次见到.
如果我们想做点有趣的事情,就可以生成一些指向不同路由表的规则,取代系统
中的路由规则.
对于内核如何处理一个IP包匹配多个规则的精确意义,请参见Alexey关于
ip-cref文档.
4.1. 简单的源策略路由
让我们再来一个真实的例子.我有两个Cable Modem,连接到了一个 Linux的
NAT ("伪装") 路由器上.这里的室友们向我付费使用 Internet.假如我其中的
一个室友因为只想访问 hotmail 而希望少付一些钱.对我来说这没有问题, 他们
肯定只能使用那个比较次的 Cable Modem.
那个比较快的cable modem 的IP地址是 212.64.94.251, PPP 链路,对端IP是
212.64.94.1.而那个比较慢的cable modem 的IP 地址是212.64.78.148,对端是
195.96.98.253.
local 表:
[ahu@home ahu]$ ip route list table local
11
/etc/iproute2/rt_tables
# ip rule add from 10.0.0.10 table John
# ip rule ls
0:
f
rom all lookup local
32765:
f
rom 10.0.0.10 lookup John
32766:
f
rom all lookup main
32767:
f
rom all lookup default
现在,剩下的事情就是为 John 的路由表创建路由项了.别忘了刷新路由缓存:
# ip route add default via 195.96.98.253 dev ppp2 table John
# ip route flush cache
这样就做好了.至于如何在 ip-up 阶段实现就留给读者自己去研究吧.
4.2. 多重上连ISP的路由
下图是很常见的配置,同一个局域网(甚至是同一台计算机)通过两个ISP连接
到互联网上.
________
+------------+ /
| | |
+-------------+ ISP 1 +-------
__ | | | /
12
___/ \_ +------+-------+ +------------+ |
_/ \__ | if1 | /
/ \ | | |
| 局域网 -----+ Linux 路由器 | | 国际互联网
\_ __/ | | |
\__ __/ | if2 | \
\___/ +------+-------+ +------------+ |
| | | \
+-------------+ ISP 2 +-------
| | |
+------------+ \________
这种情况下通常会出现两个问题.
4.2.1. 流量分割
首先是如何保证:回应来自某一个ISP的数据包时,仍然使用相同的ISP.
让我们先定义一些符号. 令第一块网卡(上图的if1)的名字叫 $IF1,而第二块网
卡叫做 $IF2 .然后设置 $IF1 的IP地址为 $IP1,$IF2 的IP地址为 $IP2.
并且,令ISP1 的网关地址为 $P1,ISP2 的网关地址为 $P2.最后,令$P1的
网络地址为 $P1_NET ,令$P2的网络地址为 $P2_NET.
额外创建两个路由表, T1 和 T2. 加入到 /etc/iproute2/rt_tables 中.然后如下
设置两个路由表中的路由:
ip route add $P1_NET dev $IF1 src $IP1 table T1
ip route add default via $P1 table T1
ip route add $P2_NET dev $IF2 src $IP2 table T2
ip route add default via $P2 table T2
没什么大不了的,不过是建立了通向该网关的一条路由,并使之成为默认网关,
分别负责一个单独的上行流,并且为这两个ISP都作这样的配置.要指出的是,
那条网络路由是必要条件,因为它能够让我们找到那个子网内的主机,也包括上
述那台网关.
下一步,我们设置"main"路由表.把包通过网卡直接路由到与网卡相连的局域
网上不失为一个好办法.要注意"src" 参数,他们能够保证选择正确的出口IP
地址.
ip route add $P1_NET dev $IF1 src $IP1
ip route add $P2_NET dev $IF2 src $IP2
然后,设置你的缺省路由:
ip route add default via $P1
接着,设置路由规则.这实际上在选择用什么路由表进行路由.你需要确认当你
从一个给定接口路由出数据包时,是否已经有了相应的源地址:你需要保证的就
是如果你已经有了相应的源地址,就应该把数据包从相应的网卡路由出去:
ip rule add from $IP1 table T1
ip rule add from $IP2 table T2
13
以上命令保证了所有的回应数据都会从他们来的那块网卡原路返回.
现在,完成了非常基本的配置.这将对于所有运行在路由器上所有的进程起作用,
实现IP伪装以后,对本地局域网也将起作用.如果不进行伪装,那么你要么拥
有两个ISP的地址空间,要么你想对两个ISP中的一个进行伪装.无论哪种情况,
你都要添加规则,基于发包的主机在局域网内的IP地址,选择从哪个ISP路由
出去.
4.2.2. 负载均衡
第二个问题是如何对于通过两个ISP流出的数据进行负载均衡.如果你已经成功
地实现了流量分割,这件事并不难.
与选择两个ISP中的一个作为缺省路由不同,这次是设置缺省路由为多路路由.
在缺省内核中,这会均衡两个ISP的路由.象下面这样做(基于前面的流量分割
实验):
ip route add default scope global nexthop via $P1 dev $IF1 weight 1 \
nexthop via $P2 dev $IF2 weight 1
这样就可以均衡两个ISP的路由.通过调整"weight"参数我们可以指定其中一
个ISP的优先权高于另一个.
应该指出,由于均衡是基于路由进行的,而路由是经过缓冲的,所以这样的均衡
并不是100%精确.也就是说,对于一个经常访问的站点,总是会使用同一个ISP.
进而,如果你对此不满意,你可能需要参考以下Julian Anastasov的内核补丁:
~julian/#routes
Julian的路由补丁会弥补上述缺陷.
14
第5章 GRE 和其他隧道
Linux有3种隧道.它们是: IP-in-IP 隧道, GRE 隧道和非内核隧道(如PPTP).
5.1. 关于隧道的几点注释
隧道可以用于实现很多非常不一般而有趣的功能.但如果你的配置有问题,却也
会发生可怕的错误.除非你确切地知道你在做什么,否则不要把缺省路由指向一
个隧道设备.而且,隧道会增加协议开销,因为它需要一个额外的IP包头.一
般应该是每个包增加20个字节,所以如果一个网络的MTU是1500字节的话,
使用隧道技术后,实际的IP包长度最长只能有1480字节了.这倒不是什么原则
性的问题,但如果你想使用隧道技术构建一个比较大规模的网络的话,最好仔细
研究一下关于IP包的分片和汇聚的知识.哦,还有,挖一个隧道最好的方法当
然是同时从两头挖.
5.2. IP-in-IP 隧道
这种隧道在Linux上已经实现很长一段时间了.需要两个内核模块:ipip.o 和
new_tunnel.o.
比如说你有3个网络:内部网A和B,中间网C(比如说:Internet).A网络的情
况:
网络地址
1
0.0.1.0
子网掩码
2
55.255.255.0
路由器
1
0.0.1.1
路由器在C网络上的地址是172.16.17.18.
B网络的情况:
网络地址
1
0.0.2.0
子网掩码
2
55.255.255.0
路由器
1
0.0.2.1
15
路由器在C网络上的IP地址是 172.19.20.21.
已知C网络已经连通,我们假定它会将所有的数据包从A传到B,反之亦然.
而且你可以随便使用Internet.
这就是你要做的:
首先,确认模块是否加载:
insmod ipip.o
insmod new_tunnel.o
然后,在A网络的路由器上输入:
ifconfig tunl0 10.0.1.1 pointopoint 172.19.20.21
route add -net 10.0.2.0 netmask 255.255.255.0 dev tunl0
并且在B网络的路由器上输入:
ifconfig tunl0 10.0.2.1 pointopoint 172.16.17.18
route add -net 10.0.1.0 netmask 255.255.255.0 dev tunl0
如果你想中止隧道,输入:
ifconfig tunl0 down
简单之极!但是你不能通过IP-in-IP隧道转发广播或者IPv6数据包.你只是连接
了两个一般情况下无法直接通讯的IPv4网络而已.至于兼容性,这部分代码已
经有很长一段历史了,它的兼容性可以上溯到1.3版的内核.据我所知,Linux
的IP-in-IP 隧道不能与其他操作系统或路由器互相通讯.它很简单,也很有效.
需要它的时候尽管使用,否则就使用GRE.
5.3. GRE 隧道
GRE是最初由CISCO开发出来的隧道协议,能够做一些IP-in-IP隧道做不到的
事情.比如,你可以使用GRE隧道传输多播数据包和IPv6数据包.在Linux下,
你需要ip_gre.o模块.
5.3.1. IPv4隧道
让我们先来做一做IPv4隧道:
比如说你有3个网络:内部网A和B,中间网C(比如说:Internet).A网络的情
况:
网络地址
1
0.0.1.0
子网掩码
2
55.255.255.0
路由器
1
0.0.1.1
16
路由器在C网络上的地址是172.16.17.18.我们称之为neta.
B网络的情况:
网络地址
1
0.0.2.0
子网掩码
2
55.255.255.0
路由器
1
0.0.2.1
路由器在C网络上的IP地址是 172.19.20.21.我们称之为netb.
已知C网络已经连通,我们假定它会将所有的数据包从A传到B,反之亦然.
至于原因,我们不考虑.
在A网络的路由器上,输入:
ip tunnel add netb mode gre remote 172.19.20.21 local 172.16.17.18 ttl 255
ip link set netb up
ip addr add 10.0.1.1 dev netb
ip route add 10.0.2.0/24 dev netb
让我们稍微讨论一下.第1行,我们添加了一个隧道设备,并且称之为netb(为
了能够表示出这个隧道通向哪里).并且表示要使用GRE协议 (mode gre),对端地
址是172.19.20.21(另一端的路由器),我们的隧道数据包发源于172.16.17.18(以便
当你的路由器在C网络中拥有多个地址的时候,你可以指定哪一个应用于隧道)
并且包的TTL字段应设置为255(ttl 255).
第2行,启用该隧道.
第3行,我们给这个新生的网卡配置了一个IP:10.0.1.1.对于小网络来说足够
了,但如果你网络中的隧道多得象无证运营的小煤窑一样,你可能就要考虑给你
的隧道规划一个单独的IP地址范围(在本例中,你可以使用10.0.3.0).
第4行,我们为B网络设置了一条路由.注意子网掩码的另一种表示方法.如
果你不熟悉这种表示,我就来解释一下:你把你的子网掩码写成二进制形式,数
数里面由多少个1.如果你连这个也不会做,不妨就简单地记住:255.0.0.0 就是
/8,255.255.0.0 就是 /16, 255.255.255.0 就是 /24.
让我们再看看B网络的路由器.
ip tunnel add neta mode gre remote 172.16.17.18 local 172.19.20.21 ttl 255
ip link set neta up
ip addr add 10.0.2.1 dev neta
ip route add 10.0.1.0/24 dev neta
如果你想从A路由器中停止隧道,输入:
ip link set netb down
ip tunnel del netb
当然,你可以把netb换成neta,在B路由器上操作.
17
5.3.2. IPv6隧道
关于IPv6地址,请参看第6章第1节.
这就开始吧.
我们假设你有如下的IPv6网络,你想把它连接到6bone或者一个朋友那里.
Network 3ffe:406:5:1:5:a:2:1/96
你的IPv4地址是172.16.17.18,6bone 路由器的IPv4地址是172.22.23.24.
ip tunnel add sixbone mode sit remote 172.22.23.24 local 172.16.17.18 ttl 255
ip link set sixbone up
ip addr add 3ffe:406:5:1:5:a:2:1/96 dev sixbone
ip route add 3ffe::/15 dev sixbone
让我们来讨论一下.我们创建了一个叫做sixbone的隧道设备.我们设置它的模
式是sit(也就是在IPv4隧道中使用IPv6)并且告诉它对端(remote)和本端 (local)
在哪里.TTL设置为最大,255.接着,我们激活了这个设备(up).然后,我们
添加了我们自己的网络地址,并添加了一条通过隧道去往3ffe::/15 (现在全部属
于6bone)的路由.
GRE隧道是现在最受欢迎的隧道技术.它也广泛地应用于Linux世界之外并成
为一个标准,是个好东西.
5.4. 用户级隧道
在内核之外,还有很多实现隧道的方法,最闻名的当然要数PPP和PPTP,但实
际上还有很多(有些是专有的,有些是安全的,有些甚至根本不用IP),但那远远
超出了本HOWTO所涉及的范围.
18
第6章 用Cisco和6bone实现IPv6
隧道
Marco Davids marco@sara.nl 著
NOTE to maintainer:
As far as I am concerned, this IPv6-IPv4 tunneling is not per definition GRE
tunneling. You could tunnel IPv6 over IPv4 by means of GRE tunnel devices (GRE
tunnels ANY to IPv4), but the device used here ("sit") only tunnels IPv6 over IPv4
and is therefore something different.
6.1. IPv6隧道
这是Linux隧道能力的另一个应用.这在IPv6的早期实现中非常流行.下面动
手试验的例子当然不是实现IPv6隧道的唯一方法.然而,它却是在Linux与支
持IPv6的CISCO路由器之间搭建隧道的常用方法,经验证明多数人都是照这样
做的.八成也适合于你 .
简单谈谈IPv6地址:
相对于IPv4地址而言, IPv6地址非常大,有128bit而不是32bit.这让我们得到
了我们需要的东西——非常非常多的IP地址.确切地说,有
340,282,266,920,938,463,463,374,607,431,768,211,465个.同时,IPv6(或者叫Ipng,
下一代IP)还能让Internet上的骨干路由器的路由表变得更小,设备的配置更简
单,IP层的安全性更好以及更好地支持QoS.
例如: 2002:836b:9820:0000:0000:0000:836b:9886
写下一个IPv6地址确实是件麻烦事.所以我们可以使用如下规则来进行简化 :
数字打头的零不要写,就像IPv4一样.
每16bit或者两个字节之间使用冒号分隔.
当出现很多连续的零时可简写成"::".在一个地址中只能使用一次.
例如:地址2002:836b:9820:0000:0000:0000:836b:9886可以写成:
2002:836b:9820::836b:9886,看上去更简单些.
另一个例子:地址3ffe:0000:0000:0000:0000:0020:34A1:F32C可以写成
3ffe::20:34A1:F32C,要短得多.
19
IPv6将可能取代现有的IPv4.因为它采用了相对更新的技术,所以现在还没有
全球范围的IPv6网络.为了能够平滑地过渡,引入了6bone计划.
IPv6网络中的站点通过现有的IPv4体系互联,把IPv6数据包封装在IPv4数据
包中进行传输.
这就是为什么引入隧道机制的原因.
为了能够使用IPv6,我们需要一个能够支持它的内核.现在有很多文档都很好
地说明了这个问题.不外乎以下几步:
找到一个新版的Linux发行版,要有合适的glibc库.
找到一份最新的内核源代码.
都准备好了以后,就可以继续编译一个带IPv6支持的内核了:
cd /usr/src/linux
make menuconfig
选择"Networking Options"
选择"The IPv6 protocol","IPv6: enable EUI-64 token format", "IPv6:
disable provider based addresses"
提示:不要编译成内核模块,那样经常会出问题.换句话说,就是把IPv6内置
入内核.
然后你就可以象往常一样保存配置并编译内核了.
提示:在编译之前,可以修改一下Makefile,把EXTRAVERSION = -x变成
EXTRAVERSION = -x-IPv6
有很多文档都很好地说明了如何编译并安装一个内核,我们这篇文档不是讨论这
个问题的.如果你在这个过程中出现了问题,请参阅合适的资料.你可以先看看
/usr/src/linux/README.
当你完成之后,用新的内核重启系统,你可以输入"/sbin/ifconfig -a"看看有没
有新的"sit0-device"设备.SIT的意思是"简单Internet过渡"(Simple Internet
Transition).如果到这里没有问题,你就可以奖励自己了,你已经向着下一代IP
网络迈进了一大步.
现在继续下一步.你需要把你的主机,或甚至整个局域网连接到另外一个IPv6
网络上.这个网络很可能是"6bone",它就是为了这个特定的目的而专门设立的.
让我们假定你有如下IPv6网络: 3ffe:604:6:8::/64,并且希望连接到6bone,或者
其他地方.请注意,/64这个子网声明的意义与IPv4相同.
你的IPv4地址是145.100.24.181,6bone的路由器的IPv4地址是145.100.1.5.
# ip tunnel add sixbone mode sit remote 145.100.1.5 [local 145.100.24.181 ttl 255]
# ip link set sixbone up
# ip addr add 3FFE:604:6:7::2/126 dev sixbone
# ip route add 3ffe::0/16 dev sixbone
20
/proc/sys/net/ipv6/conf/all/forwarding
# /usr/local/sbin/radvd
下面的一行,radvd是一个类似于zebra的路由公告守护程序,用来支持IPv6的
自动配置特性.如果感兴趣的话就用你最喜欢的搜索引擎找一找.你可以检查一
下:
# /sbin/ip -f inet6 addr
如果你的Linux网关支持IPv6且运行了radvd,在局域网上启动后,你就可以享
受IPv6的自动配置特性了:
# /sbin/ip -f inet6 addr
1: lo: mtu 3924 qdisc noqueue inet6 ::1/128 scope host
3: eth0: mtu 1500 qdisc pfifo_fast qlen 100
inet6 3ffe:604:6:8:5054:4cff:fe01:e3d6/64 scope global dynamic
valid_lft forever preferred_lft 604646sec inet6 fe80::5054:4cff:fe01:e3d6/10
scope link
你可以继续进行了,为IPv6配置你的bind.与A记录等价的,支持IPv6的记录
类型是"AAAA".与in-addr.arpa等价的是"ip6.int".这方面可以找到很多信息.
支持IPv6的应用系统曾在增加,包括ssh,telnet,inetd,Mozilla浏览器,Apache
WEB浏览器…….但那些都不是这个路由文档所应该涉及的.
作为Cisco系统,应该这样配置:
!
interface Tunnel1
description IPv6 tunnel
no ip address
no ip directed-broadcast
ipv6 address 3FFE:604:6:7::1/126
tunnel source Serial0
tunnel destination 145.100.24.181
tunnel mode ipv6ip
!
ipv6 route 3FFE:604:6:8::/64 Tunnel1
但如果你没有Cisco作为disposal,试试Internet上的众多IPv6隧道提供者之一.
他们愿意在他们的Cisco设备上为你额外创建一个隧道.大部分是友好的WEB
界面.用你常用的搜索引擎搜索一下"ipv6 tunnel broker".
21
第7章 IPSEC:Internet上安全的IP
现在,在Linux上有两种IPSEC可用.对于2.2和2.4,第一个比较正规的实现
有FreeS/WAN.他们有一个官方站点和一个经常维护的非官方站点.出于多种
原因,FreeS/WAN历来没有被合并到内核的主线上.最常提到的原因就是政治
问题——违反了美国的密码产品扩散条例.所以它不会被集成到Linux内核中.
另外,很多合作伙伴表明了他们对代码质量的忧虑.关于如何设置FreeS/WAN,
有很多文档可以参考.
Linux 2.5.47版内核里有一个内置的IPSEC实现,是由Alexey Kuznetsov和Dave
Miller在USAGI IPv6 小组的启发下写成的.经过这次合并,James Morris的
CrypoAPI也成了内核的一部分——它能够真正地加密.
本HOWTO文档仅收录2.5以上版本内核的IPSEC.Linux 2.4内核的用户推荐使
用FreeS/WAN.但是要注意,它的配置方法与内置IPSEC不同.
2.5.49版内核的IPSEC不再需要任何补丁.
我在这里收集了Alexey或Dave Miller发布的一些补丁.对于2.5.48版的内
核,在报告BUG之前请确认已经打上了那些补丁!(迄今还没有2.5.49这方
面的补丁).一些简单的用户级工具可以在这里 (编译好的可执行文件和手
册)找到.编译这些用户级工具需要修改Makefiles指向你的2.5.x内核.这种
情况可望很快解决.
编译你的内核的时候,要确认已经打开CryptoAPI中的"PF_KEY","AH",
"ESP"以及其他所有选项!netfilter中的TCP_MSS方法现在不能用,请关
掉.
本章的作者对于IPSEC完全是外行(nitwit)!如果你发现了错误,请email
通知bert hubert .
首先,我们展示一下如何在两个主机之间手动设置安全通讯.其间的大部分工作
都可以自动完成,但是为了了解细节我们仍然用手动完成.
如果你仅仅对自动密钥管理感兴趣,请跳过下面一节.但是要知道,了解手动密
要管理是很有用的.
7.1. 从手动密钥管理开始
22
IPSEC是一个复杂的主题.有很多在线信息可以查阅,这个HOWTO将集中讲
解如何设置,运行并解释一些基本原理.
很多iptables配置会丢弃IPSEC数据包!要想让IPSEC通过,运行
iptables -A xxx -p 50 -j ACCEPT
iptables -A xxx -p 51 -j ACCEPT
IPSEC提供了一个安全的IP协议版本.所谓的"安全"意味着两件事情:加密
与验证.如果认为安全仅仅就是加密哪就太天真了,很容易看出来那是不够的
——你的通讯过程或许是加密的,但是你如何保证与你通讯的对端就是你期望中
的人呢?
IPSEC使用ESP('Encapsulated Security Payload',安全载荷封装) 来支持加密,使用
AH(Authentication Header,头部验证)来支持对端验证.你可以同时使用二者,
也可以使用二者之一.
ESP和AH都依靠SA(security associations,安全联盟)来工作.一个SA由一个源,
一个目的和一个说明组成.一个验证SA看上去应该是:
add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
意思是:"从10.0.0.11到10.0.0.216的数据包需要AH,使用HMAC-MD5签名,
密码是1234567890123456".这个说明的SPI(Security Parameter Index,安全参数
索引)号码是'15700',细节后面再谈.有意思的是一个会话的两端都使用相同的
SA——它们是全等的,而不是镜像对称的.要注意的是,SA并没有自动翻转规
则——这个SA仅仅描述了从10.0.0.11到10.0.0.216的认证.如果需要双向安全,
需要2条SA.
一个简单地ESP SA:
add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
意思是:"从10.0.0.11到10.0.0.216的数据包需要ESP,使用3des-cbc加密算法,
密码是123456789012123456789012".SPI号码是'15701'.
到此,我们看到SA描述了所有的说明,但它并没有描述应该什么时候用到这些
策略.实际上,可以有很多完全相同的SA,除了它们之间的SPI不同.顺便提
一句,SPI的意思是Security Parameter Index(安全参数索引).为了进行加密操作,
我们需要声明一个策略.这个策略可以包含诸如"如果可能的话使用ipsec"或
者"除非我们有ipsec否则丢弃数据包"这样的东西.
最简单的典型SP(Security Policy,安全策略)应该是这样:
spdadd 10.0.0.216 10.0.0.11 any -P out ipsec \
esp/transport//require \
ah/transport//require;
如果在10.0.0.216上输入这个,就意味着凡是去往10.0.0.11的数据包必须经过加
密并附带AH头验证.要注意,它并没有指出使用哪一个SA,那是留给内核来
23
10.0.0.216: icmp: echo reply
注意,可看出从10.0.0.11返回的包的确是明码传输.Ping的进一步细节用tcpdump
当然看不到,但是它还是显示了用来告诉10.0.0.11如何进行解密和验证的参数
——AH和ESP的SPI值.
还有几件事情必须提及.上面给出的配置在很多IPSEC的配置范例中都有引用,
但确实是很危险的.问题就在于上述配置包含了10.0.0.216应该如何处理发往
10.0.0.11的包,和10.0.0.1如何解释那些包,但是却没有指出10.0.0.11应当丢弃
来自10.0.0.216的未进行加密及验证的包!
任何人都可以插入完全未加密的欺骗包,而10.0.0.11会不假思索地接受.为了
弥补上述漏洞我们必须在10.0.0.11上增加一个针对进入数据包的SP,如下:
#!/sbin/setkey -f
spdadd 10.0.0.216 10.0.0.11 any -P IN ipsec
esp/transport//require
ah/transport//require;
这就指明了10.0.0.11收到来自10.0.0.216的包的时候需要正确的ESP和AH处理.
现在,我们完成这个配置,我们当然也希望回去的数据包也进行加密和头验证.
10.0.0.216上完整的配置应该是:
24
#!/sbin/setkey -f
flush;
spdflush;
# AH
add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";
# ESP
add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";
spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
esp/transport//require
ah/transport//require;
spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
esp/transport//require
ah/transport//require;
10.0.0.11上:
#!/sbin/setkey -f
flush;
spdflush;
# AH
add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";
# ESP
add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";
spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
esp/transport//require
ah/transport//require;
spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
esp/transport//require
ah/transport//require;
注意,本例中通信双方的加密密钥是一样的.这在实际中是不会出现的.
为了检测一下我们刚才的配置,运行一下:
setkey -D
就会显示出SA,或者用
setkey -DP
显示出SP.
7.2. 自动密钥管理
25
在上一节中,使用了简单共享的密码进行加密.换句话说,为了保密,我们必须
通过一个安全的通道把加密配置传给对方.如果我们使用telnet配置对端的机器,
任何一个第三方都可能知道我们的共享密码,那么设置就是不安全的.
而且,因为密码是共享的,所以它就不成为真正意义的密码.就算对端不能用它
做什么,但我们需要为每一个要进行IPSEC通讯的对方都设置互补相同的密码.
这需要生成大量的密钥,如果有10个成员需要通讯,就至少需要50个不同的密
码.
除了对称密钥的问题之外,还有解决密钥轮转的问题.如果第三方搜集到足够的
数据包,就有可能反向计算出密钥.这可以通过每隔一段时间换用一个新密钥来
解决,但是这必须自动完成.
另一个问题是,如上所述的手动密钥管理,我们必须精确地指定算法和密钥长度,
与对端的协调也是个大问题.我们渴望能够用一种比较宽松的方式来描述密钥策
略,比如说:"我们可以用3DES或者Blowfish算法,密钥长度至少是多少多少
位".
为了满足这些要求,IPSEC提供了IKE(Internet Key Exchange,Internet密钥交换)
来自动随机生成密钥,并使用协商好的非对称加密算法进行密钥交换.
Linux 2.5的IPSEC实现利用KAME的"racoon"IKE守护程序来进行.截止到
11月9日,在Alexey的iptools发布包中的racoon是可以编译的,但是需要从两
个文件中删除#include .你也可以下载我提供的编译好的版本.
IKE需要使用UDP的500端口,确认你的iptables不会挡住数据包.
7.2.1. 理论
象前面所解释的自动密钥管理会为我们做很多事情.特别地,它会自动地动态生
成SA.不象大家所以为的那样,它并不会为我们设置SP.
所以,如果想使用IKE,需要设置好SP,但不要设置任何SA.内核如果发现有
一个IPSEC的SP,但不存在任何相应的SA,就会通知IKE守护程序,让它去
协商.
重申,一个SP决定了我们需要什么;而SA决定了我们如何得到它.使用自动
密钥管理就可以让我们只关心需要什么就够了.
7.2.2. 举例
Kame的racoon有非常多的选项,其中绝大部分都已经配置好了缺省值,所以我
们不用修改它们.象上面描述的,管理员需要定义一个SP而不配置SA,留给
IKE守护程序去协商.
在这个例子中,仍然是10.0.0.11和10.0.0.216之间需要配置安全通讯,但这次要
26
借助于racoon.为了简单起见,这个配置使用预先共享的密钥(又是可怕的共享
密钥).X.509证书的问题单独讨论,参见后面的7.2.3.
我们尽量保持缺省配置,两台机器是一样的:
path pre_shared_key "/usr/local/etc/racoon/psk.txt";
remote anonymous
{
exchange_mode aggressive,main;
doi ipsec_doi;
situation identity_only;
my_identifier address;
lifetime time 2 min; # sec,min,hour
initial_contact on;
proposal_check obey; #
obey, strict or claim
proposal {
encryption_algorithm 3des;
hash_algorithm sha1;
authentication_method pre_shared_key;
dh_group 2 ;
}
}
sainfo anonymous
{
pfs_group 1;
lifetime time 2 min;
encryption_algorithm 3des ;
authentication_algorithm hmac_sha1;
compression_algorithm deflate ;
}
有很多设置,我认为仍然有很多可以去掉而更接近缺省配置.很少有值得注意的
事情.我们已经配置了两个匿名配置支持所有的对端机器,让将来的配置简单些.
这里没有必要为每台机器各写一个段落,除非真的必要.
此外,我们还设置了我们基于我们的IP地址来识别我们自己('my_identifier
address'),并声明我们可以进行3DES,sha1,并且我们将使用预先共享的密钥,
写在psk.txt中.
在psk.txt中,我们设置两个条目,两台机器上都不一样.
在10.0.0.11上:
10.0.0.216
password2
在10.0.0.216上:
10.0.0.11
password2
确认这些文件必须为root所有,属性是0600,否则racoon将不信任其内容.两
个机器上的这个文件是镜像对称的.
现在,我们就剩下设置SP了,这比较简单.
在10.0.0.216上:
27
#!/sbin/setkey -f
flush;
spdflush;
spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
esp/transport//require;
spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
esp/transport//require;
在10.0.0.11上:
#!/sbin/setkey -f
flush;
spdflush;
spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
esp/transport//require;
spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
esp/transport//require;
请注意这些SP的镜像对称规律.
我们可以启动racoon了!一旦启动,当我们试图从10.0.0.11到10.0.0.216进行
telnet的时候,racoon就会开始协商:
12:18:44: INFO: isakmp.c:1689:isakmp_post_acquire(): IPsec-SA
request for 10.0.0.11 queued due to no phase1 found.
12:18:44: INFO: isakmp.c:794:isakmp_ph1begin_i(): initiate new
phase 1 negotiation: 10.0.0.216[500]10.0.0.11[500]
12:18:44: INFO: isakmp.c:799:isakmp_ph1begin_i(): begin Aggressive mode.
12:18:44: INFO: vendorid.c:128:check_vendorid(): received Vendor ID:
KAME/racoon
12:18:44: NOTIFY: oakley.c:2037:oakley_skeyid(): couldn't find
the proper pskey, try to get one by the peer's address.
12:18:44: INFO: isakmp.c:2417:log_ph1established(): ISAKMP-SA
established 10.0.0.216[500]-10.0.0.11[500] spi:044d25dede78a4d1:ff01e5b4804f0680
12:18:45: INFO: isakmp.c:938:isakmp_ph2begin_i(): initiate new phase 2
negotiation: 10.0.0.216[0]10.0.0.11 spi=15863890(0xf21052)
如果我们现在运行setkey -D列出SA,就会发现已经存在了:
10.0.0.216 10.0.0.11
esp mode=transport spi=224162611(0x0d5c7333) reqid=0(0x00000000)
E: 3des-cbc 5d421c1b d33b2a9f 4e9055e3 857db9fc 211d9c95 ebaead04
A: hmac-sha1 c5537d66 f3c5d869 bd736ae2 08d22133 27f7aa99
seq=0x00000000 replay=4 flags=0x00000000 state=mature
created: Nov 11 12:28:45 2002
current: Nov 11 12:29:16 2002
diff: 31(s)
hard: 600(s)
soft: 480(s)
last: Nov 11 12:29:12 2002
hard: 0(s)
soft: 0(s)
current: 304(bytes)
hard: 0(bytes)
soft: 0(bytes)
28
allocated: 3
hard: 0
soft: 0
sadb_seq=1 pid=17112 refcnt=0
10.0.0.11 10.0.0.216
esp mode=transport spi=165123736(0x09d79698) reqid=0(0x00000000)
E: 3des-cbc d7af8466 acd4f14c 872c5443 ec45a719 d4b3fde1 8d239d6a
A: hmac-sha1 41ccc388 4568ac49 19e4e024 628e240c 141ffe2f
seq=0x00000000 replay=4 flags=0x00000000 state=mature
created: Nov 11 12:28:45 2002
current: Nov 11 12:29:16 2002
diff: 31(s)
hard: 600(s)
soft: 480(s)
last:
hard: 0(s)
soft: 0(s)
current: 231(bytes)
hard: 0(bytes)
soft: 0(bytes)
allocated: 2
hard: 0
soft: 0
sadb_seq=0 pid=17112 refcnt=0
我们的SP是如下配置的:
10.0.0.11[any] 10.0.0.216[any] tcp
in ipsec
esp/transport//require
created:Nov 11 12:28:28 2002 lastused:Nov 11 12:29:12 2002
lifetime:0(s) validtime:0(s)
spid=3616 seq=5 pid=17134
refcnt=3
10.0.0.216[any] 10.0.0.11[any] tcp
out ipsec
esp/transport//require
created:Nov 11 12:28:28 2002 lastused:Nov 11 12:28:44 2002
lifetime:0(s) validtime:0(s)
spid=3609 seq=4 pid=17134
refcnt=3
7.2.2.1. 问题和常见的疏忽
如果不工作,检查一下所有的配置文件是不是为root所有,而且只有root才能
读取.如想前台启动racoon,就加上"-F"参数.如想强制它读取某一个配置文
件来取代缺省配置文件,使用参数"-f".如想看到超级详细的细节,往racoon.conf
中加入"log debug;"一行.
7.2.3. 使用X.509证书进行自动密钥管理
如前所述,之所以共享密码很困难,是因为它们一旦共享,就不再成为真正意义
的密码.幸运的是,我们仍可以用非对称加密技术来解决这个问题.
如果IPSEC的每个参与者都生成一对公钥和私钥,就可以让双方公开它们的公
29
钥并设置策略,从而建立安全连接.
虽然需要一些计算,但生成密钥还是相对比较简单的.以下都是基于openssl工
具实现的.
7.2.3.1. 为你的主机生成一个X.509证书
OpenSSL搭好了很多基础结构,以便我们能够使用经过或者没有经过CA签署的
密钥.现在,我们就围绕这些基础结构,并练习一下使用著名的Snake Oil安全,
而不是使用CA.
首先,我们为主机laptop发起一个"证书请求":
$ openssl req -new -nodes -newkey rsa:1024 -sha1 -keyform PEM -keyout \
laptop.private -outform PEM -out request.pem
这是可能问我们的问题:
Country Name (2 letter code) [AU]:NL
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:Delft
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Linux Advanced
Routing & Traffic Control
Organizational Unit Name (eg, section) []:laptop
Common Name (eg, YOUR name) []:bert hubert
Email Address []:ahu@ds9a.nl
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
请你根据自己的实际情况完整填写.你可以把你的主机名写进去,也可以不写,
取决于你的安全需求.这个例子中,我们写了.
我们现在自己签署这个请求:
$ openssl x509 -req -in request.pem -signkey laptop.private -out \
laptop.public
Signature ok
subject=/C=NL/L=Delft/O=Linux Advanced Routing & Traffic \
Control/OU=laptop/CN=bert hubert/Email=ahu@ds9a.nl
Getting Private key
现在,"request.pem"这个文件已经没用了,可以删除.
在你需要证书的每台机器上都重复上述过程.你现在就可以放心地发布你的
"*.public"文件了,但是一定要保证"*.private"是保密的!
7.2.3.2. 设置并启动
我们一旦拥有了一对公钥和私钥,就可以告诉racoon去使用它们了.
现在我们回到上面的配置中的两台机器,10.0.0.11 (upstairs)和10.0.0.216(laptop).
在10.0.0.11上的racoon.conf中,我们添加:
30
path certificate "/usr/local/etc/racoon/certs";
remote 10.0.0.216
{
exchange_mode aggressive,main;
my_identifier asn1dn;
peers_identifier asn1dn;
certificate_type x509 "upstairs.public" "upstairs.private";
peers_certfile "laptop.public";
proposal {
encryption_algorithm 3des;
hash_algorithm sha1;
authentication_method rsasig;
dh_group 2 ;
}
}
它们告诉racoon:证书可以在/usr/local/etc/racoon/certs/那里找到.而且还包含了
专门为10.0.0.216而写的配置项.
包含"asn1dn"的行告诉racoon,本端和对端的标识都从公钥中提取.也就是上
面输出的"subject=/C=NL/L=Delft/O=Linux Advanced Routing & Traffic
Control/OU=laptop/CN=bert hubert/Email=ahu@ds9a.nl".
"certificate_type"那一行配置了本地的公钥和私钥."peers_certfile"这行告诉
racoon读取名叫"laptop.public"的文件取得对端的公钥.
"proposal"这一段与你以前看到的基本一致,除了"authentication_method"
的值变成了"rsasig",意思是使用RSA 公钥/私钥对.
在10.0.0.216上面的配置文件与上面的是完全镜像关系,没有其它改变:
path certificate "/usr/local/etc/racoon/certs";
remote 10.0.0.11
{
exchange_mode aggressive,main;
my_identifier asn1dn;
peers_identifier asn1dn;
certificate_type x509 "laptop.public" "laptop.private";
peers_certfile "upstairs.public";
proposal {
encryption_algorithm 3des;
hash_algorithm sha1;
authentication_method rsasig;
dh_group 2 ;
}
}
现在,我们已经把两台机器的配置文件改好了,然后就应该把证书文件拷贝到正
确的位置."upstairs"这台机器需要往/usr/local/etc/racoon/certs中放入
upstairs.private,upstairs.public和laptop.public.请确认这个目录属于root,且属
性为0600,否则racoon会拒绝使用!
"laptop"这台机器需要往/usr/local/etc/racoon/certs 中放入laptop.private,
laptop.public和upstairs.public.也就是说,每台机器都需要本端的公钥和私钥,
31
以及对端的公钥.
确认一下已经写好了SP(执行在7.2.2中提到的spdadd).然后启动racoon,就应
该可以工作了.
7.2.3.3. 如何安全地建立隧道
为了与对端建立安全的通讯,我们必须交换公钥.公钥没必要保密,重要的是要
保证它不被替换.换句话说,要确保没有"中间人".
为了简化这个工作,OpenSSL提供了"digest"命令:
$ openssl dgst upstairs.public
MD5(upstairs.public)= 78a3bddafb4d681c1ca8ed4d23da4ff1
现在我们要做的就是检验一下对方是否能够得到相同的MD5散列值.这可以通
过真实地接触来完成,也可以通过电话,但是一定不要与公钥放在同一封电子邮
件里发送!
另一个办法是通过一个可信的第三方(CA)来实现.这个CA会为你的密钥进
行签名,而不是象上面那样由我们自己签名.
7.3. IPSEC隧道
迄今为止,我们只是认识了IPSEC的"transport"(透明)模式,也就是通讯的两
端都能够直接理解IPSEC.这不能代表所有的情况,有时候我们只需要路由器理
解IPSEC,路由器后面的机器利用它们进行通讯.这就是所谓的"tunnel mode"
(隧道模式).
设置这个极其简单.如果想通过10.0.0.216与10.0.0.11建立的隧道来传输从
10.0.0.0/24到130.161.0.0/16的数据包,按下面配置就可以:
#!/sbin/setkey -f
flush;
spdflush;
add 10.0.0.216 10.0.0.11 esp 34501
-m tunnel
-E 3des-cbc "123456789012123456789012";
spdadd 10.0.0.0/24 130.161.0.0/16 any -P out ipsec
esp/tunnel/10.0.0.216-10.0.0.11/require;
注意."-m tunnel"是关键!这里首先配置了一个隧道两端(10.0.0.216与10.0.0.11)
使用ESP的SA.
然后配置了实际的隧道.它指示内核,对于从10.0.0.0/24到130.161.0.0/16的数
据包需要加密.而且这些数据包被发往10.0.0.11.
10.0.0.11也需要相同的配置:
#!/sbin/setkey -f
flush;
32
spdflush;
add 10.0.0.216 10.0.0.11 esp 34501
-m tunnel
-E 3des-cbc "123456789012123456789012";
spdadd 10.0.0.0/24 130.161.0.0/16 any -P in ipsec
esp/tunnel/10.0.0.216-10.0.0.11/require;
注意,与上面基本一样,除了把"-P out"换成了"-P in".就象先前的例子一样,
我们只配置了单向的传输.完整地实现双向传输就留给读者自己研究实现吧.
这种配置的另一个更直观的名字叫做"ESP代理(proxy ESP)".
IPSEC隧道需要内核能够进行IP转发!
7.4. 其它IPSEC软件
Thomas Walpuski报告说它已经写了一个补丁,可以让OpenBSD的isakpmd与
Linux 2.5的IPSEC协同工作.可以在这个网页找到.他指出isakpmd在Linux
上仅仅需要libkeynote支持.根据他的说法,在Linux 2.5.59上面工作得很好.
isakpmd与前面提到的racoon由很大的不同,但是很多人喜欢用它,可以在这里
找到.这里有OpenBSD CVS.Thomas还为那些不习惯用CVS或者patch的人
们制作了一个tarball.
7.5. IPSEC与其它系统的互操作
求助: Write this
7.5.1. Windows
33
/proc/sys/net/ipv4/ip_forward
在这里,你可能想知道是否起了作用.所以我们ping一下缺省组224.0.0.1,看
看有没有人在.在你的LAN上所有配置并启用了多播的机器都应该予以回应,
其他机器则不会.但你会注意到,没有任何一台机器回应的时候声明自己是
224.0.0.1,多么令人惊奇 !因为这是一个组地址(对于接收者来说是"广播"),
所以组中的所有成员都用它们的地址来回应,而不是用组地址来回应.
ping -c 2 224.0.0.1
34
到此,你已经可以实现真正的多播路由了.好的,假定你需要在两个网络间进行
路由.
(To Be Continued!)
35
byte/s
9.1. 解释队列和队列规定
利用队列,我们决定了数据被发送的方式.必须认识到,我们只能对发送数据进
行整形.
根据Internet的工作方式,我们无法直接控制别人向我们发送什么数据.有点象
我们家里的信报箱,你不可能控制全世界,联系每一个人,修改别人对你发送邮
件的数量.
然而,Internet主要依靠TCP/IP,它的一些特性很有用.因为TCP/IP没办法知道
两个主机之间的网络容量,所以它会试图越来越快地发送数据(所谓的"慢起技
术") ,当因为网络容量不够而开始丢失数据时,再放慢速度.实际情况要比这
种方法更聪明,我们以后再讨论.
这就象当你尚未读完一半邮件时,希望别人停止给你寄信.与现实世界不同,在
Internet上可以做到这一点.(译注:这个例子并不恰当,TCP/IP的这种机制并不
是在网络层实现的,而是要靠传输层的TCP协议)
如果你有一个路由器,并且希望能够防止某些主机下载速度太快,你需要在你路
由器的内网卡——也就是向你的网内主机发送数据包的网卡——上进行流量整
形.
你还要保证你正在控制的是瓶颈环节.如果你有一个100M以太网卡,而你的路
由器的链路速度是256k,你必须保证你发送的数据量没有超过路由器的处理能
力.否则,就是路由器在控制链路和对带宽进行整形,而不是你.可以说,我们
需要拥有的队列必须是一系列链路中最慢的环节.幸运的是这很容易.
36
9.2. 简单的无类队列规定
如前所述,利用队列,我们决定了数据被发送的方式.无类队列规定就是那样,
能够接受数据和重新编排,延迟或丢弃数据包.
这可以用作对于整个网卡的流量进行整形,而不细分各种情况.在我们进一步学
习分类的队列规定之前,理解这部分是必不可少的!
最广泛应用的规定是pfifo_fast队列规定,因为它是缺省配置.这也解释了为什
么其它那些复杂的功能为何如此健壮,因为那些都与缺省配置相似,只不过是其
他类型的队列而已.
每种队列都有它们各自的优势和弱点.
9.2.1. pfifo_fast
这个队列的特点就象它的名字——先进先出(FIFO),也就是说没有任何数据包
被特殊对待.至少不是非常特殊.这个队列有3个所谓的"频道".FIFO规则应
用于每一个频道.并且:如果在0频道有数据包等待发送,1频道的包就不会被
处理,1频道和2频道之间的关系也是如此.
内核遵照数据包的TOS标记,把带有"最小延迟"标记的包放进0频道.
不要把这个无类的简单队列规定与分类的PRIO相混淆!虽然它们的行为有些类
似,但对于无类的pfifo_fast而言,你不能使用tc命令向其中添加其它的队列规
定.
9.2.1.1. 参数与使用
pfifo_fast队列规定作为硬性的缺省设置,你不能对它进行配置.它缺省是这样
配置的:
priomap:
内核规定,根据数据包的优先权情况,对应相应的频道.这个对应是根据
数据包的TOS字节进行的.TOS看上去是这样的:
0 1 2 3 4 5 6 7
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | |
| 优先权 | TOS | MBZ |
| | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
TOS字段的4个bit是如下定义的:
二进制 十进制 意义
-----------------------------------------
1000 8 最小延迟 (md)
0100 4 最大throughput (mt)
0010 2 最大可靠性 (mr)
37
7),它们不对应TOS映射,但是有其它的意图.
下表来自RFC 1349,告诉你应用程序可能如何设置它们的TOS:
TELNET 1000 (minimize delay)
控制 1000 (minimize delay) FTP
数据 0100 (maximize throughput)
TFTP 1000 (minimize delay)
命令阶段 1000 (minimize delay) SMTP
数据阶段 0100 (maximize throughput)
UDP 查询 1000 (minimize delay)
TCP 查询 0000
Domain Name Service
区域传输 0100 (maximize throughput)
NNTP 0001 (minimize monetary cost)
报错 0000
请求 0000 (mostly)
ICMP
响应 (mostly)
txqueuelen
38
队列的长度来自网卡的配置,你可以用ifconfig和ip命令修改.如设置队
列长度为10,执行:ifconfig eth0 txqueuelen 10
你不能用tc命令设置这个!
9.2.2. 令牌桶过滤器(TBF)
令牌桶过滤器(TBF)是一个简单的队列规定:只允许以不超过事先设定的速率到
来的数据包通过,但可能允许短暂突发流量朝过设定值.
TBF很精确,对于网络和处理器的影响都很小.所以如果您想对一个网卡限速,
它应该成为您的第一选择!
TBF的实现在于一个缓冲器(桶),不断地被一些叫做"令牌"的虚拟数据以特定
速率填充着. (token rate).桶最重要的参数就是它的大小,也就是它能够存储
令牌的数量.
每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除.这个算法关
联到两个流上——令牌流和数据流,于是我们得到3种情景:
数据流以等于令牌流的速率到达TBF.这种情况下,每个到来的数据包都
能对应一个令牌,然后无延迟地通过队列.
数据流以小于令牌流的速度到达TBF.通过队列的数据包只消耗了一部分
令牌,剩下的令牌会在桶里积累下来,直到桶被装满.剩下的令牌可以在
需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发
传输.
数据流以大于令牌流的速率到达TBF.这意味着桶里的令牌很快就会被耗
尽.导致TBF中断一段时间,称为"越限".如果数据包持续到来,将发
生丢包.
最后一种情景非常重要,因为它可以用来对数据通过过滤器的速率进行整形.
令牌的积累可以导致越限的数据进行短时间的突发传输而不必丢包,但是持续越
限的话会导致传输延迟直至丢包.
请注意,实际的实现是针对数据的字节数进行的,而不是针对数据包进行的.
9.2.2.1. 参数与使用
即使如此,你还是可能需要进行修改,TBF提供了一些可调控的参数.第一个参
数永远可用:
limit/latency
limit确定最多有多少数据(字节数)在队列中等待可用令牌.你也可以
通过设置latency参数来指定这个参数,latency参数确定了一个包在TBF
中等待传输的最长等待时间.后者计算决定桶的大小,速率和峰值速率.
39
burst/buffer/maxburst
桶的大小,以字节计.这个参数指定了最多可以有多少个令牌能够即刻被
使用.通常,管理的带宽越大,需要的缓冲器就越大.在Intel体系上,
10兆bit/s的速率需要至少10k字节的缓冲区才能达到期望的速率.
如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会
导致潜在的丢包.
mpu
一个零长度的包并不是不耗费带宽.比如以太网,数据帧不会小于64字
节.Mpu(Minimum Packet Unit,最小分组单位)决定了令牌的最低消耗.
rate
速度操纵杆.参见上面的limits!
如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况).If the
bucket contains tokens and is allowed to empty, by default it does so at infinite speed.
如果不希望这样,可以调整入下参数:
peakrate
如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样.
那可能并不是你希望的,特别是你有一个比较大的桶的时候.
峰值速率可以用来指定令牌以多块的速度被删除.用书面语言来说,就是:
释放一个数据包,但后等待足够的时间后再释放下一个.我们通过计算等
待时间来控制峰值速率
然而,由于UNIX定时器的分辨率是10毫秒,如果平均包长10k bit,我
们的峰值速率被限制在了1Mbps.
mtu/minburst
但是如果你的常规速率比较高,1Mbps的峰值速率对我们就没有什么价
值.要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包.最
有效的办法就是:再创建一个令牌桶!
这第二个令牌桶缺省情况下为一个单个的数据包,并非一个真正的桶.
要计算峰值速率,用mtu乘以100就行了. (应该说是乘以HZ数,Intel
体系上是100,Alpha体系上是1024)
9.2.2.2. 配置范例
这是一个非常简单而实用的例子:
# tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
为什么它很实用呢?如果你有一个队列较长的网络设备,比如DSL modem或者
cable modem什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上
40
载数据绝对会破坏交互性.
这是因为上载数据会充满modem的队列,而这个队列为了改善上载数据的吞吐
量而设置的特别大.但这并不是你需要的,你可能为了提高交互性而需要一个不
太大的队列.也就是说你希望在发送数据的时候干点别的事情.
上面的一行命令并非直接影响了modem中的队列,而是通过控制Linux中的队
列而放慢了发送数据的速度.
把220kbit修改为你实际的上载速度再减去几个百分点.如果你的modem确实很
快,就把"burst"值提高一点.
9.2.3. 随机公平队列(SFQ)
SFQ(Stochastic Fairness Queueing,随机公平队列)是公平队列算法家族中的一个
简单实现.它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的
计算量却很少.
SFQ的关键词是"会话"(或称作"流") ,主要针对一个TCP会话或者UDP
流.流量被分成相当多数量的FIFO队列中,每个队列对应一个会话.数据按照
简单轮转的方式发送, 每个会话都按顺序得到发送机会.
这种方式非常公平,保证了每一个会话都不会没其它会话所淹没.SFQ之所以被
称为"随机",是因为它并不是真的为每一个会话创建一个队列,而是使用一个
散列算法,把所有的会话映射到有限的几个队列中去.
因为使用了散列,所以可能多个会话分配在同一个队列里,从而需要共享发包的
机会,也就是共享带宽.为了不让这种效应太明显,SFQ会频繁地改变散列算法,
以便把这种效应控制在几秒钟之内.
有很重要的一点需要声明:只有当你的出口网卡确实已经挤满了的时候,SFQ才
会起作用!否则在你的Linux机器中根本就不会有队列,SFQ也就不会起作用.
稍后我们会描述如何把SFQ与其它的队列规定结合在一起,以保证两种情况下
都比较好的结果.
特别地,在你使用DSL modem或者cable modem的以太网卡上设置SFQ而不进
行任何进一步地流量整形是无谋的!
9.2.3.1. 参数与使用
SFQ基本上不需要手工调整:
perturb
多少秒后重新配置一次散列算法.如果取消设置,散列算法将永远不会重
新配置(不建议这样做).10秒应该是一个合适的值.
quantum
41
一个流至少要传输多少字节后才切换到下一个队列.却省设置为一个最大
包的长度(MTU的大小).不要设置这个数值低于MTU!
9.2.3.2. 配置范例
如果你有一个网卡,它的链路速度与实际可用速率一致——比如一个电话
MODEM——如下配置可以提高公平性:
# tc qdisc add dev ppp0 root sfq perturb 10
# tc -s -d qdisc ls
qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb 10sec
Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)
"800c:"这个号码是系统自动分配的一个句柄号,"limit"意思是这个队列中可
以有128个数据包排队等待.一共可以有1024个散列目标可以用于速率审计,
而其中128个可以同时激活.(no more packets fit in the queue!)每隔10秒种散列
算法更换一次.
9.3. 关于什么时候用哪种队列的建议
总之,我们有几种简单的队列,分别使用排序,限速和丢包等手段来进行流量整
形.
下列提示可以帮你决定使用哪一种队列.涉及到了第14章 所描述的的一些队列
规定:
如果想单纯地降低出口速率,使用令牌桶过滤器.调整桶的配置后可用于
控制很高的带宽.
如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,
使用随机公平队列.
如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随
机丢包(参见"高级"那一章).
如果希望对入口流量进行"整形"(不是转发流量),可使用入口流量策略,
注意,这不是真正的"整形".
如果你正在转发数据包,在数据流出的网卡上应用TBF.除非你希望让数
据包从多个网卡流出,也就是说入口网卡起决定性作用的时候,还是使用
入口策略.
如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载
而需要使用队列,使用pfifo队列(不是pfifo_fast).它缺乏内部频道但是
可以统计backlog.
最后,你可以进行所谓的"社交整形".你不能通过技术手段解决一切问
题.用户的经验技巧永远是不友善的.正确而友好的措辞可能帮助你的正
确地分配带宽!
42
9.4. 术语
为了正确地理解更多的复杂配置,有必要先解释一些概念.由于这个主题的历史
不长和其本身的复杂性,人们经常在说同一件事的时候使用各种词汇.
以下来自draft-ietf-diffserv-model-06.txt,Diffserv路由器的建议管理模型. 可以
在以下地址找到:

关于这些词语的严格定义请参考这个文档.
队列规定
管理设备输入(ingress)或输出(egress)的一个算法.
无类的队列规定
一个内部不包含可配置子类的队列规定.
分类的队列规定
一个分类的队列规定内可一包含更多的类.其中每个类又进一步地包含一
个队列规定,这个队列规定可以是分类的,也可以是无类的.根据这个定
义,严格地说pfifo_fast算是分类的,因为它实际上包含3个频道(实际上
可以认为是子类).然而从用户的角度来看它是无类的,因为其内部的子
类无法用tc工具进行配置.

一个分类的队列规定可以拥有很多类,类内包含队列规定.
分类器
每个分类的队列规定都需要决定什么样的包使用什么类进行发送.分类器
就是做这个用的.
过滤器
分类是通过过滤器完成的.一个过滤器包含若干的匹配条件,如果符合匹
配条件,就按此过滤器分类.
调度
在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据
包之前发送.这种处理叫做"调度",比如此前提到的pfifo_fast就是这样
的.调度也可以叫做"重排序",但这样容易混乱.
整形
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速
率,这种处理叫做"整形".整形在egress处进行.习惯上,通过丢包来
降速也经常被称为整形.
43

| 队列规定 \__队列规定4__/ |
| \-队列规定N_/ |
| |
+------------------------------------------------------------+
感谢Jamal Hadi Salim制作的ASCII字符图像.
整个大方框表示内核.最左面的箭头表示从网络上进入机器的数据包.它们进入
Ingress队列规定,并有可能被某些过滤器丢弃.即所谓策略.
这些是很早就发生的(在进入内核更深的部分之前).这样早地丢弃数据有利于
节省CPU时间.
数据包顺利通过的话,如果它是发往本地进程的,就会进入IP协议栈处理并提
交给该进程.如果它需要转发而不是进入本地进程,就会发往egress.本地进程
也可以发送数据,交给Egress分类器.
然后经过审查,并放入若干队列规定中的一个进行排队.这个过程叫做"入队".
在不进行任何配置的情况下,只有一个egress队列规定——pfifo_fast——总是接
收数据包.
数据包进入队列后,就等待内核处理并通过某网卡发送.这个过程叫做"出队".
44
这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况.
每块网卡都有它自己的ingress和egress.
9.5. 分类的队列规定
如果你有多种数据流需要进行区别对待,分类的队列规定就非常有用了.众多分
类的队列规定中的一种——CBQ(Class Based Queueing,基于类的队列)——经常
被提起,以至于造成大家认为CBQ就是鉴别队列是否分类的标准,这是不对的.
CBQ不过是家族中最大的孩子而已,同时也是最复杂的.它并不能为你做所有
的事情.对于某些人而言这有些不可思议,因为他们受"sendmail效应"影响较
深,总是认为只要是复杂的并且没有文档的技术肯定是最好的.
9.5.1. 分类的队列规定及其类中的数据流向
一旦数据包进入一个分类的队列规定,它就得被送到某一个类中——也就是需要
分类.对数据包进行分类的工具是过滤器.一定要记住:"分类器"是从队列规
定内部调用的,而不是从别处.
过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排
队.每个子类都可以再次使用它们的过滤器进行进一步的分类.直到不需要进一
步分类时,数据包才进入该类包含的队列规定排队.
除了能够包含其它队列规定之外,绝大多数分类的队列规定黑能够流量整形.这
对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用.如果你用一个
高速网卡(比如以太网卡)连接一个低速设备(比如cable modem或者ADSL modem)
时,也可以应用.
如果你仅仅使用SFQ,那什么用也没有.因为数据包进,出路由器时没有任何延
迟.虽然你的输出网卡远远快于实际连接速率,但路由器中却没有队列可以调度.
9.5.2. 队列规定家族:根,句柄,兄弟和父辈
每块网卡都有一个出口"根队列规定",缺省情况下是前面提到的pfifo_fast队列
规定.每个队列规定都指定一个句柄,以便以后的配置语句能够引用这个队列规
定.除了出口队列规定之外,每块网卡还有一个入口,以便policies进入的数据流.
队列规定的句柄有两个部分:一个主号码和一个次号码.习惯上把根队列规定称
为"1:",等价于"1:0".队列规定的次号码永远是0.
类的主号码必须与它们父辈的主号码一致.
9.5.2.1. 如何用过滤器进行分类
下图给出一个典型的分层关系:
45
12:2
也就是说,根所附带的一个过滤器要求把数据包直接交给12:2.
9.5.2.2. 数据包如何出队并交给硬件
当内核决定把一个数据包发给网卡的时候,根队列规定1:会得到一个出队请求,
然后把它传给1:1,然后依次传给10:,11:和12:,which each query their siblings,
然后试图从它们中进行dequeue()操作.也就是说,内和需要遍历整颗树,因为
只有12:2中才有这个数据包.
换句话说,类及其兄弟仅仅与其"父队列规定"进行交谈,而不会与网卡进行交
谈.只有根队列规定才能由内核进行出队操作!
更进一步,任何类的出队操作都不会比它们的父类更快.这恰恰是你所需要的:
我们可以把SFQ作为一个子类,放到一个可以进行流量整形的父类中,从而能
够同时得到SFQ的调度功能和其父类的流量整形功能.
9.5.3. PRIO队列规定
PRIO队列规定并不进行整形,它仅仅根据你配置的过滤器把流量进一步细分.
你可以认为PRIO队列规定是pfifo_fast的一种衍生物,区别在每个频道都是一
个单独的类,而非简单的FIFO.
当数据包进入PRIO队列规定后,将根据你给定的过滤器设置选择一个类.缺省
情况下有三个类,这些类仅包含纯FIFO队列规定而没有更多的内部结构.你可
以把它们替换成你需要的任何队列规定.
每当有一个数据包需要出队时,首先处理:1类.只有当标号更小的类中没有需要
处理的包时,才会标号大的类.
46
当你希望不仅仅依靠包的TOS,而是想使用tc所提供的更强大的功能来进行数
据包的优先权划分时,可以使用这个队列规定.它也可以包含更多的队列规定,
而pfifo_fast却只能包含简单的fifo队列规定.
因为它不进行整形,所以使用时与SFQ有相同的考虑:要么确保这个网卡的带
宽确实已经占满,要么把它包含在一个能够整形的分类的队列规定的内部.后者
几乎涵盖了所有cable modems和DSL设备.
严格地说,PRIO队列规定是一种Work-Conserving调度.
9.5.3.1. PRIO的参数与使用
tc识别下列参数:
bands
创建频道的数目.每个频道实际上就是一个类.如果你修改了这个数值,
你必须同时修改:
priomap
如果你不给tc提供任何过滤器,PRIO队列规定将参考TC_PRIO的优先
级来决定如何给数据包入队.
它的行为就像前面提到过的pfifo_fast队列规定,关于细节参考前面章节.
频道是类,缺省情况下命名为主标号:1到主标号:3.如果你的PRIO队列规定是
12:,把数据包过滤到12:1将得到最高优先级.
注意:0频道的次标号是1!1频道的次标号是2,以此类推.
9.5.3.2. 配置范例
我们想创建这个树:
root 1: prio
/ | \
1:1 1:2 1:3
| | |
10: 20: 30:
sfq tbf sfq
band 0 1 2
大批量数据使用30:,交互数据使用20:或10:.
命令如下:
# tc qdisc add dev eth0 root handle 1: prio
## 这个命令立即创建了类: 1:1, 1:2, 1:3
# tc qdisc add dev eth0 parent 1:1 handle 10: sfq
# tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
现在,我们看看结果如何:
47
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 132 bytes 2 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 174 bytes 3 pkts (dropped 0, overlimits 0)
如你所见,0频道已经有了一些流量,运行这个命令之后发送了一个包!
现在我们来点大批量数据传输(使用能够正确设置TOS标记的工具):
# scp tc ahu@10.0.0.11:./
ahu@10.0.0.11's password:
tc 100% |*****************************| 353 KB 00:00
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 2230 bytes 31 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 389140 bytes 326 pkts (dropped 0, overlimits 0)
如你所见,所有的流量都是经过30:处理的,优先权最低.现在我们验证一下交
互数据传输经过更高优先级的频道,我们生成一些交互数据传输:
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 14926 bytes 193 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 401836 bytes 488 pkts (dropped 0, overlimits 0)
正常——所有额外的流量都是经10:这个更高优先级的队列规定处理的.与先前
的整个scp不同,没有数据经过最低优先级的队列规定.
9.5.4. 著名的CBQ队列规定
如前所述,CBQ是最复杂,最琐碎,最难以理解,最刁钻的队列规定.这并不
是因为其作者的恶毒或者不称职,而是因为CBQ算法本身的不精确,而且与
Linux的内在机制不协调造成的.
48
除了可以分类之外,CBQ也是一个整形器,但是从表面上看来工作得并不好.
它应该是这样的:如果你试图把一个10Mbps的连接整形成1Mbps的速率,就应
该让链路90%的时间处于闲置状态,必要的话我们就强制,以保证90%的闲置
时间.
但闲置时间的测量非常困难,所以CBQ就采用了它一个近似值——来自硬件层
的两个传输请求之间的毫秒数——来代替它.这个参数可以近似地表征这个链路
的繁忙程度.
这样做相当慎重,而且不一定能够得到正确的结论.比如,由于驱动程序方面或
者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?
由于总线设计的原因,PCMCIA网卡永远也不会达到100Mbps.那么我们该怎么
计算闲置时间呢?
如果我们引入非物理网卡——像PPPoE,PPTP——情况会变得更糟糕.因为相
当一部分有效带宽耗费在了链路维护上.
那些实现了测量的人们都发现CBQ总不是非常精确甚至完全失去了其本来意
义.
但是,在很多场合下它还是能够很好地工作.根据下面的文档,你应该能够较好
地配置CBQ来解决答多数问题.
9.5.4.1. CBQ整形的细节
如前所述,CBQ的工作机制是确认链路的闲置时间足够长,以达到降低链路实
际带宽的目的.为此,它要计算两个数据包的平均发送间隔.
操作期间,有效闲置时间的测量使用EWMA(exponential weighted moving average,
指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按
指数增加.UNIX的平均负载也是这样算出来的.
计算出来的平均时间值减去EWMA测量值,得出的结果叫做"avgidle".最佳的
链路负载情况下,这个值应当是0:数据包严格按照计算出来的时间间隔到来.
在一个过载的链路上,avgidle值应当是负的.如果这个负值太严重,CBQ就会
暂时禁止发包,称为"overlimit"(越限).
相反地,一个闲置的链路应该有很大的avgidle值,这样闲置几个小时后,会造成
链路允许非常大的带宽通过.为了避免这种局面,我们用maxidle来限制avgidle
的值不能太大.
理论上讲,如果发生越限,CBQ就会禁止发包一段时间(长度就是事先计算出来
的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包.但是最
好参照一下下面的minburst参数.
下面是配置整形时需要指定的一些参数:
avpkt
平均包大小,以字节计.计算maxidle时需要,maxidle从maxburst得出.
49
bandwidth
网卡的物理带宽,用来计算闲置时间.
cell
一个数据包被发送出去的时间可以是基于包长度而阶梯增长的.一个800
字节的包和一个806字节的包可以认为耗费相同的时间.也就是说它设置
时间粒度.通常设置为8,必须是2的整数次幂.
maxburst
这个参数的值决定了计算maxidle所使用的数据包的个数.在avgidle跌
落到0之前,这么多的数据包可以突发传输出去.这个值越高,越能够容
纳突发传输.你无法直接设置maxidle的值,必须通过这个参数来控制.
minburst
如前所述,发生越限时CBQ会禁止发包.实现这个的理想方案是根据事
先计算出的闲置时间进行延迟之后,发一个数据包.然而,UNIX的内核
一般来说都有一个固定的调度周期(一般不大于10ms),所以最好是这样:
禁止发包的时间稍长一些,然后突发性地传输minburst个数据包,而不是
一个一个地传输.等待的时间叫做offtime.
从大的时间尺度上说,minburst值越大,整形越精确.但是,从毫秒级的时
间尺度上说,就会有越多的突发传输.
minidle
如果avgidle值降到0,也就是发生了越限,就需要等待,直到avgidle的
值足够大才发送数据包.为避免因关闭链路太久而引起的以外突发传输,
在avgidle的值太低的时候会被强制设置为minidle的值.
参数minidle的值是以负微秒记的.所以10代表avgidle被限制在-10us
上.
mpu
最小包尺寸——因为即使是0长度的数据包,在以太网上也要生成封装成
64字节的帧,而需要一定时间去传输.为了精确计算闲置时间,CBQ需
要知道这个值.
rate
期望中的传输速率.也就是"油门"!
在CBQ的内部由很多的微调参数.比如,那些已知队列中没有数据的类就不参
加计算,越限的类将被惩罚性地降低优先级等等.都非常巧妙和复杂.
9.5.4.2. CBQ在分类方面的行为
除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把
50
各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理.
每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted round
robin,加权轮转)过程,从优先权值小的类开始.
那些队列中有数据的类就会被分组并被请求出队.在一个类收到允许若干字节数
据出队的请求之后,再尝试下一个相同优先权值的类.
下面是控制WRR过程的一些参数:
allot
当从外部请求一个CBQ发包的时候,它就会按照"priority"参数指定的
顺序轮流尝试其内部的每一个类的队列规定.当轮到一个类发数据时,它
只能发送一定量的数据."allot"参数就是这个量的基值.更多细节请参
照"weight"参数.
prio
CBQ可以象PRIO设备那样工作.其中"prio"值较低的类只要有数据就
必须先服务,其他类要延后处理.
weight
"weight"参数控制WRR过程.每个类都轮流取得发包的机会.如果其
中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发
送更多的数据.
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任
意定,只要保持比例合适就可以.人们常把"速率/10"作为参数的值来
使用,实际工作得很好.归一化值后的值乘以"allot"参数后,决定了每
次传输多少数据.
请注意,在一个CBQ内部所有的类都必须使用一致的主号码!
9.5.4.3. 决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪
些类借用或者出借一部分带宽.
Isolated/sharing
凡是使用"isolated"选项配置的类,就不会向其兄弟类出借带宽.如果你
的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选
项.
选项"sharing"是"isolated"的反义选项.
bounded/borrow
一个类也可以用"bounded"选项配置,意味着它不会向其兄弟类借用带
宽.选项"borrow"是"bounded"的反义选项.
51
一个典型的情况就是你的一个链路上有多个客户都设置成了"isolated"和
"bounded",那就是说他们都被限制在其要求的速率之下,且互相之间不会借用
带宽.
在这样的一个类的内部的子类之间是可以互相借用带宽的.
9.5.4.4. 配置范例
这个配置把WEB服务器的流量控制为5Mbps,SMTP流量控制在3Mbps上.而
且二者一共不得超过6Mbps,互相之间允许借用带宽.我们的网卡是100Mbps
的.
# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit \
avpkt 1000 cell 8
# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit \
rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 \
avpkt 1000 bounded
这部分按惯例设置了根为1:0,并且绑定了类1:1.也就是说整个带宽不能超过
6Mbps.
如前所述,CBQ需要调整很多的参数.其实所有的参数上面都解释过了.相应
的HTB配置则要简明得多.
# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit \
rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 \
avpkt 1000
# tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit \
rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 \
avpkt 1000
我们建立了2个类.注意我们如何根据带宽来调整weight参数的.两个类都没
有配置成"bounded",但它们都连接到了类1:1上,而1:1设置了"bounded".
所以两个类的总带宽不会超过6Mbps.别忘了,同一个CBQ下面的子类的主号
码都必须与CBQ自己的号码相一致!
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
# tc qdisc add dev eth0 parent 1:4 handle 40: sfq
缺省情况下,两个类都有一个FIFO队列规定.但是我们把它换成SFQ队列,以
保证每个数据流都公平对待.
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip \
sport 80 0xffff flowid 1:3
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip \
sport 25 0xffff flowid 1:4
这些命令规定了根上的过滤器,保证数据流被送到正确的队列规定中去.
注意:我们先使用了"tc class add" 在一个队列规定中创建了类,然后使用"tc
qdisc add"在类中创建队列规定.
你可能想知道,那些没有被那两条规则分类的数据流怎样处理了呢?从这个例子
来说,它们被1:0直接处理,没有限制.
如果SMTP+web的总带宽需求大于6Mbps,那么这6M带宽将按照两个类的
52
weight参数的比例情况进行分割:WEB服务器得到5/8的带宽,SMTP得到3/8
的带宽.
从这个例子来说,你也可以这么认为:WEB数据流总是会得到
5/8*6Mbps=3.75Mbps的带宽.
9.5.4.5. 其它CBQ参数:split和defmap
如前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个
类去排队.
除了调用过滤器,CBQ还提供了其他方式,defmap和split.很难掌握,但好在
无关大局.但是现在是解释defmap和split的最佳时机,我会尽力解释.
因为你经常是仅仅需要根据TOS来进行分类,所以提供了一种特殊的语法.当
CBQ需要决定了数据包要在哪里入队时,要检查这个节点是否为"split节点".
如果是,子队列规定中的一个应该指出它接收所有带有某种优先权值的数据包,
权值可以来自TOS字段或者应用程序设置的套接字选项.
数据包的优先权位与defmap字段的值进行"或"运算来决定是否存在这样的匹
配.换句话说,这是一个可以快捷创建仅仅匹配某种优先权值数据包的过滤器的
方法.如果defmap等于0xff,就会匹配所有包,0则是不匹配.这个简单的配
置可以帮助理解:
# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 \
cell 8 avpkt 1000 mpu 64
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit \
rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 \
avpkt 1000
一个标准的CBQ前导.
Defmap参照TC_PRIO位(我从来不直接使用数字!):
TC_PRIO.. Num 对应 TOS
-------------------------------------------------
BESTEFFORT 0 最高可靠性
FILLER 1 最低成本
BULK 2 最大吞吐量(0x8)
INTERACTIVE_BULK 4
INTERACTIVE 6 最小延迟(0x10)
CONTROL 7
TC_PRIO..的数值对应它右面的bit.关于TOS位如何换算成优先权值的细节可
以参照pfifo_fast有关章节.
然后是交互和大吞吐量的类:
# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit \
rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 \
avpkt 1000 split 1:0 defmap c0
# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit \
rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 \
avpkt 1000 split 1:0 defmap 3f
53
"split队列规定"是1:0,也就是做出选择的地方.c0是二进制的11000000,3F
是00111111,所以它们共同匹配所有的数据包.第一个类匹配第7和第6位,也
就是负责"交互"和"控制"的数据包.第二个类匹配其余的数据包.
节点1:0现在应该有了这样一个表格:
priority send to
0 1:3
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
为了更有趣,你还可以传递一个"change掩码",确切地指出你想改变哪个优先
权值.你只有在使用了"tc class change"的时候才需要.比如,往1:2中添加best
effort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01
现在,1:0上的优先权分布应该是:
priority send to
0 1:2
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
求助: 尚未测试过"tc class change",资料上这么写的.
9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶)
Martin Devera ()正确地意识到CBQ太复杂,而且并没有按照多数常见情
况进行优化.他的Hierarchical能够很好地满足这样一种情况:你有一个固定速
率的链路,希望分割给多种不同的用途使用.为每种用途做出带宽承诺并实现定
量的带宽借用.
HTB就象CBQ一样工作,但是并不靠计算闲置时间来整形.它是一个分类的令
牌桶过滤器.它只有很少的参数,并且在它的网站能够找到很好的文档.
随着你的HTB配置越来越复杂,你的配置工作也会变得复杂.但是使用CBQ的
话,即使在很简单的情况下配置也会非常复杂!HTB3 (关于它的版本情况,请参
阅它的网站)已经成了官方内核的一部分(2.4.20-pre1,2.5.31及其后).然而,你
可能仍然要为你的tc命令打上HTB3支持补丁,否则你的tc命令不理解HTB3.
如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用HTB.
54
9.5.5.1. 配置范例
环境与要求与上述CBQ的例子一样.
# tc qdisc add dev eth0 root handle 1: htb default 30
# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k
作者建议2在那些类的下方放置SFQ:
# tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
# tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
# tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
添加过滤器,直接把流量导向相应的类:
# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20
这就完了——没有没见过的或者没解释过的数字,没有不明意义的参数.
HTB完成得相当不错——如果10:和20:都得到了保证的速率,剩下的就是分割
了,它们借用的比率是5:3,正如你其网的那样.
未被分类的流量被送到了30:,仅有一点点带宽,但是却可以任意借用剩下的带
宽.因为我们内部使用了SFQ,而可以公平发包.
9.6. 使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的"分类器链" 进行选择.这个
链中包含了这个分类队列规定所需的所有过滤器.
重复前面那棵树:
根1:
|
_1:1_
/ | \
/ | \
/ | \
10: 11: 12:
/ \ / \
10:1 10:2 12:1 12:2
当一个数据包入队的时候,每一个分支处都会咨询过滤器链如何进行下一步.典
型的配置是在1:1处有一个过滤器把数据包交给12:,然后12:处的过滤器在把包
交给12:2.
你可以把后一个过滤器同时放在1:1处,可因为…having more specific tests lower
in the chain.…而得到效率的提高.
55
另外,你不能用过滤器把数据包向"上"送.而且,使用HTB的时候应该把所
有的规则放到根上!
再次强调:数据包只能向"下"进行入队操作!只有处队的时候才会上到网卡所
在的位置来.他们不会落到树的最底层后送到网卡!
9.6.1. 过滤器的一些简单范例
就象在"分类器"那章所解释的,借助一些复杂的语法你可以详细地匹配任何事
情.下面我们就开始,从简单地匹配一些比较明显的特征开始.
比方说,我们有一个PRIO队列规定,叫做"10:",包含3个类,我们希望把去
往22口的数据流发送到最优先的频道中去.应该这样设置过滤器:
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 \
match ip dport 22 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 \
match ip sport 80 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2
什么意思呢?是说:
向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是去往22口(精
确匹配)的IP数据包,发送到频道10:1.
向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是来自80口(精
确匹配)的IP数据包,发送到频道10:1.
向eth0上的10:节点添加一个过滤规则,它的优先权是2:凡是上面未匹配的IP
数据包,发送到频道10:2.
别忘了添加"dev eth0"(你的网卡或许叫别的名字),因为每个网卡的句柄都有
完全相同的命名空间.
想通过IP地址进行筛选的话,这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 \
match ip dst 4.3.2.1/32 flowid 10:1
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 \
match ip src 1.2.3.4/32 flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 \
flowid 10:2
这个例子把去往4.3.2.1和来自1.2.3.4的数据包送到了最高优先的队列,其它的
则送到次高权限的队列.
你可以连续使用match,想匹配来自1.2.3.4的80口的数据包的话,就这么敲:
56
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 match ip
sport 80 0xffff flowid 10:1
9.6.2. 常用到的过滤命令一览
这里列出的绝大多数命令都根据这个命令改编而来:
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 ……
这些是所谓的"u32"匹配,可以匹配数据包的任意部分.
根据源/目的地址
源地址段 'match ip src 1.2.3.0/24'
目的地址段 'match ip dst 4.3.2.0/24'
单个IP地址使用"/32"作为掩码即可.
根据源/目的端口,所有IP协议
源 'match ip sport 80 0xffff'
目的 'match ip dport 80 0xffff'
根据IP协议 (tcp, udp, icmp, gre, ipsec)
使用/etc/protocols所指定的数字.
比如: icmp是1:'match ip protocol 1 0xff'.
根据fwmark
你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过
网卡的路由过程中保留下来.如果你希望对来自eth0并从eth1发出的数
据包做整形,这就很有用了.语法是这样的:
t
c filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1
注意,这不是一个u32匹配!
你可以象这样给数据包打标记:
#
iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
数字6是可以任意指定的.
如果你不想去学习所有的tc语法,就可以与iptables结合,仅仅学习按
fwmark匹配就行了.
按TOS字段
选择交互和最小延迟的数据流:
#
tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 \
57
m
atch ip tos 0x10 0xff flowid 1:4
想匹配大量传输的话,使用"0x08 0xff".
关于更多的过滤命令,请参照"高级过滤"那一章.
9.7. IMQ(Intermediate queueing device,中介队列
设备)
中介队列设备不是一个队列规定,但它的使用与队列规定是紧密相连的.就Linux
而言,队列规定是附带在网卡上的,所有在这个网卡上排队的数据都排进这个队
列规定.根据这个概念,出现了两个局限:
1. 只能进行出口整形(虽然也存在入口队列规定,但在上面实现分类的队列规定
的可能性非常小).
2. 一个队列规定只能处理一块网卡的流量,无法设置全局的限速.
IMQ就是用来解决上述两个局限的.简单地说,你可以往一个队列规定中放任
何东西.被打了特定标记的数据包在netfilter的NF_IP_PRE_ROUTING 和
NF_IP_POST_ROUTING两个钩子函数处被拦截,并被送到一个队列规定中,该
队列规定附加到一个IMQ设备上.对数据包打标记要用到iptables的一种处理方
法.
这样你就可以对刚刚进入网卡的数据包打上标记进行入口整形,或者把网卡们当
成一个个的类来看待而进行全局整形设置.你还可以做很多事情,比如:把http
流量放到一个队列规定中去,把新的连接请求放到一个队列规定中去,……
9.7.1. 配置范例
我们首先想到的是进行入口整形,以便让你自己得到高保证的带宽 .就象配置
其它网卡一样:
tc qdisc add dev imq0 root handle 1: htb default 20
tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit
tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
tc qdisc add dev imq0 parent 1:20 handle 20: sfq
tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match \
ip dst 10.0.0.230/32 flowid 1:10
在这个例子中,使用了u32进行分类.其它的分类器应该也能实现.然后,被打
上标记的包被送到imq0排队.
iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0
58
ip link set imq0 up
iptables的IMQ处理方法只能用在PREROUTING和POSTROUTING链的mangle
表中.语法是:
IMQ [ --todev n ]
n: imq设备的编号
注:ip6tables也提供了这种处理方法.
请注意,如果数据流是事后才匹配到IMQ处理方法上的,数据就不会入队.数
据流进入imq的确切位置取决于这个数据流究竟是流进的还是流出的.下面是
netfilter(也就是iptables)在内核中预先定义优先级:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
};
对于流入的包,imq把自己注册为优先权等于NF_IP_PRI_MANGLE+1,也就是
说数据包在经过了PREROUTING链的mangle表之后才进入imq设备.
对于流出的包,imq使用优先权等于NF_IP_PRI_LAST,也就是说不会白白处理
本应该被filter表丢弃的数据包.
关于补丁和更多的文档请参阅imq网站.
59
第10章 多网卡的负载均衡
有多种手段实现这个功能.最简单,最直接的方法之一就是"TEQL"——真(或
"普通的")链路均衡.就象用队列实现的大多数事情一样,负载均衡也需要双
向实现.链路的两端都要参与,才有完整的效果.
想象下列情况:
+-------+ eth1 +-------+
| |==========| |
"网络1" ------| A | | B |---- '网络 2'
| |==========| |
+-------+ eth2 +-------+
A和B是路由器,我们当然假定它们全是Linux机器.如果从网络1发往网络2
的流量需要A路由器同时使用两条链路发给B路由器.B路由器需要进行配置
以便适应这种情况.反向传输时也一样,当数据包从网络2发往网络1时,B路
由器同时使用eth1和eth2.
分配的功能是用"TEQL"设备实现的,象这样(没有比这更简单的了):
# tc qdisc add dev eth1 root teql0
# tc qdisc add dev eth2 root teql0
# ip link set dev teql0 up
别忘了"ip link set up"命令!
这在两台机器上都要做.teql0设备基本上是在eth1和eth2之间进行轮转发帧.
用源也不会有数据从teql设备上进来,只是出现在原来的eth1和eth2上.
我们现在有了网络设备,还需要有合适的路由.方法之一就是给两个链路分配一
个/31的网络,teql0也一样:
在A路由器上:
# ip addr add dev eth1 10.0.0.0/31
# ip addr add dev eth2 10.0.0.2/31
# ip addr add dev teql0 10.0.0.4/31
在B路由器上:
# ip addr add dev eth1 10.0.0.1/31
# ip addr add dev eth2 10.0.0.3/31
# ip addr add dev teql0 10.0.0.5/31
A路由器现在应该能够通过2个真实链路和一个均衡网卡ping通10.0.0.1,
10.0.0.3和10.0.0.5.B路由器应该能够ping通10.0.0.0,10.0.0.2和10.0.0.4.
如果成功的话,A路由器应该把10.0.0.5作为到达网络2的路由,B路由器应该
把10.0.0.4作为去往网络1的路由.在网络1是你家里的网络,而网络2是Internet
这种特定场合下,A路由器的缺省网关应该设为10.0.0.5.
60
/proc/sys/net/ipv4/conf/eth2/rp_filter
包的乱序也是一个大问题.比如,有6个数据包需要从A发到B,eth1可能分到
第1,3,5个包,而eth2分到第2,4,6个.在理想情况下,B路由器会按顺
序收到第1,2,3,4,5,6号包.但实际上B路由器的内核很可能按照类似2,
1,4,3,6,5这样的随机顺序收到包.这个问题会把TCP/IP搞糊涂.虽然在
链路上承载不同的TCP/IP会话并没有问题,但你无法通过捆绑多个链路来增加
一个ftp文件的下载速度,除非两端的操作系统都是Linux,因为Linux的TCP/IP
协议栈不那么容易被这种简单的乱序问题所蒙蔽.
当然,对于大多数应用系统来说,链路的负载均衡是一个好主意.
10.2. 其它可能性
William Stearns已经利用高级隧道来达到捆绑多重Internet连接的效果.可以在
他的隧道网页找到.
本HOWTO将来可能更多地描述这个问题.
61
/etc/iproute2/rt_tables
# ip rule add fwmark 1 table mail.out
# ip rule ls
0:
f
rom all lookup local
32764:
f
rom all fwmark 1 lookup mail.out
32766:
f
rom all lookup main
32767:
f
rom all lookup default
现在我们建立一个通往那条便宜链路的路由,从而生成mail.out路由表:
# /sbin/ip route add default via 195.96.98.253 dev ppp0 table mail.out
这就做完了.我们可能需要一些例外,有很多方法都能达到目的.我们可以修改
netfilter命令来排除一些主机,也可以插入一些优先权值更低的规则把需要排除
的主机的数据包发往main路由表.
我们还可以通过识别数据包的TOS位,来给不同服务类型的数据包打上不同的
标记,再为它们分别建立规则.你甚至可以利用这种方法让诸如ISDN线路支持
交互业务.
62
不用说,这当然也可以用于正在进行NAT("伪装")的机器上.
重要提醒:我们收到报告说MASQ和SNAT功能与数据包标记有冲突.Rusty
Russell在这个帖子中作了解释.关闭反方向的过滤就可以正常工作.
注意:想给数据包打标记的话,你的内和需要一些配置:
IP: advanced router (CONFIG_IP_ADVANCED_ROUTER) [Y/n/ ]
IP: policy routing (CONFIG_IP_MULTIPLE_TABLES) [Y/n/ ]
IP: use netfilter MARK value as routing key (CONFIG_IP_ROUTE_FWMARK) [Y/n/ ]
参考方便菜谱一章中的15.5.
63
第12章 对包进行分类的高级过滤器
就象在分类的队列规定一段中解释的,过滤器用与把数据包分类并放入相应的子
队列.这些过滤器在分类的队列规定内部被调用.
下面就是我们可用的分类器(部分):
fw
根据防火墙如何对这个数据包做标记进行判断.如果你不想学习tc的过
滤器语法,这倒是一个捷径.细节请参见队列那一章.
u32
根据数据包中的各个字段进行判断,如源IP地址等等.
route
根据数据包将被哪条路由进行路由来判断.
rsvp, rsvp6
根据数据包的RSVP情况进行判断.只能用于你自己的网络,互联网并不
遵守RSVP.
tcindex
用于DSMARK队列规定,参见相关章节.
通常来说,总有很多途径可实现对数据包分类,最终取决于你喜欢使用哪种系统.
分类器一般都能接受几个参数,为了方便我们列出来:
protocol
这个分类器所接受的协议.一般来说你只会接受IP数据.必要参数.
parent
这个分类器附带在哪个句柄上.句柄必须是一个已经存在的类.必要参数.
prio
这个分类器的优先权值.优先权值低的优先.
handle
对于不同过滤器,它的意义不同.
后面所有的节都假定你试图对去往HostA的流量进行整形.并且假定根类配置为
1:,并且你希望把选中的数据包送给1:1类.
64
12.1. u32分类器
U32分类器是当前实现中最先进的过滤器.全部基于哈希表实现,所以当有很多
过滤器的时候仍然能够保持健壮.
U32过滤器最简单的形式就是一系列记录,每条记录包含两个部分:一个选择器
和一个动作.下面要讲的选择器用来与IP包相匹配,一旦成功匹配就执行其指
定的动作.最简单的动作就是把数据包发送到特定的类队列.
用来配置过滤器的tc命令行由三部分组成:过滤器说明,选择器和动作.一个
过滤器可以如下定义:
tc filter add dev IF [ protocol PROTO ]
[ (preference|priority) PRIO ]
[ parent CBQ ]
上面行中,protocol字段描述了过滤器要匹配的协议.我们将只讨论IP协议的情
况.preference字段(也可以用priority代替)设置该过滤器的优先权.这非常重要,
因为你可能有几条拥有不同优先权的过滤器.每个过滤器列表都按照输入的顺序
被扫描一遍,然后优先权值低(更高的偏好值)的列表优先被处理."parent"字段
定义了过滤器所属的CBQ的顶部(如1:0).
上面描述的选项适用于所有过滤器,而不仅仅适用于U32.
12.1.1. U32选择器
u32选择器包含了能够对当前通过的数据包进行匹配的特征定义.它其实只是定
义了IP包头中某些位的匹配而已,但这种看似简单的方法却非常有效.让我们
看看这个从实际应用的系统中抄来的例子:
# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00100000 00ff0000 at 0 flowid 1:10
现在,命令的第一行已经不用解释了,前面都说过了.我们把精力集中在用
"match"选项描述选择器的第二行.这个选择器将匹配那些IP头部的第二个字
节是0x10的数据包.你应该猜到了,00ff就是匹配掩码,确切地告诉过滤器应
该匹配哪些位.在这个例子中是0xff,所以会精确地匹配这个字节是否等于0x10.
"at"关键字的意思是指出从数据包的第几个字节开始匹配——本例中是从数据
包的开头开始.完全地翻译成人类语言就是:"匹配那些TOS字段带有'最小延
迟'属性的数据包".让我们看看另一个例子:
# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00000016 0000ffff at nexthdr+0 flowid 1:10
"nexthdr"选项意味着封装在IP包中的下一个PDU的头部,也就是说它的上层
协议的头.匹配操作就是从这个协议的头部开始的,应该发生在头部开始的第
16位处.在TCP和UDP协议的头部,这个部分存放的是这个报文的目标端口.
数字是按照先高厚低的格式存储的,所以0x0016就是十进制的22(如果是TCP
的话就是ssh服务).其实,这个匹配在没有上下文的情况下含义很模糊,我们放
65
在后面讨论.
理解了上面的例子之后,下面这条选择器就很好懂了:
match c0a80100 ffffff00 at 16
表示了:匹配从IP头开始数的第17个字节到第19个字节.这个选择器将匹配
所有去往192.168.1.0/24的数据包.成功分析完上面这个例子后,我们就已经掌
握u32选择器了.
12.1.2. 普通选择器
普通选择器定义了要对数据包进行匹配的特征,掩码和偏移量.使用普通选择器,
你实际上可以匹配IP(或者上层协议)头部的任意一个bit,虽然这样的选择器比特
殊选择器难读和难写.一般选择器的语法是:
match [ u32 | u16 | u8 ] PATTERN MASK [ at OFFSET | nexthdr+OFFSET]
利用u32,u16或u8三个关键字中的一个来指明特征的bit数.然后PATTERN
和MASK应该按照它定义的长度紧挨着写.OFFSET参数是开始进行比较的偏
移量(以字节计).如果给出了"nexthdr+"关键字,偏移量就移到上层协议头部
开始的位置.
一些例子:
# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
match u8 64 0xff at 8 \
flowid 1:4
如果一个数据包的TTL值等于64,就将匹配这个选择器.TTL就位于IP包头的
第9个字节.
匹配带有ACK位的TCP数据包:
# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x10 0xff at nexthdr+13 \
flowid 1:3
用这个匹配小于64字节的ACK包:
## match acks the hard way,
## IP protocol 6,
## IP header length 0x5(32 bit words),
## IP Total length 0x34 (ACK + 12 bytes of TCP options)
## TCP ack set (bit 5, offset 33)
# tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x05 0x0f at 0 \
match u16 0x0000 0xffc0 at 2 \
match u8 0x10 0xff at 33 \
flowid 1:3
这个规则匹配了带有ACK位,且没有载荷的TCP数据包.这里我们看见了同时
使用两个选择器的例子,这样用的结果是两个条件进行逻辑"与"运算.如果我
们查查TCP头的结构,就会知道ACK标志位于第14个字节的第5个bit(0x10).
66
作为第二个选择器,如果我们采用更复杂的表达,可以写成"match u8 0x06 0xff
at 9",而不是使用特殊选择器protocol,因为TCP的协议号是6(写在IP头的第
十个字节).另一方面,在这个例子中我们不使用特殊选择器也是因为没有用来
匹配TCP的ACK标志的特殊选择器.
下面这个选择器是上面选择器的改进版,区别在于它不检查IP头部的长度.为
什么呢?因为上面的过滤器只能在32位系统上工作.
tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x10 0xff at nexthdr+13 \
match u16 0x0000 0xffc0 at 2 \
flowid 1:3
12.1.3. 特殊选择器
下面的表收入了本节文档的作者从tc程序的源代码中找出的所有特殊选择器.
它们能够让你更容易,更可靠地配置过滤器.
求助: table placeholder - the table is in separate file ,,selector.html''
求助: it's also still in Polish :-(
求助: must be sgml'ized
一些范例:
# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
match ip tos 0x10 0xff \
flowid 1:4
求助: tcp dport match does not work as described below:
上述规则匹配那些TOS字段等于0x10的数据包.TOS字段位于数据包的第二个
字节,所以与值等价的普通选择器就是:"match u8 0x10 0xff at 1".这其实给了
我们一个关于U32过滤器的启示:特殊选择器全都可以翻译成等价的普通选择
器,而且在内核的内存中,恰恰就是按这种方式存储的.这也可以导出另一个结
论:tcp和udp的选择器实际上是完全一样的,这也就是为什么不能仅用"match
tcp dport 53 0xffff"一个选择器去匹配发到指定端口的TCP包,因为它也会匹配
送往指定端口的UDP包.你一定不能忘了还得匹配协议类型,按下述方式来表
示:
# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
match tcp dport 53 0xffff \
match ip protocol 0x6 0xff \
flowid 1:2
12.2. 路由分类器
这个分类器过滤器基于路由表的路由结果.当一个数据包穿越一个类,并到达一
个标有"route"的过滤器的时候,它就会按照路由表内的信息进行分裂.当一个
67
数据包遍历类,并到达一个标记"路由"过滤器的时候,就会按照路由表的相应
信息分类.
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 route
我们向节点1:0里添加了一个优先级是100的路由分类器.当数据包到达这个节
点时,就会查询路由表,如果匹配就会被发送到给定的类,并赋予优先级100.
要最后完成,你还要添加一条适当的路由项:
这里的窍门就是基于目的或者源地址来定义"realm".象这样做:
# ip route add Host/Network via Gateway dev Device realm RealmNumber
例如,我们可以把目标网络192.168.10.0定义为realm 10:
# ip route add 192.168.10.0/24 via 192.168.10.1 dev eth1 realm 10
我们再使用路由过滤器的时候,就可以用realm号码来表示网络或者主机了,并
可以用来描述路由如何匹配过滤器:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
route to 10 classid 1:10
这个规则说:凡是去往192.168.10.0子网的数据包匹配到类1:10.
路由过滤器也可以用来匹配源策略路由.比如,一个
Linux路由器的eth2上连接
了一个子网:
# ip route add 192.168.2.0/24 dev eth2 realm 2
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
route from 2 classid 1:2
这个规则说:凡是来自192.168.2.0子网(realm 2)的数据包,匹配到1:2.
12.3. 管制分类器
为了能够实现更复杂的配置,你可以通过一些过滤器来匹配那些达到特定带宽的
数据包.你可以声明一个过滤器来来按一定比率抑制传输速率,或者仅仅不匹配
那些超过特定速率的数据包.
如果现在的流量是5M,而你想把它管制在4M,那么你要么可以停止匹配整个
的5M带宽,要么停止匹配1M带宽,来给所配置的类进行4M速率的传输.
如果带宽超过了配置的速率,你可以丢包,可以重新分类或者看看是否别的过滤
器能匹配它.
12.3.1. 管制的方式
有两种方法进行管制.
如果你编译内核的时候加上了"Estimators",内核就可以替你为每一个过滤器测
量通过了多少数据,多了还是少了.这些评估对于CPU来讲非常轻松,它只不
过是每秒钟累计25次通过了多少数据,计算出速率.
68
另一种方法是在你的过滤器内部,通过TBF(令牌桶过滤器)来实现.TBF只匹配
到达到你配置带宽的数据流,超过的部分则按照事先指定的"越限动作"来处理.
12.3.1.1. 靠内核评估
这种方式非常简单,只有一个参数"avrate".所有低于avrate的数据包被保留,
并被过滤器分到所指定的类中去,而那些超过了avrate的数据包则按照越限动作
来处理,缺省的越限动作是"reclassify"(重分类).
内核使用EWMA算法来核算带宽,以防止对瞬时突发过于敏感.
12.3.1.2. 靠令牌桶过滤器
使用下列参数:
buffer/maxburst
mtu/minburst
mpu
rate
它们的意义与前面介绍TBF时所说的完全一样.但仍然要指出的是:如果把一
个TBF管制器的mtu参数设置过小的话,将没有数据包通过,whereas the egress
TBF qdisc will just pass them slower.
另一个区别是,管制器只能够通过或者丢弃一个数据包,而不能因为为了延迟而
暂停发送.
12.3.2. 越限动作
如果你的过滤器认定一个数据包越限,就会按照"越限动作"来处理它.当前,
支持三种动作:
continue
让这个过滤器不要匹配这个包,但是其它的过滤器可能会匹配它.
drop
这是个非常强硬的选项——让越限的数据包消失.它的用途不多,经常被
用于ingress管制.比如,你的DNS在请求流量大于5Mbps的时候就会失
灵,你就可以利用这个来保证请求量不会超标.
Pass/OK
让数据包通过.可用于避免复杂的过滤器,但要放在合适的地方.
reclassify
69
最经常用于对数据包进行重分类以达到最好效果.这是缺省动作.
12.3.3. 范例
现在最真实的范例就是下面第十五章提到的"防护SYN洪水攻击".
求助: if you have used this, please share your experience with us
12.4. 当过滤器很多时如何使用散列表
如果你需要使用上千个规则——比如你有很多需要不同QoS的客户机——你可
能会发现内核花了很多时间用于匹配那些规则.
缺省情况下,所有的过滤器都是靠一个链表来组织的,链表按priority的降序排
列.如果你有1000个规则,那么就有可能需要1000次匹配来决定一个包如何处
理.
而如果你有256个链表,每个链表4条规则的话,这个过程可以更快.也就是说
如果你能把数据包放到合适的链表上,可能只需要匹配4次就可以了.
利用散列表可以实现.比如说你有1024个用户使用一个Cable MODEM,IP地
址范围是1.2.0.0到1.2.3.255,每个IP都需要不同容器来对待,比如"轻量级",
"中量级"和"重量级".你可能要写1024个规则,象这样:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.0 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.1 classid 1:1
...
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.3.254 classid 1:3
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.3.255 classid 1:2
为了提高效率,我们应该利用IP地址的后半部分作为散列因子,建立256个散
列表项.第一个表项里的规则应该是这样:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.0 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.1.0 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.2.0 classid 1:3
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.3.0 classid 1:2
下一个表项应该这么开始:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.1 classid 1:1
...
这样的话,最坏情况下也只需要4次匹配,平均2次.
70
具体配置有些复杂,但是如果你真有很多规则的话,还是值得的.
我们首先生成root过滤器,然后创建一个256项的散列表:
# tc filter add dev eth1 parent 1:0 prio 5 protocol ip u32
# tc filter add dev eth1 parent 1:0 prio 5 handle 2: protocol ip u32 divisor 256
然后我们向表项中添加一些规则:
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.0.123 flowid 1:1
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.1.123 flowid 1:2
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.3.123 flowid 1:3
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.4.123 flowid 1:2
这是第123项,包含了为1.2.0.123,1.2.1.123,1.2.2.123和1.2.3.123准备的匹配
规则,分别把它们发给1:1,1:2,1:3和1:2.注意,我们必须用16进制来表示
散列表项,0x7b就是123.
然后创建一个"散列过滤器",直接把数据包发给散列表中的合适表项:
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 800:: \
match ip src 1.2.0.0/16 \
hashkey mask 0x000000ff at 12 \
link 2:
好了,有些数字需要解释.我们定义散列表叫做"800:",所有的过滤都从这里
开始.然后我们选择源地址(它们位于IP头的第12,13,14,15字节),并声明
我们只对它的最后一部分感兴趣.这个例子中,我们发送到了前面创建的第2个
散列表项.
这比较复杂,然而实际上确实有效而且性能令人惊讶.注意,这个例子我们也可
以处理成理想情况——每个表项中只有一个过滤器!
71
/proc/sys/net/ipv4/conf//log_martians
求助: is setting the conf/[default,all]/* files enough - martijn
72
13.2. 深层设置
有很多参数可以修改.我们希望能够全列出来.在Documentation/ip-sysctl.txt中
也有部分记载.
这些设置中的部分缺省值取决于你在内核配置时是否选择了"Configure as router
and not host".
Oskar Andreasson也有一个网页比我们讨论得更详细的网页:

13.2.1. ipv4一般设置
作为一个一般性的提醒,多数速度限制功能都不对loopback起作用,所以不要
进行本地测试.限制是由"jiffies" 来提供的,并强迫使用前面提到过的TBF.
内核内部有一个时钟,每秒钟发生"HZ"个jiffies(滴嗒).在Intel平台上,HZ
的值一般都是100.所以设置*_rate文件的时候,如果是50,就意味着每秒允许
2个包.TBF配置成为如果有多余的令牌就允许6个包的突发.
下面列表中的一些条目摘录自 Alexey Kuznetsov kuznet@ms2.inr.ac.ru和Andi
Kleen ak@muc.de写的/usr/src/linux/Documentation/networking/ip-sysctl.txt.
/proc/sys/net/ipv4/icmp_destunreach_rate
一旦内核认为它无法发包,就会丢弃这个包,并向发包的主机发送ICMP
通知.
/proc/sys/net/ipv4/icmp_echo_ignore_all
根本不要响应echo包.请不要设置为缺省,它可能在你正被利用成为DoS
攻击的跳板时可能有用.
/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts [Useful]
如果你ping子网的子网地址,所有的机器都应该予以回应.这可能成为
非常好用的拒绝服务攻击工具.设置为1来忽略这些子网广播消息.
/proc/sys/net/ipv4/icmp_echoreply_rate
设置了向任意主机回应echo请求的比率.
/proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
设置它之后,可以忽略由网络中的那些声称回应地址是广播地址的主机生
成的ICMP错误.
/proc/sys/net/ipv4/icmp_paramprob_rate
一个相对不很明确的ICMP消息,用来回应IP头或TCP头损坏的异常数
据包.你可以通过这个文件控制消息的发送比率.
73
/proc/sys/net/ipv4/icmp_timeexceed_rate
这个在traceroute时导致著名的"Solaris middle star".这个文件控制发送
ICMP Time Exceeded消息的比率.
/proc/sys/net/ipv4/igmp_max_memberships
主机上最多有多少个igmp (多播)套接字进行监听.
求助: Is this true
/proc/sys/net/ipv4/inet_peer_gc_maxtime
求助: Add a little explanation about the inet peer storage Minimum interval
between garbage collection passes. This interval is in effect under low (or
absent) memory pressure on the pool. Measured in jiffies.
/proc/sys/net/ipv4/inet_peer_gc_mintime
每一遍碎片收集之间的最小时间间隔.当内存压力比较大的时候,调整这
个间隔很有效.以jiffies计.
/proc/sys/net/ipv4/inet_peer_maxttl
entries的最大生存期.在pool没有内存压力的情况下(比如,pool中entries
的数量很少的时候),未使用的entries经过一段时间就会过期.以jiffies
计.
/proc/sys/net/ipv4/inet_peer_minttl
entries的最小生存期.应该不小于汇聚端分片的生存期.当pool的大小
不大于inet_peer_threshold时,这个最小生存期必须予以保证.以jiffies
计.
/proc/sys/net/ipv4/inet_peer_threshold
The approximate size of the INET peer storage. Starting from this threshold
entries will be thrown aggressively. This threshold also determines entries'
time-to-live and time intervals between garbage collection passes. More
entries, less time-to-live, less GC interval.
/proc/sys/net/ipv4/ip_autoconfig
这个文件里面写着一个数字,表示主机是否通过RARP,BOOTP,DHCP
或者其它机制取得其IP配置.否则就是0.
/proc/sys/net/ipv4/ip_default_ttl
数据包的生存期.设置为64是安全的.如果你的网络规模巨大就提高这
个值.不要因为好玩而这么做——那样会产生有害的路由环路.实际上,
在很多情况下你要考虑能否减小这个值.
/proc/sys/net/ipv4/ip_dynaddr
74
如果你有一个动态地址的自动拨号接口,就得设置它.当你的自动拨号接
口激活的时候,本地所有没有收到答复的TCP套接字会重新绑定到正确
的地址上.这可以解决引发拨号的套接字本身无法工作,重试一次却可以
的问题.
/proc/sys/net/ipv4/ip_forward
内核是否转发数据包.缺省禁止.
/proc/sys/net/ipv4/ip_local_port_range
用于向外连接的端口范围.缺省情况下其实很小:1024到4999.
/proc/sys/net/ipv4/ip_no_pmtu_disc
如果你想禁止"沿途MTU发现"就设置它."沿途MTU发现"是一种技
术,可以在传输路径上检测出最大可能的MTU值.参见Cookbook一章
中关于"沿途MTU发现"的内容.
/proc/sys/net/ipv4/ipfrag_high_thresh
用于IP分片汇聚的最大内存用量.分配了这么多字节的内存后,一旦用
尽,分片处理程序就会丢弃分片.When ipfrag_high_thresh bytes of memory
is allocated for this purpose, the fragment handler will toss packets until
ipfrag_low_thresh is reached.
/proc/sys/net/ipv4/ip_nonlocal_bind
如果你希望你的应用程序能够绑定到不属于本地网卡的地址上时,设置这
个选项.如果你的机器没有专线连接(甚至是动态连接)时非常有用,即使
你的连接断开,你的服务也可以启动并绑定在一个指定的地址上.
/proc/sys/net/ipv4/ipfrag_low_thresh
用于IP分片汇聚的最小内存用量.
/proc/sys/net/ipv4/ipfrag_time
IP分片在内存中的保留时间(秒数).
/proc/sys/net/ipv4/tcp_abort_on_overflow
一个布尔类型的标志,控制着当有很多的连接请求时内核的行为.启用的
话,如果服务超载,内核将主动地发送RST包.
/proc/sys/net/ipv4/tcp_fin_timeout
如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态
的时间.对端可以出错并永远不关闭连接,甚至意外当机.缺省值是60
秒.2.2内核的通常值是180秒,你可以按这个设置,但要记住的是,即
使你的机器是一个轻载的WEB服务器,也有因为大量的死套接字而内存
溢出的风险,FIN-WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只
能吃掉1.5K内存,但是它们的生存期长些.参见tcp_max_orphans.
75
/proc/sys/net/ipv4/tcp_keepalive_time
当keepalive起用的时候,TCP发送keepalive消息的频度.缺省是2小时.
/proc/sys/net/ipv4/tcp_keepalive_intvl
当探测没有确认时,重新发送探测的频度.缺省是75秒.
/proc/sys/net/ipv4/tcp_keepalive_probes
在认定连接失效之前,发送多少个TCP的keepalive探测包.缺省值是9.
这个值乘以tcp_keepalive_intvl之后决定了,一个连接发送了keepalive之
后可以有多少时间没有回应.
/proc/sys/net/ipv4/tcp_max_orphans
系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上.
如果超过这个数字,孤儿连接将即刻被复位并打印出警告信息.这个限制
仅仅是为了防止简单的DoS攻击,你绝对不能过分依靠它或者人为地减
小这个值,更应该增加这个值(如果增加了内存之后).This limit exists only
to prevent simple DoS attacks, you _must_ not rely on this or lower the limit
artificially, but rather increase it (probably, after increasing installed memory),
if network conditions require more than default value, and tune network
services to linger and kill such states more aggressively. 让我再次提醒你:每
个孤儿套接字最多能够吃掉你64K不可交换的内存.
/proc/sys/net/ipv4/tcp_orphan_retries
本端试图关闭TCP连接之前重试多少次.缺省值是7,相当于50秒~16
分钟(取决于RTO).如果你的机器是一个重载的WEB服务器,你应该考
虑减低这个值,因为这样的套接字会消耗很多重要的资源.参见
tcp_max_orphans.
/proc/sys/net/ipv4/tcp_max_syn_backlog
记录的那些尚未收到客户端确认信息的连接请求的最大值.对于有128M
内存的系统而言,缺省值是1024,小内存的系统则是128.如果服务器不
堪重负,试试提高这个值.注意!如果你设置这个值大于1024,最好同
时调整include/net/tcp.h中的TCP_SYNQ_HSIZE,以保证
TCP_SYNQ_HSIZE*16 ≤tcp_max_syn_backlo,然后重新编译内核.
/proc/sys/net/ipv4/tcp_max_tw_buckets
系统同时保持timewait套接字的最大数量.如果超过这个数字,time-wait
套接字将立刻被清除并打印警告信息.这个限制仅仅是为了防止简单的
DoS攻击,你绝对不能过分依靠它或者人为地减小这个值,如果网络实际
需要大于缺省值,更应该增加这个值(如果增加了内存之后).
/proc/sys/net/ipv4/tcp_retrans_collapse
为兼容某些糟糕的打印机设置的"将错就错"选项.再次发送时,把数据
包增大一些,来避免某些TCP协议栈的BUG.
76
/proc/sys/net/ipv4/tcp_retries1
在认定出错并向网络层提交错误报告之前,重试多少次.缺省设置为RFC
规定的最小值:3,相当于3秒~8分钟(取决于RIO).
/proc/sys/net/ipv4/tcp_retries2
在杀死一个活动的TCP连接之前重试多少次.RFC 1122规定这个限制应
该长于100秒.这个值太小了.缺省值是15,相当于13~30分钟(取决
于RIO).
/proc/sys/net/ipv4/tcp_rfc1337
这个开关可以启动对于在RFC1337中描述的"tcp的time-wait暗杀危机"
问题的修复.启用后,内核将丢弃那些发往ti
()
阅读(4793) | 评论(0) | 转发(0) |
0

上一篇:Netfilter源码分析--4、FILTER

下一篇:gdb相关

给主人留下些什么吧!~~