您的位置:首页 > 理论基础 > 计算机网络

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

2016-12-12 23:18 411 查看

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成员边界进行初始化;若已存在,则执行3
3.为新创建的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的统计计数加1
5.将新创建的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_zone
b)若没有查找到,则当前创建的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_info
4.根据传递的值,设置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变量,程序返回;
否则进入2
2.将该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或路由系统的变化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息