关于 tcp syn 队列和 accept 队列

这篇文章里 说了 socket listen backlog 的意义,这里继续。

1. socket listen backlog 指定的其实就是 accept 队列,也就是 ESTABLISHED 状态的连接,这个数值不能超过 /proc/sys/net/core/somaxconn;

syn 队列里面放的是未建立的连接,数值由内核参数 /proc/sys/net/ipv4/tcp_max_syn_backlog 定义,应用代码没法修改。

 

2. 如果 accept 队列满,client 发来 ack,连接从 syn 队列移到 accept 队列的时候会发生什么呢?

1). 如果 /proc/sys/net/ipv4/tcp_abort_on_overflow 为1,会发送 RST;如果为0,则「什么都不做」,也就是「忽略」。

2). 但是,即使被忽略,对于 SYN RECEIVED 状态, 会有重试,重试次数定义在 /proc/sys/net/ipv4/tcp_synack_retries(重试时间有个算法)。

3). client 在收到 server 发来的重试 synack 之后,它认为之前发给 server 的 ack 丢失,会重发,此时如果 server 的 accept 队列有「空位」,会把连接移到 accpet 队列,并把 SYN RECEIVED 改成 ESTABLISHED。

4). 从另一个角度看, 即使 client 发的 ack 被忽略,因为 client 已经收到了 synack,client 认为连接已经建立,它可能会直接发送数据(ack 和 数据一起发送),这部分数据也会被忽略,会重传,幸好有「慢」启动机制保证重传的数据不会太多。

5). 如果 client 先等待 server 发来的数据,在 client 端连接是 ESTABLISHED,server 认为连接是 CLOSED,这会造成「半连接」。

6). 事实上,如果 accept 队列满了,内核会限制 syn 包的进入速度,如果太快,有些包会被丢弃。

 

注:本文讨论的是 Linux。

参考:

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

http://linux.die.net/man/2/listen

 

socket.listen(backlog) 中 backlog 指的是什么

1.首先,创建 socket bind 并 listen 之后,服务端就可以独自完成三次握手了(抓包确认),调用 accept 才是把「连接」从队列里取出来。

#-*- encoding: UTF-8 -*-

from socket import *

HOST = ''
PORT = 80
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(2)

while True:
    pass
    # print 'waiting for connection...'
    # tcpCliSock, addr = tcpSerSock.accept() # 等待连接
    # print '...connected from:', addr

tcpSerSock.close()

2.下面我们看看假如不调用 accept,最大能有多少 ESTABLISHED 的连接。

server 端代码还是采用上面的,client 代码如下:

#-*- encoding: UTF-8 -*-

import time
from socket import *
from multiprocessing.dummy import Pool as ThreadPool
HOST = '10.19.28.33'
PORT = 80
ADDR = (HOST, PORT)
MAX_THREAD_NUM = 100

def connect(x):
    tcpCliSock = socket(AF_INET, SOCK_STREAM)
    tcpCliSock.connect(ADDR) # 套接字连接
    time.sleep(4)
    return time.time(), tcpCliSock

pool = ThreadPool(MAX_THREAD_NUM)
result_data = pool.map(connect, xrange(MAX_THREAD_NUM))
pool.close()
pool.join()

print result_data

测试结果如下:

1). 当 server 端 listen 的 backlog 为0,最大的 ESTABLISHED 数量为 2;

2). 当 listen 的 backlog 为1,最大的 ESTABLISHED 数量为 2;

3). 当 listen 的 backlog 为 N,则最大的 ESTABLISHED 数量为 N + 1;

所以,backlog 代表着有多少个已经建立的但是没有被 accept 取走的连接数量。

另外,有个内核参数 net.core.somaxconn 定义了 socket 同时 listen 的最大连接数,所以 socket.listen() 指定的值不能超过 net.core.somaxconn。


3.如果 ESTABLISHED 满了,此时进来的连接请求状态是 SYN_RECV,那么 SYN_RECV 的数量(也就是 SYN 队列长度)最大是多少呢?

答案是由内核参数 net.ipv4.tcp_max_syn_backlog 决定。

我做了个测试,环境如下:

server 端,ip 10.19.28.33,代码如1,listen backlog 为2,net.ipv4.tcp_max_syn_backlog 设置为8,net.ipv4.tcp_syncookies 设置为0。

clinet 端,ip 10.0.11.12,代码如2,MAX_THREAD_NUM 设置为12。

两台机器的 /proc/sys/net/ipv4/tcp_synack_retries 和 /proc/sys/net/ipv4/tcp_syn_retries 都为2,而且 /proc/sys/net/ipv4/tcp_abort_on_overflow 都为0。

client 端会报:: [Errno 110] Connection timed out

在 server 端抓包,包在这里: socket.pcap

这里不纠结发生了什么,在后面的文章继续。

请求中的 Accept-Encoding Header 出错

请求类似:

GET /api/v1/feed HTTP/1.1
model: Android
If-Modified-Since: Thu, 10 Sep 2015 08:42:41 GMT+00:00
If-None-Match: “03e6c6bafcf335f108dd2fdc8359976cc”
Host: baobab.xxx.com
Connection: Keep-Alive
Accdpt-Encoding: gzip
User-Agent: okhttp/2.4.0

在 LVS 外网网卡上抓包: lvs.pcap

在 wireshark 里通过 http.request and http contains “Accdpt-Encoding” 过滤,然后再根据 ip.addr 和 tcp.port 过滤,看到如下结果:

111132432432423h1

说明进来的请求已经是有问题的了。

 

好吧,这篇文章目前为止没有价值,先 mark,如果有后续的话继续更新。

 

解决 libguestfs: error: /usr/libexec/qemu-kvm exited with error status 1 的问题

我在使用 guestfish 命令的时候遇到了这个错误。

# guestfish add /dev/vm_storage_pool_vg/vm01_data : run : mount /dev/datavg/home / : find /
libguestfs: error: /usr/libexec/qemu-kvm exited with error status 1.
To see full error messages you may need to enable debugging.
See http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs

但是很奇怪的是,敲两次 run 就好了:

# guestfish -a /dev/vm_storage_pool_vg/vm01_data

Welcome to guestfish, the libguestfs filesystem interactive shell for
editing virtual machine filesystems.

Type: ‘help’ for help on commands
‘man’ to read the manual
‘quit’ to quit the shell

><fs> run
libguestfs: error: /usr/libexec/qemu-kvm exited with error status 1.
To see full error messages you may need to enable debugging.
See http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs
><fs> run
><fs> mount /dev/datavg/home /

debug 一下:

# LIBGUESTFS_DEBUG=1 time guestfish -a /dev/null run
libguestfs: create: flags = 0, handle = 0x17f64a0
libguestfs: launch: attach-method=appliance
libguestfs: launch: tmpdir=/tmp/libguestfs1ssRuO
libguestfs: launch: umask=0022
libguestfs: launch: euid=0
libguestfs: command: run: febootstrap-supermin-helper
libguestfs: command: run: \ –verbose
libguestfs: command: run: \ -f checksum
libguestfs: command: run: \ /usr/lib64/guestfs/supermin.d
libguestfs: command: run: \ x86_64
supermin helper [00000ms] whitelist = (not specified), host_cpu = x86_64, kernel = (null), initrd = (null), appliance = (null)
supermin helper [00000ms] inputs[0] = /usr/lib64/guestfs/supermin.d
checking modpath /lib/modules/2.6.32-279.el6.x86_64 is a directory
picked vmlinuz-2.6.32-279.el6.x86_64 because modpath /lib/modules/2.6.32-279.el6.x86_64 exists
supermin helper [00000ms] finished creating kernel
supermin helper [00000ms] visiting /usr/lib64/guestfs/supermin.d
supermin helper [00000ms] visiting /usr/lib64/guestfs/supermin.d/base.img
supermin helper [00000ms] visiting /usr/lib64/guestfs/supermin.d/daemon.img
supermin helper [00000ms] visiting /usr/lib64/guestfs/supermin.d/hostfiles
supermin helper [00016ms] visiting /usr/lib64/guestfs/supermin.d/init.img
supermin helper [00016ms] visiting /usr/lib64/guestfs/supermin.d/udev-rules.img
supermin helper [00016ms] adding kernel modules
supermin helper [00041ms] finished creating appliance
libguestfs: checksum of existing appliance: 60ae12cd12306a513e0caec33c626dd574c942d10774918ace4779651ba62663
libguestfs: [00048ms] begin testing qemu features
libguestfs: command: run: /usr/libexec/qemu-kvm
libguestfs: command: run: \ -nographic
libguestfs: command: run: \ -help
libguestfs: command: run: /usr/libexec/qemu-kvm
libguestfs: command: run: \ -nographic
libguestfs: command: run: \ -version
libguestfs: qemu version 0.12
libguestfs: command: run: /usr/libexec/qemu-kvm
libguestfs: command: run: \ -nographic
libguestfs: command: run: \ -machine accel=kvm:tcg
libguestfs: command: run: \ -device ?
libguestfs: error: /usr/libexec/qemu-kvm exited with error status 1, see debug messages above
libguestfs: closing guestfs handle 0x17f64a0 (state 0)
libguestfs: command: run: rm
libguestfs: command: run: \ -rf /tmp/libguestfs1ssRuO
0.03user 0.06system 0:00.09elapsed 101%CPU (0avgtext+0avgdata 23072maxresident)k
0inputs+0outputs (0major+7951minor)pagefaults 0swaps

手动敲一下报错的命令:

# /usr/libexec/qemu-kvm -nographic -machine accel=kvm:tcg -device \?
qemu-kvm: -machine: invalid option

看一下 qemu-kvm 的版本:

# /usr/libexec/qemu-kvm -version
QEMU PC emulator version 0.12.1 (qemu-kvm-0.12.1.2), Copyright (c) 2003-2008 Fabrice Bellard

可能是版本低的问题,先升级 qemu-kvm:

yum -y install qemu-kvm

再看一下版本:

# /usr/libexec/qemu-kvm -version
QEMU PC emulator version 0.12.1 (qemu-kvm-0.12.1.2-2.479.el6), Copyright (c) 2003-2008 Fabrice Bellard

此时 /usr/libexec/qemu-kvm -nographic -machine accel=kvm:tcg -device \? 已经可以正常运行了,问题解决。

 

Tengine request_time 和 upstream_response_time 相等的问题排查

最近一段时间我们把 Tengine 从 1.4.6(nginx/1.2.9) 升级到了 Tengine 2.1.0(nginx/1.6.2),发现 request_time 出奇的小,大部分情况下都和 upstream_response_time 相等,特别对于 https 请求,一般要 0.x 秒,但是日志记录的只有 0.0x 秒。

 

搜遍了 Tengine,也没搜到有关系的内容,由于 Tengine 是 fork 的 Nginx,所以在 Nginx 测试一下,发现 nginx/1.6.2 同样有问题,继续测试 nginx/1.2.9 发现没问题。

所以是在 nginx/1.2.9 和 nginx/1.6.2 中间的某个版本引文的问题,实在没辙,只有用二分法找到了出问题的版本:

nginx-1.4.7    NOTOK
nginx-1.4.3    NOTOK
nginx-1.4.0    NOTOK
ngnx-1.3.16   NOTOK
nginx-1.3.15  NOTOK
ngnx-1.3.14   OK
nginx-1.3.9    OK
nginx-1.2.9    OK

nginx-1.3.15 的 changlog:

Changes with nginx 1.3.15 26 Mar 2013 *) Change: opening and closing a connection without sending any data in
it is no longer logged to access_log with error code 400. *) Feature: the ngx_http_spdy_module.
Thanks to Automattic for sponsoring this work.

*) Feature: the “limit_req_status” and “limit_conn_status” directives.
Thanks to Nick Marden.

*) Feature: the “image_filter_interlace” directive.
Thanks to Ian Babrou.

*) Feature: $connections_waiting variable in the
ngx_http_stub_status_module.

*) Feature: the mail proxy module now supports IPv6 backends.

*) Bugfix: request body might be transmitted incorrectly when retrying a
request to the next upstream server; the bug had appeared in 1.3.9.
Thanks to Piotr Sikora.

*) Bugfix: in the “client_body_in_file_only” directive; the bug had
appeared in 1.3.9.

*) Bugfix: responses might hang if subrequests were used and a DNS error
happened during subrequest processing.
Thanks to Lanshun Zhou.

*) Bugfix: in backend usage accounting.

changlog 都没提,擦,搜了一下,搜到了这个:

http://trac.nginx.org/nginx/ticket/356

解释如下:

As of 1.3.15, $request_time calculation was slightly changed due to some optimizations introduced, and for a first request in a connection it no longer includes time between opening a connection and reading a first byte from a client. It still works correctly though.

看样子是从收到请求数据的第一个字节开始算的,而不是按照之前建立连接时的时间(之前是否是建立时间我没真正测过,属猜测),在官方文档中看到 request_time 的说明:

request processing time in seconds with a milliseconds resolution; time elapsed between the first bytes were read from the client and the log write after the last bytes were sent to the client.

官方文档写的是第一个字节。

 

抓个包看看:request_time.pcap

日志中记录:”request_time=0.013″ “upstream_response_time=0.013”

E06FF4CC-110F-4739-B015-DBD55B0D9F75

10.0.23.74 是 Nginx,Nginx 接受到真实的请求数据是在 16,17 便是 Nginx 发送完数据的时间点,两者的时间差正好是 0.013,确认。

 

参考:

http://nginx.org/en/CHANGES-1.4

http://trac.nginx.org/nginx/ticket/356