概述
Netfilter为多种网络协议(IPv4、IPv6、ARP等)各提供了一套钩子函数。
在IPv4中定义了5个钩子函数,这些钩子函数在数据包流经协议栈的5个关键点被调用。
这就像有5个钓鱼台,在每个钓鱼台放了一个鱼钩(钩子函数),把经过的数据包钓上来,
然后根据自定义的规则,来决定数据包的命运:
可以原封不动的放回IPv4协议,继续向上层递交;可以进行修改,再放回IPv4协议;也可以直接丢弃。
Netfilter主要采用连接跟踪(Connection Tracking)、包过滤(Packet Filtering)、地址转换(NAT)、包处理
(Packet Mangling)四种技术。
(1) IP层的5个钓鱼台
- 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
- };
支持的协议类型:
- enum {
- NFPROTO_UNSPEC = 0,
- NFPROTO_IPV4 = 2,
- NFPROTO_ARP = 3,
- NFPROTO_BRIDGE = 7,
- NFPROTO_IPV6 = 10,
- NFPROTO_DECNET = 12,
- NFPROTO_NUMPROTO,
- };
(2) 钩子函数
- typedef unsigned int nf_hookfn(unsigned int hooknum,
- struct sk_buff *skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn) (struct sk_buff *));
- /* 处理函数返回值 */
- #define NF_DROP 0 /* drop the packet, don't continue traversal */
- #define NF_ACCEPT 1 /* continue traversal as normal */
- #define NF_STOLEN 2 /* I've taken over the packet, don't continue traversal */
- #define NF_QUEUE 3 /* queue the packet (usually for userspace handling) */
- #define NF_REPEAT 4 /* call this hook again */
- #define NF_STOP 5
- #define NF_MAX_VERDICT NF_STOP
(3) Netfilter实体
在使用Netfilter时,需要定义一个nf_hook_ops实例。
- struct nf_hook_ops {
- struct list_head list;
- /* User fills in from here down. */
- nf_hookfn *hook; /* 要注册的钩子函数 */
- struct module *owner;
- u_int8_t pf; /* 协议类型 */
- unsigned int hooknum; /* 哪个钓鱼台 */
- /* Hooks are ordered in asending priority. */
- int priority; /* 数值越小,优先级越高 */
- };
- typedef __u8 u_int8_t;
(4) 注册与注销
- /* Functions to register/unregister hook points. */
- int nf_register_hook(struct nf_hook_ops *reg);
- void nf_unregister_hook(struct nf_hook_ops *reg);
实现
Netfilter定义了一个全局链表:
- struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
- EXPORT_SYMBOL(nf_hooks);
- static DEFINE_MUTEX(nf_hook_mutex);
(1) 注册函数
注册函数会把nf_hook_ops放入nf_hooks相应的位置中。
- int nf_register_hook(struct nf_hook_ops *reg)
- {
- struct nf_hook_ops *elem;
- int err;
- err = mutex_lock_interruptible(&nf_hook_mutex);
- if (err < 0)
- return err;
- list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
- if (reg->priority < elem->priority)
- break;
- }
- list_add_rcu(®->list, elem->list.prev); /* 把netfilter实例添加到队列中 */
- mutex_unlock(&nf_hook_mutex);
- return 0;
- }
(2) 注销函数
- void nf_unregister_hook(struct nf_hook_ops *reg)
- {
- mutex_lock(&nf_hook_mutex);
- list_del_rcu(®->list); /* 把netfilter实例从队列中删除 */
- mutex_unlock(&nf_hook_mutex);
- synchronize_net();
- }
(3) 内核接口
内核的Netfilter钩子函数调用:
NF_HOOK
|--> NF_HOOK_THRESH
|--> nf_hook_thresh
|--> nf_hook_slow
|--> nf_iterate
- static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb,
- struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *))
- {
- /* INT_MIN表示要调用钓鱼台的所有钩子函数 */
- return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN);
- }
- static inline int NF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct sk_buff *skb,
- struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *), int thresh)
- {
- int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
- if (ret == 1)
- ret = okfn(skb); /* 如果skb没被处理掉,调用此函数 */
- return ret;
- }
- /**
- * nf_hook_thresh - 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_thresh(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
- struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int thresh)
- {
- #ifndef CONFIG_NETFILTER_DEBUG
- /* 如果协议pf的hook点上没有已注册的nf_hook_ops实例,直接返回1 */
- if (list_empty(&nf_hooks[pf][hook]))
- return 1;
- #endif
- return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
- }
- /* Returns 1 if okfn() needs to be executed by the caller, -EPERM for NF_DROP, 0 otherwise. */
- int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *indev,
- struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh)
- {
- struct list_head *elem;
- unsigned int verdict;
- int ret = 0;
- /* We may already have this, but read-locks nest anyway */
- rcu_read_lock();
- elem = &nf_hooks[pf][hook];
- next_hook:
- verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev, outdev, &elem, okfn, hook_thresh);
- if (verdict == NF_ACCEPT || verdict == NF_STOP) {
- ret = 1;
- } else if (verdict == NF_DROP) {
- kfree_skb(skb);
- ret = -EPERM;
- } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
- if (! nf_queue(skb, elem, ph, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS))
- goto next_hook;
- }
- rcu_read_unlock();
- return ret;
- }
- unsigned int nf_iterate(struct list_head *head, struct sk_buff *skb, unsigned int hook,
- const struct net_device *indev, const struct net_device *outdev, struct list_head **i,
- int (*okfn)(struct sk_buff *), int hook_thresh)
- {
- unsigned int verdict;
- /*
- * The caller must not block between calls to this function because of risk of
- * continuing from deleted element.
- */
- list_for_each_continue_rcu(*i, head) {
- struct nf_hook_ops *elem = (struct nf_hook_ops *) *i;
- /* 优先级>=hook_thresh的都会被执行 */
- if (hook_thresh > elem_priority)
- continue;
- verdict = elem->hook(hook, skb, indev, outdev, okfn); /* 已注册的执行函数 */
- if (verdict != NF_ACCEPT) {
- #ifdef CONFIG_NETFILTER_DEBUG
- if (unlikely((verdict & NF_VERDICT_MASK) > NF_MAX_VERDICT)) {
- NFDEBUG("Evil return from %p(%u).\n", elem->hook, hook);
- continue;
- }
- #endif
- if (verdict != NF_REPEAT)
- return verdict;
- *i = (*i)->prev;
- }
- }
- return NF_ACCEPT;
- }
使用
以下是一个简单的模块,加载到一个HTTP服务器上。
通过在PRE_ROUTING处注册my_hookfn,改变接收数据包的源IP为8.8.8.8(Google DNS server)。
当客户端向服务器发送一个请求时,肯定收不到服务器的响应:)
- #include <linux/netfilter.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/netfilter_ipv4.h>
- #include <linux/ip.h>
- #include <linux/inet.h>
- /**
- * Hook function to be called.
- * We modify the packet's src IP.
- */
- unsigned int my_hookfn(unsigned int hooknum,
- struct sk_buff *skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- struct iphdr *iph;
- iph = ip_hdr(skb);
- /* log the original src IP */
- printk(KERN_INFO"src IP %pI4\n", &iph->saddr);
- /* modify the packet's src IP */
- iph->saddr = in_aton("8.8.8.8");
- return NF_ACCEPT;
- }
- /* A netfilter instance to use */
- static struct nf_hook_ops nfho = {
- .hook = my_hookfn,
- .pf = PF_INET,
- .hooknum = NF_INET_PRE_ROUTING,
- .priority = NF_IP_PRI_FIRST,
- .owner = THIS_MODULE,
- };
- static int __init sknf_init(void)
- {
- if (nf_register_hook(&nfho)) {
- printk(KERN_ERR"nf_register_hook() failed\n");
- return -1;
- }
- return 0;
- }
- static void __exit sknf_exit(void)
- {
- nf_unregister_hook(&nfho);
- }
- module_init(sknf_init);
- module_exit(sknf_exit);
- MODULE_AUTHOR("zhangsk");
- MODULE_LICENSE("GPL");
一.netfilter
最近因为工作的原因,要用到netfilter进行IP包的解析和过滤,所以对netfilter进行了一些了解,目前网上比较容易搜索到的应该是这两篇文章http://alexanderlaw.blog.hexun.com/8960896_d.html,《Linux netfilter 源码解析》,
https://www.ibm.com/developerworks/cn/linux/l-ntflt/,《Linux Netfilter实现机制和扩展技术》
这两篇文章对于Linux netfilter进行了一个详细的介绍,第一篇还对其源码进行了剖析,至于其具体实现,网上也能够找到一些例子,但是问题在于,那些例子大部分都是基于老版本的linux kernel,而在2.6以上的linux内核中,关于netfilter的变动是巨大的,整个变化主要体现在skubuff.h这个头文件中,2.6以上的linux内核抛弃了以前的那种union的方式,老版本中的nh不再存在。至于更加详细的内容变化,可以直接去Google,我就不再赘述了。
另外一个需要注意的是,很多网上的例子程序给的接口是这样的
- static unsigned int sample(
- unsigned int hooknum,
- struct sk_buff ** skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn) (struct sk_buff *))
- {
- return NF_ACCEPT;
- }
而实际上使用的接口应该是
- static unsigned int sample(
- unsigned int hooknum,
- struct sk_buff * skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn) (struct sk_buff *))
- {
- return NF_ACCEPT;
- }
请注意,不是sk_buff **,而是sk_buf*,这个问题我没弄清楚是别人的代码贴的有问题,还是因为内核版本不同造成的,但是请大家注意一下。
二.示例源码
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/types.h>
- #include <linux/netdevice.h>
- #include <linux/skbuff.h>
- #include <linux/netfilter_ipv4.h>
- #include <linux/inet.h>
- #include <linux/in.h>
- #include <linux/ip.h>
- MODULE_LICENSE("GPL");
- #define NIPQUAD(addr) \
- ((unsigned char *)&addr)[0], \
- ((unsigned char *)&addr)[1], \
- ((unsigned char *)&addr)[2], \
- ((unsigned char *)&addr)[3]
- static unsigned int sample(
- unsigned int hooknum,
- struct sk_buff * skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn) (struct sk_buff *))
- {
- __be32 sip,dip;
- if(skb){
- struct sk_buff *sb = NULL;
- sb = skb;
- struct iphdr *iph;
- iph = ip_hdr(sb);
- sip = iph->saddr;
- dip = iph->daddr;
- printk("Packet for source address: %d.%d.%d.%d\n destination address: %d.%d.%d.%d\n ", NIPQUAD(sip), NIPQUAD(dip));
- }
- return NF_ACCEPT;
- }
- struct nf_hook_ops sample_ops = {
- .list = {NULL,NULL},
- .hook = sample,
- .pf = PF_INET,
- .hooknum = NF_INET_PRE_ROUTING,
- .priority = NF_IP_PRI_FILTER+2
- };
- static int __init sample_init(void) {
- nf_register_hook(&sample_ops);
- return 0;
- }
- static void __exit sample_exit(void) {
- nf_unregister_hook(&sample_ops);
- }
- module_init(sample_init);
- module_exit(sample_exit);
- MODULE_AUTHOR("chenkangrui");
- MODULE_DESCRIPTION("sample");
netfilter模块所linux系统的一部分,所以为netfilter添加hook,其实所linux内核编程,所以需要用到linux内核态的头文件。
根据sample_ops可以看到,我在NF_INET_PRE_ROUTING(老版本这个变量是NF_IP_PRE_ROUTING,哎,真是DT啊)这个位置添加了一个hook,处理函数是sample(sample的功能是将所有能够截获的IP包的源IP和目标IP给打印出来)。
另外就是,NIPQUAD这个宏在新版本中被取消了,需要自己手动定义下。
三.编译和加载
我是在ubuntu上进行编译的
- obj-m := sample.o
- KERNELBUILD :=/lib/modules/$(shell uname -r)/build
- default:
- make -C $(KERNELBUILD) M=$(shell pwd) modules
- clean:
- rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions
这是我使用的Makefile,需要注意的是,因为编译内核模块需要使用内核中的头文件,所以需要
sudo make
编译完成之后,将.ko文件加载到内核中就可以了
sudo insmod ./sample.ko
内核模块的输出printk不是直接打印到系统终端的,而是在系统日志中
你可以使用
dmesg
或者直接去/var/log/目录下查看syslog文件