码迷,mamicode.com
首页 > 系统相关 > 详细

linux路由转发表的检索过程(fib_lookup)

时间:2015-07-23 14:18:52      阅读:382      评论:0      收藏:0      [点我收藏+]

标签:

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;
}

linux路由转发表的检索过程(fib_lookup)

标签:

原文地址:http://my.oschina.net/u/2408025/blog/482738

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!