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的数据报处理

ipvs的最基本原理就是数据报的重定向传送.这个部分用如下拓扑图来分析ipvs中关于此部分的过程

.
                        ________
                       |        |
                       | client |
                       |________|
                       CIP=192.168.1.254
                           |
                        (router)
                           |
             __________    |
            |          |   |   VIP=192.168.1.110 (eth0:110)
            | director |---|
            |__________|   |   DIP=10.1.1.9 (eth0:9)
                           |
                           |
          -----------------------------------
          |                |                |
          |                |                |
   RIP1=10.1.1.2      RIP2=10.1.1.3   RIP3=10.1.1.4 (all eth0)
   _____________     _____________    _____________
  |             |   |             |  |             |
  | realserver  |   | realserver  |  | realserver  |
  |_____________|   |_____________|  |_____________|

 

(一)注册钩子函数

nf_register_hooks() -> nf_register_hook() 将ip_vs_ops[]里的4个hook添加到了nf_hooks[PF_INET][HOOK_NAME]内.每当有数据经过相应的规则链时,就会调用相应的函数对数据报进行处理.

(二)注册ipvs支持的协议

register_ip_vs_protocol()将能支持的协议加入到ip_vs_proto_table[]中.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.  };

(三)添加虚拟服务器

举个具体的例子吧,如果对于NF_INET_LOCAL_IN规则链上的ip_vs_in()而言,他会怎么去工作呢?答案是如果没有规则,那么这个函数将什么都不会做

接下来分析的重点就是如何添加规则,让ip_vs_in()这个函数知道怎么处理特定的数据.

要想添加规则,首先要做的就是在用户空间添加一个vs(虚拟主机)到内核空间.内容是添加一个虚拟服务器,协议为tcp,虚拟服务器地址是192.168.1.110:80,使用的调度方法是轮叫调度Round-Robin Scheduling

ipvsadm -A -t 192.168.1.110:80 -s rr

虚拟服务信息(svc)将存放在ip_vs_svc_table[],如果你的规则用到了防火墙标记,那么将会存放在ip_vs_svc_fwm_table[]内,在内核空间,如下代码会被执行

net/ipv4/ipvs/ip_vs_ctl.c

  1.  /*
  2.   * Add a service into the service hash table
  3.   */
  4.  static int
  5.  ip_vs_add_service(struct ip_vs_service_user *u, struct ip_vs_service **svc_p)
  6.  {
  7.   int ret = 0;
  8.   struct ip_vs_scheduler *sched = NULL; //调度控制结构
  9.   struct ip_vs_service *svc = NULL; //虚拟服务结构
  10.  
  11.   /* increase the module use count */
  12.   ip_vs_use_count_inc();
  13.  
  14.   /* Lookup the scheduler by 'u->sched_name' */
  15.   sched = ip_vs_scheduler_get(u->sched_name); //看看将要添加的服务的调度模块是否存在;如果不存在,尝试加载该模块;如果加载失败,则返回NULL; 这样做能避免不必要的模块被加载,总共有ip_vs_rr.o、ip_vs_wrr.o、ip_vs_lc.o、ip_vs_wlc.o、ip_vs_lblc.o、ip_vs_lblcr.o、ip_vs_dh.o、ip_vs_sh.o、ip_vs_sed.o和ip_vs_nq.o这样10个模块,在我们这个例子中,使用的是ip_vs_rr(轮叫调度Round-Robin Scheduling)
  16.   if (sched == NULL) {
  17.   IP_VS_INFO("Scheduler module ip_vs_%s not found\n",
  18.   u->sched_name);
  19.   ret = -ENOENT;
  20.   goto out_mod_dec;
  21.   }
  22.  
  23.   svc = kzalloc(sizeof(struct ip_vs_service), GFP_ATOMIC); //看来需要为加入ip_vs_svc_table[]准备内存空间了
  24.   if (svc == NULL) {
  25.   IP_VS_DBG(1, "ip_vs_add_service: kmalloc failed.\n");
  26.   ret = -ENOMEM;
  27.   goto out_err;
  28.   }
  29.  
  30.   /* I'm the first user of the service */
  31.   atomic_set(&svc->usecnt, 1); //使用计数器
  32.   atomic_set(&svc->refcnt, 0); //引用计数器
  33.  
  34.   svc->protocol = u->protocol; //协议,地址,端口,防火墙标记,svc标记,超时时长,掩码等的赋值
  35.   svc->addr = u->addr;
  36.   svc->port = u->port;
  37.   svc->fwmark = u->fwmark;
  38.   svc->flags = u->flags;
  39.   svc->timeout = u->timeout * HZ;
  40.   svc->netmask = u->netmask;
  41.  
  42.   INIT_LIST_HEAD(&svc->destinations); //初始化svc的rs列表,现在还没有任何rs,所以先将该双向链表初始化,便于以后rs的添加
  43.   rwlock_init(&svc->sched_lock); //读写锁初始化
  44.   spin_lock_init(&svc->stats.lock); //互斥锁初始化
  45.  
  46.   /* Bind the scheduler */
  47.   ret = ip_vs_bind_scheduler(svc, sched); //初始化svc的调度模块,我们的例子里用的是rr模块.将对svc->scheduler赋值,并且运行svc->scheduler->init_service(),对于rr来说,就是执行ip_vs_rr_init_svc(),将svc->sched_data = &svc->destinations;
  48.   if (ret)
  49.   goto out_err;
  50.   sched = NULL;
  51.  
  52.   /* Update the virtual service counters */
  53.   if (svc->port == FTPPORT) //对于ftp和端口为0的svc计数
  54.   atomic_inc(&ip_vs_ftpsvc_counter);
  55.   else if (svc->port == 0)
  56.   atomic_inc(&ip_vs_nullsvc_counter);
  57.  
  58.   ip_vs_new_estimator(&svc->stats); //new一个评估性能的estimator
  59.   ip_vs_num_services++; //svc计数器
  60.  
  61.   /* Hash the service into the service table */
  62.   write_lock_bh(&__ip_vs_svc_lock); //上锁
  63.   ip_vs_svc_hash(svc); //将svc加入ip_vs_svc_table[]或者ip_vs_svc_fwm_table[]
  64.   write_unlock_bh(&__ip_vs_svc_lock); //解锁
  65.  
  66.   *svc_p = svc;
  67.   return 0;
  68.  
  69.   out_err: //出错时,注销掉已经做过的操作
  70.   if (svc != NULL) {
  71.   if (svc->scheduler)
  72.   ip_vs_unbind_scheduler(svc);
  73.   if (svc->inc) {
  74.   local_bh_disable();
  75.   ip_vs_app_inc_put(svc->inc);
  76.   local_bh_enable();
  77.   }
  78.   kfree(svc);
  79.   }
  80.   ip_vs_scheduler_put(sched);
  81.  
  82.   out_mod_dec:
  83.   /* decrease the module use count */
  84.   ip_vs_use_count_dec();
  85.  
  86.   return ret; //返回错误号
  87.  }

(四)添加真实服务器(rs)

然后添加几个真实服务器(rs)到内核空间.内容是往tcp协议,地址为192.168.1.110:80的虚拟服务器中添加3个真实服务器,分别是10.1.1.2:80,10.1.1.3:80,10.1.1.4:80,都是使用NAT方式

ipvsadm -a -t 192.168.1.110:80 -r 10.1.1.2:80 -m
ipvsadm -a -t 192.168.1.110:80 -r 10.1.1.3:80 -m
ipvsadm -a -t 192.168.1.110:80 -r 10.1.1.4:80 -m

以下代码会在内核空间执行,将真实服务器信息添加到ip_vs_rtable[]和svc->destinations中,然后执行svc->scheduler->update_service(svc)

net/ipv4/ipvs/ip_vs_ctl.c

  1.  /*
  2.   * Add a destination into an existing service
  3.   */
  4.  static int
  5.  ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest)
  6.  {
  7.   struct ip_vs_dest *dest;
  8.   __be32 daddr = udest->addr;
  9.   __be16 dport = udest->port;
  10.   int ret;
  11.  
  12.   EnterFunction(2);
  13.  
  14.   if (udest->weight < 0) { //权重
  15.   IP_VS_ERR("ip_vs_add_dest(): server weight less than zero\n");
  16.   return -ERANGE;
  17.   }
  18.  
  19.   if (udest->l_threshold > udest->u_threshold) {
  20.   IP_VS_ERR("ip_vs_add_dest(): lower threshold is higher than "
  21.   "upper threshold\n");
  22.   return -ERANGE;
  23.   }
  24.  
  25.   /*
  26.   * Check if the dest already exists in the list
  27.   */
  28.   dest = ip_vs_lookup_dest(svc, daddr, dport); //该svc上是否已存在相同的rs
  29.   if (dest != NULL) {
  30.   IP_VS_DBG(1, "ip_vs_add_dest(): dest already exists\n");
  31.   return -EEXIST;
  32.   }
  33.  
  34.   /*
  35.   * Check if the dest already exists in the trash and
  36.   * is from the same service
  37.   */
  38.   dest = ip_vs_trash_get_dest(svc, daddr, dport); //判断该rs是否在ip_vs_dest_trash[]里(原来添加过,后来被删除了,就会被放入ip_vs_dest_trash中)
  39.   if (dest != NULL) { //如果是,则将rs从ip_vs_dest_trash移动到ip_vs_rtable
  40.   IP_VS_DBG(3, "Get destination %u.%u.%u.%u:%u from trash, "
  41.   "dest->refcnt=%d, service %u/%u.%u.%u.%u:%u\n",
  42.   NIPQUAD(daddr), ntohs(dport),
  43.   atomic_read(&dest->refcnt),
  44.   dest->vfwmark,
  45.   NIPQUAD(dest->vaddr),
  46.   ntohs(dest->vport));
  47.   __ip_vs_update_dest(svc, dest, udest);
  48.  
  49.   /*
  50.   * Get the destination from the trash
  51.   */
  52.   list_del(&dest->n_list); //将dest从ip_vs_dest_trash里删除
  53.  
  54.   ip_vs_new_estimator(&dest->stats);
  55.  
  56.   write_lock_bh(&__ip_vs_svc_lock);
  57.  
  58.   /*
  59.   * Wait until all other svc users go away.
  60.   */
  61.   IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > 1);
  62.  
  63.   list_add(&dest->n_list, &svc->destinations); //将dest.nlist加入到svc->destinations链表中
  64.   svc->num_dests++;
  65.  
  66.   /* call the update_service function of its scheduler */
  67.   svc->scheduler->update_service(svc); //运行svc的scheduler->update_service()函数,对于rr调度来说,就是ip_vs_rr_update_svc()
  68.  
  69.   write_unlock_bh(&__ip_vs_svc_lock);
  70.   return 0;
  71.   }
  72.  
  73.   /*
  74.   * Allocate and initialize the dest structure
  75.   */
  76.   ret = ip_vs_new_dest(svc, udest, &dest); //将rs加入ip_vs_rtable中
  77.   if (ret) {
  78.   return ret;
  79.   }
  80.  
  81.   /*
  82.   * Add the dest entry into the list
  83.   */
  84.   atomic_inc(&dest->refcnt);
  85.  
  86.   write_lock_bh(&__ip_vs_svc_lock);
  87.  
  88.   /*
  89.   * Wait until all other svc users go away.
  90.   */
  91.   IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > 1);
  92.  
  93.   list_add(&dest->n_list, &svc->destinations); //将dest->n_list加入到svc->destinations链表中
  94.   svc->num_dests++;
  95.  
  96.   /* call the update_service function of its scheduler */
  97.   svc->scheduler->update_service(svc); //运行svc的scheduler->update_service()函数,对于rr调度来说,就是ip_vs_rr_update_svc()
  98.  
  99.   write_unlock_bh(&__ip_vs_svc_lock);
  100.  
  101.   LeaveFunction(2);
  102.  
  103.   return 0;
  104.  }

(五)当虚拟服务器被访问

<5.1> rs的选择

要做的事情就这么些了,现在就等数据经过相应的规则链了.主要是input的时候,回来的数据只有nat方式下才需要修改,而且比较简单,所以数据返回的情况就不分析了.在我们的例子中,当client访问vs,skb经过INPUT时ip_vs_in会被调用,在ip_vs_in中对skb的关键代码为:

net/ipv4/ipvs/ip_vs_core.c

  1.   if (unlikely(!cp)) { //如果在ip_vs_conn_tab中找不到该连接(也就是该连接是第一次访问vs的话)
  2.   int v;
  3.  
  4.   if (!pp->conn_schedule(skb, pp, &v, &cp)) //利用该协议定义的conn_schedule函数为skb选择合适的rs,并根据skb,pp生成一个新的cp.并将cp添加到ip_vs_conn_tab中.rs的选择请查看相应协议的conn_schedule函数,比如tcp_conn_schedule()
  5.   return v; //添加失败时,返回错误码
  6.   }

对于tcp协议来说,pp->conn_schedule()调用的就是tcp_conn_schedule().

net/ipv4/ipvs/ip_vs_proto_tcp.c

  1.  static int
  2.  tcp_conn_schedule(struct sk_buff *skb,
  3.   struct ip_vs_protocol *pp,
  4.   int *verdict, struct ip_vs_conn **cpp)
  5.  {
  6.   struct ip_vs_service *svc;
  7.   struct tcphdr _tcph, *th;
  8.  
  9.   th = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_tcph), &_tcph); //从skb中得到tcp层头部其实位置,如果头部信息有部分不在当前内存页,则将头部的所有信息先copy到_tcph缓冲区,然后将th指向该缓存区,以保证数据的完整性
  10.   if (th == NULL) {
  11.   *verdict = NF_DROP;
  12.   return 0;
  13.   }
  14.  
  15.   if (th->syn && //如果th是同步包(syn),请求建立连接
  16.   (svc = ip_vs_service_get(skb->mark, ip_hdr(skb)->protocol, //而且需要访问的虚拟服务存在的情况下
  17.   ip_hdr(skb)->daddr, th->dest))) {
  18.   if (ip_vs_todrop()) { //先判断本机是否已经超载,用来防止DoS攻击
  19.   /*
  20.   * It seems that we are very loaded.
  21.   * We have to drop this packet :(
  22.   */
  23.   ip_vs_service_put(svc); //如果是,则释放svc的计数,返回0,将verdict设为丢弃
  24.   *verdict = NF_DROP;
  25.   return 0;
  26.   }
  27.  
  28.   /*
  29.   * Let the virtual server select a real server for the
  30.   * incoming connection, and create a connection entry.
  31.   */
  32.   *cpp = ip_vs_schedule(svc, skb); //此函数将(一)执行svc->scheduler->schedule(),选出dest(真实服务器);(二)kmem_cache_zalloc一个cp;(三)将skb,svc,dest的信息写入到cp中,其中包括将I.cp的目标地址:端口设置为rs的地址:端口;II.设置根据dest.flags设置cp->packet_xmit;(五)再将cp加入到连接列表ip_vs_conn_tab中;将cp返回给cpp
  33.   if (!*cpp) {
  34.   *verdict = ip_vs_leave(svc, skb, pp);
  35.   return 0;
  36.   }
  37.   ip_vs_service_put(svc); //释放svc的计数器
  38.   }
  39.   return 1;
  40.  }

对于我们的例子来说,svc->scheduler->schedule()将会调用ip_vs_rr_schedule()来进行rs的选择.具体的调度方法,请参考ipvs的调度

<5.2> skb的处理

rs选择好以后,只是修改了cp(kmem_cache_zalloc出来的)的目标地址并将cp加入到了ip_vs_conn_tab列表中,但是并没有修改skb.接下来还要将skb进行处理,使得skb能被转发到rs上去.在ip_vs_in函数中相关代码为

net/ipv4/ipvs/ip_vs_core.c

  1.   if (cp->packet_xmit) //调用cp的packet_xmit()将数据传送出去,函数是在建立cp的时候,由ip_vs_bind_xmit(cp),根据dest->flags(真实服务器的标记)来决定的,有5种方法ip_vs_nat_xmit,ip_vs_tunnel_xmit,ip_vs_dr_xmit,ip_vs_null_xmit,ip_vs_bypass_xmit
  2.   ret = cp->packet_xmit(skb, cp, pp);
  3.   /* do not touch skb anymore */

5.2.1> ip_vs_nat_xmit

例子中3个rs都是nat方式的,所以毫无悬念的,会调用ip_vs_nat_xmit()进行处理了,一共有nat,tunnel,dr3种方式

net/ipv4/ipvs/ip_vs_xmit.c

  1.  /*
  2.   * NAT transmitter (only for outside-to-inside nat forwarding)
  3.   * Not used for related ICMP
  4.   */
  5.  int
  6.  ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
  7.   struct ip_vs_protocol *pp)
  8.  {
  9.   struct rtable *rt; /* Route to the other host */
  10.   int mtu;
  11.   struct iphdr *iph = ip_hdr(skb);
  12.  
  13.   EnterFunction(10);
  14.  
  15.   /* check if it is a connection of no-client-port */
  16.   if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) { //如果cp中没有客户端端口信息,则从skb中得到cport,并写入到cp中
  17.   __be16 _pt, *p;
  18.   p = skb_header_pointer(skb, iph->ihl*4, sizeof(_pt), &_pt);
  19.   if (p == NULL)
  20.   goto tx_error;
  21.   ip_vs_conn_fill_cport(cp, *p);
  22.   IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p));
  23.   }
  24.  
  25.   if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos)))) //根据cp的目标地址(rip:rport)选择下一跳的路由(发给同网段的哪个mac地址),tos用来指定数据的参数,比如优先级,传输率等
  26.   goto tx_error_icmp;
  27.  
  28.   /* MTU checking */
  29.   mtu = dst_mtu(&rt->u.dst); //查找网卡的mtu值,如果skb->len大于mtu,而且skb中网络层的标记中表明不能分片的话,发出icmp包(目的地址不可达,frag_needed,mtu)
  30.   if ((skb->len > mtu) && (iph->frag_off & htons(IP_DF))) {
  31.   ip_rt_put(rt);
  32.   icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
  33.   IP_VS_DBG_RL_PKT(0, pp, skb, 0, "ip_vs_nat_xmit(): frag needed for");
  34.   goto tx_error;
  35.   }
  36.  
  37.   /* copy-on-write the packet before mangling it */
  38.   if (!skb_make_writable(skb, sizeof(struct iphdr))) //内核2.6以后,skb可能存在内存分页,如果skb的头部是分页存放的,不能直接操作,需要将多个分页合并到一个连续的内存空间里(线性化处理).方法就是copy到一个新的地址上,然后将原来的分页空间释放掉.这里看到,也不是处理全部skb,只用处理sizeof(struct iphdr)就可以了
  39.   goto tx_error_put;
  40.  
  41.   if (skb_cow(skb, rt->u.dst.dev->hard_header_len)) // 扩充skb头部空间以容纳硬件MAC头数据
  42.   goto tx_error_put;
  43.  
  44.   /* drop old route */
  45.   dst_release(skb->dst); //释放skb当前的路由cache
  46.   skb->dst = &rt->u.dst; //更新路由信息,
  47.  
  48.   /* mangle the packet */
  49.   if (pp->dnat_handler && !pp->dnat_handler(skb, pp, cp)) //修改目的端口为真实目的服务器端口,并重新计算skb的校验和(如果有端口的话,比如tcp/udp协议)
  50.  
  51.   goto tx_error;
  52.   ip_hdr(skb)->daddr = cp->daddr; //修改目的地址为真实目的服务器地址,并重新计算skb的校验和
  53.   ip_send_check(ip_hdr(skb));
  54.  
  55.   IP_VS_DBG_PKT(10, pp, skb, 0, "After DNAT");
  56.  
  57.   /* FIXME: when application helper enlarges the packet and the length
  58.   is larger than the MTU of outgoing device, there will be still
  59.   MTU problem. */
  60.  
  61.   /* Another hack: avoid icmp_send in ip_fragment */
  62.   skb->local_df = 1;
  63.  
  64.   IP_VS_XMIT(skb, rt); //发送skb到output
  65.  
  66.   LeaveFunction(10);
  67.   return NF_STOLEN;
  68.  
  69.   tx_error_icmp:
  70.   dst_link_failure(skb);
  71.   tx_error:
  72.   LeaveFunction(10);
  73.   kfree_skb(skb);
  74.   return NF_STOLEN;
  75.   tx_error_put:
  76.   ip_rt_put(rt);
  77.   goto tx_error;
  78.  }

这段代码修改了skb的目的地址,目的端口.好像没有直接对链路层的数据进行修改?个人感觉应该是这个样子的,因为skb控制的数据报在被网络设备发出送之前,链路层的信息会被重新整理.比如源/目的mac地址由skb->dst来确定.也就是说这个数据报文被发往同网段的哪一台主机,由skb->dst决定

5.2.2> ip_vs_tunnel_xmit

net/ipv4/ipvs/ip_vs_xmit.c

  1.  /*
  2.   * IP Tunneling transmitter
  3.   *
  4.   * This function encapsulates the packet in a new IP packet, its
  5.   * destination will be set to cp->daddr. Most code of this function
  6.   * is taken from ipip.c.
  7.   *
  8.   * It is used in VS/TUN cluster. The load balancer selects a real
  9.   * server from a cluster based on a scheduling algorithm,
  10.   * encapsulates the request packet and forwards it to the selected
  11.   * server. For example, all real servers are configured with
  12.   * "ifconfig tunl0 <Virtual IP Address> up". When the server receives
  13.   * the encapsulated packet, it will decapsulate the packet, processe
  14.   * the request and return the response packets directly to the client
  15.   * without passing the load balancer. This can greatly increase the
  16.   * scalability of virtual server.
  17.   *
  18.   * Used for ANY protocol
  19.   */
  20.  int
  21.  ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
  22.   struct ip_vs_protocol *pp)
  23.  {
  24.   struct rtable *rt; /* Route to the other host */
  25.   struct net_device *tdev; /* Device to other host */
  26.   struct iphdr *old_iph = ip_hdr(skb);
  27.   u8 tos = old_iph->tos;
  28.   __be16 df = old_iph->frag_off;
  29.   sk_buff_data_t old_transport_header = skb->transport_header;
  30.   struct iphdr *iph; /* Our new IP header */
  31.   unsigned int max_headroom; /* The extra header space needed */
  32.   int mtu;
  33.  
  34.   EnterFunction(10); //debug
  35.  
  36.   if (skb->protocol != htons(ETH_P_IP)) { //只处理太网的ip协议,其他不管(ATM,ARP,IPX...)
  37.   IP_VS_DBG_RL("ip_vs_tunnel_xmit(): protocol error, "
  38.   "ETH_P_IP: %d, skb protocol: %d\n",
  39.   htons(ETH_P_IP), skb->protocol);
  40.   goto tx_error;
  41.   }
  42.  
  43.   if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(tos)))) //根据cp的目标地址(rip:rport)选择下一跳的路由(发给同网段的哪个mac地址),tos用来指定数据的参数,比如优先级,传输率等
  44.   goto tx_error_icmp;
  45.  
  46.   tdev = rt->u.dst.dev; //出口设备
  47.  
  48.   mtu = dst_mtu(&rt->u.dst) - sizeof(struct iphdr); //由于需要在原有数据报上加一个新的网络层头部结构,所以网卡能传输的最大mut要减少一个iphdr大小
  49.   if (mtu < 68) { //mtu过小的话,释放rt内存空间,并报错
  50.   ip_rt_put(rt);
  51.   IP_VS_DBG_RL("ip_vs_tunnel_xmit(): mtu less than 68\n");
  52.   goto tx_error;
  53.   }
  54.   if (skb->dst)
  55.   skb->dst->ops->update_pmtu(skb->dst, mtu); //**如果skb->dst不为空,则将skb->dst的mtu值修改为新的值(减少了一个iphdr长度),这个mtu会影响到数据分片的结果
  56.  
  57.   df |= (old_iph->frag_off & htons(IP_DF));
  58.  
  59.   if ((old_iph->frag_off & htons(IP_DF)) //如果数据报的传输需要分片,而skb的标记位显示该数据不能分片,则释放rt空间后,报错
  60.   && mtu < ntohs(old_iph->tot_len)) {
  61.   icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
  62.   ip_rt_put(rt);
  63.   IP_VS_DBG_RL("ip_vs_tunnel_xmit(): frag needed\n");
  64.   goto tx_error;
  65.   }
  66.  
  67.   /*
  68.   * Okay, now see if we can stuff it in the buffer as-is.
  69.   */
  70.   max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct iphdr); //得到能够使用的最大网络层头部空间大小(tdev的加上刚从mtu里挖过来的iphdr
  71.  
  72.   if (skb_headroom(skb) < max_headroom //如果skb的空间不够,则...;否则clone一个skb,再...;如果clone失败,那就shared(skb),再... <这里真麻烦啊>
  73.   || skb_cloned(skb) || skb_shared(skb)) {
  74.   struct sk_buff *new_skb =
  75.   skb_realloc_headroom(skb, max_headroom); //将skb的头部信息复制到新的更大的new_skb内存空间上去
  76.   if (!new_skb) {
  77.   ip_rt_put(rt);
  78.   kfree_skb(skb);
  79.   IP_VS_ERR_RL("ip_vs_tunnel_xmit(): no memory\n");
  80.   return NF_STOLEN;
  81.   }
  82.   kfree_skb(skb); //释放掉原来小的skb指向的内存
  83.   skb = new_skb; //将新的new_skb交给skb
  84.   old_iph = ip_hdr(skb); //用old_iph指向新的skb的网络层头部
  85.   }
  86.  
  87.   skb->transport_header = old_transport_header;
  88.  
  89.   /* fix old IP header checksum */
  90.   ip_send_check(old_iph); //重新old_iph计算网络层校验值,
  91.  
  92.   skb_push(skb, sizeof(struct iphdr)); //在skb的头部skb->data之前加入sizeof(struct iphdr)的空间,也就是将skb的头部加了一个iphdr的空间,使得之前的old_hdr被封装了.
  93.   skb_reset_network_header(skb); //由于skb空间改变,整理下skb的指针
  94.   memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); //用0初始化(&(IPCB(skb)->opt)之后sizeof(IPCB(skb)->opt)大小的内存空间
  95.  
  96.   /* drop old route */
  97.   dst_release(skb->dst); //释放skb->dst
  98.   skb->dst = &rt->u.dst; //将skb->dst指向新的路由信息
  99.  
  100.   /*
  101.   * Push down and install the IPIP header.
  102.   */
  103.   iph = ip_hdr(skb); //对添加了新网络层头部的skb取iph(刚分配的内存空间),然后对iph进行赋值,产生一个新的ip头
  104.   iph->version = 4;
  105.   iph->ihl = sizeof(struct iphdr)>>2;
  106.   iph->frag_off = df;
  107.   iph->protocol = IPPROTO_IPIP;
  108.   iph->tos = tos;
  109.   iph->daddr = rt->rt_dst;
  110.   iph->saddr = rt->rt_src;
  111.   iph->ttl = old_iph->ttl;
  112.   ip_select_ident(iph, &rt->u.dst, NULL);
  113.  
  114.   /* Another hack: avoid icmp_send in ip_fragment */
  115.   skb->local_df = 1;
  116.  
  117.   ip_local_out(skb); //将skb发往output
  118.  
  119.   LeaveFunction(10);
  120.  
  121.   return NF_STOLEN;
  122.  
  123.   tx_error_icmp:
  124.   dst_link_failure(skb);
  125.   tx_error:
  126.   kfree_skb(skb);
  127.   LeaveFunction(10);
  128.   return NF_STOLEN;
  129.  }

5.2.3> ip_vs_dr_xmit

net/ipv4/ipvs/ip_vs_xmit.c

  1.  /*
  2.   * Direct Routing transmitter
  3.   * Used for ANY protocol
  4.   */
  5.  int
  6.  ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
  7.   struct ip_vs_protocol *pp)
  8.  {
  9.   struct rtable *rt; /* Route to the other host */
  10.   struct iphdr *iph = ip_hdr(skb);
  11.   int mtu;
  12.  
  13.   EnterFunction(10);
  14.  
  15.   if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos)))) //根据cp的目标地址(rip:rport)选择下一跳的路由(发给同网段的哪个mac地址),tos用来指定数据的参数,比如优先级,传输率等
  16.   goto tx_error_icmp;
  17.  
  18.   /* MTU checking */
  19.   mtu = dst_mtu(&rt->u.dst); //查找网卡的mtu值,如果skb->len大于mtu,而且skb中网络层的标记中表明不能分片的话,发出icmp包(目的地址不可达,frag_needed,mtu)
  20.   if ((iph->frag_off & htons(IP_DF)) && skb->len > mtu) {
  21.   icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
  22.   ip_rt_put(rt);
  23.   IP_VS_DBG_RL("ip_vs_dr_xmit(): frag needed\n");
  24.   goto tx_error;
  25.   }
  26.  
  27.   /*
  28.   * Call ip_send_check because we are not sure it is called
  29.   * after ip_defrag. Is copy-on-write needed?
  30.   */
  31.   if (unlikely((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)) { //检查skb是否正在被其他模块使用
  32.   ip_rt_put(rt);
  33.   return NF_STOLEN;
  34.   }
  35.   ip_send_check(ip_hdr(skb)); //计算网络层头部校验值
  36.  
  37.   /* drop old route */
  38.   dst_release(skb->dst); //释放skb原有路由
  39.   skb->dst = &rt->u.dst; //将skb路由指向刚生成的dst(rt->u.dst),这样下一跳的mac地址就修改为rs的mac了
  40.  
  41.   /* Another hack: avoid icmp_send in ip_fragment */
  42.   skb->local_df = 1;
  43.  
  44.   IP_VS_XMIT(skb, rt); //发送skb到OUTPUT,用(rt)->u.dst.dev作为出口网络设备
  45.  
  46.   LeaveFunction(10);
  47.   return NF_STOLEN;
  48.  
  49.   tx_error_icmp:
  50.   dst_link_failure(skb);
  51.   tx_error:
  52.   kfree_skb(skb);
  53.   LeaveFunction(10);
  54.   return NF_STOLEN;
  55.  }

这里有点特别的是skb中网络层的目的地址(vip)跟skb->dst的目的地址(rip)不一致.数据报的下一跳的mac地址由skb->dst来决定. 当rs收到这个mac地址属于自己,ip地址不属于自己的数据报时,如果不做处理的话,这个数据报将会被丢弃.所以还应该在rs上加上类似这样的命令,告诉rs将这样的数据报文接受下来

iptables -t nat -A PREROUTING -p tcp -d VIP --dport 80 -j REDIRECT
 
Done in 0.404438018799 seconds