写点什么

ipvs localhost 为何不正常

作者:Geek_f24c45
  • 2021 年 12 月 06 日
  • 本文字数:2156 字

    阅读完需:约 7 分钟

在 k8s 上, kube-proxy 使用 ipvs 模式时,发现相同的 node port 类型的 service, 使用 localhost:nodePort , 127.0.0.1:nodePort 访问时, ipvs 模式下不通, iptables 模式下可以正常。最后我们将服务的访问 改成了 service ip + service port ,绕开了该问题,那为什么 ipvs 模式下会存在该问题那?


找了一些 issue,了解下情况,ipvs 模式确实存在该问题,不是我们的使用配置存在问题。

https://github.com/kubernetes/kubernetes/issues/96879

https://github.com/kubernetes/kubernetes/issues/67730


iptables 模式下, kube-proxy 通过设置了 net.ipv4.conf.all.route_localnet=1,允许了路由本地地址,才让 localhost:nodePort 这类 nat 规则生效。

ipvs 模式下虽然也设置了 该选项,但是实际上是没必要的。

开启内核该选项也容易引起安全问题。


ipvs 模式下 通过 ipvsadm 可以看到 127.0.0.1:nodePort 的配置已经正常建立,当访问该地址时,连接数也正常的增长,但是最终没有数据包发出, 我们来了解下代码层面是不是对这个特殊地址做了什么魔法。


nat 建立后,最后的发送代码 应该是这个函数 ip_vs_nat_xmit


之后 关注下 __ip_vs_get_out_rt , 该函数在 通过路由查找之后, 通过 crosses_local_route_boundary

做了一次关于 local 的检查, 检查了这种情况,失败告终。


/* *      NAT transmitter (only for outside-to-inside nat forwarding) *      Not used for related ICMP */intip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,	       struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh){	struct rtable *rt;		/* Route to the other host */	int local, rc, was_input;
EnterFunction(10);
......... 省略 .........
was_input = rt_is_input_route(skb_rtable(skb)); local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR, NULL, ipvsh); if (local < 0) goto tx_error; rt = skb_rtable(skb);
.............. 省略 ..............
LeaveFunction(10); return rc;
tx_error: kfree_skb(skb); LeaveFunction(10); return NF_STOLEN;}
复制代码


/* Get route to destination or remote server */static int__ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,		   struct ip_vs_dest *dest,		   __be32 daddr, int rt_mode, __be32 *ret_saddr,		   struct ip_vs_iphdr *ipvsh){	struct net *net = ipvs->net;	struct ip_vs_dest_dst *dest_dst;	struct rtable *rt;			/* Route to the other host */	int mtu;	int local, noref = 1;
......... 省略 ......... local = (rt->rt_flags & RTCF_LOCAL) ? 1 : 0; if (unlikely(crosses_local_route_boundary(skb_af, skb, rt_mode, local))) { IP_VS_DBG_RL("We are crossing local and non-local addresses" " daddr=%pI4\n", &daddr); goto err_put; }
......... 省略 .........
if (!ensure_mtu_is_adequate(ipvs, skb_af, rt_mode, ipvsh, skb, mtu)) goto err_put;
skb_dst_drop(skb); if (noref) { if (!local) skb_dst_set_noref(skb, &rt->dst); else skb_dst_set(skb, dst_clone(&rt->dst)); } else skb_dst_set(skb, &rt->dst);
return local;
err_put: if (!noref) ip_rt_put(rt); return -1;
err_unreach: dst_link_failure(skb); return -1;}
复制代码


static inline bool crosses_local_route_boundary(int skb_af, struct sk_buff *skb,						int rt_mode,						bool new_rt_is_local){	bool rt_mode_allow_local = !!(rt_mode & IP_VS_RT_MODE_LOCAL);	bool rt_mode_allow_non_local = !!(rt_mode & IP_VS_RT_MODE_NON_LOCAL);	bool rt_mode_allow_redirect = !!(rt_mode & IP_VS_RT_MODE_RDR);	bool source_is_loopback;	bool old_rt_is_local;
#ifdef CONFIG_IP_VS_IPV6 if (skb_af == AF_INET6) { int addr_type = ipv6_addr_type(&ipv6_hdr(skb)->saddr);
source_is_loopback = (!skb->dev || skb->dev->flags & IFF_LOOPBACK) && (addr_type & IPV6_ADDR_LOOPBACK); old_rt_is_local = __ip_vs_is_local_route6( (struct rt6_info *)skb_dst(skb)); } else#endif { # 我们的原地址是 127, 所有 source_is_loopback 为 true source_is_loopback = ipv4_is_loopback(ip_hdr(skb)->saddr); old_rt_is_local = skb_rtable(skb)->rt_flags & RTCF_LOCAL; } # new_rt_is_local 通过得出的路由判断而来,我们的nat的下一条地址,不是local # 所以此处是 false if (unlikely(new_rt_is_local)) { if (!rt_mode_allow_local) return true; if (!rt_mode_allow_redirect && !old_rt_is_local) return true; } else { if (!rt_mode_allow_non_local) return true; # 最后这种情况下,该函数返回 true, # 最后上面走到错误处理 if (source_is_loopback) return true; } return false;}
复制代码


用户头像

Geek_f24c45

关注

还未添加个人签名 2018.03.24 加入

还未添加个人简介

评论

发布
暂无评论
ipvs localhost 为何不正常