TCP 面试题(困难版)
Comment准备了一些关于 TCP 协议的题,如果都做对了就没有人比你更懂 TCP。
参考资料:RFC793 RFC1122 RFC2525 RFC5961 RFC9293
1、TCP 连接建立后,如果 peer 直接死掉了会怎么样
如果双方的 sequence 同步了,并且没有开 TCP_KEEPALIVE,并且没有后续操作,那就不会怎么样。
如果对方不能重启,后续的 send 就不断 TIMEOUT,最后达到最大重传次数,reset。
如果对方重启,后续的 send 在对端没有匹配的 socket,直接返回 RST。
2、TCP KEEPALIVE 是什么?
是 sequence - 1,长度应当为 1 的试探报文。发送一个旧的字节试探 peer 活性,不占用序列号,不影响已有消息传输。
3、哪些报文占用序列号?
SYN 和 FIN 标志都要占用 1 个序列号。text 占用与长度相当的序列号。
4、握手的目的是什么?
传统回答:确认己方和对方的传输能力。
我的回答:还能够同步双方的序列号。第一次握手告诉对方我的序列号是什么。第二次握手告诉对方序列号已获知,并同步己方序列号。第三次握手对方确认获知我的序列号。
5、如果一方在并非全部消息被接收的情况下,想要主动断开连接,应当怎么做?
分情况:1、kernel 底层未收到对方消息的 segment。此时上层应用已读取完内核缓冲区的数据。此时首先发送 FIN,不会再发送字节。但允许对方继续发送。此时如果对方发送,就进入下一个情况。
2、kernel 已收到对方消息的 segment 并放入缓冲区,但上层应用决定直接退出。此时发送 RST,不会再接收任何消息。对方收到之后,也不会再继续发送。
6、close(sockfd) 和 shutdown(sockfd, how) 有什么区别?
与上一题相关。close 在任何情况下都会断开连接,因为底层文件描述符也被关闭。如果恰好收取完所有缓冲数据,则进入 5.1。如果未收取完,则进入 5.2
shutdown 则有 SHUT_WR SHUT_RD SHUT_RDWR 选项。但是它不会关闭文件描述符。也就是说缓冲区将被保留。如果缓冲区里有东西,那么还有继续被上层读的可能。如果以 RDWR 方式关闭,则仅保留文件描述符,但与 close 无异。如果以 RD 方式关闭,则保留写端。如果以 WR 方式关闭,则发送 FIN(因为保留了读方式),这个方式与 TCP 的协议 close 是最接近的。
7、常用的 tcpdump 为什么总是看到校验码不对?
因为校验码计算可以被 offload 给网络设备。发送出去的消息在到达 kernel 后即被 TCP dump 捕获。此时校验并未计算。待发出时由网卡计算。
8、TCP SYN SENT 状态下,如果收到一个ACK号对不上己方 SYN 的 SYNACK,应当如何?
发送 RST。我们认为这是一次失败的连接。
9、TCP_ESTABLISHED 状态下,如果收到一个 SYN,且序列号不正确,应当如何?
保活,并发送 challenge ACK。首先善意假设是对端重启,那么 challenge 的 seq 与对端的新状态不匹配迫使对方发送 RST 彻底清除这个旧连接。如果对端没出问题,这是攻击者发送的虚假报文。如果 challenge 还是路由到对端,那么对端只会认为这是冗余 ACK,连接得以保活,不会因为恶意的 SYN 模拟攻击而断连。如果 challenge 路由到攻击者,那么这种攻击不是传输层能处理的。
如果序列号正确,其实也是这样干。
challenge ACK 其实就是普通的 ACK。只不过这个场景下触发对端裁决,所以叫做 challenge。
10、如果某一方以相同的序列号,发送了不同数据的 segment,应该怎么办?
未定义。由实现定义是否拒绝这种不安全的发送。实践上,在看到相同序列号之后就扔掉了,根本不会比较。
11、TCP 的关闭流程有很多很多状态。阐述它们的不同:
FIN-WAIT1 FIN-WAIT2 CLOSE-WAIT CLOSING LAST_ACK TIME-WAIT CLOSED
简而言之,是处在握手阶段的不同,以及双方消息序的交错导致的。假设有一对处于 ESTABLISHED 状态的连接两端。
- A 发送第一个 FIN-ACK 进入 FIN-WAIT1、
- 如果 B 收到,则发送 ACK 进入 CLOSE-WAIT
- 如果 A 再收到 ACK,进入 FIN-WAIT2
- 如果这时 B 关闭,发送出 FIN-ACK,则进入 LAST-ACK
- 如果 A 收到 FIN-ACK 则发送 ACK 进入 TIME-WAIT
- 如果 B 再收到 ACK 则直接关闭进入 CLOSED
- 如果 A 收到 FIN-ACK 则发送 ACK 进入 TIME-WAIT
- 如果这时 B 关闭,发送出 FIN-ACK,则进入 LAST-ACK
- 如果 A 再收到 ACK,进入 FIN-WAIT2
- 如果 B 没收到时刚好也 close 了,则发送 FIN-ACK 进入 FIN-WAIT1
- 如果此时 A 收到 B 的 FIN-ACK 则发送 ACK 进入 CLOSING
- 如果此时再收到 B 的 ACK 则进入 TIME-WAIT
- 如果此时 B 收到 A 的 FIN-ACK 则发送 ACK 进入 CLOSING
- 如果此时再收到 A 的 ACK 则进入 TIME-WAIT
- 如果此时 A 收到 B 的 FIN-ACK 则发送 ACK 进入 CLOSING
- 如果 B 收到,则发送 ACK 进入 CLOSE-WAIT
TIME-WAIT 统一 timeout 后进入 CLOSED。
本质上是 FIN-ACK-FIN-ACK 和 FIN-FIN-ACK-ACK 的区别。
12、TCP 一端调用 accept() 之前,连接建立了吗?
建立了。如果一端 listen 另一端 connect,不管这边是否 accept,连接都已经建立了,三次握手都会完成。accept 只决定了应用程序是否处理这个已经建立好的连接。
accept,本质上只是 BSD api 对协议的适配层而不是协议的一部分。
13、TCP 双方同时向对方发起连接,会发生什么?
如果时间上赶得巧的话,两边同时发送 SYN。再互相发送 SYN-ACK。虽然是奇怪的四次握手并且没有纯 ACK。但是连接正常建立。这是协议的一部分。