iptables redirect劫持跳转引起go服务故障

前言:

😅 这是一个很有趣的事情。由于流量突增临时扩充多个node部署服务,但遇到一个问题全量接口调用失败总是返回无关的返回结果。简单说在服务里本调用其他服务接口,返回的结果莫名其妙。

该文章原文地址 http://xiaorui.cc/archives/6863

由于出问题节点已经被修复,所以该问题是在虚拟机里重现的。

排查问题:

首先确认其他接口方是否收到该请求,日志没有里没有请求,考虑到是自研封装的golang web框架可能会有问题,所以调用tcpdump抓包,结果看不到请求报文。

那么可以确认请求包没有来这台服务器上,通常来说这类请求大多出现在dns解析错误引起的。

在本机dig dns解析无异常,由于请求构建起来有些麻烦,所以没有单独拿到curl下测试。但通过 lsof 和 netstat 可以看到已建立连接是正常的解析ip,但是对端确实没有收到该请求。

重点,客户端可以看到已经建立的连接,但是服务端看不到。

// xiaorui.cc

pusher  10902 root    3u  IPv4 314336       0t0       TCP 192.168.124.13:54504->123.56.223.52:80 (ESTABLISHED)

通过strace是可以看到服务请求过程中所涉及到的系统调用。

先是dns请求,当开启dns缓存服务nscd时,程序里的域名解析不是直接连接resolver.conf的nameserver地址,而是直接跟nscd socket通信,nscd作为缓存服务有个名为hosts的持久化db。当nscd无域名的缓存时会跟nameserver进行udp请求。如果无nscd服务,那么strace可以看到nameserver建连及解析过程。

总之ip的解析正确的。

// xiaori.cc

[pid 10007] connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110  <unfinished ...>
[pid 10007] sendto(3, "\2\0\0\0\r\0\0\0\6\0\0\0hosts\0", 18, MSG_NOSIGNAL, NULL, 0 <unfinished …>
[pid 10007] close(3 <unfinished ...>

再是http的数据请求,connect ip过程没问题,发送的请求体也是没问题的,但返回值有问题。

// xiaorui.cc

fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("123.56.223.52")}, 16) = -1 EINPROGRESS (Operation now in progress)
poll([{fd=3, events=POLLOUT|POLLWRNORM}], 1, 0) = 1 ([{fd=3, revents=POLLOUT|POLLWRNORM}])
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
getpeername(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("123.56.223.52")}, [16]) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(54508), sin_addr=inet_addr("192.168.124.13")}, [16]) = 0
sendto(3, "GET /6666 HTTP/1.1\r\nUser-Agent: curl/7.29.0\r\nHost: xiaorui.cc\r\nAccept: */*\r\n\r\n", 78, MSG_NOSIGNAL, NULL, 0) = 78
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
poll([{fd=3, events=POLLIN}], 1, 1000)  = 1 ([{fd=3, revents=POLLIN}])
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
recvfrom(3, "HTTP/1.1 404 Not Found\r\nServer: ...
...

大疑问?

奇怪了。。。

那么再尝试使用tcpdump来抓包。每次请求时都会跟127.0.0.1:80建连,请求体也会转到127.0.0.1:80上。这类情况很像是做了端口劫持跳转。

在iptables里发现了redirect跳转。所有output请求会转到sidecar_outbound自定义链,在sidecar自定义链中又把目标地址中80的请求转到本地的80端口上。

// xiaorui.cc

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
OUTPUT_direct  all  --  anywhere             anywhere
SIDECAR_OUTBOUND  tcp  --  anywhere             anywhere

...

Chain SIDECAR_OUTBOUND (1 references)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             123.56.223.0      tcp dpt:http redir ports 80
...

本地为啥接收劫持的http请求? sidecar呗… 现在很火的istio service mesh就是使用iptables redirect方法劫持数据到envoy里,这样减少了用户对微服务的使用成本。把服务发现、负载均衡、熔断器/限频器、追踪等等都放在sidecar里,用户只需要关注自己的业务就可以了。

那为啥这回sidecar不能正确转发了?

因为该sidecar是半成品,开发这玩意的人跑路了,然后策略依旧存在。

如何测试?

iptables劫持脚本

// xiaorui.cc

iptables -t nat -N SIDECAR_OUTBOUND
iptables -t nat -A OUTPUT -p tcp -j SIDECAR_OUTBOUND
iptables -t nat -A SIDECAR_OUTBOUND -p tcp -d 123.56.0.0 --dport 80 -j REDIRECT --to-port 80

总结:

通过strace和lsof都不好分析到问题,而tcpdump是可以的。现在想想其实通过netstat也是可以发现问题的,奈何在使用netstat时加入了pid过滤。

记得前段时间一个同事出现过域名拼写错误引起的问题,这哥们一出问题就怀疑是不是go web问题,再就是怀疑到golang语言本身,最后都怀疑到操作系统。😅 所以说要冷静,别瞎想….


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc