TCP SNMP counters netstat -s 各项参数意义

转自 roveryu.blog.chinaunix.net

 

最近在总结2.6.32与2.6.18的差异,我特别有兴趣的是网络部分,但猛然发现其实SNMP counters我也并不能准确解释它们的含义,于是就有了以下总结,还不完整,持续总结中:
 
加粗的项代表TODO,还有一些counters没有列出来。
 
难免有错误,请大家指正吧,我的codebase是linux2.6.git~
 
TCP Basic

类别名称描述
Tcp ActiveOpens tcp_connect(),发送SYN时,加1
Tcp PassiveOpens tcp_create_openreq_child(), 被动三路握手完成,加1
Tcp AttemptFails
  1. tcp_done():如果在SYN_SENT/SYN_RECV状态下结束一个连接,加1
  2. tcp_check_req():被动三路握手最后一个阶段中的输入包中如果有RST|SYN标志,加1
Tcp CurrEstab tcp_set_state(),根据ESTABLISHED是新/旧状态,分别加减一。
Tcp EstabResets tcp_set_state(),新状态为TCP_CLOSE,如果旧状态是ESTABLISHED/TCP_CLOSE_WAIT就加1
Tcp ListenOverflows tcp_v4_syn_recv_sock():三路握手最后一步完全之后,Accept queue队列超过上限时加1
Tcp ListenDrops tcp_v4_syn_recv_sock():任何原因,包括Accept queue超限,创建新连接,继承端口失败等,加1
Tcp MaxConn 0
Tcp InSegs tcp_v4_rcv(),收到一个skb,加1
Tcp InErrs
  1. tcp_rcv_established()->tcp_validate_incoming():如果有SYN且seq >= rcv_nxt,加1
  2. 以下函数内,如果checksum错误或者包长度小于TCP header,加1:
    1. tcp_v4_do_rcv()
    2. tcp_rcv_established()
    3. tcp_v4_rcv()
Tcp OutSegs
  1. tcp_v4_send_reset(), tcp_v4_send_ack(),加1
  2. tcp_transmit_skb(), tcp_make_synack(),加tcp_skb_pcount(skb)(见TCP_COOKIE_TRANSACTIONS)
Tcp OutRsts tcp_v4_send_reset(), tcp_send_active_reset()加1
 
TCP Loss & Retrans

类别名称描述
Tcp TCPTimeouts
  1. 在RTO timer中,从CWR/Open状态下第一次超时的次数,其余状态不计入这个计数器。
  2. SYN-ACK的超时次数。
Tcp RtoAlgorithm 1,tcp_mib_init()初始化
Tcp RtoMax 120000,tcp_mib_init()初始化:TCP_RTO_MAX*1000/HZ,TCP_RTO_MAX=120*HZ
Tcp RtoMin 200,tcp_mib_init()初始化:TCP_RTO_MIN*1000/HZ,TCP_RTO_MIN=HZ/5

以下计数器,统计的是调用tcp_retransmit_skb()的次数,由于sysctl_tcp_retrans_collapse/TSO的原因,一次tcp_transmit_skb()调用可能发送多个segs

类别名称描述
Tcp RetransSegs 重传次数,包括RTO timer和常规重传,即tcp_retransmit_skb()中调用tcp_transmit_skb(),成功返回即+1。
TcpExt TCPForwardRetrans (非RTO timer)发送新数据的次数,即在tcp_fastretrans_alert()/tcp_simple_retransmit()->tcp_xmit_retransmit_queue()中,

如果发现skb->seq > tp->retransmit_high(一般为snd_una),如果当前状态为Recovery,启用了SACK,并且发送条件允许就在这个函数中发送新数据。

TcpExt TCPFastRetrans

(非RTO timer)快速重传次数,即tcp_fastretrans_alert()/tcp_simple_retransmit()->tcp_xmit_retransmit_queue()中,如果不是LOSS状态,就加1

TcpExt TCPSlowStartRetrans (非RTO timer)重传次数:即tcp_fastretrans_alert()/tcp_simple_retransmit()->tcp_xmit_retransmit_queue()中,如果是LOSS状态,就加1
TcpExt TCPLostRetransmit

根据SACK数据推测出的重段包丢失计数器:在tcp_sacktag_write_queue()->tcp_mark_lost_retrans(), 如果发现tcp_highest_sack_seq(tp)超过某skb在重传时的snd_nxt(TCB->ack_seq),就认为这个重传包 已经丢失了,加1(加的不是段数)。tcp_highest_sack_seq(tp)是被SACK过的具有最高SEQ号的skb的seq。

TcpExt TCPSpuriousRTOs tcp_process_frto(),如果frto_counter !=0 && frto_counter != 1加1
 
TCP ACK & SACK

类别名称描述
TcpExt DelayedACKLocked tcp_delack_timer(): delay ACK定时器因为user已经锁住而无法发送ACK的次数。
TcpExt DelayedACKLost
  1. tcp_validate_incoming()->tcp_send_dupack():当输入包不在接收窗口内,或者PAWS失败后,计数器加1
  2. tcp_data_queue(): 输入包的结束序列号< RCV_NXT时,加1
TcpExt DelayedACKs tcp_delack_timer():调用tcp_send_ack()的次数,无论是否是成功。
TcpExt TCPHPAcks tcp_ack():接收到包,进入quick path时加1
TcpExt TCPPureAcks tcp_ack():接收慢速路径中的pure ACK数量
TcpExt TCPDSACKIgnoredNoUndo tcp_sacktag_write_queue(): undo_marker为0并且接收到非法D-SACK块时,加1,即SACK中的序号太旧。
TcpExt TCPDSACKIgnoredOld tcp_sacktag_write_queue(): undo_marker不为0,并且接收到非法D-SACK块时,加1,即SACK中的序号太旧。
TcpExt TCPSACKDiscard tcp_sacktag_write_queue(): 非法SACK块(不包括D-SACK)计数,即SACK中的序号太旧。
TcpExt TCPDSACKOldSent tcp_dsack_set():如果SACK块开始序号小于RCV.NXT,加1
TcpExt TCPDSACKOfoSent tcp_dsack_set():如果SACK块开始序号大于等于RCV.NXT,加1
TcpExt TCPSACKReneging tcp_clean_rtx_queue(): 如果snd_una(输入skb->ack)之后的具有最小开始序号skb(即sk_write_queue中的第一个skb)中有TCPCB_SACKED_ACKED标志,此时加1,这说明接收者已经丢掉了之前它已经SACK过的数据。
TcpExt TCPSackFailures tcp_retransmit_timer(): 在Reorder状态下,或者sacked_out不为0时,发生RTO,并且启用了SACK,加1
TcpExt TCPSackRecoveryFail tcp_retransmit_timer(): 在Reovery状态下发生RTO,并且启用了SACK,加1
TcpExt TCPDSACKRecv tcp_check_dsack(): 收到D-SACK,并且SACK0开始序号 < ACK号,加1
TcpExt TCPDSACKOfoRecv tcp_check_dsack(): 收到D-SACK,并且SACK0开始序号 >= ACK号,但SACK1包括SACK0。
TcpExt TCPSackRecovery tcp_fastretrans_alert(): SACK TCP进入Reovery状态的次数
TcpExt TCPSackShifted tcp_ack()->tcp_sacktag_write_queue()->tcp_sacktag_walk()->tcp_shift_skb_data()->tcp_shifted_data()

在tcp_sacktag_walk()时,一个SACK可能会导致切割某skb,新切出来的skb放到被切的skb之后。
根据SACK的观点,如果“旧的skb”(变小了)能够与它之前的skb合并,本计数器,就加1。
这个合并过程,叫作shift

TcpExt TCPSackShiftFallback tcp_ack()->tcp_sacktag_write_queue()->tcp_sacktag_walk()->tcp_shift_skb_data()

与上相反,如果不能shift,本计数器加1。原因可能如下:

  1. 不支持GSO
  2. prev skb不完全是paged的
  3. SACK的序号已经ACK过,等等
TcpExt TCPSackMerged tcp_ack()->tcp_sacktag_write_queue()->tcp_sacktag_walk()->tcp_shift_skb_data()->tcp_shifted_data()

在上面介绍的shift过程中,如果发现分割之后的skb被它之前的skb完全“吃掉”,本计数器加1

 
TCP TIME_WAIT

类别名称描述
TcpExt TW inet_twdr_do_twkill_work(): TIME_WAIT超时的socket数量(timeout >= 4s)
之所以按超时分别对待timewait socket,可能是考虑到长超时的socket的timeout时间分布比较分散,需要使用不同的查找方法。
TcpExt TWKilled inet_twdr_twcal_tick(): TIME_WAIT超时的socket数量.(timeout < 4s),
仅在启用sysctl_tw_recycle,并且使用TCP timestamp option时才会有这种情况,这时使用3.5x RTO时作为timewait timeout,而默认timeout为60s
TcpExt TWRecycled tcp_v4_connect() -> __inet_check_established(): 在建立时,如果port是从TIME_WAIT socket中复用的,加1
TcpExt TCPTimeWaitOverflow tcp_time_wait(): 当系统无法分配新的tcp_timewait_socket,或者tw_count(scheduled timewait sockets)超过sysctl_max_tw_buckets时,加1

TCP Others

类别名称描述
TcpExt TCPRenoRecoveryFail tcp_retransmit_timer(): 在Reovery状态下发生RTO,并且没有启用SACK,加1
TcpExt TCPRenoFailures tcp_retransmit_timer(): 在Reorder状态下,或者sacked_out不为0时,发生RTO,并且没有启用SACK,加1
TcpExt TCPRenoRecovery tcp_fastretrans_alert(): 不使用SACK的TCP进入Reovery状态的次数
 
此外,可以用这个脚本收集这些counters:
 
#! /usr/bin/python

proc_files = ("/proc/net/netstat", "/proc/net/snmp")

def parse_proc_files(fn):
    stats = {}
    lines = file(fn).readlines()
    n_lines = len(lines)
    n = 0
    while n < n_lines:
        titles = lines[n].split(" ") # TcpExt: TcpXX SackXX
        values = lines[n+1].split(" ") # TcpExt: 11 23213
        kind = titles[0]
        del titles[0]
        del values[0]
        sub_stats = stats.get(kind, {})
        n_cols = len(titles)
        for i in range(n_cols):
            sub_stats[titles[i].strip()] = values[i].strip()
        stats[kind] = sub_stats
        n += 2
    return stats


def show_parsed(stats):
    kind_list = stats.keys()
    kind_list.sort()
    for kind in kind_list:
        title_list = stats[kind].keys()
        title_list.sort()
        for title in title_list:
            print "%-10s%-25s\t%20s" % (kind, title, stats[kind][title])

s = {}
for fn in proc_files:
    new_s = parse_proc_files(fn)
    for new_kind in new_s:
        if new_kind not in s: # unlikely
            s[new_kind] = new_s[new_kind]
        else:
            s[new_kind].update(new_s[new_kind])

show_parsed(s)

 

 
 
 
继续讲述/proc/net/netstat, /proc/net/snmp中TCP的故事。
 
TCP Congestion Processing

类别名称描述
TcpExt TCPDSACKUndo tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_dsack()

Disorder状态下,undo完成(undo_retrans == 0)的次数。

TcpExt TCPFullUndo tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_recovery()

Recovery状态时,接收到到全部确认(snd_una >= high_seq)后且已经undo完成(undo_retrans == 0)的次数。

TcpExt TCPPartialUndo tcp_ack() -> tcp_fastretrans_alert() -> tcp_undo_partial()

Recovery状态时,接收到到部分确认(snd_una < high_seq)时但已经undo完成(undo_retrans == 0)的次数。

TcpExt TCPLossUndo tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_loss()

Loss状态时,接收到到全部确认(snd_una >= high_seq)后且已经undo完成(undo_retrans == 0)的次数。

TcpExt TCPRenoReorder

在tcp_update_reordering()中更新,当metric > tp->reordering并且没有启用SACK,本计数器加1 综合来说,在sacked_out“不可靠”时,tp->reordering被更新为当前窗口中的“已用seg”个数,同时包括未确认(和已确认的?)数据,但不包括lost_out。

A. tcp_ack() -> tcp_fastretrans_alert() -> tcp_add_reno_sack() -> tcp_check_reno_reordering() -> tcp_update_reordering(): 
在Open/Recovery/Disorder/CWR状态下接收到dupACK时:
如果sacked_out + lost_out > packets_out,
用metric( = packets_out)调用tcp_update_reordering()
B. tcp_ack() -> tcp_clean_rtx_queue() -> tcp_remove_reno_sacks() -> tcp_check_reno_reordering() -> tcp_update_reordering() : 
在清理rtx queue时,会从packets_out, lost_out, sacked_out中减去确认了的seg数量,
如果sacked_out + lost_out > packets_out,
用metric( = packets_out + acked_pcount)调用tcp_update_reordering()

注:

  1. sacked_out : 接收到的dupACK数量
  2. lost_out : 限制最小值为1,最大值为packets_out
  3. tp->reordering: 创建socket时,被动建立连接时,进入Loss状态时,初始化为sysctl_tcp_reordering

 

TcpExt TCPSACKReorder

在tcp_update_reordering()中更新,当metric > tp->reordering并且启用SACK但关闭FACK时,本计数器加1
A. tcp_ack() -> tcp_sacktag_write_queue() -> tcp_update_reordering() 
在tcp_sacktag_walk()中会计算fackets_out(通过累加state.fack_count),这个值即从snd_una开始到已经SACK的最高序号间的seg数量(包括没有被SACK覆盖的)。判断发生乱序的条件是: (1)发现针对重传报文的D-SACK;(2)当前接收到SACK序号比以前接收到的最大SACK序号小。state.reord是发生乱序时的最小fack_count,即在“snd_una + fack_count”处发生了乱序。
metric = tp->fackets_out - state.reord,即可能发生乱序的最多报文数。
B. tcp_ack() -> tcp_clean_rtx_queue() -> tcp_update_reordering() 
与A.类似,tcp_clean_rtx_queue()计算rtx queue中被SACK过的数据(非重传)中空洞,reord保存“最小号”空洞的位置(在重传队列中的“座次”)。而prior_fackets - reord即可能发生乱序的TCP segments数量。如果没有SACK,reorder = prior_fackets = 0
metric = prior_fackets - reord

TcpExt TCPFACKReorder 与TCPSACKReorder类似,如果同时启用了SACK和FACK,就增加本计数器。
TcpExt TCPTSReorder tcp_ack() -> tcp_fastretrans_alert() -> tcp_undo_partial() -> tcp_update_reordering() 

Recovery状态时,接收到到部分确认(snd_una < high_seq)时但已经undo完成(undo_retrans == 0)的次数。 数量上与TCPPartialUndo相等。

 
 
 
TCP Others

类别名称描述
TcpExt TCPRenoRecoveryFail tcp_retransmit_timer(): 在Reovery状态下发生RTO,并且没有启用SACK,加1
TcpExt TCPRenoFailures tcp_retransmit_timer(): 在Reorder状态下,或者sacked_out不为0时,发生RTO,并且没有启用SACK,加1
TcpExt TCPRenoRecovery tcp_fastretrans_alert(): 不使用SACK的TCP进入Reovery状态的次数
TcpExt ArpFilter arp_rcv() -> NETFILTER(ARP_IN) -> arp_process()

与TCP无关,接收到ARP packet时做一次输出路由查找(sip, tip),如果找到的路由项的device与输入device的不同,计数器加1

TcpExt EmbryonicRsts tcp_v4_do_rcv() -> tcp_v4_hnd_req() -> tcp_check_req(): 在三手握手时的SYN_RECV状态中接收到RST或者SYN的次数。
TcpExt LockDroppedIcmps tcp_v4_err(): 接收到ICMP错误报文,但tcp socket被user锁住
TcpEx OfoPruned tcp_data_queue() -> tcp_try_rmem_schedule()

慢速路径中,如果不能将数据直接复制到user space,需要加入到sk_receive_queue前,会检查receiver side memory是否允许,如果rcv_buf不足就可能prune ofo queue。此时计数器加1

TcpExt OutOfWindowIcmps tcp_v4_err(): 接收到的ICMP,但ICMP中的TCP头序号不在接收窗口之内的次数,有两个可能情况:(1)LISTEN状态时,序号不等待ISN;(2)其他状态时,序号不在SND_UNA .. SND_NXT之间
TcpExt PAWSActive tcp_rcv_synsent_state_process(): 在发送SYN后,接收到ACK,但PAWS检查失败的次数。
TcpExt PAWSEstab tcp_validate_incoming()

tcp_timewait_state_process()
tcp_check_req()
输入包PAWS失败次数。

TcpExt PAWSPassive tcp_v4_conn_request(): 三路握手最后一个ACK的PAWS检查失败次数。
TcpExt PruneCalled tcp_data_queue() -> tcp_try_rmem_schedule()

慢速路径中,如果不能将数据直接复制到user space,需要加入到sk_receive_queue前,会检查receiver side memory是否允许,如果rcv_buf不足就可能prune ofo queue。此时计数器加1

TcpExt RcvPruned tcp_data_queue() -> tcp_try_rmem_schedule()

慢速路径中,如果不能将数据直接复制到user space,需要加入到sk_receive_queue前,会检查receiver side memory是否允许,如果rcv_buf不足就可能prune receive queue,如果prune失败了,此计数器加1。

TcpExt SyncookiesFailed cookie_v4_check(): SYN cookie检查失败次数。
TcpExt SyncookiesRecv cookie_v4_check(): 接收SYN cookie次数。
TcpExt SyncookiesSent cookie_v4_init_sequence(): 生成SYN cookie次数。
TcpExt TCPAbortFailed tcp_send_active_reset(): alloc_skb()或者tcp_transmit_skb()失败。
TcpExt TCPAbortOnClose tcp_close(): sk_receive_queue中仍有数据的次数。
TcpExt TCPAbortOnData tcp_rcv_state_process(): 在FIN_WAIT_1/FIN_WAIT_2状态下接收到后续数据(序号>RCV_NXT);或者,TCP_LINGER2设置值<0,计数器加1

tcp_close(): 没有未读数据,但设置了SO_LINGER并且linger timeout=0, 计数器加1,此时TCP正常断开连接sk_prot->disconnect()。

TcpExt TCPAbortOnLinger tcp_close(): 因TCP_LINGER2设置值<0,FIN_WAIT_2立即切换到CLOSE的次数。
TcpExt TCPAbortOnMemory 在执行tcp_close()/probe timer/keepalive timer时,orphan sockets数量和tcp_memory_allocated是否超过最大值的次数。
TcpExt TCPAbortOnSyn tcp_validate_incoming(): 出现SYN,并且序号大于RCV_NXT的次数。
TcpExt TCPAbortOnTimeout RTO/probe/keepalive timer到达最大重试次数或者最长重试时间的次数

 
 

TCP Others Others

类别名称描述
TcpExt TCPBacklogDrop tcp_v4_rcv() : 如果socket被user锁住,后退一步内核会把包加到sk_backlog_queue,但如果因为sk_rcv_buf不足的原因入队失败,计数器加1
TcpExt TCPDeferAcceptDrop tcp_check_req(): 如果启用TCP_DEFER_ACCEPT,这个计数器统计了被丢掉了“Pure ACK”个数。

TCP_DEFER_ACCEPT:允许listener只有在连接上有数据才创建新的socket,以抵御syn-flood攻击。

TcpExt TCPDirectCopyFromBacklog tcp_recvmsg(): 如果有数据在softirq里面从backlog queue中直接复制到userland memory上,计数器加1
TcpExt TCPDirectCopyFromPrequeue tcp_recvmsg(): 如果有数据在这个syscall里从prequeue中直接复制到userland memory上,计数器加1
TcpExt TCPHPHits tcp_rcv_established(): 如果有skb通过“快速路径”进入到sk_receive_queue上,计数器加1。

特别地,Pure ACK以及直接复制到user space上的都不算在这个计数器上。

TcpExt TCPHPHitsToUser tcp_rcv_established(): 如果有skb通过“快速路径”直接复制到user space上,计数器加1。
TcpExt TCPLossFailures tcp_retransmit_timer(): icsk_retransmit==0(第一次进入重传状态)并且处于Loss状态下,计数器加1

可能情况是:因为partial ACK中从Loss中undo了一些状态,但还有完全离开Loss

TcpExt TCPMD5NotFound tcp_v4_do_rcv() -> tcp_v4_inbound_md5_hash() : 配置了md5检查,但在输入skb中没有找到对应TCP选项。
TcpExt TCPMD5Unexpected tcp_v4_do_rcv() -> tcp_v4_inbound_md5_hash() : 未配置md5检查,但在输入skb中找到了对应TCP选项。
TcpExt TCPMemoryPressures tcp_enter_memory_pressure()在从“非压力状态”切换到“有压力状态”时计数器加1,可能的触发点有:
  • tcp_sendmsg()
  • tcp_sendpage()
  • tcp_fragment()
  • tso_fragment()
  • tcp_mtu_probe()
  • tcp_data_queue()
TcpExt TCPMinTTLDrop tcp_v4_err() / tcp_v4_rcv(): 在接收到TCP报文或者TCP相关的ICMP报文时,检查IP TTL,如果小于socket option设置的一个阀值,就丢包。这个功能是RFC5082(The Generalized TTL Security Mechanism, GTSM)规定的,使用GTSM的通信双方,都将TTL设置成最大值255,双方假定了解之间的链路情况,这样可以通过检查最小TTL值隔离攻击。
TcpExt TCPPrequeueDropped tcp_v4_rcv() -> tcp_prequeue() : 如果因为内存不足(ucopy.memory < sk->rcv_buf)而加入到prequeue失败,重新由backlog处理,计数器加1
TcpExt TCPPrequeued tcp_recvmsg() -> tcp_prequeue_process() : tcp_recvmsg()发现可以从prequeue接收到报文,计数器加1(不是每个skb加1)
TcpExt TCPRcvCollapsed tcp_prune_queue() -> tcp_collapse() -> tcp_collapse_one()

tcp_prune_ofo_queue() -> tcp_collapse() 
每当合并sk_receive_queue(ofo_queue)中的连续报文时,计数器加1

TcpExt TCPReqQFullDoCookies tcp_rcv_state_process() -> tcp_v4_conn_request() -> tcp_syn_flood_action()

syn_table过载,进行SYN cookie的次数(取决于是否打开sysctl_tcp_syncookies)。

TcpExt TCPReqQFullDrop tcp_rcv_state_process() -> tcp_v4_conn_request() -> tcp_syn_flood_action()

syn_table过载,丢掉SYN的次数。

TcpExt TCPSchedulerFailed tcp_delack_timer(): 在delay ACK处理功能内,如果prequeue中仍有数据,计数器就加1

加入到prequeue,本来是期待着userspace(使用tcp_recvmsg()之类的系统调用)尽快处理之。其中仍有数据,可能隐含着userspace行为不佳。

TcpExt IPReversePathFilter ip_rcv_finish() -> ip_route_input_noref(): 反向路径过滤掉的IP分组数量:要么反向路由查找失败,要么是找到的输出接口与输入接口不同。
 
全剧终,如有错误,敬请指正。
 
 
 

posted on 2015-12-30 10:10  春之晓者  阅读(9115)  评论(2编辑  收藏  举报

导航