如何在小程序调用接口中调用LINUX接口实现等价路由选择

Linux的高级路由和流量控制HOWTO(2)
==> 查看信息
Linux的高级路由和流量控制HOWTO(2)
第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. 从手动密钥管理开始
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 "3456";
意思是:“从10.0.0.11到10.0.0.216的数据包需要AH,使用HMAC-MD5签名,密码是3456”。这个说明的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 "";
意思是:“从10.0.0.11到10.0.0.216的数据包需要ESP,使用3des-cbc加密算法,密码是”。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//
如果在10.0.0.216上输入这个,就意味着凡是去往10.0.0.11的数据包必须经过加密并附带AH头验证。要注意,它并没有指出使用哪一个SA,那是留给内核来决定的。
换句话说,SP描述了我们需要什么;而SA描述了它应该如何实现。
发出的数据包打上SA的API标记,以便本端内核用来加密和鉴定、对端内核用来解密和验证。
接下来是一个非常简单的配置,用来从10.0.0.216到10.0.0.11使用加密和验证进行发包。注意。相反方向的发包仍然是明码传输的,没有进行相应的配置。
在10.0.0.216主机上:
#!/sbin/setkey -f
add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "3456";
add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "";
spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
esp/transport//require
ah/transport//
在10.0.0.11机器上,SA是一样的,没有SP:
#!/sbin/setkey -f
add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "3456";
add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "";
经过上面的设置(如果你在/sbin下安装了“setkey”命令,以上脚本是可以执行的), 从10.0.0.216上面“ping 10.0.0.11”的过程用tcpdump应该截获如下内容:
22:37:52 10.0.0.216 > 10.0.0.11: AH(spi=0x00005fb4,seq=0xa): ESP(spi=0x00005fb5,seq=0xa) (DF)
22:37:52 10.0.0.11 > 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//
这就指明了10.0.0.11收到来自10.0.0.216的包的时候需要正确的ESP和AH处理。
现在,我们完成这个配置,我们当然也希望回去的数据包也进行加密和头验证。10.0.0.216上完整的配置应该是: 24
#!/sbin/setkey -f
add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "3456";
add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "3456";
add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "";
add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "";
spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
esp/transport//require
ah/transport//
spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
esp/transport//require
ah/transport//
10.0.0.11上:
#!/sbin/setkey -f
add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "3456";
add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "3456";
add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "";
add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "";
spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
esp/transport//require
ah/transport//
spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
esp/transport//require
ah/transport//
注意,本例中通信双方的加密密钥是一样的。这在实际中是不会出现的。
为了检测一下我们刚才的配置,运行一下:
就会显示出SA,或者用
setkey CDP
显示出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之间需要配置安全通讯,但这次要借助于racoon。为了简单起见,这个配置使用预先共享的密钥(又是可怕的共享密钥)。X.509证书的问题单独讨论,参见后面的7.2.3。
我们尽量保持缺省配置,两台机器是一样的:
path pre_shared_key "/usr/local/etc/racoon/psk.txt";
remote anonymous
exchange_mode aggressive,
doi ipsec_
situation identity_
lifetime time 2 # sec,min,hour
proposal_ #&obey, strict or claim
proposal {
encryption_algorithm 3
hash_algorithm sha1;
authentication_method pre_shared_
dh_group 2&;
sainfo anonymous
pfs_group 1;
lifetime time 2
encryption_algorithm 3
authentication_algorithm hmac_sha1;
compression_
有很多设置,我认为仍然有很多可以去掉而更接近缺省配置。很少有值得注意的事情。我们已经配置了两个匿名配置支持所有的对端机器,让将来的配置简单些。这里没有必要为每台机器各写一个段落,除非真的必要。
此外,我们还设置了我们基于我们的IP地址来识别我们自己('my_identifier address'),并声明我们可以进行3DES、sha1,并且我们将使用预先共享的密钥,写在psk.txt中。
在psk.txt中,我们设置两个条目,两台机器上都不一样。
在10.0.0.11上:
10.0.0.216
在10.0.0.216上:
确认这些文件必须为root所有、属性是0600,否则racoon将不信任其内容。两个机器上的这个文件是镜像对称的。
现在,我们就剩下设置SP了,这比较简单。
在10.0.0.216上: 27
#!/sbin/setkey -f
spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
esp/transport//
spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
esp/transport//
在10.0.0.11上:
#!/sbin/setkey -f
spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
esp/transport//
spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
esp/transport//
请注意这些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:ff01e5b
12:18:45: INFO: isakmp.c:938:isakmp_ph2begin_i(): initiate new phase 2
negotiation: 10.0.0.216[0]10.0.0.11[0]
12:18:45: INFO: pfkey.c:1106:pk_recvupdate(): IPsec-SA established:
ESP/Transport 10.0.0.11->10.0.0.216 spi=x2a7e03b)
12:18:45: INFO: pfkey.c:1318:pk_recvadd(): IPsec-SA established:
ESP/Transport 10.0.0.216->10.0.0.11 spi=xf21052)
如果我们现在运行setkey CD列出SA,就会发现已经存在了:
10.0.0.216 10.0.0.11
esp mode=transport spi=x0d5c7333) reqid=0(0x)
E: 3des-cbc 5d421c1b d33b2a9f 4edb9fc 211d9c95 ebaead04
A: hmac-sha1 cc5d869 bd736ae2 08daa99
seq=0x replay=4 flags=0x 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)
allocated: 3
sadb_seq=1 pid=17112 refcnt=0
10.0.0.11 10.0.0.216
esp mode=transport spi=x09d79698) reqid=0(0x)
E: 3des-cbc d7af8466 acd4f14c 872c5443 ec45a719 d4b3fde1 8d239d6a
A: hmac-sha1 41ccc388 e4e024 628e240c 141ffe2f
seq=0x replay=4 flags=0x 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)
hard: 0(s)
soft: 0(s)
current: 231(bytes)
hard: 0(bytes)
soft: 0(bytes)
allocated: 2
sadb_seq=0 pid=17112 refcnt=0
我们的SP是如下配置的:
10.0.0.11[any] 10.0.0.216[any] tcp
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
10.0.0.216[any] 10.0.0.11[any] tcp
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
7.2.2.1. 问题和常见的疏忽
如果不工作,检查一下所有的配置文件是不是为root所有,而且只有root才能读取。如想前台启动racoon,就加上“-F”参数。如想强制它读取某一个配置文件来取代缺省配置文件,使用参数“-f”。如想看到超级详细的细节,往racoon.conf中加入“”一行。
7.2.3. 使用X.509证书进行自动密钥管理
如前所述,之所以共享密码很困难,是因为它们一旦共享,就不再成为真正意义的密码。幸运的是,我们仍可以用非对称加密技术来解决这个问题。
如果IPSEC的每个参与者都生成一对公钥和私钥,就可以让双方公开它们的公钥并设置策略,从而建立安全连接。
虽然需要一些计算,但生成密钥还是相对比较简单的。以下都是基于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,
my_identifier asn1
peers_identifier asn1
certificate_type x509 "upstairs.public" "upstairs.private";
peers_certfile "laptop.public";
proposal {
encryption_algorithm 3
hash_algorithm sha1;
authentication_
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,
my_identifier asn1
peers_identifier asn1
certificate_type x509 "laptop.public" "laptop.private";
peers_certfile "upstairs.public";
proposal {
encryption_algorithm 3
hash_algorithm sha1;
authentication_
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。也就是说,每台机器都需要本端的公钥和私钥,以及对端的公钥。
确认一下已经写好了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
add 10.0.0.216 10.0.0.11 esp 34501
-E 3des-cbc "";
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/
注意。“-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
add 10.0.0.216 10.0.0.11 esp 34501
-E 3des-cbc "";
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/
注意,与上面基本一样,除了把“-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
第8章 多播路由
求助: Editor Vacancy!
Multicast-HOWTO已经很古老了(相对而言) ,而且不够准确甚至会因此而误导读者。
在你开始进行多播路由之前,你需要重新配置你的Linux内核来支持你想实现的多播路由类型。这一步需要你来决定使用何种类型的多播路由。基本上有这么四种:DVMRP (RIP单播协议的多播版本),MOSPF(同理,只不过是OSPF),PIM-SM ("Protocol Independent Multicasting -&Sparse Mode",协议无关多播-稀疏模式, 它假定任意多播组的用户都是spread out的,而不是clumped的)和PIM-DM (同理,只不过是“密集模式”,它假定同一个多播组的用户适当地clumps在一起).
在Linux内核中,你会注意到并没有这些选项。这是因为这些协议本身是由路由程序负责处理的,比如Zebra、mrouted或者pimd。然而你仍然应该对于你要使用那种方案有一个明确的主意,以便选择正确的内核选项。
无论哪种多播路由,你一定都要启用“multicasting”和“multicast routing”选项。对于DVMRP和MOSPF这就够了。如果你想使用PIM,你必须还要启用PIMv1或者PIMv2选项,具体用哪个取决于你的网络究竟使用PIM协议的哪一个版本。
当你把那些都想清楚、编译了新内核之后,重启的时候应该能够看到IP协议的列表中包括了IGMP。这是用来管理多播组的协议。虽然第3版业已存在并归档,但截止到写这篇文档时为止,Linux只支持IGMP的第1版和第2版。但这并不会太多地影响我们,因为IGMPv3还太新,并没有看到多少能够用到v3特有功能的应用。因为,用IGMP处理组,仅会使用到连最简单的IGMP版本中都会包含的基本功能。绝大部分应该是IGMPv2,虽然仍能接触到IGMPv1。
到此为止,一切都好。我们已经启用了多播。现在,我们得告诉内核,做点实在的事情了――启动路由。也就是说象路由表中添加多播子网:
ip route add 224.0.0.0/4 dev eth0
(当然,我们假定你要通过eth0进行多播。你要根据你的情况选择设备。)
现在,启动Linux的包转发...
echo 1 > /proc/sys/net/ipv4/ip_forward
在这里,你可能想知道是否起了作用。所以我们ping一下缺省组224.0.0.1,看看有没有人在。在你的LAN上所有配置并启用了多播的机器都应该予以回应,其他机器则不会。但你会注意到,没有任何一台机器回应的时候声明自己是224.0.0.1,多么令人惊奇?!因为这是一个组地址(对于接收者来说是“广播”),所以组中的所有成员都用它们的地址来回应,而不是用组地址来回应。
ping -c 2 224.0.0.1
到此,你已经可以实现真正的多播路由了。好的,假定你需要在两个网络间进行路由。
(To Be Continued!)
第9章 带宽管理的队列规定
现在,当我搞清楚这一切之后,我真的大吃一惊。Linux 2.2/2.4完全能象那些最高端的专用带宽管理系统一样来管理带宽。甚至比帧中继和ATM还要优秀。
为避免概念混乱,tc 采用如下规定来描述带宽:
mbps =&1024 kbps =&1024 * 1024 bps => byte/s
mbit =&1024 kbit => kilo bit/s.
mb =&1024 kb =&1024 * 1024 b => byte
mbit =&1024 kbit => kilo bit.
内定:数字以bps和b方式储存。
但当tc输出速率时,使用如下表示:
1Mbit =&1024 Kbit =&1024 * 1024 bps => 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队列规定作为硬性的缺省设置,你不能对它进行配置。它缺省是这样配置的:
内核规定,根据数据包的优先权情况,对应相应的频道。这个对应是根据数据包的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)
0001 1&最小成本 (mmc)
0000 0&正常服务
因为在这4bit的后面还有一个bit,所以TOS字段的实际值是上述值的2倍。(Tcpdump -v -v 可以让你看到整个TOS字段的情况,而不仅仅是这4个bit)也就是你在下表的第一列看到的值:
TOS Bits 意义 Linux优先权 频道
------------------------------------------------------------
0x0 0&正常服务 0 最好效果 1
0x2 1&最小成本(mmc) 1&填充 2
0x4 2&最大可靠性(mr) 0 最好效果 1
0x6 3 mmc+mr 0 最好效果 1
0x8 4&最大吞吐量(mt) 2&大量传输 2
0xa 5 mmc+mt 2&大量传输 2
0xc 6 mr+mt 2&大量传输 2
0xe 7 mmc+mr+mt 2&大量传输 2
0x10 8&最小延迟(md) 6&交互 0
0x12 9 mmc+md 6&交互 0
0x14 10 mr+md 6&交互 0
0x16 11 mmc+mr+md 6&交互 0
0x18 12 mt+md 4&交互+大量传输 1
0x1a 13 mmc+mt+md 4&交互+大量传输 1
0x1c 14 mr+mt+md 4&交互+大量传输 1
0x1e 15 mmc+mr+mt+md 4&交互+大量传输 1
很多的数字。第二列写着与4个TOS位相关的数值,接着是它们的意义。比如,15表示一个数据包要求最小成本、最大可靠性、最大吞吐量和最小延迟。我想称之为“人代会车队”。[译者按:原作为‘荷兰数据包’]
第四列写出了Linux内核对于TOS位的理解,并表明了它们对应哪种优先权。
最后一列表明缺省的权限图。在命令行里,缺省的权限图应该是:
1, 2, 2, 2, 1, 2, 0, 0&, 1, 1, 1, 1, 1, 1, 1, 1
也就是说,比如优先权4将被映射到1频道。权限图允许你列出更高的优先权值(>7),它们不对应TOS映射,但是有其它的意图。
下表来自RFC 1349,告诉你应用程序可能如何设置它们的TOS:
(minimize delay)
(minimize delay)
(maximize throughput)
(minimize delay)
(minimize delay)
(maximize throughput)
(minimize delay)
Domain Name Service
(maximize throughput)
(minimize monetary cost)
txqueuelen
队列的长度来自网卡的配置,你可以用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字节的缓冲区才能达到期望的速率。
如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会导致潜在的丢包。
一个零长度的包并不是不耗费带宽。比如以太网,数据帧不会小于64字节。Mpu(Minimum Packet Unit,最小分组单位)决定了令牌的最低消耗。
速度操纵杆。参见上面的limits!
如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况)。If the bucket contains tokens and is allowed to empty, by default it does so at infinite speed. 如果不希望这样,可以调整入下参数:
如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样。那可能并不是你希望的,特别是你有一个比较大的桶的时候。
峰值速率可以用来指定令牌以多块的速度被删除。用书面语言来说,就是:释放一个数据包,但后等待足够的时间后再释放下一个。我们通过计算等待时间来控制峰值速率
然而,由于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什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上载数据绝对会破坏交互性。
这是因为上载数据会充满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基本上不需要手工调整:
多少秒后重新配置一次散列算法。如果取消设置,散列算法将永远不会重新配置(不建议这样做)。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
为了正确地理解更多的复杂配置,有必要先解释一些概念。由于这个主题的历史不长和其本身的复杂性,人们经常在说同一件事的时候使用各种词汇。
以下来自draft-ietf-diffserv-model-06.txt,Diffserv路由器的建议管理模型。 可以在以下地址找到:
http://www.ietf.org/internet-drafts/draft-ietf-diffserv-model-06.txt.
关于这些词语的严格定义请参考这个文档。
管理设备输入(ingress)或输出(egress)的一个算法。
无类的队列规定
一个内部不包含可配置子类的队列规定。
分类的队列规定
一个分类的队列规定内可一包含更多的类。其中每个类又进一步地包含一个队列规定,这个队列规定可以是分类的,也可以是无类的。根据这个定义,严格地说pfifo_fast算是分类的,因为它实际上包含3个频道(实际上可以认为是子类)。然而从用户的角度来看它是无类的,因为其内部的子类无法用tc工具进行配置。
一个分类的队列规定可以拥有很多类,类内包含队列规定。
每个分类的队列规定都需要决定什么样的包使用什么类进行发送。分类器就是做这个用的。
分类是通过过滤器完成的。一个过滤器包含若干的匹配条件,如果符合匹配条件,就按此过滤器分类。
在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据包之前发送。这种处理叫做“调度”,比如此前提到的pfifo_fast就是这样的。调度也可以叫做“重排序”,但这样容易混乱。
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速率,这种处理叫做“整形”。整形在egress处进行。习惯上,通过丢包来降速也经常被称为整形。 43
通过延迟或是丢弃数据包来保证流量不超过事先规定的带宽。在Linux,里,策略总是规定丢弃数据包而不是延迟。即,不存在ingress队列。
Work-Conserving
对于一个work-conserving队列规定,如果得到一个数据包,它总是立刻对它进行分发。换句话说,只要网卡(egress队列规定)允许,它就不会延迟数据包的发送。
non-Work-Conserving
有些队列――比如令牌桶过滤器――可能需要暂时停止发包以实现限制带宽。也就是说它们有时候即使有数据包需要处理,也可能拒绝发送。
现在我们简单了解了一些术语,让我们看看他们的位置:
用户级程序
+---------------+--------------------------------------------+
| -------> IP 协议栈 |
| |&/ ----------> 转发--------> |
| ^&Y /-队列规定1-\ |
| |&Egress /--队列规定2--\ |
--->->Ingress 分类器 -------队列规定3---- | ->
| 队列规定 \__队列规定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
10: 11: 12:
10:1 10:2 12:1 12:2
不要误解这张图!你千万不要想象内核处在树的顶点而下面部分是网络。数据包是在根队列规定处入队和出队的,而内核只同根打交道。
一个数据包可能是按照下面这个链状流程进行分类的:
1: -> 1:1 -> 12: -> 12:2
数据包现在应该处于12:2下属的某个队列规定中的某个队列中。在这个例子中,树的每个节点都附带着一个过滤器,用来选择下一步进入哪个分支。这样比较直观。然而,这样也是允许的:
1: -> 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识别下列参数:
创建频道的数目。每个频道实际上就是一个类。如果你修改了这个数值,你必须同时修改:
如果你不给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的内在机制不协调造成的。
除了可以分类之外,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参数。
下面是配置整形时需要指定的一些参数:
平均包大小,以字节计。计算maxidle时需要,maxidle从maxburst得出。 49
网卡的物理带宽,用来计算闲置时间。
一个数据包被发送出去的时间可以是基于包长度而阶梯增长的。一个800 字节的包和一个806字节的包可以认为耗费相同的时间。也就是说它设置时间粒度。通常设置为8,必须是2的整数次幂。
这个参数的值决定了计算maxidle所使用的数据包的个数。在avgidle跌落到0之前,这么多的数据包可以突发传输出去。这个值越高,越能够容纳突发传输。你无法直接设置maxidle的值,必须通过这个参数来控制。
如前所述,发生越限时CBQ会禁止发包。实现这个的理想方案是根据事先计算出的闲置时间进行延迟之后,发一个数据包。然而,UNIX的内核一般来说都有一个固定的调度周期(一般不大于10ms),所以最好是这样:禁止发包的时间稍长一些,然后突发性地传输minburst个数据包,而不是一个一个地传输。等待的时间叫做offtime。
从大的时间尺度上说,minburst值越大,整形越精确。但是,从毫秒级的时间尺度上说,就会有越多的突发传输。
如果avgidle值降到0,也就是发生了越限,就需要等待,直到avgidle的值足够大才发送数据包。为避免因关闭链路太久而引起的以外突发传输,在avgidle的值太低的时候会被强制设置为minidle的值。
参数minidle的值是以负微秒记的。所以10代表avgidle被限制在-10us上。
最小包尺寸――因为即使是0长度的数据包,在以太网上也要生成封装成64字节的帧,而需要一定时间去传输。为了精确计算闲置时间,CBQ需要知道这个值。
期望中的传输速率。也就是“油门”!
在CBQ的内部由很多的微调参数。比如,那些已知队列中没有数据的类就不参加计算、越限的类将被惩罚性地降低优先级等等。都非常巧妙和复杂。
9.5.4.2. CBQ在分类方面的行为
除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理。
每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted round robin,加权轮转)过程,从优先权值小的类开始。
那些队列中有数据的类就会被分组并被请求出队。在一个类收到允许若干字节数据出队的请求之后,再尝试下一个相同优先权值的类。
下面是控制WRR过程的一些参数:
当从外部请求一个CBQ发包的时候,它就会按照“priority”参数指定的顺序轮流尝试其内部的每一个类的队列规定。当轮到一个类发数据时,它只能发送一定量的数据。“allot”参数就是这个量的基值。更多细节请参照“weight”参数。
CBQ可以象PRIO设备那样工作。其中“prio”值较低的类只要有数据就必须先服务,其他类要延后处理。
“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带宽将按照两个类的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)
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
“split队列规定”是1:0,也就是做出选择的地方。c0是二进制的F是,所以它们共同匹配所有的数据包。第一个类匹配第7和第6位,也就是负责“交互”和“控制”的数据包。第二个类匹配其余的数据包。
节点1:0现在应该有了这样一个表格:
为了更有趣,你还可以传递一个“change掩码”,确切地指出你想改变哪个优先权值。你只有在使用了“tc class change”的时候才需要。比如,往1:2中添加best effort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01
现在,1:0上的优先权分布应该是:
求助: 尚未测试过“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. 使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的“分类器链” 进行选择。这个链中包含了这个分类队列规定所需的所有过滤器。
重复前面那棵树:
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发出的数据包做整形,这就很有用了。语法是这样的:
tc 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匹配就行了。?
选择交互和最小延迟的数据流:
# tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 \&57
match 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
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网站。
第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
10.1. 告诫
事情永远不会是表面看上去那样简单。A路由器和B路由器上的eth1和eth2需要关闭“返回路径过滤”,否则它们会丢弃那些返回地址不同于其源地址的数据包:
# echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter
# echo 0 > /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将来可能更多地描述这个问题。
第11章 Netfilter和iproute――给数据包作标记
迄今为止我们已经了解了iproute是如何工作的,并且多次提到了netfilter。这里,你正好可以趁机看一看Rusty出名地不可靠的指南. Netfilter本身可以在这里找到。
Netfilter可以让我们进行包过滤或者篡改数据包头。有一个特别的功能就是我们可以给数据包打上一个数字标记。使用--set-mark机制就可以。
例如,这个命令把所有发往25/tcp(发出邮件)的数据包都打上了标记:
# iptables -A PREROUTING -i eth0 -t mangle -p tcp --dport 25 \
-j MARK --set-mark 1
比如说,我们有多个连接,一个是按流量计费的,比较贵但是很快,另一个是包月的,但是比较慢。我们当然愿意让发送电子邮件的数据包走那条比较便宜的路由。
我们已经给那些数据包打上了标记“1”,我们现在命令路由策略数据库实现这个功能:
# echo 201 mail.out >> /etc/iproute2/rt_tables
# ip rule add fwmark 1&table mail.out
# ip rule ls
0: from all lookup local
32764: from all fwmark 1&lookup mail.out
32766: from all lookup main
32767: from 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。
第12章 对包进行分类的高级过滤器
就象在分类的队列规定一段中解释的,过滤器用与把数据包分类并放入相应的子队列。这些过滤器在分类的队列规定内部被调用。
下面就是我们可用的分类器(部分):
根据防火墙如何对这个数据包做标记进行判断。如果你不想学习tc的过滤器语法,这倒是一个捷径。细节请参见队列那一章。
根据数据包中的各个字段进行判断,如源IP地址等等。
根据数据包将被哪条路由进行路由来判断。
rsvp, rsvp6
根据数据包的RSVP情况进行判断。只能用于你自己的网络,互联网并不遵守RSVP。
用于DSMARK队列规定,参见相关章节。
通常来说,总有很多途径可实现对数据包分类,最终取决于你喜欢使用哪种系统。
分类器一般都能接受几个参数,为了方便我们列出来:
这个分类器所接受的协议。一般来说你只会接受IP数据。必要参数。
这个分类器附带在哪个句柄上。句柄必须是一个已经存在的类。必要参数。
这个分类器的优先权值。优先权值低的优先。
对于不同过滤器,它的意义不同。
后面所有的节都假定你试图对去往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 ff0000 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 0ffff at nexthdr+0 flowid 1:10
“nexthdr”选项意味着封装在IP包中的下一个PDU的头部,也就是说它的上层协议的头。匹配操作就是从这个协议的头部开始的,应该发生在头部开始的第16位处。在TCP和UDP协议的头部,这个部分存放的是这个报文的目标端口。数字是按照先高厚低的格式存储的,所以0x0016就是十进制的22(如果是TCP的话就是ssh服务)。其实,这个匹配在没有上下文的情况下含义很模糊,我们放
在后面讨论。
理解了上面的例子之后,下面这条选择器就很好懂了:
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 \
ma}

我要回帖

更多关于 微信小程序接口的调用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信