33.4. IPFW

FreeBSD 在 /etc/rc.firewall 中提供了一个规则集示例,它为常见场景定义了几种防火墙类型,以帮助新手用户生成适当的规则集。IPFW 提供了一种强大的语法,专业用户可以使用该语法来制作满足给定环境安全要求的自定义规则集。

本节介绍如何启用 IPFW,概述其规则语法,并演示常见配置方案的多个规则集。

33.4.1.启用 IPFW

IPFW 作为内核可加载模块包含在基本的 FreeBSD 安装中,这意味着不需要定制内核来启用 IPFW。

对于希望将 IPFW 支持静态编译为定制内核的用户,请参阅 IPFW 内核选项open in new window

要将系统配置为在引导时启用 IPFW,请添加 firewall_enable="YES"/etc/rc.conf

# sysrc firewall_enable="YES"

要使用 FreeBSD 提供的默认防火墙类型之一,请添加另一行来指定类型:

# sysrc firewall_type="open"

可用类型包括:

  • open:传递所有流量。
  • client:仅保护此计算机。
  • simple:保护整个网络。
  • closed:完全禁用除环回接口之外的 IP 流量。
  • workstation:仅使用有状态规则保护此计算机。
  • UNKNOWN:禁用加载防火墙规则。
  • filename:包含防火墙规则集的文件的完整路径。

如果 firewall_type 设置为 clientsimple,请修改 /etc/rc.firewall 中找到的默认规则以适合系统的配置。

请注意,该 filename 类型用于加载自定义规则集。

另一种加载自定义规则集的方法是将 firewall_script 变量设置为包含 IPFW 命令的可执行脚本的绝对路径。本节中使用的例子假设 firewall_script 被设置为 /etc/ipfw.rules

# sysrc firewall_script="/etc/ipfw.rules"

要通过 syslogd(8)open in new window 启用日志记录,请加入以下行:

# sysrc firewall_logging="YES"

警告

只有带有 log 选项的防火墙规则才会被记录下来。默认规则不包括这个选项,必须手动添加。因此,建议对默认规则集进行编辑以获得日志。此外,如果日志被存储在一个单独的文件中,可能需要进行日志轮换。

没有 /etc/rc.conf 变量来设置日志记录限制。要限制每次连接尝试记录规则的次数,请在 /etc/sysctl.conf 中使用以下行指定次数:

# echo "net.inet.ip.fw.verbose_limit=5" >> /etc/sysctl.conf

要通过专用接口 ipfw0 启用日志功能,请将这一行添加到 /etc/rc.conf

# sysrc firewall_logif="YES"

然后使用 tcpdump 查看正在记录的内容:

# tcpdump -t -n -i ipfw0

技巧

除非附加了 tcpdump,否则不会因日志记录而产生开销。

保存所需的编辑后,启动防火墙。要立即启用日志记录限制,还要设置上面指定的 sysctl 值:

# service ipfw start
# sysctl net.inet.ip.fw.verbose_limit=5

33.4.2. IPFW 规则的语法

当数据包进入 IPFW 防火墙时,会将其与规则集中的第一个规则进行比较,并一次推进一个规则,按顺序从上到下移动。当数据包与规则的选择参数匹配时,将执行规则的操作,并终止对规则集的搜索以查找该数据包。这被称为“首次匹配成功”。如果数据包与任何规则都不匹配,则会被强制 IPFW 默认规则编号 65535 捕获,该规则会拒绝所有数据包并以静默方式丢弃它们。但是,如果数据包与包含 countskiptotee 关键字的规则匹配,则搜索将继续。有关这些关键字如何影响规则处理的详细信息,请参阅 ipfw(8)open in new window

创建 IPFW 规则时,必须按以下顺序编写关键字。某些关键字是必需的,而其他关键字是可选的。以大写形式显示的单词表示变量,以小写形式显示的单词必须位于其后面的变量之前。# 该符号用于标记注释的开头,可能出现在规则的末尾或其自己的行上。空行将被忽略。

CMD RULE_NUMBER set SET_NUMBER ACTION log LOG_AMOUNT PROTO from SRC SRC_PORT to DST DST_PORT OPTIONS

本节概述了这些关键字及其选项。它不是每个可能选项的详尽列表。有关创建 IPFW 规则时可以使用的规则语法的完整说明,请参阅 ipfw(8)open in new window

  • CMD

    每个规则都必须以 ipfw add 开头。

  • RULE_NUMBER

    每个规则都与一个从 165534 的数字相关联。该数字用于指示规则处理的顺序。多个规则可以具有相同的编号,在这种情况下,将根据添加规则的顺序应用这些规则。

  • SET_NUMBER

    每个规则都与从 031 的设置编号相关联。可以单独禁用或启用集,从而可以快速添加或删除一组规则。如果未指定 SET_NUMBER,则该规则将被添加并设置为 0

  • ACTION

    一个规则可以与下列行动之一相关。当数据包符合规则的选择标准时,将执行指定的动作。

    allow | accept | pass | permit:这些关键词是等价的,允许符合规则的数据包。

    check-state:根据动态状态表检查该数据包。如果发现匹配,则执行与产生该动态规则的规则相关的操作,否则转到下一条规则。一个检查状态的规则没有选择标准。如果规则集中没有检查状态规则,则在第一个保持状态或限制规则处检查动态规则表。

    count: 更新所有符合该规则的数据包的计数器。搜索继续到下一条规则。

    deny | drop:任何一个词都会默默地丢弃符合该规则的数据包。

    其他操作可用。有关详细信息,请参阅 ipfw(8)open in new window

  • LOG_AMOUNT

    当一个数据包与带有 log 关键字的规则相匹配时,一条信息将被记录到 syslogd(8)open in new window,其设施名称为 SECURITY。只有当该特定规则所记录的数据包数量不超过指定的 LOG_AMOUNT 时才会发生记录。如果没有指定 LOG_AMOUNT,则限制取自 net.inet.ip.fw.verbose_limit 的值。一个零的值会移除日志记录的限制。一旦达到限制,可以通过使用 ipfw resetlog 来清除该规则的日志计数器或数据包计数器来重新启用日志记录

注意

更多操作日志记录是在满足所有其他数据包匹配条件之后,以及在对数据包执行最终操作之前完成的。管理员决定启用登录的规则。

  • PROTO

    此可选值可用于指定在 /etc/protocol 中找到的任何协议名称或编号。

  • SRC

    from 关键字后面必须有源地址或代表源地址的关键字。地址可以用 anyme(本系统接口上配置的任何地址)、me6(本系统接口上配置的任何 IPv6 地址)或 table 来表示,后面是一个包含地址列表的查询表的编号。当指定一个 IP 地址时,可以选择在其后面加上 CIDR 掩码或子网掩码。例如,1.2.3.4/251.2.3.4:255.255.255.128

  • SRC_PORT

    可以使用 /etc/services 中的端口号或名称指定可选的源端口。

  • DST

    to 关键字后必须跟目标地址或表示目标地址的关键字。SRC 部分中描述的相同关键字和地址可用于描述目标。

  • DST_PORT

    可以使用 /etc/services 中的端口号或名称指定可选的目标端口。

  • OPTIONS

    有几个关键词可以跟在源和目的地后面。顾名思义,OPTIONS 是可选的。常用的选项包括 inout,它们指定数据包流动的方向,icmp types 后面是 ICMP 消息的类型,以及 keep-state

    当保持状态规则被匹配时,防火墙将创建一个动态规则,该规则匹配使用相同协议的源和目标地址和端口之间的双向流量。

    动态规则设施很容易受到 SYN 洪水攻击的资源消耗,这种攻击会打开大量的动态规则。为了对付 IPFW 的这种类型的攻击,请使用限制。这个选项通过检查打开的动态规则,计算这个规则和 IP 地址组合出现的次数来限制同时进行的会话数量。如果这个计数大于 limit 所指定的值,数据包就会被丢弃。

    有几十个 OPTIONS 可用。参考 ipfw(8)open in new window 以了解每个可用选项的介绍。

33.4.3.规则集示例

本节演示了如何创建名为 /etc/ipfw.rules 的示例有状态防火墙规则集脚本。在此示例中,所有连接规则都使用 inout 来阐明方向。它们还使用 via 接口名称来指定数据包所经过的接口。

注意

首次创建或测试防火墙规则集时,请考虑暂时设置以下可调参数:

net.inet.ip.fw.default_to_accept="1"

这会将默认策略 ipfw(8)open in new window 设置为比默认策略 deny ip from any to any 更宽松,使得在重新启动后立即锁定系统变得更加困难。

防火墙脚本一开始就表明它是一个 Bourne shell 脚本,并刷新了任何现有的规则。然后,它创建了 cmd 变量,这样就不必在每个规则的开头输入 ipfw add。它还定义了 pif 变量,代表连接到互联网的接口名称。

#!/bin/sh
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
pif="dc0"     # interface name of NIC attached to Internet

前两个规则允许受信任的内部接口和环回接口上的所有流量:

# Change xl0 to LAN NIC interface name
$cmd 00005 allow all from any to any via xl0

# No restrictions on Loopback Interface
$cmd 00010 allow all from any to any via lo0

如果数据包与动态规则表中的现有条目匹配,则下一个规则允许数据包通过:

$cmd 00101 check-state

下一组规则定义了内部系统可以创建与互联网上的主机相关的有状态连接:

# Allow access to public DNS
# Replace x.x.x.x with the IP address of a public DNS server
# and repeat for each DNS server in /etc/resolv.conf
$cmd 00110 allow tcp from any to x.x.x.x 53 out via $pif setup keep-state
$cmd 00111 allow udp from any to x.x.x.x 53 out via $pif keep-state

# Allow access to ISP's DHCP server for cable/DSL configurations.
# Use the first rule and check log for IP address.
# Then, uncomment the second rule, input the IP address, and delete the first rule
$cmd 00120 allow log udp from any to any 67 out via $pif keep-state
#$cmd 00120 allow udp from any to x.x.x.x 67 out via $pif keep-state

# Allow outbound HTTP and HTTPS connections
$cmd 00200 allow tcp from any to any 80 out via $pif setup keep-state
$cmd 00220 allow tcp from any to any 443 out via $pif setup keep-state

# Allow outbound email connections
$cmd 00230 allow tcp from any to any 25 out via $pif setup keep-state
$cmd 00231 allow tcp from any to any 110 out via $pif setup keep-state

# Allow outbound ping
$cmd 00250 allow icmp from any to any out via $pif keep-state

# Allow outbound NTP
$cmd 00260 allow udp from any to any 123 out via $pif keep-state

# Allow outbound SSH
$cmd 00280 allow tcp from any to any 22 out via $pif setup keep-state

# deny and log all other outbound connections
$cmd 00299 deny log all from any to any out via $pif

下一组规则控制从互联网主机到内部网络的连接。它首先拒绝通常与攻击关联的数据包,然后显式允许特定类型的连接。所有源自互联网的授权服务都使用 limit 以防止泛滥。

# Deny all inbound traffic from non-routable reserved address spaces
$cmd 00300 deny all from 192.168.0.0/16 to any in via $pif     #RFC 1918 private IP
$cmd 00301 deny all from 172.16.0.0/12 to any in via $pif      #RFC 1918 private IP
$cmd 00302 deny all from 10.0.0.0/8 to any in via $pif         #RFC 1918 private IP
$cmd 00303 deny all from 127.0.0.0/8 to any in via $pif        #loopback
$cmd 00304 deny all from 0.0.0.0/8 to any in via $pif          #loopback
$cmd 00305 deny all from 169.254.0.0/16 to any in via $pif     #DHCP auto-config
$cmd 00306 deny all from 192.0.2.0/24 to any in via $pif       #reserved for docs
$cmd 00307 deny all from 204.152.64.0/23 to any in via $pif    #Sun cluster interconnect
$cmd 00308 deny all from 224.0.0.0/3 to any in via $pif        #Class D & E multicast

# Deny public pings
$cmd 00310 deny icmp from any to any in via $pif

# Deny ident
$cmd 00315 deny tcp from any to any 113 in via $pif

# Deny all Netbios services.
$cmd 00320 deny tcp from any to any 137 in via $pif
$cmd 00321 deny tcp from any to any 138 in via $pif
$cmd 00322 deny tcp from any to any 139 in via $pif
$cmd 00323 deny tcp from any to any 81 in via $pif

# Deny fragments
$cmd 00330 deny all from any to any frag in via $pif

# Deny ACK packets that did not match the dynamic rule table
$cmd 00332 deny tcp from any to any established in via $pif

# Allow traffic from ISP's DHCP server.
# Replace x.x.x.x with the same IP address used in rule 00120.
#$cmd 00360 allow udp from any to x.x.x.x 67 in via $pif keep-state

# Allow HTTP connections to internal web server
$cmd 00400 allow tcp from any to me 80 in via $pif setup limit src-addr 2

# Allow inbound SSH connections
$cmd 00410 allow tcp from any to me 22 in via $pif setup limit src-addr 2

# Reject and log all other incoming connections
$cmd 00499 deny log all from any to any in via $pif

最后一个规则记录与规则集中的任何规则都不匹配的所有数据包:

# Everything else is denied and logged
$cmd 00999 deny log all from any to any

33.4.4.内核内部 NAT

FreeBSD 的 IPFW 防火墙有两个 NAT 实现: 用户空间实现 natd(8)open in new window 和最近的内核内部 NAT 实现。两者都与 IPFW 合作提供网络地址转换。这可用于提供互联网连接共享解决方案,以便多台内部计算机可以使用单个公共 IP 地址连接到互联网。

要做到这一点,连接到互联网的 FreeBSD 机器必须充当网关。此系统必须有两个 NIC,其中一个连接到互联网,另一个连接到内部 LAN。连接到 LAN 的每台计算机都应在专用网络空间中分配一个 IP 地址,如 RFC 1918open in new window 所定义。

需要一些额外的配置才能启用 IPFW 的内核内部 NAT 功能。要在引导时启用内核内 NAT 支持,必须在 /etc/rc.conf 中设置以下内容:

gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"

注意

当设置 firewall_nat_enable 但未设置 firewall_enable 时,它将没有效果,也不执行任何操作。这是因为内核内部的 NAT 实现仅与 IPFW 兼容。

当规则集包含有状态的规则时,NAT 规则的定位很关键,因此要使用 skipto 动作。skipto 动作需要一个规则号,这样它就知道要跳到哪条规则。下面的例子建立在上一节所示的防火墙规则集的基础上。它增加了一些额外的条目,并修改了一些现有的规则,以便为内核内 NAT 配置防火墙。它首先添加了一些额外的变量,这些变量代表要跳到的规则编号、保持状态选项和一个 TCP 端口列表,这些端口将被用来减少规则的数量。

#!/bin/sh
ipfw -q -f flush
cmd="ipfw -q add"
skip="skipto 1000"
pif=dc0
ks="keep-state"
good_tcpo="22,25,37,53,80,443,110"

对于内核内部 NAT,由于 libalias(3)的体系结构,必须禁用 TCP 分段卸载(TSO),libaliasopen in new window(一个作为内核模块实现的库,以提供 IPFW 的内核内 NAT 工具)。TSO 可以使用 ifconfig(8)open in new window 在每个网络接口的基础上禁用,也可以使用 sysctl(8)open in new window 在系统范围内禁用。要禁用 TSO 系统范围,必须将其设置为 /etc/sysctl.conf

net.inet.tcp.tso="0"

一个 NAT 实例也将被配置。有可能有多个 NAT 实例,每个都有自己的配置。在这个例子中,只需要一个 NAT 实例,即 1 号 NAT 实例。配置可以有几个选项,例如:if 表示公共接口,same_ports 表示所有端口和本地端口号的映射是相同的,unreg_only 将导致只有未注册的(私有)地址空间被 NAT 实例处理,reset 将帮助保持一个正常的 NAT 实例,即使 IPFW 机器的公共 IP 地址发生变化。对于所有可以传递给单个 NAT 实例配置的可能选项,请参考 ipfw(8)open in new window。当配置一个有状态的 NAT 防火墙时,有必要允许翻译后的数据包在防火墙中被重新注入以进一步处理。这可以通过在防火墙脚本的开始处禁用 one_pass 行为来实现。

ipfw disable one_pass
ipfw -q nat 1 config if $pif same_ports unreg_only reset

入站 NAT 规则被插入到允许信任接口和环回接口上所有流量的两条规则之后,以及重新组合规则之后,但在检查状态规则之前。重要的是,为这个 NAT 规则选择的规则号,在这个例子中是 100,比前三个规则高,比检查状态规则低。此外,由于内核 NAT 的行为,建议在第一条 NAT 规则之前和允许在受信任接口上通信的规则之后放置一个重新组合规则。通常情况下,IP 碎片不应该发生,但是当处理 IPSEC/ESP/GRE 隧道流量时,它可能会发生,在把完整的数据包交给内核内 NAT 设施之前,有必要对碎片进行重新组合。

注意

用户空间 natd(8)open in new window 无需重新组装规则,因为 IPFW 操作 divert 的内部工作已经在传递到套接字之前负责重新组装数据包,如 ipfw(8)open in new window 中所述。

此示例中使用的 NAT 实例和规则编号与 rc.firewall 创建的默认 NAT 实例和规则编号不匹配。rc.firewall 是一个脚本,用于设置 FreeBSD 中存在的默认防火墙规则。

$cmd 005 allow all from any to any via xl0  # exclude LAN traffic
$cmd 010 allow all from any to any via lo0  # exclude loopback traffic
$cmd 099 reass all from any to any in       # reassemble inbound packets
$cmd 100 nat 1 ip from any to any in via $pif # NAT any inbound packets
# Allow the packet through if it has an existing entry in the dynamic rules table
$cmd 101 check-state

出站规则被修改为用 $skip 变量替换允许动作,表明规则处理将在规则 1000 处继续进行。由于 $good_tcpo 变量包含了七个允许的出站端口,七个 tcp 规则已经被规则 125 所取代。

注意

请记住,IPFW 的性能很大程度上取决于规则集中存在的规则数量。

# Authorized outbound packets
$cmd 120 $skip udp from any to x.x.x.x 53 out via $pif $ks
$cmd 121 $skip udp from any to x.x.x.x 67 out via $pif $ks
$cmd 125 $skip tcp from any to any $good_tcpo out via $pif setup $ks
$cmd 130 $skip icmp from any to any out via $pif $ks

入站规则保持不变,除了最后一条规则删除了 via $pif,以便同时捕捉入站和出站规则。NAT 规则必须在最后一条出站规则之后,必须有一个比最后一条规则更高的编号,而且规则编号必须被 skipto 动作所引用。在这个规则集中,规则号 1000 处理将所有数据包传递给我们配置的实例进行 NAT 处理。下一条规则允许任何经过 NAT 处理的数据包通过。

$cmd 999 deny log all from any to any
$cmd 1000 nat 1 ip from any to any out via $pif # skipto location for outbound stateful rules
$cmd 1001 allow ip from any to any

在这个例子中,规则 10010112510001001 控制出站和入站数据包的地址转换,以便动态状态表中的条目总是注册私人 LANIP 地址。

考虑一个内部网络浏览器,它通过 80 端口初始化一个新的出站 HTTP 会话。当第一个出站数据包进入防火墙时,它不符合规则 100,因为它是向外而不是向内。它通过了规则 101,因为这是第一个数据包,它还没有被发布到动态状态表。该数据包最终与规则 125 相匹配,因为它是在一个允许的端口上向外发送的,并且有一个来自内部局域网的源 IP 地址。在匹配这个规则时,发生了两个动作。首先,保持状态的动作为动态状态表添加了一个条目,并执行指定的动作,即跳到规则 1000。接下来,数据包经过 NAT,被发送到互联网上。这个数据包到达目的地网络服务器,在那里生成并发回一个响应数据包。这个新的数据包进入了规则集的顶部。它与规则 100 相匹配,并将其目标 IP 地址映射回原来的内部地址。然后,它被检查状态规则处理,在表中被发现是一个现有的会话,并被释放到 LAN。

在入站方面,规则集必须拒绝坏数据包,只允许授权的服务。匹配入站规则的数据包被发布到动态状态表,数据包被释放到 LAN。作为响应产生的数据包被检查状态规则识别为属于一个现有会话。然后,它被发送到规则 1000,在被释放到出站接口之前经过 NAT。

注意

从用户空间 natd(8)open in new window 到内核内部 NAT 的转换起初可能看起来是无缝的,但有一个小问题。使用 GENERIC 内核时,IPFW 将加载内核模块 libalias.ko,当 /etc/rc.conf 中启用 firewall_nat_enable 时。libalias.ko 内核模块只提供了基本的 NAT 功能,而用户区间实现的 natd(8)open in new window 在用户区间的库中提供了所有的 NAT 功能,而不需要任何额外的配置。所有的功能都是指以下内核模块,除了标准的 libalias.ko 内核模块外,在需要时还可以使用 /etc/rc.conf 中的 kld_list 指令额外加载:alias_ftp.koalias_bbt.koskinny.koirc.koalias_pptp.koalias_smedia.ko。如果使用定制内核,用户空间库的全部功能都可以在内核中使用 options LIBALIAS 编译。

33.4.4.1.端口重定向

NAT 的缺点通常是无法从互联网访问 LAN 客户端。LAN 上的客户端可以与世界建立传出连接,但不能接收传入连接。如果尝试在其中一台 LAN 客户端计算机上运行互联网服务,则会出现问题。解决此问题的一种简单方法是将 NAT 提供计算机上的选定互联网端口重定向到 LAN 客户端。

例如,IRC 服务器在客户端 A 上运行,Web 服务器在客户端 B 上运行。要使其正常工作,必须在端口 6667(IRC)和 80(HTTP)上接收到的连接重定向到相应的计算机。

使用内核内部 NAT 时,所有配置都在 NAT 实例配置中完成。有关内核内 NAT 实例可以使用的选项的完整列表,请参阅 ipfw(8)open in new window。IPFW 语法遵循 natd 的语法。redirect_port 的语法如下:

redirect_port proto targetIP:targetPORT[-targetPORT]
  [aliasIP:]aliasPORT[-aliasPORT]
  [remoteIP[:remotePORT[-remotePORT]]]

要配置上述示例设置,参数应为:

redirect_port tcp 192.168.0.2:6667 6667
redirect_port tcp 192.168.0.3:80 80

将这些参数添加到上述规则集中的 NAT 实例 1 配置后,TCP 端口将被转发到运行 IRC 和 HTTP 服务的 LAN 客户端计算机。

ipfw -q nat 1 config if $pif same_ports unreg_only reset \
  redirect_port tcp 192.168.0.2:6667 6667 \
  redirect_port tcp 192.168.0.3:80 80

单个端口上的端口范围可以用 redirect_port 指示。例如,tcp 192.168.0.2:2000-3000 2000-3000 会将端口 2000 到 3000 上收到的所有连接重定向到客户端 A 上的端口 2000 到 3000。

33.4.4.2.地址重定向

如果有一个以上的 IP 地址,地址重定向就很有用。每个局域网客户可以通过 ipfw(8)open in new window 分配自己的外部 IP 地址,然后用适当的外部 IP 地址重写从局域网客户发出的数据包,并将该特定 IP 地址上传入的所有流量重定向到特定的局域网客户。这也被称为静态 NAT。例如,如果有128.1.1.1128.1.1.2128.1.1.3 三个 IP 地址,128.1.1.1可以作为 ipfw(8)open in new window 机器的外部 IP 地址,而128.1.1.2128.1.1.3 则被转发回 LAN 客户端 AB

redirect_addr 的语法如下,其中 local IP 是 LAN 客户端的内部 IP 地址,public IP 是 LAN 客户端对应的外部 IP 地址。

redirect_addr localIP publicIP

在示例中,参数将如下所示:

redirect_addr 192.168.0.2 128.1.1.2
redirect_addr 192.168.0.3 128.1.1.3

例如 redirect_port,这些参数放置在 NAT 实例配置中。使用地址重定向,不需要端口重定向,因为在特定 IP 地址上接收的所有数据都会被重定向。

ipfw(8)open in new window 计算机上的外部 IP 地址必须处于活动状态,并且与外部接口存在别名。有关详细信息,请参阅 rc.conf(5)open in new window

33.4.4.3.用户空间 NAT

让我们从一个语句开始:用户空间 NAT 实现: natd(8)open in new window,比内核内部 NAT 具有更多的开销。对于 natd(8)open in new window 要转换数据包,数据包必须从内核复制到用户空间,然后再复制回用户空间,这带来了内核内部 NAT 所没有的额外开销。

要在引导时启用用户空间的 NAT 守护程序 natd(8)open in new window,以下是 /etc/rc.conf 中的最低配置。其中 natd_interface 设置为连接到互联网的网卡的名称。natd(8)open in new windowrc(8)open in new window 脚本将自动检查是否使用了动态 IP 地址,并配置自己来处理它。

gateway_enable="YES"
natd_enable="YES"
natd_interface="rl0"

通常,上述内核内部 NAT 所解释的规则集也可以与 natd(8)open in new window 一起使用。例外情况是内核内 NAT 实例 (ipfw -q nat 1 config …) 的配置,该配置不需要与重组规则 99 一起使用,因为其功能包含在操作中。divert 规则编号 100 和 1000 必须按顺序更改,如下所示。

$cmd 100 divert natd ip from any to any in via $pif
$cmd 1000 divert natd ip from any to any out via $pif

要配置端口或地址重定向,请使用与内核内 NAT 类似的语法。虽然现在,最好在配置文件中完成 natd(8)open in new window 的配置,而不是像内核内 NAT 那样在我们的规则集脚本中指定配置。为此,必须通过 /etc/rc.conf 传递一个额外的标志,该参数指定配置文件的路径。

natd_flags="-f /etc/natd.conf"

注意

指定的文件必须包含配置选项列表,每行一个。有关配置文件和可能的变量的更多信息,请参考 natd(8)open in new window。下面是两个示例条目,每行一个:

redirect_port tcp 192.168.0.2:6667 6667
redirect_addr 192.168.0.3 128.1.1.3

33.4.5 IPFW 命令

ipfw 可用于在活动防火墙运行时手动添加或删除单个规则。使用此方法的问题在于,当系统重新启动时,所有更改都将丢失。建议改为将所有规则写入文件,并使用该文件在启动时加载规则,并在该文件更改时替换当前正在运行的防火墙规则。

ipfw 是向控制台屏幕显示正在运行的防火墙规则的有用方法。IPFW 记帐工具为每个规则动态创建一个计数器,该计数器对与规则匹配的每个数据包进行计数。在测试规则的过程中,列出规则及其计数器是确定规则是否按预期运行的一种方法。

按顺序列出所有正在运行的规则:

# ipfw list

列出所有正在运行的规则,并带有上次匹配规则的时间戳:

# ipfw -t list

下一个示例列出了匹配规则的记帐信息和数据包计数以及规则本身。第一列是规则编号,后跟匹配的数据包和字节数,后跟规则本身。

# ipfw -a list

除静态规则外,还要列出动态规则:

# ipfw -d list

要同时显示过期的动态规则,请执行以下操作:

# ipfw -d -e list

要将计数器清零:

# ipfw zero

要仅将数字为 NUM 的规则的计数器清零:

# ipfw zero NUM

33.4.5.1.记录防火墙消息

即使启用了日志设施,IPFW 也不会自行生成任何规则日志。由防火墙管理员决定规则集中的哪些规则将被记录,并为这些规则添加日志关键字。通常情况下,只有拒绝规则被记录。习惯上,重复“ipfw default deny everything”规则,并将 log 关键字作为规则集的最后一条规则。这样,就有可能看到所有不符合规则集中任何规则的数据包。

记录是一把双刃剑。如果不小心,过量的日志数据或 DoS 攻击会使磁盘上充满日志文件。日志信息不仅被写入 syslogd,而且还被显示在根控制台屏幕上,很快就会变得很烦人。

内核选项 IPFIREWALL_VERBOSE_LIMIT=5 限制了发送到 syslogd(8)open in new window 的连续信息的数量,这些信息是关于给定规则的数据包匹配。当这个选项在内核中被启用时,关于一个特定规则的连续消息的数量被限制在指定的数量。200 条相同的日志信息是没有任何好处的。当这个选项设置为 5 时,关于某个特定规则的 5 条连续信息将被记录到 syslogd,其余的相同的连续信息将被计算并发布到 syslogd,其语句如下。

last message repeated 45 times

默认情况下,所有记录的数据包消息都写入 /var/log/security,这在 /etc/syslog.conf 中定义。

33.4.5.2.构建规则脚本

大多数有经验的 IPFW 用户创建一个包含规则的文件,并以与将它们作为脚本运行兼容的方式对其进行编码。这样做的主要好处是可以批量刷新防火墙规则,而无需重新启动系统来激活它们。此方法在测试新规则时很方便,因为该过程可以根据需要执行多次。作为脚本,符号替换可用于将常用值替换为多个规则使用。

此示例脚本与 sh(1)open in new windowcsh(1)open in new windowtcsh(1)open in new window shell 使用的语法兼容。符号替换字段以美元符号($)为前缀。符号字段没有 $ 前缀。用于填充符号字段的值必须括在双引号("")中。

启动规则文件,如下所示:

############### start of example ipfw rules script #############
#
ipfw -q -f flush       # Delete all rules
# Set defaults
oif="tun0"             # out interface
odns="192.0.2.11"      # ISP's DNS server IP address
cmd="ipfw -q add "     # build rule prefix
ks="keep-state"        # just too lazy to key this each time
$cmd 00500 check-state
$cmd 00502 deny all from any to any frag
$cmd 00501 deny tcp from any to any established
$cmd 00600 allow tcp from any to any 80 out via $oif setup $ks
$cmd 00610 allow tcp from any to $odns 53 out via $oif setup $ks
$cmd 00611 allow udp from any to $odns 53 out via $oif $ks
################### End of example ipfw rules script ############

规则并不重要,因为此示例的重点是如何填充符号替换字段。

如果上面的示例位于 /etc/ipfw.rules 中,则可以通过以下命令重新加载规则:

# sh /etc/ipfw.rules

/etc/ipfw.rules 可以位于任何位置,文件可以具有任何名称。

通过手动运行这些命令也可以完成同样的事情:

# ipfw -q -f flush
# ipfw -q add check-state
# ipfw -q add deny all from any to any frag
# ipfw -q add deny tcp from any to any established
# ipfw -q add allow tcp from any to any 80 out via tun0 setup keep-state
# ipfw -q add allow tcp from any to 192.0.2.11 53 out via tun0 setup keep-state
# ipfw -q add 00611 allow udp from any to 192.0.2.11 53 out via tun0 keep-state

33.4.6. IPFW 内核选项

为了静态地将 IPFW 支持编译为定制内核,请参阅配置 FreeBSD 内核open in new window 中的说明。以下选项可用于定制内核配置文件:

options    IPFIREWALL			# enables IPFW
options    IPFIREWALL_VERBOSE		# enables logging for rules with log keyword to syslogd(8)
options    IPFIREWALL_VERBOSE_LIMIT=5	# limits number of logged packets per-entry
options    IPFIREWALL_DEFAULT_TO_ACCEPT # sets default policy to pass what is not explicitly denied
options    IPFIREWALL_NAT		# enables basic in-kernel NAT support
options    LIBALIAS			# enables full in-kernel NAT support
options    IPFIREWALL_NAT64		# enables in-kernel NAT64 support
options    IPFIREWALL_NPTV6		# enables in-kernel IPv6 NPT support
options    IPFIREWALL_PMOD		# enables protocols modification module support
options    IPDIVERT			# enables NAT through natd(8)

注意

IPFW 可以作为内核模块加载:上述选项默认为模块编译,也可以在运行时使用可调参数进行设置。