TCP#2: 西厢记和西厢计划
自那日听琴之后,多日不见莺莺,张生害了相思病,趁红娘探病之机会,托她捎信给莺莺,莺莺回信约张生月下相会。夜晚,小姐莺莺在后花园弹琴,张生听到琴声,攀上墙头一看,是莺莺在弹琴。急欲与小姐相见,便翻墙而入,莺莺见他翻墙而入,反怪他行为下流,发誓不再见他,致使张生病情愈发严重。
《西厢记》
上篇《 TCP:学得越多越不懂》发出来以后,有朋友很委婉地说:“如果能结合现实生产场景会有意义一点。”
经过深刻的反思,我决定虚心接受建议,写一点理论结合实践的内容。
曾经在猫扑和天涯冲浪的网虫应该都还记得,谷歌当时还是 Goooooogle,是可以直接访问的。
但是如果想搜索一些奇怪的词汇(比如███),一点击"手气不错",浏览器马上就会显示无法访问,并且这个现象会持续几分钟。
连接被重置
载入页面时到服务器的连接被重置
于是很多小伙伴就换到一个号称自己更懂中文的搜索引擎了。
(该爬虫当年有个广告拍得不错: https://v.qq.com/x/page/r0137s2op5j.html )
作为一个曾被新自由主义( Neoliberalism )洗脑的年轻人,我在寻找“自由”的路上发现了墙的存在,也知道了这是方校长的杰作。
但是墙到底是个什么样的存在呢?
我们的防火墙,其名源自《 The Great Firewall of China: How to Build and Control an Alternative Version of the Internet 》这本书。
虽然名字叫防火墙( Firewall,简称 FW ),但严格来说,(在早期)它其实是一个入侵检测系统( Instrusion Detection System,简称 IDS )。
和 FW 不同的是,IDS 是监听设备,不需要部署在链路中间,只要能把流量旁路引出供它分析即可。
通过旁路分析,IDS 可以在不影响现有流量的情况下部署(只要路由器 /交换机上有镜像端口即可),在 IDS 出现异常时(例如在流量高峰 IDS 设备性能不足时 )也不会导致网络中断。
曾经有人发现,在流量特别大的时候,墙的检测功能有时会失效,因此推测其是旁路引流进行分析的(符合 IDS 的特征)。
既然是旁路的,就无法直接 Drop 数据包,为了达到阻断通信的目的,需要利用协议的特性来实现。
看了上篇《 TCP:学得越多越不懂》的同学,对报文的控制位里的 RST 可能还有点印象,在遇到异常情况时,可用于通知对方重置连接(细节详见 RFC 793 ):
If the receiving TCP is in a non-synchronized state (i.e. SYN-SENT, SYN-RECEIVED), it returns to LISTEN on receiving an acceptable reset. If the TCP is in one of the synchronized states (ESTABLISHED, FIN-WAIT1, FIN-WAIT2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), it aborts the connection and informs its user
有些同学可能像我一样懒得读英文原文,所以翻译一下:
如果连接状态处于“非连接完成”状态(例如 SYN-SEND, SYN-RECEIVED ),当收到 reset 时会将状态返回 LISTEN ;
如果 TCP 状态是 ESTABLISHED, FIN-WAIT-1, ..., LAST-ACK, TIME-WAIT 其中之一时,放弃连接并通知用户。
忘了上述状态含义的话,可以再回顾下这张状态流转图:
+---------+ ---------\ active OPEN
| CLOSED | \ -----------
+---------+<---------\ \ create TCB
| ^ \ \ snd SYN
passive OPEN | | CLOSE \ \
------------ | | ---------- \ \
create TCB | | delete TCB \ \
V | \ \
+---------+ CLOSE | \
| LISTEN | ---------- | |
+---------+ delete TCB | |
rcv SYN | | SEND | |
----------- | | ------- | V
+---------+ snd SYN,ACK / \ snd SYN +---------+
| |<----------------- ------------------>| |
| SYN | rcv SYN | SYN |
| RCVD |<-----------------------------------------------| SENT |
| | snd ACK | |
| |------------------ -------------------| |
+---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+
| -------------- | | -----------
| x | | snd ACK
| V V
| CLOSE +---------+
| ------- | ESTAB |
| snd FIN +---------+
| CLOSE | | rcv FIN
V ------- | | -------
+---------+ snd FIN / \ snd ACK +---------+
| FIN |<----------------- ------------------>| CLOSE |
| WAIT-1 |------------------ | WAIT |
+---------+ rcv FIN \ +---------+
| rcv ACK of FIN ------- | CLOSE |
| -------------- snd ACK | ------- |
V x V snd FIN V
+---------+ +---------+ +---------+
|FINWAIT-2| | CLOSING | | LAST-ACK|
+---------+ +---------+ +---------+
| rcv ACK of FIN | rcv ACK of FIN |
| rcv FIN -------------- | Timeout=2MSL -------------- |
| ------- x V ------------ x V
\ snd ACK +---------+delete TCB +---------+
------------------------>|TIME WAIT|------------------>| CLOSED |
+---------+ +---------+
TCP Connection State Diagram
Figure 6.
(tcp 连接状态图,截取自 rfc 793)
这就是上篇里提到的“我们敬爱的防火墙很爱用它”的原因了:
当检测到“入侵行为”时(例如 HTTP 报文中出现了███)发送 RST,按照 RFC 793 规范的 TCP 协议栈实现,收到 RST 后就应当放弃本次连接。
于是你就在浏览器上看到连接被重置(reset)了。
那么,如果我忽略 RST 包,不就可以不被墙欺骗吗?
实际上,用 iptables 来实现这一点很简单:
$ iptables -A INPUT -p tcp --tcp-flags RST RST -j DROP
很不幸,方校长的团队对此的解决方法也非常简单,只要向双方都发送 RST 包就可以了。
当然如果在服务器一端也忽略 RST,就可以成功绕过墙的忽悠——据说剑桥大学有人实验验证过,确实可行。
可惜的是,用户通常没法控制服务器端忽略 RST,因此这个方法的实用价值不高。
但是这个思路为西厢计划做好了铺垫。
我看到这个项目的名字的时候 ,真佩服作者的脑洞。
了解这个计划的原理之后,就更佩服作者的脑洞了。
前面说到,墙是在检测到某个关键词的时候才会发送 RST 包。
为了检测关键词,它需要工作在应用层( HTTP 协议)。
而为了工作在应用层,它需要维护 TCP 连接的状态。
由于那时的设备性能比较弱(所以会出现高峰期检测失效的情况),为了提高吞吐量,方校长团队的方案是:实现一个简化的 TCP 栈。
RFC 793 规范中定义了很多有效性检测,例如检测序列号是否有效来过滤 old duplicates 等,以保证通信的可靠性。
但这不是墙的需求,因此可以去掉很多规则,从而提高分析性能。
那么,如果我可以欺骗墙,这个连接已经被关闭,那么后续该连接的包就会被墙认为是网络中滞留的无效包,绕过关键词检测。
具体该怎么办呢?
上篇提到了一个细节:
虽然 ISN=4000,但是发送方发送的第一个包,SEQ 是 4001 开始的,TCP 协议规定 SYN 需要占一个序号(虽然 SYN 并不是实际传输的数据),所以前面示意图中 ACK 的 seq 是 x+1 。同样,FIN 也会占用一个序号,这样可以保证 FIN 报文的重传和确认不会有歧义。
TCP:学得越多越不懂 https://mp.weixin.qq.com/s/xyPUEFUr_v9sSKKqlBkI7w
我们知道,在三次握手的最后一步,A 本应发送一个 ACK(seq=y+1)。
但如果这时候 A 发送了一个 FIN 呢?
B 收到以后,由于此时连接尚未建立,会直接忽略这个包。
而墙实现的 TCP 栈比较简陋,它认为 A 已经关闭了链接,因此 A 后续发送的包就不会再触发关键词检测。
但是注意,TCP 是双向的,虽然 A 主动关闭连接,但是 B 仍然可能有数据要发送(划重点:面试题“为什么 TCP 断开连接需要 4 次”的答案),因此还需要欺骗墙说在 B 这侧也终止链接了。
这又该怎么办呢?
显然我们不能让服务器直接发一个 FIN,否则这个连接就真完了。
幸运的是,RFC 793 给了一个“梯子”:
If the connection is in any non-synchronized state (LISTEN, SYN-SENT, SYN-RECEIVED), and the incoming segment acknowledges something not yet sent (the segment carries an unacceptable ACK), or ...(省略)..., a reset is sent.
Reset Generation, RFC 793 [Page 35]
翻译:如果连接处于“非连接完成”状态,收到一个无效的 ACK,应当发出一个 reset 。
如果 A 在三次握手的最后一步,没有按规范要求发送 ACK(seq=y+1),而是发送 ACK(seq=y),那么 B 在收到以后就会按照协议的要求回复一个 RST:
这时我们可以在 A 上用“反 RST 大法”,忽略服务端返回的 RST,这个连接就不受影响。
但是墙的 TCP 栈认为客户端会按照协议终止连接,于是就不再有必要检测服务端后续的报文了。
<delete>从此张生和崔莺莺过上了幸福的生活。</delete>
方校长的团队当然不会放任这种事情的发生,西厢计划没过多久就失效了。
随着技术的进步、性能的提升,现在墙似乎已经集成到了链路中、可以直接 DROP 数据包,不再需要 RST 大法了。
不过为了业务需要,企业可以向电信主管部门申请 VPN 用于正常的生产经营。
例如字节跳动,为了建设 21 世纪数字丝绸之路,通过技术出海,在 40 多个国家和地区排在应用商店总榜前列,包括韩国、印尼、马来西亚、俄罗斯、土耳其等“一带一路”沿线的主要国家。
如果你也想过上幸福的生活,不妨投个简历,一起为一带一路做贡献吧。
关于字节跳动面试的详情,可参考我之前写的《程序员面试指北:面试官视角》
https://mp.weixin.qq.com/s/Byvu-w7kyby-L7FBCE24Uw
网盟广告(穿山甲)-后端开发(上海) https://job.toutiao.com/s/sBAvKe
网盟广告(穿山甲)-后端开发(北京) https://job.toutiao.com/s/sBMyxk
网盟广告(穿山甲)-广告策略研发(上海) https://job.toutiao.com/s/sBDMAK
其他地区、其他职能线 https://job.toutiao.com/s/sB9Jqk
[1] “西厢计划”原理小解 https://blog.youxu.info/2010/03/14/west-chamber/
[2] 从 Linux 协议栈代码和 RFC 看西厢计划原理 https://blog.csdn.net/dog250/article/details/7246895
[3] RFC 793 - TRANSMISSION CONTROL PROTOCOL https://tools.ietf.org/html/rfc793
微信扫码
▄▄▄▄▄▄▄ ▄ ▄▄▄▄ ▄▄▄▄▄▄▄
█ ▄▄▄ █ ▄▀ ▄ ▀██▄ ▀█▄ █ ▄▄▄ █
█ ███ █ █ █ █▀▀▀█▀ █ ███ █
█▄▄▄▄▄█ ▄ █▀█ █▀█ ▄▀█ █▄▄▄▄▄█
▄▄▄ ▄▄▄▄█ ▀▄█▀▀▀█ ▄█▄▄ ▄
▄█▄▄▄▄▄▀▄▀▄██ ▀ ▄ █▀▄▄▀▄▄█
█ █▀▄▀▄▄▀▀█▄▀█▄▀█████▀█▀▀█ █▄
▀▀ █▄██▄█▀ █ ▀█▀ ▀█▀ ▄▀▀▄█
█▀ ▀ ▄▄▄▄▄▄▀▄██ █ ▄████▀▀ █▄
▄▀▄▄▄ ▄ ▀▀▄████▀█▀ ▀ █▄▄▄▀▄█
▄▀▀██▄▄ █▀▄▀█▀▀ █▀ ▄▄▄██▀ ▀
▄▄▄▄▄▄▄ █ █▀ ▀▀ ▄██ ▄ █▄▀██
█ ▄▄▄ █ █▄ ▀▄▀ ▀██ █▄▄▄█▄ ▀
█ ███ █ ▄ ███▀▀▀█▄ █▀▄ ██▄ ▀█
█▄▄▄▄▄█ ██ ▄█▀█ █ ▀██▄▄▄ █▄
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.