天天看點

路由fib表之input查找

3.6版本以前的路由緩存

  緩存無處不在。現代計算機系統中,Cache是CPU與記憶體間存在一種容量較小但速度很高的存儲器,用來存放​

​CPU​

​剛使用過或最近使用的資料。路由緩存就是基于這種思想的軟體實作。核心查詢FIB前,固定先查詢cache中的記錄,如果cache命中(hit),那就直接用就好了,不必查詢FIB。如果沒有命中(miss), 就回過頭來查詢FIB,最終将結果儲存到cache,以便下次不再需要需要查詢FIB。

緩存是精确比對的, 每一條緩存表項記錄了比對的源位址和目的位址、接收發送的​

​dev​

​​,以及與核心鄰居系統(L2層)的聯系(​

​negghbour​

​​) ​

​FIB​

​​中存儲的也就是路由資訊,它常常是範圍比對的,比如像​

​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等 就不友好了!

路由fib表之input查找

 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進行類似的路由緩存操作,之後,如果原有緩存不為空,對其進行釋放。

路由fib表之input查找
路由fib表之input查找
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);
    }
}      

繼續閱讀