TCP#2: 西厢记和西厢计划

2020-04-20 12:07:13 +08:00
 felix021

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 数据包,为了达到阻断通信的目的,需要利用协议的特性来实现。

== RST 大法 ==

看了上篇《 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

https://tools.ietf.org/html/rfc793

有些同学可能像我一样懒得读英文原文,所以翻译一下:

忘了上述状态含义的话,可以再回顾下这张状态流转图:

                              +---------+ ---------\      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 大法 ==

那么,如果我忽略 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

欢迎关注我的公众号

微信扫码

   ▄▄▄▄▄▄▄   ▄      ▄▄▄▄ ▄▄▄▄▄▄▄  
   █ ▄▄▄ █ ▄▀ ▄ ▀██▄ ▀█▄ █ ▄▄▄ █  
   █ ███ █  █  █  █▀▀▀█▀ █ ███ █  
   █▄▄▄▄▄█ ▄ █▀█ █▀█ ▄▀█ █▄▄▄▄▄█  
   ▄▄▄ ▄▄▄▄█  ▀▄█▀▀▀█ ▄█▄▄   ▄    
   ▄█▄▄▄▄▄▀▄▀▄██   ▀ ▄  █▀▄▄▀▄▄█  
   █ █▀▄▀▄▄▀▀█▄▀█▄▀█████▀█▀▀█ █▄  
    ▀▀  █▄██▄█▀  █ ▀█▀ ▀█▀ ▄▀▀▄█  
   █▀ ▀ ▄▄▄▄▄▄▀▄██  █ ▄████▀▀ █▄  
   ▄▀▄▄▄ ▄ ▀▀▄████▀█▀  ▀ █▄▄▄▀▄█  
   ▄▀▀██▄▄  █▀▄▀█▀▀ █▀ ▄▄▄██▀ ▀   
   ▄▄▄▄▄▄▄ █ █▀ ▀▀   ▄██ ▄ █▄▀██  
   █ ▄▄▄ █ █▄ ▀▄▀ ▀██  █▄▄▄█▄  ▀  
   █ ███ █ ▄ ███▀▀▀█▄ █▀▄ ██▄ ▀█  
   █▄▄▄▄▄█ ██ ▄█▀█  █ ▀██▄▄▄  █▄  

2327 次点击
所在节点    程序员
29 条回复
wysnylc
2020-04-20 12:25:41 +08:00
哦吼,天网觉醒
laoyur
2020-04-20 12:30:48 +08:00
二维码的实现有点意思
Mac
2020-04-20 12:34:14 +08:00
这二维码怎么生成的,能支持多少个内容字符?
felix021
2020-04-20 12:37:28 +08:00
@Mac @laoyur google 搜一下 qr code ascii 有几个提供在线生成的服务。

linux 下也有个 libqrencode3 可以搞这个,google-authenticator 就是用它来生成终端下的 qrcode 的。
tabris17
2020-04-20 12:43:16 +08:00
怎么记得几年前就见过这玩意儿?
tabris17
2020-04-20 12:44:13 +08:00
wocao,十年前的东西了
felix021
2020-04-20 12:44:54 +08:00
@tabris17 对,毕竟咱年纪大了。。
LGA1150
2020-04-20 12:50:52 +08:00
@felix021 https://github.com/LGA1150/netfilter-spooftcp
我实现了个纯内核态的客户端,现在还可以用……
felix021
2020-04-20 12:59:04 +08:00
@LGA1150 这个厉害了,膜拜大佬,回头我看看 paper
jomenxiao
2020-04-20 13:51:20 +08:00
猛然想起大学时光里,还折腾过的西厢计划
lzyliangzheyu
2020-04-20 13:56:28 +08:00
讲的生动形象
vtea
2020-04-20 14:15:52 +08:00
印象里 08,09 年那时候用过这个,后来不是凉凉了吗
vtea
2020-04-20 14:30:37 +08:00
@vtea 楼主,不好意思,看个标题就评论了,原来是科普帖
felix021
2020-04-20 14:46:29 +08:00
@vtea 嗯,我就瞎扯扯,这项目是凉了好久了……
felix021
2020-04-20 14:46:39 +08:00
@jomenxiao 暴露年龄了
misaka19000
2020-04-20 14:51:58 +08:00
@laoyur #2
@Mac #3

我也写过这种生成器,其实很简单

https://github.com/RitterHou/Lilith
lizheming
2020-04-20 15:04:48 +08:00
果然是 felix 大大,发的广告都这么硬核!赞一个~
sexoutsex2011
2020-04-20 15:28:28 +08:00
往事如烟了
est
2020-04-20 15:30:56 +08:00
这是老文章改皮之后又发出来骗流量的吧。
Xusually
2020-04-20 15:33:30 +08:00
看到标题中#2,以为西厢计划要出新一代了呢。。

满满的回忆啊。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/664211

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX