从零开始构建 Linux 防火墙

作者:Dinil Divakaran

使用 Linux 机器可以轻松构建防火墙。本文介绍了使用 Linux 中的工具从头开始开发防火墙的基本步骤。它旨在为有兴趣了解(Linux)防火墙的新手而编写。更重要的是,本文适合所有希望亲自动手并尽快启动并运行防火墙,但又不想错过重要概念的新管理员。我在印度理工学院 (IIT) 马德拉斯分校计算机科学与工程系的 DON(分布式和光网络)实验室从事基于 Linux 的防火墙的工作经验,是撰写本文的最主要动力。

在本文中,我们将探讨开发一个位于边缘的防火墙,将您的私有网络与世界其他地方隔开;因此,该防火墙也将充当网关。

Starting a Linux Firewall from Scratch

图 1. 防火墙图

首先,您为什么需要这样的防火墙?最重要的是,您需要限制对网络中机器的访问,该网络可能由各种服务器组成。其中一台可能是邮件服务器,另一台可能是 DNS 服务器,但只需要访问这些特定服务(由这些服务器提供),而不是网络上的任何内容。简单来说,防火墙用于保护私有网络免受世界其他地方(称之为公共网络,在大多数情况下是互联网)的侵害。

拥有防火墙的一个不太明显的原因是,有必要阻止所有不需要的流量流入或流经您的网络,否则可能会限制带宽。此类流量理想情况下应在入口(网关或防火墙)处停止。一个很好的例子是存在许多子网的情况,例如在学院或大学校园中。这样一个子网中的一台机器可能感染了病毒,并可能泛洪或广播 ARP 数据包。同样,来自私有网络外部的一些 Windows PC 可能会广播 netbios (netbios-ns/netbios-dgm) 数据包,这些数据包对您的网络毫无意义,因此应被防火墙阻止。

但是,某些 ARP 数据包可能是对您网络(或子网)中机器的合法请求。如果您阻止此类合法的 ARP 广播请求,则任何数据包(好的或坏的)都将无法到达您的网络,因为私有网络外部的机器将无法获得与您网络中机器的 IP 地址相对应的以太网地址。为了解决这个问题,您应该将防火墙配置为充当 ARP 请求的代理——也就是说,您的防火墙应该回复 ARP 请求。

现在,让我们进入实施细节。假设您的私有网络是 192.168.9.0/24。您的防火墙(也是网关)必须有两个接口:一个指向您的网络 (eth0),另一个连接到公共网络 (eth1)。

首先,为两个接口配置 IP。这可以使用网络配置工具或 ifconfig 命令完成。理想情况下,最好使用系统网络配置工具(Fedora Core 2–5 中的 system-config-network)或编辑配置文件(在 FC 2–5 中位于 /etc/sysconfig/network-scripts),以便即使在网络启动(作为引导过程的一部分)或重新启动(手动)时,配置也能保留。您还可以通过在 /etc/rc.d/rc.local 的末尾附加 ifconfig 命令来配置 IP(因为此文件在引导过程结束时执行)。但是,如果您这样做,请确保在手动重新启动网络时执行这些命令。

我们使用 ifconfig 是为了与发行版无关(因为缺乏更好的术语)。

接口要使用的 IP 地址没有硬性规定,但通常,子网中的最后两个 IP 地址用于此类目的。现在,将 192.168.9.253 分配给 eth0,将 192.168.9.254 分配给 eth1

echo "Configuring eth0"
/sbin/ifconfig eth0 192.168.9.253 up

echo "Configuring eth1"
/sbin/ifconfig eth1 192.168.9.254 up

作为网关的防火墙最重要的功能是转发数据包。以下是我们执行此操作的方式

echo "Enabling IP forwarding"
echo "1" > /proc/sys/net/ipv4/ip_forward

早些时候,我们说过防火墙还应该充当 ARP 请求的代理。这意味着防火墙将回复 ARP 请求,查询您网络 (192.168.9.0/24) 中任何机器的以太网地址。防火墙会发送查询广播的机器(例如 192.168.9.8)的 MAC 地址吗?不会。相反,它将发送自己的 MAC 地址,稍后,当它收到发往 192.168.9.8 的数据包时,它会将数据包转发到 192.168.9.8(当然,前提是规则允许数据包通过)。在新发行版中启用代理 ARP 非常容易

echo "Enabling Proxy ARP"
echo "1" > /proc/sys/net/ipv4/conf/eth1/proxy_arp

接下来,在防火墙中设置路由条目。私有网络可以通过 eth0 到达,而发往公共网络的数据包应通过 eth1

echo "Route to 192.168.9.0/24 is through eth0"
/sbin/route add -net 192.168.9.0/24 eth0

echo "The default gateway is eth1"
/sbin/route add default eth1

同样,您必须告知网络中的所有机器使用 192.168.9.253 作为默认网关(因为您必须通过网关才能访问网络外的任何机器)。可以直接访问 LAN 机器。在您网络中的所有机器(防火墙除外,显然)上执行以下操作

echo "Add default route through the gateway"
/sbin/route add default gw 192.168.9.253 eth0

echo "192.168.9.0/24 is directly reachable"
/sbin/route add -net 192.168.9.0/24 eth0

接下来是防火墙规则——保护网络的规则。规则是使用 iptables 工具编写的。这是一个非常有用的工具,尽管有点复杂,在各种选项上都有详细的手册页。iptables Netfilter 使用三个不同的内置链:INPUT、FORWARD 和 OUTPUT。数据包遍历链,因此,规则是为特定链编写的。对于您的防火墙,任何发往您的防火墙(192.168.9.253 或 192.168.9.254)的数据包都会进入 INPUT 链。如果数据包旨在转发(即,它不是发往您的防火墙的,并且您的防火墙中存在到达目的地的路由),则它将通过 FORWARD 链。任何由您的防火墙生成的数据包都将通过 OUTPUT 链从盒子里发出。(此简要说明适用于任何 Linux 盒子。)

虽然您永远不希望防火墙转发通过它的每个数据包,但您可能希望测试网关的功能是否与上述配置一起工作。为此,请将 FORWARD 链的默认策略设置为 ACCEPT(使用 -P 选项)——也就是说,任何通过 forward 链的数据包都会被接受

/sbin/iptables -P FORWARD ACCEPT

现在,从网络 192.168.9.0/24 中的任何机器(除了防火墙)到网络外部任何(活动的)机器的 ping 请求都将返回 ICMP 回显回复数据包。如果外部机器无法访问,则可能是电缆或网卡有问题,或者您可能配置错误。

现在,让我们构建“墙”。设置防火墙的最简单方法是拒绝 (DROP) 每种类型的数据包,然后编写规则以允许 (ACCEPT) 您希望看到通过的数据包。因此,让我们将每个链中的默认策略设置为丢弃数据包。在执行此操作之前,请清除所有现有规则

echo "Flush existing rules"
/sbin/iptables -F

echo "Set the default policy to drop packets"
/sbin/iptables -P INPUT DROP
/sbin/iptables -P OUTPUT DROP
/sbin/iptables -P FORWARD DROP

到目前为止,您可能已经注意到,规则基本上指定了数据包必须具备的某些条件。如果满足这些条件,则执行规则中指定的操作,否则检查链中的下一个规则,依此类推,直到找到匹配的规则。如果链中没有规则匹配,则执行默认操作或策略(此处为 DROP)。

让我们编写我们的第一条规则——允许来自私有网络的传出 SSH 数据包的规则

echo "Allow outgoing SSH"
/sbin/iptables -A FORWARD -p TCP -i eth0 \
       -s 192.168.9.0/24 -d 0/0 --dport 22 -j ACCEPT

这条规则是不言自明的——好吧,几乎是。选项 -A 指定要将规则附加到的链,-p 指定协议(UDP、TCP、ICMP 等)。选项 -i 命名数据包将通过其接收的接口。由于数据包来自 192.168.9.0/24 网络(-s 指定源地址)用于传出 SSH 数据包,因此它将通过防火墙的 eth0。目标端口 (--dport) 对于 SSH 流量是 22。目标地址用 -d 选项指示,0/0 表示任何地址。最后,匹配此类数据包的操作是 ACCEPT(用 -j 选项指定),这意味着允许匹配的数据包通过。

现在,我们编写了一条规则,允许 SSH 流量从 192.168.9.0/24 到任何地方。但是,这会起作用吗?您能否从您的私有网络对公共网络中的机器执行 SSH 登录?我们在哪里允许数据包从 SSH 服务器(在公共网络中)返回到客户端(在私有网络中)?以下规则实现了这一点

/sbin/iptables -A FORWARD -p TCP -i eth1 -s 0/0 \
       --sport 22 -d 192.168.9.0/24 -j ACCEPT

这看起来不错,但是我们需要为每个服务编写这样的规则。更糟糕的是,上面的规则做的事情超出了需要。它允许任何机器使用源端口 22 连接到私有网络。我们应该做的是附加一条规则,该规则仅允许来自公共网络的那些数据包,这些数据包是 192.168.9.0/24 网络中机器发起的 SSH 连接的一部分。

iptables 维护状态信息以进行此类连接跟踪。维护的四个状态是 NEW、ESTABLISHED、RELATED 和 INVALID。我们在此不详细讨论这些状态。目前,请记住,状态 NEW 表示数据包是新连接的一部分。当在反方向看到响应数据包时,连接变为 ESTABLISHED。请注意,这与 TCP 连接建立过程中的状态无关。相应请求的 ICMP 或 UDP 回复也会将连接标记为 ESTABLISHED。请参阅 iptables-tutorial.frozentux.net/iptables-tutorial.html#STATEMACHINE 以了解连接跟踪机制的确切工作方式。现在(在删除上述规则后),为了转发构成 ESTABLISHED 连接的所有这些数据包,我们编写以下规则

echo "Allowing ESTABLISHED connections"
/sbin/iptables -A FORWARD -m state --state \
       ESTABLISHED -j ACCEPT

此规则确保仅接受作为 ESTABLISHED 连接一部分的数据包;到 192.168.9.0/24 的新连接请求将会被接受。理想情况下,要访问任何服务(例如 HTTP 或 FTP),我们只需要允许 NEW 和 ESTABLISHED 连接出去(NEW 将允许第一个数据包,ESTABLISHED 将允许同一连接的所有后续数据包),并且只允许 ESTABLISHED 连接进入私有网络。同样,如果您的网络中有一个 DNS 服务器,必须允许从外部访问(查询),则以下规则可以做到这一点(假设 198.168.9.1 是 DNS 服务器)

echo "Allowing incoming DNS requests"
/sbin/iptables -A FORWARD -p TCP -i eth1 \
       -d 198.168.9.1 --dport 53 -j ACCEPT

请注意,此处使用的接口是 eth1,因为来自公共网络的数据包将在 eth1 处接收。(我们没有使用 -s 0/0,因为它默认添加。)另外,请记住,DNS 查找将成功,仅仅是因为我们已经附加了允许 ESTABLISHED 连接到 FORWARD 列表的规则(是的,UDP 流量也具有关联的 ESTABLISHED 状态)。

到目前为止,我们已经阻止了除 SSH 和 DNS 之外的所有协议。对于新的系统管理员来说,阻止 ICMP 数据包是一种常见的做法。这不是一个好主意,因为 ICMP 数据包对于许多目的都很有用,例如了解大型 LAN 中不同互连网络之间的路由,查看机器是否启动,用于路径 MTU 发现等等。因此,假设我们是明智的管理员,让我们允许 ICMP 数据包通过防火墙

echo "Allowing ICMP packets"
/sbin/iptables -A FORWARD -p ICMP -j ACCEPT

早些时候,我们阻止了进出防火墙盒子的任何数据包(使用 INPUT 和 OUTPUT 链)。出于诊断目的,我们可以允许 ICMP 数据包通过两个链——也就是说,允许 ICMP 数据包进出防火墙

echo "Allowing ICMP packets to the firewall"
/sbin/iptables -A INPUT -p ICMP -j ACCEPT

echo "Allowing ICMP packets from the firewall"
/sbin/iptables -A OUTPUT -p ICMP -j ACCEPT

ICMP 数据包也可以进行速率限制(作为预防基于 ICMP 的攻击的预防措施)

echo "Limit ICMP requests to 5 per second"
/sbin/iptables -A FORWARD -p icmp --icmp-type \
       echo-request -m limit --limit 5/s -j ACCEPT

我们也可以选择忽略 ping 广播——也就是说,发往广播地址的 ICMP 数据包,例如 ping 192.168.9.255(ICMP 广播请求用于 Smurf 攻击)

echo "Ignoring ICMP broadcast requests"
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

一旦系统重新启动,所有这些规则(命令)都将丢失;但是,iptables 具有保存和恢复这些规则的选项。但是,更好的方法是将规则保存在文件中(例如,firewall.sh),赋予它可执行权限,并将脚本名称附加到 /etc/rc.d/rc.local 的末尾。这样,您始终可以编辑和修改防火墙脚本。

Dinil Divakaran 正忙于更多地了解自己和生活。与此同时,他喜欢教授和讨论生活以及技术。

加载 Disqus 评论