centos 7下LVS bug定位

centos 7下LVS bug定位

线上反映出现LVS 不转发的问题。已经出现过两次,问题相当诡异。

LVS的内网IP为 192.168.5.12

通过抓包命令查看LVS仍继续向该realserver转发消息。
现场LVS服务器,进行快速抓包的信息如下:

[root@Lvsout-c ipvsman]# ipvsadm -Ln | grep 91.17 -A 3
TCP  12.10.91.17:80 rr  -> 192.168.5.127:80             Tunnel  100    196843     28360       -> 192.168.5.133:80             Tunnel  100    196154     28366       -> 192.168.12.101:80            Tunnel  100    195902     28385     
TCP  12.10.91.17:443 rr  -> 192.168.5.127:443            Tunnel  100    0          87          -> 192.168.5.133:443            Tunnel  100    465        79          -> 192.168.12.101:443           Tunnel  100    464        85


[root@Lvsout-c ~]# tcpdump -nn -i any ‘ip[9] ==4’ and host 192.168.5.127  | grep “443: “

09:41:24.497785 IP 12.10.91.5 > 192.168.5.127: IP 139.217.12.238.23033 > 12.10.91.17.443: Flags [S], seq 3068484861, win 14600, options [mss 1350,sackOK,TS val 165956788 ecr 0,nop,wscale 7], length 0 (ipip-proto-4)
09:41:24.497789 IP 12.10.91.5 > 192.168.5.127: IP 139.217.12.238.23033 > 12.10.91.17.443: Flags [S], seq 3068484861, win 14600, options [mss 1350,sackOK,TS val 165956788 ecr 0,nop,wscale 7], length 0 (ipip-proto-4)

其中192.168.5.127:443不转发了,可以看到转发的活跃链接数为0.

而tcpdump能抓到数据包,说明LVS 确实在尝试转发tcp syn消息了。

通过查看珍贵的IPoverIP数据12.10.91.5 > 192.168.5.127: IP 139.217.12.238.23033 > 12.10.91.17.443

显然不应该使用12.10.91.5这个IP,而应该使用LVS的内网IP,应为192.168.5.12。
分析LVS代码逻辑后发现有问题的函数如下:

static int
__ip_vs_get_out_rt(struct sk_buff *skb, struct ip_vs_dest *dest,
                 __be32 daddr, int rt_mode, __be32 *ret_saddr)
{
       …
       if (dest) {
              dest_dst = __ip_vs_dst_check(dest); //复用dst_entry
              if (likely(dest_dst))
                     rt = (struct rtable *) dest_dst->dst_cache;
              else {
  //A步 初始化dst_entry
                     dest_dst = ip_vs_dest_dst_alloc(); 
                     spin_lock_bh(&dest->dst_lock);
                     if (!dest_dst) {
                            __ip_vs_dst_set(dest, NULL, NULL, 0);
                            spin_unlock_bh(&dest->dst_lock);
                            goto err_unreach;
                     }
  // B步: 获取路由,尤其发送消息的源IP地址
                     rt = do_output_route4(net, dest->addr.ip, rt_mode,
                                         &dest_dst->dst_saddr.ip);
                     ….略
              }
              daddr = dest->addr.ip;
              if (ret_saddr) 
  //返回路由解释的源IP,用于组装tunnel报文。 
                     *ret_saddr = dest_dst->dst_saddr.ip;
…..
}

struct ip_vs_dest_dst {
       struct dst_entry   *dst_cache;   /* destination cache entry */
       u32               dst_cookie;
       union nf_inet_addr    dst_saddr; //LVS发包的源IP
       struct rcu_head          rcu_head;
};

真正的问题是:
申请dst_etnry内存,使用了kmalloc获取的内存数据,没有经过清理。

static inline struct ip_vs_dest_dst *ip_vs_dest_dst_alloc(void){
   return kmalloc(sizeof(struct ip_vs_dest_dst), GFP_ATOMIC);
}

所以A处 返回的ip_vs_dest_dst.dst_saddr包含了垃圾数据。
B处 do_output_route4函数的逻辑是,当传入的dst_saddr.ip中的数据(垃圾数据),恰好与LVS相通时,那么该垃圾数据就作为正式的源IP地址。
这个问题在刷新LVS配置时,存在一定的概率使用错误的源IP进行转发,导致转发不正常。

问题复现:经过在测试环境反复修改LVS 配置,同时运行压测程序,问题复现。

问题解决:修改 ip_vs_dest_dst_alloc 函数,申请内存后,先对内存进行清理工作。重新编译ip_vs模块,重新加载至操作系统。

kuaikuai

老程序员一名