目录
三、在 Linux 中编写 Netfilter 追踪包转发流程的具体步骤
一、Netfilter 是什么
在 Linux 网络系统中,Netfilter 可谓是一个极其关键的存在,它是 Linux 内核的一个子系统,为网络相关操作提供了强大的支持。其采用模块化设计,这种设计方式使得它具有良好的可扩充性,就像搭积木一样,开发者可以根据实际需求轻松添加或修改功能模块。
Netfilter 与 IP 协议栈无缝契合,如同紧密咬合的齿轮,协同工作。它允许使用者对数据报进行多种操作,其中最常见的就是数据报过滤,通过设定一系列规则,决定哪些数据报可以通过,哪些需要被丢弃,就如同海关检查货物,符合规定的放行,不符合的则被拦截;地址转换也是它的重要功能之一,比如网络地址转换(NAT),可以实现私有网络地址与公共网络地址之间的转换,让多个设备能够共享一个公网 IP 访问互联网;它还能对数据报进行处理,例如修改数据报的某些字段等。
二、Netfilter 工作原理
(一)Netfilter 的 5 个 hook 点
Netfilter 在 Linux 网络协议栈中设置了 5 个关键的 hook 点,它们就像网络交通要道上的关卡,对数据包的流向和处理起着决定性作用。这 5 个 hook 点分别是 PREROUTING、LOCAL_IN、FORWARD、LOCAL_OUT 和 POSTROUTING 。
-
PREROUTING:当数据包进入系统时,首先会到达 PREROUTING hook 点,这个点位于数据包刚刚进入网络层,还未进行路由查找之前,如同城市入口的检查站,对所有进入的 “访客” 进行初步筛查。在这里,常见的操作是进行目的地址转换(DNAT),比如将外部对服务器公网 IP 的访问请求,转换为对内部实际服务器私有 IP 的访问。
-
LOCAL_IN:若数据包的目的地是本机,在经过 PREROUTING 的初步处理和路由查找后,会进入 LOCAL_IN hook 点,它是通往本机的 “最后一道门岗”。在这个点,主要进行输入数据包的过滤,决定哪些数据包可以被本机接收并处理,哪些需要被丢弃,就像门卫检查访客是否被允许进入特定房间。
-
FORWARD:当数据包不是发往本机,而是需要转发到其他主机时,就会经过 FORWARD hook 点,它承担着数据包 “中转站” 的角色。在这个点,主要对转发的数据包进行过滤,判断这些 “过路客” 是否符合转发规则,以确保网络转发的安全性和合理性。
-
LOCAL_OUT:当本机进程产生数据包要发送出去时,会先到达 LOCAL_OUT hook 点,它是数据包离开本机前的 “内部审核点”。在这里可以对数据包进行源地址转换(SNAT)等处理,比如将本机不同进程发出的数据包的源地址统一转换为公网 IP,以便在公网上进行传输。
-
POSTROUTING:这是数据包离开系统前的最后一个 hook 点,所有即将通过网络设备发送出去的数据包都会经过这里,就像货物离开仓库前的最终安检。在 POSTROUTING 点,通常进行源地址转换等操作,确保数据包以正确的源地址发送到网络中。
为了更直观地理解这 5 个 hook 点的位置和作用,我们来看下面这张图:
从图中可以清晰地看到数据包在不同情况下流经各个 hook 点的路径,比如外部数据包进入系统时,先经过 PREROUTING,若目的地为本机则进入 LOCAL_IN,若需转发则进入 FORWARD;本机发出的数据包则先经过 LOCAL_OUT,最后经过 POSTROUTING 离开系统。
(二)hook 函数的机制
在 Netfilter 的每个 hook 点上,都可以注册相应的 hook 函数,这些函数就像是关卡上的工作人员,负责对经过的数据包进行具体的处理和决策。
-
注册方式:注册 hook 函数需要使用特定的结构体和函数。在 Linux 内核中,通过定义struct nf_hook_ops结构体来描述 hook 函数的相关信息,包括 hook 函数指针、所属模块、协议族、hook 点编号以及优先级等。例如:
static struct nf_hook_ops nf_test_ops[] __read_mostly = {
{
.hook = nf_test_in_hook,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_FIRST,
},
};
然后使用nf_register_hooks函数将这些 hook 函数注册到对应的 hook 点上,如nf_register_hooks(nf_test_ops, ARRAY_SIZE(nf_test_ops));。
- 参数:hook 函数通常接收几个重要参数,以nf_test_in_hook函数为例:
static unsigned int nf_test_in_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff*))
-
hook:表示当前所处的 hook 点编号,通过这个参数可以判断函数在哪个 hook 点被调用,从而执行相应的处理逻辑。
-
skb:即struct sk_buff结构体指针,它是 Linux 内核中用于表示网络数据包的核心数据结构,包含了数据包的各种信息,如数据内容、源地址、目的地址、协议类型等,就像一个装满货物信息的清单,hook 函数可以通过它来获取和修改数据包的相关内容。
-
in和out:分别指向数据包进入和离开的网络设备,通过这两个参数可以了解数据包的来源和去向,比如判断数据包是从哪个网卡进入系统,又要从哪个网卡发送出去。
-
okfn:是一个函数指针,当所有该 hook 点的登记函数调用完后,会调用这个函数来继续后续的处理,它就像是接力赛中的下一棒选手,在当前 hook 函数处理完后接过数据包继续处理流程。
- 返回值:hook 函数的返回值决定了数据包的后续命运,常见的返回值有以下几种:
-
NF_ACCEPT:表示接受该数据包,允许其继续正常传输到网络协议栈的下一个阶段,就像关卡工作人员放行车辆,让它继续前行。例如在 LOCAL_IN hook 点的 hook 函数中,如果判断数据包符合接收规则,就返回 NF_ACCEPT,让数据包被本机接收处理。
-
NF_DROP:意味着丢弃该数据包,不再继续传输,如同关卡拦截了一辆违规车辆,禁止其通行。比如在 FORWARD hook 点,如果发现某个数据包来源可疑,可能存在安全风险,就可以返回 NF_DROP 将其丢弃,防止潜在威胁传播到其他网络。
-
NF_STOLEN:表示模块接管该数据包,告诉 Netfilter “忘掉” 这个数据包,后续由该回调函数独自处理,Netfilter 不再参与,就像某个特殊部门直接接管了一个重要物资,其他部门不再干涉。例如在某些特殊的网络处理场景中,某个模块需要对特定数据包进行深度处理,就可以返回 NF_STOLEN 接管数据包。
-
NF_QUEUE:用于将数据包进行排队,通常用于将数据包交给用户空间的进程进行处理,就像将一批货物先放在仓库排队,等待后续特定的处理流程。比如一些需要用户空间程序进行详细分析或特殊处理的数据包,可以返回 NF_QUEUE 将其排入队列,由用户空间程序进一步处理。
-
NF_REPEAT:会再次调用该回调函数,但使用时需要谨慎