Netfilter Hooks in Kernel

Netfilter Framework(ipv4)

  graph LR
    %% 定义节点样式
    classDef hook fill:#d9ead3,stroke:#6aa84f,stroke-width:2px;
    classDef process fill:#cfe2f3,stroke:#3d85c6,stroke-width:2px;
    classDef routing fill:#e6e6e6,stroke:#666666,stroke-width:2px;

    %% 主流程节点
    NetIn["Network Interface<br>(Input)"]:::process
    PreRouting("PRE_ROUTING"):::hook
    RoutingDec1["Routing Decision"]:::routing
    LocalIn("LOCAL_IN"):::hook
    HigherLayers["Higher Layers<br>Local Processes"]:::process
    Forward("FORWARD"):::hook
    LocalOut("LOCAL_OUT"):::hook
    RoutingDec2["Routing Decision"]:::routing
    PostRouting("POST_ROUTING"):::hook
    NetOut["Network Interface<br>(Output)"]:::process

    %% 连接关系
    NetIn --> PreRouting
    PreRouting --> RoutingDec1
    RoutingDec1 --> LocalIn
    RoutingDec1 --> Forward
    LocalIn --> HigherLayers
    HigherLayers --> LocalOut
    LocalOut --> RoutingDec2
    RoutingDec2 --> PostRouting
    Forward --> PostRouting
    PostRouting --> NetOut

    %% 模拟原图箭头颜色
    %% Receive packet (红色路径)
    linkStyle 0 stroke:#ff0000,stroke-width:2px;
    linkStyle 1 stroke:#ff0000,stroke-width:2px;
    linkStyle 2 stroke:#ff0000,stroke-width:2px;
    linkStyle 3 stroke:#ff0000,stroke-width:2px;

    %% Transmit packet (蓝色路径)
    linkStyle 4 stroke:#0000ff,stroke-width:2px;
    linkStyle 5 stroke:#0000ff,stroke-width:2px;
    linkStyle 6 stroke:#0000ff,stroke-width:2px;
    linkStyle 7 stroke:#0000ff,stroke-width:2px;
    linkStyle 8 stroke:#0000ff,stroke-width:2px;
    linkStyle 9 stroke:#0000ff,stroke-width:2px;

Hook Structure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct nf_hook_ops {
	/* User fills in from here down. */
	nf_hookfn		*hook;
	struct net_device	*dev;
	void			*priv;
	u_int8_t		pf;
	unsigned int		hooknum;
	/* Hooks are ordered in ascending priority. */
	int			priority;
};
  • hook callback function

    • It is a callback function
    • Signature depends on kernel version.
    1
    2
    3
    
    typedef unsigned int nf_hookfn(void *priv,
    			       struct sk_buff *skb,
    			       const struct nf_hook_state *state);
    
  • callback response

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    //netfilter.h
    
    /* Responses from hook functions. */
    #define NF_DROP 0
    #define NF_ACCEPT 1
    #define NF_STOLEN 2
    #define NF_QUEUE 3
    #define NF_REPEAT 4
    #define NF_STOP 5	/* Deprecated, for userspace nf_queue compatibility. */
    #define NF_MAX_VERDICT NF_STOP
    
Hook Chain
NF_DROP Drop the packet
NF_ACCEPT Continue normal traversal
NF_STOLEN Current hook function will take care of the packet. Don’t continue traversal
NF_QUEUE Queue the packet (usually for user space handling)
NF_REPEAT Call this hook again
NF_STOP Terminate the hook chain processing without releasing sk_buff data
  • priority
    • Callbacks of same hook point are called in ascending order of priority
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//netfilter_ipv4.h

enum nf_ip_hook_priorities {
	NF_IP_PRI_FIRST = INT_MIN,
	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,
};

Hook Position in Linux Network Stack

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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_LOCAL_IN		1
/* If the packet is destined for another interface. */
#define NF_IP_FORWARD		2
/* Packets coming from a local process. */
#define NF_IP_LOCAL_OUT		3
/* Packets about to hit the wire. */
#define NF_IP_POST_ROUTING	4
#define NF_IP_NUMHOOKS		5

在 kernel v2.6.25之后 ,将原本仅用于 IPv4 的 NF_IP_XXX 枚举合并到了统一的 NF_INET_XXX 命名空间中,可做兼容处理#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25))

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 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,
	NF_INET_INGRESS = NF_INET_NUMHOOKS,
};

PREROUTING

Hook File Function
NF_INET_PRE_ROUTING net/ipv4/ip_input.c ip_rcv()
  • Works only for incoming packets.
  • Packet is passed to this hook after the simple sanity checks.
  • Before routing decision is made (forwarded or local in).
  • This is the first hook through which incoming packet is passed through.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// net/ipv4/af_inet.c

static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),   // 匹配 EtherType = 0x0800 (IPv4)
    .func = ip_rcv,
    .list_func = ip_list_rcv,
};

static int __init inet_init(void)
{
...
	dev_add_pack(&ip_packet_type);	//将 ip_packet_type 注册到全局的 ptype_all/ptype_base 哈希表中。    
...    
}

__netif_receive_skb_core() 会根据 skb 的 protocol 字段(ETH_P_IP)在 ptype_base 哈希表中查到 ip_packet_type,并将 pt_prev 指向它,然后通过pt_prev->func 调用 ip_rcv

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// net/ipv4/ip_input.c

/*
 * IP receive entry point
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
	   struct net_device *orig_dev)
{
	struct net *net = dev_net(dev);

	skb = ip_rcv_core(skb, net);
	if (skb == NULL)
		return NET_RX_DROP;

	return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
		       net, NULL, skb, dev, NULL,
		       ip_rcv_finish);
}

INPUT

Hook File Function
NF_INET_LOCAL_IN net/ipv4/ip_input.c ip_local_deliver()
  • Works only for incoming packets.
  • Triggered after the routing decision (i.e., once the kernel determines the packet is destined for the local host).
  • Occurs before local processing (e.g., before
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// net/ipv4/ip_input.c

/*
 * 	Deliver IP Packets to the higher protocol layers.
 */
int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *	Reassemble IP fragments.
	 */
	struct net *net = dev_net(skb->dev);

	if (ip_is_fragment(ip_hdr(skb))) {
		if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}

	return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
		       net, NULL, skb, skb->dev, NULL,
		       ip_local_deliver_finish);
}

FORWARD

Hook File Function
NF_INET_FORWARD net/ipv4/ip_forward.c ip_forward()
  • Works only for incoming packet.
  • According to routing decision, if the packet is sent to another interface then this hook is called.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// net/ipv4/ip_forward.c

int ip_forward(struct sk_buff *skb)
{
	u32 mtu;
	struct iphdr *iph;	/* Our header */
	struct rtable *rt;	/* Route we use */
	struct ip_options *opt	= &(IPCB(skb)->opt);
	struct net *net;
......
......

	return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,
		       net, NULL, skb, skb->dev, rt->dst.dev,
		       ip_forward_finish);

sr_failed:
	/*
	 *	Strict routing permits no gatewaying
	 */
	 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
	 goto drop;

too_many_hops:
	/* Tell the sender its packet died... */
	__IP_INC_STATS(net, IPSTATS_MIB_INHDRERRORS);
	icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
	kfree_skb(skb);
	return NET_RX_DROP;
}

OUTPUT

Hook File Function
NF_INET_LOCAL_OUT net/ipv4/ip_output.c __ip_local_out
  • Works only for outgoing packets.
  • The packet is created locally (originating from the host itself).
  • Routing decision is made after this hook is called.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// net/ipv4/ip_output.c

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;

	skb->protocol = htons(ETH_P_IP);

	return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
		       net, sk, skb, NULL, skb_dst(skb)->dev,
		       dst_output);  //需要精细控制,不能立即调用 okfn
}

// include/net/dst.h
/* Output packet to network from transport.  */
static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	return INDIRECT_CALL_INET(skb_dst(skb)->output,
				  ip6_output, ip_output,
				  net, sk, skb);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// netfilter.h
/**
 *	nf_hook - call a netfilter hook
 *
 *	Returns 1 if the hook has allowed the packet to pass.  The function
 *	okfn must be invoked by the caller in this case.  Any other return
 *	value indicates the packet has been consumed by the hook.
 */
static inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net,
			  struct sock *sk, struct sk_buff *skb,
			  struct net_device *indev, struct net_device *outdev,
			  int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
......
	if (hook_head) {
		struct nf_hook_state state;

		nf_hook_state_init(&state, hook, pf, indev, outdev,
				   sk, net, okfn);

		ret = nf_hook_slow(skb, &state, hook_head, 0);
	}
	rcu_read_unlock();

	return ret;
}    
/* 
nf_hook走 netfilter 钩子链,但返回一个值给调用者自己判断下一步  
1	ACCEPT — 包通过了所有钩子	调用者自己调用 okfn
0	STOLEN — 包已被消费	调用者什么都不做
负数	DROP — 包被丢弃	调用者返回错误
*/
static inline int
NF_HOOK_COND(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk,
	     struct sk_buff *skb, struct net_device *in, struct net_device *out,
	     int (*okfn)(struct net *, struct sock *, struct sk_buff *),
	     bool cond)
{
	int ret;

	if (!cond ||
	    ((ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn)) == 1))
		ret = okfn(net, sk, skb);
	return ret;
}

static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
	struct net_device *in, struct net_device *out,
	int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
	int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
	if (ret == 1)
		ret = okfn(net, sk, skb);
	return ret;
}

POSTROUTING

Hook File Function
NF_INET_POST_ROUTING net/ipv4/ip_output.c ip_output()
  • Works only for outgoing packets.
  • This is the last hook. After that, the packet is sent to the lower layers.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// net/ipv4/ip_output.c
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct net_device *dev = skb_dst(skb)->dev, *indev = skb->dev;

	IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT, skb->len);

	skb->dev = dev;
	skb->protocol = htons(ETH_P_IP);

	return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
			    net, sk, skb, indev, dev,
			    ip_finish_output,
			    !(IPCB(skb)->flags & IPSKB_REROUTED));
}

Register and Unregister

Netfilter Register Routine

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// net\netfilter\core.c
static struct list_head *nf_find_hook_list(struct net *net,
					   const struct nf_hook_ops *reg)
{
	struct list_head *hook_list = NULL;

	if (reg->pf != NFPROTO_NETDEV)
		hook_list = &net->nf.hooks[reg->pf][reg->hooknum];
	else if (reg->hooknum == NF_NETDEV_INGRESS) {
#ifdef CONFIG_NETFILTER_INGRESS
		if (reg->dev && dev_net(reg->dev) == net)
			hook_list = &reg->dev->nf_hooks_ingress;
#endif
	}
	return hook_list;
}
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
{
	struct list_head *hook_list;
	struct nf_hook_entry *entry;
	struct nf_hook_ops *elem;

	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
	if (!entry)
		return -ENOMEM;

	entry->orig_ops	= reg;
	entry->ops	= *reg;

	hook_list = nf_find_hook_list(net, reg);
	if (!hook_list) {
		kfree(entry);
		return -ENOENT;
	}

	mutex_lock(&nf_hook_mutex);
	list_for_each_entry(elem, hook_list, list) {
		if (reg->priority < elem->priority)
			break;
	}
	list_add_rcu(&entry->ops.list, elem->list.prev);//插入 
	mutex_unlock(&nf_hook_mutex);
#ifdef CONFIG_NETFILTER_INGRESS
	if (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
		net_inc_ingress_queue();
#endif
#ifdef HAVE_JUMP_LABEL
	static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);
#endif
	return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// include/net/net_namespace.h
struct net {
    // ... 其他字段 ...
    struct netns_nf nf;  // Netfilter 相关
    // ...
};

// include/net/netns/netfilter.h
struct netns_nf {
    // 核心钩子链表数组
    struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
    // ... 其他字段 ...
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// net\ipv4\netfilter\iptable_nat.c
static struct nf_hook_ops nf_nat_ipv4_ops[] __read_mostly = {
	/* Before packet filtering, change destination */
	{
		.hook		= iptable_nat_ipv4_in,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_PRE_ROUTING,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	/* After packet filtering, change source */
	{
		.hook		= iptable_nat_ipv4_out,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_POST_ROUTING,
		.priority	= NF_IP_PRI_NAT_SRC,
	},
	/* Before packet filtering, change destination */
	{
		.hook		= iptable_nat_ipv4_local_fn,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_OUT,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	/* After packet filtering, change source */
	{
		.hook		= iptable_nat_ipv4_fn,
		.pf		= NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_IN,
		.priority	= NF_IP_PRI_NAT_SRC,
	},
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
static int __init iptable_nat_init(void)
{
	int err;

	err = register_pernet_subsys(&iptable_nat_net_ops);
	if (err < 0)
		goto err1;

	err = nf_register_hooks(nf_nat_ipv4_ops, ARRAY_SIZE(nf_nat_ipv4_ops));
	if (err < 0)
		goto err2;
	return 0;

err2:
	unregister_pernet_subsys(&iptable_nat_net_ops);
err1:
	return err;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//net/netfilter/core.c
int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n)
{
	unsigned int i;
	int err = 0;

	for (i = 0; i < n; i++) {
		err = nf_register_hook(&reg[i]);
		if (err)
			goto err;
	}
	return err;

err:
	if (i > 0)
		nf_unregister_hooks(reg, i);
	return err;
}

int nf_register_hook(struct nf_hook_ops *reg)
{
	struct net *net, *last;
	int ret;

	rtnl_lock();
	for_each_net(net) {
		ret = nf_register_net_hook(net, reg);
		if (ret && ret != -ENOENT)
			goto rollback;
	}
	list_add_tail(&reg->list, &nf_hook_list);
	rtnl_unlock();

	return 0;
rollback:
	last = net;
	for_each_net(net) {
		if (net == last)
			break;
		nf_unregister_net_hook(net, reg);
	}
	rtnl_unlock();
	return ret;
}

Netfilter Unregister Routine

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//net\netfilter\core.c
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)
{
	struct list_head *hook_list;
	struct nf_hook_entry *entry;
	struct nf_hook_ops *elem;

	hook_list = nf_find_hook_list(net, reg);
	if (!hook_list)
		return;

	mutex_lock(&nf_hook_mutex);
	list_for_each_entry(elem, hook_list, list) {
		entry = container_of(elem, struct nf_hook_entry, ops);
		if (entry->orig_ops == reg) {
			list_del_rcu(&entry->ops.list);
			break;
		}
	}
	mutex_unlock(&nf_hook_mutex);
	if (&elem->list == hook_list) {
		WARN(1, "nf_unregister_net_hook: hook not found!\n");
		return;
	}
#ifdef CONFIG_NETFILTER_INGRESS
	if (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
		net_dec_ingress_queue();
#endif
#ifdef HAVE_JUMP_LABEL
	static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]);
#endif
	synchronize_net();
	nf_queue_nf_hook_drop(net, &entry->ops);
	/* other cpu might still process nfqueue verdict that used reg */
	synchronize_net();
	kfree(entry);
}

Register and Unregister user defined callback

linux-learn-demo-code/netfilter at main · charles-7777/linux-learn-demo-code

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计