ip_route_output_flow

[ip_route_output_flow]

struct rtable *ip_route_output_flow(struct net *net, struct flowi4 *flp4, struct sock *sk)
{
    struct rtable *rt = __ip_route_output_key(net, flp4);
    return rt;
}

[ip_route_output_flow->__ip_route_output_key]

struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{
    __u8 tos = RT_FL_TOS(fl4);  // tos
    struct fib_result res;  // 查找结果
    int orig_oif;

    res.tclassid    = 0;
    res.fi      = NULL; // fib_info 
    res.table   = NULL; // fib_table 

    orig_oif = fl4->flowi4_oif; // 输出设备ID

    fl4->flowi4_iif = LOOPBACK_IFINDEX; // 输入设备ID设为环回接口
    fl4->flowi4_tos = tos & IPTOS_RT_MASK;
    fl4->flowi4_scope = ((tos & RTO_ONLINK) ?  RT_SCOPE_LINK : RT_SCOPE_UNIVERSE);

在路由表中找查的结果会放在res当中,res.fi指向fib_info,res.table指向fib_table,保存fl4的输出设备ID,将fl4的输入设备设为环回接口,根据tos设置fls的scope,RT_SCOPE_LINK为局域网;RT_SCOPE_UNIVERSE表示没有直接相连的路由,使用下一站路由(next hop gateway)。下面看源地址不为0的情况:

    if (fl4->saddr) {
        if (ipv4_is_multicast(fl4->saddr) ||
            ipv4_is_lbcast(fl4->saddr) ||
             ipv4_is_zeronet(fl4->saddr))
            goto out;

当源地址为多播地址,广播地址,全0时,直接返回。

        if (fl4->flowi4_oif == 0 &&
            (ipv4_is_multicast(fl4->daddr) || ipv4_is_lbcast(fl4->daddr))) {
            dev_out = __ip_dev_find(net, fl4->saddr, false);
            if (dev_out == NULL)
                goto out;

当输出设备ID为0并且目标地址为多播地址或广播地址时,查找与源地址绑定的设备,如果没找到,返回
[ip_route_output_flow->__ip_route_output_key->__ip_dev_find]

struct net_device *__ip_dev_find(struct net *net, __be32 addr, bool devref)
{
    u32 hash = inet_addr_hash(net, addr);   // 地址的哈希值
    hlist_for_each_entry_rcu(ifa, &inet_addr_lst[hash], hash) {
        if (ifa->ifa_local == addr) {
            struct net_device *dev = ifa->ifa_dev->dev;
             if (!net_eq(dev_net(dev), net))
                continue;
            result = dev;
            break;
        }
    }

inet_addr_lst是一个哈希表:static struct hlist_head inet_addr_lst[256]; 里面保存的列表项类型为in_ifaddr,表示一个地址。先计算地址的哈希值,然后开始查找。如果地址相同,并且设备的网络名字空间也相同,成功找到。

    if (!result) {
        struct flowi4 fl4 = { .daddr = addr };
        struct fib_table *local;
        local = fib_get_table(net, RT_TABLE_LOCAL); // 得到本地路由表
                if (local &&
            !fib_table_lookup(local, &fl4, &res, FIB_LOOKUP_NOREF) && res.type == RTN_LOCAL)
            result = FIB_RES_DEV(res);
    }

    return result;
} 

如果没找到,在本地路由表中查找与addr匹配的项,结果放在res中。如果找到并且类型为RTN_LOCAL时,得到设备。
[ip_route_output_flow->__ip_route_output_key->__ip_dev_find->fib_table_lookup]

int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp, struct fib_result *res, int fib_flags)
{
    struct trie *t = (struct trie *) tb->tb_data;   // 根节点
    t_key key = ntohl(flp->daddr);  // 地址作为key
    struct rt_trie_node *n;
    n = rcu_dereference(t->trie);   // 字典树节点
    if (!n)
        goto failed;

路由表被组织为一棵树,tb->tb_data指向根节点,查找的key是地址,从第一个子节点开始查找:

    /* Just a leaf? */
    if (IS_LEAF(n)) {
        ret = check_leaf(tb, t, (struct leaf *)n, key, flp, res, fib_flags);
        goto found;
    }

如果第一个子节点是一个叶子,说明整个树只有一个叶子,只要查找它就可以了:
[ip_route_output_flow->__ip_route_output_key->__ip_dev_find->fib_table_lookup->check_leaf]

static int check_leaf(struct fib_table *tb, struct trie *t, struct leaf *l,
              t_key key,  const struct flowi4 *flp,
              struct fib_result *res, int fib_flags)
{
    struct leaf_info *li;
    struct hlist_head *hhead = &l->list;
    hlist_for_each_entry_rcu(li, hhead, hlist) {
        struct fib_alias *fa;

        if (l->key != (key & li->mask_plen))
            continue;

每个叶子中都有一个leaf_info列表。先找到key相同的项

        list_for_each_entry_rcu(fa, &li->falh, fa_list) {
            struct fib_info *fi = fa->fa_info;
            int nhsel, err;

            if (fa->fa_tos && fa->fa_tos != flp->flowi4_tos)
                continue;
            if (fi->fib_dead)
                continue;
            if (fa->fa_info->fib_scope < flp->flowi4_scope)
                continue;

            if (!(fa->fa_state & FA_S_ACCESSED))
                    fa->fa_state |= FA_S_ACCESSED;

一个地址有可能有多个路由,li->falh指向一个路由列表。要从中选出一个满足条件的。它们的tos要相同,fib_dead为false,路由的scope要匹配。这些条件都满足后,设置fa的状态为FA_S_ACCESSED

            err = fib_props[fa->fa_type].error;
            if (err) {
                return err;
            }
            if (fi->fib_flags & RTNH_F_DEAD)
                continue;

fib_props是一个全局数组,它对不同的路由类型定义了一个错误值,如:RTN_UNREACHABLE的error值为-EHOSTUNREACH。如果路由的状态不正确,直接返回。还要检查路由的flags不能为RTNH_F_DEAD(Nexthop is dead)

            for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) {
                const struct fib_nh *nh = &fi->fib_nh[nhsel];

                if (nh->nh_flags & RTNH_F_DEAD)
                    continue;
                if (flp->flowi4_oif && flp->flowi4_oif != nh->nh_oif)
                    continue;
                res->prefixlen = li->plen;
                res->nh_sel = nhsel;
                res->type = fa->fa_type;
                res->scope = fa->fa_info->fib_scope;
                res->fi = fi;
                res->table = tb;
                res->fa_head = &li->falh;
                if (!(fib_flags & FIB_LOOKUP_NOREF))
                    atomic_inc(&fi->fib_clntref);
                return 0;
            }
        }
    }

    return 1;
}

下一站路由地址会cache在fi->fib_nh中,此数组的大小为fi->fib_nhs。如果flp设置了输出设备,要与下一站地址的相同。然后设置返回值,返回0表示成功。
[ip_route_output_flow->__ip_route_output_key->__ip_dev_find->fib_table_lookup]

    // 是一个根节点
    pn = (struct tnode *) n;
    chopped_off = 0;

    while (pn) {
        pos = pn->pos;
        bits = pn->bits;

        if (!chopped_off)
            cindex = tkey_extract_bits(mask_pfx(key, current_prefix_length), pos, bits);

        n = tnode_get_child_rcu(pn, cindex);    // 得到子树
        if (n == NULL) {
            goto backtrace;
        }

如果有多个叶子,就要通过循环来查找了。通过计算key,得到子树索引,并得到子树

        if (IS_LEAF(n)) {
            ret = check_leaf(tb, t, (struct leaf *)n, key, flp, res, fib_flags);
            if (ret > 0)
                goto backtrace;
            goto found;
        }
        cn = (struct tnode *)n;

如果是叶子,在当中查找。

        if (current_prefix_length < pos+bits) {
            if (tkey_extract_bits(cn->key, current_prefix_length, 
                                cn->pos - current_prefix_length) || !(cn->child[0]))
                goto backtrace;
        }
        pref_mismatch = mask_pfx(cn->key ^ key, cn->pos);
        if (pref_mismatch) {
            /* fls(x) = __fls(x) + 1 */
            int mp = KEYLENGTH - __fls(pref_mismatch) - 1;

            if (tkey_extract_bits(cn->key, mp, cn->pos - mp) != 0)
                goto backtrace;

            if (current_prefix_length >= cn->pos)
                current_prefix_length = mp;
        }

        pn = (struct tnode *)n; /* Descend */
        chopped_off = 0;
        continue;
backtrace:
        chopped_off++;

        /* As zero don't change the child key (cindex) */
        while ((chopped_off <= pn->bits)
               && !(cindex & (1<<(chopped_off-1))))
            chopped_off++;

        /* Decrease current_... with bits chopped off */
        if (current_prefix_length > pn->pos + pn->bits - chopped_off)
            current_prefix_length = pn->pos + pn->bits
                - chopped_off;

        /*
         * Either we do the actual chop off according or if we have
         * chopped off all bits in this tnode walk up to our parent.
         */

        if (chopped_off <= pn->bits) {
            cindex &= ~(1 << (chopped_off-1));
        } else {
            struct tnode *parent = node_parent_rcu((struct rt_trie_node *) pn);
            if (!parent)
                goto failed;

            /* Get Child's index */
            cindex = tkey_extract_bits(pn->key, parent->pos, parent->bits);
            pn = parent;
            chopped_off = 0;
            goto backtrace;
        }
    }
failed:
    ret = 1;
found:
    return ret;
}

通过一系列计算得到下一个叶子。如果成功找到就返回0。
[ip_route_output_flow->__ip_route_output_key]

            if (dev_out == NULL)
                goto out;

            fl4->flowi4_oif = dev_out->ifindex; // 设置输出设备的ID
            goto make_route;
        }

如果输出设备没找到,出错返回。此时输出设备为与在路由表中与源地址对应的设备。转到make_route

        if (!(fl4->flowi4_flags & FLOWI_FLAG_ANYSRC)) {
            if (!__ip_dev_find(net, fl4->saddr, false))
                goto out;
        }
    }

总结下,当源地址不为0时,如果是多播地址或广播地址,直接返回。如果目标地址是多播地址或广播地址并且输出设备ID为0,通过源地址在路由表中去找设备,如果找到,转到make_route处。然后如果没有设置FLOWI_FLAG_ANYSRC,调用__ip_dev_find查找,如果没找到,出错返回。到这里,要么是源地址为0,要么源地址不为0并且通过源地址找到了输出设备。下面来看对目标地址的处理:

    if (fl4->flowi4_oif) {  // 输出设备ID不为0
        dev_out = dev_get_by_index_rcu(net, fl4->flowi4_oif);   // 从ID得到设备
        if (dev_out == NULL)
            goto out;

        /* RACE: Check return value of inet_select_addr instead. 
         */
        if (!(dev_out->flags & IFF_UP) || !__in_dev_get_rcu(dev_out)) {
            goto out;
        }
        /* 本地多播
         * 广播
         */
        if (ipv4_is_local_multicast(fl4->daddr) ||
            ipv4_is_lbcast(fl4->daddr)) {
            if (!fl4->saddr)    // 源地址不为0
                fl4->saddr = inet_select_addr(dev_out, 0, RT_SCOPE_LINK); 
            goto make_route;
        }
        if (!fl4->saddr) {// 源地址为0
            if (ipv4_is_multicast(fl4->daddr)) // 多播
                fl4->saddr = inet_select_addr(dev_out, 0, fl4->flowi4_scope);
            else if (!fl4->daddr) // 目标地址为0
                fl4->saddr = inet_select_addr(dev_out, 0, RT_SCOPE_HOST);
        }
    }

如果输出设备不为0,先从ID得到设备,如果设备没有有被激活或因为竞争关系无法得到设备,出错。扣果目标地址为本地多播或广播,转到make_route,此时如果源地址不为0,将其设置为scope为RT_SCOPE_LINK的地址。再判断如果源地址为0,目的地址为多播,将其设置为scope为fl4->flowi4_scope的地址;目的地址为0,将其设置为scope为RT_SCOPE_HOST的地址。
[ip_route_output_flow->__ip_route_output_key->inet_select_addr]

__be32 inet_select_addr(const struct net_device *dev, __be32 dst, int scope)
{
    __be32 addr = 0;
    struct in_device *in_dev;
    in_dev = __in_dev_get_rcu(dev); // 设备
    if (!in_dev)
        goto no_in_dev;

    for_primary_ifa(in_dev) {   // 设备的所有primary地址
        if (ifa->ifa_scope > scope) // 地址的范围大于参数传进来的
            continue;
        /* 目标地址为空
         * 与目标地址相同
         */
        if (!dst || inet_ifa_match(dst, ifa)) {
            addr = ifa->ifa_local;  // 得到地址
            break;
        }
        if (!addr)  // 如果没设地址初始化addr
            addr = ifa->ifa_local;
    } endfor_ifa(in_dev);
    if (addr)
        goto out_unlock;    

得到设备,在设备的primary地址列表中查找。地址的scope要大于要求的,如果目标地址为0或与列表中的地址匹配,就找到了。如果上面条件不符合,就选择列表中的第一个地址。如果找到,就返回了。

no_in_dev:
    /* Not loopback addresses on loopback should be preferred
       in this case. It is importnat that lo is the first interface
       in dev_base list.
     */
    for_each_netdev_rcu(net, dev) { // 所有网络设备
        in_dev = __in_dev_get_rcu(dev); // 设备
        if (!in_dev)
            continue;

        for_primary_ifa(in_dev) {   // 设备的所有primary地址
            if (ifa->ifa_scope != RT_SCOPE_LINK &&
                ifa->ifa_scope <= scope) {
                addr = ifa->ifa_local;  // 得到地址
                goto out_unlock;
            }
        } endfor_ifa(in_dev);
    }
out_unlock:
    return addr;
}

如果没找到,在同一个网络名字空间的所有设备上查找。设备不能为空,然后在每个设备的primary地址列表中查找scope不大于目标地址并不等于RT_SCOPE_LINK的地址。
[ip_route_output_flow->__ip_route_output_key]

    if (!fl4->daddr) {  
        fl4->daddr = fl4->saddr;    // 目标地址设为源地址
        if (!fl4->daddr)    // 目标地址为0
            fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK);
        dev_out = net->loopback_dev;    // 输出设备设为环回接口
        fl4->flowi4_oif = LOOPBACK_IFINDEX; // 输出设备ID设为环回接口
        res.type = RTN_LOCAL;   // 本机地址
        flags |= RTCF_LOCAL;    // 本机地址
        goto make_route;
    }

目标地址为0的情况。先把目标地址设为源地址,如果两者都为0,都设为环回地址。输出设备设为环回接口,地址类型设为本机地址。转到make_route。

    if (fib_lookup(net, fl4, &res)) {   // 路由表中查找

[ip_route_output_flow->__ip_route_output_key->fib_lookup]

static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
                 struct fib_result *res)
{
    struct fib_table *table;

    table = fib_get_table(net, RT_TABLE_LOCAL);
    if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
        return 0;

    table = fib_get_table(net, RT_TABLE_MAIN);
    if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
        return 0;
    return -ENETUNREACH;
}

在本地路由表和main路由表中分别查找。
[ip_route_output_flow->__ip_route_output_key]

        res.fi = NULL;
        res.table = NULL;
        if (fl4->flowi4_oif) {  // 输出设备ID不为0
            if (fl4->saddr == 0)    // 源地址为0
                fl4->saddr = inet_select_addr(dev_out, 0, RT_SCOPE_LINK);
            res.type = RTN_UNICAST; /* Gateway or direct route  */
            goto make_route;
        }
        goto out;
    }

没有找到的情况:如果输出设备不为0,结果设为前往默认路由,此时如果源地址为0,设为scope为RT_SCOPE_LINK的本机地址,然后转向make_route。如果输出设备为0,直接返回。

    if (res.type == RTN_LOCAL) {    // 本机地址
        if (!fl4->saddr) {  // 源地址为0
            if (res.fi->fib_prefsrc)
                fl4->saddr = res.fi->fib_prefsrc;// 源IP地址
            else
                fl4->saddr = fl4->daddr;    // 源地址设为目标地址
        }
        dev_out = net->loopback_dev;    // 环回接口
        fl4->flowi4_oif = dev_out->ifindex; // 输出设备 ID
        flags |= RTCF_LOCAL;    // 本机地址
        goto make_route;
    }

如果是本机地址,输出设备设为环回接口,fl4的输出设备号设为其输入设备号;此时如果源地址为0,如果结果设置了fib_prefsrc,将其设为源地址,否则设为目标地址。转到make_route。

    if (!res.prefixlen &&
        res.table->tb_num_default > 1 &&
        res.type == RTN_UNICAST && !fl4->flowi4_oif)
        fib_select_default(&res);   // 默认路由

如果路由表长度为0,并且路由表tb_num_default > 1并且路由指向gateway,并且输出设备为0,选择默认路由
[ip_route_output_flow->__ip_route_output_key->fib_select_default]

void fib_select_default(struct fib_result *res)
{
    struct fib_info *fi = NULL, *last_resort = NULL;
    struct list_head *fa_head = res->fa_head;
    struct fib_table *tb = res->table;
    int order = -1, last_idx = -1;
    struct fib_alias *fa;

有可能多个路由项指向同一个目标地址,而这些路由仅仅是因为TOS不同。这些不同的路由项放在fa_head中

    list_for_each_entry_rcu(fa, fa_head, fa_list) {
        struct fib_info *next_fi = fa->fa_info;

        if (next_fi->fib_scope != res->scope ||
            fa->fa_type != RTN_UNICAST)
            continue;

        if (next_fi->fib_priority > res->fi->fib_priority)
            break;
        if (!next_fi->fib_nh[0].nh_gw ||
            next_fi->fib_nh[0].nh_scope != RT_SCOPE_LINK)
            continue;

        fib_alias_accessed(fa);

在fa_head列表中查找,scope不同或类型不是RTN_UNICAST的跳过;优先级大的,结束查找,因为列表是按照优先级来排序的;下上站路由的gateway为0或scope不为RT_SCOPE_LINK的跳过。

    if (fi == NULL) {
            if (next_fi != res->fi)
                break;
        } else if (!fib_detect_death(fi, order, &last_resort,
                         &last_idx, tb->tb_default)) {
            fib_result_assign(res, fi);
            tb->tb_default = order;
            goto out;
        }
        fi = next_fi;
        order++;
    }

第一次循环时如果它们指向的fib_info不同(目标地址不同),结束查找。
[ip_route_output_flow->__ip_route_output_key->fib_select_default->fib_detect_death]

static int fib_detect_death(struct fib_info *fi, int order,
                struct fib_info **last_resort, int *last_idx,
                int dflt)
{
    struct neighbour *n;
    int state = NUD_NONE;

    n = neigh_lookup(&arp_tbl, &fi->fib_nh[0].nh_gw, fi->fib_dev);
    if (n) {
        state = n->nud_state;
        neigh_release(n);
    }

arp_tbl主要用来缓冲ARP地址,这里来查找下一站地址。找到后,设备状态state
[ip_route_output_flow->__ip_route_output_key->fib_select_default->fib_detect_death->neigh_lookup]

struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
                   struct net_device *dev)
{
    struct neighbour *n;
    int key_len = tbl->key_len; // 地址长度
    u32 hash_val;
    struct neigh_hash_table *nht;

    NEIGH_CACHE_STAT_INC(tbl, lookups);

    rcu_read_lock_bh();
    nht = rcu_dereference_bh(tbl->nht); // neighbour对象的哈希表
    hash_val = tbl->hash(pkey, dev, nht->hash_rnd) >> (32 - nht->hash_shift);

计算出pkey的哈希值,这里的tbl为arp_table

for (n = rcu_dereference_bh(nht->hash_buckets[hash_val]);   // 哈希表子表
         n != NULL;
         n = rcu_dereference_bh(n->next)) {
        if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {  // 设备和KEY都相同
            if (!atomic_inc_not_zero(&n->refcnt))   // 增加引用计数
                n = NULL;
            NEIGH_CACHE_STAT_INC(tbl, hits);    // 记录状态
            break;
        }
    }

    rcu_read_unlock_bh();
    return n;
}

在哈希表中查找。
[ip_route_output_flow->__ip_route_output_key->fib_select_default->fib_detect_death]

    if (state == NUD_REACHABLE)
        return 0;
    if ((state & NUD_VALID) && order != dflt)
        return 0;
    if ((state & NUD_VALID) ||
        (*last_idx < 0 && order > dflt)) {
        *last_resort = fi;
        *last_idx = order;
    }
    return 1;
}

NUD_REACHABLE表示处在连接状态;

    if (!fl4->saddr)    // 源地址为0
        fl4->saddr = FIB_RES_PREFSRC(net, res);

    dev_out = FIB_RES_DEV(res);
    fl4->flowi4_oif = dev_out->ifindex; // 输出设备ID

如果源地址为0,如果结果设置了fib_prefsrc,将其设为源地址,否则设为目标地址。

make_route:
    rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags);    // 新增路由缓冲

out:
    rcu_read_unlock();
    return rth;
}

把结果放入缓冲中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值