V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
felix021
V2EX  ›  程序员

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

  •  
  •   felix021 ·
    felix021 · 2020-04-20 12:07:13 +08:00 · 2545 次点击
    这是一个创建于 1714 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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

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

    • 如果连接状态处于“非连接完成”状态(例如 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 大法 ==

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

    欢迎关注我的公众号

    微信扫码

       ▄▄▄▄▄▄▄   ▄      ▄▄▄▄ ▄▄▄▄▄▄▄  
       █ ▄▄▄ █ ▄▀ ▄ ▀██▄ ▀█▄ █ ▄▄▄ █  
       █ ███ █  █  █  █▀▀▀█▀ █ ███ █  
       █▄▄▄▄▄█ ▄ █▀█ █▀█ ▄▀█ █▄▄▄▄▄█  
       ▄▄▄ ▄▄▄▄█  ▀▄█▀▀▀█ ▄█▄▄   ▄    
       ▄█▄▄▄▄▄▀▄▀▄██   ▀ ▄  █▀▄▄▀▄▄█  
       █ █▀▄▀▄▄▀▀█▄▀█▄▀█████▀█▀▀█ █▄  
        ▀▀  █▄██▄█▀  █ ▀█▀ ▀█▀ ▄▀▀▄█  
       █▀ ▀ ▄▄▄▄▄▄▀▄██  █ ▄████▀▀ █▄  
       ▄▀▄▄▄ ▄ ▀▀▄████▀█▀  ▀ █▄▄▄▀▄█  
       ▄▀▀██▄▄  █▀▄▀█▀▀ █▀ ▄▄▄██▀ ▀   
       ▄▄▄▄▄▄▄ █ █▀ ▀▀   ▄██ ▄ █▄▀██  
       █ ▄▄▄ █ █▄ ▀▄▀ ▀██  █▄▄▄█▄  ▀  
       █ ███ █ ▄ ███▀▀▀█▄ █▀▄ ██▄ ▀█  
       █▄▄▄▄▄█ ██ ▄█▀█  █ ▀██▄▄▄  █▄  
    
    
    29 条回复    2020-04-20 20:11:04 +08:00
    wysnylc
        1
    wysnylc  
       2020-04-20 12:25:41 +08:00
    哦吼,天网觉醒
    laoyur
        2
    laoyur  
       2020-04-20 12:30:48 +08:00
    二维码的实现有点意思
    Mac
        3
    Mac  
       2020-04-20 12:34:14 +08:00
    这二维码怎么生成的,能支持多少个内容字符?
    felix021
        4
    felix021  
    OP
       2020-04-20 12:37:28 +08:00   ❤️ 1
    @Mac @laoyur google 搜一下 qr code ascii 有几个提供在线生成的服务。

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

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

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

    满满的回忆啊。
    mywaiting
        21
    mywaiting  
       2020-04-20 15:36:04 +08:00
    @LGA1150 为了你这个回复,我把整个招聘贴都收藏了.........

    回头测试一下
    felix021
        22
    felix021  
    OP
       2020-04-20 15:44:29 +08:00
    @Xusually 这么多年了炒这个冷饭确实是是有点中二了 哈哈
    aoyoo
        23
    aoyoo  
       2020-04-20 17:22:37 +08:00
    还以西厢有下一版本了呢. 说难听点, 你这不是骗人进来看帖么...
    felix021
        24
    felix021  
    OP
       2020-04-20 17:24:12 +08:00
    @aoyoo 我不知道西厢记竟然这么多人知道,我还以为挺小众的一个项目。。
    LGA1150
        25
    LGA1150  
       2020-04-20 17:36:45 +08:00
    @aoyoo INTANG 算是下一版本吧?
    aoyoo
        26
    aoyoo  
       2020-04-20 19:18:09 +08:00
    @felix021 程序员群体里知道的比例比一般人要大的多吧. V2EX 大把程序员...
    felix021
        27
    felix021  
    OP
       2020-04-20 19:29:49 +08:00
    @aoyoo 嗯,但是不知道的程序员也不少,这个项目也是有点老了
    littlewing
        28
    littlewing  
       2020-04-20 19:42:55 +08:00
    虽然是炒冷饭,这个方法现在也用不了了
    但是评论里又有几个人真的把这个方法搞明白了?把 TPC/IP 搞明白了?
    glasslion
        29
    glasslion  
       2020-04-20 20:11:04 +08:00
    @felix021 当时 twitter 中文圈上, 差不多人尽皆知
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   914 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 19:52 · PVG 03:52 · LAX 11:52 · JFK 14:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.