深入理解netfilter和iptables

目录

Netfilter的设计与实现

内核数据包处理流

netfilter钩子

钩子触发点

NF_HOOK宏与Netfilter裁定

回调函数与优先级

iptables

内核空间模块

xt_table的初始化

ipt_do_table()

 复杂度与更新延时

用户态的表,链与规则

conntrack


Netfilter(结合iptables)使应用程序能注册处理规则来使内核协议栈处理数据包,使能高效的网络转发和过滤。很多常用的主机端防火墙比如K8S的转发服务就是用iptables实现的。

很多关于netfilter的介绍性文章只是概括了主要部分,对于基础的内核实现方面涉及的不多。这篇文章就是主要针对Linux 2.6版本的内核代码,也许会和最新的5.x版本有很大的不同,但基本的设计思想不会改变并且不会影响对原理的理解。

这篇文章假设读者对TCP/IP有基本的理解。

Netfilter的设计与实现

netfilter是实现在Linux内核里的一种网络数据包处理框架。为了彻底地理解netfilter是如何工作的,我们首先要了解Linux内核里数据包处理流程。

内核数据包处理流

在内核里,数据包的处理流程可根据TCP/IP模型大体分为多层,例如内核代码链被调用来处理网路数据包,下面以IPv4的TCP数据包举例。

1、在设备物理层上,网卡通过DMA将接收到的网络数据包写入RX Ring缓存区。在一系列的中断和调度后,操作系统内核调用__skb_dequeue将数据包添加到响应设备的处理队列里,并将数据包转换成sk_buffer类型。这样数据包就能用sk_buffer表示了,最终调用netif_receive_skb功能来根据协议类型解析数据包并跳转到对应的数据包处理函数。流程以下图展示:

 2、假设收到的数据包是网络层数据包,那对应的响应处理函数就是ip_rcv,也就是数据包处理进入网络层。ipv_rcv会检查IP报文头并丢弃错误包,或必要时组合分片的报文。随后ip_rcv_finish函数被执行来路由报文(决定该报文是本地接收还是转发给其他主机)。假设数据包的目的地址是本机,那么下步dst_input函数会调用ip_local_deliver函数。ip_local_deliver函数会根据IP头里的协议号决定数据包的协议类型,并最终调用对应类型的数据包处理函数。在这个例子中,对于TCP协议tcp_v4_rcv函数会被调用,这样数据包处理就进入了传输层。

3、tcp_v4_rcv函数会读取数据包的TCP头,并校验checksum值,然后再报文对应的TCP控制字段填充一些必要的状态值,包括TCP序列号和SACK值。下步调用__tcp_v4_lookup函数查询报文对应的套接字,如果找不到对应的套接字或者套接字的状态是TCP_TIME_WAIT,数据包就被丢弃。如果套接字可用,会调用tcp_prequeue函数让数据包进入prequeue队列,以便于用户态的用户程序能处理。传输层的处理流程超出了本书的范围,实际是很复杂的。

netfilter钩子

让我们继续,netfilter的主要部分就是netfilter钩子。

钩子触发点

针对不同的协议(IPv4,IPv6,或ARP等),Linux内核网络协议栈会沿着协议栈的处理路径上在预定义的节点触发对应的钩子函数。以IPv4协议举例,不同协议的处理数据流里的触发点位置和对应的钩子名字入下图所示:

这些所谓的钩子在代码中实际上是枚举类型的

enumnf_inet_hooks {NF_INET_PRE_ROUTING,NF_INET_LOCAL_IN,NF_INET_FORWARD,NF_INET_LOCAL_OUT,NF_INET_POST_ROUTING,NF_INET_NUMHOOKS
};

每个钩子都放在了内核协议栈的特定触发点上,以下图IPv4举例。

 上图中钩子的详细描述如下:

NF_INET_PRE_ROUTING:这个钩子在IPv4栈的ip_rcv函数里执行或在IPv6栈的ipv6_rcv函数里执行。对于所有进来的数据包来说,是第一个触发的钩子节点(事实上,新版本的Linux添加了INGRSS作为最早的钩子触发节点),在路由判断前执行。

NF_INET_LOCAL_IN: 这个钩子在IPv4栈的ip_local_deliver函数里执行或在IPv6栈的ip6_input函数里执行。在路由判断后,所有目的地址是本机的进来的数据包都会触发该钩子函数。

NF_INET_FORWARD:这个钩子是在IPv4栈的ip_forward函数内执行或在IPv6栈的ip6_forward函数内执行。在路由判断后,所有非本机接收的进来数据包都会触发该钩子函数。

NF_INET_LOCAL_OUT:这个钩子在IPv4栈的__ip_local_out函数内执行或在IPv6栈的__ipv6_local_out函数内执行。所有本地生成的准备发出去的数据包在进入网络协议栈后都会触发该钩子函数。

NF_INET_POST_ROUTING:这个钩子在IPv4栈的ip_output函数内执行或在IPv6栈的ip6_finish_output2函数内执行。所有本地生成的准备发出去的或者需要转发的数据包在路由判断后触发该钩子函数。

NF_HOOK宏与Netfilter裁定

宏NF_HOOK被所有的钩子触发节点调用。代码展示如下:

static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb,struct net_device *in, struct net_device *out,int(*okfn)(struct sk_buff *))
{return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN);
}

NF_HOOK参数介绍如下:

  • pf: 数据包的协议族, 比如IPv4为NEPROTO_IPv4
  • hook: 上面展示过的netfilter钩子枚举类型,比如NF_INET_PRE_ROUTING 或 NF_INET_LOCAL_OUT
  • skb: 正在处理的数据包SKB结构体
  • in: 数据包的输入网络设备
  • out: 数据包的输出网络设备
  • okfn: 钩子将结束时会被调用的函数指针,通常为数据包处理流程里下一个处理函数。

NF_HOOK的返回值是如下所示并解释特定含义

  1. NF_ACCEPT: 通常继续在处理路径上(事实上是 NF-HOOK传进来的最后一个参数okfn会被执行)。
  2. NF_DROP: 丢弃数据包并终止处理。
  3. NF_STOLEN: 数据包被转发并终止处理。
  4. NF_QUEUE: 将数据包入队列给应用程序处理。
  5. NF_REPEAT: 重新进行本钩子函数处理。

回到源代码,IPv4内核网络协议栈会在代码模块内调用NF_HOOK()。

比如net/ipv4/ip_forward.c源代码内的数据包转发为例,NF_HOOK宏会在ip_forward函数的最后被调用,NF_INET_FOEWARD钩子作为NF_HOOK的输入参数,ip_forward_finish作为okfn参数输入做为下一步处理函数。

int ip_forward(struct sk_buff *skb)
{
.....
if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))ip_rt_send_redirect(skb);skb->priority = rt_tos2priority(iph->tos);return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,rt->dst.dev, ip_forward_finish);
.....
}

回调函数与优先级

netfilter的另一个组件是钩子回调函数。

内核网络协议栈使用钩子来代表指定的触发节点并用钩子(枚举值)作为索引值(NF_INET_FORWARD)来调用回调函数响应钩子节点。

内核里的其他模块可以通过netfilter提供的API向指定的钩子上注册回调函数。同一个钩子可以注册多个回调函数,并且在注册的时候能指定优先级参数priority来制定执行时按优先级来调用。

为一个钩子注册回调函数,第一步是定义nf_hook_ops结构体,示例如下:

struct nf_hook_ops {struct list_head list;/* User fills in from here down. */nf_hookfn *hook;struct module *owner;u_int8_t pf;unsigned int hooknum;/* Hooks are ordered in ascending priority. */int priority;
};

 结构体定义里有是三种重要的成员解释如下:

  • hook: 被注册的回调函数,函数的参数跟NF_HOOK参数类似,能通过okfn参数与其他函数串联在一起。
  • hooknum: 被注册的钩子的枚举值
  • priority: 回调函数的优先级,最小值优先。

定义了上述结构体后,一个或者多个回调函数用int nf_register_hook(struct nf_hook_ops *reg);函数或者int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n);来注册。同一个netfiler下的所有nf_hook_ops都是注册的时候按优先级顺序形成链表,所以注册程序会根据优先级在链表中找到nf_hook_ops的合适位置并做链表插入动作。

当NF_HOOK宏被执行来触发指定的钩子时,nf_iterate函数会被调用来遍历链表里的nf_hook_ops以响应该钩子,且每个带hookfn成员的nf_hook_ops都会按序执行。如下图所示:

这种回调函数按链工作的方式也是导致netfilter钩子被称为chain的原因,这种关系尤其反应在iptables的介绍章节。

每个调用函数都必须返回netfilter裁定值;如果裁定值是NF_ACCEPT,nf_iterate会继续调用下一个nf_hook_ops的回调函数知道所有的回调函数被调用完且都是NF_ACCEPT返回值。如果裁定值是NF_DROP,它机会打破传输直接返回NF_DROP;如果裁定值是NF_REPEAT,回调函数会被重新执行。nf_iterate的返回值会被NF_HOOK当做返回值使用,网络协议栈会根据该返回值决定是否继续处理。具体流程如下图所示:

netfiler钩子的回调函数机制有以下特点:

  • 回调函数会依优先级执行,且只有前面的回调函数返回NF_ACCEPT,下一步回调函数才会被执行。
  • 如何一个回调函数可以中断该钩子的回调函数执行链且请求整个网络协议栈中止函数处理。

iptables

基于内核netfilter提供的钩子回调函数机制,netfilter的作者Rusty Russell也开发了iptables来管理用户规则应用到用户态的数据包。

iptbles主要分成以下两个部分:

  • 用户态的ipatbels命令给用户提供了通向内核iptables模块的管理接口。
  • 内核空间的ipatbles模块在内存空间上分配了规则表,能创建或注册规则表。

内核空间模块

xt_table的初始化

在内核网络协议栈,ipatbles通过xt_table结构体有条不紊地管理了大量的数据包处理规则。一个xt_table对应一个规则表,即用户空间下发的规则表。

  • 作用于不同的netfilter钩子
  • 在同一个钩子上不同的规则表有不同的优先级

基于规则的最终目的,ipatbels会默认初始化四个不同的规则表,名字为raw,filter,nat和mangle。下面示例了xt_table的初始化和调用过程。

过滤表的定义如下:

#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \(1 << NF_INET_FORWARD) | \ (1 << NF_INET_LOCAL_OUT))static const struct xt_table packet_filter = { .name = "filter", .valid_hooks = FILTER_VALID_HOOKS, .me = THIS_MODULE, .af = NFPROTO_IPV4, .priority = NF_IP_PRI_FILTER, 
};/* net/ipv4/netfilter/iptable_filter.c */

iptable_filter.c里的初始化函数iptable_filter_init函数会调用xt_hook_link为表packet_filter(struct xt_table类型)绑定必要的钩子函数iptable_filter_init。

struct nf_hook_ops *xt_hook_link(const struct xt_table *table, nf_hookfn *fn)
{unsigned int hook_mask = table->valid_hooks;uint8_t i, num_hooks = hweight32(hook_mask);uint8_t hooknum;struct nf_hook_ops *ops;int ret;ops = kmalloc(sizeof(*ops) * num_hooks, GFP_KERNEL);if (ops == NULL)return ERR_PTR(-ENOMEM);for (i = 0, hooknum = 0; i < num_hooks && hook_mask != 0;hook_mask >>= 1, ++hooknum) {if (!(hook_mask & 1))continue;ops[i].hook     = fn;ops[i].owner    = table->me;ops[i].pf       = table->af;ops[i].hooknum  = hooknum;ops[i].priority = table->priority;++i;}ret = nf_register_hooks(ops, num_hooks);if (ret < 0) {kfree(ops);return ERR_PTR(ret);}return ops;
}
EXPORT_SYMBOL_GPL(xt_hook_link);static int __init iptable_filter_init(void)
{int ret;ret = register_pernet_subsys(&iptable_filter_net_ops);if (ret < 0)return ret;/* Register hooks */filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);if (IS_ERR(filter_ops)) {ret = PTR_ERR(filter_ops);unregister_pernet_subsys(&iptable_filter_net_ops);}return ret;
}

执行如下的初始化过程:

  1. 为xt_table作用的每个钩子遍历.valid_hooks属性,比如filter有3个钩子NF_INET_LOCAL_INNF_INET_FORWARDNF_INET_LOCAL_OUT。
  2. 为每个钩子,按xt_table的优先级属性注册回调函数到钩子。

不同表的优先级值枚举如下所示:

enum nf_ip_hook_priorities {NF_IP_PRI_RAW = -300,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,
};

当一个数据包达到钩子触发节点,所有被不同的规则表注册到该钩子的回调函数都会被有序执行,根据以上的优先级顺序执行。

ipt_do_table()

注册到filter表的钩子回调函数iptable_filter_hook会执行公共的规则检查函数ipt_do_table。ipt_do_table函数接收skb,hook和xt_table作为参数,并执行由后两个参数针对数据包选定的规则集,返回netfilter裁定值作为回调函数的返回值。

在深入规则执行前,有必要理解下内存里规则集是如何表示的。每个规则都包含三个部分

  1. ipt_entry结构体的next_offset成员指向下一个ipt_entry的内存偏移地址处。
  2. 0个或多个ipt_entry_match结构体,每个都可以动态增添额外数据。
  3. 一个ipt_entry_target结构体,每个都可以动态增添额外数据。

ipt_entry结构体定义如下:

struct ipt_entry {struct ipt_ip ip;unsigned int nfcache;/* ipt_entry + matches 在内存中的大小*/u_int16_t target_offset;/* ipt_entry + matches + target 在内存中的大小 */u_int16_t next_offset;/* 跳转后指向前一规则 */unsigned int comefrom;/* 数据包计数器 */struct xt_counters counters;/* 长度为0数组的特殊用法,作为 match 的内存地址 */unsigned char elems[0];
};

 ipt_do_table first jumps to the corresponding ruleset memory area based on the hook type and the xt_table.private.entries property, and performs the following procedure.

  1. first check whether the IP initial of the packet matches the .ipt_ip attribute of the first rule ipt_entry, if it does not match according to the next_offset attribute jump to the next rule.
  2. If the IP initial matches, all ipt_entry_match objects defined by the rule are checked in turn, and the match function associated with the object is called, with the result of returning to the callback function (and whether to drop the packet), jumping to the next rule, or continuing the check, depending on the return value of the call.
  3. ipt_entry_target is read after all checks are passed, and according to its properties returns the netfilter vector to the callback function, continues to the next rule or jumps to another rule at the specified memory address. non-standard ipt_entry_target will also call the bound function, but can only return the vector value and cannot jump to another rule.

 复杂度与更新延时

The above data structure and execution provides iptables with the power to scale, allowing us to flexibly customize the matching criteria for each rule and perform different behaviors based on the results, and even stack-hop between additional rulesets.

Because each rule is of varying length, has a complex internal structure, and the same ruleset is located in contiguous memory space, iptables uses full replacement to update rules, which allows us to add/remove rules from user space with atomic operations, but non-incremental rule updates can cause serious performance problems when the rules are orders of magnitude larger: if a large If you use the iptables approach to implementing services in a large Kubernetes cluster, when the number of services is large, updating even one service will modify the iptables rule table as a whole. The full commit process is protected by kernel lock, so there is a significant update latency.

用户态的表,链与规则

用户态的iptables命令行工具可以从给定的表里读出数据显示在终端,添加新的规则(实际是替换整个规则表内容)等。

iptables主要操作如下对象:

  • table: corresponds to the xt_table structure in kernel space. All operations of iptable are performed on the specified table, which defaults to filter.
  • chain: The set of rules that the specified table calls through a specific netfilter hook, and you can also customize the rule set and jump from the hook rule set.
  • rule: corresponds to ipt_entryipt_entry_match and ipt_entry_target above, defining the rules for matching packets and the behavior to be performed after the match.
  • match: A highly scalable custom match rule.
  • target: A highly scalable custom post-match behavior.

代码调用过程描述如下,链和规则按如下图表执行:

 iptables的一些特殊用法和命令,你可以参考如下的链接:Common Firewall Rules and Commands | DigitalOcean.

conntrack

It is not enough to filter packets by the first part of the layer 3 or 4 information, sometimes it is necessary to further consider the state of the connection. netfilter performs connection tracking with another built-in module conntrack to provide more advanced network filtering functions such as filtering by connection, address translation (NAT), etc. Because of the need to determine the connection status, conntrack has a separate implementation for protocol characteristics, while the overall mechanism is the same.

I was going to continue with conntrack and NAT, but I decided against it because of its length. I will write another article explaining the principles, applications and Linux kernel implementation of conntrack when I have time.

翻译原文如下:

In-depth understanding of netfilter and iptables - SoByte

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/21426.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络概述(一)

因特网概述 网络&#xff0c;互联网与因特网的区别联系&#xff1a; 以上是使用有线和无线链路连接的两个网络。那么&#xff0c;要让这两个网络连接起来&#xff0c;就需要路由器。若干个网络通过多个路由器互联起来&#xff0c;就称为了互联网。 因特网是当今世界上最大的互…

prometheus采集服务的jmx数据,grafana通过dashboard展示jmx数据

prometheus采集服务的jmx数据&#xff0c;grafana通过dashboard展示jmx数据 一、下载prometheus二、解压prometheus三、查看prometheus目录四、查看prometheus版本五、查看prometheus的配置文件六、启动prometheus七、登陆prometheus八、查看prometheus jmx九、下载grafana十、…

Orangepi Zero2 全志H616简介

一、简介 Linux 系统 &#xff0c;平台是 ARM 架构 特性 CPU 全志H616四核64位1.5GHz高性能Cortex-A53处理器 GPU MaliG31MP2 SupportsOpenGLES1.0/2.0/3.2、OpenCL2.0 运行内存 1GBDDR3(与GPU共享&#xff09; 存储 TF卡插槽_16G,测试128G可支持、2MBSPIFlash WIFI蓝牙 AW8…

kettle开发-Day40-AI分流之case/switch

前言&#xff1a; 前面我们讲到了很多关于数据流的AI方面的介绍&#xff0c;包括自定义组件和算力提升这块的&#xff0c;今天我们来学习一个关于kettle数据分流处理非常重要的组件Switch / Case 。当我们的数据来源于类似日志、csv文件等半结构化数据时&#xff0c;我们需要在…

node.js

文章目录 包含Buffer概述创建操作 软件运行过程线程与进程fs模块写入文件追加流式写入使用场景 读取文件文件流式读取读取场景 复制案例内存占有量 重命名和移动删除文件夹操作创建读取删除 查看文件的资源相对路径bug path模块http协议创建http服务器x获取请求体获取请求报文案…

Maven -- <dependencyManagement>管理子项目版本

背景&#xff1a; 一个旧项目&#xff0c;想使用mybatis-plus&#xff0c;想着这是比较基础的依赖包&#xff0c;就在父项目中添加对应依赖&#xff0c;如下: <!-- 依赖声明 --><dependencyManagement><dependencies><!-- mybatis-plus 依赖配置 -->&l…

JavaWeb(1)——HTML、CSS、JS 快速入门

JavaWeb 是使用 Java 技术来构建 Web 应用程序的一种方法。 HTML&#xff08;超文本标记语言&#xff0c;负责网页的结构&#xff09;是一种用于创建网页结构和内容的标记语言。它由一系列标签组成&#xff0c;每个标签都有特定的功能。开发人员可以使用 HTML 来定义页面的结构…

文字识别(OCR)介绍与开源方案对比

目录 文字识别&#xff08;OCR&#xff09;介绍与开源方案对比 一、OCR是什么 二、OCR基本原理说明 三、OCR基本实现流程 四、OCR开源项目调研 1、tesseract 2、PaddleOC 3、EasyOCR 4、chineseocr 5、chineseocr_lite 6、cnocr 7、商业付费OCR 1&#xff09;腾讯…

Self-Attention Cross-Attention

transformer的细节到底是怎么样的&#xff1f;Transformer 连环18问&#xff01; 4.1 从功能角度&#xff0c;Transformer Encoder的核心作用是提取特征&#xff0c;也有使用Transformer Decoder来提取特征。例如&#xff0c;一个人学习跳舞&#xff0c;Encoder是看别人是如何…

前端调整滚动条的外观样式

前端调整滚动条的外观样式 css主要分三个步骤 1、设置滚动条宽度 ::-webkit-scrollbar { width: 5px; } 效果&#xff1a;分别设置50px和5px宽度 2、设置里面小滑块的样式 ::-webkit-scrollbar-thumb {border-radius: 5px;-webkit-box-shadow: inset 0 0 5px rgba(0,0,0…

【Unity面试篇】Unity 面试题总结甄选 |Unity性能优化 | ❤️持续更新❤️

前言 关于Unity面试题相关的所有知识点&#xff1a;&#x1f431;‍&#x1f3cd;2023年Unity面试题大全&#xff0c;共十万字面试题总结【收藏一篇足够面试&#xff0c;持续更新】为了方便大家可以重点复习某个模块&#xff0c;所以将各方面的知识点进行了拆分并更新整理了新…

神经网络结构可视化-netron

网址&#xff1a;https://netron.app/ 点选择模型&#xff0c;将oonx文件拉到netron界面&#xff0c;即可 输出; 如何将pytorch模型转换为onnx的格式&#xff1f; 在测试&#xff08;训练好的模型&#xff09;里输入代码 to_onnx(model, 3, 28, 28, output/params.onnx)其…