netfilter分析1-钩子函数在内核的初始化

Linux内核中网络防火墙是通过NF_HOOK宏调用钩子函数进行报文处理,本文基于内核版本4.4对钩子函数的初始化流程进行描述。

以过滤本地报文的钩子函数为例。本地报文过滤钩子函数调用宏:
NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,

       net, NULL, skb, skb->dev, NULL,

       ip_local_deliver_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 *))

{

return NF_HOOK_THRESH(pf, hook, net, sk, skb, in, out, okfn, INT_MIN);

}



static inline int

NF_HOOK_THRESH(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 thresh)

{

int ret = nf_hook_thresh(pf, hook, net, sk, skb, in, out, okfn, thresh);

if (ret == 1)

ret = okfn(net, sk, skb);

return ret;

}



static inline int nf_hook_thresh(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 *),

 int thresh)

{

struct list_head *hook_list = &net->nf.hooks[pf][hook];



if (nf_hook_list_active(hook_list, pf, hook)) {

struct nf_hook_state state;



nf_hook_state_init(&state, hook_list, hook, thresh,

   pf, indev, outdev, sk, net, okfn);

return nf_hook_slow(skb, &state);

}

return 1;

}

最终通过链表头struct list_head *hook_list = &net->nf.hooks[pf][hook]这个链表头实际就是所谓的挂载点,后面钩子函数通过链表的形式挂载到这个链表头下

例如过滤本地报文通过遍历执行钩子函数挂载点net->nf.hooks [NFPROTO_IPV4][NF_INET_LOCAL_IN]上的所有钩子函数来实现对报文的过滤处理。

net->nf.hooks [NFPROTO_IPV4][NF_INET_LOCAL_IN]挂载点钩子函数是通过定义在:
net/ipv4/netfilter/iptable_netfilter.c文件中定义的iptable_filter_init进行初始化的

static int __init iptable_filter_init(void)

{

    int ret;



    ret = register_pernet_subsys(&iptable_filter_net_ops);

    if (ret < 0)

    return ret;



    /* Register hooks */

    filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);

    if (IS_ERR(filter_ops)) {

        ret = PTR_ERR(filter_ops);

        unregister_pernet_subsys(&iptable_filter_net_ops);

     }



    return ret;

}

该函数中通过xt_hook_link初始化钩子函数,该函数定义在文件:

net/netfilter/x_tables.c

struct nf_hook_ops *xt_hook_link(const struct xt_table *table, nf_hookfn *fn)

{

     unsigned int hook_mask = table->valid_hooks;

    uint8_t i, num_hooks = hweight32(hook_mask);

    uint8_t hooknum;

    struct nf_hook_ops *ops;

    int ret;



    ops = kmalloc(sizeof(*ops) * num_hooks, GFP_KERNEL);

    if (ops == NULL)

    return ERR_PTR(-ENOMEM);



    for (i = 0, hooknum = 0; i < num_hooks && hook_mask != 0;hook_mask >>= 1, ++hooknum)                 
    {

        if (!(hook_mask & 1))

        continue;

        ops[i].hook     = fn;

        ops[i].pf       = table->af;

        ops[i].hooknum  = hooknum;

        ops[i].priority = table->priority;

        ++i;
    }



    ret = nf_register_hooks(ops, num_hooks);

    if (ret < 0) {

        kfree(ops);

        return ERR_PTR(ret);

    }



    return ops;
}

参数table是对应的filter表,为了不偏离主题,本文只对钩子函数初始化用到的部分进行简单说明,不对表内容展开详细描述,filter表初始化申明如下:

static const struct xt_table packet_filter = {

.name = "filter",

.valid_hooks = FILTER_VALID_HOOKS,

.me = THIS_MODULE,

.af = NFPROTO_IPV4,

.priority = NF_IP_PRI_FILTER,

};

.priority = NF_IP_PRI_FILTER,对钩子函数优先级进行设置。

.valid_hooks = FILTER_VALID_HOOKS,钩子函数会在以下几个点进行挂载:

#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \

    (1 << NF_INET_FORWARD) | \

    (1 << NF_INET_LOCAL_OUT))

参数fn是要注册的钩子函数指针,定义如下:

static unsigned int

iptable_filter_hook(void *priv, struct sk_buff *skb,

    const struct nf_hook_state *state)

{

    if (state->hook == NF_INET_LOCAL_OUT &&

    (skb->len < sizeof(struct iphdr) ||

     ip_hdrlen(skb) < sizeof(struct iphdr)))

    /* root is playing with raw sockets. */

    return NF_ACCEPT;



    return ipt_do_table(skb, state, state->net->ipv4.iptable_filter);

}

该函数最终通过ipt_do_table用filter表中的规则对报文进行处理。本文不对表处理进行描述。针对filter表,初始化是只注册了一个相同的钩子函数iptable_filter_hook,该钩子函数注册到了NF_INET_LOCAL_IN、NF_INET_FORWARD、NF_INET_LOCAL_OUT三个挂载点。

钩子函数先存储到struct nf_hook_ops *ops结构中的hook成员,该结构定义如下:

struct nf_hook_ops {

struct list_head list;



/* User fills in from here down. */

nf_hookfn *hook;

struct net_device *dev;

void *priv;

u_int8_t pf;

unsigned int hooknum;

/* Hooks are ordered in ascending priority. */

int priority;

};

然后nf_register_hooks函数通过结构中的list将钩子函数分别挂载到上面说的

net->nf.hooks [NFPROTO_IPV4][NF_INET_LOCAL_IN]、

net->nf.hooks [NFPROTO_IPV4][ NF_INET_FORWARD]、

net->nf.hooks [NFPROTO_IPV4][ NF_INET_LOCAL_OUT]、

钩子挂载点。

nf_register_hooks定义在文件/net/netfiler/core.c中

int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n)

{

    unsigned int i;

    int err = 0;



    for (i = 0; i < n; i++) {

        err = nf_register_hook(®[i]);

        if (err)

        goto err;

    }

    return err;



    err:

    if (i > 0)

    nf_unregister_hooks(reg, i);

    return err;

}

进一步通过nf_register_hook对钩子函数进行注册,定义如下:

int nf_register_hook(struct nf_hook_ops *reg)

{

    struct net *net, *last;

    int ret;



    rtnl_lock();

    for_each_net(net) {

        ret = nf_register_net_hook(net, reg);

        if (ret && ret != -ENOENT)

        goto rollback;

    }

    list_add_tail(®->list, &nf_hook_list);

    rtnl_unlock();



    return 0;

    rollback:

    last = net;

    for_each_net(net) {

        if (net == last)

        break;

        nf_unregister_net_hook(net, reg);

    }

    rtnl_unlock();

    return ret;

}

其中进一步通过nf_register_net_hook注册到net->nf.hooks[reg->pf][reg->hooknum]挂载点

int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)

{

    struct list_head *hook_list;

    struct nf_hook_entry *entry;

    struct nf_hook_ops *elem;



    entry = kmalloc(sizeof(*entry), GFP_KERNEL);

    if (!entry)

    return -ENOMEM;



    //将nf_hook_ops转存到entty中,这里之所以转存是为了后面方便删除。

    entry->orig_ops = reg;

    entry->ops = *reg;



    hook_list = nf_find_hook_list(net, reg);

    if (!hook_list) {

    kfree(entry);

    return -ENOENT;

    }



    mutex_lock(&nf_hook_mutex);

    list_for_each_entry(elem, hook_list, list) {

    if (reg->priority < elem->priority)

    break;

    }

    list_add_rcu(&entry->ops.list, elem->list.prev);

    mutex_unlock(&nf_hook_mutex);

 #ifdef CONFIG_NETFILTER_INGRESS

    if (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)

    net_inc_ingress_queue();

 #endif

 #ifdef HAVE_JUMP_LABEL

 static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);

 #endif

 return 0;

}

其中通过nf_find_hook_list找到挂载点,没错就是函数中的&net->nf.hooks[reg->pf][reg->hooknum],终于又遇见你。


static struct list_head *nf_find_hook_list(struct net *net,

   const struct nf_hook_ops *reg)

{

struct list_head *hook_list = NULL;



if (reg->pf != NFPROTO_NETDEV)

hook_list = &net->nf.hooks[reg->pf][reg->hooknum];

else if (reg->hooknum == NF_NETDEV_INGRESS) {

#ifdef CONFIG_NETFILTER_INGRESS

if (reg->dev && dev_net(reg->dev) == net)

hook_list = ®->dev->nf_hooks_ingress;

#endif

}

return hook_list;

}

找到挂载点后就可以对钩子函数进行挂载了,挂载过程需要枷锁,然后挂载到对应优先级所在的位置,遍历执行钩子函数时是按优先级执行的,钩子函数中priority越小优先级越高,因此钩子函数也是按照优先级从低到高进行挂载。挂载过程很简单,如下所示:

mutex_lock(&nf_hook_mutex);

list_for_each_entry(elem, hook_list, list) {

if (reg->priority < elem->priority)

break;

}

list_add_rcu(&entry->ops.list, elem->list.prev);

mutex_unlock(&nf_hook_mutex);

到此钩子函数初始化就结束了,通过上面的分析可以得出钩子函数、filter表关系如下图:

该图表示针对NF_INET_LOCAL_IN、NF_INET_FORWARD、NF_INET_LOCAL_OUT挂载点注册了优先级为NF_IP_PRI_FILTER 的钩子函数:iptable_filter_hook,该钩子函数通过对将报文根据filter表中的规则进行处理。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缥缈孤鸿_jason

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值