在pf.conf中,nat的配置有一些讲究。本文分为nat masqport forwarding两部分,也就是linux iptables中常说的snat和dnat。

nat masq

有两种写法:

第一种

block out on em0
match out quick on em0 from 192.168.168.0/24 to any nat-to 1.2.3.4
pass out quick on em0 proto tcp from 1.2.3.4 to any port = 80

这种写法的好处是可以根据outbound数据包的类型进行nat,譬如上面的第3行,表明只有符合访问外网http的,才进行nat。

在第3行rule中,源地址应设为已映射的外网IP地址:1.2.3.4,而不是192.168.168.0/24,否则会出现nat失败的情况,举例说明:
match out on $wan_if from !($wan_if) to any nat-to ($wan_if)
pass out on $wan_if all
...
pass inet icmp from any to any

目的是实现内网主机访问外网时,譬如ping 221.182.235.44,将会在$wan_if上做snat,然而实际情况是,目标主机上启用tcpdump抓包,发现源地址并未改变,仍然是192.168.33.83,这意味着:

  1. icmp数据包命中了pass inet icmp from any to any,然而并未做snat;

  2. 源地址为RFC1918地址的数据包可以在互联网上进行路由,可达到目的主机。目的地址为RFC1918地址的数据包不可在互联网上路由。

最好的做法应该是将Pass out on $wan_if all改成pass out quick on $wan_if from $wan_if to any

是否正确snat可通过在$lan_if和$wan_if接口上开启tcpdump来进行检验

$ doas tcpdump -nvi <wan_if> icmp
15:10:18.770734 192.168.33.83 > 221.182.235.44: icmp: echo request (id:59fe seq:5) (DF) (ttl 63, id 0, len 84)
15:10:18.772604 221.182.235.44 > 192.168.33.83: icmp: echo reply (id:59fe seq:5) [tos 0xc0] (ttl 57, id 33332, len 84)
$ doas tcpdump -nvi <lan_if> icmp
15:10:18.770813 221.182.254.165 > 221.182.235.44: icmp: echo request (id:8ec3 seq:5) (DF) (ttl 62, id 0, len 84)
15:10:18.772540 221.182.235.44 > 221.182.254.165: icmp: echo reply (id:8ec3 seq:5) [tos 0xc0] (ttl 58, id 33332, len 84)

可以看出,在$lan_if上尚未做snat,在$wan_if上已经做snat     === 第二种

block out on em0
pass out quick on em0 proto 192.168.168.0/24 to any nat-to 1.2.3.4

与match+pass相比,这种写法比较简单,差异体现在systat的结果上,match+pass的state分两条,容易分辨,单pass的state只有一条,跟普通的pass类似,比较难分辨。

  • match + pass(nat-to)下的systat

    $ doas systat rules
    RULE  ACTION   DIR LOG Q IF     PR        K     PKTS    BYTES   STATES   MAX INFO
       3  Match    Out Log   fxp2                     61     5761        0       inet from ! (fxp2) to any
       4  Pass     Out Log   fxp2             K       61     5761       17       inet from 221.182.254.190/32 to any

    可以看出match和pass是成双出现的,能看得出是snat

  • pass(nat-to)下的systat

    $ doas systat rules
    RULE  ACTION   DIR LOG Q IF     PR        K     PKTS    BYTES   STATES   MAX INFO
       3  Pass     Out Log   fxp2             K       37     3893        9       inet from ! (fxp2) to any

    看不出已经做了snat,所以,推荐使用第一种写法。   == port forwarding

需求:允许客户端访问一台内网服务器ssh服务,openbsd是防火墙。

pass in on $ext_if inet proto tcp to $ext_carp_if port 7022 \
     rdr-to $vm_debn6 port ssh
pass quick on $dmz_if inet proto tcp to $vm_debn6 port ssh

pass rule有rdr-to选项时,必须指定方向:inout,故不能直接写:

pass on $wan_if ...

必须写成:

pass in on $wan_if ... to $wan_if port <port> \
  rdr-to $internal_server port

还需要额外增加一条:

pass on { $dmz_if, $lan_if } ... to $internal_server port <port>

如果是映射到防火墙的自有服务,写法为:

# SSH
pass in quick log inet proto tcp to $ext_if port ssh
 
# OpenVPN
pass in quick log inet proto udp to $ext_if port 1194
这个时候不能将$ext_if更改成$ext_carp_if。因为防火墙返回的数据包是以它外网卡的IP地址作为源地址,而不是虚拟的外网IP地址。因此,只能让用户直接访问防火墙的外网IP地址,不建议通过虚拟的外网IP地址来访问防火墙自身所提供的服务。