文章目录
- 1. 前言
- 2. Netfilter 简介
- 2.1 Netfilter 的功能
- 2.2 Netfilter 示例
- 2.3 Netfilter 实现概览
- 2.3.1 Netfilter hook 的 注册 和 注销
- 2.3.2 Netfilter hook 的触发
- 2.3.2.1 NF_INET_PRE_ROUTING
- 2.3.2.2 NF_INET_LOCAL_IN
- 2.3.2.3 NF_INET_FORWARD
- 2.3.2.4 NF_INET_LOCAL_OUT
- 2.3.2.5 NF_INET_POST_ROUTING
- 3. Netfilter 的经典应用
- 4. 参考资料
1. 前言
2. Netfilter 简介
2.1 Netfilter 的功能
Netfilter 有如下所列 3 大功能:
1. 数据包过滤(Packet filtering)
负责根据规则对数据包进行过滤。
2. 网络地址转换(NAT,Network Address Translation)
负责转换网络数据包的 IP 地址。
NAT 是一个重要的协议,已经成为在IPv4地址耗尽的情况下保护全局地址空间的流行和必要工具。
3. 数据包篡改(Packet mangling)
负责修改数据包内容(实际上,NAT 是数据包篡改的一种,它修改源或目标 IP 地址)。
例如,可以修改 TCP SYN 数据包的最大段大小(MSS)值,以便允许在网络上传输大尺寸的数据包。
2.2 Netfilter 示例
本文给出一个 数据包过滤(Packet filtering)的 Netfilter 内核模块 示例,在 IPv4 协议栈的 NF_INET_LOCAL_IN hook 点,插入一个钩子函数 nf_test_in_hook(),对进入的网络包进行处理。示例 netfilter_kern_pf_test.c 代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
#define NIPQUAD_FMT "%u.%u.%u.%u"
static unsigned int nf_pf_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*))
{
struct ethhdr *eth_header;
struct iphdr *ip_header;
eth_header = (struct ethhdr *)(skb_mac_header(skb));
ip_header = (struct iphdr *)(skb_network_header(skb));
pr_info("dest MAC: %pM, source MAC: %pM, protocol: %x\n",
eth_header->h_dest, eth_header->h_source, eth_header->h_proto);
pr_info("src IP:'"NIPQUAD_FMT"', dst IP:'"NIPQUAD_FMT"' \n",
NIPQUAD(ip_header->saddr), NIPQUAD(ip_header->daddr));
return NF_ACCEPT;
}
static struct nf_hook_ops nf_pf_test_ops[] __read_mostly = {
{
.hook = (void *)nf_pf_test_in_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_FIRST,
},
};
static int __init netfilter_pf_test_init(void)
{
int ret;
ret = nf_register_net_hooks(&init_net, nf_pf_test_ops, ARRAY_SIZE(nf_pf_test_ops));
if (ret < 0) {
pr_err("register nf packet-filter hook fail\n");
return ret;
}
pr_info("register packet-filter test hook\n");
return 0;
}
static void __exit netfilter_pf_test_exit(void)
{
pr_info("unregister nf packet-filter test hook\n");
nf_unregister_net_hooks(&init_net, nf_pf_test_ops, ARRAY_SIZE(nf_pf_test_ops));
}
module_init(netfilter_pf_test_init);
module_exit(netfilter_pf_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Netfliter packet-filter test");
编译 Makefile 如下:
ifneq ($(KERNELRELEASE),)
obj-m := netfilter_kern_pf_test.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions .cache.mk modules.order Module.symvers
在 Ubuntu 16.04 系统下编译、安装、运行:
$ make
$ sudo insmod netfilter_kern_pf_test.ko
$ dmesg

$ sudo rmmod netfilter_kern_pf_test
[ 4606.344200] unregister nf packet-filter test hook
2.3 Netfilter 实现概览
Netfilter 功能,首先内核要开启 CONFIG_NETFILTER 配置。在内核代码 NF_HOOK 关键字进行搜索,就可以找到所有相关的 hook 点。本文只以 IPv4 数据收、发流程中的 5 个 Netfilter hook 点做简单分析。
2.3.1 Netfilter hook 的 注册 和 注销
内核系统如下接口来 注册 和 注销 Netfilter hook 接口:
/* include/linux/netfilter.h */
/* Function to register/unregister hook points. */
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *ops);
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *ops);
int nf_register_net_hooks(struct net *net, const struct nf_hook_ops *reg,
unsigned int n);
void nf_unregister_net_hooks(struct net *net, const struct nf_hook_ops *reg,
unsigned int n);
static struct nf_hook_entries __rcu **nf_hook_entry_head(struct net *net, const struct nf_hook_ops *reg)
{
if (reg->pf != NFPROTO_NETDEV)
return net->nf.hooks[reg->pf]+reg->hooknum;
...
}
static struct nf_hook_entries *
nf_hook_entries_grow(const struct nf_hook_entries *old,
const struct nf_hook_ops *reg)
{
...
new = allocate_hook_entries_size(alloc_entries);
...
if (!inserted) {
new_ops[nhooks] = (void *)reg;
new->hooks[nhooks].hook = reg->hook; // Netlink hook 回调
new->hooks[nhooks].priv = reg->priv;
}
return new;
}
/* 注册 netfilter hook @reg 到 网络命名空间 @net */
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
{
struct nf_hook_entries *p, *new_hooks;
struct nf_hook_entries __rcu **pp;
...
/*
* 定位到 网络命名空间 @net 中 协议 / hook 类型
* (@reg->pf, @reg->hook) 对应的 Netfilter hook slot
*/
pp = nf_hook_entry_head(net, reg);
if (!pp)
return -EINVAL;
mutex_lock(&nf_hook_mutex);
p = nf_entry_dereference(*pp);
new_hooks = nf_hook_entries_grow(p, reg); /* 新建一个 Netfilter hook 对象 */
/*
* 将新建的 Netfilter hook 对象放置到
* 网络命名空间 @net 对应的 Netfilter hook slot
*/
if (!IS_ERR(new_hooks))
rcu_assign_pointer(*pp, new_hooks);
mutex_unlock(&nf_hook_mutex);
...
synchronize_net();
BUG_ON(p == new_hooks);
kvfree(p);
return 0;
}
2.3.2 Netfilter hook 的触发
先看一张图:

图中粗体字的 PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING 分别对应数据收、发过程的以下 5 个 Netfilter hook 点:
/* include/uapi/linux/netfilter.h */
enum nf_inet_hooks {
NF_INET_PRE_ROUTING, // PREROUTING
NF_INET_LOCAL_IN, // INPUT
NF_INET_FORWARD, // FORWARD
NF_INET_LOCAL_OUT, // OUTPUT
NF_INET_POST_ROUTING, // POSTROUTING
NF_INET_NUMHOOKS
};
2.3.2.1 NF_INET_PRE_ROUTING
/* net/ipv4/ip_input.c */
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);
}
/* include/linux/netfilter.h */
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;
}
/**
* 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 *))
{
struct nf_hook_entries *hook_head;
int ret = 1;
...
rcu_read_lock();
hook_head = rcu_dereference(net->nf.hooks[pf][hook]); // 网络命名空间 @net 的 Netfilter hook 表
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;
}
/* net/netfilter/core.c */
/* Returns 1 if okfn() needs to be executed by the caller,
* -EPERM for NF_DROP, 0 otherwise. Caller must hold rcu_read_lock. */
int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
const struct nf_hook_entries *e, unsigned int s)
{
unsigned int verdict;
int ret;
for (; s < e->num_hook_entries; s++) {
// 调用 nf_register_net_hooks() 系列接口注册的 hook, 如前面
// 示例代码中的 nf_pf_test_in_hook() 接口。
verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
switch (verdict & NF_VERDICT_MASK) {
case NF_ACCEPT: // 接受包
break;
case NF_DROP: // 丢弃包
kfree_skb(skb);
ret = NF_DROP_GETERR(verdict);
if (ret == 0)
ret = -EPERM;
return ret;
case NF_QUEUE:
ret = nf_queue(skb, state, e, s, verdict);
if (ret == 1)
continue;
return ret;
default:
/* Implicit handling for NF_STOLEN, as well as any other
* non conventional verdicts.
*/
return 0;
}
}
}
/* 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);
}
2.3.2.2 NF_INET_LOCAL_IN
/* 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);
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
net, NULL, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
NF_HOOK() 在 2.3.1 已经分析过,在此不再赘述。
2.3.2.3 NF_INET_FORWARD
/* net/ipv4/ip_forward.c */
int ip_forward(struct sk_buff *skb)
{
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,
net, NULL, skb, skb->dev, rt->dst.dev,
ip_forward_finish);
...
}
NF_HOOK() 在 2.3.1 已经分析过,在此不再赘述。
2.3.2.4 NF_INET_LOCAL_OUT
/* net/ipv4/ip_output.c */
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
...
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
net, sk, skb, NULL, skb_dst(skb)->dev,
dst_output);
}
NF_HOOK() 在 2.3.1 已经分析过,在此不再赘述。
2.3.2.5 NF_INET_POST_ROUTING
/* net/ipv4/ip_output.c */
int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
...
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
net, sk, skb, NULL, skb->dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
...
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
net, sk, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
3. Netfilter 的经典应用
Netfilter 的经典应用示范是 iptables,IPv4协议下,iptables 通过 setsockopt() 接口和内核模块 net/ipv4/netfilter/ip_tables.c 进行交互,感兴趣的读者可自行阅读相关源码。
看两个简单的 iptables 操作的示例:
# Drop all incoming packets from address 192.168.12.8
iptables -I INPUT -s 192.168.12.8 -j DROP
# 拒绝 ping 任何主机:将所有外发的 ICMP 包丢弃
iptables -A OUTPUT -p icmp -j DROP
4. 参考资料
[1] Netfilter’s flowtable infrastructure
[2] Nftables - Packet flow and Netfilter hooks in detail
[3] A Deep Dive into Iptables and Netfilter Architecture
[4] [译] 深入理解 iptables 和 netfilter 架构
[5] 一图带你看懂 Iptables 底层架构 Netfilter
[6] 理解 Linux 下的 Netfilter/iptables
[7] 走进Linux内核之Netfilter框架
[8] 从零开始基于Netfilter编写一个Linux防火墙



















