Netfilter子系统是由Linux内核提供的一种通用网络处理框架。内核在整个报文处理流程中预留了5个挂载点,并允许用户在这些挂载点注册回调函数,使得用户有机会参与内核的报文处理流程。用户可以在这些挂载点上实现对数据报文的操作,如过滤报文、网络地址转换、转发、丢弃等。
Netfilter整体架构参考图1-12。
图1-12 Netfilter整体架构
可以看到,整体上Netfilter的功能分为内核态和用户态两部分。下面针对该架构,从下到上依次进行说明。
➢ Netfilter HOOK Entry: 内核代码中提供了5个挂载点(Hook Point),并允许用户在每个挂载点上挂载自己的处理函数,以达到用户干预内核处理报文流程的目的,实现用户对报文的定制化处理需求。
➢ Netfilter内核模块: 内核模块提供Netfilter功能,这些模块提供一系列的规则表,用户通过向表中插入自定义规则实现报文的定制化处理。
➢ Netlink套接字: Netlink提供了一种灵活的用户空间与内核空间通信的方法,用于替代ioctl系统调用接口,用户态应用直接通过Netlink套接字与内核通信。
➢ 用户空间命令iptables: 用户调用iptables命令向内核模块写入报文处理规则,此命令相当于Netfilter功能的外部呈现。
用户使用iptables命令执行规则管理,在进入命令之前先了解iptables的三个概念:表(Table)、链(Chain)和规则(Rule)。这三者的关系如图1-13所示。
图1-13 iptables基本概念:表、链和规则
iptables提供了4张表,每张表中包含 预定义链 以及0个或者多个 自定义链 ,每个链中又可以包含0个或者多个 规则 。
➢ 预定义链: 预定义链是表的 入口链 ,此类型的链与Netfilter挂载点一一对应,每个表固定包含数个预定义链。用户可以在预定义链中增加规则以跳转到自定义链执行操作,这也是执行自定义链的唯一方法。预定义链设置了默认处理策略,如果该链上的所有规则均未匹配成功,则按照预定义链的默认策略来执行。
➢ 自定义链: 当规则比较简单时,用户直接在预定义链中增加规则即可,如果规则较为复杂或者存在嵌套调用的情况,则建议放到自定义链中实现。
如果用程序语言来描述这三个概念,则可以将链理解为“函数”,将规则理解为函数中的“语句”,如果某个规则的作用是跳转到其他自定义链执行,则可理解为“函数调用”。规则匹配过程就像函数调用过程,预定义链作为入口链,相当于main()函数,内核按照“函数调用”的顺序执行每一条链/规则,直到匹配成功。如果该预定义链上所有的规则均匹配失败,则按照预定义链的默认策略来执行。
以Kubernetes使用的filter表规则为例说明调用过程,执行iptables命令,输出如下:
内核接收报文后进入INPUT链进行匹配。首先匹配第一条规则,该规则要求与本机建立TCP连接的首条报文(ctstate NEW)进入KUBE-PROXY-FIREWALL链执行规则匹配。假设本次收到的正是TCP SYN报文,此时跳转到KUBE-PROXY-FIREWALL链继续处理,本次跳转相当于“函数调用”。继续看KUBE-PROXY-FIREWALL链,此链上没有挂载任何规则,“函数调用”返回。回到INPUT链后,由于没有匹配到任何结果,所以内核继续匹配下一条规则,转到KUBE-NODEPORTS链继续处理,以此类推。
Netfilter提供的5个挂载点在内核处理流程中的位置参考图1-14。
图1-14 Netfilter提供的5个挂载点及报文处理路径
内核在转发过程中是如何知道该调用哪个iptables链的呢?参考图1-14,Netfilter的每个挂载点分别对应了iptables的一个预定义链。
➢NF_INET_PRE_ROUTING:内核收到报文后、路由查找之前的挂载点,对应PREROUTING链。
➢NF_INET_LOCAL_IN:路由查找完成,内核将报文送到用户应用程序之前的挂载点上,对应INPUT链。
➢NF_INET_FORWARD:内核根据用户报文内容执行路由查找,当发现收包设备不是当前设备时执行转发操作,转发时内核提供了挂载点供用户使用,此挂载点对应FORWARD链。
➢NF_INET_LOCAL_OUT:本机的用户态应用程序外发报文的挂载点,对应OUTPUT链。
➢NF_INET_POST_ROUTING:从本机发出报文前的挂载点,对应POSTROUTING链,与OUTPUT链的区别在于POSTROUTING链可以同时处理本机转发的报文。
图1-14同时展示了报文处理过程,分为接收、转发、外发三种场景。
1)路径①: 主机应用接收报文 。当数据报文进入内核时,首先执行PREROUTING链上的规则。
➢这里是接收报文的第一个处理点,如果希望内核的conntrack组件不跟踪此报文,则可以在这里设置禁止跟踪报文(raw表)。
➢此时尚未执行路由查找,可以在这里执行目的地址转换(nat表)。
接下来内核根据报文头信息执行路由匹配,内核判断将此报文转发给本机应用处理,所以继续执行INPUT链上的规则。此时用户可以设置规则决定要不要接收此报文(filter表),如果规则操作结果为ACCEPT,则内核将报文送到用户的应用中继续处理。
2)路径②: 主机转发报文 。转发报文的前半程与路径①相同,路由匹配完成后发现需要将报文转发到系统外部,接下来执行FORWARD链上的规则,用户可以在此时设置规则决定要不要转发报文(filter表)。如果正常转发则继续执行POSTROUTING链上的规则,此时报文即将从本机发出,用户可以设置规则执行源地址转换(nat表),执行完成后报文从主机的物理设备发出。
3)路径③: 主机应用外发报文 。应用程序外发报文时,内核首先做路由匹配操作,然后执行OUTPUT链上的规则。
➢这里是外发报文的第一个处理点,如果希望内核的conntrack组件不跟踪此报文,则在这里设置禁止跟踪报文(raw表)。
➢用户可以在这里设置规则以决定报文是否执行外发操作(filter表)。
➢如果存在目的地址转换场景,则在这里设置目的地址转换规则(nat表)。
接下来继续执行POSTROUTING链上的规则,后续处理流程与路径②相同。
为方便管理,Netfilter提供了4张表,分别负责不同的功能,并且约定了优先级:raw>mangle>nat>filter。例如,用户同时在4张表的OUTPUT链上定义了规则,那么内核按照上述优先级顺序执行iptables规则。
1.filter表
filter表主要用于对数据报文进行过滤,内核匹配规则成功后按照该规则指定的策略执行,可选的策略有DROP、ACCEPT、REJECT等。filter表对应的内核模块为iptable_filter,该表包含三条预定义链。
➢ INPUT链: 过滤本机应用接收的报文。
➢ FORWARD链: 过滤通过本机转发的报文。
➢ OUTPUT链: 过滤本机应用外发的报文。
filter表的三条预定义链的位置如图1-15中灰色部分所示。
图1-15 iptables filter表的挂载点
filter表主要用于实现防火墙功能。接下来进行实例化验证,实验环境如图1-16所示。
图1-16 iptables filter表实验组网
exp主机和aux主机二层直连,用户在aux主机上发起ping请求,exp主机则通过增加filter规则来限制对ping请求的处理。
(1)实验1:exp主机设置拒绝ping请求
在exp主机上增加拒绝ICMP请求规则,执行完成后查看iptables规则:
在aux主机上发起对exp主机的ping请求:
结果显示,aux得到的应答错误码的意思为“目的端口不可达”,满足预期。
实验完成后执行下面的命令删除前面创建的规则:
(2)实验2:exp主机设置丢弃ping请求
在exp主机上增加丢弃ICMP请求规则:
在aux主机上发起对exp主机的ping请求:
结果显示,aux主机发起了4次ping请求,但没有收到任何应答,原因是exp主机将ping请求报文丢弃了,满足预期。
实验完成后执行下面的命令删除前面创建的规则:
2.nat表
nat表用于实现网络地址转换功能。在这里,用户可以设置规则来修改报文的IP地址、端口号等信息。nat表对应的内核模块为iptable_nat,本表包含以下4条预定义链。
➢ PREROUTING链: 此挂载点位于路由查找之前,用户可在此设置规则以修改报文的目的地址,所以目的地址转换功能在这里实现。
➢ INPUT链: 主机应用接收到报文之前的挂载点。
➢ OUTPUT链: 外发报文时的挂载点。
➢ POSTROUTING链: 报文离开本机之前的挂载点,用户可在此设置规则以执行源地址转换。
nat表的4条预定义链的位置如图1-17的灰色部分所示。
图1-17 iptables nat表的挂载点
nat表的规则支持4种处理策略。
➢ SNAT(源地址转换): 适用于POSTROUTING链,对匹配到的报文做源地址转换,使用本参数时必须指定源地址。
➢ MASQUERADE(源地址转换): 适用于POSTROUTING链,同样做源地址转换,与SNAT的差异在于SNAT必须指定源地址,而MASQUERADE不需要指定。使用MASQUERADE时,内核会自行选择一个合适的地址作为源地址。
➢ DNAT(目的地址转换): 适用于PREROUTING链和OUTPUT链,对匹配到的报文做目的地址转换。凡是修改目的地址的操作,均可在路由查找之前执行。
➢ REDIRECT(重定向): 适用于PREROUTING链和OUTPUT链,将报文重定向到指定端口。
接下来验证源地址转换功能,实验环境如图1-18所示。
图1-18 iptables nat表实验组网
aux主机相当于exp和aux2主机之间的桥梁,两个业务网卡分别连接了两个不同的网段:enp0s8连接172.16.0.0/24网段,enp0s9连接192.168.20.0/24网段。本实验希望使用iptables的地址转换功能实现从exp主机访问aux2主机。
首先在exp主机上增加访问192.168.20.0/24网段的路由,下一跳地址设置为aux的地址172.16.0.4。增加完路由后,在exp主机上发起“ping 192.168.20.3”,结果显示ping响应正常:
接下来尝试在exp上直接发起对aux2主机地址的ping请求,没有收到ping响应:
在aux2上进行抓包,结果如图1-19所示,aux2主机上收到了ping请求报文,但是由于aux2上未配置回程路由,所以aux2无法对此ping请求进行应答。
图1-19 iptables nat表实验:aux未配置iptables规则,aux2抓包
那么有没有办法在aux2主机不配置回程路由的前提下,使exp主机能够正常对aux2主机进行ping测试呢?当然是有的,只要aux主机在转发exp主机的请求报文时执行一次源地址转换,问题就解决了。
按照上述思路,在aux主机上增加如下配置:
根据iptables规则,对接收的源地址为172.16.0.3的ICMP报文进行源地址转换。接下来,再次尝试在exp主机上发起对aux2主机的ping请求,结果显示网络连通。
查看aux2主机上的抓包结果,如图1-20所示。
图1-20 iptables nat表实验:aux配置iptables规则,aux2抓包
对于aux2主机,它收到的ping请求的源地址是aux主机地址(192.168.20.3),这也就可以肯定aux主机对转发的ping请求报文做了源地址转换。
最后检查aux主机的连接跟踪信息。Linux内核中所有连接跟踪均由conntrack组件管理,使用conntrack命令可以查看系统中已经存在的网络连接信息,此命令能够显示通信双方的IP地址、协议连接状态等。
aux主机上conntrack命令的执行结果如下:
每条信息都包含两组数据,分别对应从发送端接收的报文和从响应端接收的报文。下面以第二行的ICMP流信息为例进行说明。
➢第一组数据表示本机收到的请求报文,本例中aux主机接收的请求报文的源地址为172.16.0.3、目的地址为192.168.20.1、type值为8(8表示报文为Echo Request类型)。
➢第二组数据表示本机收到的响应报文,本例中aux主机接收的应答报文的源地址为192.168.20.1、目的地址为192.168.20.3、type值为0(0表示报文为Echo Reply类型)。
conntrack组件会为NAT的每次转换都创建一条记录。内核收到报文时优先在conntrack中查询当前报文是否存在连接信息,如果存在,则直接根据此连接信息还原报文地址并转发。
3.mangle表
mangle表用于修改数据报文IP头中的参数,如TOS、TTL(Time To Live,生存周期)、数据报文的Mark标志等。Mangle表包含5条预定义链,覆盖iptables的所有挂载点,在任意挂载点上均可以设置规则对报文的字段进行修改。
以TOS字段为例,RFC 1349定义了TOS的取值范围。该字段一共包含8个比特位,每个比特位的含义如表1-5所示。
表1-5 TOS字段中比特位的含义
如果用户期望将某种类型的报文设置为最低延迟,则可以通过设置iptables规则将此报文的TOS属性修改为0x10。例如,下面的命令将HTTP(端口号80)、TELNET(端口号23)、SSH(端口号22)类型的报文设置为最低延迟:
其他字段的使用方法与之相似,不再赘述。
4.raw表
默认情况下,conntrack组件跟踪内核处理的每条报文,如果希望某些报文不受conntrack组件跟踪,那该如何处理呢?iptables提供的raw表用于处理此场景。用户在raw表中增加目标为NOTRACK的规则,一旦此规则匹配成功,那么这条报文将不受conntrack组件的跟踪。
从功能上看,raw表只能在入口处执行处理操作,所以该表仅包含两条预定义链:OUTPUT和PREROUTING。
本书不涉及mangle表和raw表的功能,如需了解更多请参考“iptables Tutorial 1.2.2”
。
限于篇幅,本节仅讲述后续用到的iptables命令及相关参数。
命令格式:
其命令参数包含如下4段。
➢ [-t table]: 选择本次操作的表。table参数的取值范围包括raw、mangle、nat、filter。如果不指定,则默认操作filter表。
➢ <command>: 指定本次操作命令,如增加规则、删除规则等。
➢ [rule]: 匹配规则,即如何筛选报文。
➢ [-j target]: 匹配成功后执行的动作。
1.操作命令(command)
操作命令用于指定本命令执行的动作,常用的操作命令如表1-6所示。
表1-6 iptables常用操作命令列表
iptables常用操作命令示例:
2.规则(rule)
iptables常用的规则如表1-7所示。
表1-7 iptables常用规则列表
iptables常用规则示例:
3.目标(target)
“-j target”表示匹配到规则后执行的动作、策略,表1-8展示了常用的动作。
表1-8 iptables常用动作列表
iptables常用动作示例:
关于MARK、DNAT、SNAT和MASQUERADE的应用请参考1.4.4节。
1.目的地址转换
DNAT操作只能用在nat表的PREROUTING和OUTPUT链上。使用DNAT操作时,iptables提供了额外的扩展选项,参考表1-9。
表1-9 DNAT扩展选项
下面的示例用于将目的地址为10.0.1.10:80、目的端口号为80的TCP报文通过DNAT操作转换到192.168.1.1:80上:
下面演示地址范围的应用。设置目的地址范围为192.168.1.1-192.168.1.10,执行目的地址转换操作时,内核随机选择范围内地址作为目的地址:
2.源地址转换
源地址转换支持两种动作——MASQUERADE和SNAT,其差异在于SNAT要求指定源地址。所以,如果待替换的源地址是静态分配的,则可以使用SNAT操作(也可以使用MASQUERADE操作)。例如,一组主机共享一个静态的外部地址访问Internet,可以使用SNAT操作指定源地址方案。在其他场景中,建议使用MASQUERADE操作,内核自动选择一个合适的地址作为源地址。MASQUERADE覆盖SNAT功能,所以建议尽量使用MASQUERADE操作。
SNAT的扩展选项与DNAT类似,参考表1-10。
表1-10 SNAT扩展选项
使用MASQUERADE时无须指定源地址,其扩展选项较于SNAT简单一些,参考表1-11。
表1-11 MASQUERADE扩展选项
下面看SNAT的例子,在参数中指定源地址和端口范围:
MASQUERADE访问外部网络时,内核自动选择源IP地址,用户指定源端口范围(也可以不指定,由内核随机选择端口):
3.设置报文标志
MARK动作用于给报文打标志,一个报文可能存在两种标志。
➢ nfmark(Netfilter mark,Netfilter标志): 用于给数据报文打标志,该标志长度为32比特,用户根据需要对报文打标志。
➢ ctmark(connection mark,连接标志): 用于给连接打标志,仅用在mangle表中。
iptables中的“--set-mark/--set-xmark value[/mask]”参数用于设置指定报文的nfmark值。对此,命令帮助信息如下:
上述帮助信息中已经给出了详细算法:首先获得mask参数中取值为1的比特位列表,将nfmark数据中对应的比特位设置为0,然后对mask和value进行“或”操作(--set-mark)、“异或”操作(--set-xmark)。
使用C语言描述上述算法:
如果执行命令时未指定mask值,则认为mask值为0xFFFFFFFF。
为了方便用户配置,iptables提供了简化的参数。
➢ “--and-mark bits” ,等价于“--set-xmark 0/invbits”。
➢ “--or-mark bits” ,等价于“--set-xmark bits/bits”。
➢ “--xor-mark bits” ,等价于“--set-xmark bits/0”。
以Kubernetes提供的iptables规则为例进行说明。下面的规则用于给报文打上“需要进行NAT转换(0x4000)”的标志:
命令参数为“--set-xmark 0x4000/0x4000”,用C语言描述如下:
执行此命令相当于将该数据报文的nfmark的第14个比特位设置为1。
4.比较报文标志
使用mark参数判断报文的nfmark值是否满足要求,参数说明如下:
此参数用于比较nfmark值是否满足指定的条件。其算法步骤为首先对nfmark与mask执行“与”操作,然后与value值进行比较。
用C语言描述上述算法:
如果未指定mask,则认为mask值为0xFFFFFFFF。
以Kubernetes提供的iptables规则为例进行说明。下面的规则用于判断数据报文是否打上了“需要进行NAT转换0x4000”的标志:
第一条规则的命令参数为“!--mark 0x4000/0x4000”,即判断nfmark&0x4000与0x4000是否相等:如果不相等,则匹配成功,直接返回;如果相等,则匹配失败,继续执行下一条规则。
第二条规则的命令参数为“--set-xmark 0x4000/0x0”,用于清除nfmark的第14个比特位的值。