原创文章:来自找出重传较高的TCP连接
这里先给出某一台主机上评估tcp重传的指标,TCP重传率定义:
TCP重传率 = TCP重传的报文数量/TCP输出的报文数量;
即tcp retransfer radio = Retrans/outSegs
在linux系统中可以通过/proc/net/snmp得到各层网络协议收发包的情况,另外一些扩展的tcp指标可以通过tcpext在/proc/net/netstat文件中读到。监控某台主机重传率的通常方法:可以每隔1秒从这两个文件中分别读到TcpRetransSegs和TcpOutSegs和上一次记录取差值后,再使用重传率计算公式。
简单的观察方法在centos 7等3.10内核中可以使用以下命令实时观察系统中每秒tcp重传报文数量:
watch -n 1 'nstat -z -t 1 | grep -e TcpExtTCPSynRetrans -e TcpRetransSegs -e TcpOutSegs -e TcpInSegs'
其中TcpExtTCPSynRetrans代表syn报文和synack报文的重传数量,TcpRetransSegs代表总的重传数量,TcpOutSegs代表总的tcp报文发出数量,TcpInSeg代表总的入报文数量通常用于计算tcp吞吐量。
备注:TcpExtTCPSynRetrans是centos 7系统(与linux内核有关)中新加入的,在2.6.32等系统内核中没有这个参数。
下边考虑一个场景:如果监控到一台主机的tcp重传率较高,如达到20%以上,且在这台主机系统上开了成百上千的tcp listen监听端口,同时间tcp连接并发高达数十万,此时如何得知哪些监听端口上重传较高? 哪些tcp连接在不断重传报文导致?可能的原因是什么?下边将解析这些问题。
方法1,如果观察到重传率较高时可以通过tcpdump或wireshark抓包,将主机中这段时间的所有tcp报文都抓下来保存为文件,再通过wireshark专家系统进行分析得到重传率较高的连接。wireshark通常过滤重传的命令是tcp.analysis.retransmission。
方法2,从内核统计重传根源处入手,使用systemtap下探点的方式拿到重传报文连接四元组信息。
如果对wireshark的统计与分析比较熟练使用方法1可以快速得到结果,但有兴趣探究内核tcp如何实现的话我选择使用方法2。下边的主要说明方法2。
找到TcpRetransSegs,TcpExtTCPSynRetrans两个参数在linux 3.10内核代码中的位置如下:
netipv4tcp_output.c
int __tcp_retransmit_skb(struct sock sk, struct sk_buff skb) 函数中也有一处:
编写打印出重传报文四元组信息简要的stap脚本如下:
#! /usr/bin/env stap
#
# fenghui8611@sina.com
#sudo stap -DSTP_NO_OVERLOAD -v -g retran_tcpseg.stp
probe begin {
print ("\n")
}
#TCPHDR_SYN=0x02
function is_retran_syn:long (skb:long)
%{
struct sk_buff *skb = (struct sk_buff *)STAP_ARG_skb;
int ret = 0;
if (((struct tcp_skb_cb *)&(skb->cb[0]))->tcp_flags & 0x02 )
ret = 1;
STAP_RETVALUE = ret;
%}
probe kernel.statement("__tcp_retransmit_skb@net/ipv4/tcp_output.c:2554")
{
printf("tcp_transmit_skb() err=%d, syn flag=%d ", $err, is_retran_syn($skb))
saddr = format_ipaddr(__ip_sock_saddr($sk), __ip_sock_family($sk))
daddr = format_ipaddr(__ip_sock_daddr($sk), __ip_sock_family($sk))
sport = __tcp_sock_sport($sk)
dport = __tcp_sock_dport($sk)
printf("%s:%d => %s:%d\n", saddr, sport, daddr, dport)
}
probe kernel.function("tcp_v4_send_synack").return
{
err = $return
if (err == 0) {
saddr = format_ipaddr(__ip_sock_saddr($sk), __ip_sock_family($sk))
daddr = format_ipaddr(__ip_sock_daddr($sk), __ip_sock_family($sk))
sport = __tcp_sock_sport($sk)
dport = __tcp_sock_dport($sk)
printf("synack retran %s:%d => %s:%d\n", saddr, sport, daddr, dport)
}
}
probe end {
print ("\n")
}
运行命令与输出信息示例:
sudo stap -DSTP_NO_OVERLOAD -v -g retran_tcpseg.stp
tcp_transmit_skb() err=0 syn flag=0 0.0.0.0:59738 => 100.116.2xx.16x:41239
tcp_transmit_skb() err=0 syn flag=0 0.0.0.0:59738 => 100.116.2xx.13x:44026
tcp_transmit_skb() err=0 syn flag=0 0.0.0.0:59738 => 100.116.2xx.13x:34901
tcp_transmit_skb() err=0 syn flag=0 0.0.0.0:59738 => 100.116.2xx.15x:11685
根据四元组信息可以深入的分析为什么这条连接有问题,是链路问题还是软件问题?比如拿到上述结果分析结论:因交换机出口处设置了带宽限制,当达到限制值时很多tcp 59738端口发出的tcp报文段被丢弃,导致此台主机重传较高;另外syn报文重传较高时可以重点分析服务器端tcp连接队列是否有问题等。