当前位置: 代码迷 >> 综合 >> 内核中的TCP的追踪分析-9-TCP(IPV4)的socket的地址绑定--续2
  详细解决方案

内核中的TCP的追踪分析-9-TCP(IPV4)的socket的地址绑定--续2

热度:79   发布时间:2024-01-21 13:26:33.0

接着昨天的继续分析,我们接着从上一节最后部分继续开始今天的内容 :
我是无名小卒,转载的朋友请注明出处,请不要抄袭做为它用,谢谢!

      在内核中CONFIG_NET_NS配置选项是为了让用户自定义自己的网络空间结构,即上面的net结构,可以看出2.6.26内核的灵活性,但是我们一般在内核中不会配置该项,所以这里应该是取得init_net,这个结构是在前一节分析的那样在do_one_initcall()机制中调用了从pure_initcall(net_ns_init)注册的net_ns_init()初始化的,
#define pure_initcall(fn)  __define_initcall("0",fn,0)
这个net_ns_init函数进一步调用setup_net()来对init_net结构进行详细的设置。我们这里不细细分析了他的过程类似于我们前边那节socket的初始化的流程,只不过在setup_net对init_net初始化中有一个非常重要的循环

net_ns_init()-->setup_net()
  list_for_each_entry(ops, &pernet_list, list) {
  if (ops->init) {
    error = ops->init(net);
    if (error0)
      goto out_undo;
  }
  }
在这个循环中会从已经注册到pernet_list队列中依次通过其钩子结构pernet_operations,调用结构中的钩子函数init对这个net网络命名空间结构进行初始设置。而这里的pernet_list是一个队列头,它在/net/core/Net_namespace.c文件中的16行处这个队列专门用于person net 用户自定义的pernet_operations结构的登记,也就是我们上面提过的CONFIG_NET_NS情形时才可以将用户自定义的pernet_operations结构链入队列。
static LIST_HEAD(pernet_list);
而这里的LIST_HEAD宏在include/linux/list.h
#define LIST_HEAD(name) /
  struct list_head name = LIST_HEAD_INIT(name)
#define LIST_HEAD_INIT(name) { &(name), &(name) }
struct list_head {
  struct list_head *next, *prev;
};
朋友们可能注意到我经常将调用在前,说明在后的排列数据结构说明,这种习惯个人感觉有助于理解和记忆。“带着问题学习”远比摸黑效率高。那么这个结构是什么时候被初始化的呢?我们结合本类的第一节关于socket的初始化中提到的initcall机制会看到在/net/core/dev.c中有一句
subsys_initcall(net_dev_init);
他引用了include/linux/init.h中的
#define subsys_initcall(fn)  __define_initcall("4",fn,4)
所以按照第一节中提到的initcall机制那部分内容,net_dev_init函数将会在开机时得到执行,在这个函数内部有这样的代码:

net_dev_init()
if (register_pernet_subsys(&netdev_net_ops))
  goto out;
  if (register_pernet_device(&default_device_ops))
  goto out;
上面代码中调用的二个函数都会调用register_pernet_operations函数

net_dev_init()-->register_pernet_subsys()和register_pernet_device()
int register_pernet_subsys(struct pernet_operations *ops)
{
  int error;
  mutex_lock(&net_mutex);
  error = register_pernet_operations(first_device, ops);
  mutex_unlock(&net_mutex);
  return error;
}
int register_pernet_device(struct pernet_operations *ops)
{
  int error;
  mutex_lock(&net_mutex);
  error = register_pernet_operations(&pernet_list, ops);
  if (!error && (first_device == &pernet_list))
  first_device = &ops->list;
  mutex_unlock(&net_mutex);
  return error;
}
我们在上面第一个函数register_pernet_subsys中看到了first_device这是在Net_namespace.c中17行处声明的

net_dev_init()-->register_pernet_operations()
static struct list_head *first_device = &pernet_list;
进入register_pernet_operations我们看一下
static int register_pernet_operations(struct list_head *list,
     struct pernet_operations *ops)
{
  if (ops->init == NULL)
  return 0;
  return ops->init(&init_net);
}
在Net_namespace.c中还有另一个同名的函数但是那必须在支持NET_NS用户自定义网络的选择项打开情况下,我们上面提到过了,所以这里会进入net_dev_init函数中层层传递下来的netdev_net_ops钩子结构,执行这个结构中的钩子函数init,我们看一下这个结构
static struct pernet_operations __net_initdata netdev_net_ops = {
  .init = netdev_init,
  .exit = netdev_exit,
};
显然是进入了netdev_init函数,在进入函数之前我们看到它是对系统默认的网络空间结构变量init_net进行的操作

net_dev_init()-->register_pernet_operations()――>netdev_init()
/* Initialize per network namespace state */
static int __net_init netdev_init(struct net *net)
{
  INIT_LIST_HEAD(&net->dev_base_head);
  net->dev_name_head = netdev_create_hash();
  if (net->dev_name_head == NULL)
  goto err_name;
  net->dev_index_head = netdev_create_hash();
  if (net->dev_index_head == NULL)
  goto err_idx;
  return 0;
err_idx:
  kfree(net->dev_name_head);
err_name:
  return -ENOMEM;
}
我是无名小卒本文是我原创完成,尽管08年3月份才开始写文章但是已经得到了朋友们认可,文章也得到了转载,所以请转载的朋友注明出处
http://qinjiana07876.cublog.cn
,我们在上面的函数中看到初始化了init_net的几个队列头,其中有二个是哈希队列。有关哈希队列的概念,请参阅深入理解linux内核第三版中的讲述,下面是其调用的二个函数,第一个函数是初始化队列头,而中一个函数则是建立了一个哈希队列头,并进行了初始化,上面可以看到net中有这二个dev_name_head(以设备名称为主键的哈希队列)和dev_index_head(以设备的序号为主键的哈希队列)哈希队列头。

net_dev_init()-->register_pernet_operations()――>netdev_init()-->INIT_LIST_HEAD()和netdev_create_hash()
static inline void INIT_LIST_HEAD(struct list_head *list)
{
  list->next = list;
  list->prev = list;
}
static struct hlist_head *netdev_create_hash(void)
{
  int i;
  struct hlist_head *hash;
  hash = kmalloc(sizeof(*hash) * NETDEV_HASHENTRIES, GFP_KERNEL);
  if (hash != NULL)
  for (i = 0; iNETDEV_HASHENTRIES; i++)
    INIT_HLIST_HEAD(&hash);
  return hash;
}
这二个函数对队列头进行了初始化,最前面的函数是对初始化普通的链表头,下面函数是初始化hash队列头。函数很简单。这二个函数分别在include/linux/list.h和/net/core/dev.c中。接下来我们再看上面已经贴出的register_pernet_device函数调用register_pernet_operations时传递的是default_device_ops钩子结构,会执行这个结构中的init钩子函数
static struct pernet_operations __net_initdata default_device_ops = {
  .exit = default_device_exit,
};

但是我们上面看到没有设置init钩子函数,那么看一下上面的register_pernet_operations函数,它在直接返回0了,然后在register_pernet_device函数中就会执行

net_dev_init()-->register_pernet_device()
  if (!error && (first_device == &pernet_list))
  first_device = &ops->list;
但是我们看到first_device在这里将会指向default_device_ops中的队列头list。到这里我们只是看到了对init_net的几个队列头的初始化,那么还有没有其他重要的初始化呢,如何对init_net的网络结构进行的设置的。我们后边随着代码的逐渐深入,将会看到这个重要的数据结构在多个地方进行了设置和调整,可以说init_net是对整个我们网络结构的总入口,所以有网络命名空间的叫法,这也是我们前边提到的struct net的主要作用,我是无名小卒,请转载的朋友注明出处,
http://qinjiana0786.cublog.cn
。我们暂时不能一一对init_net的详细初始化经过全部展开了(本文最后附录中准备附加一些过程),这里要随着我们的分析过程一步一步的加以探讨和研究,我们还是回到__inet_dev_addr_type()函数继续往下看,这是一个非常重要的函数,为了分析方便再次贴出代码在这里

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()

static inline unsigned __inet_dev_addr_type(struct net *net,
       const struct net_device *dev,
       __be32 addr)
{
  struct flowi  fl = { .nl_u = { .ip4_u = { .daddr = addr } } };
  struct fib_result  res;
  unsigned ret = RTN_BROADCAST;
  struct fib_table *local_table;
  if (ipv4_is_zeronet(addr) || ipv4_is_lbcast(addr))
  return RTN_BROADCAST;
  if (ipv4_is_multicast(addr))
  return RTN_MULTICAST;
#ifdef CONFIG_IP_MULTIPLE_TABLES
  res.r = NULL;
#endif
  local_table = fib_get_table(net, RT_TABLE_LOCAL);
  if (local_table) {
  ret = RTN_UNICAST;
  if (!local_table->tb_lookup(local_table, &fl, &res)) {
    if (!dev || dev == res.fi->fib_dev)
      ret = res.type;
    fib_res_put(&res);
  }
  }
  return ret;
}
我们上一节分析了路由键值fl已经在函数的开始将我们在练习中的设置的“电话号码”192.168.1.1初始化设置到了其中。函数接着判断地址,首先是ipv4_is_zeronet()

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->ipv4_is_zeronet()
static inline bool ipv4_is_zeronet(__be32 addr)
{
  return (addr & htonl(0xff000000)) == htonl(0x00000000);
}
注意宏htonl是将数值转换为cpu是小端结尾还是大端结尾转换成要求的字节序,它实际上是调用__cpu_to_be32 ()来转换的,我们就不详细看这个宏了,ipv4_is_zeronet ()函数就是检测IP地址高8位为0,是否为零网地址,接着

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->ipv4_is_lbcast()
static inline bool ipv4_is_lbcast(__be32 addr)
{
  /* limited broadcast */
  return addr == htonl(INADDR_BROADCAST);
}
#define  INADDR_BROADCAST  ((unsigned long int) 0xffffffff)
检查地址是否是广播地址类型,以及

sys_socketcall()-->sys_bind()-->inet_bind()-->inet_addr_type()-->__inet_dev_addr_type()-->ipv4_is_multicast()
static inline bool ipv4_is_multicast(__be32 addr)
{
  return (addr & htonl(0xf0000000)) == htonl(0xe0000000);
}
检查地址是否是多播地址类型。
这里注意区分广播、多播和单播地址
数据从一台计算机传输到网络中的所有计算机的过程就是广播;
数据从一台计算机传输到网络中一组特定的计算机的过程就是多播,也叫做组播。
数据从一台计算机传输到另外一台计算机的过程就是单播。

如果是上述的零地址和广播地址类型和多播地址类型,就直接返回该类型号,但是如果不是这些地址类型的话就要通过fib_get_table()查找具体的路由表了,这个函数是linux内核中有二个,分别在/include/net/ip_fib.h中和/net/ipv4/fib_frontend.c中。这要取决于内核是否配置了CONFIG_IP_MULTIPLE_TABLES这个选项,有资料称为“策略路由”关于这个概念网上有很多资料介绍,它的作用就是执行用户的路由策略,我们这里可能还需要重申一下路由的定义,路由其实就是提供了更好的到达目标地址的捷径,它一般是经过一系列的计算得出的,路由位于第三层即IP层,它在linux中的位置极其重要,路由表结构提供了计算路由的方法函数,我们只需要让初次接触的朋友们有一个概念性的认识避免过多的在理论上纠缠,下面我们来看一下这个fib_get_table()相信通过这个函数朋友们真正理解了路由表的作用。我们已经启用了ipv4的路由策略,所以会执行/net/ipv4/fib_frontend.c中的这个函数。篇幅所限,接下篇

  相关解决方案