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_broadcast是192.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的内存空间扩容1倍2.创建一个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_info
和fib_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_alias
和fib_info
5,目的地址相同的情况下,也可以使用多条路由,不同的路由存放在不同的fib_nh
里面fib_create_info
函数中牵涉到3个hash表:fib_info_hash
、fib_info_laddrhash
、fib_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_on
或netif_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_work
或schedule_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或路由系统的变化。
- FIB系统分析二(linux网络协议栈笔记)
- fib系统分析(linux网络协议栈笔记)
- 网络配置过程分析二(linux网络协议栈笔记)
- linux网络协议栈分析(二)
- Linux协议栈代码阅读笔记(二)网络接口的配置
- Linux内核网络协议栈笔记
- Linux内核网络协议栈笔记
- 网络配置过程分析(linux网络协议栈笔记)
- 网络层实现初步探究(linux网络协议栈笔记)
- 网络层路由系统(linux网络协议栈笔记)
- 实验二.Linux系统分析实验
- Linux 设备管理(linux网络协议栈笔记)
- Linux网络协议栈(二) -- 套接字缓存(socket buffer)
- Linux网络协议栈(二) -- 套接字缓存(socket buffer)
- TCP/IP网络协议学习笔记二
- Linux网络协议栈
- linux网络协议栈
- Linux网络协议栈
- matlab GUIDE 和GUI笔记
- 中介者模式(Mediator)
- zzulioj1752: Math Three!(水)
- spring中 hibernate实体类注解
- android(2):android studio新建项目的时候JCenter下载卡掉
- FIB系统分析二(linux网络协议栈笔记)
- python并发编程之多进程、多线程、异步和协程详解
- 截至20161212中国公司在美上市名单
- Android中利用icodetools工具快速定位App破解中关键点方法
- 素数环问题
- 【HTML5】SVG制作过山车动画(三)
- 使用kill命令批量终止进程
- 自适应四元数kalman滤波matlab学习笔记(二)
- Spring框架概述 - Spring学习笔记(一)