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模块,重新加载至操作系统。
发表评论