⬑
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