閱讀1003 返回首頁    go 技術社區[雲棲]


找出重傳較高的TCP連接

原創文章:來自找出重傳較高的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內核代碼中的位置如下:
net\ipv4\tcp_output.c

_

int __tcp_retransmit_skb(struct sock sk, struct sk_buff skb) 函數中也有一處:

2

編寫打印出重傳報文四元組信息簡要的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連接隊列是否有問題等。

最後更新:2017-11-04 23:03:35

  上一篇:go  JS中那些讓人頭昏眼花的彎子
  下一篇:go  HBase二級索引