redis 连接一直是 ESTABLISHED 的问题排查

昨天想删一台机器,发现上面还有 redis 连接:

# netstat -nat |grep ESTABLISHED |grep 6379
tcp 0 0 10.0.27.92:6379 10.0.27.157:24044 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.96.27:28975 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.69.47:58511 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.16.29.9:44571 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.29.49:48137 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.69.46:8854 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.70.67:42271 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.70.67:42269 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.24.30:17776 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.22.91:17823 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.23.79:59200 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.24.30:46296 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.23.98:31277 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.0.22.118:40458 ESTABLISHED
tcp 0 0 10.0.27.92:6379 10.16.29.9:44548 ESTABLISHED

这些连接一直都在,更奇怪的是,10.0.70.67 这个 IP 已经 ping 不通了,连接却不会断,正常情况下 tcp keepalive 会隔段时间检查一次,发现不通之后会发送 reset 。

 

为了看看到底有没有发送 tcp keepalive ack 包,抓个包看看,命令是 tcpdump -i em2 port 6379,下面是抓了一夜的包:redis.cpap

用 wireshark 分析,发现基本都是 keepalive ack 的包,取 10.0.96.27 这个IP,截个图看看:

4888351E-2D1D-4E6F-8062-F2DA259CF9C7

可以看到,10.0.96.27 主动 发送 ACK(SEQ: M、ACK: N)给 redis,redis 回复 ACK(SEQ: N、ACK:M+1),且 Len 都是0。

这能够解释大部分 IP 一直在 ESTABLISHED,因为一直有 tcp keepalive,但是 10.0.70.67 解释不通了,而且上面根本没抓到 10.0.70.67 的包,这只有一种可能: redis 不主动发送 keepalive。

 

找了下文档,发现 redis 确实默认关闭 tcp keepalive,所以对于已经建立的连接,不会发送 tcp keepalive ack 来确认对方存活,而如果对方突然死机或者关电源导致对方不主动关闭连接,那么 redis 就一直认为对方是活的,就不会去关闭连接了。

redis 提供了配置文件来更改这一默认行为:

# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
# equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 60 seconds.
tcp-keepalive 0

 

事实上,如果 redis 对 idle 有时间限制,我遇到的情况也不会存在,但是 redis 也确实默认对 idle 的连接不加时间限制, 只是提供了 timeout 参数来更改。

 

TCP 数据包格式

做个备忘。

TCP协议间交换的数据单元称为“TCP段”,包括两部分:首部和数据。标准首部长20字节,带有选项的首部会更长,选项最多 40 字节。

C9FBBF9A-04FE-4F2B-81E8-559391153C73

1. 源端口和目的端口

各占2字节,端口号加上IP地址,共同构成socket。互相通信的进程使用一对socket,包括协议、源IP、源端口、目的IP、目的端口,这五个元素唯一确定一个TCP连接。

2. 序号

占4字节,是TCP段所发送的数据部分第一个字节的序号。在TCP传送的数据流中,每一个字节都有一个序号。建立连接时,发送方将初始序号(Initial Sequence Number, ISN)填写到第一个发送的TCP段序号中。

3. 确认号

占4字节,是期望收到对方下次发送的数据的第一个字节的序号,也就是期望收到的下一个TCP段的首部中的序号,等于已经成功收到的TCP段的最后一个字节序号加1。确认号在ACK标志为1时有意义,除了主动发起连接的第一个TCP段不设置ACK标志外,其后发送的TCP段都会设置ACK标志。

4. 数据偏移

占4比特,表示数据开始的地方离TCP段的起始处有多远。实际上就是TCP段首部的长度。由于首部长度不固定,因此数据偏移字段是必要的。数据偏移以32位为长度单位,因此TCP首部的最大长度是60(15*4)个字节。

5. 控制位

一共6个,占6比特,设置为1时有效。按顺序依次为:URG、ACK、PSH、RST、SYN、FIN。

AED89D8A-403B-42F4-AA2E-3EC23D20FAB7

占2字节,表示报文段发送方期望接收的字节数,可接收的序号范围是从接收方的确认号开始到确认号加上窗口大小之间的数据。

6. 窗口

占2字节,表示报文段发送方期望接收的字节数,可接收的序号范围是从接收方的确认号开始到确认号加上窗口大小之间的数据。

7. 校验和

校验和包含了伪首部、TCP首部和数据,校验和是TCP强制要求的,由发送方计算,接收方验证。

8. 紧急指针

URG标志为1时,紧急指针有效,表示数据需要优先处理。紧急指针指出在TCP段中的紧急数据的最后一个字节的序号,使接收方可以知道紧急数据共有多长。

9. 选项   

最常用的选项是最大段大小(Maximum Segment Size,MSS),向对方通知本机可以接收的最大TCP段长度。MSS选项只在建立连接的请求中发送。
 
 
 
下面再介绍下选项。
典型的TCP头部选项结构如图3-4所示。
 
2A9044E0-E4AE-4801-B548-2CE4529ECB41
 
选项的第一个字段 kind 说明选项的类型。有的TCP选项没有后面两个字段,仅 包含 1字节的kind 字段。第二个字段 length(如果有的话)指定 该选项的总长度,该长度 包括 kind字段 和 length 字段占据的 2字节。第三个字段info(如果有的话)是选项的具体信息。常见的TCP选项有7种,如图3-5所示。
 
A35DFED0-B06F-4A59-859C-6B9B28688105
 
 
kind=0是选项表结束选项。
 
kind=1 是空操作(nop)选项,没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。
 
kind=2 是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)
字节。
 
kind=3 是窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小 是用16位表示的,故最大为65535 字节,但实际上TCP模块允许的接收通告窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子 解决了这个问题。假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N乘2M,或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改 /proc/sys/net/ipv4/tcp_window_scaling 内核变量来启用或关闭窗口扩大因子选项。
 
和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。关于窗口扩大因子选项的细节,可参考标准文档RFC 1323。
 
kind=4 是选择性确认(Selective Acknowledgment,SACK)选项。TCP通信时,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP 报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生 的,它使TCP模块只重新发送丢失的TCP报文段,不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启用或关闭选择性确认选项。
kind=5 是SACK实际工作的选项。该选项的参数 告诉 发送方 本端 已经收到 并缓存的 不连续 的 数据块,从而让发送端可以据此检查并重发丢失的 数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不 连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑 选项类型 和 长度占用的2字节 )。
 
kind=8 是时间戳选项。该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
 

AWS TCP Load Balancer 的一个坑

今天在迁移服务的时候发现了aws TCP的Load Balancer 的一个大坑, TCP 负载均衡其实是把客户端的请求截断,然后自己发送一个请求给后端,拿到后端返回的数据之后再返回给 客户端,这样后端看到的是 负载均衡器的IP,看不到客户端的真实IP了 (如果用基于HTTP的Load Balancer,会自动在HTTP头记录 X-Forwarded-For , 后端自然很容易获取到源IP )。其实这和LVS FULLNAT 模式有点像,LVS FULLNAT的解决办法是把真实IP写在TCP option里面,然后后端用toa模块拿到。

8612DFCC-601A-4F8D-92C0-CC863DDCF40E

 

对于这个问题,AWS给了一个解决办法,叫做  Proxy Protocol ,可以对TCP 负载均衡器开启Proxy Protocol,它会在请求的第一行写入 源IP、源端口等信息,以 \r\n 结尾,格式如下:

PROXY_STRING + single space + INET_PROTOCOL + single space + CLIENT_IP + single space + PROXY_IP + single space + CLIENT_PORT + single space + PROXY_PORT + “\r\n”

 

先说说如何开启 Proxy Protocol 。

第一步,为集群 ProxyProtocolTest  创建一个Proxy Protocol,叫 EnableProxyProtocol
# aws elb create-load-balancer-policy –load-balancer-name ProxyProtocolTest –policy-name EnableProxyProtocol  –policy-type-name ProxyProtocolPolicyType –policy-attributes AttributeName=ProxyProtocol,AttributeValue=True

第二步,为 ProxyProtocolTest  激活 EnableProxyProtocol
# aws elb set-load-balancer-policies-for-backend-server –load-balancer-name ProxyProtocolTest –instance-port 80 –policy-names EnableProxyProtocol

然后确认开启
# aws elb describe-load-balancers –load-balancer-name ProxyProtocolTest

可以看到类似下面的字段:

            “BackendServerDescriptions”: [
                {
                    “InstancePort”: 80,
                    “PolicyNames”: [
                        “EnableProxyProtocol”
                    ]
                }
            ]

如果想关闭Proxy Protocol 的话,用下面的命令
# aws elb set-load-balancer-policies-for-backend-server –load-balancer-name ProxyProtocolTest –instance-port 80 –policy-names “[]”

 

开启Proxy Protocol之后,看看后端如何获取到源IP,这里只看nginx。

nginx 从1.5.12 版本开始支持Proxy Protocol ,只需要配置一下就OK了,说说怎么配。

第一部分,在http 里面配置如下:
    set_real_ip_from   172.0.0.0/8;
    real_ip_header     proxy_protocol;

表示把来在172.0.0.0/8 段(TCP负载均衡器的IP段)的所有请求的来源地址,都改成 $proxy_protocol_addr,并且记录在 $remote_addr 变量里。

    log_format  main  ‘$proxy_protocol_addr $remote_addr – $remote_user [$time_local] “$request” ‘
                      ‘$status $body_bytes_sent “$http_referer” ‘
                      ‘”$http_user_agent” “$http_x_forwarded_for” ‘
                      ‘”$upstream_addr” “$request_time” “$upstream_response_time” “$upstream_cache_status”‘; 

这是日志格式。

第二部分,在server里面linsten的时候开启 proxy_protocol

    listen      80  proxy_protocol;

此时我请求的时候,日志刷出来了,这里可以看到 $proxy_protocol_addr  和 $remote_addr  都是客户端源IP。

202.134.72.73 202.134.72.73 – – [09/Oct/2014:08:00:12 -0400] “GET / HTTP/1.1” 200 0 “-” “curl/7.30.0” “-” “-” “0.000” “-” “-“
 

 

LVS fullnat模式下 syn_proxy机制对 建立连接过程 和 TCP option 的影响

我们的前端负载均衡已经切换到了LVS FULLNAT模式,它比LVS DR 模式 的好处是 LVS和后端机器不用在一个二层网络里面,非常好扩展,不好的地方是后端机器要装一台TOA模块来识别 HTTP请求的源IP。

想了解LVS FULLNAT的话可以参考这个链接:

http://kb.linuxvirtualserver.org/wiki/IPVS_FULLNAT_and_SYNPROXY

用LVS fullnat的时候其实需要注意一些TCP option的设置,比如 timestamps 和 windows scaling,而且 TCP option 和 synproxy 还有关系,在这篇文章我们手动测试,得出 怎么在LVS fullnat机器上 设置TCP option 。

 

环境:

LVS VIP :  60.28.208.10
LVS fullnat localip 段:10.0.19.0/24
后端IP :  10.0.11.12

 

探测的抓包

LVS 检测后端时LVS的抓包记录:

18:45:36.023248 IP 10.0.19.226.63923 > 10.0.11.12.http: Flags [S], seq 1537556744, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 18:45:36.023270 IP 10.0.19.226.19042 > 10.0.11.12.https: Flags [S], seq 520304659, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 18:45:36.023544 IP 10.0.11.12.http > 10.0.19.226.63923: Flags [S.], seq 3628316982, ack 1537556745, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 18:45:36.023560 IP 10.0.19.226.63923 > 10.0.11.12.http: Flags [.], ack 1, win 115, length 0 18:45:36.023565 IP 10.0.11.12.https > 10.0.19.226.19042: Flags [S.], seq 458068257, ack 520304660, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 18:45:36.023572 IP 10.0.19.226.19042 > 10.0.11.12.https: Flags [.], ack 1, win 115, length 0 18:45:36.023589 IP 10.0.19.226.63923 > 10.0.11.12.http: Flags [R.], seq 1, ack 1, win 115, length 0 18:45:36.023602 IP 10.0.19.226.19042 > 10.0.11.12.https: Flags [R.], seq 1, ack 1, win 115, length 0

LVS 检测后端时后端的抓包记录:

18:46:26.041901 IP 10.0.19.226.61028 > 10.0.11.12.http: Flags [S], seq 3032456781, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0

18:46:26.041937 IP 10.0.11.12.http > 10.0.19.226.61028: Flags [S.], seq 1104080475, ack 3032456782, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
18:46:26.041951 IP 10.0.19.226.40349 > 10.0.11.12.https: Flags [S], seq 849356025, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
18:46:26.041960 IP 10.0.11.12.https > 10.0.19.226.40349: Flags [S.], seq 1050279505, ack 849356026, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
18:46:26.042113 IP 10.0.19.226.61028 > 10.0.11.12.http: Flags [.], ack 1, win 115, length 0
18:46:26.042147 IP 10.0.19.226.40349 > 10.0.11.12.https: Flags [.], ack 1, win 115, length 0
18:46:26.042310 IP 10.0.19.226.61028 > 10.0.11.12.http: Flags [R.], seq 1, ack 1, win 115, length 0
18:46:26.042373 IP 10.0.19.226.40349 > 10.0.11.12.https: Flags [R.], seq 1, ack 1, win 115, length 0

上面可以看到,LVS 从localip 段里面选一个IP,起两个端口分别探测 80 和 443,建立连接之后 里面发送 RST 给后端,这样探测也成功了。

 

请求的抓包

在一台机器上执行下面命令:
curl -H “Host:10.0.11.12” http://60.28.208.10/test

LVS 上面抓VIP 的包如下:
tcpdump -n -i em1 host 60.28.208.10 and port 80
17:08:00.122008 IP 111.206.15.146.15405 > 60.28.208.10.http: Flags [S], seq 1679917190, win 14600, options [mss 1460,sackOK,TS val 1412716450 ecr 0,nop,wscale 7], length 0
17:08:00.122018 IP 60.28.208.10.http > 111.206.15.146.15405: Flags [S.], seq 2689133917, ack 1679917191, win 14600, options [mss 1452,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 1], length 0
17:08:00.132352 IP 111.206.15.146.15405 > 60.28.208.10.http: Flags [.], ack 1, win 115, length 0
17:08:00.132439 IP 111.206.15.146.15405 > 60.28.208.10.http: Flags [P.], seq 1:169, ack 1, win 115, length 168
17:08:00.132877 IP 60.28.208.10.http > 111.206.15.146.15405: Flags [.], ack 169, win 123, length 0
17:08:01.138207 IP 60.28.208.10.http > 111.206.15.146.15405: Flags [P.], seq 1:215, ack 169, win 123, length 214
17:08:01.148585 IP 111.206.15.146.15405 > 60.28.208.10.http: Flags [.], ack 215, win 123, length 0
17:08:01.148741 IP 111.206.15.146.15405 > 60.28.208.10.http: Flags [F.], seq 169, ack 215, win 123, length 0
17:08:01.149060 IP 60.28.208.10.http > 111.206.15.146.15405: Flags [F.], seq 215, ack 170, win 123, length 0
17:08:01.159344 IP 111.206.15.146.15405 > 60.28.208.10.http: Flags [.], ack 216, win 123, length 0

LVS 上面抓连接后端机器的包:
tcpdump -n -i em2 net 10.0.19.0/24 and host 10.0.11.12
17:08:00.132468 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [S], seq 3159822860, win 5000, options [Unknown Option 2003c2d6fce0f92,mss 1440,nop,nop,sackOK,nop,wscale 7], length 0
17:08:00.132656 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [S.], seq 2205177579, ack 3159822861, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
17:08:00.132686 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [P.], seq 1:169, ack 1, win 115, options [Unknown Option 2003c2d6fce0f92], length 168
17:08:00.132855 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [.], ack 169, win 123, length 0
17:08:01.138194 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [P.], seq 1:215, ack 169, win 123, length 214
17:08:01.148598 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [.], ack 215, win 123, length 0
17:08:01.148749 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [F.], seq 169, ack 215, win 123, length 0
17:08:01.149046 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [F.], seq 215, ack 170, win 123, length 0
17:08:01.159357 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [.], ack 216, win 123, length 0

上面的包是在开启syn_proxy的时候抓的,可以看出(注意看时间点),Client 请求来的时候,LVS 会先和Client 建好连接,然后在 Client 请求数据的时候 LVS 选择一个localip端口 和 后端 建立连接,建立成功后LVS 直接 向后端请求数据,获取数据后 再通过 VIP 传给 Client。

 

下面我们把 syn_proxy 关掉之后重新抓一下看看。

tcpdump -n -i em1 host 60.28.208.10 and port 80
17:10:54.142439 IP 111.206.15.146.15408 > 60.28.208.10.http: Flags [S], seq 50780707, win 14600, options [mss 1460,sackOK,TS val 1412890473 ecr 0,nop,wscale 7], length 0
17:10:54.142802 IP 60.28.208.10.http > 111.206.15.146.15408: Flags [S.], seq 629114004, ack 50780708, win 14600, options [mss 1452,nop,nop,sackOK,nop,wscale 7], length 0
17:10:54.153112 IP 111.206.15.146.15408 > 60.28.208.10.http: Flags [.], ack 1, win 115, length 0
17:10:54.153180 IP 111.206.15.146.15408 > 60.28.208.10.http: Flags [P.], seq 1:169, ack 1, win 115, length 168
17:10:54.153360 IP 60.28.208.10.http > 111.206.15.146.15408: Flags [.], ack 169, win 123, length 0
17:10:55.158651 IP 60.28.208.10.http > 111.206.15.146.15408: Flags [P.], seq 1:215, ack 169, win 123, length 214
17:10:55.168912 IP 111.206.15.146.15408 > 60.28.208.10.http: Flags [.], ack 215, win 123, length 0
17:10:55.168940 IP 111.206.15.146.15408 > 60.28.208.10.http: Flags [F.], seq 169, ack 215, win 123, length 0
17:10:55.169284 IP 60.28.208.10.http > 111.206.15.146.15408: Flags [F.], seq 215, ack 170, win 123, length 0
17:10:55.179620 IP 111.206.15.146.15408 > 60.28.208.10.http: Flags [.], ack 216, win 123, length 0

tcpdump -n -i em2 net 10.0.19.0/24 and host 10.0.11.12
17:10:54.142475 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [S], seq 1583761902, win 14600, options [Unknown Option 2003c306fce0f92,mss 1460,sackOK,TS val 1412890473 ecr 0,nop,wscale 7], length 0
17:10:54.142766 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [S.], seq 629114004, ack 1583761903, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
17:10:54.153128 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [.], ack 1, win 115, options [Unknown Option 2003c306fce0f92], length 0
17:10:54.153192 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [P.], seq 1:169, ack 1, win 115, options [Unknown Option 2003c306fce0f92], length 168
17:10:54.153348 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [.], ack 169, win 123, length 0
17:10:55.158639 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [P.], seq 1:215, ack 169, win 123, length 214
17:10:55.168925 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [.], ack 215, win 123, length 0
17:10:55.168944 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [F.], seq 169, ack 215, win 123, length 0
17:10:55.169272 IP 10.0.11.12.http > 10.0.19.227.commplex-link: Flags [F.], seq 215, ack 170, win 123, length 0
17:10:55.179630 IP 10.0.19.227.commplex-link > 10.0.11.12.http: Flags [.], ack 216, win 123, length 0

通过上面的抓包可以看到(同样看时间点),关闭syn_proxy 的时候,LVS 接受到请求,此时开启一个localip的一个端口 给后端发送 SYN,收到 后端的SYN+ACK之后,LVS通过VIP 给Client 返回 SYN+ACK,然后 Client 会再发送 ACK,VIP 会接收到这个ACK,然后再通过localip 传给 后端,后端收到后 建立连接。看这个流程,LVS 纯粹扮演 Proxy 的角色。

syn_proxy 机制会影响 Client 到 Server 的TCP 连接的Option 选项,下面细看。

 

关于timestamps 的测试

Client —> LVS —> Server

LVS 上面有三个和 timestamps 相关的参数 :
net.ipv4.tcp_timestamps
net.ipv4.vs.fullnat_timestamp_remove_entry
net.ipv4.vs.synproxy_timestamp

(LVS 默认 net.ipv4.vs.fullnat_timestamp_remove_entry = 1 ,net.ipv4.vs.synproxy_timestamp = 0)

在开启 syn_proxy 情况下的测试:

1. 即使 Client 和 Server 的 timestamps 都设置为1 ,只要 LVS net.ipv4.vs.synproxy_timestamp 不设置为1 ,timestamps 就不会启用。此时  LVS 的 net.ipv4.tcp_timestamps 和 net.ipv4.vs.fullnat_timestamp_remove_entry 完全没用。

2. 如果Client 关闭了 net.ipv4.tcp_timestamps ,timestamps 选项不会启用。

3. 比较诧异的是,无论 Server 的 net.ipv4.tcp_timestamps 是否设置为1,只要LVS 开启 net.ipv4.vs.synproxy_timestamp 和 Client 开启 net.ipv4.tcp_timestamps,建立的TCP连接 就会启用 timestamps (此时在Server 上抓包 会看到 TS val 和 ecr )

在关闭 syn_proxy 情况下的测试:

1. 此时 net.ipv4.vs.fullnat_timestamp_remove_entry  参数起作用,即使 Client 和 Server 的 timestamps 都设置为1 ,只要 LVS net.ipv4.vs.fullnat_timestamp_remove_entry 不设置为0 ,timestamps 就不会启用。

2. 如果Client 关闭了 net.ipv4.tcp_timestamps ,timestamps 选项不会启用。

3. 如果Server关闭了 net.ipv4.tcp_timestamps ,timestamps 选项不会启用。

总结:

1. syn_proxy 开启,net.ipv4.vs.synproxy_timestamp 决定LVS 是否启用timestamps。

2. syn_proxy 关闭,net.ipv4.vs.fullnat_timestamp_remove_entry 决定LVS 是否启用timestamps。

3. LVS FULLNAT 模式 的 net.ipv4.tcp_timestamps 参数 完全没用,设置与不设置都不会影响timestamps

4. 简单点,如果想开启LVS的 timestamps,把 net.ipv4.vs.synproxy_timestamp 设置成1,同时把  net.ipv4.vs.fullnat_timestamp_remove_entry 设置成 0

 

关于windows  scaling 的测试

Client —> LVS —> Server

在开启 syn_proxy 情况下的测试:

1. net.ipv4.vs.synproxy_wscale 决定连接 是否 wscale,即使 Client 和 Server 都开启了wscale,只要LVS 不开启 net.ipv4.vs.synproxy_wscale,连接就不会开启wscale。

2. 如果Client 不开启 net.ipv4.tcp_window_scaling ,连接就不会开启 wscale。

3. Server 的 net.ipv4.tcp_window_scaling 变的没用,即使没打开,只要 Client 打开了net.ipv4.tcp_window_scaling 而且LVS 打开了 net.ipv4.vs.synproxy_wscale ,连接就会打开 wscale

在关闭 syn_proxy 情况下的测试:

1. 只要 Client 和 Server 同时开启 wscale ,连接就会开启 wscale,不管 LVS 如何设置。

总结:

1. LVS 的 net.ipv4.tcp_window_scaling 完全没用,可忽略这个参数。

2. 想开启LVS 的wscale, 把 net.ipv4.vs.synproxy_wscale  设置成1