第17章 TCP保活机制

 

TCP保活机制
===================================================================================
TCP保活机制概述
    保活功能在默认情况下是关闭的。TCP连接的任何一端都可以请求打开这一功能。保活功能可以被设置在连接的一端、两端,或者两端都没有。
    有几个配置参数可以用来控制保活功能的操作。
    如果在一段时间(称为保活时间, keepalive time)内连接处于非活动状态,开启保活功能的一端将向对方发送一个保活探测报文。
    如果发送端没有收到响应报文,那么经过一个已经提前配置好的保活时间间隔(keepalive interval),将继续发送保活探测报文,直到发送探测报文的次数达到保活探测数(keepalive probe),这时对方主机将被确认为不可到达,连接也将被中断。 
    保活探测报文为一个空报文段(或只包含1字节)。它的序列号等于对方主机发送的 ACK报文的最大序列号减1。
        因为这一序列号的数据段已经被成功接收,所以不会对到达的报文段造成影响,但探测报文返回的响应可以确定连接是否仍在工作。探测及其响应报文都不包含任何新的有效数据(它是“垃圾”数据),当它们丢失时也不会进行重传。
        
    
TCP保活机制的目的:
    1.客户端、服务器需要了解什么时候终止进程or与对方断开连接。
    2.一些情况下,应用进程之间没有任何数据交换,但仍然需要通过连接保持一个最小的数据流

保活机制不是TCP规范的一部分,实际上都是应用程序来实现保活机制的。
keepAlive是由一个保活计时器实现的。当计时器被激发,连接一端 将发送一个保活探测(简称保活)报文,另一端接收报文的同时会发送一个ACK作为响应。

TCP保活机制的弊端:
    1.在出现短暂的网络错误时,保活机制会使一个好的连接断开
    2.保活机制占用了不必要的带宽
    3.按流量计费的话,会花掉更多的钱
TCP保活机制

 

TCP保活功能工作过程的状态;与TCP保活机制相关的攻击
================================================================================================
TCP保活功能工作过程中,开启该功能的一端会发现对方处于以下四种状态之一:
1.对方主机仍在工作,并且可以到达。
    对方的TCP响应正常,并且请求端也知道对方 在正常工作。请求端将保活计时器重置(重新设定为保活时间值)。
    如果在计时器超时之前有应用程序通过该连接传输数据,那么计时器将再次被设定为保活时间值。
2.对方主机已经崩溃,包括已经关闭或者正在重新启动。
    这时对方的TCP将不会响应。 请求端不会接收到响应报文,并在经过保活时间间隔指定的时间后超时。
    超时前,请求端会 持续发送探测报文,一共发送保活探测数指定次数的探测报文,如果请求端没有收到任何探 测报文的响应,那么它将认为对方主机已经关闭,连接也将被断开。
3.客户主机崩溃并且已重启。
    在这种情况下,请求端会收到一个对其保活探测报文的响应,但这个响应是一个重置报文段,请求端将会断开连接。
4.对方主机仍在工作,但是由于某些原因不能到达请求端(例如网络无法传输,而且可 圃 能使用ICMP通知也可能不通知对方这一事实)。
    这种情况与状态2相同,因为TCP不能区 分状态2与状态4,结果都是没有收到探测报文的响应。

请求端不必担心对方主机正常关闭然后重启(不同于主机崩溃)的情况。当系统关机时, 所有的应用进程也会终止(即对方的进程),这会使对方的TCP发送一个FIN。请求端接收 到FIN后,会向请求端进程报告文件结束,并在检测到该状态后退出。

------------------------------------------------------------------------------------------
与TCP保活机制相关的攻击
使系统长时间地维护不必要的会话资源
是获得端系统隐藏的一些信息(虽然这些信息对于攻击者而言可能实用性有限)。
由于默认情况下TCP不会对保活报文进行加密,所以保活探测报文和确认报文都有可能被利用。然而,对于应用层的保活机制(例如ssh),这些报文都会被加密,所以也就不会出现上述情况。
TCP保活功能工作过程的状态;与TCP保活机制相关的攻击

 

=====================================================================================

《网络排查案例课》07 | 保活机制:心跳包异常导致应用重启?

心跳包异常导致应用重启的案例
======================================================
案例背景:
    应用client和应用server需要保持tcp长连接,同时由client侧应用层实现tcp的keepalive,每隔45秒发送一次心跳包;若心跳探测失败,则重启client应用,建立新连接

故障分析逻辑:进行抓包分析,然后通过wireshark过滤规则,锁定到所有的心跳报文;观察到最后一条心跳居然是间隔58秒才发出的;正是由于应用层的这个BUG,导致client端判断keepalive失败,重置了长连接
心跳包异常导致应用重启的案例
TCP Keep-alive特性需要在应用程序中主动打开;Keep-alive 有关的全局配置项;TCP Keep-alive 报文特征
---------------------------------------------------------------------------
默认创建出来的 TCP Socket 是不启用 Keep-alive 的,也就是都不会发送心跳包。
要打开这个 TCP Keep-alive 特性,你需要使用 setsockopt() 系统调用,对已经创建的 Socket 进行配置,启用 Keep-alive。具体的调用方法,你可以参考 man setsockopt。


---------------------------------------------------------------------------
在 Linux 操作系统层级,也有三个跟 Keep-alive 有关的全局配置项。
    间隔时间:net.ipv4.tcp_keepalive_time,其值默认为 7200(秒),也就是 2 个小时。
    最大探测次数:net.ipv4.tcp_keepalive_probes,在探测无响应的情况下,可以发送的最多连续探测次数,其默认值为 9(次)。
    最长间隔:net.ipv4.tcp_keepalive_intvl,在探测无响应的情况下,连续探测之间的最长间隔,其值默认为 75(秒)。

 man tcp
tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
    The number of seconds between TCP keep-alive probes.
 
tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
    The  maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end.
 
tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
    The number of seconds a connection needs to be idle before TCP  begins  sending  out  keep-alive probes.   Keep-alives are sent only when the SO_KEEPALIVE socket option is enabled.  The default value is 7200 seconds (2 hours).  An idle connection is terminated after approximately an  additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled.


-------------------------------------------------------------------------
 TCP 本身提供的 Keep-alive 报文特征
    首先,它的序列号就很奇特,是上一个报文的序列号减 1,载荷为 0。
    回复的报文也同样特别,确认号为收到的序列号加 1。
    而且,无论是探测包还是回复包,其载荷长度都为 0。


    chrome实现的keepalive 的间隔为65秒,是基于tcp的keepalive
TCP Keep-alive特性需要在应用程序中主动打开;Keep-alive 有关的全局配置项;TCP Keep-alive 报文特征
HTTP Keep-alive(HTTP header---Connection: Keep-alive)
=================================================================================
HTTP 的 Keep-alive,是用 Connection 这样一个 HTTP header 来实现的。
    Keep-alive 头部,就是这个形式:Connection: Keep-alive(注意这里有一个“-”符号)。

HTTP 的版本有 0.9、1.0、1.1、2.0。HTTP/0.9 现在基本不再使用了,而 HTTP/1.0 占的流量比例也已经很低了(在公网上小于 1%)。目前占主流的是 HTTP/1.1 和 HTTP/2,而这两者都默认使用长连接。
HTTP/1.1 默认是长连接了,为什么还要有这个 Connection 头部呢?2个原因:
    1.协议兼容性
        在 HTTP/1.0 版本中,默认用的是短连接。Connection: Keep-alive 头部被扩充进了 HTTP/1.0,用来显式地声明这应该是一次长连接。
    2.关闭连接的现实需求
        即使在 HTTP/1.1 里,也并非所有的长连接都需要永久维持。有时候任意一方想要关闭这个连接,又想用一种“优雅”(graceful)的方式,那么就可以用 Connection: Close 这个头部来达到“通知对端关闭连接”的目的,体面而有效。
        另外,在 HTTP/2 里,连接关闭是通过另外的机制实现的,与 Connection 头部无关。
    

对于 HTTP Keep-alive 的知识点,你需要理解:
    HTTP/1.0 默认是短连接,HTTP/1.1 和 2 默认是长连接。
    Connection: Keep-alive 在 HTTP/1.0 里,能起到维持长连接的作用,而在 HTTP/1.1 里面没有这个作用(因为默认就是长连接)。
    Connection: Close 在 HTTP/1.1 里,可以起到优雅关闭连接的作用。这个头部在流量调度场景下也很有用,能明显加快基于 DNS/GSLB 的流量调整的收敛速度。
HTTP Keep-alive(HTTP header---Connection: Keep-alive)
python socket编程开启keepalive功能
===============================================================================================
[root@yefeng test05]# cat server.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
ip_add=('127.0.0.1',9000)

s = socket.socket()     # 创建套接字对象
s.bind(ip_add)          # 绑定ip和端口必须是元组
s.listen(5)             # 设置连接池挂起的数量

while True:
    conn,addr = s.accept()  # 接受客户端的连接,conn是客户端连接服务端的电信号,addr客户端ip,port
    #未设置sk.setblocking(bool)时,默认阻塞

    while True:
        try:
            recv_data = conn.recv(1024)    # conn.recv接收客户端信息1024允许接受的字节最大8k
            #未设置sk.setblocking(bool)时,默认阻塞

            if len(recv_data) == 0:break

            send_data = recv_data.upper()
            conn.send(send_data)   # conn.send发送信息
        except Exception:
            break

    conn.close()
-----------------------------------------------------------------------------------
[root@yefeng test05]# cat client.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
ip_add = ('127.0.0.1', 9000)

s = socket.socket()  # 创建套接字对象
s.connect(ip_add)  # s.connect连接服务器

if s is not None:
    s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, True)  #在 socket 编程中,我们对指定的 socket 添加 SO_KEEPALIVE 这个 option,这个 socket 便可以启用 KeepAlive 功能。
    s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 5)       # 覆盖tcp_keepalive_time;keepalive间隔
    s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 3)      # 覆盖tcp_keepalive_intvl;探测失败后的keepalive间隔
    s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 2)         # 覆盖tcp_keepalive_probes;允许的探测最大失败次数

while True:
    send_data = input(">>>:")
    if send_data == 'exit': break
    if len(send_data) == 0: continue
    s.send(bytes(send_data, encoding="utf-8"))  # s.send发送信息

    recv_data = s.recv(1024)  # s.recv接收服务器发来的信息
    # 未设置sk.setblocking(bool)时,默认阻塞
    print(str(recv_data, encoding="utf-8"))

s.close()

https://www.jianshu.com/p/e3791f975d7b
-------------------------------------------------------------------------------------------
#5秒的keepalive间隔
[root@yefeng ~]# tcpdump -nni any tcp port 9000
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
11:40:43.937463 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [S], seq 617778687, win 65495, options [mss 65495,sackOK,TS val 2883153129 ecr 0,nop,wscale 7], length 0
11:40:43.937475 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [S.], seq 1368551434, ack 617778688, win 65483, options [mss 65495,sackOK,TS val 2883153129 ecr 2883153129,nop,wscale 7], length 0
11:40:43.937483 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883153129 ecr 2883153129], length 0
11:40:49.103446 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883158295 ecr 2883153129], length 0
11:40:49.103454 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883158295 ecr 2883153129], length 0
11:40:54.159072 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883163351 ecr 2883158295], length 0
11:40:54.159083 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883163351 ecr 2883153129], length 0
11:40:59.215396 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883168407 ecr 2883163351], length 0
11:40:59.215407 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883168407 ecr 2883153129], length 0
11:41:04.271374 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883173463 ecr 2883168407], length 0
11:41:04.271384 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883173463 ecr 2883153129], length 0
11:41:09.327415 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883178519 ecr 2883173463], length 0
11:41:09.327424 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883178519 ecr 2883153129], length 0
11:41:14.383210 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883183575 ecr 2883178519], length 0
11:41:14.383238 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883183575 ecr 2883153129], length 0
11:41:19.439112 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883188631 ecr 2883183575], length 0
11:41:19.439140 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883188631 ecr 2883153129], length 0
11:41:24.495360 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883193687 ecr 2883188631], length 0
11:41:24.495369 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883193687 ecr 2883153129], length 0
11:41:29.551562 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883198743 ecr 2883193687], length 0
11:41:29.551572 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883198743 ecr 2883153129], length 0
11:41:34.607426 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883203799 ecr 2883198743], length 0
11:41:34.607434 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883203799 ecr 2883153129], length 0
11:41:39.663270 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883208855 ecr 2883203799], length 0
11:41:39.663277 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883208855 ecr 2883153129], length 0
11:41:44.719824 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883213912 ecr 2883208855], length 0
11:41:44.719860 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883213912 ecr 2883153129], length 0
11:41:49.775708 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883218968 ecr 2883213912], length 0
11:41:49.775719 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883218968 ecr 2883153129], length 0
11:41:54.832326 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883224024 ecr 2883218968], length 0
11:41:54.832338 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883224024 ecr 2883153129], length 0
11:41:59.887319 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883229079 ecr 2883224024], length 0
11:41:59.887328 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883229079 ecr 2883153129], length 0
11:42:04.942755 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883234135 ecr 2883229079], length 0
11:42:04.942763 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883234135 ecr 2883153129], length 0
11:42:09.999066 IP 127.0.0.1.45376 > 127.0.0.1.9000: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883239191 ecr 2883234135], length 0
11:42:09.999075 IP 127.0.0.1.9000 > 127.0.0.1.45376: Flags [.], ack 1, win 512, options [nop,nop,TS val 2883239191 ecr 2883153129], length 0
python socket编程开启keepalive功能

 

posted @ 2022-04-21 17:46  雲淡風輕333  阅读(46)  评论(0编辑  收藏  举报