tcp的那些事儿

  • 126 浏览
文字内容
1. TCP的那些事儿 xiaorui.cc github.com/rfyiamcool
2. 1 tcp basic 2 tcp timer 3 tcp windows 4 tcp strategy 5 tcp other
3. 1 tcp basic
4. tcp vs udp 面向连接 面向无连接,快 不可靠 无序 udp 可靠的 tcp 校验和 丢包 包的序列号解决乱序、重复 无流量控制 面向报文且有边界 网络编程难度加大 超时重传、流量控制、拥塞控制 面向字节流 (没有固定的报文边界) 单播,多播,广播 全双工 socket 缓冲区满或者小造成的UDP报文丢弃
5. tcp frame
6. 四元组 client router nginx backend 通过四元组确认一条连接 !!! 源地址 源端⼝ ⽬的地址 目标端口
7. 三次握⼿ TCP 标志位 CLOSE syn seq = client_isn CLOSE LISTEN SYS_SEND ack ack = server_isn + 1 syn/ack ack syn+ack ack = client_isn + 1 seq = server_isn 序号 Seq SYS_RECV ESTABLISHED syn seq初始为随机值 ack时加一 ESTABLISHED 校验seq 建连时TCP状态变更
8. 三次握⼿ lost syn ? tcp_syn_retries lost syn/ack ? tcp_synack_retries lost ack ? client发完第三个ack就认为ESTABLISHED 或往syn_recv状态的连接发数据引发rst 或满足超时重传 或满足对因为tcp_synack_retries超时后closed的sokcet发送书引发rst
9. 四次挥⼿ TCP 标志位 ESTABLISHED fin ESTABLISHED fin CLOSED_WAIT FIN_WAIT_1 ack ack FIN_WAIT_2 fin LAST_ACK 四次握手的意义 TIME_WAIT ack CLOSE 连接的状态变更 CLOSE
10. 传输中 seq、ack号存在于TCP报⽂段的首部中 . seq是序号, ack是确认号, ⼤小均为4字节 . ack seq + len
11. 总过程 握手和挥手时 isn + 1 传输数据 isn + len
12. mtu & mss mtu size = default 1500 over 链路层 mss mtu - ip header - tcp header - tcp options ip header = 20byte tcp header = 20byte tcp options = 12byte over 数据传输层
13. mtu & mss 上层write写到socket写缓冲区 写到写缓冲区不代表已发送 由内核协议栈来具体接管 read 写缓冲区的数据被切分多个mtu发出去 packet 4k packet send buffer recv buffer packet … packet write 200k write 20 20 0-1460 4 ip头 tcp头 payload fcs 4k
14. fast tcp open 过程 通过三次握⼿获取的 (Fast Open Cookie) 下次重连可携带cookie和数据请求报⽂ net.ipv4.tcp_fastopen = 3 socket setsockopt with TCP_FASTOPEN
15. tcp timestamp 目的 两端往返的时延测量 rrtm 更加准确的RTT测量数据,尤其是有丢包时 序号回绕问题 paws 避免⾼速⽹络下迷途包序号回绕的问题 比如, tcp timestamp小于skb的ts-recent则丢弃 当⼀⽅不开启时,两⽅都将停用timestamps
16. 2 tcp timer
17. tcp ⼏个定时器 状态下重试及超时 连接定时器 syn-ack定时器 重传定时器 fin-wait1定时器 Persist定时器 fin-wait2定时器 心跳定时器 time-wait定时器 延迟ack定时器 last-ack定时器
18. 连接定时器 连接定时器 tcp_syn_retries = 6 120 s
19. 重传定时器 重传定时器 /proc/sys/net/ipv4/tcp_retries2 = 15 max interval 120s 总耗时约 15 min
20. persist定时器 主动探测零窗口, 规避丢失 “窗口通知” 引发死锁风险. 当接收端接收窗口为 0 时,发送端 A 此时不能再发送数据. 发送端此时开启 Persist 定时器,超时后发送一个特殊的报文给接 收端看对方窗口是否已经恢复. 探测时间间隔为5、6、12、24、48、60、60 秒 ...
21. ⼼跳定时器 sysctl -a net.ipv4.tcp_keepalive_time = 7200 空闲7200秒后才开启保活检测 net.ipv4.tcp_keepalive_intvl = 75 每次间隔 75s net.ipv4.tcp_keepalive_probes = 9 共尝试9次 最少需要 2 小时 11 分 15 秒 才可判定连接死亡 !
22. ⼼跳定时器 set keepalive in socket 长度为0的⼼跳包 查看下次的时间戳
23. fin-wait2 定时器 主动挥手端等待fin的时间 不会定时重发数据, 超时直接closed /proc/sys/net/ipv4/tcp_fin_timeout = 60
24. other syn/ack tcp_synack_retries = 5 fin-wait1 net.ipv4.tcp_orphan_retries = 8 last-ack 无配置,消逝时间跟time-wait相似,在一分钟左右
25. time-wait 哪一端 ? 通常来说, 谁挥手谁存在time-wait !!! rfc = 120s = 2min 目的 可靠的实现 TCP 全双工的连接终止 (处理最后 ACK 丢失的情况) 避免当前关闭连接与后续连接混淆 (让旧连接的包在网络中消逝) https://elixir.bootlin.com/linux/v3.10/source/include/net/tcp.h#L117
26. 可靠性关闭 规定 msl 为报文最长生命周期 2msl为来回 ! ⽹络引起的漫游回包
27. time-wait net.ipv4.tcp_tw_recycle = 1 server 不要开, 不要开,不要开 !!! wan ip nat环境有大问题 !!! linux 4.12 会移除该配置 !!! tcp_tw_reuse=1 安全选项 router 5:01 client-1 5:05 client-2 一秒后复用tw状态的端口 适当优化 tcp_max_tw_buckets client1时间晚于client2, client2 先于server 建联,client1会失败 !!! server 开启 tw_recycle 和 timestamp配置, 启用per-host的paws机制.
28. time-wait 根本问题在于 短连接 ? 连接池爆满 ? 应用协议异常 ? 超过idle timeout ? … 为什么有大量的time-wait !!! 为何跟 time-wait 过不去 ? socket占用缓冲区内存 ? 占用进程 RSS 内存 ? 占用local_range_port ? 无法申请地址 ?
29. time-wait error 服务器未打开tw_reuse redis pool maxidle = 50 maxactive = 300 cannot assign requested addr
30. 3 tcp optimize
31. 连接队列 server client syn queue bind() listen() 半连接队列 syn connect() 收到第⼀个syn syn_recv syn_sent syn + ack 回复syn + ack ack established established 全连接队列 psh 完成三次握⼿ write() established psh 并未accept取⾛ accept() read() accept queue
32. 连接队列 全连接队列 半连接队列 get min value tcp_max_syn_backlog (1024) 其实跟三个值都有关系 backlog、max_syn_backlog、somaxconn ( 内核代码有描述) listen backlog Nginx = 511 Redis = 511 somaxconn (128) accept原⼦消费且不惊群 还在全队列的连接可接收数据 超出则拒绝建连
33. 连接队列 tcp_abort_on_overflow equal 0 (default) 半连接队列满了 ? 忽略客户端syn, 服务端不做任何反应 客户端不断重试发syn, ⼀直到 tcp_syn_retries = 5 全连接队列满了 ? 忽略客户端ack, 服务端设立定时器发送syn/ack ⼀直到 tcp_synack_retries = 5 equal 1 retrun rst connection reset by peer & connection refused
34. close()下状态 孤⼉连接 为什么 netstat -pnat 看不到进程信息 ? fin-wait1 && fin-wait2 孤儿连接 shutdown ( SHUT_WR ) 允许半关闭状态下依然传输数据; 解决孤儿连接就可减少内存占用 发送fin但还可接收数据 ; tcp_max_orphans 连接处于 FIN_WAIT2 状态直到超时 或 收到fin ! 指定内核能接管的孤儿连接数目, 超过则rst . tcp_fin_timeout = 60 (fin-wait2) 指定孤儿连接在内核中生存的时间 . tcp_orphan_retries (fin-wait1) 当达到重试次数时,连接不会触发rst,而是closed . close 只要触发close系统调用;那么连接就跟该进程无关 ; 由内核托管维护,这就是孤儿进程.
35. syncookie syncookie 避免为一个新连接的syn分配tcb数据区,节省内存的消耗! 返回的 什么是 syn flood ? ack是靠特征实时推算! 可劲的发送syn包,但又不去接收ack ? 通过对称算法给一个连接的tcp五元组及其他信息组合生成一个 seq,client回应ack要seq + 1。 syn flood 目的 ? 消耗服务器的内存 空连接会使半连接队列爆满,拒绝其他正常连接
36. 小结-连接优化 策略 TCP内核参数 调整syn报文的重传次数 tcp_syn_retries 调整 syn 半连接队列 tcp_max_syn_backlog backlog somaxconn (没搞错 !!!) 调整 syn+ack 报文的重传次数 tcp_synack_retries 优化accept全队列队列 min(backlog, somaxconn) 绕过三次握手 tcp_fastopen 抵御半连接攻击 syncookie
37. 小结-挥⼿优化 策略 TCP内核参数 优化 FIN 重传次数 tcp_orphan_retries 优化 FIN_WAIT2 (只适用于close关闭) tcp_fin_timeout 调整孤儿连接个数 (close) tcp_max_orphans 调整time_wait状态的上线个数 tcp_max_tw_buckets 复用 time_wait 状态的连接 ( 连接发起端) tcp_tw_reuse, tcp_timestamp
38. 3 tcp strategy
39. 重传策略 超时重传 快速重传 (fast retransmit) 选择性确认 (sack) 重复选择确认 (d-sack)
40. 超时重传 什么时候重传 ? 数据包丢失 确认应答丢失 RTT ( Round-Trip Time 往返时延) 数据从⽹络⼀端到另⼀端所需的往返时间 RTO (Retransmission Timeout 超时重传时间) 超时重传时间 超时重传时间 RTO应该略⼤于报⽂往返 RTT rto 比 rtt 小 ? 频繁触发重传 rto 比 rtt ⼤的多 ? 重传间隔太慢
41. 快速重传 当收到三个相同的 ACK 报⽂时,会在定时器过期之前,重传丢失的报⽂段。 处理过程 第⼀份 Seq1 先送到了, 于是就 Ack 回 2; 结果 Seq2 因为某些原因没收到, Seq3 到达了, 于是还是 Ack 回 2; 后面的 Seq4 和 Seq5 都到了, 但还是 Ack 回 2, 因为 Seq2 还是没有收到; 发送端收到了三个 Ack = 2 的确认, 知道了 Seq2 还没有收到, 就会在定时 器过期之前, 重传丢失的 Seq2。 最后接收端收到了 Seq2, 此时因为 Seq3、Seq4、Seq5 都收到了,于是 Ack 回 Seq6
42. 选择重传 net.ipv4.tcp_sack = 1 (default) TCP头部选项字段增加 SACK, 最多可描述4个不连续数据段 接收端将已收到的seq发送给发送⽅,这样发送⽅就可以知 道哪些数据收到了,这样只重传丢失的数据. 当触发超时重传或快速重传时, 只会发送未ack的数据。
43. d-scak net.ipv4.tcp_dsack = 1 (default) 在sack基础上设立的机制 帮助发送⽅判断,是否发⽣了包失序、ACK 丢失、包重复或伪重传. 让 TCP 可以更好的做⽹络流控. ack丢失 ⽹络时延
44. 拥塞控制 慢启动 每收到⼀个ack, cwnd = cwnd +1 每⼀轮RTT, cwnd = cwnd x 2 拥塞避免 cwnd > ssthresh使用拥塞避免算法 每⼀轮RTT, cwnd = cwnd + 1 拥塞发⽣ 超时重传 threhold = cwnd/2 , cwnd = 1 进⼊慢启动阶段 快速重传 cwnd = cwnd /2, threhold = cwnd 进⼊快速恢复 快速恢复 cwnd = threhold + 3 MSS
45. 拥塞窗⼝ 发送端主动去控制流量 实际发送初始流量是 拥塞窗⼝和滑动的最小值 . linux3.0之前initcwnd为3 . linux3.0以后采取了Google的建议窗⼝调到10 . 查看 ss -nli grep cwnd tcpdump windows 设置 ip route change 10.0.0.0/8 via 10.1.1.1 dev bond0 initrwnd 30 /proc/sys/net/ipv4/tcp_initcwnd
46. 拥塞窗⼝ cwnd = 30 42340 ≈ 30 x 1460 cwnd = 10 14600 = 10 x 1460
47. ssr 建议禁用慢启动空闲重启 (ssr Slow-Start Restart) 在⼀段时间没有包传输,则会重新进⼊慢启动 . 这个常量在include/net/tcp.h中定义为1s (linux 3.10) net.ipv4.tcp_slow_start_after_idle = 0 (default 1)
48. 接收窗⼝ 作用 为了防止发送方无脑的发送数据, 导致接收方缓冲区被填满 利用接收窗口的特性达到流量控制 接收窗口是由接收方指定, 向发送方告知接收方当前TCP 缓冲区大小 应用读取缓冲区数据,窗口自然变大 应用程序一直不读取数据,则窗口逐步变小,一直到0. 窗口大小在握手期间协商, 后续ack报文时携带win窗口大小 零窗口通知与窗口探测
49. 滑动窗⼝ Sent and Acknowledged Send But Not Yet Acknowledged Not Sent,Recipient Ready to Receive Not Sent,Recipient Not Ready to Receive 古老的 send—wait--send
50. 滑动窗⼝ 目的是什么 ? 解决串行, 提高吞吐量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 已发送 已发送 待发送 未发送 已ACK 未ACK 未ACK 未ACK 15 (delete) 重传丢包 TCP的包可以分为四种状态 乱序 已发送并且已经确认的包 重复 已发送但是没有确认的包 未发送但是可以发送的包 不允许被发送的包
51. nagle Nagle目的 理论上提高网络利用率, 减少小包发送, 小包合并大包发送 ! 只发送一个字节的数据 Nagle定义 也要携带20byte的ip头及20byte的tcp头 ! 最多只能有一个未被确认的小包 ! linux默认开启nagle 只缓冲小包的数据,并排队小包 ! Nagle算法 如果发送内容大于1个MSS立即发送 如果当前没有包未被确认立即发送 如果之前有包未被确认, 缓存发送内容 如果收到ack, 立即发送缓存的内容 如何关闭nagle TCP_NODELAY golang net默认关闭nagle
52. delay ack 目的跟nagle相似, 提高带宽利用率. 什么时候发送ack? 当有多个ack报文发送时 有数据要发送携带ack 关闭delay ack 触发定时器 TCP_QUICKACK (快速ack) 需要针对每个socket fd设置 linux 40ms windows 200ms
53. 5 tcp other
54. ddos 1.1.1.1 1. syn with src 1.2 1. syn with src 1.3 server 2. syn/ack 3. retry syn/ack 3. rst 2. syn/ack 1.1.1.2
55. ddos defend anycast dns dns dns 1. 检测 异常检测系统 ⾼防ip ⾼防ip ⾼防ip anycast server server server router 安全 cdn 安全 cdn 安全 cdn anycast 3. 流量回注 2. 牵引 流量清洗系统 流量清洗系统 流量清洗系统
56. ddos 找警察叔叔 ? 😅 😔 😭 syn flood 就狂发 syn !!! 目的 防护cdn 分散攻击, ⽣抗流量 校验攻击特征, 策略防御 连接队列打满 流量清洗设备 ( tcp proxy) 节点流量打满 路由间隔性输出镜像流量做异常检测 恶意半连接占用内存 牵引流量清洗后并回注流量 防御设备辅助server建立连接
57. anycast anycast 寻路链路优化、负载均衡、线路故障 北京 1.1.1.1 面向同⼀个 ip 地址 分解攻击流量到各区域机房 bgp任意播需要运营商强⼒合作 常用于dns及cdn系统 !!! 上海 1.1.1.1 ⼴州 1.1.1.1 避免一个机房被打死 !!! server server server
58. 闲聊cc 分布式集群 cc 攻击 (应用层攻击) iptables policy nginx lua ⽆差异爆站 检验length post 流量型攻击 面向url和ip的限速限频 ⿊白名单 耗尽net、cpu、mem及io资源 lua 慢速请求攻击 ? token = hash(ip + ts + random) … set cookie and 302 by javascript 买高防 ! 买高防 ! 买高防 ! …
59. bbr google 出品的拥塞算法 在有⼀定丢包率的⽹络链路上充分利用带宽. 降低⽹络链路上的 buffer 占用率降低延迟. 适用场景, 存在⼀定丢包率的⾼延迟⽹络 bbr对丢包不敏感 基于bdp带宽延迟探测的拥塞控制 in linux kernal 4.9 cubic vs bbr
60. kcp 相比tcp 基于udp实现的快速可靠传输协议 !!! ⽆需三次握⼿和四次挥⼿ 更加激进贪婪, 牺牲⽹络的公平性 RTO不翻倍, 只做 1.5 倍 特点 真正的选择性重传 比 TCP浪费10%-20%的带宽的代价 快速重传 ( 收到2个重复ack ) 换取平均延迟降低 30%-40% 使用udp实现可靠性协议 且最⼤延迟降低三倍的传输效果 !!! github.com/skywind3000/kcp FEC (Reed-Solomon纠删码) 多路复用 …
61. when trigger tcp rst 向⼀个closed的端⼝发数据由对⽅内核回馈rst time-wait数量超过net.ipv4.tcp_max_tw_buckets 缓冲区数据未读完进⾏close() 开启linger模式, 通过rst关闭 关闭连接超过tcp_max_orphans 全队列已满, 另外tcp_abort_on_overflow=1时 对 last-ack 状态的连接发送请求 ⽹络迷包,客户端收到的第⼆次握⼿的ack序号不正确,则向服务端发起rst …… rst丢失怎么办 ??? ha… 发完rst就变为 closed 了
62. tcp rst socket rst errno connection reset by peer (errno = ECONNRESET) 收到对⽅的连接重置 broken pipe (errno = EPIPE) 往⼀个已收到rst的连接写数据 内核触发 sigpipe 信号, 默认⾏为是⼲掉进程, but … 多数⽹络框架劫持 sigpipe 信号⾏为, 上层只需控制异常 做好异常处理,没什么⽑病 !!!
63. kill tcp tcpkill (only active conn) libpcap监听并抓取当前的tcp seq 主动触发rst killcx Challenge ACK client随意发送⼀个syn server返回正确的seq 携带正确的seq发送rst Challenge ACK Linux 内核对于ESTABLISHED连接收到的乱序 SYN 报文,会 回复一个携带了正确序列号和确认号的 ACK 报文。
64. 常见的限制 连接端⼝限制 ? ⽂件描述符限制 (ulimit) 内存限制 (buffer/cache) other sysctl.conf 服务端 排除限制条件下,连接数要看tcp 4 元祖 client的ip数量最⼤为2的32次⽅, port为2的16次⽅ 所以单机理论是⼤约可到2的48次⽅ 客户端 客户端在四元组不冲突下,没有端口限制 !!! 排除限制条件下,连接数要看tcp 4 元祖 ip_local_port_range (default 32768 - 60999)
65. 连接端⼝限制 ? ip_local_port_range = 3w - 6w client ⼦ip 客户端在四元组不冲突下,没有端口限制 !!! 3w 3w 3w :80 :81 3w 3w 1.1.1.3:80 3w 1.1.1.4:80 :81 ⼦ip 1.1.1.1 1.1.1.2 1.1.1.5
66. when process crash ? 当进程退出后, 由操作系统内核来收尾 … FIN ⽆未读缓冲 init 0 (关机) 内核接管并主动发起 fin kill -15 pid 有未读缓冲 kill -9 pid 触发 rst OOM without recv-q linger 触发 rst
67. when server crash ? 为什么在curl时 … 有时会阻塞很久才失败 ? 有时立马就返回失败 ?
68. when router crash ? client router router client 如何感知连接已关闭 ? tcp的各状态超时 或 心跳 !!! server
69. netfilter client client lvs 跟⽹关层建立连接 iptables conntrack iptables netstat 看不到 client 和 server的连接信息 server server 跟client建立连接
70. debug 探测时延 ping (icmp) tcping (tcp handshark) mtr (经过的路由时延) ⽹络质量 iperf3 模拟⽹络故障 iptables tc
71. wireshark 统计 rst? 丢包率 ? 重传 ? 快速重传 ? 窗口变化 ?
72. debug ss 替代 netstat !!! tcpdump systemtap bcc
73. question ? A 向 B 发送5个包数据, 但后面包先到达是否影响socket读取 ? 使用tcp做消息的载体为何出现粘包 ? 为什么通常说多连接下载会更快 ? a把数据发了出去, 但 b 说收到 ? 如何确认某socket还有未读数据,如何确认进程已读取该消息 ? 为什么越来越多的厂商使用udp协议通信 ? …
74. Q&A - xiaorui.cc - github.com/rfyiamcool