问题现象
- 客户的云主机每隔一段时间用curl工具获取http服务端的信息,通常情况可以正常工作。如下图所示,但同样的curl命令,多使用几次,就会出现一次curl卡住,命令不返回的情况。用户反馈使用其它网络线路节点上的虚机,访问相同http服务,不会出现。图中隐去了ip和port。
定位过程
虚机侧
- 首先使用strace命令查看curl命令卡住的用户栈,如下:
- curl工具实际上用于模拟http协议的request,它可以发送GET/PUT/POST等http request。如果不指定curl方法,默认发送GET请求。通过strace工具能看到,curl发送http request时调用了socket接口发起tcp连接服务端,上图我们隐去了服务端的地址和端口,可以看到curl卡在了connect之后的poll上。再查看curl进程的状态,调用poll接口后进程睡眠了,如下:
- 查看内核栈确实在睡眠,当服务端响应,返回tcp数据包之后,curl进程才会被唤醒。
主机侧
- 在curl命令运行时对主机侧虚机vnet网卡抓包,使用命令
tcpdump -nni ${ETH} host ${IP} and port ${port}
。正常运行时抓包如下:
- curl卡住时抓包如下,对比可以发现curl卡住时ip报文卡在tcp三次握手的第一次握手sync包。从左边的时间戳可以确认,sync包重传了,tcp重传的时间指数级增加:1、2、4、8、16、32。
- 抓取链路层数据,进一步分析sync包的内容,
tcpdump -nnxi ${ETH} host ${host} and port ${port}
:
- 以上分析可以确认,云主机内部的curl命令卡住的原因是主机侧发起tcp连接时,发送的sync包卡住了,服务端没有ack包回应,因此一直在重传,虚机内部看到的现象就是curl一直阻塞。至此我们知道虚机的流量至少走到了主机的vnet侧,下一步需要确认流量是否从物理网卡发出,查看vnet桥接的ovs桥,找到其对应的物理口。
1. virsh domiflist vm
查看虚机vnet桥接的ovs桥为br-int
2. ovs-vsctl show
查看主机上ovs桥信息及其上的port
3. ovs-ofctl -O OpenFlow13 show br-int
查看br-int桥上的流表信息,找到虚机vnet口在ovs桥上对应的port
4. ovs-ofctl -O OpenFlow13 dump-flows br-int
导出ovs上的流表,查看其转发规则,找到output
- 抓取报文,
tcpdump -nneei ${ETH} host ${host}
,发现curl卡住时仍然有流量,并且也是sync包,因此可以sync包已经从宿主机发出,为什么服务端没有ack回应,有两种可能:
- 链路丢包了,根本没有到达服务端,宿主机tcp协议发包后计数器超时,触发自动重传
- 链路到达了服务端,但服务端的cpu忙或者http server处理有问题,没能及时回复,触发自动重传
基础知识
HTTP
- http协议可以用于实现网络资源的操作和访问,也可以实现rest风格的API,它使用cs模式传输内容,传输的两端分别时客户端工具和服务端的web应用。其格式如下:
- 在我们的案例中curl就是一个组装http请求的工具,如果不指定method,默认为GET,内容如下:
- http协议的内容作为tcp协议的payload存在,curl在发送http请求时首先和服务端建立一个tcp的连接,我们strace跟踪到的connect系统调用就是socket编程中连接服务端的接口。tcp连接的核心工作就是进行三次握手,三次握手完成之后连接就建立了。之后curl工具可以将http的内容填入tcp连接的buffer中,传输到服务端。服务端读取tcp的内容后,从中解析出http的数据之后,传递给上层的web server程序。从而实现http协议内容的传输。当传输完成,curl断开这个tcp连接。对于http来说,它都是以短连接的方式使用tcp协议,每次发送完一个请求就会断开tcp连接。
TCP
- tcp协议两端是应用程序,linux下一个tcp连接可以通过socket编程接口,对于tcp的服务端,它监听一个端口,每当端口有数据到达便解析并进行相应处理,然后回复。其格式如下:
- 使用tcpdump工具抓包将tcp协议的头部解析出来,使用
tcpdump -i tap42a4a5cc-1a -nnx host 10.251.53.131 and port 8890 -vv
查看三次握手过程中发送的tcp报文,如下:
- tcpdump的信息中,多数字段都比较明确,下面只分析Flags和options两个字段,Flags字段就是解析tcp头部中的Flags,用可读的字母表示出来,手册解释如下:
Tcpflags are some combination of S (SYN), F (FIN), P (PUSH), R (RST), U (URG), W (ECN CWR), E (ECN-Echo) or `.' (ACK), or `none' if no flags are set
- SYN: 表示三次握手中的同步包,用于建立tcp连接,这个包只有头部,没有数据。
- FIN: 表示四次挥手中的结束包,用于关闭tcp连接,这个包只有头部,没有数据。
- PUSH: 表示tcp协议中有数据,tcp的payload不是空的,这是真正传输数据的包。如果上层是http协议,那么它传输的就是http协议的内容,当目的端的socket的server接收到tcp连接传输的数据后,它将tcp承载的内容解析出来,交给上层的web server,web server按照http协议约定解析相关字段,进行对应处理。
- ACK: 标识回复包,除了连接建立时第一次握手syn包不带ACK标识,其余任何时候的包都带有ACK标识,用于告诉对端自己希望收到的下一个包的起始序号,为表示方便就用一个
.
代替ACK标识。
- options字段长度可变,在0~40字节范围内,用于实现一些测试和验证的功能,下面介绍几个常见的option,更多细节参考TCP协议:
- MSS: Max Segment Size,用于设置tcp协议中能够发送的最大报文长度,这里的长度指的时tcp的payload,需要减去tcp头部长度,通常是MTU-40(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。
- TS val: Timestamps value,记录发送方在发送报文是当前系统的时间戳,这样可以计算整个包到达目的端的耗时,从而计算往返时间(Round-trip time)。
- wscale: Window Scale,早期的tcp协议中,接受窗口大小的最大值为65535字节,随着硬件能力的提升,能处理的窗口大小越来越大,可以进一步增大窗口大小,而tcp协议中给window size字段设计的长度是16bit,只能表示65535字节,为扩大窗口范围,给出了一个解决方案,在tcp协议的可选字段中增加wscale,表示window size的单位(窗口缩放因子)。在三次握手通信双方把自己的wscale告知对方,在正常通信解析window size时,将window size乘以2^wscale。假设wscale协商为7,如果window size的值为500,那么实际的窗口大小为500 * 2 ^ 7 = 500 * 128 = 64000。
IP
- IP报文不保证可靠连接,它的格式如下:
- 下图为IP报文的内容分析: