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 };
成员list用于链入全局勾子数组nf_hooks中,它一定在第一位,保证&nf_hook_ops->list的值与&nf_hook_ops相同,稍后在使用时会用到这一技巧;
成员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,表示数据包没有通过钩子函数的处理,应该丢弃此数据包。
既然 Netfilter 是通过调用 NF_HOOK 宏来调用钩子函数链表上的钩子函数,那么内核在什么地方调用这个宏呢?
比如数据包进入 IPv4 协议层的处理函数 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 函数来继续对数据包进行处理。
 
7、NetFilter注册
注册:
  int nf_register_hook(struct nf_hook_ops *reg);
  int nf_register_hooks(struct nf_hook_ops *reg,unsigned int n);
注销:
  void nf_unregister_hook(struct nf_hook_ops *reg);
  void nf_unregister_hooks(struct nf_hook_ops *reg,unsigned int n)
 
 

二、Netfilter 之 五个钩子点

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_hookfw_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参数的意义:
上面已经讲过,它决定了在协议流程的何处调用勾子函数;
因为使用NetFilter的目的是在内核态处理报文,而哪些地方可以处理报文只能是内核已经定义好的。
一般来说,内核会在报文发送和接收的关键位置添加勾子函数处理,查找代码中NF_HOOK即可知。
下面以bridge,为例,来看下在哪些地方用到了,以及这些值的含义:

接收:

 

发送:

 

 

posted on 2022-10-23 16:08  背影_墨白  阅读(699)  评论(0编辑  收藏  举报