linux路由轉發表的檢索過程(fib_lookup)

1) 轉發表(fib_table)是記錄IP轉發信息的索引表, 轉發表的每一記錄(節點)描述了具備某一類目的地址的IP包應該使用哪一輸出設備發給哪一目的主機. 轉發表記錄按網絡區進行分類, 每一網絡區描述了在特定網絡地址位長下具備不一樣網絡號的目的地址的轉發信息. 第0區的網絡地址位長爲0, 與全部的IP地址都匹配, 用來描述缺省網關, 第32區的網絡地址位長爲32, 用來與完整的IP地址匹配. 在創建網絡區時, 它們按網絡地址位長從大到小的順序排列, 在搜索IP地址時, 先從全主機地址的第32區開始匹配, 最後第0區與全部的地址都匹配, 產生缺省網關.

2) 系統至少使用兩個轉發表, 一個是局部轉發表, 描述與全部本地地址匹配的轉發信息, 另外一個是主轉發表, 描述與外部地址匹配的轉發信息. 能夠經過策略表來選擇指定的轉發表.

; net/ipv4/fib_rules.c fib_frontend.c fib_hash.c fib_semantics.c

static struct fib_rule *fib_rules = &local_rule; 轉發策略鏈表

static struct fib_rule default_rule = { NULL, ATOMIC_INIT(2), 0x7FFF, RT_TABLE_DEFAULT, RTN_UNICAST, };
static struct fib_rule main_rule = { &default_rule, ATOMIC_INIT(2), 0x7FFE, RT_TABLE_MAIN, RTN_UNICAST, };
static struct fib_rule local_rule = { &main_rule, ATOMIC_INIT(2), 0, RT_TABLE_LOCAL, RTN_UNICAST, };

int fib_lookup(const struct rt_key *key, struct fib_result *res)
{
    int err;
    struct fib_rule *r, *policy;
    struct fib_table *tb;

    u32 daddr = key->dst;
    u32 saddr = key->src;

FRprintk("Lookup: %u.%u.%u.%u <- %u.%u.%u.%u ",
    NIPQUAD(key->dst), NIPQUAD(key->src));
    read_lock(&fib_rules_lock);
    for (r = fib_rules; r; r=r->r_next) { 掃描策略表
        if (((saddr^r->r_src) & r->r_srcmask) || 若是源地址不匹配
            ((daddr^r->r_dst) & r->r_dstmask) || 或者目的地址不匹配
#ifdef CONFIG_IP_ROUTE_TOS
            (r->r_tos && r->r_tos != key->tos) || 或者服務類型不等
#endif
#ifdef CONFIG_IP_ROUTE_FWMARK
            (r->r_fwmark && r->r_fwmark != key->fwmark) || 或者轉發標記不等
#endif
            (r->r_ifindex && r->r_ifindex != key->iif)) 或者輸入設備不等
            continue; 下一策略

FRprintk("tb %d r %d ", r->r_table, r->r_action);
        switch (r->r_action) { 策略所表示操做
        case RTN_UNICAST: 單目轉發
        case RTN_NAT: 地址變換轉發
            policy = r; 獲取匹配的策略
            break;
        case RTN_UNREACHABLE: 不可達的轉發
            read_unlock(&fib_rules_lock);
            return -ENETUNREACH;
        default:
        case RTN_BLACKHOLE: 轉發到黑洞
            read_unlock(&fib_rules_lock);
            return -EINVAL;
        case RTN_PROHIBIT: 禁止轉發
            read_unlock(&fib_rules_lock);
            return -EACCES;
        }

        if ((tb = fib_get_table(r->r_table)) == NULL) 取策略對應的轉發表
            continue;
        err = tb->tb_lookup(tb, key, res); 查詢轉發表
        if (err == 0) { 查詢成功
            res->r = policy; 在轉發信息中設置該策略表地址
            if (policy)
                atomic_inc(&policy->r_clntref); 引用策略表
            read_unlock(&fib_rules_lock);
            return 0;
        }
        if (err < 0 && err != -EAGAIN) {
            read_unlock(&fib_rules_lock);
            return err; 錯誤
        }
    }
FRprintk("FAILURE\n");
    read_unlock(&fib_rules_lock);
    return -ENETUNREACH;
}

static inline struct fib_table *fib_get_table(int id)
{
    if (id == 0)
        id = RT_TABLE_MAIN;

    return fib_tables[id];
}

static inline struct fib_table *fib_new_table(int id)
{
    if (id == 0) 0號轉發表爲主轉發表
        id = RT_TABLE_MAIN;

    return fib_tables[id] ? : __fib_new_table(id);
}
struct fib_table *__fib_new_table(int id)
{
    struct fib_table *tb;

    tb = fib_hash_init(id);
    if (!tb)
        return NULL;
    fib_tables[id] = tb;
    return tb;
}

/* Reserved table identifiers */

enum rt_class_t
{
    RT_TABLE_UNSPEC=0,
/* User defined values */
    RT_TABLE_DEFAULT=253,
    RT_TABLE_MAIN=254,
    RT_TABLE_LOCAL=255
};
#define RT_TABLE_MAX RT_TABLE_LOCAL

struct fib_table *fib_tables[RT_TABLE_MAX+1]; 轉發表數組

#ifdef CONFIG_IP_MULTIPLE_TABLES
struct fib_table * fib_hash_init(int id)
#else
struct fib_table * __init fib_hash_init(int id)
#endif
{
    struct fib_table *tb;

    if (fn_hash_kmem == NULL)
        fn_hash_kmem = kmem_cache_create("ip_fib_hash",
                         sizeof(struct fib_node),
                         0, SLAB_HWCACHE_ALIGN,
                         NULL, NULL);

    tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash), GFP_KERNEL);
    if (tb == NULL) 分配轉發表操做結構和索引結構
        return NULL;

    tb->tb_id = id;
    tb->tb_lookup = fn_hash_lookup; 檢索轉發表
    tb->tb_insert = fn_hash_insert; 插入轉發表記錄
    tb->tb_delete = fn_hash_delete; 刪除轉發表記錄
    tb->tb_flush = fn_hash_flush; 洗刷轉發表無效記錄
    tb->tb_select_default = fn_hash_select_default; 選取缺省的轉發信息
#ifdef CONFIG_RTNETLINK
    tb->tb_dump = fn_hash_dump; 向鏈路套接字傾倒轉發表記錄
#endif
#ifdef CONFIG_PROC_FS
    tb->tb_get_info = fn_hash_get_info; 從PROC文件系統顯示轉發表信息
#endif
    memset(tb->tb_data, 0, sizeof(struct fn_hash)); 清除散列盤
    return tb;
}

static int
fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
{
    int err;
    struct fn_zone *fz;
    struct fn_hash *t = (struct fn_hash*)tb->tb_data;

    read_lock(&fib_hash_lock);
    for (fz = t->fn_zone_list; fz; fz = fz->fz_next) { 掃描網絡區
        struct fib_node *f;
        fn_key_t k = fz_key(key->dst, fz); 取目標地址在該網絡區的網絡號
        for (f = fz_chain(k, fz); f; f = f->fn_next) { 掃描該網絡號的散列鏈
            if (!fn_key_eq(k, f->fn_key)) { 若是與鏈節的網絡號不相等
                if (fn_key_leq(k, f->fn_key)) 若是網絡號比鏈節的網絡號小(散列鏈是按網絡號從小到大排序的)
                    break; 下一轉發區
                else
                    continue; 下一鏈節
            }
            ; 搜索到網絡號相等的節點
#ifdef CONFIG_IP_ROUTE_TOS
            if (f->fn_tos && f->fn_tos != key->tos) 若是它們的服務類型不等
                continue; 下一鏈節
#endif
            f->fn_state |= FN_S_ACCESSED; 訪問標記

            if (f->fn_state&FN_S_ZOMBIE) 若是該節點無效
                continue; 下一鏈節
            if (f->fn_scope < key->scope) 若是該節點的轉發域值小於請求的轉發域值(或者說該節點的轉發域大於請求的轉發域)
                continue; 下一鏈節
            
            err = fib_semantic_match(f->fn_type, FIB_INFO(f), key, res); 進一步與節點轉發信息匹配
            if (err == 0) { 匹配成功
                res->type = f->fn_type; 輸出轉發類型
                res->scope = f->fn_scope; 輸出轉發域
                res->prefixlen = fz->fz_order; 輸出該區網絡地址所佔位長
                goto out; 返回
            }
            if (err < 0)
                goto out;
        }
    }
    err = 1;
out:
    read_unlock(&fib_hash_lock);
    return err;
}

static struct
{
    int    error;
    u8    scope;
} fib_props[RTA_MAX+1] = {
        { 0, RT_SCOPE_NOWHERE},        /* RTN_UNSPEC */  沒法轉發
    { 0, RT_SCOPE_UNIVERSE},    /* RTN_UNICAST */ 全網範圍轉發
    { 0, RT_SCOPE_HOST},        /* RTN_LOCAL */   本機範圍轉發
    { 0, RT_SCOPE_LINK},        /* RTN_BROADCAST */ 設備範圍轉發
    { 0, RT_SCOPE_LINK},        /* RTN_ANYCAST */
    { 0, RT_SCOPE_UNIVERSE},    /* RTN_MULTICAST */
    { -EINVAL, RT_SCOPE_UNIVERSE},    /* RTN_BLACKHOLE */
    { -EHOSTUNREACH, RT_SCOPE_UNIVERSE},/* RTN_UNREACHABLE */
    { -EACCES, RT_SCOPE_UNIVERSE},    /* RTN_PROHIBIT */
    { -EAGAIN, RT_SCOPE_UNIVERSE},    /* RTN_THROW */
#ifdef CONFIG_IP_ROUTE_NAT
    { 0, RT_SCOPE_HOST},        /* RTN_NAT */
#else
    { -EINVAL, RT_SCOPE_NOWHERE},    /* RTN_NAT */
#endif
    { -EINVAL, RT_SCOPE_NOWHERE}    /* RTN_XRESOLVE */
};

#define FIB_RES_NH(res)        ((res).fi->fib_nh[(res).nh_sel])
#define FIB_RES_RESET(res)    ((res).nh_sel = 0)

#define for_nexthops(fi) { int nhsel; const struct fib_nh * nh; \
for (nhsel=0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)

int
fib_semantic_match(int type, struct fib_info *fi, const struct rt_key *key, struct fib_result *res)
{
    int err = fib_props[type].error; 取轉發類型錯誤碼

    if (err == 0) { 容許的轉發類型
        if (fi->fib_flags&RTNH_F_DEAD) 若是該轉發節點不通
            return 1;

        res->fi = fi; 輸出轉發節點信息

        switch (type) {
#ifdef CONFIG_IP_ROUTE_NAT
        case RTN_NAT: 地址變換轉發
            FIB_RES_RESET(*res); 復位轉發地址選擇編號
            atomic_inc(&fi->fib_clntref);
            return 0;
#endif
        case RTN_UNICAST: 單目轉發
        case RTN_LOCAL: 本地轉發
        case RTN_BROADCAST: 廣播轉發
        case RTN_ANYCAST: 任意轉發
        case RTN_MULTICAST: 多目轉發
            for_nexthops(fi) { 對於轉發信息中的每個轉發地址
                if (nh->nh_flags&RTNH_F_DEAD) 若是轉發地址不通
                    continue; 下一轉發地址
                if (!key->oif || key->oif == nh->nh_oif) 匹配轉發地址的輸出設備
                    break; 匹配成功
            }
#ifdef CONFIG_IP_ROUTE_MULTIPATH 多徑路由
            if (nhsel < fi->fib_nhs) {
                res->nh_sel =     nhsel; 輸出轉發地址編號
                atomic_inc(&fi->fib_clntref);
                return 0; 成功返回
            }
#else
            if (nhsel < 1) { 非多徑路由轉發地址編號必須小於1
                atomic_inc(&fi->fib_clntref);
                return 0; 成功返回
            }
#endif
            endfor_nexthops(fi);
            res->fi = NULL;
            return 1; 匹配失敗
        default:
            res->fi = NULL;
            printk(KERN_DEBUG "impossible 102\n");
            return -EINVAL;
        }
    }
    return err;
}node

相關文章
相關標籤/搜索