写在前面:
对于linuxnetfilter机制,一直感觉比较难,总没有开始阅读这部分带,但为了提升与扩展自己的知识面,特写下此系列的学习文档,以推动自己不断前行,不断学习。
基于linux2.6.21
一、netfilter介绍
Linuxnetfilter就是借助一整套的hook函数的管理机制,实现数据包在三层以上的过滤、地址转换(SNAT、DNAT)、基于协议的连接跟踪。我们所说的内核的netfilter,应该包括二层数据的filter操作,以及对三层及三层以上数据的filter等操作。
只不过二层的filter实现与三层及三层也上的filter实现有所不同。其中二层的filter与应用层程序ebtables结合使用,而三层及以上的filter结合iptables使用。但是二层filter与三层filter使用的都是统一的hook机制。
下面我们就在分析三层及三层以上的netfilter之前,分析一下整体的hook机制及工作流程
linux抽象出整体的hook架构,通过在以下几个数据流经点添加hook机制,为实现netfilter提供基础框架:
NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING。
这五个点在数据的流经方向如下图:
二、数据结构
1、nf_hook_ops
Hook点回调函数相关的数据结构,其是nfhook机制的重要数据结构,各成员的意义如下:
structnf_hook_ops
{
structlist_headlist;//链表结构,实现hook函数链接
/*Userfillsinfromheredown.*/
nf_hookfn*hook;//hook处理函数
structmodule*owner;//模块所属
intpf;//协议号
inthooknum;//hook点
/*Hooksareorderedinascendingpriority.*/
intpriority;//优先级
};
2、nf_hookfn
Hook回调函数的定义
hooknum:hook点
skb:数据包
in:数据包入口设备
out:数据包出口设备
okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,
调用该函数
typedefunsignedintnf_hookfn(unsignedinthooknum,
structsk_buff**skb,
conststructnet_device*in,
conststructnet_device*out,
int(*okfn)(structsk_buff*));
3、nf_hooks
nf_hooks是一个二维数组,该二维数组的每一个成员均是一个链表。每一个链表的所有节点都代表一个协议在一个hook点上的所有的hook成员其中协议有32个,而hook点有8个,目前使用的是5个,分别为NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING。而在同一个链表上,节点是按照优先级顺序排列的,优先级值最小的hook变量存在链表的最前面,优先执行。
structlist_headnf_hooks[NPROTO][NF_MAX_HOOKS];
三、HOOK机制的注册、执行相关的函数
1、hook注册函数nf_register_hook
功能:将一个新的hook结构添加nf_hooks数组的相应的成员链表中
A根据协议号、hook点,确定链表
B根据优先级将该hook结构添加到链表的合适位置
注意:访问nf_hooks,需要添加自旋锁
intnf_register_hook(structnf_hook_ops*reg)
{
structlist_head*i;
spin_lock_bh(&nf_hook_lock);
list_for_each(i,&nf_hooks[reg->pf][reg->hooknum]){
if(reg->priority<((structnf_hook_ops*)i)->priority)
break;
}
list_add_rcu(?->list,i->prev);
spin_unlock_bh(&nf_hook_lock);
synchronize_net();
return0;
}
2、Hook注销函数nf_unregister_hook
功能:将一个hook结构从nf_hooks数组的相应的成员链表中删除
删除操作就是平常的删除链表成员的操作,比较简单
voidnf_unregister_hook(structnf_hook_ops*reg)
{
spin_lock_bh(&nf_hook_lock);
list_del_rcu(?->list);
spin_unlock_bh(&nf_hook_lock);
synchronize_net();
}
3、hook执行函数
目前调用执行hook回调函数的为宏NF_HOOK
NF_HOOK
功能:执行hook回调函数
该宏调用NF_HOOK_THRESH来实现具体功能
#defineNF_HOOK(pf,hook,skb,indev,outdev,okfn)\
NF_HOOK_THRESH(pf,hook,skb,indev,outdev,okfn,INT_MIN)
NF_HOOK_THRESH
功能:执行hook回调函数
相比于NF_HOOK,该宏增加了一个变量thresh,thresh是变量hook回调函数的优先级
A该宏调用nf_hook_thresh来实现遍历hook回调函数并执行操作后返回执行结果
B若返回值为1,则继续调用回调函数okfn进行后续操作。
#defineNF_HOOK_THRESH(pf,hook,skb,indev,outdev,okfn,thresh) \
({int__ret; \
if((__ret=nf_hook_thresh(pf,hook,&(skb),indev,outdev,okfn,thresh,1))==1)\
__ret=(okfn)(skb); \
__ret;})
NF_HOOK_COND:
与NF_HOOK_THRESH相比,将thresh值设置了默认值,而增加了cond变量,这两个宏最终都会调用函数nf_hook_thresh
#defineNF_HOOK_COND(pf,hook,skb,indev,outdev,okfn,cond) \
({int__ret; \
if((__ret=nf_hook_thresh(pf,hook,&(skb),indev,outdev,okfn,INT_MIN,cond))==1)\
__ret=(okfn)(skb); \
__ret;})
nf_hook_slow:
这个函数才是真正干活的函数,真正遍历hook链表并执行hook回调函数。
pf:协议号
hook:hook点
pskb:数据包
indev:数据包入口函数
outdev:数据包出口函数
okfn:回调函数(此处不执行)
hook_thresh:起始优先级,只执行该hook点上优先级大于该值所有hook函数
功能:遍历协议号为pf,hook点为hook的链表,找到所有优先级大于或等于
hook_thresh的所有hook结构,执行每一个hook结构的hook回调函数。
若调用nf_iterate的返回值是NF_DROP,则释放skb,且返回错误
若返回值为NF_ACCEPT、NF_STOP,则返回1表示允许数据包继续前行
若是NF_QUEUE,则将数据方式netfilter的队列中,数据包可以从内核传递到
用户层处理,并将处理结果返回。
intnf_hook_slow(intpf,unsignedinthook,structsk_buff**pskb,
structnet_device*indev,
structnet_device*outdev,
int(*okfn)(structsk_buff*),
inthook_thresh)
{
structlist_head*elem;
unsignedintverdict;
intret=0;
/*Wemayalreadyhavethis,butread-locksnestanyway*/
rcu_read_lock();
elem=&nf_hooks[pf][hook];
next_hook:
verdict=nf_iterate(&nf_hooks[pf][hook],pskb,hook,indev,
outdev,&elem,okfn,hook_thresh);
if(verdict==NF_ACCEPT||verdict==NF_STOP){
ret=1;
gotounlock;
}elseif(verdict==NF_DROP){
kfree_skb(*pskb);
ret=-EPERM;
}elseif((verdict&NF_VERDICT_MASK)==NF_QUEUE){
NFDEBUG("nf_hook:Verdict=QUEUE.\n");
/*nf_queue是netfilter的基本机制–队列模型,
可以经内核数据包递交到用户层处理,并根据用户态的处理结果,对数据包进行相应的操作*/
if(!nf_queue(pskb,elem,pf,hook,indev,outdev,okfn,
verdict>>NF_VERDICT_BITS))
gotonext_hook;
}
unlock:
rcu_read_unlock();
returnret;
}
上面就是HOOK机制涉及到的主要的函数,HOOK机制还是比较好理解的,就是这样的机制作为基石,为netfilter的实现提供了坚实的基础,使netfilter能够实现很强大的功能。
四、实践
下面主要是通过上面接收的hook注册函数,实现向内核中相应的hook点注册hook回调函数,结合icmp数据包的格式,实现对icmp数据包的处理。
icmp_reply_filter.c
主要是数据接收方向的NF_IP_LOCAL_IN点注册回调函数,该回调函数对接收到的icmpreply报文,将序列号是9的倍数的reply报文丢弃掉
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/ioctl.h>
#include<linux/version.h>
#include<linux/skbuff.h>
#include<linux/netfilter.h>
#include<linux/netfilter_ipv4.h>
#include<linux/moduleparam.h>
#include<linux/netfilter_ipv4/ip_tables.h>
#include<linux/icmp.h>
#include<net/ip.h>
staticunsignedint
icmp_reply_hook_func(unsignedinthook,
structsk_buff**skb,
conststructnet_device*in,
conststructnet_device*out,
int(*okfn)(structsk_buff*))
{
conststructiphdr*iph;
structicmphdr*icmph;
if((*skb)->len<sizeof(structiphdr)||
ip_hdrlen(*skb)<sizeof(structiphdr))
returnNF_ACCEPT;
iph=ip_hdr(*skb);
icmph=(structicmphdr*)(iph+1);
if(iph->protocol==1)
{
if(icmph->type==0)
{
if((icmph->un.echo.sequence)%(0x9)==0)
{
printk("—-drop%x—\n",icmph->un.echo.sequence);
returnNF_DROP;
}
}
}
returnNF_ACCEPT;
}
staticstructnf_hook_ops__read_mostlyicmp_reply_hook=
{
.hook=icmp_reply_hook_func,
.owner=THIS_MODULE,
.pf=PF_INET,
.hooknum=NF_IP_LOCAL_IN,
.priority=NF_IP_PRI_FIRST,
};
staticint__initicmp_reply_hook_init()
{
printk(KERN_INFO"—init—\n");
returnnf_register_hook(&icmp_reply_hook);
}
staticvoid__exiticmp_reply_hook_exit(void)
{
printk(KERN_INFO"—exit—\n");
nf_unregister_hook(&icmp_reply_hook);
}
module_init(icmp_reply_hook_init);
module_exit(icmp_reply_hook_exit);
MODULE_AUTHOR("licky");
MODULE_DESCRIPTION("icmp_reply_hook");
MODULE_LICENSE("GPL");
icmp_request_filter.c
主要是数据发送方向的NF_IP_LOCAL_OUT点注册回调函数,该回调函数对发送的icmprequest报文,将序列号是5的倍数的request报文丢弃掉。
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/ioctl.h>
#include<linux/version.h>
#include<linux/skbuff.h>
#include<linux/netfilter.h>
#include<linux/netfilter_ipv4.h>
#include<linux/moduleparam.h>
#include<linux/netfilter_ipv4/ip_tables.h>
#include<linux/icmp.h>
#include<net/ip.h>
staticunsignedint
icmp_request_hook_func(unsignedinthook,
structsk_buff**skb,
conststructnet_device*in,
conststructnet_device*out,
int(*okfn)(structsk_buff*))
{
conststructiphdr*iph;
structicmphdr*icmph;
if((*skb)->len<sizeof(structiphdr)||
ip_hdrlen(*skb)<sizeof(structiphdr))
returnNF_ACCEPT;
iph=ip_hdr(*skb);
icmph=(structicmphdr*)(iph+1);
if(iph->protocol==1)
{
if(icmph->type==8)
{
if((icmph->un.echo.sequence)%5==0)
{
printk("—-drop—\n");
returnNF_DROP;
}
}
}
returnNF_ACCEPT;
}
staticstructnf_hook_ops__read_mostlyicmp_request_hook=
{
.hook=icmp_request_hook_func,
.owner=THIS_MODULE,
.pf=PF_INET,
.hooknum=NF_IP_LOCAL_OUT,
.priority=NF_IP_PRI_FIRST,
};
staticint__initicmp_request_init()
{
printk(KERN_INFO"—init—\n");
returnnf_register_hook(&icmp_request_hook);
}
staticvoid__exiticmp_request_exit(void)
{
printk(KERN_INFO"—exit—\n");
nf_unregister_hook(&icmp_request_hook);
}
module_init(icmp_request_init);
module_exit(icmp_request_exit);
MODULE_AUTHOR("licky");
MODULE_DESCRIPTION("icm_request_hook");
MODULE_LICENSE("GPL");
MAKEFILE:
KVERSION=$(shelluname-r)
obj-m+=icmp_reply_filter.o
obj-m+=icmp_request_filter.o
build:kernel_modules
kernel_modules:
$(MAKE)-C/lib/modules/$(KVERSION)/buildSUBDIRS=$(PWD)modules
clean:
$(MAKE)-C/lib/modules/$(KVERSION)/buildM=$(CURDIR)clean
小结:本节主要是对HOOK机制进行分析,还没有具体涉及到netfilter的具体内容,三层netfilter机制也是借助该hook机制以及iptables,实现三层及三层以上数据的防火墙机制。
然后继续努力,把让自己跌倒的石头搬掉或绕过去,不就解决问题了吗