什么是 Linux Netfilter

Demon.Lee 2023年03月26日 1,368次浏览

在处理云原生相关问题时,总会看到通过 iptables 来配置相关规则,或使用 ipvs 来完成负载均衡等内容,但这背后的逻辑,自己并不清楚。大多数情况,我们使用搜索引擎一查,哦,原来是这么个意思。可然后呢?

不明就里,没有真正理解一座软件大厦是如何一步步搭建起来的,终归无法让人心安。怎么办?学呗,用呗,总结呗。学习,学习,的是知识,的是技能,并且要持续学习,系统学习。通过学习的过程,不断构建自己的知识框架,借此逐步消除那种让人不安的不确定感。

说回正题,要知道 Netfilter 是什么,就得先聊一聊防火墙(Firewall),因为它就是一个防火墙框架。

防火墙

何谓防火墙

因为我们是在聊网络,所以要严谨一点的话,这里的防火墙,其实指的是网络防火墙。

由于防火墙这个名字比较生动,看到这个词,我们也能大致明白其基本的作用:通过一道墙,把外部环境与内部环境隔离开,从而保护我们内部的网络资源不被攻击,借此避免或减少财产的损失。

防火墙(来源:维基百科)


接着,我们再来看看维基百科给出的定义

防火墙(英语:Firewall)在计算机科学领域中是一个架设在互联网与内网之间的信息安全系统,根据持有者预定的策略来监控往来的传输。防火墙可能是一台专属的网络设备,也可执行于主机之上,以检查各个网络接口的网络传输。它是目前最重要的一种网络防护设备,从专业角度来说,防火墙是位于两个或以上网络间,实行网络间访问或控制的一组组件集合的硬件或软件。

通过这个定义,我们来简单梳理一下:

  • 如果只有一个网络,也就无所谓攻击或防范了,所以防火墙至少用在两个网络之间;
  • 有了防火墙,哪些请求能通过,哪些不能(丢弃或拒绝),通过在防火墙上添加策略就能实现。从这个角度来看,防火墙就像是一个路由器,其中的策略就是路由规则;
  • 防火墙可以是一个独立的网络设备,也可以是一个纯软件。其实独立的网络设备里面运行的也是软件,只不过其中没有我们的用户进程罢了;
  • 因为要隔离,所以防火墙的位置要架在网络的枢纽点上,即它能过滤网络之间来往的所有数据包,否则就变成了马奇诺防线;
  • 隔离的是两个或两个以上的多个网络,但这不表示只能隔离公网(如 WAN 或 WWW 万维网)与组织(或个人)的私有网络,而是任何有交互的网络之间都可以。比如,一个大型企业内部,针对财务、人力资源、研发中心等团队可能都有独立的子网,要想访问对方,就需要向网络管理员发起申请(即配置访问策略),否则请求数据包无法通过;
  • 前面提到,防火墙要能过滤网络之间来往的所有数据包,所以不能理解它只能过滤外部进入的包。对于内部向外部发送的包(不论是主动请求还是被动响应),也能过滤。基于此,类似于 TCP 三次握手的环节,我们便能掐断某一类型的包,比如 SYN,从而阻止连接的建立;
  • ……

好,了解了防火墙的基本概念,也明白了建设防火墙的目的,那我们使用防火墙进行策略配置的条件有哪些呢?

防火墙的过滤条件

仔细想想,过滤网络包最简单的方式就是 IP 地址,比如只允许 192.168.0/24网段 IP 的流量通过。那,还有吗?其实也简单,无论怎么变,数据包走的路也逃不过 TCP/IP 和 OSI 参考模型。所以,过滤的条件就是网卡接收到数据包后,流量所经环节中能被处理的属性(参数)。

Wireshark 抓包示例


上图是运行 curl -i baidu.com时通过 Wireshark 抓包的截图。图中展示的各个网络层次上的属性,其实就是我们的过滤依据:

  • 数据链路层:比如发送端或响应端的 MAC 地址;
  • 网络层:IP、IP Header Length(IP 包头的长度)、Time to Live、Total Length(不含链路层包头的总体长度)、Flags(数据包是否被分割)以及 Protocol (上层协议的类型,比如这里的 TCP(6));
  • 传输层:从上图可以看到,相比网络层,传输层可以控制的参数就更多了,比如 Header Length(TCP 的包头长度)、Port、Flags(连接控制参数) 等;
  • 应用层:即数据包中传输的具体内容,比如图中的 http 协议,我们就可以使用 Host 头字段来过滤要访问的服务对象。

了解了防火墙的过滤依据,我们再来看看防火墙的主要分类。

防火墙的分类

简单思考一下,就可以从上面的过滤条件推导出一种分类方式:过滤位置。根据过滤位置的不同,可以将其分为两种:网络层防火墙和应用层防火墙。

  • 网络层防火墙,又称为数据包过滤防火墙,作用于 TCP/IP 协议族的 Internet 层,也就是 OSI 参考模型的第三层;
  • 应用层防火墙,作用在 TCP/IP 协议族的 Application 层。

OSI vs TCP/IP


对网络层来说,数据是一个个流转的数据包,至于里面是什么内容,它不关注。所以,防火墙能检测的最小粒度就是一个个数据包。前面 Wireshark 抓包实例中,我们已经看到了网络层中可以作为过滤的条件,比如源 IP 或目标 IP、数据包长度等。由于这些条件的判断比较简单,所以数据包过滤防火墙对内存和 CPU 的要求比较低。当然,其缺点也很明显,就是过滤的精细程度不够,数据包中是否有非法信息无法得知,比如 http 响应报文或邮件内容中是否包含病毒。

网络层防火墙 vs 应用层防火墙


与此对应的应用层防火墙则正好相反,其检测粒度小,可以进行非常多的控制,比如广告、病毒等信息的过滤。由于网络传输通道带宽的差异,一个稍微大一点的应用报文发送出去时,都会拆成一个个较小的数据包。网络层的数据包就是其中被拆的一种,因而即使能看到网络层的数据包内容,也是缺胳膊少腿,不完整的。这就是为什么前面说,网络层防火墙检测粒度较粗的原因。一个不完整的报文如何过滤呢?但到了应用层就不一样,一个个小数据包会组装成完整的应用报文,每一个字节都可以被检测到。如此一来,应用程序想进行非法信息的检测也就更容易了。

除了前面提到的分类方式,我们还可以按照防火墙的结构对其进行划分,在《Linux网络安全技术与实现》一书中,作者列举了以下几类:单机防火墙、网关式防火墙和透明防火墙,其中的网关式防火墙又因安全的问题迭代了几个不同的版本。结合书中的内容,笔者在这里对这三类防火墙做一个简单总结:

1、单机防火墙

这是最简单的一类,就是在当前这台机器上安装防火墙软件,其工作在网卡的驱动程序上,以此来管控所有进出的流量。其实笔者觉得,前面提到的网络防火墙和应用层防火墙都可以作为单机防火墙的一种细化分类。

2、网关式防火墙

网关的意思,就是这个防火墙不再只保护某一台主机,而是它背后的整个私有网络。


可以看到,对外提供服务的 mail 和 web 两台机器与企业内部工作网络都在一个网段内,如果 mail 或 web 其中一台被攻破,整个企业的网络就全部瘫痪了。

这种架构下,一般都会使用 NAT 机制来实现公网 IP 与内网 IP 之间的映射,即在防火墙上会配置 ISP 分配的公网 IP,一个给企业内部工作上网使用,一个给 web 服务器,一个给 mail 服务器。NAT 全称 Network Address Translation,即网络地址转换,路由器、防火墙等设备一般都具备该功能。

为此,就有了下面两种改进:

1)多加一块网卡,把对外提供服务的机器和内部工作网络分隔成两个私有网段,并设置反向代理服务器,把接收请求的机器和处理请求的机器隔开,如下图所示:


2)另一种是,在上一条的基础上,再加一个防火墙(并且不是一个厂家的),双重安全保障,如下图所示:


3、透明防火墙

最后一种叫透明防火墙,这里的透明其实指的是对黑客透明,因为这类防火墙工作在 OSI 的第二层,而 OSI 二层上没有 IP,故也叫桥接式防火墙。之前的防火墙基本都工作在 OSI 三层之上,因此又被称为路由模式。


这种防火墙没有 IP,黑客针对 IP 的攻击自然就无效,而其依然具备数据包过滤的功能,所以隐蔽性和安全性都很好。另外一个特点就是适配性好,部署能力强。这是因为没有 IP,无需对现有的网络重新分区。关于 Linux 网桥,笔者将在后续的学习中做深入分析,这里不再赘述。

Netfilter

了解了防火墙的基本原理和结构之后,我们开始正式介绍 Netfilter 框架。

概念

Netfilter 是 GNU/Linux 下第三代防火墙,在这之前还有 ipfwadmipchains两款,但它们都没有 Netfilter 出色。从 Linux Kernel 2.4 开始,就把 Netfilter 框架作为默认防火墙加入到 Linux 内核当中,即 Netfilter 是 Linux Kernel 的一个子功能,专门用来增强网络安全。

Netfilter 由 Linux 防火墙和网络的主要维护者 Rusty Russell 提出并主导设计实现的。但需要说明的是,Linux Kernel 和 Netfilter 是两个不同的组织开发并维护的,其中 Netfilter 对应的组织即为 www.netfilter.org


从官方文档的描述可以看到,Netfilter 是一个开源项目,其主要功能包括包过滤、网络地址转换(包括端口)、数据包日志记录、用户态数据包排队以及其他数据包处理等。

那它是如何对网络数据包进行干预的呢?答案是在网络协议栈(主要在网络层)中预留钩子(hooks)。这些钩子允许内核模块注册回调函数,一旦有网络数据包经过这些钩子,就会触发对应的回调函数。如此,便实现了通过程序代码来干预 Linux 网络通信。

下面我们就来看看 Netfilter 的整体架构,看看这些钩子是如何干预网络的。

框架

由于 Netfilter 是干预网络的,所以我们先来复习一下整个网络协议栈:


上图中的结构很清晰,这里再单独重述一下 Device 和 Driver 两个关节部位:

1)Driver 是网卡驱动程序,即网络链接层靠近硬件一侧的接口。发送数据包时,它会通过 DMA(Direct Memory Access,直接内存访问)将主存中的待发送的数据包复制到驱动内部的缓冲区之中,同时把上层提供的相关信息(如 IP 数据包,下一跳的 MAC 地址,网卡的 MAC 地址 等)一并封装成以太帧(Ethernet Frame),并自动计算校验和。接收数据包时,操作正好相反。
2)Device 则代表着网络设备,但不是物理上的设备,而是一个抽象,即网络链接层靠近系统一侧的接口(向操作系统开放的接口)。抽象背后的设备可以是真正的物理设备(比如网卡),也可以是一段程序代码,很多抓包软件就工作在这里,比如 Wireshark,tcpdump 等。Device 的主要作用是抽象出统一的接口,供程序代码调用,控制数据包收发的出入口。另外,它还负责准备好 Driver 中需要使用的数据,比如 IP 数据包、下一跳的 MAC 地址(通过 ARP 请求拿到)等。

网卡内部结构示意图(来源:《网络是怎样连接的》)


在上面网络协议栈的基础上,我们把 Netfilter 框架植入其中:


结合上图,根据五个钩子的名称,我们也能大致猜测其作用:

  • Prerouting:只要数据包从设备(如网卡)那里进入到协议栈,就会触发该钩子。当我们需要修改数据包的 “Destination IP” 时,会使用到它,即 Prerouting Hook 主要用于目标网络地址转换(DNAT,Destination NAT)。
  • Forward:顾名思义,这里指代转发数据包。前面的 Prerouting Hook 并未经过 IP 路由(Linux 下可以使用 ip routeroute -nv查看路由表),所以不管数据包是不是发往本机的,全部都照单全收。但经过 IP 路由后,如果发现数据包不是发往本机,则会触发 Forward Hook 进行处理。此时,本机就相当于一个路由器,作为网络数据包的中转站,Forward Hook 的作用就是处理这些被转发的数据包,以此来保护其背后真正的“后端”机器。
  • Input:经过 IP 路由后,如果发现数据包是发往本机的,则会触发本钩子。Input Hook 一般用来加工发往本机的数据包,当然也可以做数据过滤,从而保护本机的安全。
  • Output:数据包送达到应用层处理后,会把结果送回请求端,在经过 IP 路由之前,会触发该钩子。Output Hook 一般用于加工本地进程输出的数据包,同时也可以限制本机的访问权限,比如发往 www.example.org的数据包都丢弃掉。
  • Postrouting:数据出协议栈之前,都会触发该钩子,无论这个数据是转发的,还是经过本机进程处理过的。Postrouting Hook 一般用于源网络地址转换(SNAT,Source NAT)。

关于 DNAT,SNAT,笔者将在后续的文章中使用具体的示例展开说明,这里先略过。

从上面的示意图来看,Netfilter 看起来好像也没那么复杂,这就是优秀的架构带给我们的美感:一眼就明白其中的大体逻辑。其实这个示意图是简化后的,我们再来看一个官方提供的流程图


好,了解了 Netfilter 的整个架构逻辑,接下来,我们来看看它是如何干预网络流量的。

干预逻辑

前面提到,每个钩子处都可以注册回调函数,并且可以注册多个,所以会有优先级的概念,保证执行顺序。把这些回调函数串起来,就构成了一条链,我们将其称为回调链(Chained Callbacks)。虽说 Netfilter 框架就是一套简单的事件回调机制,但它却是整个 Linux 网络大厦的基石,包括地址转换、封包处理、地址伪装、基于协议的连接跟踪、数据包过滤、透明代理、带宽限速以及访问控制等,都是在 Netfilter 基础上实现的。

由于要注册回调函数,而且还位于内核层面,这对日常系统运维来说很不友好。为此,基于 Netfilter 框架开发的应用便出现了,典型的就是 Xtables 系列,包括 iptables,nftables,ebtables,arptables,ip6tables 等,其中 iptables 和 nftables 是 Netfilter 官方主推的两个工具,而 nftablesiptables 的继任者。

  • nftables replaces the popular {ip,ip6,arp,eb}tables. This software provides a new in-kernel packet classification framework that is based on a network-specific Virtual Machine (VM) and a new nft userspace command line tool. nftables reuses the existing Netfilter subsystems such as the existing hook infrastructure, the connection tracking system, NAT, userspace queueing and logging subsystem. This software is available upstream since Linux kernel 3.13.
  • iptables is the userspace command line program used to configure the Linux 2.4.x and later packet filtering ruleset. It is targeted towards system administrators. Since Network Address Translation is also configured from the packet filter ruleset, iptables is used for this, too. The iptables package also includes ip6tables. ip6tables is used for configuring the IPv6 packet filter.

除了查阅相关资料,在 Linux 上,我们也可以通过 man Xtables进行了解:

  • ebtables is an application program used to set up and maintain the tables of rules (inside the Linux kernel) that inspect Ethernet frames. It is analogous to the iptables application, but less complicated, due to the fact that the Ethernet protocol is much simpler than the IP protocol.
  • arptables is a user space tool, it is used to set up and maintain the tables of ARP rules in the Linux kernel. These rules inspect the ARP frames which they see. arptables is analogous to the iptables user space tool, but arptables is less complicated.

我们以 iptables 为例,它作为一个用户态的命令行工具,通过配置数据包过滤规则,从而控制内核层面实现相应的需求。这就大大降低了大家使用 Netfilter 框架的门槛。我们可以通过 man 8 iptables大致了解该工具的使用规则:

       iptables [-t table] {-A|-C|-D} chain rule-specification

       ip6tables [-t table] {-A|-C|-D} chain rule-specification

       iptables [-t table] -I chain [rulenum] rule-specification

       iptables [-t table] -R chain rulenum rule-specification

       iptables [-t table] -D chain rulenum

       iptables [-t table] -S [chain [rulenum]]

       iptables [-t table] {-F|-L|-Z} [chain [rulenum]] [options...]

       iptables [-t table] -N chain

       iptables [-t table] -X [chain]

       iptables [-t table] -P chain target

       iptables [-t table] -E old-chain-name new-chain-name

       rule-specification = [matches...] [target]

       match = -m matchname [per-match-options]

       target = -j targetname [per-target-options]

注意最后的 target = -j targetname [per-target-options],这里指定了规则的具体行为,比如允许数据包通过,或拒绝,或转发等。结合 man 8 iptables-extensions,我们来看看 iptables 中内置的行为:

  • ACCEPT:允许数据包通过,继续执行后续的规则;
  • DROP:丢弃数据包;
  • RETURN:跳出当前规则链,继续执行前一个调用链(即调用当前链的链)的后续规则;
  • AUDIT:对命中规则的数据包创建审计记录,主要用在允许、丢弃和拒绝三种规则的数据包上;
  • LOG:内核对数据包进行日志记录;
  • DNAT:修改数据包的目标网络地址;
  • SNAT:修改数据包的源网络地址;
  • RATEEST:流量速率估算器;
  • REDIRECT:在本机上做端口映射,比如将 80 端口映射到 8080,访问 80 端口的数据包将会重定向到 8080 端口对应的监听服务;
  • REJECT:功能与 DROP 类似,只不过它会通过 ICMP 协议给发送端返回错误信息,比如 Destination network unreachableDestination host unreachable等,默认返回 Destination port unreachable
  • TOS:设置 IP 头部的 TOS 字段,即 Type of Service;
  • MASQUERADE:地址伪装,可以理解为动态的 SNAT。通过它可以将源地址绑定到某个网卡上,因为这个网卡的 IP 可能是动态变化的,此时用 SNAT 就不好实现;
  • ……

上面列出的这些行为就可以挂载到几个钩子下面,那 Netfilter 是这样做的吗?答案是否定的。

不同的链上能处理的事情有区别,而相同的行为放在一起也便于管理,比如包过滤的规则(ACCEPT,DROP,RETURN,REJECT 等)就可以合并到一起,这便有了规则表的概念。把规则表与链进行关联,而不是规则本身与链关联,通过一个中间层解耦了链与具体的某条规则,原先复杂的对应关系就变得简单了。

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。

Netfilter 中内置了 5 张表,与前面的 5 条链相互组合,便有了“5 表 5 链”的叫法,这 5 张表分别是:

  • raw 表:配置该表主要用于去除数据包上的连接追踪机制。默认情况下,连接会被跟踪,所以配置该表后,可以加速数据包穿越防火墙,提高性能。

    This table is used mainly for configuring exemptions from connection tracking in combination with the NOTRACK target.
    One of the important features built on top of the Netfilter framework is connection tracking.Connection tracking allows the kernel to keep track of all logical network connections or sessions, and thereby relate all of the packets which may make up that connection. NAT relies on this information to translate all related packets in the same way, and iptables can use this information to act as a stateful firewall.

  • mangle 表:修改数据包内容,常用于数据包报文头的修改,比如服务类型(Type of Service, ToS),生存周期(Time to Live, TTL),Mark 标记等。

    This table is used for specialized packet alteration.

  • nat 表:用于修改数据包的源地址或目标地址,可以将其理解为一个 IP 分享器,但要更强大。

  • filter 表:数据包过滤,控制到达某条链上的数据包是放行(ACCEPT),还是拒绝(REJECT),或是丢弃(DROP)等。前面提到 iptables 命令的使用规则:iptables [-t table] ...,如果省略 -t table,则默认操作的就是 filter 表。

  • security 表:安全增强,一般用于 SELinux 中,其他情况并不常用。

    This table is used for Mandatory Access Control (MAC) networking rules, such as those enabled by the SECMARK and CONNSEC‐MARK targets. Mandatory Access Control is implemented by Linux Security Modules such as SELinux. The security table is called after the filter table, allowing any Discretionary Access Control (DAC) rules in the filter table to take effect before MAC rules.

    安全增强型 Linux(SELinux)是一种采用安全架构的 Linux® 系统,它能够让管理员更好地管控哪些人可以访问系统。它最初是作为 Linux 内核的一系列补丁,由美国国家安全局(NSA)利用 Linux 安全模块(LSM)开发而成。SELinux 于 2000 年发布到开源社区,并于 2003 年集成到上游 Linux 内核中。

一个链上可以关联的表可以有多个,所以这 5 张表在一个链上执行的时候得有个顺序:raw --> mangle --> nat --> filter --> security,即先去连接追踪,再改数据包,然后做源或目标地址转换,最后是过滤和安全。表与链的对应关系如下表所示:

ruleset\chain Prerouting Forward Input Output Postrouting
raw
mangle
nat(Source)
nat(Destination)
filter
security

从表上看,有些复杂,笔者觉得理解其逻辑就行,等需要用时,查一下该表即可。在维基百科上,有一个流程图,笔者将其引用到这里,作为参考:


5 条链源自 5 个钩子,而它们与 5 张规则表的关系是固定的。表不可新增,但可以在某张表中新增自定义链,并且自定义链只能通过默认的 5 条链 jump(比如 iptables 中的 -j, --jump target参数)过去,对应的规则才能被执行。

了解 Netfilter 框架对网络通信的干预逻辑之后,我们知道了这个框架内部的复杂程度。前面也提到,如果直接操控 Netfilter 框架的各个钩子是不太现实的,所以就有了 Xtables 系列工具。假设把 Netfilter 框架当作服务端,那么 Xtables 系列工具就是客户端,这是一个典型的 C/S 架构,如同 Linux 下各类终端命令一般。

目前我们最常用的网络工具就是 iptables,下面笔者就用两个小示例来初步体验一把。关于 iptables 更多深入的用法和内部逻辑,笔者将会在后续的文章中继续探讨。

iptables 示例

演示环境:

demon@ubuntu-2204-dev:~$ hostnamectl 
 Static hostname: ubuntu-2204-dev
       Icon name: computer-vm
         Chassis: vm
      Machine ID: 57f64bea7cc24c56810ab
         Boot ID: bbeebba4fce046cb85c3c
  Virtualization: vmware
Operating System: Ubuntu 22.04.2 LTS              
          Kernel: Linux 5.19.0-38-generic
    Architecture: arm64
 Hardware Vendor: VMware, Inc.
  Hardware Model: VMware20,1
demon@ubuntu-2204-dev:~$

示例1: docker 服务新增的自定义链

笔者用 docker-compose 命令启动两个服务,对应网桥的 IP 为 172.18.0.1,而默认的 docker0 网桥其 IP 为 172.17.0.1,使用 iptables -t filter -nvL命令可以查看 filter 表上的规则:

demon@ubuntu-2204-dev:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:cd:0e:21 brd ff:ff:ff:ff:ff:ff
    altname enp2s0
    inet 192.168.10.125/24 brd 192.168.10.255 scope global noprefixroute ens160
       valid_lft forever preferred_lft forever
    inet6 fe80::a431:912b:d93d:d239/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:85:4b:16:b0 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
4: br-e9e25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:d3:3c:04:12 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-e9e25
       valid_lft forever preferred_lft forever
    inet6 fe80::42:d3ff:fe3c:412/64 scope link 
       valid_lft forever preferred_lft forever
6: veth4df6314@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-e9e25 state UP group default 
    link/ether 2a:9b:ea:45:0c:f5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::289b:eaff:fe45:cf5/64 scope link 
       valid_lft forever preferred_lft forever
8: vethd96f10c@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-e9e25 state UP group default 
    link/ether 32:17:df:d9:ff:94 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::3017:dfff:fed9:ff94/64 scope link 
       valid_lft forever preferred_lft forever
demon@ubuntu-2204-dev:~$
demon@ubuntu-2204-dev:~$ docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED       STATUS          PORTS                                       NAMES
85bbcf64e747   nginx:1.22.1-alpine   "/docker-entrypoint.…"   4 weeks ago   Up 50 minutes   0.0.0.0:8068->80/tcp, :::8068->80/tcp       nginx-nfdev
2e7326dead05   php:7.4-fpm           "docker-php-entrypoi…"   4 weeks ago   Up 50 minutes   0.0.0.0:9000->9000/tcp, :::9000->9000/tcp   php-nfdev
demon@ubuntu-2204-dev:~$ 
demon@ubuntu-2204-dev:~$ docker network ls
NETWORK ID     NAME            DRIVER    SCOPE
b17ca90e30da   bridge          bridge    local
059bd54e08cd   host            host      local
e9e25          nfdev_default   bridge    local
2e370db1409c   none            null      local
demon@ubuntu-2204-dev:~$
demon@ubuntu-2204-dev:~$ sudo iptables -t filter -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  *      br-e9e25  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      br-e9e25  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  br-e9e25 !br-e9e25  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  br-e9e25 br-e9e25  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  !br-e9e25 br-e9e25  0.0.0.0/0            172.18.0.2           tcp dpt:80
    0     0 ACCEPT     tcp  --  !br-e9e25 br-e9e25  0.0.0.0/0            172.18.0.3           tcp dpt:9000

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 DOCKER-ISOLATION-STAGE-2  all  --  br-e9e25 !br-e9e25  0.0.0.0/0            0.0.0.0/0           
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain DOCKER-ISOLATION-STAGE-2 (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 DROP       all  --  *      br-e9e25  0.0.0.0/0            0.0.0.0/0           
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           
demon@ubuntu-2204-dev:~$

从输出结果可以看到,在 filter 表中除了常规的 3 条链(INPUT,FORWARD,OUTPUT)外,还有 4 条自定义链:DOCKER,DOCKER-ISOLATION-STAGE-1,DOCKER-ISOLATION-STAGE-2 和 DOCKER-USER。其中的 DOCKER,DOCKER-USER 和 DOCKER-ISOLATION-STAGE-1 从 FORWARD 链(INPUT 和 OUTPUT 两条链上没有规则)跳转过去,而 DOCKER-ISOLATION-STAGE-2 又是从 DOCKER-ISOLATION-STAGE-1 链跳过去。

示例2: 新增过滤规则,对 9700 端口的数据包进行 DROP 和 REJECT

笔者在机器上启动了一个 WEB 服务,暴露端口为 9700,在未配置过滤规则的情况下,其运行情况如下:

demon@ubuntu-2204-dev:~$ curl -i http://127.0.0.1:9700/getStudentDetails/jack
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 26 Mar 2023 07:00:10 GMT

{"name":"jack","address":"Shanghai","cls":"MSA201"}
demon@ubuntu-2204-dev:~$

现在,我们在 filter 表的 INPUT 链上新增一条 DROP 规则,然后观察调用结果:

demon@ubuntu-2204-dev:~$ sudo iptables -t filter -A INPUT -p tcp --dport 9700 -j DROP
demon@ubuntu-2204-dev:~$ 
demon@ubuntu-2204-dev:~$ sudo iptables -t filter -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9700

Chain FORWARD (policy DROP 0 packets, 0 bytes)
...
...
demon@ubuntu-2204-dev:~$
demon@ubuntu-2204-dev:~$ curl -i http://127.0.0.1:9700/getStudentDetails/jack
curl: (28) Failed to connect to 127.0.0.1 port 9700 after 130609 ms: Connection timed out
demon@ubuntu-2204-dev:~$

可以看到,调用该接口,等待 130 秒后以超时返回,说明我们的 DROP 规则生效了。如果不想调用该接口,用 telnet 命令测试也是可行的:

demon@ubuntu-2204-dev:~$ telnet 127.0.0.1 9700
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection timed out
demon@ubuntu-2204-dev:~$ 

这里简单说明一下几个命令参数:
1)-A INPUT:表示在 INPUT 链上追加一条规则;
2)-p tcp --dport 9700:表示应用的协议是 tcp,端口为 9700;
3)-j DROP:表示执行 DROP 规则。

接着,我们将前面的规则删除,再按照相同的条件配置一条 REJECT 规则:

demon@ubuntu-2204-dev:~$ sudo iptables -t filter -D INPUT -p tcp --dport 9700 -j DROP
demon@ubuntu-2204-dev:~$ 
demon@ubuntu-2204-dev:~$ sudo iptables -t filter -A INPUT -p tcp --dport 9700 -j REJECT
demon@ubuntu-2204-dev:~$ 
demon@ubuntu-2204-dev:~$ sudo iptables -t filter -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 REJECT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9700 reject-with icmp-port-unreachable

Chain FORWARD (policy DROP 0 packets, 0 bytes)
...
...
demon@ubuntu-2204-dev:~$ 
demon@ubuntu-2204-dev:~$ curl -i http://127.0.0.1:9700/getStudentDetails/jack
curl: (7) Failed to connect to 127.0.0.1 port 9700 after 0 ms: Connection refused
demon@ubuntu-2204-dev:~$ 
demon@ubuntu-2204-dev:~$ telnet 127.0.0.1 9700
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused
demon@ubuntu-2204-dev:~$

可以看到,这一次调用接口没有超时,而是立刻返回:Connection refused。

小结

从防火墙出发,笔者简单梳理了防火墙的概念,分类等,并借此引入 Linux 下自带的防火墙 Netfilter 框架。Netfilter 框架通过事件回调机制,来调用注册在预留钩子上的函数,从而干预网络通信。一个钩子上可以挂载多个函数,于是便有了链的概念,而那些操控流量的规则又按照类别抽象成了规则表,把链与规则表关联,便有了“5 表 5 链”。最后,笔者通过 iptables 工具演示了两个例子,让偏理论的内容在实践下有一个较直观的理解。

接下来,笔者将结合 iptables 对 Netfilter 中的规则进行更深入的探讨,并借此梳理总结 ipvs 等内容,我们下次见。


延伸阅读: 网络协议之基本通信模型