IPVS源代码分析----发送函数的实现

发送方法是在钩子函数里面,处理完包之后,调用的发包函数。根据采用的不同的策略:nat\tunnel\dr,有三种不同的发送函数。另外bypass不属于LVS负载均衡处理的范畴。
NAT部分只是修改目的地址,不修改端口号。
tunnel部分是添加一个新的IP头部
dr目的地址也不修改。
发送时,都要先查找路由。

NAT发送只发送请求方向的数据,因此是进行目的NAT。dnat_handler是在ip_vs_nat_xmit中调用的。snat_handler 是在handler_response中调用的。在这两个handler中,会调用到app层的packet in 和 packet out 函数。
int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
      struct rtable *rt;              /* Route to the other host */
      int mtu;
      struct iphdr *iph = skb->nh.iph;
      //如果连接标志了客户端端口为0,将当前skb中的端口填给连接
      if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) {
            __u16 _pt, *p;
            p = skb_header_pointer(skb, iph->ihl*4, sizeof(_pt), &_pt);
            if (p == NULL)
                  goto tx_error;

            ip_vs_conn_fill_cport(cp, *p);// *p是源端口
            IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p));
      }
      //查找路由,找不到的话发ICMP出错包
      if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
            goto tx_error_icmp;

      //检查路由发出网卡的MTU,如果包长超过MTU又有DF标志,发送ICMP错误信息,而不进行分片操作
      mtu = dst_mtu(&rt->u.dst);
      if ((skb->len > mtu) && (iph->frag_off&__constant_htons(IP_DF))) {
            ip_rt_put(rt);
            icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
            IP_VS_DBG_RL_PKT(0, pp, skb, 0, "ip_vs_nat_xmit(): frag needed for");
            goto tx_error;
      }
      //让skb包的IP头部分是可写的
      if (!ip_vs_make_skb_writable(&skb, sizeof(struct iphdr)))
            goto tx_error_put;

      //扩充skb头部空间以容纳硬件MAC头数据
      if (skb_cow(skb, rt->u.dst.dev->hard_header_len))
            goto tx_error_put;

//释放skb当前的路由cache
      dst_release(skb->dst);
      skb->dst = &rt->u.dst;

      //对上层协议(TCP/UDP...)进行目的NAT,因为要发送给实际的目的服务器,看上面协议实现
      if (pp->dnat_handler && !pp->dnat_handler(&skb, pp, cp))
            goto tx_error;

//修改目的地址为真实目的服务器地址
      skb->nh.iph->daddr = cp->daddr;
      ip_send_check(skb->nh.iph);//计算IP头校验和

      /* Another hack: avoid icmp_send in ip_fragment */
      skb->local_df = 1;
      //发送数据包,实际还是HOOK住netfilter的OUTPUT点,受OUTPUT规则限制
      IP_VS_XMIT(skb, rt);
      return NF_STOLEN;
tx_error_icmp:
      dst_link_failure(skb);
tx_error:
      kfree_skb(skb);
      return NF_STOLEN;
tx_error_put:
      ip_rt_put(rt);
      goto tx_error;
}
TUNNEL发送是把原来的IP部分再加在一个IPIP协议头后发出去,新头的目的IP是真实目的服务器,源IP是真实客户端IP,
该包是可以路由的,服务器的回应包将直接路由回去而不经过IPVS.
int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
      struct rtable *rt;                      /* Route to the other host */
      struct net_device *tdev;                /* Device to other host */
      struct iphdr  *old_iph = skb->nh.iph;
      u8     tos = old_iph->tos;
      __be16 df = old_iph->frag_off;
      struct iphdr  *iph;                     /* Our new IP header */
      int    max_headroom;                    /* The extra header space needed */
      int    mtu;

      //只包装IP包,其他协议如ARP,IPX等不管
      if (skb->protocol != __constant_htons(ETH_P_IP)) {
            IP_VS_DBG_RL("ip_vs_tunnel_xmit(): protocol error, ETH_P_IP: %d, skb protocol: %d\n", __constant_htons(ETH_P_IP), skb->protocol);
            goto tx_error;
      }
      //根据连接信息找外出的路由cache
      if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(tos))))
            goto tx_error_icmp;
      //数据包发出网卡
      tdev = rt->u.dst.dev;

      //检查路径的MTU
      mtu = dst_mtu(&rt->u.dst) - sizeof(struct iphdr);
      if (mtu < 68) {
            ip_rt_put(rt);
            IP_VS_DBG_RL("ip_vs_tunnel_xmit(): mtu less than 68\n");
            goto tx_error;
      }
      //更新路由的MTU
      if (skb->dst)
            skb->dst->ops->update_pmtu(skb->dst, mtu);
      
      df |= (old_iph->frag_off & __constant_htons(IP_DF));
      //检查don't fragement标志
      if ((old_iph->frag_off & __constant_htons(IP_DF)) && mtu < ntohs(old_iph->tot_len)) {
            icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
            ip_rt_put(rt);
            IP_VS_DBG_RL("ip_vs_tunnel_xmit(): frag needed\n");
            goto tx_error;
      }
      //#define LL_RESERVED_SPACE(dev) (((dev)->hard_header_len&~(HH_DATA_MOD - 1)) + HH_DATA_MOD)
      //计算需要添加的IP头的最大长度
      max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct iphdr);

      if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
            //重新分配一个skb包,该skb头部足够大可容纳外部IP头空间
            //分配失败则不发送该包了
            struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
            if (!new_skb) {
                  ip_rt_put(rt);
                  kfree_skb(skb);
                  IP_VS_ERR_RL("ip_vs_tunnel_xmit(): no memory\n");
                  return NF_STOLEN;
            }
            kfree_skb(skb);//将原来的skb释放掉
            skb = new_skb;//将skb指向新包
            old_iph = skb->nh.iph;//更新ip头指针
      }
      //skb->h是传输层头,现在要新加个IP头,原来的IP头就升级为传输层头
      skb->h.raw = (void *) old_iph;

      //计算老IP头的校验和
      ip_send_check(old_iph);

      //skb的data指针前移出IP头长度作为新IP头的起点
      skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
      memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));

      //更新路由cache
      dst_release(skb->dst);
      skb->dst = &rt->u.dst;

      //填写新IP头部信息
      iph                     =       skb->nh.iph;
      iph->version            =       4;
      iph->ihl                =       sizeof(struct iphdr)>>2;
      iph->frag_off           =       df;
      iph->protocol           =       IPPROTO_IPIP;//协议设置为IPIP, 值为4
      iph->tos                =       tos;
      iph->daddr              =       rt->rt_dst;
      iph->saddr              =       rt->rt_src;
      iph->ttl                =       old_iph->ttl;
      iph->tot_len            =       htons(skb->len);
      ip_select_ident(iph, &rt->u.dst, NULL); //设置IP头中的ID值
      ip_send_check(iph);//计算IP头校验和

      /* Another hack: avoid icmp_send in ip_fragment */
      skb->local_df = 1;
      //发送新的skb包
      IP_VS_XMIT(skb, rt);
      return NF_STOLEN;
tx_error_icmp:
      dst_link_failure(skb);
tx_error:
      kfree_skb(skb);
      return NF_STOLEN;
}
DR发送是将原来的skb包中的目的MAC地址修改为目的服务器的MAC地址后直接发出,因此是不能路由的,IPVS均衡设备和目的服务器物理上必须在同一个二层子网。
在DR模式下,IPVS和服务器都配置了相同的对外服务的VIP,服务器也配了自己的真实IP,不过服务器上配VIP的网卡属性中的NOARP信息是打开的,
就是在该网卡上不响应ARP信息,但可以接收到达该VIP的数据包,这样外面请求包先是到IPVS均衡器,因为IPVS的VIP是响应ARP的,
然后根据调度找一台服务器,用服务器的真实IP来确定路由,然后直接把包发出来,这时包中所有数据都没修改,
因为目的服务器上VIP地址符合包中的目的地址,因此是可以接收该包的。
int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
      struct rtable *rt;                      /* Route to the other host */
      struct iphdr  *iph = skb->nh.iph;
      int    mtu;
      //根据连接指定的目的服务器找路由
      if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
            goto tx_error_icmp;

      //检查MTU
      mtu = dst_mtu(&rt->u.dst);
      if ((iph->frag_off&__constant_htons(IP_DF)) && skb->len > mtu) {
            icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
            ip_rt_put(rt);
            IP_VS_DBG_RL("ip_vs_dr_xmit(): frag needed\n");
            goto tx_error;
      }

      //防止skb包是共用的,还被其他地方使用
      if (unlikely((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)) {
            ip_rt_put(rt);
            return NF_STOLEN;
      }
      ip_send_check(skb->nh.iph);//重新计算IP头校验和
      dst_release(skb->dst);//释放原来的路由
      skb->dst = &rt->u.dst;

      skb->local_df = 1;
      IP_VS_XMIT(skb, rt);//直接发出了
      return NF_STOLEN;
tx_error_icmp:
      dst_link_failure(skb);
tx_error:
      kfree_skb(skb);
      return NF_STOLEN;
}
什么都没作
int ip_vs_null_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
        /* we do not touch skb and do not need pskb ptr */
        return NF_ACCEPT;
}
旁路模式,实际数据包不是给IPVS均衡器自己的,由IPVS进行转发
int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
      struct rtable *rt;                      /* Route to the other host */
      struct iphdr  *iph = skb->nh.iph;
      u8     tos = iph->tos;
      int    mtu;
      struct flowi fl = {//用当前IP包的目的地址作为查路由的key
         .oif = 0,
         .nl_u = {
                 .ip4_u = {
                         .daddr = iph->daddr,
                         .saddr = 0,
                         .tos = RT_TOS(tos), } },
      };
      //查找当前数据包的目的IP地址对应的路由,而不是IPVS连接的信息找路由
      if (ip_route_output_key(&rt, &fl)) {
            IP_VS_DBG_RL("ip_vs_bypass_xmit(): ip_route_output error, dest: %u.%u.%u.%u\n", NIPQUAD(iph->daddr));
            goto tx_error_icmp;
      }
      //MTU检查
      mtu = dst_mtu(&rt->u.dst);
      if ((skb->len > mtu) && (iph->frag_off & __constant_htons(IP_DF))) {
            ip_rt_put(rt);
            icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
            IP_VS_DBG_RL("ip_vs_bypass_xmit(): frag needed\n");
            goto tx_error;
      }
      //防止skb包是共用的,还被其他地方使用
      if (unlikely((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)) {
            ip_rt_put(rt);
            return NF_STOLEN;
      }
      //计算IP头校验和
      ip_send_check(skb->nh.iph);
      //释放老路由,更新路由
      dst_release(skb->dst);
      skb->dst = &rt->u.dst;

      /* Another hack: avoid icmp_send in ip_fragment */
      skb->local_df = 1;
      IP_VS_XMIT(skb, rt);//发送
      return NF_STOLEN;
tx_error_icmp:
      dst_link_failure(skb);
tx_error:
      kfree_skb(skb);
      return NF_STOLEN;
}

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页