FIB系统分析二(linux网络协议栈笔记)

来源:互联网 发布:android 网络图片尺寸 编辑:程序博客网 时间:2024/04/25 07:31

FIB配置过程续

当给设备设置IP地址的的时候,内核给inetdev_chain通知链发送了一个NETDEV_UP事件,FIB系统正好对这个事件感兴趣,就把下面这个结构注册到了inetaddr_chain上:

static struct notifier_block fib_inetaddr_notifier = {     .notifier_call = fib_inetaddr_event,  }; 

为什么FIB系统会对这个事情感兴趣?因为给设备分配IP地址相对于给系统增加一条type为RTN_LOCAL的路由,其路由范围是RT_SCOPE_HOST,没错吧?给系统增加IP地址,其实也就和路由软件(OSPF、RIP等)往内核里增加路由表项一样,基本的流程相同,只是个别参数不同而已(我甚至觉得,路由可以理解为地址,区别在于这是别人的地址,以后如果实在不理解路由的本质,你就把它认为是别人的地址就得了)。重点在这里是你了解了FIB系统是如何增删路由表项的。

其回调函数就是fib_inetaddr_event,此函数如果收到NETDEV_DOWN消息,它就删除FIB中存在的地址,如果ifa_dev->ifa_list是空,则disable这个设备,否则就刷新一下路由cache(注意,这里是路由cache,不是FIB表)
这里写图片描述

很明显,目前的流程是走向了左边。其参数就是inet_insert_ifa中传入的ifa。我们只是访问其成员变量,而不再更改。

void fib_add_ifaddr(struct in_ifaddr *ifa){    struct in_device *in_dev = ifa->ifa_dev;    struct net_device *dev = in_dev->dev;    struct in_ifaddr *prim = ifa;    __be32 mask = ifa->ifa_mask;    __be32 addr = ifa->ifa_local;    __be32 prefix = ifa->ifa_address & mask;    if (ifa->ifa_flags & IFA_F_SECONDARY) {        prim = inet_ifa_byprefix(in_dev, prefix, mask);        if (!prim) {            pr_warn("%s: bug: prim == NULL\n", __func__);            return;        }    }    //如果是loopback接口配置,addr是127.0.0.1,而配置例子中192.168.18.2    fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);    if (!(dev->flags & IFF_UP))        return;    /* Add broadcast address, if it is explicitly assigned. */    if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))     //ifa->ifa_broadcast192.168.18.255,而对于loopback接口是不会进入这一行        fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);    if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags & IFA_F_SECONDARY) &&        (prefix != addr || ifa->ifa_prefixlen < 32)) {         //第二个参数loopback接口是RTN_LOCAL, prefix是127,而配置例子对应的是RTN_UNICAST,prefix是192.168.18,这导致它们存取的FIB表不一样。        if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE))            fib_magic(RTM_NEWROUTE,                  dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,                  prefix, ifa->ifa_prefixlen, prim);        /* Add network specific broadcasts, when it takes a sense */        if (ifa->ifa_prefixlen < 31) {         //loopback接口prefix是127, 而配置例子是192.168.18            fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim);         //loopback接口的prefix|~mask是255.255.255.127,而配置例子是192.168.18.255            fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix | ~mask,                  32, prim);        }    }}

我们已经给出函数中调用fib_magic的第三个参数。根据这些参数我们看看它的执行是什么结果:

/*创建并初始化一个核内路由命令消息,这实际是和netlink消息的处理过程 *(请参考inet_rtm_newaddr函数)一样, * 但是又不能直接引用netlink的代码,因为不太好设置传入的参数。在处理这些FIB 引擎时,netlink已经被锁住*/static void fib_magic(int cmd, int type, __le16 dst, int dst_len, struct dn_ifaddr *ifa){    struct dn_fib_table *tb;    struct {        struct nlmsghdr nlh;        struct rtmsg rtm;    } req;    struct {        struct nlattr hdr;        __le16 dst;    } dst_attr = {        .dst = dst,    };    struct {        struct nlattr hdr;        __le16 prefsrc;    } prefsrc_attr = {        .prefsrc = ifa->ifa_local,    };    struct {        struct nlattr hdr;        u32 oif;    } oif_attr = {        .oif = ifa->ifa_dev->dev->ifindex,    };    struct nlattr *attrs[RTA_MAX+1] = {        [RTA_DST] = (struct nlattr *) &dst_attr,        [RTA_PREFSRC] = (struct nlattr * ) &prefsrc_attr,        [RTA_OIF] = (struct nlattr *) &oif_attr,    };    memset(&req.rtm, 0, sizeof(req.rtm));//获得ip_fib_main_table或ip_fib_local_table的指针,而不是根据其函数名创建一个新的FIB表    if (type == RTN_UNICAST)        tb = dn_fib_get_table(RT_MIN_TABLE, 1);    else        tb = dn_fib_get_table(RT_TABLE_LOCAL, 1);    if (tb == NULL)        return;//下面就开始构造一个nlm和rtm消息体,其实仅仅是把它们传入tb_insert函数,而非要把他们真正发送出去    req.nlh.nlmsg_len = sizeof(req);    req.nlh.nlmsg_type = cmd;    req.nlh.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND;    req.nlh.nlmsg_pid = 0;    req.nlh.nlmsg_seq = 0;    req.rtm.rtm_dst_len = dst_len;    req.rtm.rtm_table = tb->n; //要记住这种方式下尽管是用户运行ifconfig命令产生了路由,但实际是由内核产生了路由    req.rtm.rtm_protocol = RTPROT_KERNEL;    req.rtm.rtm_scope = (type != RTN_LOCAL ? RT_SCOPE_LINK : RT_SCOPE_HOST);    req.rtm.rtm_type = type;//往系统配置地址本质就是往路由系统增加一个比较特殊的路由,所以cmd是NEWROUTE,而不是NEWADDR    if (cmd == RTM_NEWROUTE)        tb->insert(tb, &req.rtm, attrs, &req.nlh, NULL);    else        tb->delete(tb, &req.rtm, attrs, &req.nlh, NULL);}

tb->insert在此指向了fn_hash_insert

static int fn_hash_insert(struct fib_table *tb, struct fib_config *cfg){    struct fn_hash *table = (struct fn_hash *) tb->tb_data;    struct fib_node *new_f, *f;    struct fib_alias *fa, *new_fa;    struct fn_zone *fz;    struct fib_info *fi;    u8 tos = cfg->fc_tos;    __be32 key;    int err;    /*cfg->fc_dst_len网络掩码长度*/    if (cfg->fc_dst_len > 32)        return -EINVAL;    /*根据掩码长度获取相应的fn_zone*/    fz = table->fn_zones[cfg->fc_dst_len];    if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len)))        return -ENOBUFS;    /*根据路由的目的地址与掩码的值,获取该目的地址对应的网络地址。即搜索关键字*/    key = 0;    if (cfg->fc_dst) {        if (cfg->fc_dst & ~FZ_MASK(fz))            return -EINVAL;        key = fz_key(cfg->fc_dst, fz);    }    /*根据用户传递的参数构建fib_info结构变量*/    fi = fib_create_info(cfg);    if (IS_ERR(fi))        return PTR_ERR(fi);    /*如果在当前fn_zone变量的hash链表中添加的fib_node节点的数目已经大于当前    fn_zone变量的最大值时,则对该fn_zone变量的hash链表数组进行容量扩充。    扩充操作由函数fn_rehash_zone完成    */    if (fz->fz_nent > (fz->fz_divisor<<1) &&        fz->fz_divisor < FZ_MAX_DIVISOR &&            (cfg->fc_dst_len == 32 ||             (1 << cfg->fc_dst_len) > fz->fz_divisor))            fn_rehash_zone(fz);    f = fib_find_node(fz, key);    if (!f)        fa = NULL;    else        fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority);/* Now fa, if non-NULL, points to the first fib alias * with the same keys [prefix,tos,priority], if such key already * exists or to the node before which we will insert new one. * * If fa is NULL, we will need to allocate a new one and * insert to the head of f. * * If f is NULL, no fib node matched the destination key * and we need to allocate a new one of those as well. *//*当一个fib_alias变量的tos与要添加的路由的tos相等,且该fib_alias关联的fib_info变量的优先级与要添加的路由的优先级也相等时a)若应用层添加路由的操作置位了flag的NLM_F_EXCL位时,则程序返回失败(路由已存在)b)若应用层添加路由的操作置位了flag的NLM_F_REPLACE位时(即替换已存在的路由时),则   替换已存在且相等的路由项的fib_alias、fib_info变量c)对于不满足上面a)、b)两点,则表示是需要添加的路由,此时就需要对fib_node下的路由项   进行精确匹配,即判断tos、type、scope、priority以及fib_info的匹配,   i)若找到一个匹配的路由项,则说明路由项已存在,不进行添加操作,程序返回   ii)若没有找到,则说明不存在相同的路由项,则执行添加操作。*/    if (fa && fa->fa_tos == tos &&        fa->fa_info->fib_priority == fi->fib_priority) {    struct fib_alias *fa_orig;    err = -EEXIST;    if (cfg->fc_nlflags & NLM_F_EXCL)    goto out;    if (cfg->fc_nlflags & NLM_F_REPLACE) {    struct fib_info *fi_drop;    u8 state;    write_lock_bh(&fib_hash_lock);    fi_drop = fa->fa_info;    fa->fa_info = fi;    fa->fa_type = cfg->fc_type;    fa->fa_scope = cfg->fc_scope;    state = fa->fa_state;    fa->fa_state &= ~FA_S_ACCESSED;    fib_hash_genid++;    write_unlock_bh(&fib_hash_lock);    fib_release_info(fi_drop);    if (state & FA_S_ACCESSED)    rt_cache_flush(-1);    return 0;    }    /* Error if we find a perfect match which     * uses the same scope, type, and nexthop     * information.     */    fa_orig = fa;    fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);    list_for_each_entry_continue(fa, &f->fn_alias, fa_list) {    if (fa->fa_tos != tos)        break;    if (fa->fa_info->fib_priority != fi->fib_priority)        break;    if (fa->fa_type == cfg->fc_type &&        fa->fa_scope == cfg->fc_scope &&        fa->fa_info == fi)    goto out;    }    /*这个主要是用于在表头添加fib_alias还是在表尾添加fib_alias*/    if (!(cfg->fc_nlflags & NLM_F_APPEND))    fa = fa_orig;    }    /*若用户传递过来的配置中,没有对flag的NLM_F_CREATE位置位,则不进行添加操作,程序返回*/    err = -ENOENT;    if (!(cfg->fc_nlflags & NLM_F_CREATE))        goto out;/*1.创建一个新的fib_alias变量2.若fib_node变量也不存在,则创建新的fib_node变量,  并设置fn_key的值,并对fn_hash、fn_alias成员边界进行初始化;若已存在,则执行33.为新创建的fib_alias变量的fa_info、fa_tos、fa_type、fa_scope、fa_state变量进行赋值4.若fib_node是新创建的,则调用fib_insert_node将该fib_node变量插入到fib_node->fz_hash[]相对   应的hash链表中,且fn_zone->fz_nent的统计计数加15.将新创建的fib_alias变量添加到fib_node->fn_alias链表中对应的位置。      */    err = -ENOBUFS;    new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);    if (new_fa == NULL)        goto out;    new_f = NULL;    if (!f) {        new_f = kmem_cache_alloc(fn_hash_kmem, GFP_KERNEL);        if (new_f == NULL)        goto out_free_new_fa;        INIT_HLIST_NODE(&new_f->fn_hash);        INIT_LIST_HEAD(&new_f->fn_alias);        new_f->fn_key = key;        f = new_f;    }    new_fa->fa_info = fi;    new_fa->fa_tos = tos;    new_fa->fa_type = cfg->fc_type;    new_fa->fa_scope = cfg->fc_scope;    new_fa->fa_state = 0;/* * Insert new entry to the list. */    write_lock_bh(&fib_hash_lock);    if (new_f)        fib_insert_node(fz, new_f);    list_add_tail(&new_fa->fa_list,     (fa ? &fa->fa_list : &f->fn_alias));    fib_hash_genid++;    write_unlock_bh(&fib_hash_lock);    if (new_f)        fz->fz_nent++;    rt_cache_flush(-1);    rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id,      &cfg->fc_nlinfo);    return 0;    out_free_new_fa:    kmem_cache_free(fn_alias_kmem, new_fa);out:fib_release_info(fi);return err;}

当对应掩码长度的zone没有被创建时,就应该创建一个。里面的成员基本上都能根据掩码的长度z来确定。

static struct fn_zone *fn_new_zone(struct fn_hash *table, int z){    int i;    struct fn_zone *fz = kzalloc(sizeof(struct fn_zone), GFP_KERNEL);    if (!fz)        return NULL;    /*默认创建16个hash链表,每一个hash链表都用来将掩码为z的路由链接在一起*/    if (z) {        fz->fz_divisor = 16;    } else {        fz->fz_divisor = 1;    }    fz->fz_hashmask = (fz->fz_divisor - 1);    fz->fz_hash = fz_hash_alloc(fz->fz_divisor);    if (!fz->fz_hash) {        kfree(fz);        return NULL;    }    memset(fz->fz_hash, 0, fz->fz_divisor * sizeof(struct hlist_head *));    /*设置掩码长度,并根据掩码长度设置掩码,存放在fz_mask里*/    fz->fz_order = z;    fz->fz_mask = inet_make_mask(z);/*1.查找路由表的fn_zone数组中,是否已经创建了比当前创建的fn_zone的掩码更大的,a)若查找到第一个符合要求的fn_zone,则将fn_zone的next指针指向当前创建的fn_zoneb)若没有查找到,则当前创建的fn_zone,在所有已创建的fn_zone的掩码最大,则将该fn_zone插入到table->fn_zone_list的表头。 这样操作主要是由于其路由查找是通过最长匹配来实现的, 当查找一个路由时,我们首先搜索掩码最长的fn_zone。这样保证了精确匹配。*//* Find the first not empty zone with more specific mask */    for (i=z+1; i<=32; i++)        if (table->fn_zones[i])            break;        write_lock_bh(&fib_hash_lock);        if (i>32) {/* No more specific masks, we are the first. */            fz->fz_next = table->fn_zone_list;            table->fn_zone_list = fz;        } else {        fz->fz_next = table->fn_zones[i]->fz_next;        table->fn_zones[i]->fz_next = fz;    }    table->fn_zones[z] = fz;    fib_hash_genid++;    write_unlock_bh(&fib_hash_lock);    return fz;}

按上节说的FIB库分层结构,创建了zone就应该创建fib_node{}了,可是没有,它先创建了fib_info{},先硬着头皮往下看吧。

/*功能:创建一个struct fib_info结构的变量1.当前fib_info的数目大于等于fib_hash_size时,要对hash表fib_info_hash、fib_info_laddrhash的内存空间扩容12.创建一个fib_info结构的变量,为该fib_info结构变量的fib_protocol、fib_flags、   fib_priority、fib_prefsrc成员进行赋值,并增加fib_info_cnt的统计计数3.设置该fib_info变量的所有fib_nh变量的nh_parent指针指向该fib_info4.根据传递的值,设置fib_metrics的值5.判断应用层传递的路由项的fc_scope值是否正确,若不正确,则程序返回;  若正确,则继续执行6.对下一跳网关对应的fib_nh结构变量的nh_scope、nh_dev等成员项进行赋值。7.调用fib_find_info,判断刚申请并初始化的变量是否已存在系统中: 若存在,则对原来的fib_info变量的fib_treeref计数加一即可,则可以释放掉新申请的 fib_info变量占用的内存; 若不存在,则将新创建的fib_info变量添加到系统的hash表中。*/struct fib_info *fib_create_info(struct fib_config *cfg){    int err;    struct fib_info *fi = NULL;    struct fib_info *ofi;    int nhs = 1;    /* Fast check to catch the most weird cases, 所有类型的scope都预定义了一个最大值 */    if (fib_props[cfg->fc_type].scope > cfg->fc_scope)        goto err_inval;#ifdef CONFIG_IP_ROUTE_MULTIPATH    if (cfg->fc_mp) {        /* 计算下一跳个数 */        nhs = fib_count_nexthops(cfg->fc_mp, cfg->fc_mp_len);        if (nhs == 0)            goto err_inval;    }#endif#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED    if (cfg->fc_mp_alg) {        if (cfg->fc_mp_alg < IP_MP_ALG_NONE ||            cfg->fc_mp_alg > IP_MP_ALG_MAX)            goto err_inval;    }#endif/*当前fib_info的数目大于等于fib_hash_size时,要对hash表fib_info_hash、fib_info_laddrhash的内存空间扩容1倍*/      err = -ENOBUFS;    if (fib_info_cnt >= fib_hash_size) {        /* fib_info个数大于fib_node哈希表的大小时扩容 */        unsigned int new_size = fib_hash_size << 1;        struct hlist_head *new_info_hash;        struct hlist_head *new_laddrhash;        unsigned int bytes;        if (!new_size)            new_size = 1;        bytes = new_size * sizeof(struct hlist_head *);        new_info_hash = fib_hash_alloc(bytes);        new_laddrhash = fib_hash_alloc(bytes);        if (!new_info_hash || !new_laddrhash) {            fib_hash_free(new_info_hash, bytes);            fib_hash_free(new_laddrhash, bytes);        } else {            memset(new_info_hash, 0, bytes);            memset(new_laddrhash, 0, bytes);            /* fib_info移动到新的链表中 */            fib_hash_move(new_info_hash, new_laddrhash, new_size);        }        if (!fib_hash_size)            goto failure;    }/*创建一个fib_info结构的变量*/  fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);  if (fi == NULL)        goto failure;    fib_info_cnt++;/*为该fib_info结构变量的fib_protocol、fib_flags、fib_priority、fib_prefsrc成员进行赋值*/    fi->fib_protocol = cfg->fc_protocol;    fi->fib_flags = cfg->fc_flags;    fi->fib_priority = cfg->fc_priority;    fi->fib_prefsrc = cfg->fc_prefsrc;/*下一跳网关对应的的fib_nh结构变量的个数*/    fi->fib_nhs = nhs;/*设置该fib_info变量的所有fib_nh变量的nh_parent指针指向该fib_info*/    change_nexthops(fi) {        nh->nh_parent = fi;    } endfor_nexthops(fi)/*若应用层有传递设置fib_metrics的参数,则下面的代码片段用来对fib_metrics中的各值进行赋值*/    if (cfg->fc_mx) {        struct nlattr *nla;        int remaining;        nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining) {            int type = nla->nla_type;                if (type) {                    if (type > RTAX_MAX)                        goto err_inval;                    fi->fib_metrics[type - 1] = nla_get_u32(nla);                    }                }            }/*1.当内核支持多路径路由时,则应用层传递的fc_mp大于0时,  则调用fib_get_nhs进行设置所有的fib_nh.2.当内核不支持多路径路由时,且应用层传递的fc_map大于0时,则返回出错。3.当应用层传递的fc_map为0时,则对该fib_info的fib_nh变量的的网关ip、输      出接口、flag等进行赋值。*/        if (cfg->fc_mp) {#ifdef CONFIG_IP_ROUTE_MULTIPATH        err = fib_get_nhs(fi, cfg->fc_mp, cfg->fc_mp_len, cfg);        if (err != 0)                goto failure;        if (cfg->fc_oif && fi->fib_nh->nh_oif != cfg->fc_oif)                goto err_inval;        if (cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw)                goto err_inval;#ifdef CONFIG_NET_CLS_ROUTE        if (cfg->fc_flow && fi->fib_nh->nh_tclassid != cfg->fc_flow)                goto err_inval;#endif#else        goto err_inval;#endif        } else {        struct fib_nh *nh = fi->fib_nh;            nh->nh_oif = cfg->fc_oif;            nh->nh_gw = cfg->fc_gw;            nh->nh_flags = cfg->fc_flags;#ifdef CONFIG_NET_CLS_ROUTE            nh->nh_tclassid = cfg->fc_flow;#endif#ifdef CONFIG_IP_ROUTE_MULTIPATH            nh->nh_weight = 1;#endif        }#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED        fi->fib_mp_alg = cfg->fc_mp_alg;#endif        if (fib_props[cfg->fc_type].error) {                if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)                        goto err_inval;                goto link_it;            }/*对于应用层创建的路由,如果其路由scope大于RT_SCOPE_HOST,则返回错误*/        if (cfg->fc_scope > RT_SCOPE_HOST)                goto err_inval;/*1.当创建路由的scope值为RT_SCOPE_HOST,说明这是一个到本地接口的变量, 则此时的fib_info的fib_nh结构的成员变量的scope需要设置为RT_SCOPE_NOWHERE,并设置nh_dev的值  a)若nhs值大于1时,则说明路由不对,因为对于scope为RT_SCOPE_HOST,其nhs是不可能大于1的  b)若nhs为1,但是fib_info->fib_nh->nh_gw不为0时,则说明路由不对,因为若下一跳网关的地址不为0,则当前路由的scope必须小于等于 RT_SCOPE_UNIVERSE。2.当创建路由的scope值小于RT_SCOPE_HOST时,则对于该fib_info变量下的所有fib_nh结构的变量,调用fib_check_nh函数进行合法性检查及设置到达下一跳地址的出口设备*/    if (cfg->fc_scope == RT_SCOPE_HOST) {                struct fib_nh *nh = fi->fib_nh;        /* Local address is added. */            if (nhs != 1 || nh->nh_gw)                goto err_inval;            nh->nh_scope = RT_SCOPE_NOWHERE;            nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);                err = -ENODEV;            if (nh->nh_dev == NULL)            goto failure;            } else {                change_nexthops(fi) {                if ((err = fib_check_nh(cfg, fi, nh)) != 0)                    goto failure;            } endfor_nexthops(fi)        }        /*首先源地址?*/        if (fi->fib_prefsrc) {            if (cfg->fc_type != RTN_LOCAL || !cfg->fc_dst ||            fi->fib_prefsrc != cfg->fc_dst)            if (inet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)            goto err_inval;        }/*1.若刚创建的fib_info结构的变量已经存在,则释放该fib_info变量,程序返回;否则进入22.将该fib_info变量添加到相应的hash链表fib_info_hash[fib_info_hashfn(fi)]中3.若该fib_info变量的首先源地址不为空,则将该fib_info变量添加到相应的hash链表fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)]中4.对于该fib_info变量的所有对应的fib_nh结构的变量中,若fib_nh->nh_dev不为空,则将该fib_nh变量添加到hash数组fib_info_devhash对应的hash链表中5.程序返回已创建的fib_info变量 */link_it:    if ((ofi = fib_find_info(fi)) != NULL) {        /* 已经存在相同的fib_info, 直接返回它 */        fi->fib_dead = 1;        free_fib_info(fi);        ofi->fib_treeref++;        return ofi;    }    /* 表示已经被fib_alias引用 */    fi->fib_treeref++;    /* fib_info本身被引用的次数 */    atomic_inc(&fi->fib_clntref);    spin_lock_bh(&fib_info_lock);    /* 链入fib_info_hash */    hlist_add_head(&fi->fib_hash,               &fib_info_hash[fib_info_hashfn(fi)]);    /* 指定了preferred src 则链入fib_info_laddrhash */    if (fi->fib_prefsrc) {        struct hlist_head *head;        head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)];        hlist_add_head(&fi->fib_lhash, head);    }    change_nexthops(fi) {        struct hlist_head *head;        unsigned int hash;        if (!nh->nh_dev)            continue;        /* fib_nh链入fib_info_devhash */        hash = fib_devindex_hashfn(nh->nh_dev->ifindex);        head = &fib_info_devhash[hash];        hlist_add_head(&nh->nh_hash, head);    } endfor_nexthops(fi)    spin_unlock_bh(&fib_info_lock);    return fi;err_inval:    err = -EINVAL;failure:    if (fi) {        fi->fib_dead = 1;        free_fib_info(fi);    }    return ERR_PTR(err);}

从这份代码可以推断出fib_infofib_nh的关系,要注意的是fib_nh里的nh_dev是根据自身的nh_oif查找到的,这是fib_magic函数里把ifa->ifa_dev->dev->ifindex赋给rta.rta_oif,然后传到这里来的。

static int fib_check_nh(struct fib_config *cfg, struct fib_info *fi,            struct fib_nh *nh){    int err;    if (nh->nh_gw) {        /* 其他路由, 带下一跳网关 */        struct fib_result res;#ifdef CONFIG_IP_ROUTE_PERVASIVE        if (nh->nh_flags&RTNH_F_PERVASIVE)            return 0;#endif        /* 下一跳网关与本地直连 */        if (nh->nh_flags&RTNH_F_ONLINK) {            struct net_device *dev;            /* 路由的scope必须大于下一跳的scope, 一般为RT_SCOPE_UNIVERSE(实际数值的大小是相反的, RT_SCOPE_UNIVERSE是0) */            if (cfg->fc_scope >= RT_SCOPE_LINK)                return -EINVAL;            if (inet_addr_type(nh->nh_gw) != RTN_UNICAST)                return -EINVAL;            if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)                return -ENODEV;            if (!(dev->flags&IFF_UP))                return -ENETDOWN;            nh->nh_dev = dev;            dev_hold(dev);            nh->nh_scope = RT_SCOPE_LINK;            return 0;        }        /* 下一跳没有与本地直连, 必须搜索到达该下一跳的路由 */        {            struct flowi fl = {                .nl_u = {                    .ip4_u = {                        .daddr = nh->nh_gw,                        .scope = cfg->fc_scope + 1,                    },                },                .oif = nh->nh_oif,            };            /* It is not necessary, but requires a bit of thinking */            if (fl.fl4_scope < RT_SCOPE_LINK)                fl.fl4_scope = RT_SCOPE_LINK;            if ((err = fib_lookup(&fl, &res)) != 0)                return err;        }        err = -EINVAL;        /* 到达该下一跳必须是单播路由或本机? */        if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)            goto out;        /* 下一跳的scope是到达该下一跳路由的scope */        nh->nh_scope = res.scope;        nh->nh_oif = FIB_RES_OIF(res);        if ((nh->nh_dev = FIB_RES_DEV(res)) == NULL)            goto out;        dev_hold(nh->nh_dev);        err = -ENETDOWN;        if (!(nh->nh_dev->flags & IFF_UP))            goto out;        err = 0;out:        fib_res_put(&res);        return err;    } else {        /* 其他路由, 下一跳为本地接口 */        struct in_device *in_dev;        if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))            return -EINVAL;        in_dev = inetdev_by_index(nh->nh_oif);        if (in_dev == NULL)            return -ENODEV;        if (!(in_dev->dev->flags&IFF_UP)) {            in_dev_put(in_dev);            return -ENETDOWN;        }        nh->nh_dev = in_dev->dev;        dev_hold(nh->nh_dev);        /* 通过本地接口地址作为下一跳网关 */        nh->nh_scope = RT_SCOPE_HOST;        in_dev_put(in_dev);    }    return 0;}

这里写图片描述

让我们放大fib_node的结构,并将其与fib_zone联系起来,于是得到下面这张图:
这里写图片描述
通过这么一段时间的研究,可以这样理解这些结构之间的关系:
1,fib_table中包含fn_hash结构指针
2,fn_hash中包含fn_zone的数组,按照目的地址长度进行分类,相同长度的地址共用一个fz_zone
3, fn_key相同的两条路由(同一子网),共享一个路由节点(fn_node
4,根据具体子网内地址的不同,使用不同的fib_aliasfib_info
5,目的地址相同的情况下,也可以使用多条路由,不同的路由存放在不同的fib_nh里面fib_create_info函数中牵涉到3个hash表:fib_info_hashfib_info_laddrhashfib_info_devhash,为了简化讨论,我们不考虑第二个表,那么第二个和第三个表的关系如下:
这里写图片描述
可以看出,fib_info是指向fib_nh的,而它们分别被放到2个hash表中。

第1次fib_magic完成的结果:
这里写图片描述

第3次fib_magic完成的结果:
这里写图片描述

第4次fib_magic完成的结果:
这里写图片描述

第5次fib_magic完成的结果:
这里写图片描述

当配置例子中的接口IP地址时,会产生如下的结果:

这里写图片描述

在上图发现什么问题没有?前文说过,loopback接口的IP地址设置有4次fib_magic,普通设备的IP地址设置有5次,不过最后一次不会再创建node了。所以在上图中应该会发现有8个node,但是只有7个。为什么会这样?这是因为普通设备的流程和loopback接口不同的是在第3 次fib_magic里面访问的是main表而不是local表,所以这个node放到了main表:
这里写图片描述

直接访问路由表

目前所有基于Linux的路由系统基本属于下面的架构:
这里写图片描述
路由协议采用的netlink接口来更新FIB表,在本书中不可能再用一种路由协议去示例访问路由表的方式,我们可以采用类似的方式,比如静态配置路由表的方式来介绍netlink内部的实现。在Linux上有两种访问路由表系统的命令
第一种: 使用route 命令配置路由表
示例1:添加到主机路由

# route add –host 192.168.18.2 dev eth0  # route add –host 192.168.183 gw 192.168.18.1  

示例2:添加到网络的路由

# route add –net 10.10.10.10 netmask 255.255..0.0 eth0  # route add –net 20.20.20.20 netmask 255.255..0.0gw 192.168.18.1  # route add –net 30.30.30.30/24 eth1  

示例3:添加默认网关

# route add default gw 192.168.18.1  

示例4:删除路由

# route del –host 192.168.18.1 dev eth0

第二种: 使用ip route命令
示例1: 设置到网络10.0.0/24的路由经过网关192.168.18.1

# ip route add 10.10.10.0/24 via 192.168.18.1  

示例2: 修改到网络10.10.10.0/24的直接路由,使其经过设备eth0

# ip route chg 10.10.10.0/24 dev eth0  

使用route命令则会调用ioctl,内核中会进入到ip_rt_ioctl函数。

/* * Handle IP routing ioctl calls. * These are used to manipulate the routing tables */int ip_rt_ioctl(struct net *net, unsigned int cmd, void __user *arg){    struct fib_config cfg;    struct rtentry rt;    int err;    switch (cmd) {    case SIOCADDRT:     /* Add a route */    case SIOCDELRT:     /* Delete a route */        if (!ns_capable(net->user_ns, CAP_NET_ADMIN))            return -EPERM;        if (copy_from_user(&rt, arg, sizeof(rt)))            return -EFAULT;        rtnl_lock();        err = rtentry_to_fib_config(net, cmd, &rt, &cfg);        if (err == 0) {            struct fib_table *tb;            if (cmd == SIOCDELRT) {                tb = fib_get_table(net, cfg.fc_table);                if (tb)                    err = fib_table_delete(tb, &cfg);                else                    err = -ESRCH;            } else {                tb = fib_new_table(net, cfg.fc_table);                if (tb)                    err = fib_table_insert(tb, &cfg);                else                    err = -ENOBUFS;            }            /* allocated by rtentry_to_fib_config() */            kfree(cfg.fc_mx);        }        rtnl_unlock();        return err;    }    return -EINVAL;}

除了给接口分配IP地址会造成FIB表数据变化之外,还有另外一种方式,就是通过rtnetlink方式给内核增加路由。谁在使用这个接口?在一台路由器上任何路由协议都可以使用,比如OSPF、RIP等,当它们完成一条路由的计算之后,就会把这条路由加入到内核中,我们前篇曾介绍过rtmsg的内容,而rtnetlink方式也是借助这种数据结构给内核下达增删路由的命令。不过不是通过fib_magic,而是转到inet_rtm_newroute函数,其中再调用tb->tb_insert()来完成,其方式与fib_magic类似。只不过我们可以随意指定fib_info的部分字段了,比如fib_info->fib_protocol=rtmmsg->protocol,可以是列表中的值。

static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh){    struct net *net = sock_net(skb->sk);    struct fib_config cfg;    struct fib_table *tb;    int err;    err = rtm_to_fib_config(net, skb, nlh, &cfg);    if (err < 0)        goto errout;    tb = fib_new_table(net, cfg.fc_table);    if (!tb) {        err = -ENOBUFS;        goto errout;    }    err = fib_table_insert(tb, &cfg);errout:    return err;}

接口状态变化的处理过程

上面介绍了FIB配置系统的工作流程,给系统分配一个IP:192.168.1.1,协议栈做了什么?首先它调用ip_route_connect ->ip_route_output_key -> ip_route_output_slow,然后创建一个邻居表项。(用ifconfig eth0 del 192.168.1.1 0 删除这个网卡的IP地址)
事情到这里好像都完美无缺,然后我们可以ping外部主机了。等等,好像有些东西没有提及因为在大多数情况,我们主机的以太网口都插上了网线,也就是说,如果按照上面的分析,似乎协议栈已经准备好了,但我们到现在还没有提到neighbour系统的设置,难道不需要这个子系统的协助吗?下面我们就来一步一步研究这个问题的答案。我们已经知道在Linux2.6下每个网络设备驱动程序的设备实体(net_device)都有一个priv指针,指向了设备独有的数据块。虽然每种设备独有的数据结构有点区别,但是绝大多数都有一个timer结构,loopback设备没有这个结构,为什么呢?分析设备驱动的初始化例程即在dev -> open函数中发现这个timer的时间处理函数指针都指向了各自的所谓watchdog函数。熟悉嵌入式开发的读者可能要在此处迷惑,watchdog一般都是值防止某个应用程序出现长时间占用CPU而提出的概念,外什么要这这里讨论watchdog呢?原因在于此dog非彼dog。在网络设备驱动里,这个watchdog就只是用来轮询接口状态的定时器函数。Loopback不需要检测链路状态,所以,它就没有这个函数,读者们明白了吗? 我们来看看这个定时器是如何工作的。当设备管理器调用某个网卡驱动dev -> open例程的时候,就给这个驱动指定一个定时器,其处理函数约定熟成的叫做xxx_watchdog,当然也有某些驱动程序开发者为了避免概念混淆,只给它起了一个很普通的名字,比如xxx_timer。在实现这个处理函数中,一般有这样的惯例:
第5步. 轮询时间一般在1秒以上
第6步. 读取设备芯片关于网口的寄存器,检查其是否变化
第7步. 如果有变化,统一调用netif_carrier_onnetif_carrier_off来通知系统内部其他模块,比如邻居子系统。
由于每个驱动程序的实现不一样,而定时器这一部分相对一致,我们就不仔细研究设备驱动程序的实现了。现在假设我们有一台没有接网线的主机,当我们插上另一端已经有主机的网线,当watchdog定时器扫描发现已经链路已经有信号时,就会调用netif_carrier_on函数,其实现如下:

 void netif_carrier_on(struct net_device *dev){    //去掉NO_CARRIER标志,然后进入下面的函数。     if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)).    linkwatch_fire_event(dev);    //如果设备没有被卸载,那么还得继续定时器扫描     if (netif_running(dev))        __netdev_watchdog_up(dev); } 

linkwatch_fire_event函数比较复杂,我们在这里不打算列出其代码,只列出工作流程:
1. 创建一个lw_event{}结构,表示一个事件。
2. 把这个结构挂到lweventlist工作队列上。
3. 调用schedule_workschedule_delayed_work(如果当事件太频繁)促使内核对lweventlist扫描并执行该节点上回调函数——linkwatch_event
当系统有时间处理工作队列时,执行linkwatch_event
这里写图片描述
之所以要将图中两个函数设为灰底,是因为在将来的设备发送/接收过程中要接触到这两个函数的工作。现在我们先放在一边,看看netdev_state_change。此函数比较简单,但是非常重用,它就是设备连接邻居子系统的通道。它就是调用了下面这行代码:

 raw_notifier_call_chain (&netdev_chain, NETDEV_CHANGE, dev);  

到此为止,驱动程序的任务基本完成,接下来要看看这行代码到底做了什么事。 驱动程序最终发送了一个notify,其接收对象netdev_chain链表,凡是对网络设备事件感兴趣的模块,都要挂在这个链表上。搜遍整个源代码,可以看到挂接到此链表的子系统挺多的,但是,对NETDEV_CHANGE这个事件感兴趣的,嗯,几乎没有。
这里写图片描述

从上图可以看出,没有人对设备的状态的变化感兴趣,所以,可以回答这个问题了:网卡接口是否接通与ARP工作与否或其他子系统没有直接关系。也就是说,如果网口从物理down变成物理up,不会触发ARP或路由系统的变化。

1 0
原创粉丝点击