../
getsockopt的隐藏功能
====================

2023-01-28

在排查网络问题的时候,有个很常见的需求是获取到TCP连接的信息,例如传输了多少数据、
重传率、ACK延迟等等。虽然一些简单的统计功能可以自己实现,例如可以在所有的 send
和recv处都加上记录函数以统计收发的字节数。但是这种方式终究还是不太优雅。如果用
eBPF,又会难免会涉及到内核态,很麻烦。所以,如果不需要考虑其他类UNIX系统的可移
植性的话,可以用Linux的专有接口。

## 接口

    int getsockopt(int sockfd, int level, int optname,
                   void *restrict optval, socklen_t *restrict optlen);

需要获取TCP连接信息的时候,level填写IPPROTO_TCP,optname填写TCP_INFO。optval填
写用来存储结果的buffer的指针,optlen这个参数既是输入也是输出,用来传入buffer的
大小,传出结果的大小。

## 数据结构

对于TCP信息,输出结果是一个结构体struct tcp_info。不过这个结构体的定义有两个版
本,第一个版本在<netinet/tcp.h>中:


    struct tcp_info
    {
      uint8_t	tcpi_state;
      uint8_t	tcpi_ca_state;
      uint8_t	tcpi_retransmits;
      uint8_t	tcpi_probes;
      uint8_t	tcpi_backoff;
      uint8_t	tcpi_options;
      uint8_t	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;

      uint32_t	tcpi_rto;
      uint32_t	tcpi_ato;
      uint32_t	tcpi_snd_mss;
      uint32_t	tcpi_rcv_mss;

      uint32_t	tcpi_unacked;
      uint32_t	tcpi_sacked;
      uint32_t	tcpi_lost;
      uint32_t	tcpi_retrans;
      uint32_t	tcpi_fackets;

      /* Times. */
      uint32_t	tcpi_last_data_sent;
      uint32_t	tcpi_last_ack_sent;	/* Not remembered, sorry.  */
      uint32_t	tcpi_last_data_recv;
      uint32_t	tcpi_last_ack_recv;

      /* Metrics. */
      uint32_t	tcpi_pmtu;
      uint32_t	tcpi_rcv_ssthresh;
      uint32_t	tcpi_rtt;
      uint32_t	tcpi_rttvar;
      uint32_t	tcpi_snd_ssthresh;
      uint32_t	tcpi_snd_cwnd;
      uint32_t	tcpi_advmss;
      uint32_t	tcpi_reordering;

      uint32_t	tcpi_rcv_rtt;
      uint32_t	tcpi_rcv_space;

      uint32_t	tcpi_total_retrans;
    };

这个版本是和FreeBSD等其他类UNIX系统兼容的,提供的信息比较少。

第二个版本在<linux/tcp.h>中,是Linux专有的,信息全面一些。因为前面部分的字段是
一样的,所以这里只放了额外的字段。

    struct tcp_info {
        // 同上

    	__u64	tcpi_pacing_rate;
    	__u64	tcpi_max_pacing_rate;
    	__u64	tcpi_bytes_acked;    /* RFC4898 tcpEStatsAppHCThruOctetsAcked */
    	__u64	tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */
    	__u32	tcpi_segs_out;	     /* RFC4898 tcpEStatsPerfSegsOut */
    	__u32	tcpi_segs_in;	     /* RFC4898 tcpEStatsPerfSegsIn */

    	__u32	tcpi_notsent_bytes;
    	__u32	tcpi_min_rtt;
    	__u32	tcpi_data_segs_in;	/* RFC4898 tcpEStatsDataSegsIn */
    	__u32	tcpi_data_segs_out;	/* RFC4898 tcpEStatsDataSegsOut */

    	__u64   tcpi_delivery_rate;

    	__u64	tcpi_busy_time;      /* Time (usec) busy sending data */
    	__u64	tcpi_rwnd_limited;   /* Time (usec) limited by receive window */
    	__u64	tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */

    	__u32	tcpi_delivered;
    	__u32	tcpi_delivered_ce;

    	__u64	tcpi_bytes_sent;     /* RFC4898 tcpEStatsPerfHCDataOctetsOut */
    	__u64	tcpi_bytes_retrans;  /* RFC4898 tcpEStatsPerfOctetsRetrans */
    	__u32	tcpi_dsack_dups;     /* RFC4898 tcpEStatsStackDSACKDups */
    	__u32	tcpi_reord_seen;     /* reordering events seen */

    	__u32	tcpi_rcv_ooopack;    /* Out-of-order packets received */

    	__u32	tcpi_snd_wnd;	     /* peer's advertised receive window after
    				      * scaling (bytes)
    				      */
    };

可以看到<linux/tcp.h>中的struct tcp_info多出了一些很重要的信息,比如收发的字节
数,如果要做流量统计的话就非常管用了。

## 示例代码

    #include <linux/tcp.h>

    // int fd = ...;
    struct tcp_info tcpi;
    int bufsz = sizeof(tcpi);
    int ret = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpi, &bufsz);
    if (ret != 0) {
        // TODO: error handling
    }
    printf("fd: %d has received %lu bytes and sent %lu bytes.\n",
           fd, tcpi.tcpi_bytes_receiver, tcpi.tcpi_bytes_sent);



--------------------------------------------------------------------
Email: i (at) mistivia (dot) com