文章目录
- 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防火墙