TCP
2024-06-07 20:03:26 0 举报
AI智能生成
TCP协议知识体系
作者其他创作
大纲/内容
Linux 文件描述符限制
不同级别
系统级别
fs.file-max
表示整个系统可打开的最大文件数, 不限制 root 用户
进程级别
fs.nr_open
若加大了 hard nofie 则该值也需加大. 若 hard nofile 设置的值大于 fs.nr_open 那将导致用户无法登陆, 若设置的是 * 则所有用户将无法登录
用户级别
/etc/security/limits.conf
soft nofile
针对不同用户配置不同的值
若需增大该值, 那么 hard nofile也需一并调整,因为系统实际可打开的最大文件数取决于两者最小者
代表警告的设定,可以超过这个设定值,但是超过后会有警告
hard nofile
代表严格的设定,不允许超过这个设定的值
进程打开百万文件描述符的配置
# vim /etc/sysctl.conf
fs.file-max=1100000 # 系统级别
fs.nr_open=1100000 # 进程级别, 保证大于 hard nofile
# vim /etc/security/limits.conf
// 用户进程级别都设置为 100 万
* soft nofile 1000000
* hard nofile 1000000
相关流程
建立连接(三次握手)
1. 客户端向服务器发送连接请求报文,SYN=1, seq=x
2. 服务器接收刀连接请求后,返回确认信息,SYN=1,ACK=1,seq=y, ack=x+1
3. 客户端收到应答后,再次向服务器发送一个确认报文,ACK=1,seq=x+1, ack=y+1
为什么要建立三次连接?
防止失效的连接请求报文段被服务端接受,从而产生错误
示意图
分支主题
TCP状态异常
1. SYN_SENT过多
访问的网站不存在或线路不好
用扫描软件扫描一个网段的机器,也会出现很多SYN_SENT
可能中了病毒了,例如中了"冲击波",病毒发作时会扫描其它机器,这样会有很多SYN_SENT出现
2. SYN_RCVD状态过多,或三次握手如果
最后一次没有成功的话,服务端会怎么处理
攻击软件随机伪造地址,服务器在收到连接请求时将标志位ACK和SYN置1发送给客户端(握手的第二步),但是这些客户端的IP地址都是伪造的,服务器根本找不到客户机,也就是说握手的第三步不可能完成。这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟)
关闭连接(四次挥手)
1. 客户端发出连接释放报文,并停止发送数据。FIN=1, seq=x, 进入FIN-WAIT-1状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
3. 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认
5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段存活时间)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些
示意图
分支主题
疑问
1. 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能回到CLOSE状态
1. 为了保证客户端发送的最后一个ack报文能够到达服务器
2. 客户端在发送完ACK报文段后,再经过2MSL时间,就可以使本连接持续的时间所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求的报文段。
2. 如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
服务器TCP连接状态异常
1. close_wait过多
close_wait 按照正常操作的话应该很短暂的一个状态,接收到客户端的fin包并且回复客户端ack之后,会继续发送fin包告知客户端关闭连接,之后迁移到Last_ACK状态。但是close_wait过多只能说明没有迁移到Last_ACK,也就是服务端是否已发送fin包,只有发送fin包才会发生迁移,所以问题定位在是否已发送fin包。fin包的底层实现其实就是调用socket的close方法,这里的问题出在没有执行close方法。说明服务端socket忙于读写。
2. time_wait停留时间过长
什么情况会出现三次挥手?
当被动关闭方在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
因为 TCP 延迟确认机制是默认开启的,所以导致我们抓包时,看见三次挥手的次数比四次挥手还多。
相关参数
MSL
最大报文存活时间: Maximum Segment Lifetime
MSS
每一个TCP报文段中的数据字段部分的最大长度: MSS=TCP报文段长度-TCP首部长度
MSS是在三次握手的时候,在两端主机之间被计算得出的。两端主机在发出“建立TCP连接请求的SYN包”时,会在SYN包的TCP首部中写入MSS选项,告诉对方自己所能够适应的MSS的大小,然后发送端主机会在两者之间选择一个较小的MSS值投入使用
最理想的情况是,最大消息长度MSS正好是IP层中不被分片处理的最大数据长度
TCP在传送大量数据的时候,是以“段=MSS的大小”将数据进行分割发送的,进行重发时也是以MSS为单位的。
MTU
最大传送单元,以太网默认1500字节
分支主题
RTT(Round Trip Time)
一个连接的往返时间,即数据发送时刻到接收到确认的时刻的差值
RTO(Retransmission Time Out)
重传超时时间,即从数据发送时刻算起,超过这个时间便执行重传。
RST
重置标志位,用于异常的关闭连接。发送RST包关闭连接时,不必等缓冲区的包都发出去(不像上面的FIN包),直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认
案例
1. A向B发起连接,但B之上并未监听相应的端口,这时B操作系统上的TCP处理程序会发RST
2. AB正常建立连接了,正在通讯时,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现connect reset by peer错误。
RST攻击
A和服务器B之间建立了TCP连接,此时C伪造了一个A的TCP包发给B,使B异常的断开了与A之间的TCP连接
1. 若这个包是RST包,B将会丢弃与A的缓冲区上所有数据,强制关掉连接。
2. 若是SYN包,那么,B会表示A已经发疯了(与OS的实现有关),正常连接时又来建新连接,B主动向A发个RST包,并在自己这端强制关掉连接
前提条件是知道源IP端口以及目标IP端口
TCP_NODELAY
在网络拥塞控制领域,我们知道有一个非常有名的算法叫做 Nagle 算法( Nagle algorithm ),这是使用它的发明人 John Nagle 的名字来命名的, John Nagle 在1984年首次用这个算法来尝试解决福特汽车公司的网络拥塞问题( RFC 896),该问题的具体描述是:如果我们的应用程序一次产生1个字节的数据,而这个1个字节数据又以网络数据包的形式发送到远端服务器,那么就很容易导致网络由于太多的数据包而过载。比如,当用户使用 Telnet 连接到远程服务器时,每一次击键操作就会产生1个字节数据,进而发送出去一个数据包,所以,在典型情况下,传送一个只拥有1个字节有效数据的数据包,却要发费40个字节长包头
SO_LINGER
选项
1. 若 l_onoff = 0,则关闭该选项, l_linger值被忽略,此时调用close或shutdown会立即返回。若此时套接字发送缓冲区有残留数据,则系统会试着将这些数据发送出去
2. l_onoff !=0 且 l_linger = 0 . 那么调用close后会立即发送一个RST标志给对端,该TCP连接将跳过四次握手,也即跳过TIME_WAIT直接关闭。该情况下排队中的数据不会被发送,被动关闭方也无从得知对端已彻底断开。若此时被动关闭方正阻塞在recv调用上,那么被动方收到RST时会抛出 connet reset by peer
3. l_onoff !=0 且 l_linger != 0那么调用close后,调用close的线程将阻塞,直到数据被发送出去,或者设置的 l_linger 计时时间超时
设置TCP关闭连接(close 或 shutdown)时的行为
net.ipv4.tcp_tw_reuse, 仅作用客户端
若开启该选项, 连接发起方在调用 connect 函数时,内核会随机选择一个 time_wait 状态超过 1 秒的连接给新的连接复用, 仅适用于连接发起方
开启 tw_reuse 可能遇到的问题
1. 历史 RST报文可能会终止后面相同四元组的连接,因为 PAWS检查到 RST的过期报文时也不会丢弃
2. 如果第四次挥手的ACK报文丢失,有可能被动连接一方无法正常关闭
net.ipv4.tcp_tw_recycle
若开启该选项则允许处于 time_wait 状态的连接被快速回收
在 NAT 网络下不安全
Linux 在 4.12 以后直接取消了该参数
net.ipv4.tcp_keepalive_time(默认2h)
net.ipv4.tcp_keepalive_intvl (默认 75s )
net.ipv4.tcp_keepalve_probes (默认9)
SO_REUSEADDR
tcp_wmem(仅针对单个连接)
net.ipv4.tcp_wmem = 8192 65536 16777216
tcp_wmem 中这三个数字的含义分别为 min、default、max。TCP 发送缓冲区的大小会在 min 和 max 之间动态调整,初始的大小是 default,这个动态调整的过程是由内核自动来做的,应用程序无法干预。自动调整的目的,是为了在尽可能少的浪费内存的情况下来满足发包的需要。
tcp_wmem 中的 max 不能超过 net.core.wmem_max 这个配置项的值,如果超过了,TCP 发送缓冲区最大就是 net.core.wmem_max。通常情况下,我们需要设置 net.core.wmem_max 的值大于等于 net.ipv4.tcp_wmem 的 max
net.ipv4.tcp_mem(所有连接内存总和)
OSI 与 TCP/IP
可靠性保证
校验和
一个端到端的校验和,由发送端计算,然后由接收端验证
伪首部共有12字节,包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)
确认应答与序列号
流量控制
TCP根据接收端对数据的处理能力,决定发送端的发送速度
窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小。这个数字越大,证明接收端接收缓冲区的剩余空间越大,网络的吞吐量越大。接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值的改变进而改变自己的发送速度。
如果发送端接收到的窗口大小为0时,发送端会停止发送数据,并定期地向接收端发送一个1字节的探测报文,对方会在此时回应自身的接收窗口大小,如果结果仍未0,则重设持续计时器,继续等待。
16位的窗口大小最大能表示65535,但是TCP的窗口大小最大并不是64K。在TCP首部中40个字节的选项中还包含了一个窗口扩大因子M,实际的窗口大小就是16位窗口字段的值左移M位。每移一位,扩大两倍
拥塞控制
慢开始 (cwnd < ssthresh)
拥塞避免 (cwnd > ssthresh)
快重传
快恢复
连接队列
区分
半连接队列
在三次握手的第一步,服务端收到客户端的syn消息后,会把相关信息放到半连接队列中,同时开启一个定时任务。如果超时还未收到ack则进行重传,重传次数由 tcp_synack_retries决定
全连接队列
第三步服务器端收到客户端的ack, 若此时全连接队列未满,则从半连接队列拿出相关信息放入到全连接对立中,否则按 tcp_abort_on_overflow指示执行
tcp_abort_on_overflow=0表示三次握手最后一步全连接队列满以后sever会丢掉client发过来的ack,服务端随后会进行 SYN+ACK
tcp_abort_on_overflow=1表示全连接队列满以后sever 端会直接发送 RST 给客户端
实战
1. 全连接队列溢出
1. 使用 wrk 压测工具发起大量连接
2.使用 ss 命令查看全连接队列数
3. 使用 netstat 查看被服务端丢弃的连接数
wrk -t 6 -c 10000 -d 60s http://192.168.0.20:8088
ss -lnt | grep :9090
netstat -s | grep overflowed
2. 半连接队列溢出
1. 使用 hping3工具发起 SYN 攻击
2. 使用netstat查看半连接队列大小
3. 使用netstat查看半连接队列溢出数
hping3 -S -p 8088 --flood 192.168.3.200
netstat -natp|grep SYN_RECV|wc -l
netstat -s |grep "SYNs to LISTEN"
TCP第一次握手时会被丢弃的三种条件
1. 若半连接队列已满,且未开启 tcp_syncookies 则会丢弃
2. 若全连接队列已满,且没有重传 SYN + ACK 包的连接请求多于 1 个,则丢弃
3. 如果没有开启 tcp_cookies ,并且 max_syn_backlog 减去 当前半连接队列长度 小于 (max_syn_backlog >> 2), 则丢弃
半连接队列已满,并不一定只能丢弃连接,可以开启syncookies功能就可以在不使用半连接队列的情况下建立连接:服务器根据当前状态计算出一个值,放在待发出的SYN+ACK报文中发出,当客户端返回 ACK报文时 取出该值验证,如果合法就认为连接建立成功
syncookies参数
0,关闭功能
1,仅当 半连接队列放不下时才会开启
2,无条件开启
防御SYN攻击
1. 增大半连接队列
调整 tcp_max_syn_backlog , somaxconn 及backlog
echo 1024 > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo 1024 > /proc/sys/net/core/somaxconn
nginx 配置:
server {
listen 8088 default backlog=1024;
}
2. 开启 tcp_syncookies
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
3. 减少 SYN+ACK重传次数
echo 1 > /proc/sys/net/ipv4/tcp_synack_retries
调整参数
调整全连接队列大小
TCP 全连接队列最大值取决于somaxconn 和 backlog 之间的最小值
somaxconn 是 Linux 内核的参数,默认值是 128,可以通过 /proc/sys/net/core/somaxconn 来设置其值;
backlog 是 listen (int sockfd, int backlog) 函数中的 backlog 大小,Nginx 默认值是 511,可以通过修改配置文件设置其长度;
调整半连接队列大小
TCP半连接队列大小取决与 max_sync_backlog,somaxconn 及 backlog
1. 当 max_sync_backlog > min(somaxconn, backlog) 时 半连接队列最大值= min(somaxconn, backlog) * 2
2. 当 max_sync_backlog < min(somaxconn, backlog) 时 半连接队列最大值= max_sync_backlog * 2
拥塞控制算法
基于丢包的拥塞控制
Reno
Cubic
基于时延的拥塞控制
Vegas
FastTCP
基于链路容量的拥塞控制
BBR
基于学习的拥塞控制
Remy
操作系统支持
0 条评论
下一页