PF防火墙(全称:PacketFilter)是UNIXLIKE系统上进行TCP/IP流量过滤和网络地址转换的软件系统。PF同样也能提供TCP/IP流量的整形和控制,并且提供带宽控制和数据包优先集控制。
要激活pf并且使它在启动时调用配置文件,编辑/etc/rc.conf文件,修改配置pf的一行:
pf=YES
也可以通过pfctl程序启动和停止pf
#pfctl-e#pfctl-d
注意这仅仅是启动和关闭PF,实际它不会载入规则集,规则集要么在系统启动时载入,要在PF启动后通过命令单独载入。
配置
pf.conf文件有7个部分:
除去宏和表,其他的段在配置文件中也应该按照这个顺序出现,尽管对于一些特定的应用并不是所有的段都是必须的。
空行会被忽略,以#开头的行被认为是注释.
引导之后,PF可以通过pfctl程序进行操作,以下是一些例子:
#pfctl-sn显示当前的NAT规则#pfctl-sr显示当前的过滤规则#pfctl-ss显示当前的状态表#pfctl-si显示过滤状态和计数#pfctl-sa显示任何可显示的
完整的命令列表,请参阅pfctl的man手册页。
列表
当pfctl在载入规则集碰到列表时,它产生多个规则,每条规则对于列表中的一个条目。例如:
展开后:
blockoutonfxp0from192.168.0.1toanyblockoutonfxp0from10.5.32.6toany
多种列表可以在规则中使用,并不仅仅限于过滤规则:
rdronfxp0prototcpfromanytoanyport{2280}->\192.168.0.6blockoutonfxp0proto{tcpudp}from{192.168.0.1,\10.5.32.6}toanyport{sshtelnet}
注意逗号在列表条目之间是可有可无的。
宏
宏名称必须以字母开头,可以包括字母,数字和下划线。宏名称不能包括保留关键字如:pass,out,以及queue.
ext_if="fxp0"
blockinon$ext_iffromanytoany
这生成了一个宏名称为ext_if.当一个宏在它产生以后被引用时,它的名称前面以$字符开头。
宏也可以展开成列表,如:
friends="{192.168.1.1,10.0.2.5,192.168.43.53}"
宏能够被重复定义,由于宏不能在引号内被扩展,因此必须使用下面得语法:
host1="192.168.1.1"host2="192.168.1.2"all_hosts="{"$host1$host2"}"
宏$all_hosts现在会展开成192.168.1.1,192.168.1.2.
*过滤,整形,NAT和重定向中的源或者目的地址.*NAT规则中的转换地址.*重定向规则中的重定向地址.*过滤规则选项中route-to,reply-to,和dup-to的目的地址.
表可以通过在pf.conf里配置和使用pfctl生成。
*constant-这类表的内容一旦创建出来就不能被改变。如果这个属性没有指定,可以使用pfctl添加和删除表里的地址,即使系统是运行在2或者更高得安全级别上。*persist-即使没有规则引用这类表,内核也会把它保留在内存中。如果这个属性没有指定,当最后引用它的规则被取消后内核自动把它移出内存。
实例:
table{192.0.2.0/24}tableconst{192.168.0.0/16,172.16.0.0/12,10.0.0.0/8}tablepersist
blockinonfxp0from{,}toanypassinonfxp0fromtoany
地址也可以用“非”来进行修改,如:
table{192.0.2.0/24,!192.0.2.5}
goodguys表将匹配除192.0.2.5外192.0.2.0/24网段得所有地址。
注意表名总是在符号得里面。
表也可以由包含IP地址和网络地址的文本文件中输入:
tablepersistfile"/etc/spammers"
blockinonfxp0fromtoany
用pfctl进行操作
表可以使用pfctl进行灵活的操作。例如,在上面产生的表中增加条目可以这样写:
#pfctl-tspammers-Tadd218.70.0.0/16
如果这个表不存在,这样会创建出这个表来。列出表中的内容可以这样:
#pfctl-tspammers-Tshow
-v参数也可以使用-Tshow来显示每个表的条目内容统计。要从表中删除条目,可以这样:
#pfctl-tspammers-Tdelete218.70.0.0/16
指定地址
除了使用IP地址来指定主机外,也可以使用主机名。当主机名被解析成IP地址时,IPv4和IPv6地址都被插进规则中。IP地址也可以通过合法的接口名称或者self关键字输入表中,这样的表会分别包含接口或者机器上(包括loopback地址)上配置的所有IP地址。
一个限制时指定地址0.0.0.0/0以及0/0在表中不能工作。替代方法是明确输入该地址或者使用宏。
地址匹配
表中的地址查询会匹配最接近的规则,比如:
table{172.16.0.0/16,!172.16.1.0/24,172.16.1.100}
blockinondc0allpassinondc0fromtoany
任何自dc0上数据包都会把它的源地址和goodguys表中的地址进行匹配:
*172.16.50.5-精确匹配172.16.0.0/16;数据包符合可以通过*172.16.1.25-精确匹配!172.16.1.0/24;数据包匹配表中的一条规则,但规则是“非”(使用“!”进行了修改);数据包不匹配表会被阻塞。*172.16.1.100-准确匹配172.16.1.100;数据包匹配表,运行通过*10.1.4.55-不匹配表,阻塞。
过滤规则集指定了数据包必须匹配的标准和规则集作用后的结果,在规则集匹配时通过或者阻塞。规则集由开始到结束顺序执行。除非数据包匹配的规则包含quick关键字,否则数据包在最终执行动作前会通过所有的规则检验。最后匹配的规则具有决定性,决定了数据包最终的执行结果。存在一条潜在的规则是如果数据包和规则集中的所有规则都不匹配,则它会被通过。
规则语法
一般而言,最简单的过滤规则语法是这样的:
actiondirection[log][quick]oninterface[af][protoprotocol]\fromsrc_addr[portsrc_port]todst_addr[portdst_port]\[tcp_flags][state]
另外,:0修饰词可以附加到接口名称或者上面的修饰词后面指示PF在替代时不包括网络接口的其余附加(alias)地址。这些修饰词也可以在接口名称在括号()内时使用。例如:fxp0:network:0
表.上面的所有项但使用!(非)修饰词使用列表的一系列地址.关键字any代表所有地址关键字all是fromanytoany的缩写。src_port,dst_port4层数据包头中的源/目标端口。端口可以指定为:1到65535之间的整数/etc/services中的合法服务名称使用列表的一系列端口一个范围:o!=(不等于)o(大于)o=(大于等于)o>(反转范围)
最后2个是二元操作符(他们需要2个参数),在范围内不包括参数。
o:(inclusiverange)
inclusiverange也是二元操作符但范围内包括参数。
tcp_flags指定使用TCP协议时TCP头中必须设定的标记。标记指定的格式是:flagscheck/mask.例如:flagsS/SA-这指引PF只检查S和A(SYNandACK)标记,如果SYN标记是“on”则匹配。
state指定状态信息在规则匹配时是否保持。keepstate-对TCP,UDP,ICMP起作用modulatestate-只对TCP起作用.PF会为匹配规则的数据包产生强壮的初始化序列号。synproxystate-代理外来的TCP连接以保护服务器不受TCPSYNFLOODs欺骗。这个选项包含了keepstate和modulatestate的功能。
默认拒绝
产生一个默认拒绝的过滤规则,开始2行过滤规则必须是:
blockinallblockoutall
这会阻塞任何通信方在任何方向上进入任意接口的所有流量。
通过流量
流量必须被明确的允许通过防火墙或者被默认拒绝的策略丢弃。这是数据包标准如源/目的端口,源/目的地址和协议开始活动的地方。无论何时数据包在被允许通过防火墙时规则都要设计的尽可能严厉。这是为了保证设计中的流量,也只有设计中的流量可以被允许通过。
#允许本地网络192.168.0.0/24流量通过dc0接口进入访问openbsd机器的IP地址#192.168.0.1,同时也允许返回的数据包从dc0接口出去。passinondc0from192.168.0.0/24to192.168.0.1passoutondc0from192.168.0.1to192.168.0.0/24
quick关键字
每个数据包都要按自上至下的顺序按规则进行过滤。默认情况下,数据包被标记为通过,这个可以被任一规则改变,在到达最后一条规则前可以被来回改变多次,最后的匹配规则是“获胜者”。存在一个例外是:过滤规则中的quick关键字具有取消进一步往下处理的作用,使得规则指定的动作马上执行。看一下下面的例子:
错误:
blockinonfxp0prototcpfromanytoanyportsshpassinall
在这样的条件下,block行会被检测,但永远也不会有效果,因为它后面的一行允许所有的流量通过。
正确:
blockinquickonfxp0prototcpfromanytoanyportsshpassinall
这些规则执行的结果稍有不同,如果block行被匹配,由于quick选项的原因,数据包会被阻塞,而且剩下的规则也会被忽略。
状态保持
当一条规则使用了keepstate选项,第一个匹配这条规则的数据包在收发双方之间建立了一个状态。现在,不仅发送者到接收者之间的数据包匹配这个状态绕过规则检验,而且接收者回复发送者的数据包也是同样的。例如:
passoutonfxp0prototcpfromanytoanykeepstate
这允许fxp0接口上的任何TCP流量通过,并且允许返回的流量通过防火墙。状态保持是一个非常有用的特性,由于状态查询比使用规则进行数据包检验快的多,因此它可以大幅度提高防火墙的性能。
passoutonfxp0proto{tcp,udp,icmp}fromanytoanymodulatestate
状态记录的范围被state-policyruntime选项总体控制,也能基于单条规则由if-bound,group-bound,和floatingstate选项关键字设定。这些针对单条规则的关键字在使用时具有和state-policy选项同样的意义。例如:
passoutonfxp0proto{tcp,udp,icmp}fromanytoanymodulatestate(if-bound)
状态规则指示为了使数据包匹配状态条目,它们必须通过fxp0网络接口传递。
需要注意的是,nat,binat,rdr规则隐含在连接通过过滤规则集审核的过程中产生匹配连接的状态。
UDP状态保持
TCP标记
基于标记的TCP包匹配经常被用于过滤试图打开新连接的TCP数据包。TCP标记和他们的意义如下所列:
要使PF在规则检查过程中检查TCP标记,flag关键需按如下语法设置。
flagscheck/mask
mask部分告诉PF仅检查指定的标记,check部分说明在数据包头中哪个标记设置为“on”才算匹配。
passinonfxp0prototcpfromanytoanyportsshflagsS/SA
上面的规则通过的带SYN标记的TCP流量仅查看SYN和ACK标记。带有SYN和ECE标记的数据包会匹配上面的规则,而带有SYN和ACK的数据包或者仅带有ACK的数据包不会匹配。
注意:在前面的openbsd版本中,下面的语法是支持的:
...flagsS
现在,这个不再支持,mask必须被说明。
标记常常和状态保持规则联合使用来控制创建状态条目:
passoutonfxp0prototcpallflagsS/SAkeepstate
这条规则允许为所有输出中带SYN和ACK标记的数据包中的仅带有SYN标记的TCP数据包创建状态。
使用标记时必须小心,理解在做什么和为什么这样做。小心听取建议,因为相当多的建议时不好的。一些人建议创建状态“只有当SYN标记设定而没有其他标记”时,这样的规则如下:...flagsS/FSRPAUEW糟糕的主意!!
这个理论是,仅为TCP开始会话时创建状态,会话会以SYN标记开始,而没有其他标记。问题在于一些站点使用ECN标记开始会话,而任何使用ECN连接你的会话都会被那样的规则拒绝。比较好的规则是:
...flagsS/SAFR
这个经过实践是安全的,如果流量进行了整形也没有必要检查FIN和RST标记。整形过程会让PF丢弃带有非法TCP标记的进入数据包(例如SYN和FIN以及SYN和RST)。强烈推荐总是进行流量整形:
scrubinonfxp0...passinonfxp0prototcpfromanytoanyportsshflagsS/SA\keepstate
TCPSYN代理
通过,当客户端向服务器初始化一个TCP连接时,PF会在二者直接传递握手数据包。然而,PF具有这样的能力,就是代理握手。使用握手代理,PF自己会和客户端完成握手,初始化和服务器的握手,然后在二者之间传递数据。这样做的优点是在客户端完成握手之前,没有数据包到达服务器。这样就消沉了TCPSYNFLOOD欺骗影响服务器的问题,因为进行欺骗的客户端不会完成握手。
TCPSYN代理在规则中使用synproxystate关键字打开。例如:
passinon$ext_ifprototcpfromanyto$web_serverportwww\flagsS/SAsynproxystate
这样,web服务器的连接由PF进行TCP代理。
由于synproxystate工作的方式,它具有keepstate和modulatestate一样的功能。
如果PF工作在桥模式下,SYN代理不会起作用。
阻塞欺骗数据包
PF通过antispoof关键字提供一些防止地址欺骗的保护。
antispoof[log][quick]forinterface[af]
log指定匹配的数据包应该被pflogd进行日志记录quick如果数据包匹配这条规则,则这是最终的规则,不再进行其他规则集的检查。interface激活要进行欺骗保护的网络接口。也可以是接口的列表。af激活进行欺骗保护的地址族,inet代表Ipv4,inet6代表Ipv6。
antispoofforfxp0inet
当规则集载入时,任何出现了antispoof关键字的规则都会扩展成2条规则。假定接口fxp0具有ip地址10.0.0.1和子网掩码255.255.255.0(或者/24),上面的规则会扩展成:
blockinon!fxp0inetfrom10.0.0.0/24toanyblockininetfrom10.0.0.1toany
这些规则实现下面的2个目的:
*阻塞任何不是由fxp0接口进入的10.0.0.0/24网络的流量。由于10.0.0.0/24的网络是在fxp0接口,具有这样的源网络地址的数据包绝不应该从其他接口上出现。*阻塞任何由10.0.0.1即fxp0接口的IP地址的进入流量。主机绝对不会通过外面的接口给自己发送数据包,因此任何进入的流量源中带有主机自己的IP地址都可以认为是恶意的!
注意:antispoof规则扩展出来的过滤规则会阻塞loopback接口上发送到本地地址的数据包。这些数据包应该明确的配置为允许通过。例如:
passquickonlo0all
使用antispoof应该仅限于已经分配了IP地址的网络接口,如果在没有分配IP地址的网络接口上使用,过滤规则会扩展成:
blockdropinon!fxp0inetallblockdropininetall
这样的规则会存在阻塞所有接口上进入的所有流量的危险。
被动操作系统识别
被动操作系统识别是通过基于远端主机TCPSYN数据包中某些特征进行操作系统被动检测的技术。这些信息可以作为标准在过滤规则中使用。
PF检测远端操作系统是通过比较TCPSYN数据包中的特征和已知的特征文件对照来确定的,特征文件默认是/etc/pf.os。如果PF起作用,可是使用下面的命令查看当前的特征列表。
#pfctl-sosfp
在规则集中,特征可以指定为OS类型,版本,或者子类型/补丁级别。这些条目在上面的pfctl命令中有列表。要在过滤规则中指定特征,需使用os关键字:
passinon$ext_ifanyosOpenBSDkeepstateblockinon$ext_ifanyos"Windows2000"blockinon$ext_ifanyos"Linux2.4ts"blockinon$ext_ifanyosunknown
指定的操作系统类型unknow允许匹配操作系统未知的数据包。
注意以下的内容::
*操作系统识别偶尔会出错,因为存在欺骗或者修改过的使得看起来象某个操作系统得数据包。*某些修改或者打过补丁得操作系统会改变栈得行为,导致它或者和特征文件不符,或者符合了另外得操作系统特征。*OSFP仅对TCPSYN数据包起作用,它不会对其他协议或者已经建立得连接起作用。
IP选项
passinquickonfxp0allallow-opts
过滤规则实例
下面是过滤规则得实例。运行PF的机器充当防火墙,在一个小得内部网络和因特网之间。只列出了过滤规则,queueing,nat,rdr,等等没有在实例中列出。
ext_if="fxp0"int_if="dc0"lan_net="192.168.0.0/24"
#scrubincomingpacketsscrubinall
#setupadefaultdenypolicyblockinallblockoutall
#passtrafficontheloopbackinterfaceineitherdirectionpassquickonlo0all
#activatespoofingprotectionfortheinternalinterface.antispoofquickfor$int_ifinet
#onlyallowsshconnectionsfromthelocalnetworkifit‘sfromthe#trustedcomputer,192.168.0.15.use"blockreturn"sothataTCPRSTis#senttocloseblockedconnectionsrightaway.use"quick"sothatthis#ruleisnotoverriddenbythe"pass"rulesbelow.blockreturninquickon$int_ifprototcpfrom!192.168.0.15\to$int_ifportsshflagsS/SA
#passalltraffictoandfromthelocalnetworkpassinon$int_iffrom$lan_nettoanypassouton$int_iffromanyto$lan_net
#passtcp,udp,andicmpoutontheexternal(Internet)interface.#keepstateonudpandicmpandmodulatestateontcp.passouton$ext_ifprototcpallmodulatestateflagsS/SApassouton$ext_ifproto{udp,icmp}allkeepstate
#allowsshconnectionsinontheexternalinterfaceaslongasthey‘re#NOTdestinedforthefirewall(i.e.,they‘redestinedforamachineon#thelocalnetwork).logtheinitialpacketsothatwecanlatertell#whoistryingtoconnect.usethetcpsynproxytoproxytheconnection.passinlogon$ext_ifprototcpfromanyto{!$ext_if,!$int_if}\portsshflagsS/SAsynproxystate
担任NAT任务的操作系统必须至少要2块网卡,一块连接到因特网,另一块连接内部网络。NAT会转换内部网络的所有请求,使它们看起来象是来自进行NAT工作的主机系统。
NAT如何工作
*源IP地址(例如,192.168.1.35)*源TCP或者UDP端口(例如,2132)
例如,会发生下面的改变:
*源IP:被网关的外部地址所替换(例如,24.5.0.5)*源端口:被随机选择的网关没有在用的端口替换(例如,53136)
内部主机和因特网上的主机都不会意识到发生了这个转变步骤。对于内部主机,NAT系统仅仅是个因特网网关,对于因特网上的主机,数据包看起来直接来自NAT系统,它完全不会意识到内部工作站的存在。
当因特网网上的主机回应内部主机的数据包时,它会使用NAT网关机器的外部地址和转换后的端口。然后NAT网关会查询状态表来确定返回的数据包是否匹配某个已经建立的连接。基于IP地址和端口的联合找到唯一匹配的记录告诉PF这个返回的数据包属于内部主机192.168.1.35。然后PF会进行和出去的数据包相反的转换过程,将返回的数据包传递给内部主机。
ICMP数据包的转换也是类似的,只是不进行源端口的修改。
还要注意由于转换是在过滤之前进行,过滤引擎所看到的是上面“nat如何工作”中所说的经过转换后的ip地址和端口的数据包。
IP转发
由于NAT经常在路由器和网关上使用,因此IP转发是需要的,使得数据包可以在UNIX机器的不同网络接口间传递。IP转发可以通过sysctl命令打开:
#sysctl-wnet.inet.ip.forwarding=1#sysctl-wnet.inet6.ip6.forwarding=1(ifusingIPv6)
要使这个变化永久生效,可以增加如下行到/etc/sysctl.conf文件中:
net.inet.ip.forwarding=1net.inet6.ip6.forwarding=1
这些行是本来就存在的,但默认安装中被#前缀注释掉了。删除#,保存文件,IP转发在机器重启后就会发生作用。
配置NAT
一般的NAT规则格式在pf.conf文件中是这个样子:
nat[pass]oninterface[af]fromsrc_addr[portsrc_port]to\dst_addr[portdst_port]->ext_addr[pool_type][static-port]
nat开始NAT规则的关键字。pass使得转换后的数据包完全绕过过滤规则。interface进行数据包转换的网络接口。af地址类型,inet代表Ipv4,inet6代表Ipv6。PF通常能根据源/目标地址自动确定这个参数。src_addr被进行转换的IP头中的源(内部)地址。源地址可以指定为:单个的Ipv4或者Ipv6地址.CIDR网络地址.能够在规则集载入时通过DNS解析到的合法的域名,IP地址会替代规则中的域名。网络接口名称。网络接口上配置的所有ip地址会替代进规则中。带有/掩码(例如/24)的网络接口的名称。每个根据掩码确定的CIDR网络地址都会被替代进规则中。.带有()的网络接口名称。这告诉PF如果网络接口的IP地址改变了,就更新规则集。这个对于使用DHCP或者拨号来获得IP地址的接口特别有用,IP地址改变时不需要重新载入规则集。带有如下的修饰词的网络接口名称:o:network-替代CIDR网络地址段(例如,192.168.0.0/24)o:broadcast-替代网络广播地址(例如,192.168.0.255)o:peer-替代点到点链路上的ip地址。
表.上面的所有项但使用!(非)修饰词使用列表的一系列地址.关键字any代表所有地址关键字all是fromanytoany的缩写。
src_port4层数据包头中的源端口。端口可以指定为:1到65535之间的整数/etc/services中的合法服务名称使用列表的一系列端口一个范围:o!=(不等于)o(大于)o=(大于等于)o>(反转范围)
Port选项在NAT规则中通常不使用,因为目标通常会NAT所有的流量而不过端口是否在使用。dst_addr被转换数据包中的目的地址。目的地址类型和源地址相似。dst_port4层数据包头中的目的目的端口,目的端口类型和源端口类型相似。ext_addrNAT网关上数据包被转换后的外部地址。外部地址可以是:单个的Ipv4或者Ipv6地址.CIDR网络地址.能够在规则集载入时通过DNS解析到的合法的域名,IP地址会替代规则中的域名。网络接口名称。网络接口上配置的所有ip地址会替代进规则中。带有/掩码(例如/24)的网络接口的名称。每个根据掩码确定的CIDR网络地址都会被替代进规则中。.带有()的网络接口名称。这告诉PF如果网络接口的IP地址改变了,就更新规则集。这个对于使用DHCP或者拨号来获得IP地址的接口特别有用,IP地址改变时不需要重新载入规则集。带有如下的修饰词的网络接口名称:o:network-替代CIDR网络地址段(例如,192.168.0.0/24)o:broadcast-替代网络广播地址(例如,192.168.0.255)o:peer-替代点到点链路上的ip地址。
另外,:0修饰词可以附加到接口名称或者上面的修饰词后面指示PF在替代时不包括网络接口的其余附加(alias)地址。这些修饰词也可以在接口名称在括号()内时使用。例如:fxp0:network:0使用列表的一系列地址.pool_type指定转换后的地址池的类型static-port告诉PF不要转换TCP和UDP数据包中的源端口
这条规则最简单的形式如下:
natontl0from192.168.1.0/24toany->24.5.0.5
尽管上面的规则是正确的,但却不是推荐的形式。因为维护起来有困难,当内部或者外部网络有变化时都要修改这一行。比较一下下面这条比较容易维护的规则:(tl0时外部,dc0是内部):
natontl0fromdc0:networktoany->tl0
优点是相当明显的,可以任意改变2个接口的IP地址而不用改变这条规则。
象上面这样在地址转换中使用接口名称时,IP地址在pf.conf文件载入时确定,并不是凭空的。如果使用DHCP还配置外部地址,这会存在问题。如果分配的IP地址改变了,NAT仍然会使用旧的IP地址转换出去的数据包。这会导致对外的连接停止工作。为解决这个问题,应该给接口名称加上括号,告诉PF自动更新转换地址。
natontl0fromdc0:networktoany->(tl0)
这个方法对IPv4和IPv6地址都用效。
例如:
web_serv_int="192.168.1.100"web_serv_ext="24.5.0.6"
binatontl0from$web_serv_inttoany->$web_serv_ext
转换规则例外设置
使用no关键字可以在转换规则中设置例外。例如,如果上面的转换规则修改成这样:
nonatontl0from192.168.1.10toanynatontl0from192.168.1.0/24toany->24.2.74.79
则除了192.168.1.10以外,整个192.168.1.0/24网络地址的数据包都会转换为外部地址24.2.74.79。
注意第一条匹配的规则起了决定作用,如果是匹配有no的规则,数据包不会被转换。No关键字也可以在binat和rdr规则中使用。
检查NAT状态
要检查活动的NAT转换可以使用pfctl带-sstate选项。这个选项列出所有当前的NAT会话。
#pfctl-sstatefxp0TCP192.168.1.35:2132->24.5.0.5:53136->65.42.33.245:22TIME_WAIT:TIME_WAITfxp0UDP192.168.1.35:2491->24.5.0.5:60527->24.2.68.33:53MULTIPLE:SINGLE
解释(对第一行):
fxp0显示状态绑定的接口。如果状态是浮动的,会出现self字样。
TCP连接使用的协议。
192.168.1.35:2132内部网络中机器的IP地址(192.168.1.35),源端口(2132)在地址后显示,这个也是被替换的IP头中的地址。ofthemachineontheinternalnetwork.Thesourceport(2132)isshownaftertheaddress.ThisisalsotheaddressthatisreplacedintheIPheader.
24.5.0.5:53136IP地址(24.5.0.5)和端口(53136)是网关上数据包被转换后的地址和端口。
65.42.33.245:22IP地址(65.42.33.245)和端口(22)是内部机器要连接的地址和端口。
TIME_WAIT:TIME_WAIT这表明PF认为的目前这个TCP连接的状态。
看个例子:
rdrontl0prototcpfromanytoanyport80->192.168.1.20
上面rdr行中fromanytoany部分非常有用。如果明确知道哪些地址或者子网被允许访问web服务器的80端口,可以在这部分严格限制:
rdrontl0prototcpfrom27.146.49.0/24toanyport80->192.168.1.20
这样只会重定向指定的子网。注意这表明可以重定向外部不同的访问主机到网关后面不同的机器上。这非常有用。例如,如果知道远端的用户连接上来时使用的IP地址,可以让他们使用网关的IP地址和端口访问他们各自的桌面计算机。
rdrontl0prototcpfrom27.146.49.14toanyport80->\192.168.1.20rdrontl0prototcpfrom16.114.4.89toanyport80->\192.168.1.22rdrontl0prototcpfrom24.2.74.178toanyport80->\192.168.1.23
重定向和包过滤
注意:转换后的数据包仍然会通过过滤引擎,根据定义的过滤规则进行阻塞或者通过。唯一的例外是如果rdr规则中使用了pass关键字,会使得重定向的数据包直接通过过滤引擎。
还要注意由于转换是在过滤之前进行,过滤引擎所看到的是在匹配rdr规则经过转换后的目标ip地址和端口的数据包。
*192.0.2.1-因特网上的主机*24.65.1.13-openbsd路由器的外部地址*192.168.1.5-web服务器的内部IP地址
rdrontl0prototcpfrom192.0.2.1to24.65.1.13port80\->192.168.1.5port8000
数据包在经过rdr规则前的模样:
*源地址:192.0.2.1*源端口:4028(由操作系统任意选择)*目的地址:24.65.1.13*目的端口:80
数据包经过rdr规则后的模样:
*源地址:192.0.2.1*源端口:4028*目的地址:192.168.1.5*目的:8000
过滤引擎看见的IP数据包时转换发生之后的情况。
安全隐患
重定向和反射
通常,重定向规则是用来将因特网上到来的连接转发到一个内部网络或者局域网的私有地址。例如:
server=192.168.1.40
rdron$ext_ifprototcpfromanyto$ext_ifport80->$server\port80
但是,当一个重定向规则被从局域网上的客户端进行测试时,它不会正常工作。这是因为重定向规则仅适用于通过指定端口($ext_if,外部接口,在上面的例子中)的数据包。从局域网上的主机连接防火墙的外部地址,并不意味着数据包会实际的通过外部接口。防火墙上的TCP/IP栈会把到来的数据包的目的地址在通过内部接口时与它自己的IP地址或者别名进行对比检测。那样的数据包不会真的通过外部接口,栈在任何情况下也不会建立那样的通道。因而,PF永远也不会在外部接口上看到那些数据包,过滤规则由于指定了外部接口也不会起作用。
指定第二条针对内部接口的也达不到预想的效果。当本地的客户端连接防火墙的外部地址时,初始化的TCP握手数据包是通过内部接口到达防火墙的。重定向规则确实起作用了,目标地址被替换成了内部服务器,数据包通过内部接口转发到了内部的服务器。但源地址没有进行转换,仍然包含的是本地客户端的IP地址,因此服务器把它的回应直接发送给了客户端。防火墙永远收不到应答不可能返回客户端信息,客户端收到的应答不是来自它期望的源(防火墙)会被丢弃,TCP握手失败,不能建立连接。
当然,局域网里的客户端仍然会希望象外部客户一样透明的访问这台内部服务器。有如下的方法解决这个问题:
水平分割DNS
将服务器移到独立的本地网络
TCP代理
一般而言,TCP代理可以在防火墙上设置,监听要转发的端口或者将内部接口上到来的连接重定向到它监听的端口。当本地客户端连接防火墙时,代理接受连接,建立到内部服务器的第二条连接,在通信双方间进行数据转发。
127.0.0.1:5000streamtcpnowaitnobody/usr/bin/ncnc-w\20192.168.1.1080
下面的重定向规则转发内部接口的80端口到代理:
rdron$int_ifprototcpfrom$int_netto$ext_ifport80->\127.0.0.1port5000
RDR和NAT结合
通过对内部接口增加NAT规则,上面说的转换后源地址不变的问题可以解决。
rdron$int_ifprototcpfrom$int_netto$ext_ifport80->\$servernonaton$int_ifprototcpfrom$int_ifto$int_netnaton$int_ifprototcpfrom$int_netto$serverport80->\$int_if
这会导致由客户端发起的初始化连接在收到内部接口的返回数据包时转换回来,客户端的源ip地址被防火墙的内部接口地址代替。内部服务器会回应防火墙的内部接口地址,在转发给本地客户端时可以反转NAT和RDR。这个结构是非常复杂的,因为它为每个反射连接产生了2个单独的状态。必须小心配置防止NAT规则应用到了其他流量,例如连接由外部发起(通过其他的重定向)或者防火墙自己。注意上面的rdr规则会导致TCP/IP栈看到来自内部接口带有目的地址是内部网络的数据包。
简介
PF提供了许多方法来进行规则集的简化。一些好的例子是使用宏和列表。另外,规则集的语言或者语法也提供了一些使规则集简化的捷径。首要的规则是,规则集越简单,就越容易理解和维护。
使用宏
#definemacrosforeachnetworkinterfaceIntIF="dc0"ExtIF="fxp0"DmzIF="fxp1"
另一个惯例是使用宏来定义IP地址和网络,这可以大大减轻在IP地址改变时对规则集的维护。
#defineournetworksIntNet="192.168.0.0/24"ExtAdd="24.65.13.4"DmzNet="10.0.0.0/24"
如果内部地址扩展了或者改到了一个不同的IP段,可以更新宏为:
IntNet="{192.168.0.0/24,192.168.1.0/24}"
当这个规则集重新载入时,任何东西都跟以前一样。
使用列表
blockinquickontl0inetfrom127.0.0.0/8toanyblockinquickontl0inetfrom192.168.0.0/16toanyblockinquickontl0inetfrom172.16.0.0/12toanyblockinquickontl0inetfrom10.0.0.0/8toanyblockoutquickontl0inetfromanyto127.0.0.0/8blockoutquickontl0inetfromanyto192.168.0.0/16blockoutquickontl0inetfromanyto172.16.0.0/12blockoutquickontl0inetfromanyto10.0.0.0/8
看看下面更简单的例子:
blockinquickontl0inetfrom{127.0.0.0/8,192.168.0.0/16,\172.16.0.0/12,10.0.0.0/8}toanyblockoutquickontl0inetfromanyto{127.0.0.0/8,\192.168.0.0/16,172.16.0.0/12,10.0.0.0/8}
这个规则集从8行减少到2行。如果联合使用宏,还会变得更好:
NoRouteIPs="{127.0.0.0/8,192.168.0.0/16,172.16.0.0/12,\10.0.0.0/8}"ExtIF="tl0"blockinquickon$ExtIFfrom$NoRouteIPstoanyblockoutquickon$ExtIFfromanyto$NoRouteIPs
注意虽然宏和列表简化了pf.conf文件,但是实际是这些行会被pfctl(8)扩展成多行,因此,上面的例子实际扩展成下面的规则:
blockinquickontl0inetfrom127.0.0.0/8toanyblockinquickontl0inetfrom192.168.0.0/16toanyblockinquickontl0inetfrom172.16.0.0/12toanyblockinquickontl0inetfrom10.0.0.0/8toanyblockoutquickontl0inetfromanyto10.0.0.0/8blockoutquickontl0inetfromanyto172.16.0.0/12blockoutquickontl0inetfromanyto192.168.0.0/16blockoutquickontl0inetfromanyto127.0.0.0/8
可以看到,PF扩展仅仅是简化了编写和维护pf.conf文件,实际并不简化pf(4)对于规则的处理过程。
宏不仅仅用来定义地址和端口,它们在PF的规则文件中到处都可以用:
pre="passinquickonep0inetprototcpfrom"post="toanyport{80,6667}keepstate"
#David‘sclassroom$pre21.14.24.80$post
#Nick‘shome$pre24.2.74.79$post$pre24.2.74.178$post
扩展后:
passinquickonep0inetprototcpfrom21.14.24.80toany\port=80keepstatepassinquickonep0inetprototcpfrom21.14.24.80toany\port=6667keepstatepassinquickonep0inetprototcpfrom24.2.74.79toany\port=80keepstatepassinquickonep0inetprototcpfrom24.2.74.79toany\port=6667keepstatepassinquickonep0inetprototcpfrom24.2.74.178toany\port=80keepstatepassinquickonep0inetprototcpfrom24.2.74.178toany\port=6667keepstate
PF语法
PF的语法相当灵活,因此,允许编写非常灵活的规则集。PF能够自动插入某些关键字因此它们不必在规则中明确写出,关键字的顺序也是随意的,因此不需要记忆严格的语法限制。
减少关键字
要定义全部拒绝的策略,使用下面2条规则:
这可以简化为:
blockall
如果没有指定方向,PF会认为规则适用于数据包传递的进、出2个方向。
同样的,"fromanytoany"和"all"子句可以在规则中省略,例如
blockinonrl0allpassinquicklogonrl0prototcpfromanytoanyport22keepstate
可以简化为:
blockinonrl0passinquicklogonrl0prototcptoport22keepstate
第一条规则阻塞rl0上从任意到任意的进入数据包,第二条规则允许rl0上端口22的TCP流量通过。
Return简化
用于阻塞数据包,回应TCPRST或者ICMP不可到达的规则集可以这么写:
blockinallblockreturn-rstinprototcpallblockreturn-icmpinprotoudpallblockoutallblockreturn-rstoutprototcpallblockreturn-icmpoutprotoudpall
可以简化为::
blockreturn
当PF看到return关键字,PF可以智能回复合适应答,或者完全不回复,取决于要阻塞的数据包使用的协议。W
关键字顺序
在大多数情况下,关键字的顺序是非常灵活的。例如,规则可以这么写:
passinlogquickonrl0prototcptoport22\flagsS/SAkeepstatequeuesshlabelssh
也可以这么写:
passinquicklogonrl0prototcptoport22\queuesshkeepstatelabelsshflagsS/SA
其他类似的顺序也能够正常工作。
运行选项
运行选项是控制pf操作的选择。这些选项在pf.conf中使用set指定。
setblock-policy设定过滤规则中指定的block动作的默认行为。drop-数据包悄然丢弃.return-TCPRST数据包返回给遭阻塞的TCP数据包,ICMP不可到达数据包返回给其他。注意单独的过滤规则可以重写默认的响应。
setdebug设定pf的调试级别。none-不显示任何调试信息。urgent-为严重错误产生调试信息,这是默认选择。misc-为多种错误产生调试信息。(例如,收到标准化/整形的数据包的状态,和产生失败的状态)。.loud-为普通条件产生调试信息(例如,收到被动操作系统检测信息状态)。
setfingerprintsfile设定应该装入的进行操作系统识别的操作系统特征文件来,默认是/etc/pf.os.
setloginterfaceint设定PF要统计进/出流量和放行/阻塞的数据包的数目的接口卡。统计数目一次只能用于一张卡。注意match,bad-offset,等计数器和状态表计数器不管loginterface是否设置都会被记录。
setoptimization为以下的网络环境优化PF:normal-适用于绝大多数网络,这是默认项。high-latency-高延时网络,例如卫星连接。aggressive-自状态表中主动终止连接。这可以大大减少繁忙防火墙的内存需求,但要冒空闲连接被过早断开的风险。conservative-特别保守的设置。这可以避免在内存需求过大时断开空闲连接,会稍微增加CPU的利用率。
setstate-policy设定PF在状态保持时的行为。这种行为可以被单条规则所改变。见状态保持章节。if-bound-状态绑定到产生它们的接口。如果流量匹配状态表种条目但不是由条目中记录的接口通过,这个匹配会失败。数据包要么匹配一条过滤规则,或者被丢弃/拒绝。group-bound-行为基本和if-bound相同,除了数据包允许由同一组接口通过,例如所有的ppp接口等。floating-状态可以匹配任何接口上的流量。只要数据包匹配状态表条目,不管是否匹配它通过的接口,都会放行。这是默认的规则。
settimeoutinterval-丢弃过期的状态和数据包碎片的秒数。frag-不能重组的碎片过期的秒数。
settimeoutinterval10settimeoutfrag30setlimit{frags5000,states2500}setoptimizationhigh-latencysetblock-policyreturnsetloginterfacedc0setfingerprints/etc/pf.os.testsetstate-policyif-bound
流量整形是将数据包标准化避免最终的数据包出现非法的目的。流量整形指引同时也会重组数据包碎片,保护某些操作系统免受某些攻击,丢弃某些带有非法联合标记的TCP数据包。流量整形指引的简单形式是:
scrubinall
这会对所有接口上到来的数据包进行流量整形。
一个不在接口上进行流量整形的原因是要透过PF使用NFS。一些非openbsd的平台发送(和等待)奇怪的数据包,对设置不分片位的数据包进行分片。这会被流量整形(正确的)拒绝。这个问题可以通过设置no-df选项解决。另一个原因是某些多用户的游戏在进行流量整形通过PF时存在连接问题。除了这些极其罕见的案例,对所有的数据包进行流量整形时强烈推荐的设置。
流量整形指引语法相对过滤语法是非常简单的,它可以非常容易的选择特定的数据包进行整形而不对没指定的数据包起作用。
选项
流量整形有下面的选项:
scrubinonfxp0allfragmentreassemblemin-ttl15max-mss1400scrubinonfxp0allno-dfscrubonfxp0allreassembletcp
除了主要的规则集,PF还可以载入子规则该网络的队列策略:
*为Bob保留玩在线游戏的80Kbps下行带宽,以减少另外两人对他的影响,并且总带宽富余的情况下可以超出该限制。*交互的SSH和即时信息流量要有高于其他流量的优先级。*DNS请求和反馈数据流要有第二高的优先级。*流出的TCPACK数据包的优先级要高于其他流出数据包的优先级。
下面是对应的策略(省略了其他部分策略,如rdr、nat等):
#enablequeueingontheexternalinterfacetocontroltrafficgoingto#theInternet.usethepriqschedulertocontrolonlypriorities.set#thebandwidthto610KbpstogetthebestperformanceoutoftheTCP#ACKqueue.
altqonfxp0priqbandwidth610Kbqueue{std_out,ssh_im_out,dns_out,\tcp_ack_out}
#definetheparametersforthechildqueues.#std_out-thestandardqueue.anyfilterrulebelowthatdoesnot#explicitlyspecifyaqueuewillhaveitstrafficadded#tothisqueue.#ssh_im_out-interactiveSSHandvariousinstantmessagetraffic.#dns_out-DNSqueries.#tcp_ack_out-TCPACKpacketswithnodatapayload.
queuestd_outpriq(default)queuessh_im_outpriority4priq(red)queuedns_outpriority5queuetcp_ack_outpriority6
#enablequeueingontheinternalinterfacetocontroltrafficcomingin#fromtheInternet.usethecbqschedulertocontrolbandwidth.max#bandwidthis2Mbps.
altqondc0cbqbandwidth2Mbqueue{std_in,ssh_im_in,dns_in,bob_in}
#definetheparametersforthechildqueues.#std_in-thestandardqueue.anyfilterrulebelowthatdoesnot#explicitlyspecifyaqueuewillhaveitstrafficadded#tothisqueue.#ssh_im_in-interactiveSSHandvariousinstantmessagetraffic.#dns_in-DNSreplies.#bob_in-bandwidthreservedforBob‘sworkstation.allowhimto#borrow.
queuestd_incbq(default)queuessh_im_inpriority4queuedns_inpriority5queuebob_inbandwidth80Kbcbq(borrow)
#...inthefilteringsectionofpf.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="{222022}"im_ports="{186351905222}"
#filterrulesforfxp0inboundblockinonfxp0all
#filterrulesforfxp0outboundblockoutonfxp0allpassoutonfxp0inetprototcpfrom(fxp0)toanyflagsS/SA\keepstatequeue(std_out,tcp_ack_out)passoutonfxp0inetproto{udpicmp}from(fxp0)toanykeepstatepassoutonfxp0inetproto{tcpudp}from(fxp0)toanyportdomain\keepstatequeuedns_outpassoutonfxp0inetprototcpfrom(fxp0)toanyport$ssh_ports\flagsS/SAkeepstatequeue(std_out,ssh_im_out)passoutonfxp0inetprototcpfrom(fxp0)toanyport$im_ports\flagsS/SAkeepstatequeue(ssh_im_out,tcp_ack_out)
#filterrulesfordc0inboundblockinondc0allpassinondc0from$local_net
#filterrulesfordc0outboundblockoutondc0allpassoutondc0fromanyto$local_netpassoutondc0proto{tcpudp}fromanyportdomainto$local_net\queuedns_inpassoutondc0prototcpfromanyport$ssh_portsto$local_net\queue(std_in,ssh_im_in)passoutondc0prototcpfromanyport$im_portsto$local_net\queuessh_im_inpassoutondc0fromanyto$bobqueuebob_in
实例#2:公司网络
(ITDept)[Boss‘sPC]||T1---------------------dc0[OpenBSD]fxp0--------(Internet)|fxp1[COMP1][WWW]/|/------------‘
实现上述要求的策略如下:
#enablequeueingontheexternalinterfacetoqueuepacketsgoingout#totheInternet.usethecbqschedulersothatthebandwidthuseof#eachqueuecanbecontrolled.themaxoutgoingbandwidthis1.5Mbps.
altqonfxp0cbqbandwidth1.5Mbqueue{std_ext,www_ext,boss_ext}
#enablequeueingontheinternalinterfacetocontroltrafficcoming#fromtheInternetortheDMZ.usethecbqschedulertocontrolthe#bandwidthofeachqueue.bandwidthonthisinterfaceissettothe#maximum.trafficcomingfromtheDMZwillbeabletouseallofthis#bandwidthwhiletrafficcomingfromtheInternetwillbelimitedto#1.0Mbps(because0.5Mbps(500Kbps)isbeingallocatedtofxp1).
altqondc0cbqbandwidth100%queue{net_int,www_int}
#definetheparametersforthechildqueues.#net_int-containerqueuefortrafficfromtheInternet.bandwidth#is1.0Mbps.#std_int-thestandardqueue.alsothedefaultqueueforoutgoing#trafficondc0.#it_int-traffictotheITDeptnetwork.#boss_int-traffictotheboss‘sPC.#www_int-trafficfromtheWWWserverintheDMZ.
queuenet_intbandwidth1.0Mb{std_int,it_int,boss_int}queuestd_intcbq(default)queueit_intbandwidth500Kbcbq(borrow)queueboss_intpriority3queuewww_intcbq(red)
#enablequeueingontheDMZinterfacetocontroltrafficdestinedfor#theWWWserver.cbqwillbeusedonthisinterfacesincedetailed#controlofbandwidthisnecessary.bandwidthonthisinterfaceisset#tothemaximum.trafficfromtheinternalnetworkwillbeabletouse#allofthisbandwidthwhiletrafficfromtheInternetwillbelimited#to500Kbps.
altqonfxp1cbqbandwidth100%queue{internal_dmz,net_dmz}
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"
#defaultdenyblockon{fxp0,fxp1,dc0}all
#filterrulesforfxp0outboundpassoutonfxp0from$int_netstoanykeepstatepassoutonfxp0from$bosstoanykeepstatequeueboss_ext
#filterrulesfordc0inboundpassinondc0from$int_netstoanykeepstatepassinondc0from$it_nettoanyqueueit_intpassinondc0from$bosstoanyqueueboss_intpassinondc0prototcpfrom$int_netsto$wwwservport{21,80,\>49151}flagsS/SAkeepstatequeuewww_int
#filterrulesfordc0outboundpassoutondc0fromdc0to$int_nets
#filterrulesforfxp1inboundpassinonfxp1proto{tcp,udp}from$wwwservtoanyport53\keepstate
有4种使用地址池的方法:
sticky-address选项可以在random和round-robin池类型中使用,保证特定的源地址始终映射到同样的重定向地址。
NAT地址池
地址池在NAT规则中可以被用做转换地址。连接的源地址会被转换成使用指定的方法从地址池中选择的地址。这对于PF负载一个非常大的网络的NAT会非常有用。由于经过NAT的连接对每个地址是有限的,增加附加的转换地址允许NAT网关增大服务的用户数量。
在这个例子中,2个地址被用来做输出数据包的转换地址。对于每一个输出的连接,PF按照顺序循环使用地址。
naton$ext_ifinetfromanytoany->{192.0.2.5,192.0.2.10}
naton$ext_ifinetfromanytoany->192.0.2.4/31source-hash
这条NAT规则使用地址池192.0.2.4/31(192.0.2.4-192.0.2.5)做为输出数据包的转换地址。每一个内部地址会被转换为同样的外部地址,由于source-hash关键字的缘故。
外来连接负载均衡
web_servers="{10.0.0.10,10.0.0.11,10.0.0.13}"
rdron$ext_ifprototcpfromanytoanyport80->$web_servers\round-robinsticky-address
成功的连接将按照顺序重定向到web服务器,从同一个源到来的连接发送到同一个服务器。这个stickyconnection会和指向这个连接的状态一起存在。如果状态过期,stickyconnection也过期。那个主机的更多连接被重定向到按顺序的下一个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"
passinon$int_ifroute-to\{($ext_if1$ext_gw1),($ext_if2$ext_gw2)}round-robin\from$lan_nettoanykeepstate
route-to选项用来在收到流量的内部接口上指定平衡的流量经过各自的网关到输出的网络接口。注意route-to选项必须在每个需要均衡的过滤规则上出现。返回的数据包会路由到它们出去时的外部接口(这是由ISP做的),然后正常路由回内部网络。
要保证带有属于$ext_if1源地址的数据包总是路由到$ext_gw1($ext_if2和$ext_gw2也是同样的),下面2行必须包括在规则集中:
passouton$ext_if1route-to($ext_if2$ext_gw2)from$ext_if2\toanypassouton$ext_if2route-to($ext_if1$ext_gw1)from$ext_if1\toany
最后,NAT也可以使用在输出接口中:
naton$ext_if1from$lan_nettoany->($ext_if1)naton$ext_if2from$lan_nettoany->($ext_if2)
一个完整的输出负载均衡的例子应该是这个样子:
#natoutgoingconnectionsoneachinternetinterfacenaton$ext_if1from$lan_nettoany->($ext_if1)naton$ext_if2from$lan_nettoany->($ext_if2)
#defaultdenyblockinfromanytoanyblockoutfromanytoany
#passalloutgoingpacketsoninternalinterfacepassouton$int_iffromanyto$lan_net#passinquickanypacketsdestinedforthegatewayitselfpassinquickon$int_iffrom$lan_netto$int_if#loadbalanceoutgoingtcptrafficfrominternalnetwork.passinon$int_ifroute-to\{($ext_if1$ext_gw1),($ext_if2$ext_gw2)}round-robin\prototcpfrom$lan_nettoanyflagsS/SAmodulatestate#loadbalanceoutgoingudpandicmptrafficfrominternalnetworkpassinon$int_ifroute-to\{($ext_if1$ext_gw1),($ext_if2$ext_gw2)}round-robin\proto{udp,icmp}from$lan_nettoanykeepstate
#general"passout"rulesforexternalinterfacespassouton$ext_if1prototcpfromanytoanyflagsS/SAmodulatestatepassouton$ext_if1proto{udp,icmp}fromanytoanykeepstatepassouton$ext_if2prototcpfromanytoanyflagsS/SAmodulatestatepassouton$ext_if2proto{udp,icmp}fromanytoanykeepstate
#routepacketsfromanyIPson$ext_if1to$ext_gw1andthesamefor#$ext_if2and$ext_gw2passouton$ext_if1route-to($ext_if2$ext_gw2)from$ext_if2toanypassouton$ext_if2route-to($ext_if1$ext_gw1)from$ext_if1toany
数据包标记是给数据包打内部标记的方法,以后可以在过滤和转换规则中使用。使用标记,有可能做这样的事情,比如在接口间产生信任关系,或者确定数据包是否已经经过了转换规则处理。也可能从基于规则的过滤中移出,开始执行基于策略的过滤。
给数据包打标记
要给数据包打标记,使用tag关键字:
passinon$int_ifalltagINTERNAL_NETkeepstate
标记INTERNAL_NET会增加到任何匹配上述规则的数据包中。注意keepstate的使用;keepstate(或者modulatestate/synproxystate)在标记数据包通过的规则中使用。
标记也可以通过宏来打,比如:
name="INTERNAL_NET"passinon$int_ifalltag$namekeepstate
有一组预先定义的宏也可以被使用。
*$if-接口*$srcaddr-源IP地址*$dstaddr-目的IP地址*$srcport-源端口*$dstport-目的端口*$proto-协议*$nr-规则号
这些宏在规则集装入时扩展,而不是运行时。
标记遵循以下规则:
*标记是粘性的。一旦一个标记被匹配的规则打到一个数据包,就不能被删除。但它可以被不同的标记替换。*由于标记的粘性,打了标记的数据包会一直保持,即使所有的规则都没有使用这个标记。*一个数据包一次最多只能打一个标记。*标记是内部标识符,标记不会被送到网上。
看看下面的例子:
(1)passinon$int_iftagINT_NETkeepstate(2)passinquickon$int_ifprototcptoport80tag\INT_NET_HTTPkeepstate(3)passinquickon$int_iffrom192.168.1.5keepstate
*按照规则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关键字:
passouton$ext_iftaggedINT_NETkeepstate
在$ext_if输出的数据包为了匹配上述规则必须打上INT_NET标记。反转匹配也可以使用!操作:
passouton$ext_iftagged!WIFI_NETkeepstate
策略过滤
过滤策略提供了编写过滤规则集的不同方法。定义的策略设定规则,说明哪种流量放行,哪种流量阻塞。数据包被基于传统的标准如源/目的IP地址,协议等等分配到不同的策略。例如,检查下面的防火墙策略:
*自内部LAN到DMZ的流量是允许的(LAN_DMZ)。*自因特网到DMZ的服务器流量是允许的。(INET_DMZ)*自因特网被重定向到spamd(8)是允许的(SPAMD)*其他所有流量阻塞。
注意策略是如何覆盖所有通过防火墙的流量的。括号里面的项目指示这个策略项目将使用的标记。
需要过滤和转换规则来把数据包分配到不同的策略。
rdron$ext_ifprototcpfromtoportsmtp\tagSPAMD->127.0.0.1port8025
blockallpassinon$int_iffrom$int_nettagLAN_INETkeepstatepassinon$int_iffrom$int_netto$dmz_nettagLAN_DMZkeepstatepassinon$ext_ifprototcpto$www_serverport80tagINET_DMZkeepstate
现在要设置定义策略的规则。
passinquickon$ext_iftaggedSPAMDkeepstatepassoutquickon$ext_iftaggedLAN_INETkeepstatepassoutquickon$dmz_iftaggedLAN_DMZkeepstatepassoutquickon$dmz_iftaggedINET_DMZkeepstate
mail_server="192.168.0.10"...passinon$ext_ifprototcpto$mail_serverport{smtp,pop3}\tagINET_DMZkeepstate
Email流量会作为INET-DMZ策略的条目被放行。t完整的规则:
#macrosint_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"
#classification--classifypacketsbasedonthedefinedfirewall#policy.rdron$ext_ifprototcpfromtoportsmtp\tagSPAMD->127.0.0.1port8025
blockallpassinon$int_iffrom$int_nettagLAN_INETkeepstatepassinon$int_iffrom$int_netto$dmz_nettagLAN_DMZkeepstatepassinon$ext_ifprototcpto$www_serverport80tagINET_DMZkeepstatepassinon$ext_ifprototcpto$mail_serverport{smtp,pop3}\tagINET_DMZkeepstate
#policyenforcement--pass/blockbasedonthedefinedfirewallpolicy.passinquickon$ext_iftaggedSPAMDkeepstatepassoutquickon$ext_iftaggedLAN_INETkeepstatepassoutquickon$dmz_iftaggedLAN_DMZkeepstatepassoutquickon$dmz_iftaggedINET_DMZkeepstate
标记以太网帧
打标记可以在以太网级别进行,如果执行标记/过滤的机器同时做为网桥。通过创建使用tag关键字的网桥过滤规则,PF可以建立基于源/目的MAC地址的过滤规则。网桥规则可以由brconfig(8)命令产生,例如:
#brconfigbridge0rulepassinonfxp0src0:de:ad:be:ef:0\tagUSER1
然后在pf.conf文件中:
passinonfxp0taggedUSER1
PF的包日志是由pflogd完成的,它通过监听pflog0接口然后将包以tcpdump二进制格式写入日志文件(一般在/val/log/pflog)。过滤规则定义的日志和log-all关键字所定义的日志都是以这种方式记录的。
读取日志文件
使用如下格式查看日志信息:
#tcpdump-n-e-ttt-r/var/log/pflog
使用tcpdump(查看日志文件并不是实时的,若要实时查询日志信息需加上pflog0参数:
#tcpdump-n-e-ttt-ipflog0
注意:当查看日志时需要特别注意tcpdump的详细协议解码(通过在命令行增加-v参数实现)。Tcpdump的详细协议解码器并不具备完美的安全历史,至少在理论上是这样。日志记录设备所记载的部分包信息可能会引发延时攻击,因此推荐在查询日志文件信息之前先将该日志文件从防火墙上移走。
另外需要注意的是对日志文件的安全访问。默认情况下,pflogd将在日志文件中记录96字节的包信息。访问日志文件将提供访问部分敏感包信息的途径(就像telnet(1)或者ftp(1)的用户名和密码)。
导出日志
由于pflogd以tcpdump二进制格式记录日志信息,因此当回顾这些日志时可以使用tcpdump的很多特点。例如,只查看与特定端口匹配的包:
#tcpdump-n-e-ttt-r/var/log/pflogport80
甚至可以限定具体的主机和端口:
#tcpdump-n-e-ttt-r/var/log/pflogport80andhost192.168.1.3
同样的方法可以应用到直接从pflog0接口读取的信息:
#tcpdump-n-e-ttt-ipflog0host192.168.4.2
注意这与包被记录到pflogd日志文件不相冲突;上述语句只以包被记录的形式显示。
除了使用标准的tcpdump(8)过滤规则外,OpenBSD的tcpdump过滤语言为读取pflogd而被扩展:
*ip-IPv4版本地址。*ip6-IPv6版本地址。*onint-包通过int接口。*ifnameint-与onint相同.*rulenumnum-包匹配的过滤规则编号为num。*actionact-对包的操作。可能是pass(通过)或者block(阻断)。*reasonres-执行对包操作的原因。可能的原因是match(匹配),bad-offset,fragment,short,normalize(规格化),memory(内存)。*inbound-入栈包。*outbound-出栈包。
举例:
#tcpdump-n-e-ttt-ipflog0inboundandactionblockandonwi0
这将以实时方式显示被wi0接口阻断的入栈包的日志信息。
通过Syslog记录日志
完成后建立如下两个脚本:
/etc/pflogrotateFILE=/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-gt24];thenmv/var/log/pflog$FILEchownpflogger$FILEkill-HUP$(cat/var/run/pflogd.pid)fi
/home/pflogger/pfl2syslforlogfilein/home/pflogger/pflog5min*;dotcpdump-n-e-ttt-r$logfile|logger-tpf-plocal0.inform$logfiledone
编辑root的cron任务:
#crontab-uroot-e
增加如下两行:
为用户pflogger建立一个cron任务:
#crontab-upflogger-e
#feedrotatedpflogfile(s)tosyslog0-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,因此rotationofpflogbynewsyslog(Cool不再必需可以被禁用。然而,/var/log/pflog.txt替代/var/log/pflogandrotationofit要被启用。改变/etc/newsyslog.conf如下:
#/var/log/pflog6003250*ZB/var/run/pflogd.pid/var/log/pflog.txt6007*24
PF将日志以ASCII格式记录到/var/log/pflog.txt.如果这样配置/etc/syslog.conf,系统将把日志存到远程服务器。存储过程不会马上发生,但是会在符合条件的包出现在文件中前5-6分钟实现。
“PF可以处理多少带宽?”“我需要多少台计算机处理因特网连接?”
这个问题没有简单的答案。对于一些应用程序来说,一台486/66主机,带有2个比较好的ISA网卡在做过滤和NAT时接近5Mbps,但是对于其他应用程序,一个更快的计算机加上更有效的PCI网卡也会显得能力不足。真正的问题不是每秒处理的位数而是每秒处理的包数和规则集的复杂程度。
体现PF性能的几个参数:
*规则集的设计和复杂性。规则越复杂越慢。越多的包通过keep和quick方式过滤,性能越好。对每个包的策略越多,性能越差。
*值得一提:CPU和内存。由于PF是基于内核的进程,它不需要swap空间。所以,如果你有足够的内存,它将运行很好,如果没有,将受影响。不需要太大量的内存。32MB内存对小型办公室或者家庭应用足够,300MHz的cpu如果配置好网卡和规则集,足够满足要求。
人们经常询问PF的基准点。唯一的基准是在一个环境下系统的性能。不考虑环境因素将影响所设计的防火墙的系统性能。
PF曾经在非常大流量的系统中工作,同时PF的开发者也是它的忠实用户。
FTP的工作模式分为被动(passive)和主动(active)两种。通常这两种选择被用来确定哪边有防火墙问题。实际上,为了方便用户应该全部支持这两种模式。
工作在防火墙之后的FTP客户端
如前所述,FTP对NAT和防火墙支持不好。
包过滤机制通过将FTP数据包重定向到一个FTP代理服务器解决这一问题。这一过程将引导FTP数据包通过NAT网关/防火墙。OpenBSD和PF使用的FTP代理是ftp-proxy(8),可以通过在pf.conf中的NAT章节增加下列信息激活该代理:
rdron$int_ifprototcpfromanytoanyport21->127.0.0.1\port8021
显然该代理服务器应该已在OpenBSD中启动并运行。配置方法为在/etc/inetd.conf中增加下列信息:
127.0.0.1:8021streamtcpnowaitroot/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范围内的端口,显然,必须要有对应的过滤规则放行这些端口的数据:
passinon$ext_ifprototcpfromanytoanyport21keepstatepassinon$ext_ifprototcpfromanytoanyport>49151\keepstate
如果需要可以调整上述端口范围。在OpenBSD的ftpd(8)环境下,可以通过sysctl(8)进行调整net.inet.ip.porthifirst和net.inet.ip.porthilast。
使用NAT外部PF防火墙保护FTP服务器
这种情况下,防火墙必须将数据重定向到FTP服务器。为了讨论方便,我们假设该FTP服务器使用标准的OpenBSDftpd(8),并使用默认端口范围。
这里有个例子
ftp_server="10.0.3.21"
rdron$ext_ifprototcpfromanytoanyport21->$ftp_server\port21rdron$ext_ifprototcpfromanytoanyport49152:65535->\$ftp_serverport49152:65535
#inon$ext_ifpassinquickon$ext_ifprototcpfromanyto$ftp_server\port21keepstatepassinquickon$ext_ifprototcpfromanyto$ftp_server\port>49151keepstate
#outon$int_ifpassoutquickon$int_ifprototcpfromanyto$ftp_server\port21keepstatepassoutquickon$int_ifprototcpfromanyto$ftp_server\port>49151keepstate
FTP的更多信息过滤FTP和FTP如何工作的更多信息可以参考下面的白皮书。
Authpf可以应用在:
*在允许用户访问因特网之前进行身份验证。*赋予特殊用户访问受限网络的权利,例如管理员。*只允许特定的无线网络用户访问特定的网络。*允许公司员工在任何时候访问公司网络,而公司之外的用户不能访问,并可以将这些用户重定向到特定的基于用户名的资源(例如他们自己的桌面)。*在类似图书馆这样的地方通过PF限制guest用户对因特网的访问。Authpf可以用来向已注册用户开放完全的因特网连接。
配置authpf的基本步骤大致描述如下。详细的信息请查看man手册。
将authpf连入主策略集
通过使用锚点策略将authpf连入主策略集:
nat-anchorauthpfrdr-anchorauthpfbinat-anchorauthpfanchorauthpf
锚点策略放入策略集的位置就是PF中断执行主策略集转为执行authpf策略的位置。上述4个锚点策略并不需要全部存在,例如,当authpf没有被设置加载任何nat策略时,nat-anchor策略可被省略。
配置加载的策略
Authpf通过下面两个文件之一加载策略:
*/etc/authpf/users/$USER/authpf.rules*/etc/authpf/authpf.rules
过滤器和传输策略与其他的PF策略集语法一样,但有一点不同:authpf允许使用预先定义的宏:
推荐使用宏$user_ip,只赋予通过身份验证的计算机透过防火墙的权限。
访问控制列表
可以通过在/etc/authpf/banned/目录下建立以用户名命名的文件来阻止该用户使用authpf。文件的内容将在authpf断开与该用户的连接之前显示给他,这为通知该用户被禁止访问的原因并告知他解决问题联系人提供了一个便捷的途径。
如果authpf不能断定一个用户名是被允许还是禁止,它将打印一个摘要信息并断开该用户的连接。明确禁止将会使明确允许失效。
将authpf配置为用户shell
有两种途径将authpf设置为用户shell:
查看登陆者
#ps-ax|grepauthpf23664p0Is0:00.11-authpf:charlie@192.168(authpf)
#kill-TERM23664
实例
文件/etc/authpf/authpf.rules包含下列策略:
wifi_if="wi0"dns_servers="{10.0.1.56,10.0.2.56}"
管理员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}"
定义在/etc/pf.conf中的主策略集配置如下:
#macroswifi_if="wi0"ext_if="fxp0"
#filterblockdropall
passoutquickon$ext_ifprototcpfrom$wifi_if:networkflagsS/SA\modulatestatepassoutquickon$ext_ifproto{udp,icmp}from$wifi_if:network\keepstate
passinquickon$wifi_ifprototcpfrom$wifi_if:networkto$wifi_if\portsshflagsS/SAkeepstate
anchorauthpfinon$wifi_if
该策略集非常简单,作用如下:
设计主策略集的主导思想为:阻断任何包并允许尽可能小的数据流通过。在外部接口上流出数据包是允许的,但是默认否策略阻断了由无线接口进入的数据包。用户一旦通过验证,他们的数据包被允许通过无线接口进入并穿过网关到达其他网络。
概况
网络
网络配置如下:
[COMP1][COMP3]||ADSL---------------------fxp0[OpenBSD]ep0--------(因特网)|[COMP2]
目标
准备
这里假设作为网关的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"
下列两个选项用来设置阻断后的默认操作为反馈,并在外部接口设置开启日志记录:
setblock-policyreturnsetloginterface$ext_if
流量整修
没有理由不起用对所有进入防火墙的所有包进行规格化,因此只需要简单的一行:
NAT(网络地址转换)
为所有内部网启用NAT可以通过下列策略:
naton$ext_iffrom$int_if:networktoany->($ext_if)
由于外部网卡的IP地址是动态获得的,因此在外部网卡接口处增加小括号以使当IP地址发生变化时PF可以自适应。
重定向
第一个需要重定向策略的是ftp-proxy(8),只有这样内部网上的FTP客户端才可以访问因特网上的FTP服务器。
rdron$int_ifprototcpfromanytoanyport21->127.0.0.1port8021
注意这条策略只捕获到21端口的数据包,如果用户通过其他端口访问FTP服务器,则在定义目的端口时需要使用list(列表),例如:fromanytoanyport{21,2121}。
第二个重定向策略捕获因特网上的用户访问防火墙80端口的数据包。用户试图访问网络的web服务器时将产生合法的访问该端口的数据包,这些连接请求需要重定向到主机COMP3:
rdron$ext_ifprototcpfromanytoanyport80->$comp3
过滤规则
过滤规则第一行是默认否规则:
这时没有任何数据包可以流过防火墙,甚至来自内部网络的数据包。下面的规则将逐个依据上面提到的目标开启防火墙上的虚拟接口。
每个Unix系统都有一个“loopback(回送)”接口,它是用于系统中应用程序间通信的虚拟网络接口。在OpenBSD中,回送接口是lo(4)。
下一步,由RFC1918定义的私有地址将在外部网卡接口的进和出方向被阻断。这些地址不应该出现在公网上,通过阻断这些地址可以保证防火墙不向外部网泄漏内网地址,同时也阻断了来自外部网中源地址为这些私有地址的数据包流入内网。
blockdropinquickon$ext_iffrom$priv_netstoanyblockdropoutquickon$ext_iffromanyto$priv_nets
这里blockdrop用来通知PF停止反馈TCPRST或者ICMPUnreachabel数据包。因为RFC1918规定的地址不会存在于因特网上,发往那些地址的数据包将没有意义。Quick选项用来通知PF如果这条规则匹配则不再进行其他规则的匹配操作,来自或流向$priv_nets的数据包将被立即丢弃。
现在将打开因特网上的一些服务所用到的端口:
passinon$ext_ifinetprototcpfromanyto($ext_if)\port$tcp_servicesflagsS/SAkeepstate
通过在宏$tcp_services中定义服务端口可以更方便的进行维护。开放UDP服务也可一模仿上述语句,只不过改为protoudp。
已经有了一条rdr策略将web访问请求转发到主机COMP3上,我们必须建立另一条过滤规则使得这些访问请求可以通过防火墙:
passinon$ext_ifprototcpfromanyto$comp3port80\flagsS/SAsynproxystate
现在将允许ICMP包通过防火墙:
passininetprotoicmpallicmp-type$icmp_typeskeepstate
类似于宏$tcp_services,当需要增加允许进入防火墙的ICMP数据包类型时可以容易地编辑宏$icmp_types。注意这条策略将应用于所有网络接口。
现在数据流必须可以正常出入内部网络。我们假设内网的用户清楚自己的所作所为并且确定不会导致麻烦。这并不是必然有效的假设,在某些环境下更具限制性的策略集会更适合。
passinon$int_iffrom$int_if:networktoanykeepstate
上面的策略将允许内网中的任何计算机发送数据包穿过防火墙;然而,这并没有允许防火墙主动与内网的计算机建立连接。这是一种好的方法吗?评价这些需要依靠网络配置的一些细节。如果防火墙同时充当DHCP服务器,它需要在分配一个地址之前ping一下该地址以确认该地址没有被占用。允许防火墙访问内部网络同时也允许了在因特网上通过ssh控制防火墙的用户访问内网。请注意禁止防火墙直接访问内网并不能带来高安全性,因为如果一个用户可以访问防火墙,他也可以改变防火墙的策略。增加下列策略可以使防火墙具备访问内网的能力:
passouton$int_iffromanyto$int_if:networkkeepstate
如果这些策略同时存在,则keepstate选项将不是必须的;所有的数据包都可以流经内网接口,因为一条策略规定了双向放行数据包。然而,如果没有passout这条策略时,passin策略必须要有keepstate选项。这也是keepstate的有点所在:在执行策略匹配之前将先进行state表检查,如果state表中存在匹配记录,数据包将直接放行而不比再进行策略匹配。这将提高符合比较重的防火墙的效率。
最后,允许流出外部网卡接口的数据包通过防火墙
passouton$ext_ifprototcpallmodulatestateflagsS/SApassouton$ext_ifproto{udp,icmp}allkeepstate
TCP,UDP,和ICMP数据包将被允许朝因特网的方向出防火墙。State信息将被保存,以保证反馈回来的数据包通过防火墙。
完整规则集
#macrosint_if="fxp0"ext_if="ep0"
#optionssetblock-policyreturnsetloginterface$ext_if
#scrubscrubinall
#nat/rdrnaton$ext_iffrom$int_if:networktoany->($ext_if)rdron$int_ifprototcpfromanytoanyport21->127.0.0.1\port8021rdron$ext_ifprototcpfromanytoanyport80->$comp3
#filterrulesblockall
passinon$int_iffrom$int_if:networktoanykeepstatepassouton$int_iffromanyto$int_if:networkkeepstate