• 路由fib表之input查找


    3.6版本以前的路由缓存

      缓存无处不在。现代计算机系统中,Cache是CPU与内存间存在一种容量较小但速度很高的存储器,用来存放CPU刚使用过或最近使用的数据。路由缓存就是基于这种思想的软件实现。内核查询FIB前,固定先查询cache中的记录,如果cache命中(hit),那就直接用就好了,不必查询FIB。如果没有命中(miss), 就回过头来查询FIB,最终将结果保存到cache,以便下次不再需要需要查询FIB。

    缓存是精确匹配的, 每一条缓存表项记录了匹配的源地址和目的地址、接收发送的dev,以及与内核邻居系统(L2层)的联系(negghbourFIB中存储的也就是路由信息,它常常是范围匹配的,比如像ip route 1.2.3.0/24 dev eth0这样的网段路由

      看上去的确可能能提高性能! 只要cache命中率足够高。要获得高的cache命中率有以下两个途径:1. 存储更多的表项; 2.存储更容易命中的表项

    缓存中存放的表项越多,那么目标报文与表项匹配的可能性越大。但是cache又不能无限制地增大,cache本身占用内存是一回事,更重要的是越多的表项会导致查询cache本身变慢。使用cache的目的是为了加速,如果不能加速,那就没用了!

      内核为了避免cache表项过多,内核还会在一定时机下清除过期的表项。有两个这样的时机,其一是添加新的表项时,如果冲突链的表项过多,就删除一条已有的表项;其二是内核会启动一个专门的定时器周期性地老化一些表项.

      获得更高的cache命中率的第二个途径是存储更容易命中的表项,什么是更容易命中的呢? 那就是真正有效的报文。遗憾的是,内核一点也不聪明:只要输入路由系统的报文不来离谱,它就会生成新的缓存表项。坏人正好可以利用这一点,不停地向主机发送垃圾报文,内核因此会不停地刷新cache。这样每个skb都会先在cache表中进行搜索,再查询FIB表,最后再创建新的cache表项,插入到cache表。这个过程中还会涉及为每一个新创建的cache表项绑定邻居,这又要查询一次ARP表。

    要知道,一台主机上的路由表项可能有很多,特别是对于网络交换设备,由OSPF**BGP等路由协议动态下发的表项有上万条是很正常的事。而邻居节点却不可能达到这个数量。对于转发或者本机发送的skb来说,路由系统能帮它们找到下一跳邻居**就足够了。

    总结起来就是,3.6版本以前的这种路由缓存在skb地址稳定时的确可能提高性能。但这种根据skb内容决定的性能却是不可预测和不稳定的。

    3.6版本以后的下一跳缓存

      正如前面所说,3.6版本移除了FIB查找前的路由缓存。这意味着每一个接收发送的skb现在都必须要进行FIB查找了。这样的好处是现在查找路由的代价变得稳定(consistent)了。

    路由缓存完全消失了吗? 并没有!在3.6以后的版本, 你还可以在内核代码中看到dst_entry。这是因为,3.6版本实际上是将FIB查找缓存到了下一跳(fib_nh)结构上,也就是下一跳缓存

    ········什么需要缓存下一跳呢? 我们可以先来看下没有下一跳缓存的情况。以转发过程为例,相关的伪代码如下:

    FORWARD:
    
    fib_result = fib_lookup(skb)
    dst_entry  = alloc_dst_entry(fib_result)
    skb->dst = dst_entry;
    
    skb->dst.output(skb)   
    nexthop = rt_nexthop(skb->dst, ip_hdr(skb)->daddr)
    neigh = ipv4_neigh_lookup(dev, nexthop)
    dst_neigh_output(neigh,skb)
    release_dst_entry(skb->dst)

      内核利用FIB查询的结果申请dst_entry, 并设置到skb上,然后在发送过程中找到下一跳地址,继而查找到邻居结构(查询ARP),然后邻居系统将报文发送出去,最后释放dst_entry。

    下一跳缓存的作用就是尽量减少最初和最后的申请释放dst_entry,它将dst_entry缓存在下一跳结构(fib_nh)上。这和之前的路由缓存有什么区别吗? 很大的区别!之前的路由缓存是以源IP和目的IP为KEY,有千万种可能性,而现在是和下一跳绑定在一起,一台设备没有那么多下一跳的可能。这就是下一跳缓存的意义!

    early demux

      early demux是在skb接收方向的加速方案。如前面所说,在取消了FIB查询前的路由缓存后,每个skb应该都需要查询FIB。而early demux是基于一种思想:如果一个skb是本机某个应用程序的套接字需要的,那么我们可以将路由的结果缓存在内核套接字结构上,这样下次同样的报文(四元组)到达后,我们可以在FIB查询前就将报文提交给上层,也就是提前分流(early demux)。但是 对于非面向链接的socket等 就不友好了!

     ip_route_input_slow  分析:

    /*
     *    NOTE. We drop all the packets that has local source
     *    addresses, because every properly looped back packet
     *    must have correct destination already attached by output routine.
     *
     *    Such approach solves two big problems:
     *    1. Not simplex devices are handled properly.
     *    2. IP spoofing attempts are filtered with 100% of guarantee.
     *    called with rcu_read_lock()
     */
    
    static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
                       u8 tos, struct net_device *dev)
    {
        struct fib_result res;
        struct in_device *in_dev = __in_dev_get_rcu(dev);//这里要使用输入网络设备dev,增加引用计数
        struct ip_tunnel_info *tun_info;
        struct flowi4    fl4;
        unsigned int    flags = 0;
        u32        itag = 0;
        struct rtable    *rth;
        int        err = -EINVAL;
        struct net    *net = dev_net(dev);
        bool do_cache;
    
        /* IP on this device is disabled. */
    
        if (!in_dev)
            goto out;
    
        /* Check for the most weird martians, which can be not detected
           by fib_lookup.
         */
    
        tun_info = skb_tunnel_info(skb);
        if (tun_info && !(tun_info->mode & IP_TUNNEL_INFO_TX))
            fl4.flowi4_tun_key.tun_id = tun_info->key.tun_id;
        else
            fl4.flowi4_tun_key.tun_id = 0;
        skb_dst_drop(skb);
    
        if (ipv4_is_multicast(saddr) || ipv4_is_lbcast(saddr))
            goto martian_source;
        debug_v4route("%s-->begin to slow looking\n",__FUNCTION__);
    
        res.fi = NULL;
        res.table = NULL;
        if (ipv4_is_lbcast(daddr) || (saddr == 0 && daddr == 0))
            goto brd_input;
    
        /* Accept zero addresses only to limited broadcast;
         * I even do not know to fix it or not. Waiting for complains :-)
         */
        if (ipv4_is_zeronet(saddr))
            goto martian_source;
    
        if (ipv4_is_zeronet(daddr))
            goto martian_destination;
    
        /* Following code try to avoid calling IN_DEV_NET_ROUTE_LOCALNET(),
         * and call it once if daddr or/and saddr are loopback addresses
         */
        if (ipv4_is_loopback(daddr)) {
            if (!IN_DEV_NET_ROUTE_LOCALNET(in_dev, net))
                goto martian_destination;
        } else if (ipv4_is_loopback(saddr)) {
            if (!IN_DEV_NET_ROUTE_LOCALNET(in_dev, net))
                goto martian_source;
        }
    
        /*
         *    Now we are ready to route packet.
         */
        fl4.flowi4_oif = 0;
        fl4.flowi4_iif = l3mdev_fib_oif_rcu(dev);
        fl4.flowi4_mark = skb->mark;
        fl4.flowi4_tos = tos;
        fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
        fl4.flowi4_flags = 0;
        fl4.daddr = daddr;
        fl4.saddr = saddr;
        err = fib_lookup(net, &fl4, &res, 0);
        pr_err("dev_name:%s  src:%pI4-->dst:%pI4 mark:%d fib_loopup[err%d res.type:%d]\n", 
            in_dev->dev->name,&saddr, &daddr, skb->mark, err, res.type);
        if (err != 0) {
            if (!IN_DEV_FORWARD(in_dev))
                err = -EHOSTUNREACH;
            goto no_route;pr_err
        }
    // /*根据查找到的路由类型,分类处理 广播处理*/
        if (res.type == RTN_BROADCAST)
            goto brd_input;
    
        if (res.type == RTN_LOCAL) {
             /*如果是发给本机的包,则验证原地址是否合法*/
            err = fib_validate_source(skb, saddr, daddr, tos,
                          0, dev, in_dev, &itag);
            /*对于RTN_LOCAL类型或者RTN_BROADCAST类型的路由表项,如果反向路由查找失败,
            也认定源地址为非法地址,在函数ip_handle_martian_source中递增in_martian_src计*/
            pr_err("dev_name:%s  src:%pI4-->dst:%pI4 mark:%d fib_validate_source[err:%d ]\n", 
                in_dev->dev->name,&saddr, &daddr, skb->mark, err);
            if (err < 0)
                goto martian_source;
            goto local_input;
        }
    
        if (!IN_DEV_FORWARD(in_dev)) {
            err = -EHOSTUNREACH;
            goto no_route;
        }
        if (res.type != RTN_UNICAST)
            goto martian_destination;
        /*当查到的路由类型是指向远端的主机,把此路由加入cache中*/
        err = ip_mkroute_input(skb, &res, &fl4, in_dev, daddr, saddr, tos);
    out:    return err;
    
    brd_input:/*当目的地址是广播地址,或查到的路由类型是广播类型*/
        /*报文的目的地址为广播地址;或者源地址和目的地址同时为全0;
        或者fib查询的结果为RTN_BROADCAST类型路由,如果接收设备的广播转发开关开启,
        有函数ip_mkroute_input创建路由缓存,但是其仅递增了in_slow_tot计数*/
        if (skb->protocol != htons(ETH_P_IP))
            goto e_inval;
    
        if (!ipv4_is_zeronet(saddr)) {
            err = fib_validate_source(skb, saddr, 0, tos, 0, dev,
                          in_dev, &itag);
            if (err < 0)
                goto martian_source;
        }
        flags |= RTCF_BROADCAST;
        res.type = RTN_BROADCAST;
        RT_CACHE_STAT_INC(in_brd);
    
    local_input:/*当查找到的路由指向本机时*/
        do_cache = false;
        if (res.fi) {
            if (!itag) {
                rth = rcu_dereference(FIB_RES_NH(res).nh_rth_input);
                if (rt_cache_valid(rth)) {
                    skb_dst_set_noref(skb, &rth->dst);
                    err = 0;
                    goto out;
                }
                do_cache = true;
            }
        }
    
        rth = rt_dst_alloc(net->loopback_dev, flags | RTCF_LOCAL, res.type,
                   IN_DEV_CONF_GET(in_dev, NOPOLICY), false, do_cache);
        /* rth->dst.input= ip_local_deliver; ----->路由查找结束后会调用此函数把报文送给上层处理*/
        if (!rth)
            goto e_nobufs;
    
        rth->dst.output= ip_rt_bug;
    #ifdef CONFIG_IP_ROUTE_CLASSID
        rth->dst.tclassid = itag;
    #endif
        rth->rt_is_input = 1;
        if (res.table)
            rth->rt_table_id = res.table->tb_id;
    
        RT_CACHE_STAT_INC(in_slow_tot);//统计路由cache分配次数
        if (res.type == RTN_UNREACHABLE) {// no route
            rth->dst.input= ip_error;/*PS:没有查找到路由的时候也会向缓存中添加一条不可达路由项*/
            rth->dst.error= -err;
            rth->rt_flags     &= ~RTCF_LOCAL;
        }
        if (do_cache) {//需要执行缓存操作 do_cache ,函数rt_cache_route来实现
            if (unlikely(!rt_cache_route(&FIB_RES_NH(res), rth))) {
                rth->dst.flags |= DST_NOCACHE;
                rt_add_uncached_list(rth);//如果缓存失败,将路由项添加到uncached_list链表
            }
        }
        skb_dst_set(skb, &rth->dst);
        err = 0;
        goto out;
    
    no_route:
        /*没有查找到路由的时候,向缓存中添加一条不可达路由项*/
        //统计fib查询失败次数,以及路由下一跳设备转发未启用
        RT_CACHE_STAT_INC(in_no_route);
        res.type = RTN_UNREACHABLE;
        res.fi = NULL;
        res.table = NULL;
        goto local_input;
    
        /*
         *Do not cache martian addresses: they should be logged (RFC1812)如果报文的目的地址为0;或者为回环地址
         但是接收设备不允许回环地址(可通过PROC文件配置,例如eth0配置文件:
         /proc/sys/net/ipv4/conf/eth0/route_localnet)。
    或者,查询fib表的结果得到的路由类型不等于RTN_BROADCAST、RTN_LOCAL和RTN_UNICAST中的任何一个,
    认为此报文的目的地址为非法地址。增加in_martian_dst计数。
         */
    martian_destination:
        RT_CACHE_STAT_INC(in_martian_dst);
    #ifdef CONFIG_IP_ROUTE_VERBOSE
        if (IN_DEV_LOG_MARTIANS(in_dev))
            net_warn_ratelimited("martian destination %pI4 from %pI4, dev %s\n",
                         &daddr, &saddr, dev->name);
    #endif
    
    e_inval:
        err = -EINVAL;
        goto out;
    
    e_nobufs:
        err = -ENOBUFS;
        goto out;
    /*在查找路由时,如果报文的源地址为多播地址,或者全F的广播地址,或者源地址为0。
    或者目的地址不是回环地址,但是源地址为回环地址,并且接收设备没有开启接收开关,
    认定报文的源地址为非法地址。
    对于RTN_LOCAL类型或者RTN_BROADCAST类型的路由表项,如果反向路由查找失败,
    也认定源地址为非法地址,在函数ip_handle_martian_source中递增in_martian_src计*/
    martian_source:
        ip_handle_martian_source(dev, in_dev, skb, daddr, saddr);
        goto out;
    }
    /* called in rcu_read_lock() section */
    static int __mkroute_input(struct sk_buff *skb,
                   const struct fib_result *res,
                   struct in_device *in_dev,
                   __be32 daddr, __be32 saddr, u32 tos)
    {
        struct fib_nh_exception *fnhe;
        struct rtable *rth;
        int err;
        struct in_device *out_dev;
        bool do_cache;
        u32 itag = 0;
    
        /* get a working reference to the output device */
        out_dev = __in_dev_get_rcu(FIB_RES_DEV(*res));;//获取输出报文的网络设备
        if (!out_dev) {
            net_crit_ratelimited("Bug in ip_route_input_slow(). Please report.\n");
            return -EINVAL;
        }
        //路由合法性检查,在调用该函数前,已经找到一条从saddr->daddr的路由项,
                //需要进行判断daddr->saddr反向路由是否存在,否则认为它是非法的
        err = fib_validate_source(skb, saddr, daddr, tos, FIB_RES_OIF(*res),
                      in_dev->dev, in_dev, &itag);
        if (err < 0) {
            ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,
                         saddr);
    
            goto cleanup;
        }
    
        do_cache = res->fi && !itag;
        if (out_dev == in_dev && err && IN_DEV_TX_REDIRECTS(out_dev) &&
            skb->protocol == htons(ETH_P_IP) &&
            (IN_DEV_SHARED_MEDIA(out_dev) ||
             inet_addr_onlink(out_dev, saddr, FIB_RES_GW(*res))))
            IPCB(skb)->flags |= IPSKB_DOREDIRECT;
    
        if (skb->protocol != htons(ETH_P_IP)) {//如果不是ip 比如arp
            /* Not IP (i.e. ARP). Do not create route, if it is
             * invalid for proxy arp. DNAT routes are always valid.
             *
             * Proxy arp feature have been extended to allow, ARP
             * replies back to the same interface, to support
             * Private VLAN switch technologies. See arp.c.
             */// arp 报文的处理 也会涉及到路由处理
            if (out_dev == in_dev &&
                IN_DEV_PROXY_ARP_PVLAN(in_dev) == 0) {
                err = -EINVAL;
                goto cleanup;
            }
        }
        //  查找 fl4->daddr 是否存在 fib_nh_exception
        fnhe = find_exception(&FIB_RES_NH(*res), daddr);
        if (do_cache) {
            if (fnhe) {
                rth = rcu_dereference(fnhe->fnhe_rth_input);
                if (rth && rth->dst.expires &&
                    time_after(jiffies, rth->dst.expires)) {//检验是否过期等
                    ip_del_fnhe(&FIB_RES_NH(*res), daddr);
                    fnhe = NULL;
                } else { // 如果有 且没过期可以使用,直接使用其绑定的路由缓存
                    goto rt_cache;
                }
            }
    
            rth = rcu_dereference(FIB_RES_NH(*res).nh_rth_input);
    
    rt_cache:
            if (rt_cache_valid(rth)) { // 如果有,直接使用其绑定的路由缓存
                skb_dst_set_noref(skb, &rth->dst);
                goto out;
            }
        }
        //创建新的路由缓存项
        rth = rt_dst_alloc(out_dev->dev, 0, res->type,
                   IN_DEV_CONF_GET(in_dev, NOPOLICY),
                   IN_DEV_CONF_GET(out_dev, NOXFRM), do_cache);
        if (!rth) {
            err = -ENOBUFS;
            goto cleanup;
        }
    
        rth->rt_is_input = 1;
        if (res->table)
            rth->rt_table_id = res->table->tb_id;
        RT_CACHE_STAT_INC(in_slow_tot);//统计路由cache分配次数
        //so  RT_CACHE_STAT_INC(in_slow_mc)  统计多播路由cache分配次数
    
        rth->dst.input = ip_forward;
    //对于转发路由,由函数rt_set_nexthop处理路由缓存
        rt_set_nexthop(rth, daddr, res, fnhe, res->fi, res->type, itag);
        if (lwtunnel_output_redirect(rth->dst.lwtstate)) {
            rth->dst.lwtstate->orig_output = rth->dst.output;
            rth->dst.output = lwtunnel_output;
        }
        if (lwtunnel_input_redirect(rth->dst.lwtstate)) {
            rth->dst.lwtstate->orig_input = rth->dst.input;
            rth->dst.input = lwtunnel_input;
        }
        //设置更新skb的dst  路由信息
        skb_dst_set(skb, &rth->dst);
    out:
        err = 0;
     cleanup:
        return err;
    }

      在缓存路由项时,如果缓存成功(cmpxchg),并且原有缓存值不为空,将原有路由缓存值添加到uncached_list链

    static bool rt_cache_route(struct fib_nh *nh, struct rtable *rt)
    {
        struct rtable *orig, *prev, **p;
        bool ret = true;
    
        if (rt_is_input_route(rt)) {
            p = (struct rtable **)&nh->nh_rth_input;
        } else {
            p = (struct rtable **)raw_cpu_ptr(nh->nh_pcpu_rth_output);
        }
        orig = *p;
    /*在缓存路由项时,如果缓存成功(cmpxchg),并且原有缓存值不为空,并执行释放操作(有延迟)。
    否者,如果缓存操作失败,返回错误
        */
        prev = cmpxchg(p, orig, rt);
        if (prev == orig) {
            if (orig)
                rt_free(orig);
        } else
            ret = false;
    
        return ret;
    }

     对于转发路由,由函数rt_set_nexthop处理路由缓存。

    static void rt_set_nexthop(struct rtable *rt, __be32 daddr,
                   const struct fib_result *res,
                   struct fib_nh_exception *fnhe,
                   struct fib_info *fi, u16 type, u32 itag)
    {
        bool cached = false;
    
        if (fi) {
            struct fib_nh *nh = &FIB_RES_NH(*res);
    
            if (nh->nh_gw && nh->nh_scope == RT_SCOPE_LINK) {
                rt->rt_gateway = nh->nh_gw;
                rt->rt_uses_gateway = 1;
            }
            dst_init_metrics(&rt->dst, fi->fib_metrics->metrics, true);
            if (fi->fib_metrics != &dst_default_metrics) {
                rt->dst._metrics |= DST_METRICS_REFCOUNTED;
                atomic_inc(&fi->fib_metrics->refcnt);
            }
    #ifdef CONFIG_IP_ROUTE_CLASSID
            rt->dst.tclassid = nh->nh_tclassid;
    #endif
            rt->dst.lwtstate = lwtstate_get(nh->nh_lwtstate);
    /*如果fnhe有值,由函数rt_bind_exception处理,并进行路由缓存;否则,如果do_cache为真,
    由之前介绍的函数rt_cache_route进行缓存操作。最后如果路由缓存操作失败的话,
    将路由项链接到uncached_list链表上*/
            if (unlikely(fnhe))
                cached = rt_bind_exception(rt, fnhe, daddr);
            else if (!(rt->dst.flags & DST_NOCACHE))
                cached = rt_cache_route(nh, rt);
            if (unlikely(!cached)) {
                /* Routes we intend to cache in nexthop exception or
                 * FIB nexthop have the DST_NOCACHE bit clear.
                 * However, if we are unsuccessful at storing this
                 * route into the cache we really need to set it.
                 */
                rt->dst.flags |= DST_NOCACHE;
                if (!rt->rt_gateway)
                    rt->rt_gateway = daddr;
                rt_add_uncached_list(rt);
            }
        } else {
        //如果fib_info结构变量fi为空(没有路由缓存位置),不进行路由缓存,直接加入uncached_list链表
            rt_add_uncached_list(rt);
           }
    
    #ifdef CONFIG_IP_ROUTE_CLASSID
    #ifdef CONFIG_IP_MULTIPLE_TABLES
        set_class_tag(rt, res->tclassid);
    #endif
        set_class_tag(rt, itag);
    #endif
    }

    在函数rt_bind_exception中,没有使用rt_cache_route函数中的cmpxchg指令。而是由rcu_dereference和rcu_assign_pointer进行类似的路由缓存操作,之后,如果原有缓存不为空,对其进行释放。

    static bool rt_bind_exception(struct rtable *rt, struct fib_nh_exception *fnhe,
                      __be32 daddr)
    {
        bool ret = false;
    
        spin_lock_bh(&fnhe_lock);
    /*在函数rt_bind_exception中,没有使用rt_cache_route函数中的cmpxchg指令。
    而是由rcu_dereference和rcu_assign_pointer进行类似的路由缓存操作,
    之后,如果原有缓存不为空,对其进行释放。
    如果缓存操作未执行,将由以上的调用函数rt_set_nexthop将路由缓存项添加到uncached_list链表。
        */
        if (daddr == fnhe->fnhe_daddr) {
            struct rtable __rcu **porig;
            struct rtable *orig;
            int genid = fnhe_genid(dev_net(rt->dst.dev));
    
            if (rt_is_input_route(rt))
                porig = &fnhe->fnhe_rth_input;
            else
                porig = &fnhe->fnhe_rth_output;
            orig = rcu_dereference(*porig);
    
            if (fnhe->fnhe_genid != genid) {
                fnhe->fnhe_genid = genid;
                fnhe->fnhe_gw = 0;
                fnhe->fnhe_pmtu = 0;
                fnhe->fnhe_expires = 0;
                fnhe_flush_routes(fnhe);
                orig = NULL;
            }
            fill_route_from_fnhe(rt, fnhe);
            if (!rt->rt_gateway)
                rt->rt_gateway = daddr;
    
            if (!(rt->dst.flags & DST_NOCACHE)) {
                rcu_assign_pointer(*porig, rt);
                if (orig)
                    rt_free(orig);
                ret = true;
            }
    
            fnhe->fnhe_stamp = jiffies;
        }
        spin_unlock_bh(&fnhe_lock);
    
        return ret;
    }
    View Code

    uncached_list链表删除

    //当释放路由缓存时, 检测其是否在uncached_list链表中,为真将其从链表中删除
    static void ipv4_dst_destroy(struct dst_entry *dst)
    {
        struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
        struct rtable *rt = (struct rtable *) dst;
    
        if (p != &dst_default_metrics && atomic_dec_and_test(&p->refcnt))
            kfree(p);
    
        if (!list_empty(&rt->rt_uncached)) {
            struct uncached_list *ul = rt->rt_uncached_list;
    
            spin_lock_bh(&ul->lock);
            list_del(&rt->rt_uncached);
            spin_unlock_bh(&ul->lock);
        }
    }

     当系统注销一个网络设备时,遍历所有的uncached_list链表上的路由缓存项,如果其路由设备等于要注销的设备,将设备更换为黑洞设备blackhole_netdev,路由到此设备的报文都将被丢弃。

    /*当系统注销一个网络设备时,遍历所有的uncached_list链表上的路由缓存项,
    如果其路由设备等于要注销的设备,将设备更换为黑洞设备blackhole_netdev,
    路由到此设备的报文都将被丢弃。*/
    void rt_flush_dev(struct net_device *dev)
    {
        struct net *net = dev_net(dev);
        struct rtable *rt;
        int cpu;
    
        for_each_possible_cpu(cpu) {
            struct uncached_list *ul = &per_cpu(rt_uncached_list, cpu);
    
            spin_lock_bh(&ul->lock);
            list_for_each_entry(rt, &ul->head, rt_uncached) {
                if (rt->dst.dev != dev)
                    continue;
                rt->dst.dev = net->loopback_dev;== blackhole_netdev
                dev_hold(rt->dst.dev);
                dev_put(dev);
            }
            spin_unlock_bh(&ul->lock);
        }
    }
  • 相关阅读:
    python视频教程大全(转载)
    数据挖掘十大经典算法(转载)
    等值线算法(转载)
    主成分分析PCA(转载)
    K-Means 算法(转载)
    面试常见问题小结
    二叉树的深度和宽度
    二叉树最大路径和-Binary Tree Maximum Path Sum
    C++中单例模式
    OC对象的动态和静态构造区别
  • 原文地址:https://www.cnblogs.com/codestack/p/15965641.html
Copyright © 2020-2023  润新知