Netfilter 是 Linux网络内核协议栈提供了报文过滤(防火墙)框架,HOOK机制是Netfilter的核心。
在协议栈中相应位置嵌入Netfilter的函数NF_HOOK,来拦截报文送到Netfilter中进行处理。
我们知道Linux网络内核内置了5条链PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING, 其实就是在内核的五个位置嵌入了NF_HOOK函数,然后通过NF_HOOK
进入Netfilter框架处理。我们以PREROUTING为例,看下插入NF_HOOK的五个位置。
网卡驱动接收到报文,经过二层处理后,会调用net_receive_skb
传递给具体的协议处理函数, 对于IPv4来说,这里的协议处理函数指的就是ip_rcv
了。而我们PREROUTING链的NF_HOOK函数正是在ip_rcv
中嵌入的。
/*
* Main IP Receive routine.
* 主要功能:对IP头部合法性进行严格检查,然后把具体功能交给ip_rcv_finish。
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
......
/* 进入Netfilter处理,处理完后如果报文还要继续往下传递,则进入ip_rcv_finish函数处理 */
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
......
}
其他几条链的嵌入位置分别如下:
+ PREROUTING: ip_rcv
+ INPUT:ip_local_deliver
+ FORWARDip_forward
+ OUTPUT:raw_send_hdrinc
+ POSTROUTING:ip_mc_output
和ip_mc_output
钩子函数存储在全局二维数组nf_hooks
中。 struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS] __read_mostly;
从nf_hooks的定义我们可以看出,该结构体每一个成员都是一个struct list_head
对象。
NFPROTO_NUMPROTO有一下取值
enum {
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
NF_MAX_HOOKS有以下取值
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
};
关于全局二维数组的成员,即各个链表的head节点struct nf_hook_ops
形式如下:
struct nf_hook_ops
{
struct list_head list; //该成员将会把这个结构添加在nf_hooks数组引导的队列头中。
/* User fills in from here down. */
nf_hookfn *hook; //钩子处理函数:用于判断报文是否需要经过本钩子到检测过滤等
struct module *owner; //可以使用动态加载的内核模块
u_int8_t pf; //协议族
unsigned int hooknum; //钩子点
/* Hooks are ordered in ascending priority. */
int priority;//这里不是一个先来先服务队列,而是一个优先级队列。
};
该结构体的第二个成员nf_hookfn *hook;
其实就是对应一个钩子处理函数。关于nf_hookfn的定义如下:
//Netfilter 钩子函数
//return: NF_DROP,NF_ACCEPT...
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 *));
int nf_register_hook(struct nf_hook_ops *reg)
{
struct nf_hook_ops *elem;
int err;
//使用互斥锁nf_hook_mutex来保护二维数组下的hook_ops链表
err = mutex_lock_interruptible(&nf_hook_mutex);
if (err < 0)
return err;
//比较优先级,注册钩子 按照hook_op的优先级来把hook_op注册到全局二维数组中去
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);
mutex_unlock(&nf_hook_mutex);
return 0;
}
EXPORT_SYMBOL(nf_register_hook);
#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;})
#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;})
//拦截报文,Netfilter通过在协议栈的相应位置嵌入NF_HOOK来拦截报文
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN) //*important
钩子函数最后会调用(okfn)(skb)
, 即经过Netfilter处理后,如果报文没有被丢弃,应该交付给okfn指向的函数处理。
NF_HOOK函数的核心在于nf_hook_thresh
,我们看下它的实现。
/**
* 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, //pf:协议族 hook:协议定义到hook点
struct sk_buff *skb, //报文
struct net_device *indev, //入接口
struct net_device *outdev, //出接口
int (*okfn)(struct sk_buff *), int thresh, //netfilter处理完成后来处理报文的函数
int cond)
{
if (!cond)
return 1; //直接跳过netfilter处理
#ifndef CONFIG_NETFILTER_DEBUG
if (list_empty(&nf_hooks[pf][hook]))
return 1;
#endif
return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh); //netfilter 处理
}
/* 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]; //找到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) { //NF_STOP也可以导致一个报文被接收,这就是某些钩子在中间直接放行了报文,之后的钩子将没有机会进行检测。
ret = 1;
} else if (verdict == NF_DROP) { //drop
kfree_skb(skb);
ret = -EPERM;
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
/*这里比较少见,大致看了一下,好像是实现用户态防火墙的一种方法。
* 可能使用之前说过的netlink机制将这个报文发送到用户态的侦听套接口,
* 从而让用户态程序对这个报文进行全面检测,如果检测通过,再发送到内核
* 的netlink套接口中,对应的netlink协议簇为*/
/* 如果返回值是要把报文存入用户自定义的队列中(TARGET QUEUE和 NFQUEUE
* 使用),把verdict字段分成了两个部分,前一部分来定义返回值为入队操作,
* 后半部分给出入队的队列号*/
if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
verdict >> NF_VERDICT_BITS))
goto next_hook;
}
rcu_read_unlock();
return ret;
}
EXPORT_SYMBOL(nf_hook_slow);
这里会获取对应的个规则链elem = &nf_hooks[pf][hook];
,然后遍历调用注册的钩子函数,
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
outdev, &elem, okfn, hook_thresh); //进入钩子处理