使用 Linux 构建透明防火墙,第 IV 部分
我一直在撰写一个关于使用 Linux 构建透明(桥接)防火墙的多部分系列文章。具体来说,我正在使用运行在 Linksys WRT54GL 宽带路由器上的 OpenWrt 发行版,硬件选择主要是出于我对 WRT54GL 内置的五端口以太网交换机及其运行 OpenWrt Linux 能力的好奇。
到目前为止,我已经介绍了安装 OpenWrt、重新编译启用 iptables 桥接功能的新 OpenWrt 镜像,以及使用 OpenWrt 的 uci(统一配置接口)命令配置网络。
本月,我回顾了示例网络拓扑,并最终开始配置 iptables,这是整个项目的核心。但在我这样做之前,还有一些 OpenWrt 的日常管理任务需要完成:调整内核和网络配置,以及禁用 OpenWrt 的原生防火墙系统。
OpenWrt 作为透明防火墙的性能
在研究这篇文章时,我感到非常惊讶。虽然过去我看到过关于使用 OpenWrt 构建透明防火墙的文章和操作指南,但 Kamikaze 和 Backfire 版本默认不支持此操作模式。据报道,在 OpenWrt 下以桥接模式运行 iptables 会使整体系统性能降低高达 40%!
我还是继续写了这个系列,因为我想亲眼看看这种影响有多大,而且在我看来,这个系列仍然有用,仅仅是为了解释如何安装和使用 OpenWrt,以及解释如何为透明防火墙编写 iptables 规则。然而,在几个地方,我写下了我对示例 OpenWrt/WRT54GL 是否适合高带宽/高可用性设置的怀疑。
而且,希望听起来不太自命不凡,我希望通过激发人们对 OpenWrt 缺陷能力的更大兴趣,我可能会鼓励某人深入了解为什么在桥接模式下运行 iptables 时 OpenWrt 性能会骤降。肯定有理由解释为什么这个不算太新的内核功能在 OpenWrt 中存在问题!
我说这一切是因为我想明确表示,尽管一般的透明 Linux 防火墙构成了一种有趣且有用的技术,但 65 美元的宽带路由器加上在这种模式下运行的 OpenWrt 的特定组合可能仅适用于家庭或实验室环境,而不适用于任何需要非常快速且非常可靠地移动大量数据包的情况(考虑到 WRT54GL 最初是面向家庭用户的,这希望是不言而喻的)。我还这样说,以便您了解为什么要经历重新编译 OpenWrt 镜像和编辑 /etc/sysctl.conf 以使 iptables 桥接在 OpenWrt 中工作的繁琐步骤。
在 Linux 内核中设置 CONFIG_BRIDGE_NETFILTER=y 重新编译 OpenWrt 镜像是在 OpenWrt 中启用 iptables 桥接模式的两个步骤中的第一步。第二步是删除 /etc/sysctl.conf 中的以下参数,或将每个参数设置为“1”而不是“0”
net.bridge.bridge-nf-call-arptables=0 net.bridge.bridge-nf-call-ip6tables=0 net.bridge.bridge-nf-call-iptables=0
此外,我需要纠正上次向您展示的 OpenWrt 网络配置中犯的一个错误。您可能还记得,我更改了 OpenWrt 的默认配置,使得所有以太网端口都分配给单个 VLAN 和网桥。
但可能是由于 Linux 内核与我的 Linksys WRT54GL 上的桥接硬件交互的方式,使用该配置,我发现 iptables 忽略了 VLAN 间流量——即同一 VLAN 上端口之间的流量。为了使 iptables 在此硬件和 OpenWrt 上正常工作,我实际上需要两个 VLAN:一个对应于我的“上行链路”(连接到外部世界的以太网端口)和我的“LAN”(所有其他端口)。但是,这两个 VLAN 仍然与同一个桥接接口关联。
为了为我的上行链路端口创建一个单独的 VLAN,它是我的 WRT54GL 的“WAN”端口(或 OpenWrt 的“端口 4”),我在我的路由器上发出以下命令
root@sugartongs:/etc/config# uci set network.eth0_1=switch_vlan root@sugartongs:/etc/config# uci set network.eth0_1.device="eth0" root@sugartongs:/etc/config# uci set network.eth0_1.vlan="1" root@sugartongs:/etc/config# uci set network.eth0_1.ports="4 5"
(您会记得,端口 5 是与内核关联的虚拟端口,必须包含在 OpenWrt 网络配置中的所有“端口”语句中,这就是为什么我们的“...ports”语句设置为“4 5”而不是仅仅“4”。)
为了从我上次设置的其他 VLAN (eth0_0) 中删除 WAN 端口,我使用以下命令
root@sugartongs:/etc/config# uci set network.eth0_0.ports="0 1 2 3 5"
接下来,在我的桥接配置中,对于我命名的“lan”网络,我将两个 VLAN 都与网桥关联
root@sugartongs:/etc/config# uci set ↪network.lan.ifname="eth0.0 eth0.1"
最后,我列出我的新网络配置以确保一切正确,提交更改并重新启动
root@sugartongs:/etc/config# uci show network root@sugartongs:/etc/config# uci commit root@sugartongs:/etc/config# reboot
列表 1 显示了生成的 /etc/config/network 文件的外观。
列表 1. 更正后的 /etc/config/network
config 'switch' 'eth0' option 'enable' '1' config 'switch_vlan' 'eth0_1' option 'device' 'eth0' option 'vlan' '1' option 'ports' '4 5' config 'switch_vlan' 'eth0_0' option 'device' 'eth0' option 'vlan' '0' option 'ports' '0 1 2 3 5' config 'interface' 'loopback' option 'ifname' 'lo' option 'proto' 'static' option 'ipaddr' '127.0.0.1' option 'netmask' '255.0.0.0' config 'interface' 'lan' option 'type' 'bridge' option 'proto' 'static' option 'netmask' '255.255.255.0' option 'ipaddr' '10.0.0.253' option 'ifname' 'eth0.0 eth0.1'
请注意,在您的系统上,节可能“乱序”列出,例如,一个 VLAN 节在顶部附近,另一个在底部附近。给定节内的命令需要以正确的顺序排列,但节本身不需要,所以不用担心!
您还需要禁用 OpenWrt 的原生 DHCP 和 iptables 系统。禁用 DHCP 服务的必要性显而易见:充当 DHCP 服务器不会是非常“透明”的行为!因此,使用以下两个命令禁用它
root@sugartongs# /etc/init.d/dnsmasq disable root@sugartongs# /etc/init.d/dnsmasq stop
如果您想将 OpenWrt 用作标准的“第 3 层”(路由)防火墙,则 OpenWrt 的原生 iptables 脚本 (/etc/init.d/firewall) 很好。启用此脚本后,您可以使用 uci 命令和文件 /etc/config/firewall 来管理 iptables,方式与管理网络配置和其他 OpenWrt 系统设置非常相似。
但是,此系统不太适合以桥接模式运行 iptables——要以这种方式使用它,您需要广泛地修改脚本,考虑到它在“INPUT”、“OUTPUT”和“FORWARDING”之外使用的众多自定义表,这将是一项令人困惑的任务。因此,像这样禁用它
root@sugartongs# /etc/init.d/firewall disable root@sugartongs# /etc/init.d/firewall stop
现在您可以创建一个更适合透明防火墙的自定义 iptables 脚本。
为了编写防火墙脚本,您需要考虑您的网络拓扑以及透明防火墙如何适应。图 1 显示了我在本系列的第二部分中草拟的示例家庭网络,防火墙通过电缆连接在网络的 Internet 上行链路(通过 DSL 路由器或电缆调制解调器)和骨干网(回退到配置了 Internet 上行链路和 LAN 在同一逻辑子网上的无线宽带路由器)之间。

图 1. 示例家庭网络
您可以使用多种拓扑结构。如果您的内部网络上只有少量主机,并且您的 Internet 上行链路设备已经提供 DHCP 服务,您可以将透明防火墙用作宽带路由器(尽管在 OpenWrt 上配置 WLAN 超出了本系列的范围)。如果您的电缆调制解调器或 DSL 路由器包含交换机和/或无线 LAN 接入点,您可以将一些网络节点直接连接到该设备,并使用透明防火墙来保护其他设备。
但是,为了简单起见,我将坚持图 1 中的拓扑结构。应该足够清楚如何为您选择的任何拓扑结构自定义我的示例 iptables 脚本。让我们仔细看看图 1。
您应该注意到的第一件事是,除了电缆/DSL 调制解调器的 WAN 接口(连接到 Internet 的接口)之外,此网络上的所有内容都位于同一逻辑子网 (10.0.0.0/24) 上,该接口具有 Internet 可路由地址 4.3.2.1。该 WAN 地址仅用于说明;在实际应用中,住宅 Internet 场景中的 WAN IP 地址通常由您的 Internet 服务提供商自动分配,因此请不要尝试将您的地址设置为 4.3.2.1!
另一个重点是,在此示例网络上,客户端 PC 通过 DHCP 从 10.0.0.2 到 10.0.0.100 的池中分配 IP 地址。我的图表没有指示哪个主机提供 DHCP 服务。是电缆/DSL 调制解调器、宽带路由器还是 Web 代理?
事实上,这无关紧要!因为整个网络结构都是交换式的,DHCP 请求将自由传播,包括通过透明防火墙。但是,如果电缆/DSL 调制解调器充当 DHCP 服务器,您将需要在防火墙上编写规则以允许 DHCP 在两个方向上通过。
现在您了解了网络的结构,让我们确定如何管理其数据流。在我的示例场景中,防火墙将具有“默认拒绝”策略,就像任何好的防火墙都应该的那样。因此,任务将是预测和允许您需要防火墙允许的数据流。
首先,假设 LAN 的 DHCP 服务器位于防火墙的上游,您需要允许 UDP 端口 67(DHCP 服务器端口)和 UDP 端口 68(DHCP 客户端端口)之间的 DHCP 流量。
接下来,您不想将自己锁定在防火墙之外!您需要允许来自 LAN 的流量到达防火墙上的 TCP 端口 22。
正如您在图 1 中看到的那样,示例网络具有出站 Web 代理。由于防火墙的最佳用途之一是强制使用 Web 代理,因此您肯定只想允许来自 Web 代理的出站 Web 流量。您还将允许出站 DNS 查询(以及相应的回复)。
就是这样!防火墙下游的东西——即连接到图 1 中所示的宽带路由器的主机之间的事务——不需要防火墙允许。例如,从有线和无线 DHCP 客户端发送到网络打印机的打印作业不需要“允许 LPR”规则,因为这些数据包永远不应该到达透明防火墙。
(但是,如果您在 LAN 上只有少量主机,并且选择省略下游交换机或宽带路由器并将它们直接连接到透明防火墙,则情况并非如此。您将需要允许该类型的“LAN 到 LAN”事务。)
现在,最后,您已准备好编写自定义防火墙脚本!当然,您可以简单地编辑文件 /etc/init.d/firewall。但是,这将使以后更难恢复到 OpenWrt 的原生 uci 驱动的防火墙系统——最好保持该脚本不变。我更喜欢从头开始创建一个新脚本,任意命名为 /etc/init.d/iptables.custom。
列表 2 显示了 /etc/init.d/iptables.custom 需要是什么样子才能实现我们在上一节中达成的防火墙策略。让我们剖析一下它。
列表 2. 自定义 iptables 启动脚本
#!/bin/sh /etc/rc.common # Customized iptables script for OpenWrt 10.03 START=46 IPTABLES=/usr/sbin/iptables LOCALIP=10.0.0.253 LOCALLAN=10.0.0.0/24 WEBPROXY=10.0.0.111 stop() { echo "DANGER: Unloading firewall's Packet Filters!" $IPTABLES --flush $IPTABLES -P INPUT ACCEPT $IPTABLES -P FORWARD ACCEPT $IPTABLES -P OUTPUT ACCEPT } start() { echo "Loading custom bridging firewall script" # Flush active rules, custom tables $IPTABLES --flush $IPTABLES --delete-chain # Set default-deny policies for all three default tables $IPTABLES -P INPUT DROP $IPTABLES -P FORWARD DROP $IPTABLES -P OUTPUT DROP # Don't restrict loopback (local process intercommunication) $IPTABLES -A INPUT -i lo -j ACCEPT $IPTABLES -A OUTPUT -o lo -j ACCEPT # Block attempts at spoofed loopback traffic $IPTABLES -A INPUT -s $LOCALIP -j DROP # pass DHCP queries and responses $IPTABLES -A FORWARD -p udp --sport 68 --dport 67 -j ACCEPT $IPTABLES -A FORWARD -p udp --sport 67 --dport 68 -j ACCEPT # Allow SSH to firewall from the local LAN $IPTABLES -A INPUT -p tcp -s $LOCALLAN --dport 22 -j ACCEPT $IPTABLES -A OUTPUT -p tcp --sport 22 -j ACCEPT # pass HTTP and HTTPS traffic only to/from the web proxy $IPTABLES -A FORWARD -p tcp -s $WEBPROXY --dport 80 -j ACCEPT $IPTABLES -A FORWARD -p tcp --sport 80 -d $WEBPROXY -j ACCEPT $IPTABLES -A FORWARD -p tcp -s $WEBPROXY --dport 443 -j ACCEPT $IPTABLES -A FORWARD -p tcp --sport 443 -d $WEBPROXY -j ACCEPT # pass DNS queries and their replies $IPTABLES -A FORWARD -p udp -s $LOCALLAN --dport 53 -j ACCEPT $IPTABLES -A FORWARD -p tcp -s $LOCALLAN --dport 53 -j ACCEPT $IPTABLES -A FORWARD -p udp --sport 53 -d $LOCALLAN -j ACCEPT $IPTABLES -A FORWARD -p tcp --sport 53 -d $LOCALLAN -j ACCEPT # cleanup-rules $IPTABLES -A INPUT -j DROP $IPTABLES -A OUTPUT -j DROP $IPTABLES -A FORWARD -j DROP }
首先,请注意顶部的 includes 文件 /etc/rc.common:这提供了 enable、disable 和其他 OpenWrt 用于管理启动文件的日常管理功能。
接下来,START=46指定了在启动时运行此脚本的优先级/顺序。46与默认 OpenWrt “防火墙”启动脚本使用的插槽相同,也就是说,在启用网络之后但在 DropBear SSH 服务器和其他网络服务启动之前。
接下来是一些我们将在整个脚本中使用的“速记”变量。IPTABLES,显然,指定了本地 iptables 命令的完整路径。LOCALIP是防火墙的桥接 IP 地址;LOCALLAN是本地 LAN 的网络地址,以及WEBPROXY给出了 Web 代理的 IP 地址。
“stop”函数(如./iptables.custom stop)使脚本从内核内存中刷新所有 iptables 规则,并加载默认ACCEPT策略,用于所有三个默认防火墙表,INPUT, FORWARD和OUTPUT。这不会“停止所有流量”;相反,它停止对流量的所有限制(因此,警告消息)。
现在我们来看脚本的核心:“start”函数,其中包含防火墙策略,形式为 iptables 命令列表。
首先,刷新任何活动规则并删除任何自定义表,以便您从一个干净的状态开始($IPTABLES --flush和$IPTABLES --delete-chain)。接下来,为 INPUT、FORWARD 和 ACCEPT 链设置默认拒绝策略。(您可以同样轻松地选择REJECT作为默认策略,但因为这涉及向被拒绝的客户端发送 ICMP 回复,而 DROP 只是忽略它们,所以 DROP 具有轻微的性能优势。)
接下来是两条规则,通过允许所有来自和发往“环回”接口的数据包,来允许防火墙本身上的进程间通信。然而,紧随其后的是一条反欺骗规则,该规则阻止来自防火墙自身 IP 地址的寻址到防火墙的流量。
接下来是两条规则,允许 DHCP 请求——即从 UDP 端口 68 发送到 UDP 端口 67 的数据包——和 DHCP 响应——即从 UDP 端口 67 发送到 UDP 端口 68 的数据包。只有当您的 DHCP 服务器位于防火墙的另一侧,远离 DHCP 客户端时,这两条规则才是必要的。
您可能已经注意到,这两条 DHCP 规则和随后的 SSH、HTTP 代理和 DNS 规则都是“无状态的”。您没有调用 iptables “state”模块(该模块允许您例如允许出站 DHCP 查询,同时让内核决定什么是有效的响应),而是显式地允许回复流量。这是一种公认的编写 iptables 规则的过时方法。
但是,正如我在侧边栏中提到的,OpenWrt 用作桥接防火墙时存在严重的性能问题。由于“state”模块会带来更多的性能损失,并且因为此防火墙策略一开始就很简单,所以我正在以老式的方式进行操作。对于性能更好的发行版/硬件组合上的桥接防火墙,我肯定会利用 Linux 的状态跟踪功能!
列表 2 中的下一对规则允许 SSH 连接到防火墙本身,但仅允许来自本地 LAN 的连接。请注意,SSH 事务的“传入”部分在 INPUT 表中处理,而“传出”部分在 OUTPUT 表中处理。如果您使用的是-m state,则 OUTPUT 部分将是隐式的。
接下来是两对规则,仅允许 Web 代理发送和接收往返于 TCP 端口 80 和 443 的流量,当然,这分别对应于 HTTP 和 HTTPS。
除非 DNS 也这样做,否则这将不起作用,因此接下来是允许 DNS 查询到 TCP 和 UDP 端口 53 的规则(通常,DNS 查询仅使用 UDP,但偶尔它们也可能通过 TCP 发生)。
最后,脚本以三个“清理”规则结尾,这些规则在每个默认表的底部放置一个“丢弃所有”规则。当然,这些规则与我在start()函数开头设置的默认“DROP”策略是冗余的,但指定此类清理规则是防火墙的最佳实践;有时冗余是可取的!
当您键入任何防火墙脚本时,请务必小心!至少,仔细检查允许访问防火墙的 SSH 规则。如果这些规则有任何问题,一旦您运行脚本,您将被锁定,甚至可能需要重新刷新防火墙才能恢复。如果 SSH 工作正常,您可以修复其他问题,但如果 SSH 不工作,您将被困住。
一旦您有足够的信心测试您的规则,请保存新脚本。务必像这样设置其“执行”位
root@sugartongs:/etc/init.d# chmod a+x ./iptables.custom
并且,像这样在启动时启用脚本
root@sugartongs:/etc/init.d# ./iptables.custom enable
现在是关键时刻——加载规则
root@sugartongs:/etc/init.d# ./iptables.custom start
通过确保您希望工作的事情仍然有效(通过 SSH 连接回防火墙,通过您的 Web 代理浏览 Web 等)来测试规则。另外,请务必测试一些您不希望工作的事情,例如不通过代理浏览 Web 或使用 FTP 客户端应用程序连接到 FTP 服务器。以我自己的经验来看,OpenWrt 的挑战在于让 iptables “看到”并对流量采取行动;真正的考验是确保它阻止任何东西!
资源
OpenWrt 项目主页:www.openwrt.org
OpenWrt 的统一配置接口文档:wiki.openwrt.org/doc/uci
OpenWrt 论坛(如果您不仅仅是随意使用 OpenWrt,迟早会在那里寻求帮助):https://forum.openwrt.org
Mick Bauer (darth.elmo@wiremonkeys.org) 是美国最大的银行之一的网络安全架构师。他是 O'Reilly 图书Linux 服务器安全第二版(以前称为使用 Linux 构建安全服务器)的作者,信息安全会议的特约演讲者和“网络工程波尔卡舞曲”的作曲家。