OpenBSD支持另外两种日程:
* 基于类的队列
* 优先级队列
基于类的队列
基于类的队列(CBQ)是一种排序算法,它将网络连接带宽在很多队列和类中分摊,每个队列都拥有基于源、目的地址,端口号,协议等信息分配的网络流量。一个队列可以被随意配置借用它父队列的带宽,前提是父队列没有占用该带宽。队列也有优先级,例如SSH,它的数据包将提前于含有大量数据流的队列(例如FTP)被处理。
CBQ队列以分等级的方式部署,最高一级是根队列,用来定义全部带宽。子队列建立在根队列之下,每一个子队列可以分配根队列所规定的部分带宽。例如,定义如下队列:
Root Queue (2Mbps)
Queue A (1Mbps)
Queue B (500Kbps)
Queue C (500Kbps)
这里总带宽被设置为2Mbps,然后由子队列分割。
在子队列中仍然可以定义下一级策略。用来在不同用户中平等地分配带宽,同时对他们的流量进行分类,以便使某个协议不会抢占其他协议的带宽。具体队列结构定义如下:
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)
如果UserA队列实际占用的带宽小于1Mbps(比如ssh队列没用完全占用100Kbps),则ftp队列的流量超过900Kbps时,ftp子队列将占用UserA父队列中定义的剩余的带宽。通过这种途径可以使ftp子队列超过它所定义的负荷时得到更多的带宽。随着ssh子队列流量的增加,被占用的这些带宽将被接上:
返还。
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队列比较优先级,因为他们不在一个层上。
优先级队列
优先级队列(PRIQ)为一个网络接口上的多个队列逐一分配唯一的优先级。拥有高优先级的队列总是在低优先级队列前被处理。
PRIQ中的队列结构是平面的,你不可以在一个队列中定义子队列。根队列用来定义全部带宽,其他队列定义在根队列之后。请看如下实例:
Root Queue (2Mbps)
Queue A (priority 1)
Queue B (priority 2)
Queue C (priority 3)
上面定义了一个有2Mbps带宽的根队列和3个子队列。最高优先级的队列被先处理,该队列中的所有包均被处理完后,或者该队列是空,PRIQ将进一步处理下一优先级的队列。在一个队列中,各个包是按照先进先出原则进行处理。
需要注意的是当使用PRIQ时必须非常谨慎地进行设计,因为PRIQ的工作机制是先高后低,如果一个高优先级的队列收到的数据包是持续的流,那么它将延时处理低优先级的队列,更有甚者导致丢包。
随机早期检测
随机早期检测 (RED)是一种避免网络拥塞的算法,它通过确认队列没有超长来避免网络拥塞。实现方法是不停的计算队列的平均大小并与两个阈值比较,如果计算出的平均值低于小阈值将不会丢弃任何包;如果在两个阈值之间将通过计算概率丢掉一些包;换言之,如果计算的平均值越接近大阈直则被丢弃的包越多。当丢掉一些包时,RED随机选择从哪些连接丢包,占用大带宽的连接被丢包的几率高。
RED的用处非常大,因为它可以避免一种被称为全体同步的状态,也可以调整突发流量。全体同步指多个连接的数据包在同一时间被丢弃导致的吞吐量全部消失的情况。例如,如果承载10个FTP连接流量的一台路由器出现拥塞,大部分包被丢弃,总的流量将迅速下降,这并不是最好的处理方法,因为所有的FTP连接都降低了流量,换句话说,这个网络将不会再次发挥最大潜能。RED通过只在随机挑选的连接上丢包来避免上述情况。占用大带宽的连接被丢包的几率高,这样,占用大带宽的连接将受到节制,避免了拥塞,同时总流量迅速降低的
现象也不会出现。另外,RED可以处理突发流量,因为它在队列装满之前就开始丢弃数据包,当突发流量到来时,队列中有足够的空间保存新发来的数据包。
RED只能被用在传输协议有能力反馈拥塞指示的情况。在大多数情况下,这也就是说RED被用来处理TCP数据流而不是DUP或ICMP数据流。
外部拥塞告知
外部拥塞告知(ECN)与RED协作来发现两台主机网络通讯路径上的任何拥塞现象。原理是使RED在头包中设置一个标志位并返回而不是丢弃该包。假设发送端主机支持ECN,它将读到这个标志位信息并减少发出网络流量。
更多关于ECN的信息请参考RFC 3168
配置队列
自OpenBSD3.0后交互队列(ALTQ)就成为基本系统的一部分。到了OpenBSD3.3,ALTQ被集成到了PF中。ALTQ支持CBQ、PRIQ,也支持RED和ECN。
既然ALTQ被集成到了PF,那么PF就必须使队列工作。在开始一章有配置PF的介绍。
队列定义在pf.conf中,有两种指令模式:
* altq on - 在某个接口上开启队列,定义使用哪些日程,建立根队列
* queue - 定义子队列的属性
altq的语法为:
altq on interface scheduler bandwidth bw qlimit qlim \
tbrsize size queue { queue_list }
* interface - 开启队列的网络接口。
* scheduler - 使用的队列日程。可能的取值是cbq和priq。某个接口在某个时间只能启动1个日程。
* bw - 日程所能用到的所有带宽。可以使用后缀b,Kb,Mb和Gb表示,可以是具体的数值也可以是当前接口带宽的百分比。
* qlim - 队列保存数据包的最大数量。该值是可选的,默认50。
* size - 承载容量的大小,单位bytes。如果没有指定,将基于接口带宽自动设置。
* queue_list - 在根队列下建立的一个子队列列表。
实例:
altq on fxp0 cbq bandwidth 2Mb queue { std, ssh, ftp }
这条策略在接口fxp0启用CBQ,总带宽设置为2Mbps,定义了3个子队列:std,ssh和ftp。
队列指令的语法是:
queue name [on interface] bandwidth bw [priority pri] [qlimit qlim] \
scheduler ( sched_options ) { queue_list }
* name - 队列的名称,必须与altq中定义的队列列表中的某个队列名称一致。对于cbq,还可以与以前定义的队列列表中的某个名称一致。长度不能超过15个字符。
* interface - 队列所生效的网络接口,是可选项,如果没指定,将在所有接口生效。
* bw - 队列可以使用的总带宽。可以是具体数值加上后缀b,Kb,Mb和Gb来表示,也可以是总带宽的百分比。该参数只有在使用cbq日程时可用。
* pri - 队列的优先级。对于cbq,优先级的范围是0~7,对于priq,范围是0~15。0是最低的优先级,如果没定义,默认优先级为1。
* qlim - 队列可以保存的最大包数,默认50。
* scheduler - 日程,cbq或者priq。必须和根队列一致。
* sched_options - 控制日程行为的更多选项:
+ default - 定义默认队列,当包不匹配其他队列时的默认值。
+ red - 在当前队列启用RED。
+ rio - 对进/出启用RED。在这种状态下,RED会维护多个平均队列长度和平均门限值,每个IP服务质量级别对应一个。
+ ecn - 在当前队列启用ECN。
+ borrow - 队列可以向父类借用带宽值,只被用于cbq日程模式。
* queue_list - 在当前队列下建立的一系列子队列。只有在cbq日程模式下有效。
继续上述实例:
queue std bandwidth 50% cbq(default)
queue ssh { ssh_login, ssh_bulk }
queue ssh_login priority 4 cbq(ecn)
queue ssh_bulk cbq(ecn)
queue ftp bandwidth 500Kb priority 3 cbq(borrow red)
这里将设置前面已经定义的队列。Std队列分配了根队列的50%带宽,也就是1Mbps,并被设置为默认队列。Ssh队列定义了两个子队列,ssh_login和ssh_bulk。前者被分配了高于后者的优先级,并且都开启了ECN。ftp队列分配了500Kbps带宽,拥有第3优先级,当总带宽富余时它可以借用,同时也开启了RED。
为队列分配数据流
通过在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 Unreachable数据包将被分配到特定对列。
注意队列除了可以在altq语句生效外,还可以在接口上生效:
altq on fxp0 cbq bandwidth 2Mb queue { std, ftp }
queue std cbq(default)
queue ftp bandwidth 1.5Mb
pass in on dc0 from any to any port 21 queue ftp
fxp0接口上指定了队列,但是这个队列却可以在dc0上生效。如果数据包匹配了fxp0接口外的那条pass策略,它们将被应用到ftp队列。这种用法在路由器上用处很广。
一般情况下queue关键字后面只有一个队列,如果有另外一个,这个队列将被用处理低延时的包和TCP ACK的包,这种包没有有效数据载荷。例如,当使用SSH时,SSH的登录会话将设置为低延时性,然而SCP和SFTP会话则不会。PF可以通过这些信息对属于登录会话的数据包取出并重新排列,这对于赋予登录会话的数据包高优先级非常有用。
pass out on fxp0 from any to any port 22 queue(ssh_bulk, ssh_login)
这条策略将属于SSH登录连接的数据包分配给ssh_logind队列,属于SCP和SFTP连接的数据包分配给ssh_bulk队列。由于前者拥有比后者高的优先级,所以登录连接的数据包被优先处理。
在非对称连接情况下给TCP ACK包分配一个高优先级的队列将是很有用的,比如,ADSL就是一种非对称连接,因为它的上行和下行带宽不一致。如果上行通道已满,而恰在这时开始了下载动作,下载过程的启动需要客户端返回TCP ACK包,但此时该包由于上行阻塞不能及时到达下载服务器,下载过程将会进入等待状态。试验证明,为了取得好的效果,上行队列的带宽最好设置比实际带宽小一些。例如,一个ADSL线路最大上行带宽640Kbps,那么设置根队列600Kbps的带宽效果会很好。
使用keep state的队列策略:
pass in on fxp0 proto tcp from any to any port 22 flags S/SA \
keep state queue ssh
PF将在状态表中记录ssh队列,当数据从fxp0接口流出时如果符合表中的记录它将被放入ssh队列。注意,尽管queue关键字应用到了过滤规则中,目的在于影响流出的数据包,上述策略中队列不会对流入的数据包起作用。
实例 #1: 小型家庭网络
[ Alice ] [ Charlie ]
| | ADSL
---+-----+-------+------ dc0 [ OpenBSD ] fxp0 -------- ( Internet )
|
[ Bob ]
在这个例子中,OpenBSD充当一个家庭网的网关,提供包过滤和NAT服务,家庭网中有3个客户端,因特网连接是通过ADSL实现,并有2Mbps下行和640Kbps上行的带宽。
该网络的队列策略:
* 为Bob保留玩在线游戏的80Kbps下行带宽,以减少另外两人对他的影响,并且总带宽富余的情况下可以超出该限制。
* 交互的SSH和即时信息流量要有高于其他流量的优先级。
* DNS请求和反馈数据流要有第二高的优先级。
* 流出的TCP ACK 数据包的优先级要高于其他流出数据包的优先级。
下面是对应的策略(省略了其他部分策略,如rdr、nat等):
# 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 cbq(default)
queue ssh_im_in priority 4
queue dns_in 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部门有自己的子网,老板的电脑主要用来收发电子邮件和网页冲浪。防火墙通过1.5Mbps双向带宽的T1电路连接因特网,其他网段均使用快速以太网(100Mbps)。
实现上述要求的策略如下:
* 限制WWW服务器到因特网之间的双向流量——500Kbps。
* WWW服务器和内部网络之间没有流量限制。
* 赋予WWW服务器和因特网间的流量高于其他流量的优先级(例如FTP上传流量)。
* 为IT部门保留500Kbps的带宽使他们可以下载到最新的软件,同时如果总带宽富余,他们可以借用。
* 为老板访问因特网的流量赋予比其他访问因特网流量高的优先级。
下面是对应的策略(省略了其他部分策略,如rdr、nat等):
# 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
# www_ext_misc - all non-http traffic from the WWW server
# boss_ext - traffic coming from the boss‘s computer
queue std_ext cbq(default)
queue www_ext bandwidth 500Kb { www_ext_http, www_ext_misc }
queue www_ext_http priority 3 cbq(red)
queue www_ext_misc priority 1
queue boss_ext priority 3
# 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.
# boss_int - traffic to the boss‘s PC.
# www_int - traffic from the WWW server in the DMZ.
queue net_int bandwidth 1.0Mb { std_int, it_int, boss_int }
queue std_int cbq(default)
queue it_int bandwidth 500Kb cbq(borrow)
queue boss_int priority 3
queue www_int cbq(red)
# 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.
# net_dmz_misc - all non-http traffic. this is also the default queue.
queue internal_dmz # no special settings needed
queue net_dmz bandwidth 500Kb { net_dmz_http, net_dmz_misc }
queue net_dmz_http priority 3 cbq(red)
queue net_dmz_misc priority 1 cbq(default)
# ... 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
------------------------------------------------------------------------------
$OpenBSD: queueing.html,v 1.21 2004/06/11 20:26:53 saad Exp $
==============================================================================
PF: 地址池和负载均衡
------------------------------------------------------------------------------
目录
* 简介
* NAT 地址池
* 外来连接负载均衡
* 输出流量负载均衡
+ 规则集实例
------------------------------------------------------------------------------
简介
地址池是提供2个以上的地址供一组用户共享的。地址池可以是rdr规则中的重定向地址;可以是nat规则中的转换地址;也可以是route-to, reply-to, 和 dup-to filter选项中的目的地址。
有4种使用地址池的方法:
* bitmask - 截取被修改地址(nat规则的源地址;rdr规则的目标地址)的最后部分和地址池地址的网络部分组合。例如:如果地址池是192.0.2.1/24,而被修改地址是10.0.0.50,则结果地址是192.0.2.50。如果地址池是192.0.2.1/25,而被修改地址是10.0.0.130,这结果地址是192.0.2.2。
* random - 从地址池中随机选择地址.
* source-hash - 使用源地址 hash 来确定使用地址池中的哪个地址。这个方法保证给定的源地址总是被映射到同一个地址池。Hash算法的种子可以在source-hash关键字后通过16进制字符或者字符串来指定。默认情况下,pfctl(8)在规则集装入时会随机产生种子。
* round-robin - 在地址池中按顺序循环,这是默认方法,也是表中定义的地址池唯一的方法。
除了round-robin方法,地址池的地址必须表达成CIDR(Classless Inter-Domain Routing )的网络地址族。round-robin 方法可以接受多个使用列表和表的单独地址。
sticky-address 选项可以在 random 和round-robin 池类型中使用,保证特定的源地址始终映射到同样的重定向地址。
NAT 地址池
地址池在NAT规则中可以被用做转换地址。连接的源地址会被转换成使用指定的方法从地址池中选择的地址。这对于PF负载一个非常大的网络的NAT会非常有用。由于经过NAT的连接对每个地址是有限的,增加附加的转换地址允许NAT网关增大服务的用户数量。
在这个例子中,2个地址被用来做输出数据包的转换地址。对于每一个输出的连接,PF按照顺序循环使用地址。
nat on $ext_if inet from any to any -> { 192.0.2.5, 192.0.2.10 }
这个方法的一个缺点是成功建立连接的同一个内部地址不会总是转换为同一个外部地址。这会导致冲突,例如:浏览根据用户的ip地址跟踪登录的用户的web站点。一个可选择的替代方法是使用source-hash 方法,以便每一个内部地址总是被转换为同样的外部地址。,要实现这个方法,地址池必须是CIDR网络地址。
nat on $ext_if inet from any to any -> 192.0.2.4/31 source-hash
这条NAT规则使用地址池192.0.2.4/31 (192.0.2.4 - 192.0.2.5)做为输出数据包的转换地址。每一个内部地址会被转换为同样的外部地址,由于source-hash关键字的缘故。
外来连接负载均衡
地址池也可以用来进行外来连接负载均衡。例如,外来的web服务器连接可以分配到服务器群。
web_servers = "{ 10.0.0.10, 10.0.0.11, 10.0.0.13 }"
rdr on $ext_if proto tcp from any to any port 80 -> $web_servers \
round-robin sticky-address
成功的连接将按照顺序重定向到web服务器,从同一个源到来的连接发送到同一个服务器。这个sticky connection会和指向这个连接的状态一起存在。如果状态过期,sticky
connection也过期。那个主机的更多连接被重定向到按顺序的下一个web服务器。
输出流量负载均衡
地址池可以和route-to过滤选项联合使用,在多路径路由协议(例如BGP4)不可用是负载均衡2个或者多个因特网连接。通过对round-robin地址池使用route-to,输出连接可以平均分配到多个输出路径。
需要收集的附加的信息是邻近的因特网路由器IP地址。这要加入到route-to选项后来控制输入数据包的目的地址。
下面的例子通过2条到因特网的连接平衡输出流量:
lan_net = "192.168.0.0/24"
int_if = "dc0"
ext_if1 = "fxp0"
ext_if2 = "fxp1"
ext_gw1 = "68.146.224.1"
ext_gw2 = "142.59.76.1"
pass in on $int_if route-to \
{ ($ext_if1 $ext_gw1), ($ext_if2 $ext_gw2) } round-robin \
from $lan_net to any keep state
route-to 选项用来在收到流量的内部接口上指定平衡的流量经过各自的网关到输出的网络接口。注意route-to 选项必须在每个需要均衡的过滤规则上出现。返回的数据包会路由到它们出去时的外部接口(这是由ISP做的),然后正常路由回内部网络。
要保证带有属于$ext_if1源地址的数据包总是路由到$ext_gw1($ext_if2 和 $ext_gw2也是同样的),下面2行必须包括在规则集中:
pass out on $ext_if1 route-to ($ext_if2 $ext_gw2) from $ext_if2 \
to any
pass out on $ext_if2 route-to ($ext_if1 $ext_gw1) from $ext_if1 \
to any
最后,NAT也可以使用在输出接口中:
nat on $ext_if1 from $lan_net to any -> ($ext_if1)
nat on $ext_if2 from $lan_net to any -> ($ext_if2)
一个完整的输出负载均衡的例子应该是这个样子:
lan_net = "192.168.0.0/24"
int_if = "dc0"
ext_if1 = "fxp0"
ext_if2 = "fxp1"
ext_gw1 = "68.146.224.1"
ext_gw2 = "142.59.76.1"
# nat outgoing connections on each internet interface
nat on $ext_if1 from $lan_net to any -> ($ext_if1)
nat on $ext_if2 from $lan_net to any -> ($ext_if2)
# default deny
block in from any to any
block out from any to any
# pass all outgoing packets on internal interface
pass out on $int_if from any to $lan_net
# pass in quick any packets destined for the gateway itself
pass in quick on $int_if from $lan_net to $int_if
# load balance outgoing tcp traffic from internal network.
pass in on $int_if route-to \
{ ($ext_if1 $ext_gw1), ($ext_if2 $ext_gw2) } round-robin \
proto tcp from $lan_net to any flags S/SA modulate state
# load balance outgoing udp and icmp traffic from internal network
pass in on $int_if route-to \
{ ($ext_if1 $ext_gw1), ($ext_if2 $ext_gw2) } round-robin \
proto { udp, icmp } from $lan_net to any keep state
# general "pass out" rules for external interfaces
pass out on $ext_if1 proto tcp from any to any flags S/SA modulate state
pass out on $ext_if1 proto { udp, icmp } from any to any keep state
pass out on $ext_if2 proto tcp from any to any flags S/SA modulate state
pass out on $ext_if2 proto { udp, icmp } from any to any keep state
# route packets from any IPs on $ext_if1 to $ext_gw1 and the same for
# $ext_if2 and $ext_gw2
pass out on $ext_if1 route-to ($ext_if2 $ext_gw2) from $ext_if2 to any
pass out on $ext_if2 route-to ($ext_if1 $ext_gw1) from $ext_if1 to any
------------------------------------------------------------------------------
$OpenBSD: pools.html,v 1.12 2004/05/07 01:55:24 nick Exp $
==============================================================================
PF: 数据包标记
------------------------------------------------------------------------------
目录
* 简介
* 给数据包打标记
* 检查数据包标记
* 过滤策略
* 标记以太网的帧
------------------------------------------------------------------------------
简介
数据包标记是给数据包打内部标记的方法,以后可以在过滤和转换规则中使用。使用标记,有可能做这样的事情,比如在接口间产生信任关系,或者确定数据包是否已经经过了转换规则处理。也可能从基于规则的过滤中移出,开始执行基于策略的过滤。
.
给数据包打标记
要给数据包打标记,使用tag 关键字:
pass in on $int_if all tag INTERNAL_NET keep state
标记 INTERNAL_NET 会增加到任何匹配上述规则的数据包中。
注意keep state的使用; keep state (或者 modulate state/synproxy state) 在标记数据包通过的规则中使用。
标记也可以通过宏来打,比如:
name = "INTERNAL_NET"
pass in on $int_if all tag $name keep state
有一组预先定义的宏也可以被使用。
* $if - 接口
* $srcaddr - 源 IP 地址
* $dstaddr - 目的 IP 地址
* $srcport - 源端口
* $dstport - 目的端口
* $proto - 协议
* $nr - 规则号
这些宏在规则集装入时扩展,而不是运行时。
标记遵循以下规则:
* 标记是粘性的。一旦一个标记被匹配的规则打到一个数据包,就不能被删除。但它可以被不同的标记替换。
* 由于标记的粘性,打了标记的数据包会一直保持,即使所有的规则都没有使用这个标记。
* 一个数据包一次最多只能打一个标记。
* 标记是内部标识符,标记不会被送到网上。
看看下面的例子:
(1) pass in on $int_if tag INT_NET keep state
(2) pass in quick on $int_if proto tcp to port 80 tag \
INT_NET_HTTP keep state
(3) pass in quick on $int_if from 192.168.1.5 keep state
* 按照规则1,$int_if 接口上收到的数据包会打上INT_NET 标记。
* $int_if 接口上收到的目标端口80的数据包根据规则1首先打上INT_NET 标记,然后根据规则2,被INT_NET_HTTP 标记替代。
* $int_if 接口上收到的来自192.168.1.5的数据包根据规则3会方向,由于这是最终匹配规则,因此如果它们的目标端口是80,则标记是INT_NET_HTTP ,否则标记是INT_NET 。
标记除了适用于过滤规则以外, nat, rdr, binat转换规则也可以用tag关键字使用标记。
检查数据包标记
要检查先前已经打的标记,可以使用tagged关键字:
pass out on $ext_if tagged INT_NET keep state
在$ext_if输出的数据包为了匹配上述规则必须打上INT_NET标记。反转匹配也可以使用!操作:
pass out on $ext_if tagged ! WIFI_NET keep state
策略过滤
过滤策略提供了编写过滤规则集的不同方法。定义的策略设定规则,说明哪种流量放行,哪种流量阻塞。数据包被基于传统的标准如源/目的IP地址,协议等等分配到不同的策略。例如,检查下面的防火墙策略:
* 自内部LAN到DMZ的流量是允许的 (LAN_DMZ)。
* 自因特网到DMZ的服务器流量是允许的。 (INET_DMZ)
* 自因特网被重定向到spamd(8)是允许的 (SPAMD)
* 其他所有流量阻塞。
注意策略是如何覆盖所有通过防火墙的流量的。括号里面的项目指示这个策略项目将使用的标记。
需要过滤和转换规则来把数据包分配到不同的策略。
rdr on $ext_if proto tcp from to port smtp \
tag SPAMD -> 127.0.0.1 port 8025
block all
pass in on $int_if from $int_net tag LAN_INET keep state
pass in on $int_if from $int_net to $dmz_net tag LAN_DMZ keep state
pass in on $ext_if proto tcp to $www_server port 80 tag INET_DMZ keep
state
现在要设置定义策略的规则。
pass in quick on $ext_if tagged SPAMD keep state
pass out quick on $ext_if tagged LAN_INET keep state
pass out quick on $dmz_if tagged LAN_DMZ keep state
pass out quick on $dmz_if tagged INET_DMZ keep state
现在要建立整个规则集,修改分类规则。例如,如果pop3/SMTP服务器增加到了DMZ区,就需要增加针对POP3和SMTP流量的分类,如下:
mail_server = "192.168.0.10"
...
pass in on $ext_if proto tcp to $mail_server port { smtp, pop3 } \
tag INET_DMZ keep state
Email 流量会作为INET-DMZ策略的条目被放行。t
完整的规则:
# macros
int_if = "dc0"
dmz_if = "dc1"
ext_if = "ep0"
int_net = "10.0.0.0/24"
dmz_net = "192.168.0.0/24"
www_server = "192.168.0.5"
mail_server = "192.168.0.10"
table persist file "/etc/spammers"
# classification -- classify packets based on the defined firewall
# policy.
rdr on $ext_if proto tcp from to port smtp \
tag SPAMD -> 127.0.0.1 port 8025
block all
pass in on $int_if from $int_net tag LAN_INET keep state
pass in on $int_if from $int_net to $dmz_net tag LAN_DMZ keep state
pass in on $ext_if proto tcp to $www_server port 80 tag INET_DMZ keep state
pass in on $ext_if proto tcp to $mail_server port { smtp, pop3 } \
tag INET_DMZ keep state
# policy enforcement -- pass/block based on the defined firewall policy.
pass in quick on $ext_if tagged SPAMD keep state
pass out quick on $ext_if tagged LAN_INET keep state
pass out quick on $dmz_if tagged LAN_DMZ keep state
pass out quick on $dmz_if tagged INET_DMZ keep state
标记以太网帧
打标记可以在以太网级别进行,如果执行标记/过滤的机器同时做为网桥。通过创建使用tag关键字的网桥过滤规则,PF可以建立基于源/目的MAC地址的过滤规则。网桥规则可以由brconfig(8)命令产生,例如:
# brconfig bridge0 rule pass in on fxp0 src 0:de:ad:be:ef:0 \
tag USER1
然后在 pf.conf文件中:
pass in on fxp0 tagged USER1
------------------------------------------------------------------------------
$OpenBSD: tagging.html,v 1.4 2004/05/07 01:55:24 nick Exp $
==============================================================================
* 附加主题
PF: 日志
------------------------------------------------------------------------------
目录
* 简介
* 读取日志文件
* 导出日志
* 通过Syslog记录日志
------------------------------------------------------------------------------
简介
PF的包日志是由pflogd(8)完成的,它通过监听pflog0接口然后将包以tcpdump(8)二进制格式写入日志文件(一般在/val/log/pflog)。过滤规则定义的日志和log-all关键字所定义的日志都是以这种方式记录的。
读取日志文件
由pflogd生成的二进制格式日志文件不能通过文本编辑器读取,必须使用Tcpdump来查看日志。
使用如下格式查看日志信息:
# tcpdump -n -e -ttt -r /var/log/pflog
使用tcpdump(8)查看日志文件并不是实时的,若要实时查询日志信息需加上pflog0参数:
# tcpdump -n -e -ttt -i pflog0
注意:当查看日志时需要特别注意tcpdump的详细协议解码(通过在命令行增加-v参数实现)。
Tcpdump的详细协议解码器并不具备完美的安全历史,至少在理论上是这样。日志记录设备所记载的部分包信息可能会引发延时攻击,因此推荐在查询日志文件信息之前先将该日志文件从防火墙上移走。
另外需要注意的是对日志文件的安全访问。默认情况下,pflogd 将在日志文件中记录96字节的包信息。访问日志文件将提供访问部分敏感包信息的途径(就像telnet(1)或者ftp(1)的用户名和密码)。
导出日志
由于pflogd以tcpdump二进制格式记录日志信息,因此当回顾这些日志时可以使用tcpdump的很多特点。例如,只查看与特定端口匹配的包:
# tcpdump -n -e -ttt -r /var/log/pflog port 80
甚至可以限定具体的主机和端口:
# tcpdump -n -e -ttt -r /var/log/pflog port 80 and host 192.168.1.3
同样的方法可以应用到直接从pflog0接口读取的信息:
# tcpdump -n -e -ttt -i pflog0 host 192.168.4.2
注意这与包被记录到pflogd日志文件不相冲突;上述语句只以包被记录的形式显示。
除了使用标准的tcpdump(8)过滤规则外,OpenBSD的tcpdump过滤语言为读取pflogd而被扩展:
* ip -IPv4版本地址。
* ip6 - IPv6版本地址。
* on int - 包通过int接口。
* ifname int - 与 on int相同.
* rulenum num - 包匹配的过滤规则编号为num。
* action act - 对包的操作。可能是pass(通过)或者block(阻断)。
* reason res - 执行对包操作的原因。可能的原因是match(匹配), bad-offset, fragment, short, normalize(规格化), memory(内存)。
* inbound -入栈包。
* outbound - 出栈包。
举例:
# tcpdump -n -e -ttt -i pflog0 inbound and action block and on wi0
这将以实时方式显示被wi0接口阻断的入栈包的日志信息。
通过Syslog记录日志
很多情况下需要将防火墙的日志记录以ASCII代码格式存储,或者(同时)把这些日志存到远程的日志服务器上。这些可以通过两个小的脚本文件实现,是对openbsd配置文件和syslogd(8),日志守护进程的少许修改。Syslogd进程以ASCII格式存储日志,同时可以将日志存储到远程日志服务器。
首先我们必须建立一个用户,pflogger,使用 /sbin/nologin shell.最简单的建立用户的方法是使用adduser(8)。
完成后建立如下两个脚本:
/etc/pflogrotate
FILE=/home/pflogger/pflog5min.$(date "+%Y%m%d%H%M")
kill -ALRM $(cat /var/run/pflogd.pid)
if [ $(ls -l /var/log/pflog | cut -d " " -f 8) -gt 24 ]; then
mv /var/log/pflog $FILE
chown pflogger $FILE
kill -HUP $(cat /var/run/pflogd.pid)
fi
/home/pflogger/pfl2sysl
for logfile in /home/pflogger/pflog5min* ; do
tcpdump -n -e -ttt -r $logfile | logger -t pf -p local0.info
rm $logfile
done
编辑root的cron 任务:
# crontab -u root -e
增加如下两行:
# rotate pf log file every 5 minutes
0-59/5 * * * * /bin/sh /etc/pflogrotate
为用户pflogger建立一个cron任务:
# crontab -u pflogger -e
增加如下两行:
# feed rotated pflog file(s) to syslog
0-59/5 * * * * /bin/sh /home/pflogger/pfl2sysl
将下行增加到 /etc/syslog.conf:
local0.info /var/log/pflog.txt
如果需要日志记录到远程日志服务器,增加:
local0.info @syslogger
确定主机syslogger已在hosts(5)中定义。
建立文件 /var/log/pflog.txt 使 syslog 可以向该文件写入日志:
# touch /var/log/pflog.txt
重启syslogd使变化生效:
# kill -HUP $(cat /var/run/syslog.pid)
所有符合标准的包将被写入/var/log/pflog.txt. 如果增加了第二行,这些信息也将被存入远程日志服务器syslogger。
脚本 /etc/pflogrotate 将执行,然后删除 /var/log/pflog ,因此
rotation of pflog by newsyslog(8) 不再必需可以被禁用。然而, /var/log/pflog.txt 替代 /var/log/pflog and rotation of it 要被启用。 改变 /etc/newsyslog.conf 如下:
#/var/log/pflog 600 3 250 * ZB /var/run/pflogd.pid
/var/log/pflog.txt 600 7 * 24
PF 将日志以ASCII格式记录到/var/log/pflog.txt. 如果这样配置 /etc/syslog.conf, 系统将把日志存到远程服务器。存储过程不会马上发生,但是会在符合条件的包出现在文件中前5-6分钟实现。
------------------------------------------------------------------------------
$OpenBSD: logging.html,v 1.15 2004/05/07 01:55:23 nick Exp $
==============================================================================
PF: 性能
------------------------------------------------------------------------------
“PF可以处理多少带宽?”
“我需要多少台计算机处理因特网连接?”
这个问题没有简单的答案。对于一些应用程序来说,一台486/66主机,带有2个比较好的ISA网卡在做过滤和NAT时接近5Mbps,但是对于其他应用程序,一个更快的计算机加上更有效的PCI网卡也会显得能力不足。真正的问题不是每秒处理的位数而是每秒处理的包数和规则集的复杂程度。
体现PF性能的几个参数:
* 每秒处理包的数量. 一个1500字节的包和一个只有1个字节的包所需要的处理过程的数目几乎是一样的。每秒处理包的数量标志着状态表和过滤规则集在每秒内被评估的次数,标志着一个系统最有效的需求(在没有匹配的情况下)。
* 系统总线性能. ISA 总线最大带宽 8MB/秒, 当处理器访问它时, 它必须降速到80286的有效速度,不管处理器的真实处理速度如何,PCI总线有更有效的带宽,与处理器的冲突更小。
* 网卡的效率. 一些网卡的工作效率要高于其他网卡。 基于Realtek 8139 (rl(4)) 的网卡性能较低,而基于Intel 21143 (dc(4)) 的网卡性能较好。为了取得更好的性能,建议使用千兆网卡,尽管所连接的网络不是千兆网,这些千兆网卡拥有高级的缓存,可以大幅提高性能。
* 规则集的设计和复杂性。规则越复杂越慢。越多的包通过keep和quick方式过滤,性能越好。对每个包的策略越多,性能越差。
* 值得一提: CPU 和内存。由于PF是基于内核的进程,它不需要swap空间。所以,如果你有足够的内存,它将运行很好,如果没有,将受影响。不需要太大量的内存。 32MB内存对小型办公室或者家庭应用足够,300MHz的cpu如果配置好网卡和规则集,足够满足要求。
人们经常询问PF的基准点。唯一的基准是在一个环境下系统的性能。不考虑环境因素将影响所设计的防火墙的系统性能。
PF 曾经在非常大流量的系统中工作,同时PF的开发者也是它的忠实用户。
------------------------------------------------------------------------------
$OpenBSD: perf.html,v 1.14 2004/05/07 01:55:24 nick Exp $
==============================================================================
PF: 研究 FTP
------------------------------------------------------------------------------
目录
* FTP 模式
* 工作在防火墙之后的FTP客户端
* PF "自保护" FTP服务器
* 被运行NAT的外部PF防火墙所保护的FTP服务器
* FTP的更多信息
------------------------------------------------------------------------------
FTP 模式
FTP是一种协议,它可以追溯到因特网发展初期,那时的因特网规模小,联网的计算机彼此友好,过滤和严格安全性在那时不是必须的。FTP设计之初就没有考虑包过滤、穿透防火墙和NAT。
FTP的工作模式分为被动(passive)和主动(active)两种。通常这两种选择被用来确定哪边有防火墙问题。实际上,为了方便用户你应该全部支持这两种模式。
在active模式下,当用户访问远程FTP服务器并请求一个文件信息时,那台FTP服务器将与该用户建立一个新的连接用来传输请求的数据,这被称为数据连接。具体过程为:客户端随机选择一个端口号,在该端口监听的同时将端口号传给服务器,由服务器向客户端的该端口发起连接请求,然后传递数据。在NAT后的用户访问FTP服务器的时候会出现问题,由于NAT的工作机制,服务器将向NAT网关的外部地址的所选端口发起连接,NAT网关收到该信息后将在自己的状态表中查找该端口对应的内部主机,由于状态表中不存在这样的记录,因此该包被丢弃,导致无法建立连接。
在passive模式下(OpenBSD的ftp客户端默认模式),由客户端请求服务器随机选择一个端口并在此端口监听,服务器通知客户端它所选择的端口号,等待客户端连接。不幸的是,ftp服务器前的防火墙可能会阻断客户端发往服务器的请求信息。OpenBSD的ftp(1)默认使用passive模式;要强制改为active模式,使用-A参数,或者在“ftp>”提示符下使用命令“passive off”关闭passive模式。
工作在防火墙之后的FTP客户端
如前所述,FTP对NAT和防火墙支持不好。
包过滤机制通过将FTP数据包重定向到一个FTP代理服务器解决这一问题。这一过程将引导FTP数据包通过NAT网关/防火墙。OpenBSD和PF使用的FTP代理是ftp-proxy(8),可以通过在pf.conf中的NAT章节增加下列信息激活该代理:
rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 \
port 8021
这条语句的解释为:在内部接口上的ftp数据包被重定向到本机的8021端口
显然该代理服务器应该已在OpenBSD中启动并运行。配置方法为在/etc/inetd.conf中增加下列信息:
127.0.0.1:8021 stream tcp nowait root /usr/libexec/ftp-proxy \
ftp-proxy
重启系统或者通过下列命令发送一个‘HUP’标记来生效:
kill -HUP `cat /var/run/inetd.pid`
ftp代理在8021端口监听,上面的rdr语句也是将数据包转发到这一端口。这一端口号是可选的,因为8021端口没有被其他应用程序占用,因此不失为一个好的选择。
请注意ftp-proxy(8)是用来帮助位于PF过滤器后的ftp客户端传递信息;并不用于PF过滤器后的ftp服务器。
PF“自保护”FTP服务器
当PF运行在一个FTP服务器上,而不是单独的一台防火墙。这种情况下处理passive模式的FTP连接请求时FTP服务器将随机取一个较大的TCP端口接收数据。默认情况下OpenBSD的本地FTP服务器ftpd(8)使用49152~65535范围内的端口,显然,必须要有对应的过滤规则放行这些端口的数据:
pass in on $ext_if proto tcp from any to any port 21 keep state
pass in on $ext_if proto tcp from any to any port > 49151 \
keep state
如果需要可以调整上述端口范围。在OpenBSD的ftpd(8)环境下,可以通过sysctl(8)进行调整 net.inet.ip.porthifirst 和net.inet.ip.porthilast。
使用NAT外部 PF防火墙保护FTP服务器
这种情况下,防火墙必须将数据重定向到FTP服务器。为了讨论方便,我们假设该FTP服务器使用标准的OpenBSD ftpd(8),并使用默认端口范围。
这里有个例子
ftp_server = "10.0.3.21"
rdr on $ext_if proto tcp from any to any port 21 -> $ftp_server \
port 21
rdr on $ext_if proto tcp from any to any port 49152:65535 -> \
$ftp_server port 49152:65535
# in on $ext_if
pass in quick on $ext_if proto tcp from any to $ftp_server \
port 21 keep state
pass in quick on $ext_if proto tcp from any to $ftp_server \
port > 49151 keep state
# out on $int_if
pass out quick on $int_if proto tcp from any to $ftp_server \
port 21 keep state
pass out quick on $int_if proto tcp from any to $ftp_server \
port > 49151 keep state
FTP的更多信息
过滤FTP和FTP如何工作的更多信息可以参考下面的白皮书。
ftp reviewed!(原文有超级链接)
------------------------------------------------------------------------------
$OpenBSD: 1.14 2004/05/07 01:55:23 nick Exp $
==============================================================================
PF: pf验证: 用Shell 进行网关验证
------------------------------------------------------------------------------
目录
* 简介
* 配置
+ 将authpf连入主策略集
+ 配置加载的策略
+ 访问控制列表
+ 将authpf配置为用户shell
* 查看登陆者
* 更多信息
* 实例
------------------------------------------------------------------------------
简介
Authpf(8)是身份认证网关的用户shell。身份认证网关类似于普通网关,只不过用户必须在网关上通过身份验证后才能使用该网关。当用户shell被设置为/usr/sbin/authpf时(例如,代替了ksh(1),csh(1)等),并且用户通过SSH登录,authpf将对pf(4)策略集做必要的修改以便该用户的数据包可以通过过滤器或者(和)使用NAT、重定向功能。一旦用户退出登录或者连接被断开,authpf将移除加载到该用户上的所有策略,同时关闭该用户打开的所有会话。因此,只有当用户保持着他的SSH会话进程时他才具备透过防火墙发送数据包的能力。
Authpf通过向附加到锚点的命名策略集增加策略来改变pf(4)的策略集。每次用户进行身份验证,authpf建立一个新的命名策略集,并将已经配置好的filter、nat、binat和rdr规则加载上去。被authpf所加载的策略可以被配置为针对单独的一个用户相关或者针对总体。
Authpf可以应用在:
* 在允许用户访问因特网之前进行身份验证。
* 赋予特殊用户访问受限网络的权利,例如管理员。
* 只允许特定的无线网络用户访问特定的网络。
* 允许公司员工在任何时候访问公司网络,而公司之外的用户不能访问,并可以将这些用户重定向到特定的基于用户名的资源(例如他们自己的桌面)。
* 在类似图书馆这样的地方通过PF限制guest用户对因特网的访问。Authpf可以用来向已注册用户开放完全的因特网连接。
Authpf通过syslogd(8)记录每一个成功通过身份验证用户的用户名、IP地址、开始结束时间。通过这些信息,管理员可以确定谁在何时登陆,也使得用户对其网络流量负责。
配置
配置authpf的基本步骤大致描述如下。详细的信息请查看man手册。
将authpf连入主策略集
通过使用锚点策略将authpf连入主策略集:
nat-anchor authpf
rdr-anchor authpf
binat-anchor authpf
anchor authpf
锚点策略放入策略集的位置就是PF中断执行主策略集转为执行authpf策略的位置。上述4个锚点策略并不需要全部存在,例如,当authpf没有被设置加载任何nat策略时,nat-anchor策略可被省略。
配置加载的策略
Authpf通过下面两个文件之一加载策略:
* /etc/authpf/users/$USER/authpf.rules
* /etc/authpf/authpf.rules
第一个文件包含只有当用户$USER(将被替换为具体的用户名)登录时才被加载的策略。当特殊用户(例如管理员)需要一系列不同于其他默认用户的策略集时可以使用每用户策略配置。第二个文件包含没定义自己的authpf.rules文件的用户所默认加载的策略。如果用户定义的文件存在,将覆盖默认文件。这两个文件至少存在其一,否则authpf将不会工作。
过滤器和传输策略与其他的PF策略集语法一样,但有一点不同:authpf允许使用预先定义的宏:
* $user_ip - 登录用户的IP地址
* $user_id - 登录用户的用户名
推荐使用宏$user_ip,只赋予通过身份验证的计算机透过防火墙的权限。
访问控制列表
可以通过在/etc/authpf/banned/目录下建立以用户名命名的文件来阻止该用户使用authpf。文件的内容将在authpf断开与该用户的连接之前显示给他,这为通知该用户被禁止访问的原因并告知他解决问题联系人提供了一个便捷的途径。
相反,有可能只允许特定的用户访问,这时可以将这些用户的用户名写入/etc/authpf/authpf.allow文件。如果该文件不存在或者文件中输入了“*”,则authpf将允许任何成功通过SSH登录的用户进行访问(没有被明确禁止的用户)。
如果authpf不能断定一个用户名是被允许还是禁止,它将打印一个摘要信息并断开该用户的连接。明确禁止将会使明确允许失效。
将authpf配置为用户shell
authpf必须作为用户的登录shell才能正常工作。当用户成功通过sshd(8)登录后,authpf将被作为用户的shell执行。它将检查该用户是否有权使用authpf,并从适当的文件中加载策略,等等。
有两种途径将authpf设置为用户shell:
1.为每个用户手动使用chsh(1), vipw(8), useradd(8), usermod(8),等。
2.通过把一些用户分配到一个登录类,在文件/etc/login.conf中改变这个登录类的shell属性
查看登陆者
一旦用户成功登录,并且authpf调整了PF的策略,authpf将改变它的进程名以显示登录者的用户名和IP地址:
# ps -ax | grep authpf
23664 p0 Is+ 0:00.11 -authpf: (authpf)
在这里用户chalie从IP地址为192.168.1.3的主机登录。用户可以通过向authpf进程发送SIGTERM信号退出登录。Authpf也将移除加载到该用户上的策略并关闭任何该用户打开的会话连接。
# kill -TERM 23664
更多信息
请查询man手册
实例
OpenBSD网关通过authpf对一个大型校园无线网的用户进行身份验证。一旦某个用户验证通过,假设他不在禁用列表中,他将被允许SSH并访问网页(包括安全网站https),也可以访问该校园的任一个DNS服务器。
文件 /etc/authpf/authpf.rules包含下列策略:
wifi_if = "wi0"
dns_servers = "{ 10.0.1.56, 10.0.2.56 }"
pass in quick on $wifi_if proto udp from $user_ip to $dns_servers \
port domain keep state
pass in quick on $wifi_if proto tcp from $user_ip to port { ssh, http, \
https } flags S/SA keep state
管理员charlie除了网页冲浪和使用SSH外还需要访问校园网的SMTP和POP3服务器。下列策略被配置在/etc/authpf/users/charlie/authpf.rules 中:
wifi_if = "wi0"
smtp_server = "10.0.1.50"
pop3_server = "10.0.1.51"
dns_servers = "{ 10.0.1.56, 10.0.2.56 }"
pass in quick on $wifi_if proto udp from $user_ip to $dns_servers \
port domain keep state
pass in quick on $wifi_if proto tcp from $user_ip to $smtp_server \
port smtp flags S/SA keep state
pass in quick on $wifi_if proto tcp from $user_ip to $pop3_server \
port pop3 flags S/SA keep state
pass in quick on $wifi_if proto tcp from $user_ip to port { ssh, http, \
https } flags S/SA keep state
定义在/etc/pf.conf中的主策略集配置如下:
# macros
wifi_if = "wi0"
ext_if = "fxp0"
scrub in all
# filter
block drop all
pass out quick on $ext_if proto tcp from $wifi_if:network flags S/SA \
modulate state
pass out quick on $ext_if proto { udp, icmp } from $wifi_if:network \
keep state
pass in quick on $wifi_if proto tcp from $wifi_if:network to $wifi_if \
port ssh flags S/SA keep state
anchor authpf in on $wifi_if
该策略集非常简单,作用如下:
* 阻断所有(默认拒绝)。
* 放行外部网卡接口上的来自无线网络并流向外部的TCP,UDP和ICMP数据包。
* 放行来自无线网络,目的地址为网关本身的SSH数据包。该策略是用户登录所必须的。
* 在无线网络接口上为流入数据建立锚点“authpf”。
设计主策略集的主导思想为:阻断任何包并允许尽可能小的数据流通过。在外部接口上流出数据包是允许的,但是默认否策略阻断了由无线接口进入的数据包。用户一旦通过验证,他们的数据包被允许通过无线接口进入并穿过网关到达其他网络。
------------------------------------------------------------------------------
$OpenBSD: authpf.html,v 1.7 2004/05/07 01:55:23 nick Exp $
==============================================================================
实例:家庭和小型办公室防火墙
PF: 实例:家庭和小型办公室防火墙
------------------------------------------------------------------------------
目录
* 概况
+ 网络
+ 目标
+ 准备
* 规则集
+ 宏
+ 选项
+ 规格化
+ NAT
+ 重定向
+ 过滤规则
* 完整规则集
------------------------------------------------------------------------------
概况
在这个例子中,PF作为防火墙和NAT网关运行在OpenBSD机器上,为家庭或办公室的小型网络提供服务。总的目标是向内部网提供因特网接入,允许从因特网到防火墙的限制访问。下面将详细描述:
网络
网络配置如下:
[ COMP1 ] [ COMP3 ]
| | ADSL
---+------+-----+------- fxp0 [ OpenBSD ] ep0 -------- ( 因特网 )
|
[ COMP2 ]
内部网有若干机器,图中只划出了3台,这些机器除了COMP3之外主要进行网页冲浪、电子邮件、聊天等;COMP3运行一个小型web服务器。内部网使用192.168.0.0/24网段。
OpenBSD网关运行在 Pentium 100计算机上,装有两块网卡:一个3com 3c509B(ep0),另一个Intel EtherExpress Pro/100(fxp0)。该网关通过ADSL连接到因特网,同时通过NAT向内网共享因特网连接。外部网卡的IP地址动态分配。
目标
* 向内部网络的每台计算机提供无限制的因特网接入。
* 启用一条“默认拒绝”策略。
* 允许下列来自因特网的请求访问防火墙:
+ SSH (TCP 端口 22): 用来远程维护防火墙。
+ Auth/Ident (TCP 端口 113): SMTP和IRC服务用到的端口。
+ ICMP Echo Requests: ping(8)用到的ICMP包类型。
* 重定向访问80端口(访问web的请求)的请求到计算机COMP3,同时,允许指向COMP3计算机的80端口的数据流过防火墙。
* 记录外部网卡接口上的过滤日志。
* 默认为阻断的包返回一个 TCP RST 或者 ICMP Unreachable 信息。
* 尽量保持策略集简单并易于维护。
准备
这里假设作为网关的OpenBSD主机已经配置完成,包括IP网络配置,因特网连接和设置net.inet.ip.forwarding 的值为1。
规则集
下面将逐步建立策略集以满足上诉要求。
宏
定义下列宏以增强策略集的可维护性和可读性:
int_if = "fxp0"
ext_if = "ep0"
tcp_services = "{ 22, 113 }"
icmp_types = "echoreq"
priv_nets = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }"
comp3 = "192.168.0.3"
前两行定义了发生过滤的网络接口。第3、4行定义了向因特网开放的服务端口号(SSH和ident/auth)和允许访问防火墙的ICMP包类型。第5行定义了回送地址段和RFC1918定义的私有地址段。最后一行定义了主机COMP3的IP地址。
注意:如果ADSL接入因特网需要PPPoE,则过滤和NAT将发生在tun0接口上而不是ep0接口。
选项
下列两个选项用来设置阻断后的默认操作为反馈,并在外部接口设置开启日志记录:
set block-policy return
set loginterface $ext_if
流量整修
没有理由不起用对所有进入防火墙的所有包进行规格化,因此只需要简单的一行:
scrub in all
NAT(网络地址转换)
为所有内部网启用NAT可以通过下列策略:
nat on $ext_if from $int_if:network to any -> ($ext_if)
由于外部网卡的IP地址是动态获得的,因此在外部网卡接口处增加小括号以使当IP地址发生变化时PF可以自适应。
重定向
第一个需要重定向策略的是ftp-proxy(8),只有这样内部网上的FTP客户端才可以访问因特网上的FTP服务器。
rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 port 8021
注意这条策略只捕获到21端口的数据包,如果用户通过其他端口访问FTP服务器,则在定义目的端口时需要使用list(列表),例如: from any to any port { 21, 2121 }。
第二个重定向策略捕获因特网上的用户访问防火墙80端口的数据包。用户试图访问网络的web服务器时将产生合法的访问该端口的数据包,这些连接请求需要重定向到主机COMP3:
rdr on $ext_if proto tcp from any to any port 80 -> $comp3
过滤规则
过滤规则第一行是默认否规则:
block all
这时没有任何数据包可以流过防火墙,甚至来自内部网络的数据包。下面的规则将逐个依据上面提到的目标开启防火墙上的虚拟接口。
每个Unix系统都有一个“loopback(回送)”接口,它是用于系统中应用程序间通信的虚拟网络接口。在OpenBSD中,回送接口是lo(4)。
pass quick on lo0 all
下一步,由RFC 1918定义的私有地址将在外部网卡接口的进和出方向被阻断。这些地址不应该出现在公网上,通过阻断这些地址可以保证防火墙不向外部网泄漏内网地址,同时也阻断了来自外部网中源地址为这些私有地址的数据包流入内网。
block drop in quick on $ext_if from $priv_nets to any
block drop out quick on $ext_if from any to $priv_nets
这里block drop用来通知PF停止反馈TCP RST或者ICMP Unreachabel 数据包。因为RFC 1918规定的地址不会存在于因特网上,发往那些地址的数据包将没有意义。Quick 选项用来通知PF如果这条规则匹配则不再进行其他规则的匹配操作,来自或流向$priv_nets的数据包将被立即丢弃。
现在将打开因特网上的一些服务所用到的端口:
pass in on $ext_if inet proto tcp from any to ($ext_if) \
port $tcp_services flags S/SA keep state
通过在宏$tcp_services中定义服务端口可以更方便的进行维护。开放UDP服务也可一模仿上述语句,只不过改为proto udp。
已经有了一条rdr策略将web访问请求转发到主机COMP3上,我们必须建立另一条过滤规则使得这些访问请求可以通过防火墙:
pass in on $ext_if proto tcp from any to $comp3 port 80 \
flags S/SA synproxy state
考虑到安全问题,我们使用TCP SYN Proxy保护web服务器——synproxy state。
现在将允许ICMP包通过防火墙:
pass in inet proto icmp all icmp-type $icmp_types keep state
类似于宏$tcp_services,当需要增加允许进入防火墙的ICMP数据包类型时可以容易地编辑宏$icmp_types。注意这条策略将应用于所有网络接口。
现在数据流必须可以正常出入内部网络。我们假设内网的用户清楚自己的所作所为并且确定不会导致麻烦。这并不是必然有效的假设,在某些环境下更具限制性的策略集会更适合。
pass in on $int_if from $int_if:network to any keep state
上面的策略将允许内网中的任何计算机发送数据包穿过防火墙;然而,这并没有允许防火墙主动与内网的计算机建立连接。这是一种好的方法吗?评价这些需要依靠网络配置的一些细节。如果防火墙同时充当DHCP服务器,它需要在分配一个地址之前ping一下该地址以确认该地址没有被占用。允许防火墙访问内部网络同时也允许了在因特网上通过ssh控制防火墙的用户访问内网。请注意禁止防火墙直接访问内网并不能带来高安全性,因为如果一个用户可以访问防火墙,他也可以改变防火墙的策略。增加下列策略可以使防火墙具备访问内网的能力:
pass out on $int_if from any to $int_if:network keep state
如果这些策略同时存在,则keep state选项将不是必须的;所有的数据包都可以流经内网接口,因为一条策略规定了双向放行数据包。然而,如果没有pass out这条策略时,pass in策略必须要有keep state选项。这也是keep state的有点所在:在执行策略匹配之前将先进行state表检查,如果state表中存在匹配记录,数据包将直接放行而不比再进行策略匹配。这将提高符合比较重的防火墙的效率。
最后,允许流出外部网卡接口的数据包通过防火墙
pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep state
TCP, UDP, 和 ICMP数据包将被允许朝因特网的方向出防火墙。State信息将被保存,以保证反馈回来的数据包通过防火墙。
完整规则集
# macros
int_if = "fxp0"
ext_if = "ep0"
tcp_services = "{ 22, 113 }"
icmp_types = "echoreq"
priv_nets = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }"
comp3 = "192.168.0.3"
# options
set block-policy return
set loginterface $ext_if
# scrub
scrub in all
# nat/rdr
nat on $ext_if from $int_if:network to any -> ($ext_if)
rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 \
port 8021
rdr on $ext_if proto tcp from any to any port 80 -> $comp3
# filter rules
block all
pass quick on lo0 all
block drop in quick on $ext_if from $priv_nets to any
block drop out quick on $ext_if from any to $priv_nets
pass in on $ext_if inet proto tcp from any to ($ext_if) \
port $tcp_services flags S/SA keep state
pass in on $ext_if proto tcp from any to $comp3 port 80 \
flags S/SA synproxy state
pass in inet proto icmp all icmp-type $icmp_types keep state
pass in on $int_if from $int_if:network to any keep state
pass out on $int_if from any to $int_if:network keep state
pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep state
------------------------------------------------------------------------------
$OpenBSD: example1.html,v 1.15 2004/05/15 02:34:02 nick Exp $