引
生产环境的问题,按出现频率,大体可以分为两类:
- 高频问题
- 低频问题
- 周期性低频问题
- 无周期随机发生问题
- 单例发生
- 多例发生
一般,遇到 无周期随机发生问题 且 单例发生 的情况,是最难定位问题根源的。
前不久,我就遇到应用对外发起连接 无周期随机发生 且 单例发生 的情况 。你不可能在大流量的情况下,做 tcpdump 去分析连接情况的。而能透视内核状态的 eBPF/BPF 可能是个更合适的选择。
eBPF 可以在以下事件发生时,抓取到 连接信息:
- 应用发起
connect(),连接进入SYN_SENT状态时 - 在发送
SYN一定时间后,无收到响应而重传SYN时 - connect timeout 后,应用
close()socket。连接从SYN_SENT状态变为CLOSE状态时
这里说的连接信息,包括:
- 连接 4 元组: source_IP: local_port <-> destination_IP:dest_port
- 事发时的连接状态
而这些 事件 + 连接信息 可以为进一步研判问题的方向,提供一个客观的事实证据。
应用场景例子
之前,我了一篇:特定条件下 Istio 发生 half-close 连接泄漏与出站连接失败 。其中模拟了一个场景:
应用程序出站outbound连接超时因为应用程序选择了一个与-15001outboundlistener-上的现有套接字冲突的临时端口
App invoke syscall
connect(sockfd, peer_addr), kernel allocation aephemeral port(44410 in this case) , bind the new socket to thatephemeral portand sentSYNpacket to peer.
SYNpacket reach conntrack and it create atrack entryinconntrack table:$ conntrack -L tcp 6 108 SYN_SENT src=172.29.73.7 dst=172.21.206.198 sport=44410 dport=7777 src=127.0.0.1 dst=172.29.73.7 sport=15001 dport=44410
SYNpacket DNAT to127.0.0.1:15001
SYNpacket reach the already existingFIN-WAIT-2 127.0.0.1:15001 172.29.73.7:44410socket, then sidecar reply aTCP Challenge ACK(TCP seq-no is from the oldFIN-WAIT-2) packet to App
大体意思是,如果应用在发起出站连接时,内核自动选择的临时本地端口如果与 “Envoy 在 15001 上的 FIN-WAIT-2 状态的 socket 的对端端口” 相同时,就会有问题。为了提高问题发生机率,我当时用 nc -p $临时本地端口号 的方法指定 临时本地端口号,而不是让内核自动选择, 模拟端口号冲突的。
但实际应用是让内核自动选择临时本地端口号 的。那么问题来了,如果要证明在问题发生时, 内核自动选择的临时本地端口号 就是我设想的情况?
实时跟踪 TCP 连接失败与重试
我们知道,TCP 连接超时的处理过程,大概会有发下几个事件(函数)点:
- 应用调用
connect()。连接进入SYN_SENT状态- 对应内核的
tcp_connect()
- 对应内核的
- 在发送
SYN一定时间后,因无收到响应而间歇重传SYN- 对应内核的
tcp_retransmit_skb()
- 对应内核的
- connect timeout 后,应用
close()socket。连接从SYN_SENT状态变为CLOSE状态时- 对应内核的
inet_sk_state_store()
- 对应内核的
Talk is cheap, show you the code :
|
|
运行 bpftrace ,执行上面脚本:
|
|
向一个不会回复的 ip 发送连接请求。且指定 10s 超时时长:
|
|
这时,bpftrace 会出现跟踪信息:
$ bpftrace trace-poll-timeout.bt
Attaching 5 probes...
Tracing IP TCP connect latency and SYN retry with stacks. Ctrl-C to end.
21:47:55 0 192.168.1.14:42636 1.1.1.146:22 SYN_SENT
21:47:57 0 192.168.1.14:42636 1.1.1.146:22 SYN_SENT
21:48:01 0 192.168.1.14:42636 1.1.1.146:22 SYN_SENT
21:48:04 TCP_SYN_SENT to CLOSE: 23023 nc 10009ms(尝试连接用时) 4 192.168.1.14:42636(内核自动分配的本地临时端口号) --> 1.1.1.146:22
kstack:
inet_sk_state_store+1
__tcp_close+678
tcp_close+37
inet_release+69
__sock_release+63
sock_close+21
__fput+156
____fput+14
task_work_run+106
exit_to_user_mode_loop+343
exit_to_user_mode_prepare+160
syscall_exit_to_user_mode+39
do_syscall_64+105
entry_SYSCALL_64_after_hwframe+97
ustack:
0x7f1b1c6f8117
0x600000001
^C
就这样,应用对外发起连接 无周期随机发生 且 单例发生 的连接问题,就可以找到证据了。
谢谢阅读!