Linux netfilter 笔记之 一 NF_HOOK分析 写在前面: 对于linux netfilter机制,是怀着敬畏的心情开始代码学习的,总感觉linux netfilter机制比较强大,总不敢轻易写下分析文档,但为了提升与扩展自己的知识面,特写下此系列的学习文档,以推动自己不断前行,不断学习。 一、netfilter介绍 Linux netfilter就是借助一整套的 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点回调函数相关的数据结构,其是nf hook机制的重要数据结构,各成员的意义如下: struct nf_hook_ops { struct list_head list; //链表结构,实现hook函数链接 /* User fills in from here down. */ nf_hookfn *hook;//hook处理函数
struct module *owner;//模块所属
int pf;//协议号
int hooknum;//hook 点
/* Hooks are ordered in ascending priority. */
int priority;//优先级
};
2、 nf_hookfn
Hook 回调函数的定义
hooknum:hook 点
skb:数据包
in:数据包入口设备
out:数据包出口设备
okfn:是个函数指针,当所有的该 HOOK 点的所有登记函数调用完后,
调用该函数
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 *));
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 变量存在链表的最前面,优先执行。
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
三、HOOK 机制的注册、执行相关的函数
1、hook 注册函数 nf_register_hook
功能: 将一个新的 hook 结构添加 nf_hooks 数组的相应的成员链表中
A 根据协议号、hook 点,确定链表
B 根据优先级将该 hook 结构添加到链表的合适位置
注意:访问 nf_hooks,需要添加自旋锁
int nf_register_hook(struct nf_hook_ops *reg)
{
struct list_head *i;
spin_lock_bh(&nf_hook_lock);
list_for_each(i, &nf_hooks[reg->pf][reg->hooknum]) {
if (reg->priority < ((struct nf_hook_ops *)i)->priority)
break;
}
list_add_rcu(®->list, i->prev);
spin_unlock_bh(&nf_hook_lock);
synchronize_net();
return 0;
}
2、Hook 注销函数 nf_unregister_hook
功能: 将一个 hook 结构从 nf_hooks 数组的相应的成员链表中删除
删除操作就是平常的删除链表成员的操作,比较简单
void nf_unregister_hook(struct nf_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 来实现具体功能
#define NF_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 进行后续操作。
#define NF_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
#define NF_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 的队列中,数据包可以从内核传递到
用户层处理,并将处理结果返回。
int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb,
{
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], pskb, hook, indev,
outdev, &elem, okfn, hook_thresh);
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
ret = 1;
goto unlock;
} else if (verdict == NF_DROP) {
kfree_skb(*pskb);
ret = -EPERM;
} else if ((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))
goto next_hook;
}
unlock:
rcu_read_unlock();
return ret;
}
上面就是 HOOK 机制涉及到的主要的函数,HOOK 机制还是比较好理解的,就是这样的机
制作为基石,为 netfilter 的实现提供了坚实的基础,使 netfilter 能够实现很强大的功能。
四、实践
下面主要是通过上面接收的 hook 注册函数,实现向内核中相应的 hook 点注册 hook 回调函
数,结合 icmp 数据包的格式,实现对 icmp 数据包的处理。
icmp_reply_filter.c
主要是数据接收方向的 NF_IP_LOCAL_IN 点注册回调函数,该回调函数对接收到的 icmp
reply 报文,将序列号是 9 的倍数的 reply 报文丢弃掉
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static unsigned int
{
icmp_reply_hook_func(unsigned int hook,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
const struct iphdr *iph;
struct icmphdr *icmph;
if ((*skb)->len < sizeof(struct iphdr) ||
ip_hdrlen(*skb) < sizeof(struct iphdr))
return NF_ACCEPT;
iph = ip_hdr(*skb);
icmph = (struct icmphdr *)(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);
return NF_DROP;
return NF_ACCEPT;
}
static struct nf_hook_ops __read_mostly icmp_reply_hook =
{
.hook = icmp_reply_hook_func,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_FIRST,
};
static int __init icmp_reply_hook_init()
{
}
printk(KERN_INFO"---init---\n");
return nf_register_hook(&icmp_reply_hook);
static void __exit icmp_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 点注册回调函数,该回调函数对发送的 icmp
request 报文,将序列号是 5 的倍数的 request 报文丢弃掉。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static unsigned int
icmp_request_hook_func(unsigned int hook,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
const struct iphdr *iph;
struct icmphdr *icmph;
if ((*skb)->len < sizeof(struct iphdr) ||
ip_hdrlen(*skb) < sizeof(struct iphdr))
return NF_ACCEPT;
iph = ip_hdr(*skb);
icmph = (struct icmphdr *)(iph + 1);
if(iph->protocol == 1)
{
}
if(icmph->type == 8)
{
}
if((icmph->un.echo.sequence)%5 == 0)
{
}
printk("----drop---\n");
return NF_DROP;
return NF_ACCEPT;
}
static struct nf_hook_ops __read_mostly icmp_request_hook =
{
};
.hook = icmp_request_hook_func,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_OUT,
.priority = NF_IP_PRI_FIRST,
static int __init icmp_request_init()
{
}
printk(KERN_INFO"---init---\n");
return nf_register_hook(&icmp_request_hook);
static void __exit icmp_request_exit(void )
printk(KERN_INFO"---exit---\n");
nf_unregister_hook(&icmp_request_hook);
{
}