netfilter 框架是 Linux 网络子系统里的一个核心模块,iptables 就是基于 netfilter 框架实现的一个网络包处理工具。

netfilter 框架原理介绍

在内核里,每个网络命名空间(network namespace)都给 ipv4ipv6arpbridgedecnet 等维持一个 netfilter 钩子列表(struct nf_hook_entries *hooks[5]), struct nf_hook_entries 里维护了 struct nf_hook_entry hooks[]

iptables 的四表五链来源于 netfilter 的二维数组钩子:

钩子序号 钩子列表
NF_INET_PRE_ROUTING struct nf_hook_entry hooks[]
NF_INET_LOCAL_IN struct nf_hook_entry hooks[]
NF_INET_FORWARD struct nf_hook_entry hooks[]
NF_INET_LOCAL_OUT struct nf_hook_entry hooks[]
NF_INET_POST_ROUTING struct nf_hook_entry hooks[]

netfilter 模块初始化

 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
// net/netfilter/core.c

static void __net_init
__netfilter_net_init(struct nf_hook_entries __rcu **e, int max)
{
	int h;

	for (h = 0; h < max; h++)
		RCU_INIT_POINTER(e[h], NULL);
}

static int __net_init netfilter_net_init(struct net *net)
{
	// 初始化 ipv4 钩子数组
	__netfilter_net_init(net->nf.hooks_ipv4, ARRAY_SIZE(net->nf.hooks_ipv4));
	// 初始化 ipv6 钩子数组
	__netfilter_net_init(net->nf.hooks_ipv6, ARRAY_SIZE(net->nf.hooks_ipv6));
	// 初始化 arp 钩子数组
	__netfilter_net_init(net->nf.hooks_arp, ARRAY_SIZE(net->nf.hooks_arp));
	// 初始化 bridge 钩子数组
	__netfilter_net_init(net->nf.hooks_bridge, ARRAY_SIZE(net->nf.hooks_bridge));
	// 初始化 decnet 钩子数组
	__netfilter_net_init(net->nf.hooks_decnet, ARRAY_SIZE(net->nf.hooks_decnet));

	...

	return 0;
}

netfilter 钩子执行过程

netfilter 钩子的执行过程并不复杂,主要是遍历执行钩子序号对应的钩子列表里的钩子函数。它的函数调用栈如下:

1
2
3
4
|-->NF_HOOK()                           // include/linux/netfilter.h
    |-->nf_hook()
        |-->nf_hook_slow()              // net/netfilter/core.c
            |-->nf_hook_entry_hookfn()  // include/linux/netfilter.h

以下为详细的函数调用讲解:

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

/*
 * IP 层入口
 */
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);
	...
	return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, // 执行 ipv4 PRE_ROUTING 列表里的钩子
			   net, NULL, skb, dev, NULL,
			   ip_rcv_finish);
}
 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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// include/linux/netfilter.h

static inline int
NF_HOOK(...)
{
	int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
	if (ret == 1)
		ret = okfn(net, sk, skb);
	return ret;
}

/**
 *	nf_hook - 调用 netfilter 钩子
 */
static inline int nf_hook(...)
{
	struct nf_hook_entries *hook_head = NULL;
	int ret = 1;
	...
	switch (pf) { // 在此处区分 ipv4, ipv6, arp, bridge, decnet,并获取指定钩子序号的钩子列表
	case NFPROTO_IPV4:
		hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
		break;
	case NFPROTO_IPV6:
		hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
		break;
	case NFPROTO_ARP:
		hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
		break;
	case NFPROTO_BRIDGE:
		hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
		break;
	case NFPROTO_DECNET:
		hook_head = rcu_dereference(net->nf.hooks_decnet[hook]);
		break;
	default:
		WARN_ON_ONCE(1);
		break;
	}

	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); // 执行列表里的钩子
	}

	return ret;
}

// net/netfilter/core.c

int nf_hook_slow(...)
{
	unsigned int verdict;
	int ret;

	for (; s < e->num_hook_entries; s++) { // 遍历钩子列表
		verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state); // 执行钩子函数
		switch (verdict & NF_VERDICT_MASK) {
		case NF_ACCEPT:    // -j ACCEPT
			break;
		case NF_DROP:      // -j DROP
			kfree_skb(skb);
			...
			return ret;
		case NF_QUEUE:     // -j NFQUEUE
			ret = nf_queue(skb, state, s, verdict); // NFQUEUE 的处理入口
			if (ret == 1)
				continue;
			return ret;
		default:
			/* Implicit handling for NF_STOLEN, as well as any other
			 * non conventional verdicts.
			 */
			return 0;
		}
	}

	return 1;
}

// include/linux/netfilter.h

static inline int
nf_hook_entry_hookfn(const struct nf_hook_entry *entry, struct sk_buff *skb,
			 struct nf_hook_state *state)
{
	return entry->hook(entry->priv, skb, state);
}

netfilter 的网络包处理流程

下图比较详细地描述了 iptables 和 ebtables 的规则执行时机:

netfilter packet flow

图片来源:wikimedia

内核源代码查看工具

总结

带着问题阅读内核源代码,是入门内核学习的有效办法。

掌握了阅读内核源代码这一能力,就不再惧怕 Linux 网络问题;因为遇到问题就阅读网络子系统的源代码嘛。