PHP
首页 | 下载 | 博客 | 链接

区域

工具

查看代码的工具推荐用Eclipse Platform,将includes文件路径设置好以后,查找原型/函数/结构相当方便,如果怕麻烦,lxr也是个不错的选择,比如lxr.linux.no


相关链接

启动中的ipvs
运行中的ipvs
ipvs的调度
ipvs的数据报转送

ipvs的hash算法
数据包结构/操作分析
[转]ipvs基本原理/框架


资源链接

netfilter官方指南(中文)
ipvs官方网站(中文)
developerWorks-cluster(中文)


说明

文章中出现的内核代码版本是linux-2.6.26,为了保证行号的一致性,中文的注释没有插入回车/换行符.可能会显得有些紧巴巴的.
由于个人能力有限,网站内容存在许多错误和不足,希望读者能踊跃给予批评指正或建议. 本人联系方式:yubo@yubo.org

ipvs代码分析

linux-2.6.26中ipvs代码包括头文件在内,总共有12996行.分析的顺序我按照自己看代码的过程,分为以下4个部分

ipvs中还用到了些其他的技术,比如hash算法,socket buff等.对整个代码理解不构成影响,则作为单独的部分提出来进行分析

启动中的ipvs

当计算机启动以后,内核的随着被加载,内核的模块也会被加载.加载的那一刻开始,很多东西就运动起来了.对于ipvs来说,最开始运行的就是初始化函数__init ip_vs_init(),与之对应的还有一个退出时运行的函数[__exit]就从这里开始分析吧

net/ipv4/ipvs/ip_vs_core.c

  1.  /*
  2.   * Initialize IP Virtual Server
  3.   */
  4.  static int __init ip_vs_init(void)
  5.  {
  6.   int ret;
  7.   //登记ipvs的sockopt控制,这样用户空间可通过
  8.   ret = ip_vs_control_init(); //setsockopt函数来和ipvs进行通信.
  9.   if (ret < 0) {
  10.   IP_VS_ERR("can't setup control.\n");
  11.   goto cleanup_nothing;
  12.   }
  13.   //初始化ipvs能支持的协议,
  14.   ip_vs_protocol_init(); //一共4个(tcp/udp/ha/esp),使用了双向链表结构
  15.  
  16.   ret = ip_vs_app_init(); //应用层辅助协议初始化
  17.   if (ret < 0) {
  18.   IP_VS_ERR("can't setup application helper.\n");
  19.   goto cleanup_protocol;
  20.   }
  21.  
  22.   ret = ip_vs_conn_init(); //初始化ipvs连接
  23.   if (ret < 0) {
  24.   IP_VS_ERR("can't setup connection table.\n");
  25.   goto cleanup_app;
  26.   }
  27.   //注册hook函数
  28.   ret = nf_register_hooks(ip_vs_ops, ARRAY_SIZE(ip_vs_ops));
  29.   if (ret < 0) {
  30.   IP_VS_ERR("can't register hooks.\n");
  31.   goto cleanup_conn;
  32.   }
  33.  
  34.   IP_VS_INFO("ipvs loaded.\n");
  35.   return ret;
  36.  
  37.   cleanup_conn:
  38.   ip_vs_conn_cleanup(); //清除ip_vs_conn_init()产生的连接参数
  39.   cleanup_app:
  40.   ip_vs_app_cleanup(); //清除ip_vs_app_init()产生的参数
  41.   cleanup_protocol:
  42.   ip_vs_protocol_cleanup(); //清除ip_vs_protocol_init()产生的参数
  43.   ip_vs_control_cleanup(); //..
  44.   cleanup_nothing:
  45.   return ret;
  46.  }

ip_vs_control_init()

ipvs一旦运起起来,管理员是通过ipvsadm来控制它的.在这个部分里,将会搞清楚用户空间工具ipvsadm是如何同内核空间ipvs通讯的,以及ipvsadm所做的配置是以什么形式保存的

net/ipv4/ipvs/ip_vs_ctl.c

  1.  int ip_vs_control_init(void)
  2.  {
  3.   int ret;
  4.   int idx;
  5.  
  6.   EnterFunction(2); //如果开启了debug功能,EnterFunction()函数将显示debug信息
  7.  
  8.   ret = nf_register_sockopt(&ip_vs_sockopts); //注册ip_vs_sockopts[]内的opt,这样用户空间可通过setsockopt()/getsockopt()函数来和ipvs通讯
  9.   if (ret) {
  10.   IP_VS_ERR("cannot register sockopt.\n");
  11.   return ret;
  12.   }
  13.  
  14.   proc_net_fops_create(&init_net, "ip_vs", 0, &ip_vs_info_fops); //建立/proc/net/ip_vs和/proc/net/ip_vs_stats只读项,便于读取ipvs信息
  15.   proc_net_fops_create(&init_net, "ip_vs_stats",0, &ip_vs_stats_fops);
  16.  
  17.   sysctl_header = register_sysctl_paths(net_vs_ctl_path, vs_vars); //建立/proc/sys/net/ipv4/vs目录下的各可读写控制参数,路径由net_vs_ctl_path定义,文件个数以及文件的参数由vs_vars定义
  18.  
  19.   /* Initialize ip_vs_svc_table, ip_vs_svc_fwm_table, ip_vs_rtable */
  20.   for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
  21.   INIT_LIST_HEAD(&ip_vs_svc_table[idx]); //创建并初始化ip_vs_svc_table[255],用户通过协议/地址/端口信息来查找vs服务信息的hash双向链表
  22.   INIT_LIST_HEAD(&ip_vs_svc_fwm_table[idx]); //同上
  23.   }
  24.   for(idx = 0; idx < IP_VS_RTAB_SIZE; idx++) {
  25.   INIT_LIST_HEAD(&ip_vs_rtable[idx]); //创建并初始化ip_vs_rtable[16],用户通过协议/地址/端口信息来查找目的rs主机信息的hash双向链表
  26.   }
  27.  
  28.   memset(&ip_vs_stats, 0, sizeof(ip_vs_stats)); //将ip_vs_stats指向的空间用0填充,相当于初始化ip_vs_stats,
  29.   spin_lock_init(&ip_vs_stats.lock); //ip_vs_stats的结构在ip_vs.h中定义,为全局变量,内部存放了vs(虚拟服务)的状态(接受/发送的数据包/数据量/速率等信息,以及一个自旋锁
  30.   ip_vs_new_estimator(&ip_vs_stats); //对ip_vs_stats建立一个预估器,可用于计算服务器的性能参数.相当于从ip_vs_stats复制了一份状态信息到est_list循环单向链表
  31.  // est_list -> est.next -> set.next -> est_list
  32.   /* Hook the defense timer */
  33.   schedule_delayed_work(&defense_work, DEFENSE_TIMER_PERIOD);
  34.  //延迟调度工作, 延迟一定时间后再将工作结构挂接到工作队列,加入的任务defense_work好像是空的,由DECLARE_DELAYED_WORK(defense_work, defense_work_handler)初始化完成,DECLARE_DELAYED_WORK() -> __DELAYED_WORK_INITIALIZER -> __WORK_INITIALIZER(TIMER_INITIALIZER) -> __WORK_INITIALIZER -> __WORK_INIT_LOCKDEP_MAP
  35.   LeaveFunction(2); //报告一个debug信息,最后返回0,用来表示函数正常结束
  36.   return 0;
  37.  }

根据我个人的经验,再linux内核中对sock进行操作的时候,大多都会有些结构名称带有opt/ops.
opt一般是泛指某种具体的操作(operate),可能是添加/删除/修改某个连接...比如nf_register_sockopt(),nf_setsockopt(),nf_sockopt_find().
ops可理解为opt的复数形式,表示一钟类型操作的集合,结构中一般具有对sock进行处理的函数指针,一般为双向链表结构,协议族类型,比如nf_hook_ops,nf_sockopt_ops

在这个函数里出现了一个ip_vs_sockopts变量

这个变量的内容包含了用户空间ipvsadm所能做出的所有操作,每个具体的操作用一个数字(int)表示,这里的操作是对于netfilter而言的,所以ipvs的操作代号不能和netfilter或者netfilter的其他模块冲突,必须唯一,否则将无法用nf_register_sockopt()注册

操作分为2类(get/set),每类操作都有一个范围(min/max),用nf_register_sockopt()进行注册,成功以后,就会将ip_vs_sockopts加入到nf_sockopts双向链表中,ipvs的操作就像是一个嵌入到netfilter的模块,每当用户空间使用getsockopt() 以及 setsockopt()函数时,内核空间会去调用ip_setsockopt() 以及ip_getsockopt(),如果操作(opt)不认识,而且系统内有netfilter模块,则还会去尝试调用nf_setsockopt() 以及 nf_getsockopt()这个2个函数都会调用nf_sockopt()nf_sockopt_find()寻找有没有相对应的opt.从代码中可以看出nf_sockopt_find()会遍历nf_sockopts这个双向链表,找出能覆盖opt范围(optmin < opt < optmax)的ops节点.如果找到了ops,就调用相对应的ops->get()或者ops->set()

nf_sockopts的结构如下

   +-<-----------------------------------<-----------~~~---------------------<-+
   |     +-----------+            +--------------+         +-------------+     |
   +---> |nf_sockopts|            |ip_vs_sockopts|         | ..._sockopts|     |
         |   next --------------> |  list.next ------~~~-> |  list.next ---->--+
   +-<------ prev    | <------------ list.prev   | <-~~~----- list.prev  |
   |     +-----------+            |    get       |         |    get      |
   |                              |  compat_get  |         |  compat_get |
   |                              |  get_optmin  |         |  get_optmin |
   |                              |  get_optmax  |         |  get_optmax |
   |                              |    set       |         |    set      |
   |                              |  compat_set  |         |  compat_set | <---+
   |                              |  set_optmin  |         |  get_optmin |     |
   |                              |  set_optmax  |         |  get_optmax |     |
   |                              +--------------+         +-------------+     |
   +->----------------------------------->-----------~~~--------------------->-+

在ip_vs_sockopts中,get和set这2个函数指针分别指向了do_ip_vs_get_ctl()和do_ip_vs_set_ctl()这2个函数

do_ip_vs_get_ctl()

net/ipv4/ipvs/ip_vs_ctl.c

  1.  static int
  2.  do_ip_vs_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
  3.  {
  4.   unsigned char arg[128]; //用来存放用户空间的参数
  5.   int ret = 0;
  6.  
  7.   if (!capable(CAP_NET_ADMIN)) //判断是否有管理员权限
  8.   return -EPERM;
  9.  
  10.   if (*len < get_arglen[GET_CMDID(cmd)]) { //判断参数长度是否异常
  11.   IP_VS_ERR("get_ctl: len %u < %u\n",
  12.   *len, get_arglen[GET_CMDID(cmd)]);
  13.   return -EINVAL;
  14.   }
  15.  
  16.   if (copy_from_user(arg, user, get_arglen[GET_CMDID(cmd)]) != 0) //内核空间不能直接访问用户空间的user变量,需要copy_from_user()函数将参数拷贝到arg内
  17.   return -EFAULT;
  18.  
  19.   if (mutex_lock_interruptible(&__ip_vs_mutex)) //得到互斥锁,上锁
  20.   return -ERESTARTSYS;
  21.  
  22.   switch (cmd) { //根据不同的get动作,调用相应的函数
  23.   case IP_VS_SO_GET_VERSION:
  24.   {
  25.   char buf[64];
  26.  
  27.   sprintf(buf, "IP Virtual Server version %d.%d.%d (size=%d)", //将ipvs的版本存入buf内
  28.   NVERSION(IP_VS_VERSION_CODE), IP_VS_CONN_TAB_SIZE);
  29.   if (copy_to_user(user, buf, strlen(buf)+1) != 0) { //使用copy_to_user将buf的内容拷贝到用户空间的user变量
  30.   ret = -EFAULT;
  31.   goto out;
  32.   }
  33.   *len = strlen(buf)+1;
  34.   }
  35.   break;
  36.  
  37.   case IP_VS_SO_GET_INFO: //获得版本信息
  38.   {
  39.   struct ip_vs_getinfo info;
  40.   info.version = IP_VS_VERSION_CODE;
  41.   info.size = IP_VS_CONN_TAB_SIZE;
  42.   info.num_services = ip_vs_num_services;
  43.   if (copy_to_user(user, &info, sizeof(info)) != 0)
  44.   ret = -EFAULT;
  45.   }
  46.   break;
  47.  
  48.   case IP_VS_SO_GET_SERVICES: //获得多个服务器规则信息
  49.   {
  50.   struct ip_vs_get_services *get;
  51.   int size;
  52.  
  53.   get = (struct ip_vs_get_services *)arg; //做一个类型转换给get
  54.   size = sizeof(*get) +
  55.   sizeof(struct ip_vs_service_entry) * get->num_services; //得到get指向变量的大小
  56.   if (*len != size) { //比较size的大小,如果于len不等,退出
  57.   IP_VS_ERR("length: %u != %u\n", *len, size);
  58.   ret = -EINVAL;
  59.   goto out;
  60.   }
  61.   ret = __ip_vs_get_service_entries(get, user); //依次得到get指向vs服务(多个)的规则信息,用ip_vs_copy_service()/ip_vs_copy_stats()复制变量,然后用copy_to_user()将其拷贝到用户空间,其中大量用到memcpy()
  62.   }
  63.   break;
  64.  
  65.   case IP_VS_SO_GET_SERVICE: //得到一个vs服务的规则信息
  66.   {
  67.   struct ip_vs_service_entry *entry;
  68.   struct ip_vs_service *svc;
  69.  
  70.   entry = (struct ip_vs_service_entry *)arg;
  71.   if (entry->fwmark) //如果有firwall,则通过掩码得到vs服务指针svc
  72.   svc = __ip_vs_svc_fwm_get(entry->fwmark);
  73.   else //否则通过协议/地址/端口得到vs服务指针svc
  74.   svc = __ip_vs_service_get(entry->protocol,
  75.   entry->addr, entry->port);
  76.   if (svc) {
  77.   ip_vs_copy_service(entry, svc); //将svc的内容复制给entry
  78.   if (copy_to_user(user, entry, sizeof(*entry)) != 0) //将entry的内容复制给用户空间变量user
  79.   ret = -EFAULT;
  80.   ip_vs_service_put(svc);
  81.   } else
  82.   ret = -ESRCH;
  83.   }
  84.   break;
  85.  
  86.   case IP_VS_SO_GET_DESTS: //得到多个rs服务信息
  87.   {
  88.   struct ip_vs_get_dests *get;
  89.   int size;
  90.  
  91.   get = (struct ip_vs_get_dests *)arg;
  92.   size = sizeof(*get) +
  93.   sizeof(struct ip_vs_dest_entry) * get->num_dests;
  94.   if (*len != size) {
  95.   IP_VS_ERR("length: %u != %u\n", *len, size);
  96.   ret = -EINVAL;
  97.   goto out;
  98.   }
  99.   ret = __ip_vs_get_dest_entries(get, user); //复制到用户空间
  100.   }
  101.   break;
  102.  
  103.   case IP_VS_SO_GET_TIMEOUT: //获取超时信息
  104.   {
  105.   struct ip_vs_timeout_user t;
  106.  
  107.   __ip_vs_get_timeouts(&t);
  108.   if (copy_to_user(user, &t, sizeof(t)) != 0)
  109.   ret = -EFAULT;
  110.   }
  111.   break;
  112.  
  113.   case IP_VS_SO_GET_DAEMON: //获取IPVS内核守护进程信息结构
  114.   {
  115.   struct ip_vs_daemon_user d[2];
  116.  
  117.   memset(&d, 0, sizeof(d));
  118.   if (ip_vs_sync_state & IP_VS_STATE_MASTER) {
  119.   d[0].state = IP_VS_STATE_MASTER;
  120.   strlcpy(d[0].mcast_ifn, ip_vs_master_mcast_ifn, sizeof(d[0].mcast_ifn));
  121.   d[0].syncid = ip_vs_master_syncid;
  122.   }
  123.   if (ip_vs_sync_state & IP_VS_STATE_BACKUP) {
  124.   d[1].state = IP_VS_STATE_BACKUP;
  125.   strlcpy(d[1].mcast_ifn, ip_vs_backup_mcast_ifn, sizeof(d[1].mcast_ifn));
  126.   d[1].syncid = ip_vs_backup_syncid;
  127.   }
  128.   if (copy_to_user(user, &d, sizeof(d)) != 0)
  129.   ret = -EFAULT;
  130.   }
  131.   break;
  132.  
  133.   default:
  134.   ret = -EINVAL;
  135.   }
  136.  
  137.   out:
  138.   mutex_unlock(&__ip_vs_mutex); //解锁
  139.   return ret;
  140.  }

do_ip_vs_set_ctl()

net/ipv4/ipvs/ip_vs_ctl.c

  1.  static int
  2.  do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
  3.  {
  4.   int ret;
  5.   unsigned char arg[MAX_ARG_LEN];
  6.   struct ip_vs_service_user *usvc;
  7.   struct ip_vs_service *svc;
  8.   struct ip_vs_dest_user *udest;
  9.  
  10.   if (!capable(CAP_NET_ADMIN))
  11.   return -EPERM;
  12.  
  13.   if (len != set_arglen[SET_CMDID(cmd)]) { //检查len准确性
  14.   IP_VS_ERR("set_ctl: len %u != %u\n",
  15.   len, set_arglen[SET_CMDID(cmd)]);
  16.   return -EINVAL;
  17.   }
  18.  
  19.   if (copy_from_user(arg, user, len) != 0) //将用户空间的user变量内容复制给arg变量
  20.   return -EFAULT;
  21.  
  22.   /* increase the module use count */
  23.   ip_vs_use_count_inc(); //ipvs使用计数器+1
  24.  
  25.   if (mutex_lock_interruptible(&__ip_vs_mutex)) { //给ipvs上锁
  26.   ret = -ERESTARTSYS;
  27.   goto out_dec;
  28.   }
  29.  
  30.   if (cmd == IP_VS_SO_SET_FLUSH) {
  31.   /* Flush the virtual service */
  32.   ret = ip_vs_flush();
  33.   goto out_unlock;
  34.   } else if (cmd == IP_VS_SO_SET_TIMEOUT) {
  35.   /* Set timeout values for (tcp tcpfin udp) */
  36.   ret = ip_vs_set_timeout((struct ip_vs_timeout_user *)arg);
  37.   goto out_unlock;
  38.   } else if (cmd == IP_VS_SO_SET_STARTDAEMON) { //开启守护进程
  39.   struct ip_vs_daemon_user *dm = (struct ip_vs_daemon_user *)arg;
  40.   ret = start_sync_thread(dm->state, dm->mcast_ifn, dm->syncid);
  41.   goto out_unlock;
  42.   } else if (cmd == IP_VS_SO_SET_STOPDAEMON) { //关闭守护进程
  43.   struct ip_vs_daemon_user *dm = (struct ip_vs_daemon_user *)arg;
  44.   ret = stop_sync_thread(dm->state);
  45.   goto out_unlock;
  46.   }
  47.  
  48.   usvc = (struct ip_vs_service_user *)arg; //将usvc指向arg,并将类型转化为ip_vs_service_user
  49.   udest = (struct ip_vs_dest_user *)(usvc + 1); //将udest类型转化为ip_vs_dest_user,并指向usvc的后一个单位
  50.  
  51.   if (cmd == IP_VS_SO_SET_ZERO) { //如果是归零操作,先查看是否是对于某个指定服务的,如果不是,则全部清零
  52.   /* if no service address is set, zero counters in all */
  53.   if (!usvc->fwmark && !usvc->addr && !usvc->port) {
  54.   ret = ip_vs_zero_all();
  55.   goto out_unlock;
  56.   }
  57.   }
  58.  
  59.   /* Check for valid protocol: TCP or UDP, even for fwmark!=0 */
  60.   if (usvc->protocol!=IPPROTO_TCP && usvc->protocol!=IPPROTO_UDP) { //如果usvc协议类型不是tcp/udp,则跳至out_unlock
  61.   IP_VS_ERR("set_ctl: invalid protocol: %d %d.%d.%d.%d:%d %s\n",
  62.   usvc->protocol, NIPQUAD(usvc->addr),
  63.   ntohs(usvc->port), usvc->sched_name);
  64.   ret = -EFAULT;
  65.   goto out_unlock;
  66.   }
  67.  
  68.   /* Lookup the exact service by <protocol, addr, port> or fwmark */
  69.   if (usvc->fwmark == 0) //如果fwmark为0,通过协议,地址,端口的hash值到ip_vs_svc_table[]双向链表里寻找相应的svc
  70.   svc = __ip_vs_service_get(usvc->protocol,
  71.   usvc->addr, usvc->port);
  72.   else //否则通过fwmark的hash值到ip_vs_svc_fwm_table[]双向链表里寻找相应的svc
  73.   svc = __ip_vs_svc_fwm_get(usvc->fwmark);
  74.  
  75.   if (cmd != IP_VS_SO_SET_ADD //不是添加规则时,而且svc没有找到或者于vsvc的协议类型不符,则跳至out_unlock
  76.   && (svc == NULL || svc->protocol != usvc->protocol)) {
  77.   ret = -ESRCH;
  78.   goto out_unlock;
  79.   }
  80.  
  81.   switch (cmd) {
  82.   case IP_VS_SO_SET_ADD: //添加服务器规则(svc)
  83.   if (svc != NULL)
  84.   ret = -EEXIST;
  85.   else
  86.   ret = ip_vs_add_service(usvc, &svc); //该函数会判断svc是否已存在,如果不存在,会将svc添加到相应hash值的列表中(ip_vs_svc_fwm_table[]和ip_vs_svc_table[])
  87.   break;
  88.   case IP_VS_SO_SET_EDIT: //编辑
  89.   ret = ip_vs_edit_service(svc, usvc);
  90.   break;
  91.   case IP_VS_SO_SET_DEL: //删除
  92.   ret = ip_vs_del_service(svc);
  93.   if (!ret)
  94.   goto out_unlock;
  95.   break;
  96.   case IP_VS_SO_SET_ZERO: //将特定svc计数清零
  97.   ret = ip_vs_zero_service(svc);
  98.   break;
  99.   case IP_VS_SO_SET_ADDDEST: //将rs服务器信息添加到ip_vs_rtable[],svc->destinations中
  100.   ret = ip_vs_add_dest(svc, udest);
  101.   break;
  102.   case IP_VS_SO_SET_EDITDEST: //编辑rs服务器信息
  103.   ret = ip_vs_edit_dest(svc, udest);
  104.   break;
  105.   case IP_VS_SO_SET_DELDEST: //将rs服务器信息从svc->destinations中删除
  106.   ret = ip_vs_del_dest(svc, udest);
  107.   break;
  108.   default:
  109.   ret = -EINVAL;
  110.   }
  111.  
  112.   if (svc)
  113.   ip_vs_service_put(svc); //svc使用完毕,释放svc使用计数
  114.  
  115.   out_unlock:
  116.   mutex_unlock(&__ip_vs_mutex); //ipvs解锁
  117.   out_dec:
  118.   /* decrease the module use count */
  119.   ip_vs_use_count_dec(); //ipvs使用完毕,释放ipvs计数器
  120.  
  121.   return ret;
  122.  }

可以看到,set/get的大部分操作就是对svc/rs的信息进行读取/添加/修改/删除,那么这么多信息,如何才能快速得访问呢?这里用到了3个hash的双向链表结构

我们可以用svc的协议/地址/端口通过hash换算得到一个0~255范围内的值x,然后对ip_vs_svc_table[x]进行操作
我们可以用svc的firewall mask通过hash换算得到一个0~255范围内的值x,然后对ip_vs_svc_fwm_table[x]进行操作
我们可以用rs的协议/地址/端口通过hash换算得到一个0~15范围内的值x,然后对ip_vs_rtable[x]进行操作
举一个例子,链表结构大致是这个样子的

ip_vs_svc_table[0] <-> svc.list <-> svc.list <-~~-> ip_vs_svc_table[0]
ip_vs_svc_table[1] <-> svc.list <-> svc.list <-~~-> ip_vs_svc_table[1]
ip_vs_svc_table[2] <-> svc.list <-> svc.list <-~~-> ip_vs_svc_table[2]
~
~
ip_vs_svc_table[255] <-> svc.list <-> svc.list <-> ... <-> ip_vs_svc_table[255]


ip_vs_protocol_init()

初始化ipvs能支持的协议

net/ipv4/ipvs/ip_vs_proto.c

  1.  int ip_vs_protocol_init(void)
  2.  {
  3.   char protocols[64];
  4.  #define REGISTER_PROTOCOL(p) \ //用来注册协议的宏
  5.   do { \
  6.   register_ip_vs_protocol(p); \ //注册协议的函数
  7.   strcat(protocols, ", "); \ //将 ",(p)->name"添加到字符串protocols之后
  8.   strcat(protocols, (p)->name); \
  9.   } while (0)
  10.  
  11.   protocols[0] = '\0'; //初始化protocols
  12.   protocols[2] = '\0';
  13.  #ifdef CONFIG_IP_VS_PROTO_TCP
  14.   REGISTER_PROTOCOL(&ip_vs_protocol_tcp); //使得ipvs能支持TCP协议
  15.  #endif
  16.  #ifdef CONFIG_IP_VS_PROTO_UDP
  17.   REGISTER_PROTOCOL(&ip_vs_protocol_udp); //注册UDP协议
  18.  #endif
  19.  #ifdef CONFIG_IP_VS_PROTO_AH
  20.   REGISTER_PROTOCOL(&ip_vs_protocol_ah); //注册AH(认证头)协议
  21.  #endif
  22.  #ifdef CONFIG_IP_VS_PROTO_ESP
  23.   REGISTER_PROTOCOL(&ip_vs_protocol_esp); //注册ESP(封装安全载荷)协议
  24.  #endif
  25.   IP_VS_INFO("Registered protocols (%s)\n", &protocols[2]);
  26.   //debug信息,从protocols的第2个字符开始输出
  27.   return 0;
  28.  }

认证头(AH)协议:协议的目的是用来增加IP数据包的安全性。AH协议提供无连接的完整性、数据源认证和抗重播保护服务。

封装安全载荷(ESP)协议:协议的目的和认证头(AH)一样,是用于提高IP的安全性。ESP提供数据保密、数据源认证、无连接完整性、抗重播服务和有限的数据流保护。

对于ipvs来说,其中ah,esp只与传输层有关,一般不用做什么处理,相应的代码可以在ip_vs_proto_esp/ah.c中找到

tcp协议和udp协议基本上大同小异,这里主要对tcp进行分析,用来注册的变量ip_vs_protocol_tcp如下

net/ipv4/ipvs/ip_vs_proto_tcp.c

  1.  struct ip_vs_protocol ip_vs_protocol_tcp = {
  2.   .name = "TCP",
  3.   .protocol = IPPROTO_TCP,
  4.   .num_states = IP_VS_TCP_S_LAST, //协议的状态,比如ESTABLISHED/LISTEN/LAST等
  5.   .dont_defrag = 0, //禁止重组***,ah/esp=1,tcp/udp=0
  6.   .appcnt = ATOMIC_INIT(0), //连接数
  7.   .init = ip_vs_tcp_init, //协议初始化的函数名
  8.   .exit = ip_vs_tcp_exit, //协议退出时的函数名
  9.   .register_app = tcp_register_app, //注册函数
  10.   .unregister_app = tcp_unregister_app, //注销函数
  11.   .conn_schedule = tcp_conn_schedule, //建立连接函数
  12.   .conn_in_get = tcp_conn_in_get, //
  13.   .conn_out_get = tcp_conn_out_get, //
  14.   .snat_handler = tcp_snat_handler, //snat
  15.   .dnat_handler = tcp_dnat_handler, //dnat
  16.   .csum_check = tcp_csum_check, //
  17.   .state_name = tcp_state_name, //
  18.   .state_transition = tcp_state_transition, //
  19.   .app_conn_bind = tcp_app_conn_bind, //
  20.   .debug_packet = ip_vs_tcpudp_debug_packet, //
  21.   .timeout_change = tcp_timeout_change, //
  22.   .set_state_timeout = tcp_set_state_timeout, //
  23.  };

这个变量是使用register_ip_vs_protocol()来注册的

net/ipv4/ipvs/ip_vs_proto.c

  1.  /*
  2.   * register an ipvs protocol
  3.   */
  4.  static int __used register_ip_vs_protocol(struct ip_vs_protocol *pp)
  5.  {
  6.   unsigned hash = IP_VS_PROTO_HASH(pp->protocol); //用pp->protocol(u16)算出hashkey
  7.  
  8.   pp->next = ip_vs_proto_table[hash]; //将pp插入到ip_vs_proto_table[hash]之后
  9.   ip_vs_proto_table[hash] = pp; //ip_vs_proto_table[hash]是一个单向链表
  10.  
  11.   if (pp->init != NULL) //如果该协议定义了初始化函数,运行之
  12.   pp->init(pp);
  13.  
  14.   return 0;
  15.  }

看到这里,有些人可能会认为一共才4个协议,感觉hash表+单链结构是不是太过复杂了?看看下面的hashkey的取得就能明白了,其实就是用protocol当hashkey.单链结构也是非常简单的,总共只用到32个指针

net/ipv4/ipvs/ip_vs_proto.c

  1.  /*
  2.   * IPVS protocols can only be registered/unregistered when the ipvs
  3.   * module is loaded/unloaded, so no lock is needed in accessing the
  4.   * ipvs protocol table.
  5.   */
  6.  
  7.  #define IP_VS_PROTO_TAB_SIZE 32 /* must be power of 2 */
  8.  #define IP_VS_PROTO_HASH(proto) ((proto) & (IP_VS_PROTO_TAB_SIZE-1))
  9.  
  10.  static struct ip_vs_protocol *ip_vs_proto_table[IP_VS_PROTO_TAB_SIZE];

接下来看看tcp协议是如何初始化的

net/ipv4/ipvs/ip_vs_proto_tcp.c

  1.  static void ip_vs_tcp_init(struct ip_vs_protocol *pp)
  2.  {
  3.   IP_VS_INIT_HASH_TABLE(tcp_apps); //初始化tcp_apps[16]双向链表数组
  4.   pp->timeout_table = tcp_timeouts; //超时列表赋值
  5.  }

到这里,关于tcp协议的初始化就完成了


ip_vs_app_init()

net/ipv4/ipvs/ip_vs_app.c

  1.  int ip_vs_app_init(void)
  2.  {
  3.   /* we will replace it with proc_net_ipvs_create() soon */
  4.   proc_net_fops_create(&init_net, "ip_vs_app", 0, &ip_vs_app_fops); //建立/proc/net/ip_vs_app只读项
  5.   return 0;
  6.  }

这个部分是由ip_masq_app.c文件改写而成,原先用作数据报文的伪装(在kernel2.2中).ipvs在具体运行中好像并没有用到这个部分(至少我测试的时候,这部分的数据一直为空)


ip_vs_conn_init()

net/ipv4/ipvs/ip_vs_conn.c

  1.  int ip_vs_conn_init(void)
  2.  {
  3.   int idx;
  4.  
  5.   /*
  6.   * Allocate the connection hash table and initialize its list heads
  7.   */
  8.   ip_vs_conn_tab = vmalloc(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head));
  9.   if (!ip_vs_conn_tab) //CONFIG_IP_VS_TAB_BITS会参照CONFIG_IP_VS_TAB_BITS的数值,但在[8,20]之间浮动
  10.   return -ENOMEM; //为记录连接信息分配列表存储空间
  11.  
  12.   /* Allocate ip_vs_conn slab cache *///这里是在系统的缓存区注册一个ip_vs_conn_cachep类型的内存池,一个单位大小就是sizeof(struct ip_vs_conn),如果需要分配一个内存空间,可以用kmem_cache_zalloc()来分配一个单位大小,具体实现在mm/slab.c里
  13.   ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn",
  14.   sizeof(struct ip_vs_conn), 0,
  15.   SLAB_HWCACHE_ALIGN, NULL);
  16.   if (!ip_vs_conn_cachep) {
  17.   vfree(ip_vs_conn_tab);
  18.   return -ENOMEM;
  19.   }
  20.  
  21.   IP_VS_INFO("Connection hash table configured " //debug信息
  22.   "(size=%d, memory=%ldKbytes)\n",
  23.   IP_VS_CONN_TAB_SIZE,
  24.   (long)(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head))/1024);
  25.   IP_VS_DBG(0, "Each connection entry needs %Zd bytes at least\n",
  26.   sizeof(struct ip_vs_conn));
  27.  
  28.   for (idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) { //初始化ip_vs_conn_tab[]的双向链表结构
  29.   INIT_LIST_HEAD(&ip_vs_conn_tab[idx]);
  30.   }
  31.  
  32.   for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++) { //初始化array for conn table的锁结构,初始为未上锁
  33.   rwlock_init(&__ip_vs_conntbl_lock_array[idx].l);
  34.   }
  35.  
  36.   proc_net_fops_create(&init_net, "ip_vs_conn", 0, &ip_vs_conn_fops);//创建/proc/net/ipvs_conn(_sync)只读文件
  37.   proc_net_fops_create(&init_net, "ip_vs_conn_sync", 0, &ip_vs_conn_sync_fops);
  38.  
  39.   /* calculate the random value for connection hash */
  40.   get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd)); //向ip_vs_conn_rnd注入sizeof(ip_vs_conn_rnd)大小的随机信息(int类型),用来产生hash值的jhash_3words()函数会用到这个变量
  41.  
  42.   return 0;
  43.  }

值得留意的是用来存放连接状态的数据结构使用了kmem_cache_create()来注册到系统的缓存区.由于ipvs运行以后,连接会非常频繁,那么用来存储连接状态的conn_table会不断的添加,删除.对于内存来说,会有很大开销,而频繁释放和分配的内存他们的结构和大小是一致的.slab算法就是针对这样特点设计,会大幅度提高使用效率,用kmem_cache_zalloc申请的内存时带上ip_vs_conn_cachep这个参数,就会在ip_vs_conn_cachep的内存块里分配到一个单位的空间,对于kmem_cache_free和kmem_cache_destroy也一样.

这样,连接的初始化也就完成了


nf_register_hooks()

net/netfilter/core.c

  1.  int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n)
  2.  {
  3.   unsigned int i;
  4.   int err = 0;
  5.  
  6.   for (i = 0; i < n; i++) { //可以看到,这里就是将*reg数组里的每个元素一个一个的用nf_register_hook()进行注册
  7.   err = nf_register_hook(&reg[i]);
  8.   if (err)
  9.   goto err;
  10.   }
  11.   return err;
  12.  
  13.  err: //出现异常就注销
  14.   if (i > 0)
  15.   nf_unregister_hooks(reg, i);
  16.   return err;
  17.  }
  18.  EXPORT_SYMBOL(nf_register_hooks); //让其他模块能也能调用此函数

那再来看看nf_register_hook()是如何工作的

net/netfilter/core.c

  1.  int nf_register_hook(struct nf_hook_ops *reg) //*reg就是将要添加到nf_hooks列表中的ops了
  2.  {
  3.   struct nf_hook_ops *elem; //用来遍历数组用的临时指针
  4.   int err;
  5.  
  6.   err = mutex_lock_interruptible(&nf_hook_mutex); //nf_hook_mutex变量是nf_hook的互斥锁,操作的时候先要得到锁
  7.   if (err < 0)
  8.   return err;
  9.   list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {//遍历nf_hooks[reg->pf][reg->hooknum]->list双向链表
  10.   if (reg->priority < elem->priority) //比较优先级,priority值越小,优先级越高.这里按照优先级由高到低的顺序插入到nf_hooks链表里
  11.   break;
  12.   }
  13.   list_add_rcu(&reg->list, elem->list.prev); //进行加入,elem->list.prev就是找到的插入点
  14.   mutex_unlock(&nf_hook_mutex); //释放锁
  15.   return 0;
  16.  }
  17.  EXPORT_SYMBOL(nf_register_hook);

运行到这里,所有的准备工作就完成了.管理员就可以在用户空间使用ipvsadm工具向ipvs发出指令.如果ipvs中有相应的配置,那么ipvs也就会工作起来

ipvs是如何工作的呢?是由什么事件来触发ipvs的进行相应的操作呢?那就继续往下,进入第二部分的分析《运行中的ipvs》


参考资料

  1. Kernel command using Linux system calls
  2. netfilter: Linux 防火墙在内核中的实现
 
Done in 0.435304164886 seconds