0x01 缘由
最近在结合linux tcp/ip协议栈,以及上层socket编程来进行相关学习,学习过程中发现一些有趣的东西,但是也想做做记录。于是有了这篇文章。
tcp超时重传机制:https://baike.baidu.com/item/TCP%E8%B6%85%E6%97%B6%E9%87%8D%E4%BC%A0%E6%9C%BA%E5%88%B6/2122456?fr=aladdin
0x02 工具介绍
翻译:http://www.brendangregg.com/blog/2014-09-06/linux-ftrace-tcp-retransmit-tracing.html
工具下载:https://github.com/laoar/tcpretrans
对网络数据包分析工具较多,有tcpdump、tcpretrans、wireshark等工具。下面介绍一款不太熟悉的工具tcpretrans。
包的细节和内核状态,如下图:
tcpretrans是一个perf-tools脚本集,并使用ftrace来动态地调用tcp_retransmit_skb()内核函数。这种处理方式的资源开销是可以忽略不计的。它仅仅加了一些指令到重传路径。它也使用一些linux内核特征,ftrace 和kprobes,甚至不需要内核debuginfo。
它不会记录和过滤每个包,如果使用tcpdump/libpcap/kerbel-packet-filter方式,可能因为流量大而变得痛苦。用一个跟踪方式意味着能够挖掘内核状态,然而在线路上利用tcpdump是做不到这点的。仅实现了包含内核状态列,其他列可以定制。
-s选项将包含一个内核堆栈跟踪,当导致重传时:
这种情况发生在,当收到一个包(ip_rcv()),处理TCP ACK(tcp_ack()),然后通过tcp_fastretrans_alert()触发。下图是一个tcp快速重传:
下面是基于定时器的超时重传:
栈的回调情况,以及tcp_write_timer_handler()。基于时间定时器的回传的情况比快速重传的情况更糟糕,因为它们的应用基于定时器的延迟。在Linux中通常是1000 ms。
tcpretrans是基于ftrace的一个修改,不能在没有任何修改的系统工作。(也不支持IPv6)。为了挖掘一些细节,像点分制IP地址,我真的应该开发一个款类似SystemTap的工具。然而,我想尝试仅仅使用ftrace去做,使其更简单的在我的(Netflix 云)上使用。
基本流程:
1.用kprobes添加指令到tcp_retransmit_skb(),并且读取%di寄存器。
2.假设skb指针指向%di寄存器(为确定的知道需要利用内核debuginfo,通畅不会在生产环境中使用)。在非X86系统,可能是另外一个寄存器,那么这个脚本需要修改为相关寄存器。
3.等待1秒。
4.用skb指针读内核tcp_retransmit_skb()缓存事件。
5.通过skb指针读/proc/net/tcp和socket cache。
6.假设一个长时间的会话的重传发生时,这个会话的细节我们仍然可以在/proc/net/tcp中读到。
7.分析tcp_retransmit_skb()内核缓存事件,打印已经从/proc/net/tcp中读取的重传事件的细节内容。
8.Goto 3。
下面是蘑菇街工程师为了定位tcp重传问题,改进的一个版本tcpretrans。
0x03 tcp重传产生的原因
1、网络丢包,没有收到对方ack,导致确认丢包;
2、网络延迟太大,导致超时重传;
0x04 源码解读
kernel 2.6.32,本来想详细分析TCP拥塞状态机 tcp_fastretrans_alert()(传送:http://blog.csdn.net/shanshanpt/article/details/22202259)函数,但是考虑里面的内容,暂时搁置,先看自己熟悉的一块,对ack包的处理情况。tcp_ack函数:
博客其他文章有对tcp流重组算法等做了一个说明,类似linux内核也需要考虑类型的场景,现在单独调试tcp_ack处理过程,进一步加深印象:
1.调试环境准备,前面有几篇文章讲述了调试linux内核环境,下面开启设置断点到tcp_ack()
2.通过ssh连接guest机器,进行三次握手,当客户端发送syn后,guest机器,需要回复ack,这样就进入我们设置的断点,函数栈调用信息如下:
本来想自己好好去理解源码段,看到有人已经分析得比较清楚了,站在别人的基础上看待问题,是一种快速学习的手段。
传送:http://blog.csdn.net/zhangskd/article/details/7103336
0x05 问题
显示networking disabled 解决办法
第一步,先把网络停掉
sudo service network-manager stop
第二步,清理对应的网络状态文件
sudo rm /var/lib/NetworkManager/NetworkManager.state
第三步,启动网络即可
sudo service network-manager start
0x06 总结
我有时在想,学习linux网络协议栈的用处有啥?但是想想大部分网络设备产品和服务端程序,都涉及到相关知识点,希望以此开阔自己的视野。