Nftables - 数据包流和 Netfilter 钩子

Nftables - 数据包流和 Netfilter 钩子

来源 https://thermalcircle.de/doku.php?id=blog:linux:nftables_packet_flow_netfilter_hooks_detail

 

如果您使用的是 Iptables 或更新的 Nftables,并且您只是在做一些简单的 使用 IPv4 进行数据包过滤,那么您可能会从 官方文档和快速浏览网站 提供示例配置。 但是,如果您正在处理更复杂的事情,例如在使用 IPsec 的同时同时处理 IPv4 和 IPv6 的 Nftables 规则 以及做 NAT 或其他“更有趣”的事情......然后事情趋向于 变得更棘手一点。 如果你想确保知道你在做什么,并正确地创建和放置你的表格规则,让它们做正确的事情...... 那么了解网络数据包的流向和 Nftables 和底层 Netfilter 框架的内部工作原理 更详细一点。

理由

就我自己而言,我总是想知道事情是如何运作的,并深入挖掘 只是获得解决手头问题所需的最低限度的知识。 有关此主题的可用文档还不错,但与大多数其他文档一样 它往往会在你的脑海中留下一些空白和问题没有得到解答,还有很多 可用的文档已过时。许多更有趣的细节 通常只在关注 Nftables 前身 Iptables 的旧文章中介绍。 在挖掘了很多网站之后,一些内核源码,做了一些实用的 涉及 Nftables 的跟踪日志功能的实验, 我喜欢分享我学到的一些东西。 在本文中,我将尝试解释 Nftables 的概念,如基础链优先级地址系列,并将它们联系起来 到通过 Netfilter 挂钩的实际网络数据包流。

胜过千言万语

多年来,已经创建了几张图像,旨在将 网络数据包流通过 Linux 内核中的 Netfilter 钩子, 因此,数据包流经 Iptables 或 Nftables 的规则。可能是最著名、最详细和最好的 维护的图像如图 1 所示。

图 1:Netfilter 数据包流图像,发布在维基百科上,CC BY-SA 3.01)

但是,此图显示的是通过 Netfilter 钩子的数据包流,因此数据包流经,就像它们在旧的 Iptables 中一样。然而,在Nftables中,你可以根据自己的喜好自由创建和命名,所以事情看起来可能会有所不同。该图像仍然非常有用,特别是因为它包含许多进一步的细节,例如桥接入口钩子和 IPsec/Xfrm2),但是在解释它时,您需要“在字里行间阅读一点点”。

网过滤器

Linux 内核中的 Netfilter 框架是构建数据包选择系统(如 Iptables 或更新的 Nftables)的基本构建块。 它在 Linux 内核内部提供了一堆钩子,当这些钩子流经内核时,网络数据包会遍历这些钩子。其他内核组件可以向这些钩子注册回调函数,这使它们能够检查数据包并决定是丢弃(=删除)还是接受数据包(=继续通过内核)。图 2 是 Netfilter 数据包流图像的简化版本,其中显示了这些钩子。

图 2:Netfilter 钩子 - 简单框图

在网络设备上接收的网络数据包首先遍历预路由挂钩。然后进行路由决策,从而确定该数据包是否发往本地进程(例如,监听系统的服务器的套接字)或是否应转发数据包(在这种情况下,系统充当路由器)。在第一种情况下,数据包会遍历 Input 钩子,然后被提供给本地进程。在第二种情况下,数据包在网络设备上发送之前,先遍历 Forward 挂钩,最后遍历 Postrouting 挂钩。由本地进程(例如,喜欢在网络上发送某些内容的客户端或服务器软件)生成的数据包首先遍历 Output 钩子,然后遍历 Postrouting 钩子,然后再在网络设备上发送出去。

这五个钩子在 Linux 内核中已经存在了很长时间。例如,您可以在 2002 年的 Linux netfilter Hacking HOWTO 中找到图 2 的等效项。好消息是,至少从鸟瞰的角度来看,这一切在今天仍然是准确的。当然,如果你仔细研究一下,现在事情就更复杂了。我尝试在图 3 中展示这一点。图像中的 courier 字体表示 Linux 内核源代码中事物的命名方式。

图 3:Netfilter 钩子的更多细节:IPv4、IPv6、ARP、桥接、网络命名空间和入口
(单击以放大)

如您所见,IPv4 和 IPv6 协议(即 IPv4 和 IPv6 数据包各自遍历自己的钩子)的这五个钩子是独立存在的。存在进一步的钩子,可以由 ARP 数据包遍历,或者在进行桥接时进行遍历(我在这里不详细介绍这些钩子)。存在一个额外的入口挂钩,它为每个网络设备独立存在。这样的例子不胜枚举......不保证完整性3)。Nftables 用它所谓的地址族(、、、)来抽象这些东西,但稍后会详细介绍。ipip6inetarpbridgenetdev

网络命名空间

如果您不使用或关心网络命名空间,或者您不知道是什么 他们是,那么你可以忽略这部分。请注意:即使您不这样做 显式使用网络命名空间(例如,通过创建其他命名空间), 仍然有一个实例,即默认网络命名空间“init_net”,始终存在 然后所有网络都发生在这个命名空间内。

所有提到的钩子都独立存在(=正在重新创建)在每个钩子中 network namespace4),如图 3 所示。这意味着 Linux 内核中保存回调列表的数据结构 使用钩子注册的函数将重新创建(最初为空) 对于每个新的网络命名空间。因此,谁注册了这些钩子 每个网络命名空间都不同且各不相同。 当然,网络命名空间的实际概念及其影响是 远不止于此,但这不是本文的主题。

寄存器钩子函数

如前所述,钩子的想法是让其他内核组件有机会使用 Netfilter 钩子注册回调函数,然后为遍历该钩子的每个网络数据包调用这些函数。Netfilter 提供了一个 API 来做到这一点,Iptables 和 Nftables 以及连接跟踪等其他系统都使用它。此 API 提供函数和 5) 使用特定钩子注册/注销回调函数。图 4 对此进行了可视化。nf_register_net_hook()nf_unregister_net_hook()

图 4:使用 hook 注册/注销回调(“钩子函数”)的 Netfilter API

可以使用同一个钩子注册多个回调函数。Netfilter 将这些函数的函数指针(以及一些元数据)保存在一个数组中,每次当某些组件注册/注销函数时,该数组都会动态增长或收缩。每个 Netfilter 钩子都有自己的数组,在内核中作为 的实例实现。 在 Internet 上的大多数其他文档以及 Netfilter 开发人员社区的讨论中,这些注册的回调函数通常被称为“钩子函数”6)。因此,从现在开始,我也将它们称为“钩子函数”。struct nf_hook_entries

优先权

此数组中钩子函数的顺序很重要,因为遍历钩子的网络数据包将按照数组中存在的顺序遍历钩子函数。注册钩子函数时,调用方需要指定一个优先级值(如图 4 中以红色显示),然后 Netfilter 使用该值来确定将新的钩子函数插入数组的 WHERE。优先级是有符号整数值 (),可以使用该数据类型的整个值范围。如图 4 所示,Netfilter 按优先级值从低到高的升序对钩子函数进行排序。因此,具有较低值的 hook 函数 like 位于具有较高值的 hook 函数之前,例如 。然而,在实践中,似乎并没有使用优先级整数的全部值范围。内核包含多个枚举,这些枚举定义了一些常见的离散优先级值。这里的事情似乎有点混乱,因为每个协议的这些枚举(有点)不同(= 对于每个地址系列Nftables 将如何称呼它)。图 5 显示了 IPv4 协议的枚举示例。int-200100

enum nf_ip_hook_priorities {
	NF_IP_PRI_FIRST = INT_MIN,
	NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
	NF_IP_PRI_CONNTRACK_DEFRAG = -400,
	NF_IP_PRI_RAW = -300,
	NF_IP_PRI_SELINUX_FIRST = -225,
	NF_IP_PRI_CONNTRACK = -200,
	NF_IP_PRI_MANGLE = -150,
	NF_IP_PRI_NAT_DST = -100,
	NF_IP_PRI_FILTER = 0,
	NF_IP_PRI_SECURITY = 50,
	NF_IP_PRI_NAT_SRC = 100,
	NF_IP_PRI_SELINUX_LAST = 225,
	NF_IP_PRI_CONNTRACK_HELPER = 300,
	NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
	NF_IP_PRI_LAST = INT_MAX,
};

 

图 5:IPv4 钩子优先级枚举
源自 include/uapi/linux/netfilter_ipv4.h 的源代码提取

 

我在这里详细介绍了这些值,因为这个枚举显示了内核组件(如连接跟踪)在向 Netfilter 钩子注册自己的钩子函数时使用的离散优先级值。这与 Iptables 和 Nftabless 相关,您将在下面看到。

硬编码与灵活性

Netfilter 钩子本身被硬编码到 Linux 内核网络堆栈中。如果搜索名为 7) 的函数调用,则可以在源代码中找到它们。如果您想知道,为什么需要其他内核组件在运行时向这些 Netfilter 钩子注册钩子函数,以及为什么这些钩子函数不是硬编码的......好吧,我没有写这段代码,所以你的猜测和我的一样好。有许多潜在的原因可能导致这些设计决策,但常识(以及某些网站上的评论)至少使以下两个原因对我来说是显而易见的:NF_HOOK()

  1. 这一次,运行时的这种灵活性是内核中的基本要求,其中许多组件(还有 NftablesIptables 和连接跟踪)可以在运行时作为内核模块加载或卸载,并且采用强大的进一步抽象概念,如网络命名空间
  2. 性能是一个关键问题。每个网络数据包都需要遍历使用 Netfilter 钩子注册的所有钩子函数。因此,应以经济的方式注册这些钩子函数。这可能是与 Iptables 的预定义链相比,Nftables 中的基础链需要由用户显式创建的原因之一(更多细节见下文)。

钩子遍历和判定

现在,让我们更详细地了解网络数据包如何遍历使用同一 Netfilter 钩子注册的钩子函数。 对于遍历此挂钩的每个网络数据包,将逐个调用挂钩函数 在它们出现在钩子数组中的序列/顺序中(由 优先级值)。

图 6:通过 Netfilter 钩子注册的钩子函数的数据包流(单击以放大)

网络数据包在 Linux 内核中表示为 的实例(通常称为“套接字缓冲区”,缩写为“skb”)。指向此类 skb 实例的指针作为所有这些钩子函数的函数参数,因此每个函数都可以检查数据包。每个钩子函数都需要将“判定”作为返回值返回给 Netfilter。“判决”有几种可能的值,但要理解这些概念,只有这两个是相关的:和 . 告诉 Netfilter,hook 函数“接受”网络数据包。这意味着数据包现在遍历了使用此钩子注册的下一个钩子函数(如果存在)。如果此 hook 的所有 hook 函数都返回 ,则数据包最终会继续遍历内核网络堆栈。但是,如果钩子函数返回 ,则数据包将被“丢弃”(=删除),并且不会遍历其他钩子函数或网络堆栈的一部分。struct sk_buffNF_ACCEPTNF_DROPNF_ACCEPTNF_ACCEPTNF_DROP

Iptables的

为了说明具体情况,让我们简要了解一下 Iptables 作为 Nftables 的前身。iptables 将其规则组织成表和链,而在大多数情况下只是将组合在一起的一种手段(容器),它们有一些共同点。例如,用于 nat 的属于 8) 。实际规则位于内部。如上所述,iptables 通过注册自己的钩子函数,向 Netfilter 钩子注册其。这意味着当网络数据包遍历钩子(例如预路由)时,该数据包将遍历向该钩子注册的,从而遍历其规则nat

如果是 Iptables,所有这些都是预定义的。存在一组固定表,每个包含一组固定的链9)。这些链的命名方式类似于注册它们的 Netfilter 钩子。

桌子包含链命令来显示
filter INPUT, ,FORWARDOUTPUT iptables [-t filter] -L
nat PREROUTING, ()10), ,INPUTOUTPUTPOSTROUTING iptables -t nat -L
mangle PREROUTING, , , ,INPUTFORWARDOUTPUTPOSTROUTING iptables -t mangle -L
raw PREROUTING,OUTPUT iptables -t raw -L

当数据包遍历钩子时,被遍历的顺序(它们的优先级)也已经固定。Netfilter 数据包流图像(图 1)详细显示了此序列。在图中,使用钩子注册的每个链都由一个块表示,如图 7 所示,其中包含的名称及其所属的表

图 7:了解如何在 Netfilter 数据包流图像中可视化表/链 (1)。

我还在这里显示了优先级(红色),因为我想进一步详细说明它。但是,优先级值不会显示在原始 Netfilter 数据包流映像中。 cmdline 工具本身仅负责配置用于处理 IPv4 数据包的表规则。因此,其对应的内核组件仅向 IPv4 协议的 5 个 Netfilter 钩子注册其。为了涵盖所有协议系列,完整的 Iptables 套件被拆分为几个不同的 cmdline 工具和相应的内核组件:iptables

  • iptables对于 IPv4 /NFPROTO_IPV4
  • ip6tables对于 IPv6 /NFPROTO_IPV6
  • arptables用于 ARP /NFPROTO_ARP
  • ebtables桥接 /NFPROTO_BRIDGE

让我们来看看 IPv4。由于 Iptables 是以它们注册的钩子命名的,因此解释 Netfilter 数据包流映像非常简单,如图 8 所示。iptables

图 8:使用 IPv4 Netfilter 钩子注册的 iptables 链 (+conntrack) (点击放大) (与 1 相比)

连接跟踪

如图 8 所示,连接跟踪系统也向 Netfilter 钩子注册自身,并且根据优先级值 (),您可以清楚地看到哪个 Iptables 链被称为 BEFORE,哪个 Iptables 被称为 AFTER,连接跟踪钩子函数被称为 AFTER。关于连接跟踪,还有很多要讲的内容。如果你进一步研究细节,那么你会发现连接跟踪系统实际上甚至向 Netfilter 钩子注册了比这里显示的更多的钩子函数。但是,所示的两个钩子函数代表了一个足够的模型,用于理解创建 Iptables 或 Nftables 规则时连接跟踪的行为。我在单独的一系列博客文章中详细阐述连接跟踪这一主题,从连接跟踪 - 第 1 部分:模块和钩子开始。-200

Nftables

一般来说,Nftables 以与 Iptables 相同的方式将其规则组织到中。桌子又是链的容器,承载着规则。 但是,与 Iptables 相比,不存在预定义的。所有都必须由用户显式创建。用户可以在创建表和时为它们指定任意名称。Nftables 区分了所谓的基础链和常规链基础链是向 Netfilter 挂钩注册的链(通过上述挂钩函数),您必须在创建时指定该挂钩。 常规链未注册到任何钩子(本文未介绍常规链11)。 因此,用户不会被迫命名基础链,就像它们将注册到的 Netfilter 钩子一样。这显然提供了更多的自由度和灵活性,但因此也更有可能造成混乱。

地址家族

与 Iptables 相比,Nftables 没有拆分为几个用户空间工具和相应的内核组件来处理 Netfilter 提供的不同钩子组。它通过引入所谓的地址族的概念来解决这个问题。创建时,需要指定它属于哪个地址系列。 存在以下地址系列,并映射到以下 Netfilter 挂钩组:

  • ip:映射到 IPv4 协议钩子 /(默认)NFPROTO_IPV4
  • ip6:映射到 IPv6 协议钩子 /NFPROTO_IPV6
  • inet:映射到 IPv4 和 IPv6 协议挂钩
  • arp:映射到 ARP 协议钩子 /NFPROTO_ARP
  • bridge:映射到桥接钩子 /NFPROTO_BRIDGE
  • netdev:映射到 Ingress Hook /NFPROTO_NETDEV

因此,您在表中创建的所有基链都将注册到您为该表选择的地址系列的指定 Netfilter 挂钩。地址系列 (IPv4) 是默认的地址系列。因此,如果在创建表时未指定任何地址系列,则此将属于 。ipip

以下示例创建一个名为 的新表,属于 address family,然后创建一个名为 table 的新基础链,将其注册到地址系列(=IPv4 协议)的 Netfilter 钩子并指定优先级(我在这里明确指定 Address Family 只是为了强调正在发生的事情;可以省略它。fooipbarfooinputip0ip

nft create table ip foo
nft create chain ip foo bar {type filter hook input priority 0\;}

地址家族很特别。当您创建属于该系列的表,然后在该中创建基本链时,此基本链将注册到两个 Netfilter 挂钩:IPv4 和 IPv6 的等效子。这意味着 IPv4 和 IPv6 数据包都将遍历此的规则。 以下示例在 address family 中创建一个表和一个基链。基础链将注册到 IPv4 的 Netfilter 钩子和 IPv6 的 Netfilter 钩子。inetfoobarinetbarinputinput

nft create table inet foo
nft create chain inet foo bar {type filter hook input priority 0\;}

优先权

在上面的示例中,您已经看到 Nftables 要求您在创建基础链时指定优先级值。这与我已经描述的优先级完全相同 在上面介绍Netfilter时会详细介绍。您可以指定整数值,但较新的 Nftables 的版本还定义了几个离散优先级值的占位符名称,这些优先级值类似于 Netfilter 中提到的枚举。下表列出了这些占位符名称12)。

名字优先级值
raw -300
mangle -150
conntrack13) -200
dstnat -100
filter 0
security 50
srcnat 100

例如,在创建基础链时,您可以指定哪个转换为 .以下示例创建一个以地址系列 (IPv4) 命名的。然后,它创建两个名为 和 的基本链,将它们注册到 Netfilter IPv4 挂钩输入,但每个链具有不同的优先级。图 9 显示了结果。遍历 Netfilter 挂钩输入的 IPv4 网络数据包将首先遍历,然后遍历priority filterpriority 0myfilteripfoobarfoobar

nft create table ip myfilter
nft create chain ip myfilter foo {type filter hook input priority 0\;}
nft create chain ip myfilter bar {type filter hook input priority 50\;}
 
# alternatively you could create the same chains using named priority values:
nft create chain ip myfilter foo {type filter hook input priority filter\;}
nft create chain ip myfilter bar {type filter hook input priority security\;}

图 9:使用 Netfilter Ipv4 输入钩子注册的基础链 foo 和 bar

Nftables 目前有一个限制(参见 bug ticket),这使得在命令行上为优先级输入负整数值变得困难(或至少不舒服)。使用占位符名称可能是最舒适的解决方法。在另一种方法之后添加:nft--nft

nft -- add chain foo bar {type nat hook input priority -100\;}

但是,当您使用具有相同优先级的相同钩子注册两个基础链时,实际上会发生什么?Netfilter 的源代码回答了这个问题。它实际上允许使用具有相同优先级值的相同钩子注册钩子函数。在以下示例中,首先为 chain1 调用函数,然后为 chain2 调用函数。nf_register_net_hook()

nft create chain ip table1 chain1 {type filter hook input priority 0\;}
nft create chain ip table1 chain2 {type filter hook input priority 0\;}

我检查了内核源代码14),并能够使用 Nftables 功能确认行为: 内核代码将 chain2 放在 BEFORE 之前 (在前面)chain1 在此钩子的钩子函数数组中。因此, 然后,网络数据包在 chain1 之前遍历 chain2。这意味着这里 您发出命令以注册两条链的顺序/顺序变得相关! 但是,我想最好的做法是考虑两个 在同一钩子上具有相同优先级的链被遍历为 “未定义”,因此要么避免这种情况,要么设计添加的规则 以一种不依赖于链遍历顺序的方式添加到这些中。毕竟,我在这里描述的行为是内部的 未记录的内核行为,实现可能会因任何 较新的内核版本。因此,你不应该依赖它!nftrace

最佳做法

在大多数用户中,似乎在Nftables中对使用相同的命名概念已成为一种普遍做法,就像在Iptables中使用的那样(主要是像它们注册的钩子一样命名基础链)。此外,最好只创建用例所需的。这不仅使您的规则集更小,可能更易于阅读和维护,而且与性能有关。请注意,您创建并使用其中一个 Netfilter 钩子注册的每个链(= 每个“基本链”)实际上都会被网络数据包遍历,从而造成性能损失。

示例:NAT 边缘路由器

图 10 中的示例演示了一个边缘路由器,它执行一些简单的 IPv4 数据包过滤和 SNAT(伪装)。我在这里只是举了一个极简主义的例子。甚至可以再次删除输出,因为我没有向它添加任何规则。实际上,您肯定会添加一组更复杂的规则。

nft create table ip nat
nft create chain ip nat postrouting {type nat hook postrouting priority srcnat\;}
nft add rule ip nat postrouting oif eth1 masquerade
 
nft create table ip filter
nft create chain ip filter input {type filter hook input priority filter\;}
nft create chain ip filter forward {type filter hook forward priority filter\;}
nft create chain ip filter output {type filter hook output priority filter\;}
nft add rule ip filter forward iif eth1 oif eth0 ct state new,invalid drop
nft add rule ip filter input iif eth1 ip protocol != icmp ct state new,invalid drop

图 10:边缘路由器执行 SNAT 的简约 Nftables 规则集示例,以及由此产生的 Netfilter IPv4 挂钩注册的基础链 (+conntrack)(单击以放大)。

 

列表钩子函数(即将推出)

Nftables 开发人员在 2021 年 7 月宣布了一项新功能,该功能将 可能会包含在即将发布的下一个版本的 Nftables 中; 请参阅最近的 Git 提交。此功能允许 Nftables 列出当前所有钩子函数 使用指定的 Netfilter 挂钩及其分配的 Netfilter 挂钩注册 优先 级。例如,如果您想列出当前在 Netfilter 中注册的所有钩子函数 IPv4 预路由钩子,执行此操作的语法可能类似于 .nft list hook ip prerouting

上下文

所描述的行为和实现已在 Debian 10 (buster) 系统,在 amd64 架构上使用 Debian 向后移植

  • 内核:5.4.19-1~bpo10+1
  • nftables:0.9.3-2~bpo10+1
  • libnftnl:1.1.5-1~bpo10+1

反馈

非常欢迎对本文的反馈

发布时间 2020-05-17, 最后修改时间 2022-08-07

1)作者:Jan Engelhardt
这允许我在兼容的许可下发布内容时使用此图像。谢谢。请参阅页面底部的许可声明。
2) 查看我的另一篇文章 Nftables - Netfilter 和 VPN/IPsec 数据包流,我在其中介绍了该主题。
3)我已经故意遗漏了一些未提及的东西,例如我认为具有历史意义的DECNET
4) 这里唯一的例外是 ingress hook,它绑定到 单个网络设备,从而(至少不是直接)连接到网络命名空间
5) 以及这些功能的进一步变化
6)有时它们被简单地称为“钩子”,这会产生一些歧义。当您在互联网上某处读到有关“钩子”的内容时要小心......其含义可能是“Netfilter 钩子”,但它也可能是向其中一个 Netfilter 钩子注册的“回调函数”。
7) 或类似...存在一些变体
8) 好吧,nat 已经是一个特例,它背后还有更多的魔力。 例如,只有每个连接的第一个数据包会遍历 nat 表的,但该主题超出了本文的范围。
9) 好的,作为用户,如果您愿意,您还可以创建其他链,但这些没有注册到 Netfilter 钩子,无论如何这是一个不同的主题。
10) 现在看来 nat 表也包含一个 INPUT 链。命令 iptables -t nat -L 显示了它。但是,在撰写本文时,它尚未显示在最新版本的 Netfilter 数据包流映像中。似乎它已经在内核 2.6.35 左右的某个时候通过此提交添加了。
11) 常规链代表与我已经提到的 Iptables 相同的功能。用户可以创建任意数量的链,这些未注册到任何钩子上,并使用它们,就像在编程语言中使用函数一样。但这是一个完全不同的话题。
12) 不保证完整性。在撰写本文时,这似乎仍在大量开发中。有关详细信息,请参见手册页 man 8 nft
13) 正如你所猜到的,这不是您可以使用的占位符名称之一。我在这里添加它是为了提醒为连接跟踪钩子功能保留哪个优先级值。
14) 参见内核 v5.4.0 中 net/netfilter/core.c 中的函数 nf_hook_entries_grow()
 

 

================ End

 

 
posted @ 2023-12-28 13:43  lsgxeva  阅读(245)  评论(0编辑  收藏  举报