《11. OpenBSD PF用户指南之高级配置——数据包的列队与优先》
发表于 : 2010-01-21 10:24
PF: 数据包的列队和优先
--------------------------------------------------------------------------------
目录
列队
调度器
分类列队
优先列队
随机早期检测
显式拥塞通告
列队配置
为队列分配通讯
Example #1: 小型, 家用网络
Example #2: 公司网络
--------------------------------------------------------------------------------
列队
为某物列队就是在它们等候处理时按顺序储存。在一个计算机网络中, 当数据包从某台主机发出后, 它们进入一个等待操作系统处理的队列, 然后操作系统决定处理哪一个队列和其中的哪些数据包应该被处理。操作系统选择处理数据包的顺序可能影响网络性能。例如, 用户正在运行两个网络应用程序: 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队列的带宽。例如, 队列可用如下方式定义:
Root Queue (2Mbps)
Queue A (1Mbps)
Queue B (500Kbps)
Queue C (500Kbps)
这个例子中, 总带宽设定为2M/秒(Mbps), 然后带宽被分配给三个子队列。
可以通过在队列中定义新队列而扩展队列的级别, 这样可以在不同的用户和类之间平等地分配带宽, 以避免某些协议掠夺其它协议的带宽, 定义一个这样的队列:
Root Queue (2Mbps)
UserA (1Mbps)
ssh (50Kbps)
bulk (950Kbps)
UserB (1Mbps)
audio (250Kbps)
bulk (750Kbps)
http (100Kbps)
other (650Kbps)
注意分配给每个级别各队列的带宽总和不能多于分配给其父系队列的带宽。
如果一个队列的父系队列有闲置的带宽, 该队列可以向其父系队列借用, 这是因为这些闲置带宽无法被父系队列的其它子队列利用, 建立一个像这样的队列应该:
Root Queue (2Mbps)
UserA (1Mbps)
ssh (100Kbps)
ftp (900Kbps, borrow)
UserB (1Mbps)
如果ftp队列要超过900K的流量而UserA队列的流量不足1Mbps(因为ssh队列没有完全使用分配给它的100Kbps带宽), 那么ftp队列将向临时UserA借用带宽。这样的方案使ftp队列在过载时可以使用更多的带宽;但是一旦ssh队列的负荷增加, FTP队列必须归还临时借用的带宽。
CBQ给每个队列指派一个优先级别。如果两个队列同为一个父队列的子队列(换句话说, 如果两个队列在级别分支的同一层次上), 在网络拥塞时先处理优先级别高的那个队列。 如果两个队列的级别一样则采用轮询方式, 例如:
Root Queue (2Mbps)
UserA (1Mbps, priority 1)
ssh (100Kbps, priority 5)
ftp (900Kbps, priority 3)
UserB (1Mbps, priority 1)
CBQ将对UserA和UserB这两个队列采用轮询方式—— 不会照顾任何一个队列。当UserA队列被处理时, CBQ也会处理其子队列。本例中, ssh队列有更高的优先级所以在网络拥塞时会优先于ftp队列使用带宽。注意为什么ssh和ftp没有与UserA和UserB比较优先级呢, 这是因为它们不在一个层次分支上。
要了解CBQ理论更多细节请看 References on CBQ.
优先列队
优先列队(PRIQ)
将对个队列分配个一个网络接口, 同时每个队列被赋予一个优先级别。高优先级别队列永远在低优先级别队列的前面处理。如果两个或更多的队列的优先级别相同, 则这些队列按轮询方式处理。
PRIQ的列队结构是无层次的T——你不能在队列中再定义队列。先定义root队列, 它被设置成占用所有带宽, 然后在root下定义子队列, 看看下面的例子:
Root Queue (2Mbps)
Queue A (priority 1)
Queue B (priority 2)
Queue C (priority 3)
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)与RED一同工作可以将链路上的任何拥塞通告给两个主机。它是通过启用RED在(返回的)数据包头部设置一个标志而非扔掉这个数据包。假设发送数据包的主机支持ECN, 它就能读取这个标志并从而节流它的网络通讯量。
更多有关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 on - 在一个接口上启用列队, 定义使用哪个调度器, 然后产生root队列
queue - 定义一个子队列的属性
altq指令的语法是:
altq on interface scheduler bandwidth bw qlimit qlim \
tbrsize size queue { queue_list }
interface - 在此网络接口上激活列队。
scheduler - 使用的列队调度器。有效值是cbq 和 priq。一个接口每次只能激活一个在调度器。
bw - 调度器可用总带宽。这可指定为一个绝对值加上后缀 b, Kb, Mb, 和 Gb 分别表示 比特, k, M, 和 G/秒, 或用一个接口带宽的百分比。
qlim - 一个队列可容纳数据包的最大值。这个参数是可选项, 默认是50。
size - 令牌桶调整器的大小, 以bytes为单位。如果没有指定, 这个大小取决于接口带宽。
queue_list - root队列下生成一个子队列列表。
例如:
这在接口fxp0上启用CBQ。总有效带宽设置为2Mbps, 定义了三个子队列: std, ssh, 和 ftp。
queue 指令的语法为:
name - 队列名。 这个队列名必须与altq定义的 queue_list.项内的一个名字匹配。对cbq来说, 这个队列名也可以匹配以前的队列指令的 queue_list 选项中的一个名字。队列名不能超过15个字符。
interface - 启用列队功能的网络接口。这是可选项, 如未指定将在所有接口上启用列队功能。
bw - 可用于队列的总有效带宽。这可指定为一个绝对值加上后缀 b, Kb, Mb, 和 Gb 分别表示 比特, k, M, 和 G/秒, 或用一个接口带宽的百分比。这个参数仅在使用cbq调度器时才能用。若未指定, 默认值为父系队列的100%带宽。
pri - 这个队列的优先级别。对cbq来说, 优先级别的从0到7, 而对priq是从0到15。级别0是最低优先级。若未指定, 使用默认值1。
qlim - 一个队列可容纳数据包的最大值。若未指定, 使用默认值50。
scheduler - 使用的调度器, 不是cbq就是priq, 必须与root队列一致。
sched_options - 更多的控制调度器行为选项:
default - 定义一个默认队列, 所有不匹配其它队列的数据包将在这个队列里列队, 一点也没错, 一个默认队列是必需的。
red - 在这个队列上启用随机早期检测(RED)。
rio - 以IN/OUT模式启用RED。这种模式下, RED将维持多个队列平均长度和多个极限值, 每个IP服务质量级别(QoS)对应一个。
ecn - 在这个队列上启用显式拥塞通告(ECN)。Ecn意味着red。
borrow - 这个队列可以向其父系队列借用带宽。这个选项只能在使用cbq调度器时才能指定。
queue_list - root队列下生成一个子队列列表。 一个 queue_list选项只能在使用cbq调度器时才能定义。
继续上面的例子:
这里设置了先前定义的子队列的参数。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关键字。例如, 一组过滤规则包含一行如下内容:
可以通过queue关键字将匹配这条规则的数据包分配到指定的队列:
当queue关键字与block指令一同使用时, 结果任何TCP RST或者ICMP不可抵达数据包全被分配到指定的队列。
注意:除了可以在altq里用指令定义这个队列外, 还可以在接口上定义:
队列在fxp0上被启用, 但是指定却发生在dc0上。 如果接口fxp的出站数据包匹配这条pass规则, 它们将在ftp队列里列队。这种类型的列队在路由器上十分有用。
通常queue关键字仅给定一个队列名, 但是如果指定了第二个队列名, 这个队列将被用于低延迟服务类型(ToS)的数据包和TCP ACK这类无数据有效负荷的数据包。在使用SSH是发现的一个很好的例子, SSH登录会话会被设定成低延迟服务类型而SCP和SFTP会话则不会。PF能利用这个信息, 将属于登录连接的数据包安排在一个不同的队列里与非登录数据包分, 这对优先处理登录连接的数据包再处理文件传输数据包很有用处。
这是将属于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 关键字时:
PF将把这个队列记录为状态表项, 所以出站的回程数据包到fxp0接口时可以匹配这个状态化连接, 并最终进入ssh队列。注意:尽管queue关键字用在了进站通讯过滤规则里, 但它的目的是为相应的出站通讯指定一个队列;上面的规则不为进站通讯列队。
例 #1: 小型, 家用网络
[ Alice ] [ Charlie ]
| | ADSL
---+-----+-------+------ dc0 [ OpenBSD ] fxp0 -------- ( Internet )
|
[ Bob ]
这个例子中, OpenBSD被用来担当小型家庭网络的Internet网关并带有三个工作站。这个网关承担包过滤和网络地址转换的工作。Internet连接是通过一条ADSL线路, 下载2Mbps, 上传640Kbps。
这个网络的队列策略:
保留 80Kbps 下载带宽给Bob, 这样他可以玩他的在线游戏而不会被Alice或Charlie的下载延迟。允许Bob在带宽空闲时使用多于80Kbps的带宽。
交互式的SSH和即时信息通讯将比普通通讯有更高的优先级。
DNS查询和回应是第二最高优先级。
出站的TCP ACK数据包将比其他出站通讯有更高的优先级。
下面是适合这个网络策略的规则集。注意这里仅显示了用以完成上述策略的pf.conf指令;未显示 nat, rdr, options 等。
例 #2: 公司网络
( IT Dept ) [ Boss's PC ]
| | T1
--+----+-----+---------- dc0 [ OpenBSD ] fxp0 -------- ( Internet )
| fxp1
[ COMP1 ] [ WWW ] /
| /
--+----------'
这个例子中, OpenBSD主机担当公司网络的防火墙。 公司在DMZ网段运行了一台WWW服务器, 客户可以通过FTP软件将自己的站点传送到这台服务器上。IT部门有自己的子网并连接到主要网络, 而老板有一台PC用来收发邮件和网上冲浪。通过一条T1线路连接到Internet上, 上行下载速度全1.5Mbps。 所有其它网段使用高速以太网(100Mbps)。
网络管理员决定使用如下策略:
限制WWW服务器和Internet之间任何方向的连接速度大于500Kbps的通讯
分配250Kbps给HTTP通讯。
分配250Kbps给"其它"通讯(例如, 非HTTP通讯)。
允许任何队列借用带宽, 最高至500Kbps。
给予WWW服务器和Internet之间的HTTP通讯比WWW服务器和Internet之间的其它通讯(例如ftp上传)更高的优先级别。
WWW服务器和内部网络之间的通讯可以完全使用网络提供的100Mbps带宽。
保留500Kbps带宽给IT部分是他们可以及时地下载最新软件更新。如果有带宽空闲允许他们使用500Kbps以上的带宽。
老板的PC到Internet间的通讯比其它通讯有更高的上传下载优先级。
下面是适合这个网络策略的规则集。注意这里仅显示了用以完成上述策略的pf.conf指令;未显示 nat, rdr, options 等。
--------------------------------------------------------------------------------
目录
列队
调度器
分类列队
优先列队
随机早期检测
显式拥塞通告
列队配置
为队列分配通讯
Example #1: 小型, 家用网络
Example #2: 公司网络
--------------------------------------------------------------------------------
列队
为某物列队就是在它们等候处理时按顺序储存。在一个计算机网络中, 当数据包从某台主机发出后, 它们进入一个等待操作系统处理的队列, 然后操作系统决定处理哪一个队列和其中的哪些数据包应该被处理。操作系统选择处理数据包的顺序可能影响网络性能。例如, 用户正在运行两个网络应用程序: 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队列的带宽。例如, 队列可用如下方式定义:
Root Queue (2Mbps)
Queue A (1Mbps)
Queue B (500Kbps)
Queue C (500Kbps)
这个例子中, 总带宽设定为2M/秒(Mbps), 然后带宽被分配给三个子队列。
可以通过在队列中定义新队列而扩展队列的级别, 这样可以在不同的用户和类之间平等地分配带宽, 以避免某些协议掠夺其它协议的带宽, 定义一个这样的队列:
Root Queue (2Mbps)
UserA (1Mbps)
ssh (50Kbps)
bulk (950Kbps)
UserB (1Mbps)
audio (250Kbps)
bulk (750Kbps)
http (100Kbps)
other (650Kbps)
注意分配给每个级别各队列的带宽总和不能多于分配给其父系队列的带宽。
如果一个队列的父系队列有闲置的带宽, 该队列可以向其父系队列借用, 这是因为这些闲置带宽无法被父系队列的其它子队列利用, 建立一个像这样的队列应该:
Root Queue (2Mbps)
UserA (1Mbps)
ssh (100Kbps)
ftp (900Kbps, borrow)
UserB (1Mbps)
如果ftp队列要超过900K的流量而UserA队列的流量不足1Mbps(因为ssh队列没有完全使用分配给它的100Kbps带宽), 那么ftp队列将向临时UserA借用带宽。这样的方案使ftp队列在过载时可以使用更多的带宽;但是一旦ssh队列的负荷增加, FTP队列必须归还临时借用的带宽。
CBQ给每个队列指派一个优先级别。如果两个队列同为一个父队列的子队列(换句话说, 如果两个队列在级别分支的同一层次上), 在网络拥塞时先处理优先级别高的那个队列。 如果两个队列的级别一样则采用轮询方式, 例如:
Root Queue (2Mbps)
UserA (1Mbps, priority 1)
ssh (100Kbps, priority 5)
ftp (900Kbps, priority 3)
UserB (1Mbps, priority 1)
CBQ将对UserA和UserB这两个队列采用轮询方式—— 不会照顾任何一个队列。当UserA队列被处理时, CBQ也会处理其子队列。本例中, ssh队列有更高的优先级所以在网络拥塞时会优先于ftp队列使用带宽。注意为什么ssh和ftp没有与UserA和UserB比较优先级呢, 这是因为它们不在一个层次分支上。
要了解CBQ理论更多细节请看 References on CBQ.
优先列队
优先列队(PRIQ)
将对个队列分配个一个网络接口, 同时每个队列被赋予一个优先级别。高优先级别队列永远在低优先级别队列的前面处理。如果两个或更多的队列的优先级别相同, 则这些队列按轮询方式处理。
PRIQ的列队结构是无层次的T——你不能在队列中再定义队列。先定义root队列, 它被设置成占用所有带宽, 然后在root下定义子队列, 看看下面的例子:
Root Queue (2Mbps)
Queue A (priority 1)
Queue B (priority 2)
Queue C (priority 3)
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)与RED一同工作可以将链路上的任何拥塞通告给两个主机。它是通过启用RED在(返回的)数据包头部设置一个标志而非扔掉这个数据包。假设发送数据包的主机支持ECN, 它就能读取这个标志并从而节流它的网络通讯量。
更多有关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 on - 在一个接口上启用列队, 定义使用哪个调度器, 然后产生root队列
queue - 定义一个子队列的属性
altq指令的语法是:
altq on interface scheduler bandwidth bw qlimit qlim \
tbrsize size queue { queue_list }
interface - 在此网络接口上激活列队。
scheduler - 使用的列队调度器。有效值是cbq 和 priq。一个接口每次只能激活一个在调度器。
bw - 调度器可用总带宽。这可指定为一个绝对值加上后缀 b, Kb, Mb, 和 Gb 分别表示 比特, k, M, 和 G/秒, 或用一个接口带宽的百分比。
qlim - 一个队列可容纳数据包的最大值。这个参数是可选项, 默认是50。
size - 令牌桶调整器的大小, 以bytes为单位。如果没有指定, 这个大小取决于接口带宽。
queue_list - root队列下生成一个子队列列表。
例如:
代码: 全选
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 }
interface - 启用列队功能的网络接口。这是可选项, 如未指定将在所有接口上启用列队功能。
bw - 可用于队列的总有效带宽。这可指定为一个绝对值加上后缀 b, Kb, Mb, 和 Gb 分别表示 比特, k, M, 和 G/秒, 或用一个接口带宽的百分比。这个参数仅在使用cbq调度器时才能用。若未指定, 默认值为父系队列的100%带宽。
pri - 这个队列的优先级别。对cbq来说, 优先级别的从0到7, 而对priq是从0到15。级别0是最低优先级。若未指定, 使用默认值1。
qlim - 一个队列可容纳数据包的最大值。若未指定, 使用默认值50。
scheduler - 使用的调度器, 不是cbq就是priq, 必须与root队列一致。
sched_options - 更多的控制调度器行为选项:
default - 定义一个默认队列, 所有不匹配其它队列的数据包将在这个队列里列队, 一点也没错, 一个默认队列是必需的。
red - 在这个队列上启用随机早期检测(RED)。
rio - 以IN/OUT模式启用RED。这种模式下, RED将维持多个队列平均长度和多个极限值, 每个IP服务质量级别(QoS)对应一个。
ecn - 在这个队列上启用显式拥塞通告(ECN)。Ecn意味着red。
borrow - 这个队列可以向其父系队列借用带宽。这个选项只能在使用cbq调度器时才能指定。
queue_list - root队列下生成一个子队列列表。 一个 queue_list选项只能在使用cbq调度器时才能定义。
继续上面的例子:
代码: 全选
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关键字用在了进站通讯过滤规则里, 但它的目的是为相应的出站通讯指定一个队列;上面的规则不为进站通讯列队。
例 #1: 小型, 家用网络
[ Alice ] [ Charlie ]
| | ADSL
---+-----+-------+------ dc0 [ OpenBSD ] fxp0 -------- ( Internet )
|
[ Bob ]
这个例子中, OpenBSD被用来担当小型家庭网络的Internet网关并带有三个工作站。这个网关承担包过滤和网络地址转换的工作。Internet连接是通过一条ADSL线路, 下载2Mbps, 上传640Kbps。
这个网络的队列策略:
保留 80Kbps 下载带宽给Bob, 这样他可以玩他的在线游戏而不会被Alice或Charlie的下载延迟。允许Bob在带宽空闲时使用多于80Kbps的带宽。
交互式的SSH和即时信息通讯将比普通通讯有更高的优先级。
DNS查询和回应是第二最高优先级。
出站的TCP ACK数据包将比其他出站通讯有更高的优先级。
下面是适合这个网络策略的规则集。注意这里仅显示了用以完成上述策略的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
例 #2: 公司网络
( IT Dept ) [ Boss's PC ]
| | T1
--+----+-----+---------- dc0 [ OpenBSD ] fxp0 -------- ( Internet )
| fxp1
[ COMP1 ] [ WWW ] /
| /
--+----------'
这个例子中, OpenBSD主机担当公司网络的防火墙。 公司在DMZ网段运行了一台WWW服务器, 客户可以通过FTP软件将自己的站点传送到这台服务器上。IT部门有自己的子网并连接到主要网络, 而老板有一台PC用来收发邮件和网上冲浪。通过一条T1线路连接到Internet上, 上行下载速度全1.5Mbps。 所有其它网段使用高速以太网(100Mbps)。
网络管理员决定使用如下策略:
限制WWW服务器和Internet之间任何方向的连接速度大于500Kbps的通讯
分配250Kbps给HTTP通讯。
分配250Kbps给"其它"通讯(例如, 非HTTP通讯)。
允许任何队列借用带宽, 最高至500Kbps。
给予WWW服务器和Internet之间的HTTP通讯比WWW服务器和Internet之间的其它通讯(例如ftp上传)更高的优先级别。
WWW服务器和内部网络之间的通讯可以完全使用网络提供的100Mbps带宽。
保留500Kbps带宽给IT部分是他们可以及时地下载最新软件更新。如果有带宽空闲允许他们使用500Kbps以上的带宽。
老板的PC到Internet间的通讯比其它通讯有更高的上传下载优先级。
下面是适合这个网络策略的规则集。注意这里仅显示了用以完成上述策略的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