策略路由之添加

规则的添加

在规则初始化时,会注册添加函数fib_nl_newrule
rtnl_register(PF_UNSPEC, RTM_NEWRULE, fib_nl_newrule, NULL);
接下来,分析创建规则fib_nl_newrule函数
功能:
(1)根据应用层传递的协议类型,找到相应的fib_rules_ops变量;
(2)解析应用层传入的数据;
(3)针对源IP和目的IP,有效性检查
(4)分配新的fib_rule缓存,并对优先级、接口index、接口名称、mark值、action等赋值;
(5)调用协议对应的configure函数,对fib_rule的源IP、目的IP、掩码、tos进行配置;
(6)将fib_rule添加到rule_lists中,并通过netlink通知其他进程。

static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh)
{
    struct net *net = sock_net(skb->sk);
    struct fib_rule_hdr *frh = nlmsg_data(nlh);
    struct fib_rules_ops *ops = NULL;
    struct fib_rule *rule, *r, *last = NULL;
    struct nlattr *tb[FRA_MAX+1];
    int err = -EINVAL, unresolved = 0;

    if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh)))
        goto errout;
    //根据应用层传递的协议类型family af_inet ,找到相应的fib_rules_ops变量
        /*/ipv4对应的是 fib4_rules_ops
        根据协议簇 在链表rules_ops中查找符合要求的fib_rules_ops类型的变量;
        对于IPV4,变量为fib4_rules_ops。*/
    ops = lookup_rules_ops(net, frh->family);
    if (ops == NULL) {
        err = -EAFNOSUPPORT;
        goto errout;
    }
    //解析应用层传入的数据nlh,放入tb中。
    err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy);
    if (err < 0)
        goto errout;

    err = validate_rulemsg(frh, tb, ops);//有效性检查,针对源IP和目的IP
    if (err < 0)
        goto errout;

    rule = kzalloc(ops->rule_size, GFP_KERNEL);//分配新的fib_rule缓存
    if (rule == NULL) {
        err = -ENOMEM;
        goto errout;
    }
    rule->fr_net = net;//增加rule->net的引用计数

    rule->pref = tb[FRA_PRIORITY] ? nla_get_u32(tb[FRA_PRIORITY])
                                  : fib_default_rule_pref(ops);//设置优先级

    if (tb[FRA_IIFNAME]) {
        struct net_device *dev;

        rule->iifindex = -1;//入接口index
        nla_strlcpy(rule->iifname, tb[FRA_IIFNAME], IFNAMSIZ);
        dev = __dev_get_by_name(net, rule->iifname);//通过接口名找dev
        if (dev)
            rule->iifindex = dev->ifindex;//将dev的index赋值给rule接口index
    }

    if (tb[FRA_OIFNAME]) {
        struct net_device *dev;

        rule->oifindex = -1;//和入接口一样 设置
        nla_strlcpy(rule->oifname, tb[FRA_OIFNAME], IFNAMSIZ);
        dev = __dev_get_by_name(net, rule->oifname);
        if (dev)
            rule->oifindex = dev->ifindex;
    }

    if (tb[FRA_FWMARK]) {
        rule->mark = nla_get_u32(tb[FRA_FWMARK]);//mark值
        if (rule->mark)//mark值
            /* compatibility: if the mark value is non-zero all bits
             * are compared unless a mask is explicitly specified.
             */
            rule->mark_mask = 0xFFFFFFFF;//默认 mark掩码值
    }

    if (tb[FRA_FWMASK])
        rule->mark_mask = nla_get_u32(tb[FRA_FWMASK]);

    if (tb[FRA_TUN_ID])
        rule->tun_id = nla_get_be64(tb[FRA_TUN_ID]);
    
    //设置规则action,table id,实现规则与路由表的关联
    rule->action = frh->action;
    rule->flags = frh->flags;
    rule->table = frh_get_table(frh, tb);
    if (tb[FRA_SUPPRESS_PREFIXLEN])
        rule->suppress_prefixlen = nla_get_u32(tb[FRA_SUPPRESS_PREFIXLEN]);
    else
        rule->suppress_prefixlen = -1;

    if (tb[FRA_SUPPRESS_IFGROUP])
        rule->suppress_ifgroup = nla_get_u32(tb[FRA_SUPPRESS_IFGROUP]);
    else
        rule->suppress_ifgroup = -1;

    err = -EINVAL;
    if (tb[FRA_GOTO]) {
        if (rule->action != FR_ACT_GOTO)
            goto errout_free;

        rule->target = nla_get_u32(tb[FRA_GOTO]);
        /* Backward jumps are prohibited to avoid endless loops */
        if (rule->target <= rule->pref)
            goto errout_free;

        list_for_each_entry(r, &ops->rules_list, list) {
            if (r->pref == rule->target) {
                RCU_INIT_POINTER(rule->ctarget, r);
                break;
            }
        }

        if (rcu_dereference_protected(rule->ctarget, 1) == NULL)
            unresolved = 1;
    } else if (rule->action == FR_ACT_GOTO)
        goto errout_free;
 //调用协议对应的configure函数,这里就是协议相关的添加
    err = ops->configure(rule, skb, frh, tb); 
 //ipv4的配置函数,根据应用层传参,设置fib4_rule变量参数:源IP、目的IP、掩码、路由表ID、tos
    if (err < 0)
        goto errout_free;
    //找到第一个pref比新创建的fib_rule的pref值大的 fib_rule
        //然后将新创建的fib_rule添到该规则之前
        //若没有找到,则添加到已有规则链表之后
    list_for_each_entry(r, &ops->rules_list, list) {
        if (r->pref > rule->pref)
            break;
        last = r;
    }

    fib_rule_get(rule);//增加fib_rule的引用计数

    if (last) //根据优先级,将fib_rule添到rules_list链表中
        list_add_rcu(&rule->list, &last->list);
    else
        list_add_rcu(&rule->list, &ops->rules_list);

    if (ops->unresolved_rules) {
        /*
         * There are unresolved goto rules in the list, check if
         * any of them are pointing to this new rule.
         */
        list_for_each_entry(r, &ops->rules_list, list) {
            if (r->action == FR_ACT_GOTO &&
                r->target == rule->pref &&
                rtnl_dereference(r->ctarget) == NULL) {
                rcu_assign_pointer(r->ctarget, rule);
                if (--ops->unresolved_rules == 0)
                    break;
            }
        }
    }

    if (rule->action == FR_ACT_GOTO)
        ops->nr_goto_rules++;

    if (unresolved)
        ops->unresolved_rules++;

    if (rule->tun_id)
        ip_tunnel_need_metadata();
    //通过netlink通知其他进程
    notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid);
    flush_route_cache(ops);//刷新路由缓存
    rules_ops_put(ops);
    return 0;

errout_free:
    kfree(rule);
errout:
    rules_ops_put(ops);
    return err;
}

 

策略路由查找

策略规则的查找函数fib_rules_lookup;

net->ipv4.rules_ops中的rules_list链表保存了当前net namespace所有的路由策略。通过list_for_each_entry_rcu遍历链表,然后通过fib_rule_match匹配策略

$ip rule
0: from all lookup local
32765: from all fwmark 0x3 lookup local
32766: from all lookup main
32767: from all lookup default

 

match 通用规则匹配
1)如指定入接口,数据包的入接口必须和策略rule中指定的入接口相同; //iif eth1
2)如指定出接口,二者的出接口必须相同;  //oif eth0
3)如指定流标记,二者的流标记必须相同; //fwmark 0x3
4)如指定隧道ID,二者的隧道ID必须相同; //tunid


另外,针对IPv4协议:
5)策略rule中指定的源IP地址与数据包的源IP地址,与掩码进行位与操作,结果必须相同;//from
5)策略rule中指定的目的IP地址与数据包的目的IP地址,与掩码进行位与操作,结果必须相同; //to
7)如指定TOS值,二者的TOS值必须相同; //服务类型,即指定服务的流量 //tos

 

功能:

(1)遍历rules_list链表;

(2)调用fib_rule_match进行规则匹配

(3)调用函数指针action,进行路由表项的查找;
(4)将arg->rule指向该规则的首地址。

首先遍历到的0: from all lookup local在进入到fib4_rule_match后会因为什么都没有设置而直接返回1,也就是直接匹配到。此刻本条规则的大致数据如下:

r->action = FR_ACT_TO_TBL;
r->pref = 0;
r->table = RT_TABLE_LOCAL;
r->flags = 0;
 匹配到规则后返回至fib_rules_lookup,判断下action接后执行ops->action的操作,此刻应该是fib4_rule_action

在没有使用l3mdev的情况下,使用rule->table作为table id,然后调用fib_get_table获取该表,最后通过fib_table_lookup进行路由项查找

int __fib_lookup(struct net *net, struct flowi4 *flp,
         struct fib_result *res, unsigned int flags)
{
    struct fib_lookup_arg arg = {
        .result = res,
        .flags = flags,
    };
    int err;

    /* update flow if oif or iif point to device enslaved to l3mdev */
    l3mdev_update_flow(net, flowi4_to_flowi(flp));

    err = fib_rules_lookup(net->ipv4.rules_ops, flowi4_to_flowi(flp), 0, &arg);
#ifdef CONFIG_IP_ROUTE_CLASSID
    if (arg.rule)
        res->tclassid = ((struct fib4_rule *)arg.rule)->tclassid;
    else
        res->tclassid = 0;
#endif

    if (err == -ESRCH)
        err = -ENETUNREACH;

    return err;
}

 

int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
             int flags, struct fib_lookup_arg *arg)
{
    struct fib_rule *rule;
    int err;

    rcu_read_lock();

    list_for_each_entry_rcu(rule, &ops->rules_list, list) {//遍历ops->rules_list的所有fib_rule节点;比如 ipv4-rules 
jumped:

        pr_info("family:%d tableid:%d pref:0X%x  src:%pI4--->dst:%pI4\n", ops->family, rule->table, rule->pref, 
                &(fl->u.ip4.saddr), &(fl->u.ip4.daddr));
        pr_info("rule  iiindex:%d  oifindex:%d mark:0x%x mark_mask:0x%x flags:%d targer:%d action:%d\n",
            rule->iifindex, rule->oifindex, rule->mark, rule->mark_mask, rule->flags, rule->target,
            rule->action);
        /*对于传入的fib_rule变量与传入的路由查找键值flowi变量比较
            //判断输入接口index是否相等;mark是否相等
            /源IP、目的IP是否相等
            //如果tos 非0; tos是否相等
            //协议规则的匹配,ipv4调用fib4_rule_match
        */
        if (!fib_rule_match(rule, ops, fl, flags)) //规则匹配
            continue;
        pr_info("family:%d tableid:%d pref:0X%x action:%d\n", ops->family, rule->table, rule->pref, rule->action);
        if (rule->action == FR_ACT_GOTO) {
            struct fib_rule *target;

            target = rcu_dereference(rule->ctarget);
            if (target == NULL) {
                continue;
            } else {
                rule = target;
                goto jumped;
            }
        } else if (rule->action == FR_ACT_NOP)
            continue;
        else
            err = ops->action(rule, fl, flags, arg);//fib4_rule_action 查找对应的路由表项
            //获取到相应的路由表 以及  查找符合要求的路由项;其逻辑/是/根据id值,获取相应的路由表,然后在tb 中查找fib 结果
        pr_info("action:%d err:%d \n", rule->action, err);
        if (!err && ops->suppress && ops->suppress(rule, arg))
            continue;

        if (err != -EAGAIN) {
            struct fib_result * res = arg->result;
            struct fib_info *pfib = res->fi;
        
            pr_info("prefixlen:%d nh_sel:%d type:%d scope:%d tb_id:%d prefsrc:%pI4 priority:%d oifname:%s nexthop:%pI4 \n",
                   res->prefixlen, res->nh_sel, res->type, res->scope, res->table->tb_id, 
                   &pfib->fib_prefsrc, pfib->fib_priority, pfib->fib_nh[0].nh_dev->name, &pfib->fib_nh[0].nh_gw);

            if ((arg->flags & FIB_LOOKUP_NOREF) ||
                likely(atomic_inc_not_zero(&rule->refcnt))) {
                arg->rule = rule;
                goto out;
            }
            break;
        }
    }

    err = -ESRCH;
out:
    rcu_read_unlock();

    return err;
}
EXPORT_SYMBOL_GPL(fib_rules_lookup);

 

posted @ 2022-03-04 14:48  codestacklinuxer  阅读(68)  评论(0)    收藏  举报