[上一小节: 锚] [总目录] [下一小节: 地址池和负载平衡]
为某物列队就是在它们等候处理时按顺序储存。在一个计算机网络中, 当数据包从某台主机发出后, 它们进入一个等待操作系统处理的队列, 然后操作系统决定处理哪一个队列和其中的哪些数据包应该被处理。操作系统选择处理数据包的顺序可能影响网络性能。例如, 用户正在运行两个网络应用程序: SSH和FTP, 理想的情况是SSH数据包在FTP数据包之前处理, 这是因为考虑了SSH的时间敏感特性;当在SSH客户端输入一个key时, 客户端希望马上得到一个回应, 而一个FTP传输被延迟了额外的几秒钟却几乎不会令人难以忍受和引起注意。但是如果路由器在处理SSH连接前需要处理大量的FTP数据包会怎样呢? SSH连接的数据包将会滞留在队列中(或者如果因队列没有足够的空间容纳所有的数据包, 在被处理前路由器可能会将SSH数据包丢弃), 而SSH会话或因此滞后或变慢。通过调整队列策略, 可将网络带宽在不同的应用程序、用户和计算机间公正合理地分享。
注意列队仅对出站方向的数据包有效, 因为一旦一个进站数据包数据包抵达一个接口时, 让它列队已经太晚了——它刚抵达收到它的接口时已经消耗了网络带宽。唯一解决的办法是在毗连的路由器上启用列队, 或者如果收到此数据包的主机作为一台路由器, 那么在出站数据包抵达的路由器的内部接口上启用列队功能。
调度器决定处理哪些队列及以处理顺序。默认情况下, OpenBSD使用一个先入先出(FIFO)的调度器。一个FIFO队列就像一个超市收款台的付款队列——根据队列顺序依次处理, 当一个新的数据包到达时会被加到队列的最后。如果队列满了, 这里就像超市停止营业了, 新来的数据包将被遗弃, 这就是所谓的"去尾"。
OpenBSD支持两个额外的调度器:
分类列队(CBQ)一种列队法则, 它将网络带宽分配给多个队列或类型。每个队列分配的带宽是基于源/目标地址、端口号、协议等。当一个队列的父系队列的带宽未充分使用时, 这个队列就可以从其父系队列借用带宽。队列也有优先级, 含有交互式数据的队列(例如SSH)可以在包含大量数据的队列(例如FTP)前面处理。
CBQ列队是按级别划分的。最高级别是root队列, root队列定义了全部的有效带宽, 子队列产生在root队列下, 每个子队列分配了一部分root队列的带宽。例如, 队列可用如下方式定义:
这个例子中, 总带宽设定为2M/秒(Mbps), 然后带宽被分配给三个子队列。
可以通过在队列中定义新队列而扩展队列的级别, 这样可以在不同的用户和类之间平等地分配带宽, 以避免某些协议掠夺其它协议的带宽, 定义一个这样的队列:
注意分配给每个级别各队列的带宽总和不能多于分配给其父系队列的带宽。
如果一个队列的父系队列有闲置的带宽, 该队列可以向其父系队列借用, 这是因为这些闲置带宽无法被父系队列的其它子队列利用, 建立一个像这样的队列应该:
如果ftp队列要超过900K的流量而UserA队列的流量不足1Mbps(因为ssh队列没有完全使用分配给它的100Kbps带宽), 那么ftp队列将向临时UserA借用带宽。这样的方案使ftp队列在过载时可以使用更多的带宽;但是一旦ssh队列的负荷增加, 临时借用的带宽必须归还。
CBQ给每个队列指派一个优先级别。如果两个队列同为一个父队列的子队列(换句话说, 如果两个队列在级别分支的同一层次上), 在网络拥塞时先处理优先级别高的那个队列。如果两个队列的级别一样则采用轮询方式, 例如:
CBQ将对UserA和UserB这两个队列采用轮询方式——不会照顾任何一个队列。当UserA队列被处理时, CBQ也会处理它的子队列。本例中, ssh队列有更高的优先级所以在网络拥塞时会优先于ftp队列使用带宽。注意为什么ssh和ftp没有与UserA和UserB比较优先级呢, 这是因为它们不在一个层次分支上。
要了解CBQ理论更多细节请看 References on CBQ.
优先列队(PRIQ)将多个队列分配到一个网络接口, 同时每个队列被赋予一个优先级别。高优先级别队列总是在低优先级别队列的前面处理。如果两个或更多的队列的优先级别相同, 则这些队列按轮询方式处理。
PRIQ的列队结构是无层次的T——你不能在队列中再定义队列。定义root队列时设置了总的占用带宽, 然后在root下定义子队列, 看看下面的例子:
root队列和其子队列被定义为可以使用2Mbps带宽, 先处理优先级别最高的队列 (最大的 priority 值), 一旦这个队列的所有数据包被处理完毕, 或者这个队列被发现已经空了, PRIQ将转到下一个的优先级别最高的队列。在一个队列中, 处理数据包是采用先入先出(FIFO)的顺序。
很重要的是当你采用PRIQ时, 你必须非常小心地设计你的队列, 因为PRIQ永远只先处理高优先级别的队列, 有可能出现这样的情况, 高级别的队列持续接收恒定流量的数据包可能会造成低级别队列的数据包对延迟或丢弃。
随机早期检测(RED)是一个避免拥塞的算法。它的职责是确保队列未排满, 从而避免了网络拥塞。它通过不断地计算队列的平均长度(大小)并将其与极限值进行对比, 一个是上限;一个是下限。如果队列的平均大小低于下限就不会丢弃任何数据包;如果平均值高于上限则所有的新抵达的数据包将被丢弃。如果平均值介于上下限之间, 则数据包是否被丢弃基于一个平均队列长度的概率计算。换句话说, 当平均队列长度逼近上限时, 越来越多的数据包会被丢弃。当丢弃数据包时RED随机选择丢弃哪个连接的数据包。占用大量带宽的那些连接的数据包最有可能被丢弃。
RED十分有用, 因为它避免了所谓"全局同步"的情形, 并且它可以应付激增的通讯。全局同步是指同时丢弃几个连接的数据包造成总吞吐量损失的情况, 例如, 当路由器上有10个FTP连接时发生了拥塞, 然后, 所有(或大多数)连接的数据包被丢弃(就像FIFO队列), 总吞吐量将骤然下降。这不是一个理想的状态, 因为它导致所有的FTP连接的吞吐量下降, 并且这意味着网络的最大潜能没有被充分利用。RED采用随机丢弃某个连接的数据包而不是丢弃所有连接的数据包的方式, 这样就避免了"全局同步"现象。占用大量带宽的那些连接的数据包最有可能先被丢弃。 这种情形下, 高带宽占用的连接将被节流, 拥塞被避免, 而突发的吞吐量骤降情况也不会出现。另外, RED 还可应付激增的通讯, 因为它在队列被这些新来的数据包充满前已经开始丢弃数据包了。
RED应该仅用于那些对网络拥塞提示有反应能力的传送协议。多数情况下这意味着RED应该应用在TCP通讯队列, 而非UDP或者ICMP通讯。
关于RED理论的更多知识, 请参阅 References on RED。
更多有关ECN的信息, 请参阅 RFC 3168.
从OpenBSD 3.0开始 交错队列 (ALTQ)的列队方式已经成为基本系统的一部分。从OpenBSD 3.3开始ALTQ被并入PF内。OpenBSD中的ALTQ支持分类列队(CBQ)和优先列队(PRIQ)调度器, 同时它也支持随机早期检测(RED)和赛事拥塞通告(ECN)。
因为ALTQ已经整合进PF, 所以列队功能必须启用PF, 怎样启用PF可以在开始里找到。
列队是在 pf.conf 里进行配置。可以用有两种类型的指令进行列队设置:
altq指令的语法是:
altq on interface scheduler bandwidth bw qlimit qlim \
tbrsize size queue { queue_list }
例如:
这在接口fxp0上启用CBQ。总有效带宽设置为2Mbps, 定义了三个子队列: std, ssh, 和 ftp。altq on fxp0 cbq bandwidth 2Mb queue { std, ssh, ftp }
queue 指令的语法为:
queue name [on interface] bandwidth bw [priority pri] [qlimit qlim] \
scheduler ( sched_options ) { queue_list }
继续上面的例子:
queue std bandwidth 50% cbq(default) queue ssh bandwidth 25% { ssh_login, ssh_bulk } queue ssh_login bandwidth 25% priority 4 cbq(ecn) queue ssh_bulk bandwidth 75% cbq(ecn) queue ftp bandwidth 500Kb priority 3 cbq(borrow red)
这里设置了先前定义的子队列的参数。std队列分配了50%的root队列带宽(或者说 1Mbps)并且被设置成默认队列。ssh队列分配了root队列25%的带宽(500kb), 而且它还包含了两个子队列,ssh_login和ssh_bulk。ssh_login的优先级高于ssh_bulk并且启用了ECN。ftp队列被分配了500Kbps的带宽,并且优先级别为3, 在可能时,它还可以在可以借用带宽,并且启用了RED。
注意: 每个子队列单独指定其带宽, PF会给予队列100%的父系队列带宽。这种情形下, 规则载入后可能会产生一个错误, 因为如果有一个队列占用了100%的带宽, 就没有剩余的带宽可以分配, 也就不能定义同级别的其它队列。
为队列分配通讯, 要在PF的 过滤规则 里配合使用queue关键字。例如, 一组过滤规则包含一行如下内容:
pass out on fxp0 from any to any port 22
可以通过queue关键字将匹配这条规则的数据包分配到指定的队列:
pass out on fxp0 from any to any port 22 queue ssh
当queue关键字与block指令一同使用时, 结果任何TCP RST或者ICMP不可抵达数据包全被分配到指定的队列。
注意:除了可以在altq里用指令定义这个队列外, 还可以在接口上定义:
altq on fxp0 cbq bandwidth 2Mb queue { std, ftp } queue std bandwidth 500Kb cbq(default) queue ftp bandwidth 1.5Mb
pass in on dc0 from any to any port 21 queue ftp
队列在fxp0上被启用, 但是指定却发生在dc0上。 如果接口fxp的出站数据包匹配这条pass规则, 它们将在ftp队列里列队。这种类型的列队在路由器上十分有用。
通常queue关键字仅给定一个队列名, 但是如果指定了第二个队列名, 这个队列将被用于低延迟服务类型(ToS)的数据包和TCP ACK这类无数据有效负荷的数据包。在使用SSH是发现的一个很好的例子, SSH登录会话会被设定成低延迟服务类型而SCP和SFTP会话则不会。PF能利用这个信息, 将属于登录连接的数据包安排在一个不同的队列里与非登录数据包分, 这对优先处理登录连接的数据包再处理文件传输数据包很有用处。
pass out on fxp0 from any to any port 22 queue(ssh_bulk, ssh_login)
这是将属于SSH登录连接的数据包分配给ssh_login队列, 并把属于SCP或SFTP的数据包分配给ssh_bulk队列。SSH 登录连接的数据包会在SCP和SFTP连接前面处理, 因为lssh_login队列有更高的优先权。
在一个非对称连接中将TCP ACK数据包分配到一个高优先级队列是很有用的, 非对称连接是指不同的上传下载带宽, 例如ADSL线路。当使用ADSL线路时, 如果上传通道被占满而此时开始一个下载时, 下载速度会很慢, 这是因为想通过上传通道将TCP ACK数据包发出去时发生了网络拥塞。实验证明为了到达最好的效果, 上传队列的带宽应该设置得比上传最大带宽小一些, 例如一个ADSL线路有640Kbps的最大上传速度, 将root队列的上传带宽值设为600Kb会有更好的性能。反复试验可以得出最佳的带宽设定。
当在含有keep state的规则里使用queue 关键字时:
pass in on fxp0 proto tcp from any to any port 22 flags S/SA \ keep state queue ssh
PF将把这个队列记录为状态表项, 所以出站的回程数据包到fxp0接口时可以匹配这个状态化连接, 并最终进入ssh队列。注意:尽管queue关键字用在了进站通讯过滤规则里, 但它的目的是为相应的出站通讯指定一个队列;上面的规则不为进站通讯列队。
[ Alice ] [ Charlie ] | | ADSL ---+-----+-------+------ dc0 [ OpenBSD ] fxp0 -------- ( Internet ) | [ Bob ]
这个例子中, OpenBSD被用来担当小型家庭网络的Internet网关并带有三个工作站。这个网关承担包过滤和网络地址转换的工作。Internet连接是通过一条ADSL线路, 下载2Mbps, 上传640Kbps。
这个网络的队列策略:
下面是适合这个网络策略的规则集。注意这里仅显示了用以完成上述策略的pf.conf指令;未显示 nat, rdr, options 等。
# enable queueing on the external interface to control traffic going to # the Internet. use the priq scheduler to control only priorities. set # the bandwidth to 610Kbps to get the best performance out of the TCP # ACK queue. altq on fxp0 priq bandwidth 610Kb queue { std_out, ssh_im_out, dns_out, \ tcp_ack_out } # define the parameters for the child queues. # std_out - the standard queue. any filter rule below that does not # explicitly specify a queue will have its traffic added # to this queue. # ssh_im_out - interactive SSH and various instant message traffic. # dns_out - DNS queries. # tcp_ack_out - TCP ACK packets with no data payload. queue std_out priq(default) queue ssh_im_out priority 4 priq(red) queue dns_out priority 5 queue tcp_ack_out priority 6 # enable queueing on the internal interface to control traffic coming in # from the Internet. use the cbq scheduler to control bandwidth. max # bandwidth is 2Mbps. altq on dc0 cbq bandwidth 2Mb queue { std_in, ssh_im_in, dns_in, bob_in } # define the parameters for the child queues. # std_in - the standard queue. any filter rule below that does not # explicitly specify a queue will have its traffic added # to this queue. # ssh_im_in - interactive SSH and various instant message traffic. # dns_in - DNS replies. # bob_in - bandwidth reserved for Bob's workstation. allow him to # borrow. queue std_in bandwidth 1.6Mb cbq(default) queue ssh_im_in bandwidth 200Kb priority 4 queue dns_in bandwidth 120Kb priority 5 queue bob_in bandwidth 80Kb cbq(borrow) # ... in the filtering section of pf.conf ... alice = "192.168.0.2" bob = "192.168.0.3" charlie = "192.168.0.4" local_net = "192.168.0.0/24" ssh_ports = "{ 22 2022 }" im_ports = "{ 1863 5190 5222 }" # filter rules for fxp0 inbound block in on fxp0 all # filter rules for fxp0 outbound block out on fxp0 all pass out on fxp0 inet proto tcp from (fxp0) to any flags S/SA \ keep state queue(std_out, tcp_ack_out) pass out on fxp0 inet proto { udp icmp } from (fxp0) to any keep state pass out on fxp0 inet proto { tcp udp } from (fxp0) to any port domain \ keep state queue dns_out pass out on fxp0 inet proto tcp from (fxp0) to any port $ssh_ports \ flags S/SA keep state queue(std_out, ssh_im_out) pass out on fxp0 inet proto tcp from (fxp0) to any port $im_ports \ flags S/SA keep state queue(ssh_im_out, tcp_ack_out) # filter rules for dc0 inbound block in on dc0 all pass in on dc0 from $local_net # filter rules for dc0 outbound block out on dc0 all pass out on dc0 from any to $local_net pass out on dc0 proto { tcp udp } from any port domain to $local_net \ queue dns_in pass out on dc0 proto tcp from any port $ssh_ports to $local_net \ queue(std_in, ssh_im_in) pass out on dc0 proto tcp from any port $im_ports to $local_net \ queue ssh_im_in pass out on dc0 from any to $bob queue bob_in
( IT Dept ) [ Boss's PC ] | | T1 --+----+-----+---------- dc0 [ OpenBSD ] fxp0 -------- ( Internet ) | fxp1 [ COMP1 ] [ WWW ] / | / --+----------'
这个例子中, OpenBSD主机担当公司网络的防火墙。 公司在DMZ网段运行了一台WWW服务器, 客户可以通过FTP软件将自己的站点传送到这台服务器上。IT部门有自己的子网并连接到主要网络, 而老板有一台PC用来收发邮件和网上冲浪。通过一条T1线路连接到Internet上, 上行下载速度全1.5Mbps。 所有其它网段使用高速以太网(100Mbps)。
网络管理员决定使用如下策略:
下面是适合这个网络策略的规则集。注意这里仅显示了用以完成上述策略的pf.conf指令;未显示 nat, rdr, options 等。
# enable queueing on the external interface to queue packets going out # to the Internet. use the cbq scheduler so that the bandwidth use of # each queue can be controlled. the max outgoing bandwidth is 1.5Mbps. altq on fxp0 cbq bandwidth 1.5Mb queue { std_ext, www_ext, boss_ext } # define the parameters for the child queues. # std_ext - the standard queue. also the default queue for # outgoing traffic on fxp0. # www_ext - container queue for WWW server queues. limit to # 500Kbps. # www_ext_http - http traffic from the WWW server; higher priority. # www_ext_misc - all non-http traffic from the WWW server. # boss_ext - traffic coming from the boss's computer. queue std_ext bandwidth 500Kb cbq(default borrow) queue www_ext bandwidth 500Kb { www_ext_http, www_ext_misc } queue www_ext_http bandwidth 50% priority 3 cbq(red borrow) queue www_ext_misc bandwidth 50% priority 1 cbq(borrow) queue boss_ext bandwidth 500Kb priority 3 cbq(borrow) # enable queueing on the internal interface to control traffic coming # from the Internet or the DMZ. use the cbq scheduler to control the # bandwidth of each queue. bandwidth on this interface is set to the # maximum. traffic coming from the DMZ will be able to use all of this # bandwidth while traffic coming from the Internet will be limited to # 1.0Mbps (because 0.5Mbps (500Kbps) is being allocated to fxp1). altq on dc0 cbq bandwidth 100% queue { net_int, www_int } # define the parameters for the child queues. # net_int - container queue for traffic from the Internet. bandwidth # is 1.0Mbps. # std_int - the standard queue. also the default queue for outgoing # traffic on dc0. # it_int - traffic to the IT Dept network; reserve them 500Kbps. # boss_int - traffic to the boss's PC; assign a higher priority. # www_int - traffic from the WWW server in the DMZ; full speed. queue net_int bandwidth 1.0Mb { std_int, it_int, boss_int } queue std_int bandwidth 250Kb cbq(default borrow) queue it_int bandwidth 500Kb cbq(borrow) queue boss_int bandwidth 250Kb priority 3 cbq(borrow) queue www_int bandwidth 99Mb cbq(red borrow) # enable queueing on the DMZ interface to control traffic destined for # the WWW server. cbq will be used on this interface since detailed # control of bandwidth is necessary. bandwidth on this interface is set # to the maximum. traffic from the internal network will be able to use # all of this bandwidth while traffic from the Internet will be limited # to 500Kbps. altq on fxp1 cbq bandwidth 100% queue { internal_dmz, net_dmz } # define the parameters for the child queues. # internal_dmz - traffic from the internal network. # net_dmz - container queue for traffic from the Internet. # net_dmz_http - http traffic; higher priority. # net_dmz_misc - all non-http traffic. this is also the default queue. queue internal_dmz bandwidth 99Mb cbq(borrow) queue net_dmz bandwidth 500Kb { net_dmz_http, net_dmz_misc } queue net_dmz_http bandwidth 50% priority 3 cbq(red borrow) queue net_dmz_misc bandwidth 50% priority 1 cbq(default borrow) # ... in the filtering section of pf.conf ... main_net = "192.168.0.0/24" it_net = "192.168.1.0/24" int_nets = "{ 192.168.0.0/24, 192.168.1.0/24 }" dmz_net = "10.0.0.0/24" boss = "192.168.0.200" wwwserv = "10.0.0.100" # default deny block on { fxp0, fxp1, dc0 } all # filter rules for fxp0 inbound pass in on fxp0 proto tcp from any to $wwwserv port { 21, \ > 49151 } flags S/SA keep state queue www_ext_misc pass in on fxp0 proto tcp from any to $wwwserv port 80 \ flags S/SA keep state queue www_ext_http # filter rules for fxp0 outbound pass out on fxp0 from $int_nets to any keep state pass out on fxp0 from $boss to any keep state queue boss_ext # filter rules for dc0 inbound pass in on dc0 from $int_nets to any keep state pass in on dc0 from $it_net to any queue it_int pass in on dc0 from $boss to any queue boss_int pass in on dc0 proto tcp from $int_nets to $wwwserv port { 21, 80, \ > 49151 } flags S/SA keep state queue www_int # filter rules for dc0 outbound pass out on dc0 from dc0 to $int_nets # filter rules for fxp1 inbound pass in on fxp1 proto { tcp, udp } from $wwwserv to any port 53 \ keep state # filter rules for fxp1 outbound pass out on fxp1 proto tcp from any to $wwwserv port { 21, \ > 49151 } flags S/SA keep state queue net_dmz_misc pass out on fxp1 proto tcp from any to $wwwserv port 80 \ flags S/SA keep state queue net_dmz_http pass out on fxp1 proto tcp from $int_nets to $wwwserv port { 80, \ 21, > 49151 } flags S/SA keep state queue internal_dmz
[上一小节: 锚] [总目录] [下一小节: 地址池和负载平衡]