Netfilter源码分析(1)—— filter表的实现

出自:爱快官方技术博客 blog.ikuai8.com

作者:爱快老高


注:本文的内核源码为3.2.44


  iptables在配置规则时,需要指定将规则添加到哪个表中,默认是filter表。而iptables一共有5个表,分别是filter,nat,mangle,raw和security,本文将分析这5个表在内核的实现。
  Netfilter使用ipt_alloc_initial_table来申请表的空间和进行初始化,使用ipt_register_table来注册iptables的表。利用以上两个函数查询,前面三个表的初始化分别位于iptable_filter.c、nf_nat_rule.c、iptable_mangle.c、iptable_raw.c和iptable_security.c中。
  下面以最常用的filter表为例,其初始化代码是namespace的初始化函数:
//filter表的默认策略为NF_ACCEPT
static int forward = NF_ACCEPT;
module_param(forward, bool, 0000);

static int __net_init iptable_filter_net_init(struct net *net)
{
    struct ipt_replace *repl;

    //申请表结构,并初始化;
    repl = ipt_alloc_initial_table(&packet_filter);
    if (repl == NULL)
        return -ENOMEM;
    //设置FORWARD链的默认策略
    /* Entry 1 is the FORWARD hook */
    ((struct ipt_standard *)repl->entries)[1].target.verdict =
        -forward - 1;

    //注册表
    net->ipv4.iptable_filter =
        ipt_register_table(net, &packet_filter, repl);
    kfree(repl);
    if (IS_ERR(net->ipv4.iptable_filter))
        return PTR_ERR(net->ipv4.iptable_filter);
    return 0;
}
filter表的结构定义如下:
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
                (1 << NF_INET_FORWARD) | \
                (1 << NF_INET_LOCAL_OUT))

static const struct xt_table packet_filter = {
    .name       = "filter", //表名
    .valid_hooks    = FILTER_VALID_HOOKS, //该表检查点,分别为LOCAL_IN、FORWARD和LOCAL_OUT
    .me     = THIS_MODULE,
    .af     = NFPROTO_IPV4, // 适用于IPv4
    .priority   = NF_IP_PRI_FILTER, // 检查点的优先级
};
而filter表的检查入口,是在模块的初始化函数中注册的。
static int __init iptable_filter_init(void)
{
    int ret;

    /*
    检查模块参数是否合法,这里其实是有一个小问题的。
    前面的代码中,forward作为模块参数,其类型为bool,只可能为1或者0。
    所以这里的判断完全是不必要的。因为forward不可能小于0,也不可能大于NF_MAX_VERDICT
    这部分代码在后面的版本中被去掉了。
    */
    if (forward < 0 || forward > NF_MAX_VERDICT) {
        pr_err("iptables forward must be 0 or 1\n");
        return -EINVAL;
    }

    //注册network namespace的子系统
    ret = register_pernet_subsys(&iptable_filter_net_ops);
    if (ret < 0)
        return ret;

    /* Register hooks */
    //注册filter表的入口钩子函数
    filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);
    if (IS_ERR(filter_ops)) {
        ret = PTR_ERR(filter_ops);
        goto cleanup_table;
    }

    return ret;

 cleanup_table:
    unregister_pernet_subsys(&iptable_filter_net_ops);
    return ret;
}
  从上面的代码中,可以明确知道filter表的hook函数为iptable_filter_hook。
static unsigned int
iptable_filter_hook(unsigned int hook, struct sk_buff *skb,
            const struct net_device *in, const struct net_device *out,
            int (*okfn)(struct sk_buff *))
{
    const struct net *net;

    /*
    如注释所言,当在LOCAL_OUT链上,skb的包可以是非法的IP报文,
    因为root可以用raw socket组任意包。
    这时因为该skb为非法的IP报文,所以直接返回NF_ACCEPT
    */
    if (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;

    net = dev_net((in != NULL) ? in : out);
    //遍历filter表的规则并执行
    return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter);
}
下面再看一下注册表的hook函数的xt_hook_link。
struct nf_hook_ops *xt_hook_link(const struct xt_table *table, nf_hookfn *fn)
{
    //得到合法的hook值
    unsigned int hook_mask = table->valid_hooks;
    //将hook位值转算为hook的个数
    uint8_t i, num_hooks = hweight32(hook_mask);
    uint8_t hooknum;
    struct nf_hook_ops *ops;
    int ret;

    //申请Netfilter的hook操作结构
    ops = kmalloc(sizeof(*ops) * num_hooks, GFP_KERNEL);
    if (ops == NULL)
        return ERR_PTR(-ENOMEM);

    //根据表信息,初始化hook操作结构
    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].owner    = table->me;
        ops[i].pf       = table->af;
        ops[i].hooknum  = hooknum;
        ops[i].priority = table->priority;
        ++i;
    }

    //注册hook点
    ret = nf_register_hooks(ops, num_hooks);
    if (ret < 0) {
        kfree(ops);
        return ERR_PTR(ret);
    }

    return ops;
}
 Netfilter的hook操作结构注册流程为nf_register_hooks->nf_register_hook
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;
    //根据协议族、hook点及hook的优先级,将当前hook操作函数添加到适当的位置
    list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
        //priority越小,优先级越高
        if (reg->priority < elem->priority)
            break;
    }
    list_add_rcu(&reg->list, elem->list.prev);
    mutex_unlock(&nf_hook_mutex);
    return 0;
}
从上面的代码中可以发现Netfilter的规则结构,是由一个三维结构组成。
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS] __read_mostly;
EXPORT_SYMBOL(nf_hooks);

由协议族和hook点,定位到规则链表,然后链表中的规则根据优先级,由高到低排列。

当内核进行Netfilter规则匹配时,是通过调用NF_HOOK函数来进行规则匹配。
如在内核的转发函数ip_forward中,指定了协议组和hook点。

    return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
               rt->dst.dev, ip_forward_finish);

然后进入NF_HOOK->NF_HOOK_THRESH->nf_hook_thresh->nf_hook_slow。

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();

    //通过协议组和hook点,找到链表头
    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) {
        //如果判断结果是NF_ACCEPT或NF_STOP,则返回true
        ret = 1;
    } else if ((verdict & NF_VERDICT_MASK) == NF_DROP) {
        //如果判断结果是NF_DROP,则直接释放该skb
        kfree_skb(skb);
        ret = NF_DROP_GETERR(verdict);
        if (ret == 0)
            ret = -EPERM;
    } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
        //如果判断结果是NF_QUEUE,则调用nf_queue将skb入队列
        int err = nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
                        verdict >> NF_VERDICT_QBITS);
        if (err < 0) {
            if (err == -ECANCELED)
                goto next_hook;
            if (err == -ESRCH &&
               (verdict & NF_VERDICT_FLAG_QUEUE_BYPASS))
                goto next_hook;
            kfree_skb(skb);
        }
    }
    rcu_read_unlock();
    return ret;
}

这里就不进入nf_iterate函数,因为其就是一个简单的遍历链表,执行节点hook函数的操作。

至此,通过以filter表为例,应该已经对Netfilter中表的实现及遍历,有了比较清楚的认识了。


爱快官方技术博客blog.ikuai8.com——爱快老高出品

发表评论

电子邮件地址不会被公开。 必填项已用*标注