全部博文(668)
分类:
2009-01-01 09:07:55
__inet_dev_addr_type()函数中,我们看到在这个函数中
sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()
|
我们只贴出相关的部分代码,先是找到我们的本地路由表,这个过程我们已经详细分析了,然后local_table就指向了我们找到的本地路由表,现在要调用路由表结构中的tb_lookup()函数,我们前一节说过fn_hash_lookup()函数,这个过程我们已经看过了,我们接着看这个函数
sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->fn_hash_lookup()
|
首先我们先看一下传递进来的参数tb是我们已经找到的本地路由表结构,flp是包含我们绑定地址的路由键值结构,而res是用来返回路由查找结果的结构,是用来返回给__inet_dev_addr_type()函数用的。我们在一上节http://blog.chinaunix.net/u2/64681/showart.php?id=1721724 fib_hash_table()函数中看过分配路由表空间时一并分配了struct fn_hash结构的空间,而这个结构的地址是包含在struct fib_table结构中的tb_data中的,在分析那个函数的过程中我们看到了二者建立的联系,而且我们知道struct fn_hash是用来封装路由区struct fn_zone队列或者缓存而使用的。首先我们注意flp->fl4_dst是在取得我们绑定到路由键值的IP地址,因为fl4_dst在struct flowi 是一个宏
|
我们看到这种在结构体中使用宏的方法让我们简化了结构体的调用而且名称更直接。代码中是根据fn_hash中的fn_zone_list队列头依次沿着fn_zone的队列通过fz_key()来
sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->fn_hash_lookup()-->fz_key()
|
这里要取得在fn_zone结构中的子网掩码与我们的地址相与,所得结果赋值给k,这是个__be32变量,也就是无符号整型数值u32,我们练习中指定要绑定的IP地址是192.168.1.1,假设我们的子网掩码是255.255.255.0,那么这里与之后结果就是192.168.1.0,这也就意味着我们下边的查找路由代码会在子网192.168.1.0/24之间,然后将k与路由区中的fz_order在fn_hash进行哈希算法,其结果做为路由区结构中fz_hash数组的下标得到路由节点fib_node的队列链头,然后从这个链头开始依次取出路由节点结构中的fn_key与k相比较看是否相同,这个节点结构是什么时间装载进来的,我们稍后来分析linux是如何初始化这些路由的,先让我们把这个过程看完,只匹配了节点中的fn_key还不够,还要通过fib_semantic_match()函数进行详细的检查,如果在这个函数中通过了检查,就会进一步初始化传递过去的res,这个值是为了我们之前传入的,所以会逐层返回给__inet_dev_addr_type()函数,在那里将会通过ret = res.type;取得检查结果中的地址类型。fn_hash_lookup()函数有二层循环,不断的沿着路由区结构中的fn_zone_list队列比对路由节点,然后调用fib_semantic_match()函数,我们需要看一下这个函数,当然我们还没有看到路由节点是什么时候初始化的,这是在路由设置的过程完成的,我们稍后完成这里的检查函数再分析。
sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->fn_hash_lookup()-->fib_semantic_match()
|
我们看到一个新的数据结构,fib_alias从英文上看就是路由表的别名,它的作用是为了区别同一个目标网络地址的不同路由所使用的。
|
我们看到上边传递下来的是路由节点的fn_alias做为这个结构的队列头来进行循环,所以很显然这是fib_alias结构通过fa_list链入到相应的路由节点中的。所以依次取出挂入到路由节点中的fib_alias,在循环检查符合的fib_alias,代码中沿着路由节点中的fn_alias队列执行循环检查,在循环中首先是检查fib_alias的fa_tos,即路由别名的服务类型是否设置了,如果设置就检查是否与我们键值中的服务类型fl4_tos相同,fl4_tos仍然是一个宏
|
接着就是查看路由别名数据结构中是否定义了路由范围,如果我们设置的键值中的路由范围大于它的话就跳到下一个循环检查路由路由别名结构,接着为路由别名结构状态标志增加FA_S_ACCESSED,表示已经访问了,接着要从全局的fib_props数组中取得错误码,这个数组定义了路由表的范围和错误码。
|
函数中以路由别名结构中的fa_type即路由类型为下标取出数组中的错误码error赋值给err变量,如果错误码是0的话就表示支持该路由类型,就从路由别名结构中的fa_info 指针处取得struct fib_info结构指针变量fi。这个结构我们还没有看过
|
这个结构代表一个路由,它反映了一个路由的重要信息。很显然路由节点和路由别名结构都包括了这个结构。接下来要检查找到的这个路由信息结构是否正处于被删除状态,即它的fib_flags 标志中是否RTNH_F_DEAD被置位了。接下来要根据路由别名结构中的路由类型来确实switch语句要执行的语句段,RTN_UNICAST是单播路由类型,RTN_LOCAL是回环的路由(本地转发)类型,RTN_BROADCAST是广播的路由类型,RTN_ANYCAST是任意路由类型,RTN_MULTICAST是多播或者组播的路由类型。我们看到这些路由类型都是使用相同的处理方法,注意他们之间没有break,所以都会执行RTN_MULTICAST标号处的代码,首先是调用了for_nexthops宏,内核中根据是否设置了CONFIG_IP_ROUTE_MULTIPATH而确定采用哪一个宏,下面是未打开该功能的
|
另一处是
|
我们看到上面的宏是从fib_info结构中的指针fib_nh结构的,而fib_nh实际上代表着跳转信息
|
fib_nh结构体是一个描述路由跳转信息的结构。每一次跳转都有这么一个结构来表示。我们还是不关心具体的结构变量内容。那么dn_fib_nh结构是什么作用呢,从上面的宏来看它代表着目标跳转内容。所以结合起这个宏来看
|
这段代码就是循环检查路由表中的目标跳转结构,检查是否处于移除状态,接着如果路由键值没有定义oif或者与目标跳转结构中的nh_oif相同的话就会跳出循环。如果检查通过了而检查的次数在也就是跳转的次数在键值规则的fib_nhs跳转数量以内,就跳转到out_fill_res标号处设置res结构变量的值,包括路由检查的前缀长度prefixlen,这是通过上边函数调用时传递下来的路由区中的fz_order来指定的,及路由跳转的次数nh_sel,以及路由的范围scope,还有路由结构信息,最重要的是 res->type = fa->fa_type将路由别名结构中的fa_type赋值给res的type,然后我们的过程就直接从这个函数返回了,最后函数层层返回到__inet_dev_addr_type()中,将取得的res中的type做为返回值返回给inet_bind()函数。但是,这里给我们留下了一个疑问,这些路由的相关数据结构,例如路由节点、路由别名和路由跳转结构是什么时候设置和初始化的呢?
本来我们应该回到inet_bind()函数中继续我们主线过程,但是,上面的过程中我们没有看到我们练习中的地址到底在这个路由过程中是怎么取得所属的地址类型,并且路由的过程也是对我们整个tcp/ip协议的重要组成部分,还是趁热打铁,把路由的上面的几个关键的数据结构内容得补上才能让我们把上面的过程理解清楚,也就是说我们只有数据的结果了,但是从哪里产生的呢,源泉在哪里?我们现在趁机分析一下路由的设置,应该说路由主要是借助于外力,即工具或者命令行来设置,主要是通过net-tools和IPROUTE2来设置的路由,一般来说net-tools不能配置象Multipath多通道路由和 Policy Routing策略路由这样的高级路由特性,而IPROUTE2是最新的而且强大的配置路由配置方法,主要是一系列的IP命令来实现操作,并且IPROUTE2可以兼容net-tools。我们将从三个路由配置路线分析设置的过程,分别用红色文字表示路由设置路线A、B、C。请朋友们注意,不关心这些过程的朋友可以直接看下一篇的路由初始化函数fn_hash_insert和fn_hash_delete。
路由设置路线A:
net-tools是通过ioctl系统调用来进入ip_rt_ioctl()函数中,来通过路由表的操作函数实现增加或者删除路由
|
我们只关心上面代码中的调用路由表的tb_insert和tb_delete的钩子函数代码,所以省略了其他一些内容。我们在inet_init()-->ip_init()--> ip_rt_init()-->ip_fib_init()-->fib_net_init()--> ip_fib_net_init()-->fib4_rules_init()-->fib_hash_table()也就是初始化路由的过程中在fib_hash_table()函数中曾经看到
|
很明显上边的函数中增加路由则进入fn_hash_insert,删除路由则进入fn_hash_delete中。
路由设置路线B:
上面是对net-tools的设置路由过程的简介,接着我们看IPROUTE2,它设置路由主要是我们前边讲过的netlink的socket来实现的,而netlink通过inet_rtm_newroute增加路由或者inet_rtm_delroute删除路由。我们应该记得在前面讲过ip_fib_init()函数中会调用rtnl_register()
http://blog.chinaunix.net/u2/64681/showart.php?id=1717120 在这个链接中我们看到了inet_rtm_newaddr和inet_rtm_deladdr这二个函数登记注册过程。我们就不再重复了。我们来看这二个函数的简单过程
|
很显然他们也是通过tb->tb_insert()和tb->tb_delete()象net-tools那样增加路由则进入fn_hash_insert,删除路由则进入fn_hash_delete中,也就是最终在内核中都是集中在这二个函数上。至于net-tools和IPROUTE2我们就不详细分析他们的工作原理了。请朋友们在网上查阅相关这二个工具包的介绍了。
路由设置路线C:
另外,我们在ip_fib_init()函数中还看到过
|
注册了二个通知链,这是在http://blog.chinaunix.net/u2/64681/showart.php?id=1721724 那节中详细介绍了注册的过程以及这二个通知链分别被插入到netdev_chain链(网络设备状态变动时通知链)和inetaddr_chain链(IP地址变动时的通知链),所以当网络设备安装或者初始化时,当IP地址改变时都触发通知链的处理函数,我们在那节谈到通知链时没有说明何时通知链被操作的,在内核中有一个专门用做通知链操作的函数notifier_call_chain()我们在那里并没有介绍这个函数,主要是时机不到因为结合不到具体的实际场景直接叙述难免太过单一,这里我们看一下,为了让大家有个清晰的认识,我们这里拿网卡的驱动程序为例看一下,我们这里以dm9000网卡的驱动程序为例,为什么不以我们电脑上的网卡为例,那是因为电脑上的网卡驱动程序需要结合PCI总线协议来看,象rtl8139网卡等,不适合我们拿来做例子,所以这里拿dm9000或者cs8900这类网卡是比较直观的,避免了其他协议的干扰,让我们本篇文章更能集中精力在tcp的协议上。我们简要的看一下在dm9000初始化函数dm9000_probe()中,要调用register_netdev()向内核登记注册自己的设备结构struct net_device *ndev,具体代码我们不贴出了,所有的网卡都会统一调用这个函数向内核登记自己的网卡设备结构。我们来看这个register_netdev()
dm9000_probe()-->register_netdev()
|
很显然上边的函数进一步调用register_netdevice()来注册登记
dm9000_probe()-->register_netdev()-->register_netdevice()
|
我们只关心与我们要介绍的主题相关的部分,可以看到他向内核发出了通知“有新的设备出现了”。
dm9000_probe()-->register_netdev()-->register_netdevice()-->call_netdevice_notifiers()
|
上边直接把所有调用函数按顺序贴了出来,最后显然是调用了我们上边要讲的notifier_call_chain()函数,这里结构dm9000的调用过程来看一下相关的参数,首先是我们看到了是使用的netdev_chain这个链,val是NETDEV_REGISTER,v是网络设备的结构指针dev,nr_to_call是-1,nrcalls是NULL。那么我们来看这个通知链的重要函数
|
函数依次在netdev_chain的链上,调用各个通知链中的notifier_call()通知处理函数。那么就与我们上边的fib_netdev_notifier通知链中的钩子函数。看一下http://blog.chinaunix.net/u2/64681/showart.php?id=1721724 那节中的fib_netdev_notifier 结构内容,显然是调用的fib_netdev_event()函数。这样我们就明白了通知链的执行过程,他显然是内核主动通过notifier_call_chain()来执行的。另一面,我们在http://blog.chinaunix.net/u2/64681/showart.php?id=1709869 那节中曾经看了如何通过socket的ops钩子结构进入的inet_stream_ops结构,在这个结构中我们看到了
|
也就是说如果我们对socket执行ioctl()系统调用时会调用这个inet_ioctl()函数。在这个函数中我们会根据我们的ioctl传递的控制命令设置IP地址而进入devinet_ioctl()函数,接着在这个函数中调用inet_set_ifa(),近而调用inet_insert_ifa,然后进入__inet_insert_ifa
ioctl()-->inet_ioctl()-->devinet_ioctl()-->inet_set_ifa()-->__inet_insert_ifa()
|
再进入blocking_notifier_call_chain(),它实际是调用__blocking_notifier_call_chain()函数
ioctl()-->inet_ioctl()-->devinet_ioctl()-->inet_set_ifa()-->__inet_insert_ifa()-->__blocking_notifier_call_chain()
|
很明显也是调用了notifier_call_chain()函数来实现对inetaddr_chain这个链上的所有通知链函数的调用。上边的过程会执行我们ip_fib_init()函数中登记注册的通知链(http://blog.chinaunix.net/u2/64681/showart.php?id=1721724 )
|
很明显这里是执行的fib_inetaddr_event()函数。
当然我们上边只是举了其中的一个过程的例子来展开分析的。不论哪一种通知链的调用路线,都最终归结到了notifier_call_chain()这个函数上来,而且也只有执行上面二个链netdev_chain和inetaddr_chain。上面对二个链的操作我们看到到达我们的fib_netdev_event和fib_inetaddr_event这二个函数。我们可以看到在这二个函数中都调用了 fib_add_ifaddr()函数, fib_inetaddr_event多了一个对fib_del_ifaddr()函数的调用。在这二个函数中都有对fib_magic()的调用,这就是另一路对我们的路由的设置路线
fib_inetaddr_event()-->fib_magic() && fib_netdev_event()-->fib_magic()
|
很显然,这个函数也是对路由起着设置作用。我们先只关心与我们这里的路由相关的最后二句,这个函数可以看出即可以增加路由也可以删除路由。也是象前边我们看到的路由配置net-tools和IPROUTE2一样,增加路由调用fn_hash_insert,删除路由则进入fn_hash_delete中。这二个函数是我们分析的重点,上面说了这些过程为了防止朋友混淆,特此在前面加了路线红色文字指示。下一篇继续这二个关键路由设置函数的分析。