Linux: Netfilter 简介

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防火墙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值