nf_hook_ops数据结构
1 struct nf_hook_ops 2 { 3 struct list_head list; //链表成员 4 /* User fills in from here down. */ 5 nf_hookfn *hook; //钩子函数指针 6 struct module *owner; 7 int pf; //协议簇,对于ipv4而言,是PF_INET 8 int hooknum; //hook类型 9 /* Hooks are ordered in ascending priority. */ 10 int priority; //优先级 11 };
成员hook即用户定义的勾子函数;
owner表示注册这个勾子函数的模块,因为netfilter是内核空间的,所以一般为模块来完成勾子函数注册;
pf与hooknum一起索引到特定协议特定编号的勾子函数队列,用于索引nf_hooks;
priority决定在同一队列(pf与hooknum相同)的顺序,priority越小则排列越靠前。
struct nf_hook_ops只是存储勾子的数据结构,而真正存储这些勾子供协议栈调用的是nf_hooks,从定义可以看出,它其实就是二维数组的链表。
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
一、nf_hook_ops中使用常量定义
1、协议/地址簇相关(#include <linux/socket.h>)
1 /* Supported address families. */ 2 #define AF_UNSPEC 0 3 #define AF_UNIX 1 /* Unix domain sockets */ 4 #define AF_LOCAL 1 /* POSIX name for AF_UNIX */ 5 #define AF_INET 2 /* Internet IP Protocol */ 6 #define AF_AX25 3 /* Amateur Radio AX.25 */ 7 #define AF_IPX 4 /* Novell IPX */ 8 #define AF_APPLETALK 5 /* AppleTalk DDP */ 9 #define AF_NETROM 6 /* Amateur Radio NET/ROM */ 10 #define AF_BRIDGE 7 /* Multiprotocol bridge */ 11 #define AF_ATMPVC 8 /* ATM PVCs */ 12 #define AF_X25 9 /* Reserved for X.25 project */ 13 #define AF_INET6 10 /* IP version 6 */ 14 #define AF_ROSE 11 /* Amateur Radio X.25 PLP */ 15 #define AF_DECnet 12 /* Reserved for DECnet project */ 16 #define AF_NETBEUI 13 /* Reserved for 802.2LLC project*/ 17 #define AF_SECURITY 14 /* Security callback pseudo AF */ 18 #define AF_KEY 15 /* PF_KEY key management API */ 19 #define AF_NETLINK 16 20 #define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */ 21 #define AF_PACKET 17 /* Packet family */ 22 #define AF_ASH 18 /* Ash */ 23 #define AF_ECONET 19 /* Acorn Econet */ 24 #define AF_ATMSVC 20 /* ATM SVCs */ 25 #define AF_RDS 21 /* RDS sockets */ 26 #define AF_SNA 22 /* Linux SNA Project (nutters!) */ 27 #define AF_IRDA 23 /* IRDA sockets */ 28 #define AF_PPPOX 24 /* PPPoX sockets */ 29 #define AF_WANPIPE 25 /* Wanpipe API Sockets */ 30 #define AF_LLC 26 /* Linux LLC */ 31 #define AF_IB 27 /* Native InfiniBand address */ 32 #define AF_MPLS 28 /* MPLS */ 33 #define AF_CAN 29 /* Controller Area Network */ 34 #define AF_TIPC 30 /* TIPC sockets */ 35 #define AF_BLUETOOTH 31 /* Bluetooth sockets */ 36 #define AF_IUCV 32 /* IUCV sockets */ 37 #define AF_RXRPC 33 /* RxRPC sockets */ 38 #define AF_ISDN 34 /* mISDN sockets */ 39 #define AF_PHONET 35 /* Phonet sockets */ 40 #define AF_IEEE802154 36 /* IEEE802154 sockets */ 41 #define AF_CAIF 37 /* CAIF sockets */ 42 #define AF_ALG 38 /* Algorithm sockets */ 43 #define AF_NFC 39 /* NFC sockets */ 44 #define AF_VSOCK 40 /* vSockets */ 45 #define AF_MAX 41 /* For now.. */ 46 47 /* Protocol families, same as address families. */ 48 #define PF_UNSPEC AF_UNSPEC 49 #define PF_UNIX AF_UNIX 50 #define PF_LOCAL AF_LOCAL 51 #define PF_INET AF_INET 52 #define PF_AX25 AF_AX25 53 #define PF_IPX AF_IPX 54 #define PF_APPLETALK AF_APPLETALK 55 #define PF_NETROM AF_NETROM 56 #define PF_BRIDGE AF_BRIDGE 57 #define PF_ATMPVC AF_ATMPVC 58 #define PF_X25 AF_X25 59 #define PF_INET6 AF_INET6 60 #define PF_ROSE AF_ROSE 61 #define PF_DECnet AF_DECnet 62 #define PF_NETBEUI AF_NETBEUI 63 #define PF_SECURITY AF_SECURITY 64 #define PF_KEY AF_KEY 65 #define PF_NETLINK AF_NETLINK 66 #define PF_ROUTE AF_ROUTE 67 #define PF_PACKET AF_PACKET 68 #define PF_ASH AF_ASH 69 #define PF_ECONET AF_ECONET 70 #define PF_ATMSVC AF_ATMSVC 71 #define PF_RDS AF_RDS 72 #define PF_SNA AF_SNA 73 #define PF_IRDA AF_IRDA 74 #define PF_PPPOX AF_PPPOX 75 #define PF_WANPIPE AF_WANPIPE 76 #define PF_LLC AF_LLC 77 #define PF_IB AF_IB 78 #define PF_MPLS AF_MPLS 79 #define PF_CAN AF_CAN 80 #define PF_TIPC AF_TIPC 81 #define PF_BLUETOOTH AF_BLUETOOTH 82 #define PF_IUCV AF_IUCV 83 #define PF_RXRPC AF_RXRPC 84 #define PF_ISDN AF_ISDN 85 #define PF_PHONET AF_PHONET 86 #define PF_IEEE802154 AF_IEEE802154 87 #define PF_CAIF AF_CAIF 88 #define PF_ALG AF_ALG 89 #define PF_NFC AF_NFC 90 #define PF_VSOCK AF_VSOCK 91 #define PF_MAX AF_MAX
2、hook返回值
LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0) typedef unsigned int nf_hookfn(void *priv, struct sk_buff *skb, const struct nf_hook_state *state); struct nf_hook_state { unsigned int hook; int thresh; u_int8_t pf; struct net_device *in; struct net_device *out; struct sock *sk; struct net *net; struct list_head *hook_list; int (*okfn)(struct net *, struct sock *, struct sk_buff *); }; static inline void nf_hook_state_init(struct nf_hook_state *p, struct list_head *hook_list, unsigned int hook, int thresh, u_int8_t pf, struct net_device *indev, struct net_device *outdev, struct sock *sk, struct net *net, int (*okfn)(struct net *, struct sock *, struct sk_buff *)) { p->hook = hook; p->thresh = thresh; p->pf = pf; p->in = indev; p->out = outdev; p->sk = sk; p->net = net; p->hook_list = hook_list; p->okfn = okfn; }
1 //hook函数由 指定,其函数声明如下: nf_hookfn *hook 2 3 // include/linux/netfilter.h 4 5 typedef unsigned int nf_hookfn(unsigned int hooknum, 6 struct sk_buff *skb, 7 const struct net_device *in, 8 const struct net_device *out, 9 int (*okfn)(struct sk_buff *));
1 /* Responses from hook functions. */ 2 3 #define NF_DROP 0 ----丢弃,释放sk_buff结构 4 5 #define NF_ACCEPT 1 ----继续正常传输数据报(按照5个钩子点正常传输) 6 7 #define NF_STOLEN 2 ----模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter获取了该数据包的所有权。 8 9 #define NF_QUEUE 3 ----对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理) 10 11 #define NF_REPEAT 4 ----再次调用该回调函数,应当谨慎使用这个值,以免造成死循环 12 13 #define NF_STOP 5 ----终止hook链处理,不会释放sk_buff数据
3、 hook点及优先级相关
1 enum nf_ip_hook_priorities { 2 NF_IP_PRI_FIRST = INT_MIN, 3 NF_IP_PRI_CONNTRACK_DEFRAG = -400, 4 NF_IP_PRI_RAW = -300, 5 NF_IP_PRI_SELINUX_FIRST = -225, 6 NF_IP_PRI_CONNTRACK = -200, 7 NF_IP_PRI_MANGLE = -150, 8 NF_IP_PRI_NAT_DST = -100, 9 NF_IP_PRI_FILTER = 0, 10 NF_IP_PRI_SECURITY = 50, 11 NF_IP_PRI_NAT_SRC = 100, 12 NF_IP_PRI_SELINUX_LAST = 225, 13 NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, 14 NF_IP_PRI_LAST = INT_MAX, 15 };
4、NFPROTO_NUMPROTO表示勾子关联的协议
1 enum { 2 NFPROTO_UNSPEC = 0, 3 NFPROTO_IPV4 = 2, 4 NFPROTO_ARP = 3, 5 NFPROTO_BRIDGE = 7, 6 NFPROTO_IPV6 = 10, 7 NFPROTO_DECNET = 12, 8 NFPROTO_NUMPROTO, 9 }; //能在协议/地址簇相关(#include <linux/socket.h>)找到对应值
5、NF_MAX_HOOKS表示勾子应用的位置,可选值在每个协议模块内部定义,这些值代表了勾子函数在协议流程中应用的位置
///< 以下宏定义为userspace中使用的 (include/uapi/linux/netfilter_ipv4.h) /* IP Hooks */ /* After promisc drops, checksum checks. */ #define NF_IP_PRE_ROUTING 0 /* If the packet is destined for this box */ #define NF_IP_LOCCAL_IN 1 /* If the packet is destined for another interface */ #define NF_IP_FORWARD 2 /* Packets coming from a local process */ #define NF_IP_LOCCAL_OUT 3 /* Packets about to hit the wire */ #define NF_IP_POST_ROUTING 4 #define NF_IP_NUMHOOKS 5 ///< 对于内核空间使用的Hook点的定义,使用如下的定义(include/uapi/linux/netfilter.h) enum nf_inet_hooks { NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING, NF_INET_NUMHOOKS }; /* BR、IP 与 INET 是一致的 NF_XXX_PRE_ROUTING, NF_XXX_LOCAL_IN, NF_XXX_FORWARD, NF_XXX_LOCAL_OUT, NF_XXX_POST_ROUTING, NF_XXX_NUMHOOKS */
6、触发钩子函数
钩子函数已经被保存到不同的链上,什么时候才会触发调用这些钩子函数来处理数据包?要触发调用某个挂载点上(链)的所有钩子函数,需要使用
NF_HOOK
宏来实现,其定义如下:
1 // 文件:include/linux/netfilter.h 2 3 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (list_empty(&nf_hooks[(pf)][(hook)]) ? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
NF_HOOK
宏的各个参数的作用:
pf
:协议类型,就是nf_hooks
数组的第一个维度,如 IPv4 协议就是PF_INET
。hook
:要调用哪一条链(挂载点)上的钩子函数,如NF_IP_PRE_ROUTING
。indev
:接收数据包的设备对象。outdev
:发送数据包的设备对象。okfn
:当链上的所有钩子函数都处理完成,将会调用此函数继续对数据包进行处理。
NF_HOOK
宏的实现也比较简单,okfn
函数来处理数据包,nf_hook_slow
函数来处理数据包。我们来看看 nf_hook_slow
函数的实现:1 // 文件:net/core/netfilter.c 2 3 int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb, 4 struct net_device *indev, struct net_device *outdev, 5 int (*okfn)(struct sk_buff *)) 6 { 7 struct list_head *elem; 8 unsigned int verdict; 9 int ret = 0; 10 11 elem = &nf_hooks[pf][hook]; // 获取要调用的钩子函数链表 12 13 // 遍历钩子函数链表,并且调用钩子函数对数据包进行处理 14 verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev, outdev, &elem, okfn); 15 ... 16 // 如果处理结果为 NF_ACCEPT, 表示数据包通过所有钩子函数的处理, 那么就调用 okfn 函数继续处理数据包 17 // 如果处理结果为 NF_DROP, 表示数据包被拒绝, 应该丢弃此数据包 18 switch (verdict) { 19 case NF_ACCEPT: 20 ret = okfn(skb); 21 break; 22 case NF_DROP: 23 kfree_skb(skb); 24 ret = -EPERM; 25 break; 26 } 27 28 return ret; 29 }
nf_hook_slow
函数的实现也比较简单,过程如下:
- 首先调用
nf_iterate
函数来遍历钩子函数链表,并调用链表上的钩子函数来处理数据包。 - 如果处理结果为
NF_ACCEPT
,表示数据包通过所有钩子函数的处理, 那么就调用okfn
函数继续处理数据包。 - 如果处理结果为
NF_DROP
,表示数据包没有通过钩子函数的处理,应该丢弃此数据包。
NF_HOOK
宏来调用钩子函数链表上的钩子函数,那么内核在什么地方调用这个宏呢?ip_rcv
函数中就调用了 NF_HOOK
宏来处理数据包,1 // 文件:net/ipv4/ip_input.c 2 3 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) 4 { 5 ... 6 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); 7 }
ip_rcv
函数中调用了 NF_HOOK
宏来处理输入的数据包,NF_IP_PRE_ROUTING
。而 okfn
设置为 ip_rcv_finish
,NF_IP_PRE_ROUTING
链上的所有钩子函数都成功对数据包进行处理后,ip_rcv_finish
函数来继续对数据包进行处理。1、挂载点解析:
PRE_ROUTING
:路由前。数据包进入IP层后,但还没有对数据包进行路由判定前。LOCAL_IN
:进入本地。对数据包进行路由判定后,如果数据包是发送给本地的,在上送数据包给上层协议前。FORWARD
:转发。对数据包进行路由判定后,如果数据包不是发送给本地的,在转发数据包出去前。LOCAL_OUT
:本地输出。对于输出的数据包,在没有对数据包进行路由判定前。POST_ROUTING
:路由后。对于输出的数据包,在对数据包进行路由判定后。
路由判定:
从上图可以看出,路由判定是数据流向的关键点。
- 第一个路由判定通过查找输入数据包
IP头部
的目的IP地址
是否为本机的IP地址
,如果是本机的IP地址
,说明数据是发送给本机的。否则说明数据包是发送给其他主机,经过本机只是进行中转。 - 第二个路由判定根据输出数据包
IP头部
的目的IP地址
从路由表中查找对应的路由信息,然后根据路由信息获取下一跳主机(或网关)的IP地址
,然后进行数据传输。
- 发往本地:NF_INET_PRE_ROUTING-->NF_INET_LOCAL_IN
- 转发:NF_INET_PRE_ROUTING-->NF_INET_FORWARD-->NF_INET_POST_ROUTING
- 本地发出:NF_INET_LOCAL_OUT-->NF_INET_POST_ROUTING
挂载点
注册钩子函数,就能够对处于不同阶段的数据包进行过滤或者修改操作。LOCAL_IN
挂载点)时,就会相继调用ipt_hook
和 fw_confirm
钩子函数来处理数据包。链
,挂载点对应的链名称如下所示:
LOCAL_IN
挂载点:又称为INPUT链
。LOCAL_OUT
挂载点:又称为OUTPUT链
。FORWARD
挂载点:又称为PORWARD链
。PRE_ROUTING
挂载点:又称为PREROUTING链
。POST_ROUTING
挂载点:又称为POSTOUTING链
。
2、代码分析
NF_INET_PRE_ROUTING
当二层收包结束后,会根据注册的协议和回调函数分发数据包,其中ipv4的数据包会分发到ip_rcv函数进行三层协议栈处理,该函数对数据包的合法性进行检查,并且设置一些必要字段之后,经过PRE_ROUTING钩子点;
1 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) 2 { 3 /* IP数据报的合法性检查和一些必要字段设置,此处省略 */ 4 5 /* 经过PRE_ROUTING钩子点 */ 6 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, 7 net, NULL, skb, dev, NULL, 8 ip_rcv_finish); 9 10 }
NF_INET_LOCAL_IN
上面的ip_rcv函数在经过了PRE_ROUTING钩子点之后,会调用ip_rcv_finish函数,该函数的主要功能是查路由,决定数据包是输入到本地还是转发,并调用dst_input函数;当数据包输入本地时,dst_input函数实际调用了ip_local_deliver函数,函数首先对分片进行检查,如果是分片则需要进行重组,然后经过NF_INET_LOCAL_IN钩子点,之后调用ip_local_deliver_finish继续进行输入本地的其他工作;
1 int ip_local_deliver(struct sk_buff *skb) 2 { 3 struct net *net = dev_net(skb->dev); 4 5 /* 分片重组 */ 6 if (ip_is_fragment(ip_hdr(skb))) { 7 if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER)) 8 return 0; 9 } 10 11 /* 经过LOCAL_IN钩子点 */ 12 return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, 13 net, NULL, skb, skb->dev, NULL, 14 ip_local_deliver_finish); 15 }
NF_INET_FORWARD
上面的ip_rcv函数在经过了PRE_ROUTING钩子点之后,会调用ip_rcv_finish函数,该函数的主要功能是查路由,决定数据包是输入到本地还是转发,并调用dst_input函数;当数据包输入本地时,dst_input函数实际调用了ip_forward函数,函数数据包进行合法性检查,然后经过NF_INET_FORWARD钩子点,之后调用ip_forward_finish继续进行转发的其他工作,ip_forward_finish在输出数据包的时候,实际上又调用dst_output,实际上就是ip_output函数;
1 static int ip_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 /* 合法性检查等,此处省略 */ 4 5 /* 经过FORWARD钩子点 */ 6 return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, 7 net, NULL, skb, skb->dev, rt->dst.dev, 8 ip_forward_finish); 9 10 }
NF_INET_LOCAL_OUT
从本机发出的数据包,在查询路由成功之后,会调用__ip_local_out函数,函数首先进行必要字段设置和校验和计算,然后经过NF_INET_LOCAL_OUT钩子点,之后会调用dst_output继续完成数据包输出的其他工作,ipv4的路由输出函数实际上就是ip_output函数;
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb) { struct iphdr *iph = ip_hdr(skb); /* 设置总长度 */ iph->tot_len = htons(skb->len); /* 计算校验和 */ ip_send_check(iph); /* if egress device is enslaved to an L3 master device pass the * skb to its handler for processing */ skb = l3mdev_ip_out(sk, skb); if (unlikely(!skb)) return 0; /* 设置ip协议 */ skb->protocol = htons(ETH_P_IP); /* 经过NF的LOCAL_OUT钩子点 */ return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, net, sk, skb, NULL, skb_dst(skb)->dev, dst_output); }
NF_INET_POST_ROUTING
转发的数据包或者是本地输出的数据包,最后都会经过ip_output进行输出,函数设置设备和协议之后,经过NF_INET_POST_ROUTING钩子点,之后调用ip_finish_output进行后续输出操作,其中包括了分片等;
1 int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 struct net_device *dev = skb_dst(skb)->dev; 4 5 IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT, skb->len); 6 7 /* 设置输出设备和协议 */ 8 skb->dev = dev; 9 skb->protocol = htons(ETH_P_IP); 10 11 /* 经过NF的POST_ROUTING钩子点 */ 12 return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, 13 net, sk, skb, NULL, dev, 14 ip_finish_output, 15 !(IPCB(skb)->flags & IPSKB_REROUTED)); 16 }
最后还是以bridge来说明下hooks参数的意义:

发送:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)