引言
我最讨厌给别人解决的一个问题就是,“我上不去网了,不知道为什么,帮我看下吧”。这个问题是如此的复杂,中间可能出问题的环节是如此的多,以至于我都是直接回答,“我也不懂”。特别是除了设置异常造成完全无法联网,还可能有些网站总是上不去。在富强民主文明和谐的社会主义 qiang 国,我们公民当然可以自由的浏览互联网上的合法内容。如果是某些极个别的网站连接总是出现问题,并且浏览器上写着您的连接已被重置的话,要么是网站充斥着违法内容,要么就是你的网络设置存在一定不恰当之处,因此从分析你的设置导致的网络环境异常开始,我希望这系列文章最后能让读者正常的使用网络,成为一个21世纪的人类。
该文可能部分内容具有很强的时效性,大部分试验都是2018年1月在几个不同的地点作出的,笔者不保证相关情况不发生变化。
2019.01.31 更新:从 2019.01.15开始,教育网部分 ipv6 的 ip 开始被墙,教育网内 ipv6 的无墙时代结束。具体被墙 ip 可以参考这个 issue。基本情况是大陆直连被分配的 googlevideo (youtube CDN) 的 ip 被墙,由于该 ip 无法通过修改 host 调整,使得无法再通过直连的方式收看 YouTube 视频 (但依旧可以看到 youtube 的网页)。至于 google 的 ip,由于只启用了少量 ip 的封禁,而没有部署 sni 嗅探阻断,暂时还可以通过指定正常 ip 的方式直连。因此下文的和教育网 ipv6 相关的内容,请酌情食用。
故障原理
声明:该节内容都是指的由于个人的错误配置造成的局部网络环境出现的小规模异常,不代表任何普遍情况和官方行为。
DNS 相关
如果错误的设置了 DNS 选项,那么您可能会遇到这小节的各种情况。
比如随便查询一下国外某知名视频网站的真实 IP。
$ dig @114.114.114.114 youtube.com
; <<>> DiG 9.8.3-P1 <<>> @114.114.114.114 youtube.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3869
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN A
;; ANSWER SECTION:
youtube.com. 68 IN A 203.98.7.65
;; Query time: 34 msec
;; SERVER: 114.114.114.114#53(114.114.114.114)
;; WHEN: Sun Jan 7 20:47:35 2018
;; MSG SIZE rcvd: 45
嗯,看起来不错,一下就拿到了 IP。将这个 203.98.7.65 Google 一下,如果没什么意外的话,排名较高的有一个条目叫做域名服务器缓存污染- 维基百科,自由的百科全书,这个 IP 赫然在列。所以你看,很多人都会和你一样对网络设置出现错误,导致这都专门有了一个维基词条,这种国内服务器由于一些暂时的原因或你的设置给出错误答案的行为就叫做 DNS 劫持。更直接一点如下,果然是 ping 都 ping 不通。当然这属于 IP 封锁,是另一个局部网络环境异常导致的了。
$ ping -c 5 203.98.7.65
PING 203.98.7.65 (203.98.7.65): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
--- 203.98.7.65 ping statistics ---
5 packets transmitted, 0 packets received, 100.0% packet loss
随便 whois 一下,发现这是个新西兰的IP,也许是 Google 在那边开了个化名分公司吧。
看来国内某些 DNS 服务器临时异常解析出错了,让我们用国外的 DNS 服务器再试一发。Google 自家的服务总不会不认识自家的 IP 吧。
$ dig @8.8.8.8 youtube.com
; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 youtube.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12346
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN A
;; ANSWER SECTION:
youtube.com. 196 IN A 203.98.7.65
;; Query time: 41 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Jan 7 21:05:59 2018
;; MSG SIZE rcvd: 45
WTF…这返回的 IP,看起来有点眼熟啊,难道 Google 这么浓眉大眼的家伙也叛变革命了?赶紧打开 wireshark 再来一盘。竟然有三个来自 8.8.8.8 的 DNS 回复,发出请求仅过了 0.0022s ,第一个回复就杀到了,就是 dig 显示的 203.98.7.65, 几乎同时,第二个是 8.7.198.45,吓得我赶紧 Google 一下,果然是上面一个 IP 同词条的兄弟。老夫掐指一算,这时间就算所有路由不做停留,也就走个 300km 的来回,难道我大 Google 有 DNS 服务器和我同城,还是网速比光速快,老夫才疏学浅,就不枉议了。最后一个回复在发出请求后 0.35s 后到达,这些资本主义的服务,就是没有社会主义快捷。不过收到的 IP 172.217.160.110,搞不好还真是 youtube 呢(不用去 ping 了,能 ping 通才怪)。总之,这种查询外国服务器收到一些机智的抢答的现象就是 DNS 污染。
一定是 tcp 直接查询安全性更高,赶紧来一发。
dig +tcp @8.8.8.8 youtube.com
;; communications error to 8.8.8.8#53: connection reset
这就很尴尬了,超纲了啊,TCP reset 是我们还没讲到的知识点啊。
如果我们查询 IPv6 地址呢,说干就干。
dig @8.8.8.8 youtube.com AAAA
; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 youtube.com AAAA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57834
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN AAAA
;; ANSWER SECTION:
youtube.com. 217 IN A 203.98.7.65
;; Query time: 43 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Jan 7 21:45:07 2018
;; MSG SIZE rcvd: 45
你说你给我返回 IPv4 地址就算了,这地址还那么熟悉还热乎着呢,这抢答我给0分。打开 wireshark 再来一次,和刚才结果差不多,第三个返回的结果才是个 IPv6 地址: 2404:6800:4008:802::200e。 Google 这个地址,你可能会看到一大堆 host 的项目,这才是正确答案。而且只要你用 IPv6 网络,直接 ping 通,现阶段网络的要点就是,IPv6 的 IP 没有任何封锁,这个下小节细谈。最喜感的是,即使请求的 IP 根本不是 DNS 服务器,甚至根本 ping 不通,但只要 IP 在境外,就会有好心人抢答。
$ dig +short @1.1.1.1 youtube.com AAAA
203.98.7.65
$ ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2047ms
利用这一点我们只要反复`dig @1.1.1.1 somedomain`,就可以筛查出哪些域名有 DNS 污染,比如下面,就说明了 baidu.com 没有污染(好像是废话)。
$ dig @1.1.1.1 baidu.com
; <<>> DiG 9.8.3-P1 <<>> @1.1.1.1 baidu.com
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached
同样的方式我们可以证明 google.com 无污染,但 www.google.com 有污染,这一现象还会在下面反复出现。比如国内 DNS 的劫持情况和 DNS over TCP 的阻断情况可以相互印证。其实dig @8.8.8.8
也可以直接排查 DNS 污染情况,只需要看 dig 的 query time 项就好了,一般在国内低于100ms的就是好心人的抢答。
继续测试 AAAA 记录查询情况,换回国内 DNS 试一下呢。
$ dig @114.114.114.114 youtube.com AAAA
; <<>> DiG 9.8.3-P1 <<>> @114.114.114.114 youtube.com AAAA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2598
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN AAAA
;; ANSWER SECTION:
youtube.com. 37 IN AAAA 2404:6800:4012::200e
;; Query time: 106 msec
;; SERVER: 114.114.114.114#53(114.114.114.114)
;; WHEN: Sun Jan 7 21:51:25 2018
;; MSG SIZE rcvd: 57
不管你信不信这个地址是对的。。。厉害了,所以刚才都说了是暂时局部的网络问题惹。当然并不是所有 DNS 服务器都这么幸运,比如。
$ dig @180.76.76.76 youtube.com AAAA
; <<>> DiG 9.8.3-P1 <<>> @180.76.76.76 youtube.com AAAA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 22161
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN AAAA
;; Query time: 69 msec
;; SERVER: 180.76.76.76#53(180.76.76.76)
;; WHEN: Sun Jan 7 21:59:58 2018
;; MSG SIZE rcvd: 29
这是什么意思,没回答,难道百度 DNS 不支持 IPv4v6 双栈么,测试一下。
$ dig +short @180.76.76.76 hdtv.neu6.edu.cn AAAA
2001:da8:9000::130
这就尴尬了,您老人家是认识 IPv6 的双栈 DNS 服务器啊,这种行为既不是 DNS 污染也不是劫持,我觉得这种现象可以叫做 DNS 白痴,假装一问三不知,给 IP 是不可能给 IP 的,这辈子都不会给,假的都不告诉你。还有一些更恶劣的,像这样
$ dig @223.5.5.5 youtube.com AAAA
; <<>> DiG 9.8.3-P1 <<>> @223.5.5.5 youtube.com AAAA
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached
你能相信,它还可以假装自己不是一台 DNS 服务器,我的天啊,你这假动作差点把我也骗了呢,害我等你结果等了十秒,但你为什么国内的 IPv6 网站就秒回了呢,一定是刚才网络不好。
$ dig +short @223.5.5.5 hdtv.neu6.edu.cn AAAA
2001:da8:9000::130
这种现象应该叫啥, DNS 装死?所以进入新时代,DNS 劫持和污染已经概括不全了,更高端的 DNS 装死和 DNS 白痴已经粉墨登场了。学术点的话,也许可以叫做 DNS 拒绝服务? 不过有趣的是,以上国内知名 DNS 服务器均可正确解析 google.com 的 IPv6 地址,同时请求境外 DNS 服务器 google.com 的 IPv6 地址时,似乎也未见抢答。但请求 www.google.com 时,就会境内 DNS 劫持加境外 DNS 污染伺候,可能只是错误网络配置里正则没写好吧。不过一旦你使用 Google,之后的连接都是 www. 开头的,仅仅 google.com 能获取正确的 IPv6 地址作用也不大。
最后我们看一下,直接请求 IPv6 地址的 DNS 服务器会发生啥。试一试传说中的我国首个公共 IPv6 DNS。
$ dig @240c::6666 youtube.com AAAA
; <<>> DiG 9.8.3-P1 <<>> @240c::6666 youtube.com AAAA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 28904
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;youtube.com. IN AAAA
;; Query time: 79 msec
;; SERVER: 240c::6666#53(240c::6666)
;; WHEN: Sun Jan 7 22:15:31 2018
;; MSG SIZE rcvd: 29
ANSWER 0,复习一下刚才的知识,这属于哪一类来着,没错,DNS 白痴,DNS 服务器表示您所请求的网站不存在,有没有很熟悉的感觉。在 AAAA 记录查询上,我们也碰到了 DNS 劫持这位老朋友,不信你看,返回的这 IP 维基词条榜上有名。
$ dig +short @240c::6666 facebook.com AAAA
200:2:f3b9:bb27::
那么国外的 DNS 走 IPv6 请求又会如何呢。
$ dig +short @2001:4860:4860::8888 youtube.com AAAA
2404:6800:4008:801::200e
$ dig +short @2001:4860:4860::8888 youtube.com
172.217.160.110
bingo!效果很好,无论是请求 A 记录还是 AAAA 记录都毫无干扰,抓包一看,没有人尝试抢答。
总之,所以我说网络环境很复杂嘛,你请求同一个网站,随便换个 DNS 服务器,拿到的 IP 都可能不一样,要想正确的上网确实有点魔幻,这就是为什么大多数人的网络设置都有一定程度问题的原因。那么怎么能确定到底某个 DNS 多靠谱呢,欢迎使用我写的小脚本,再也不用手动来检查 DNS 的正确性了。使用效果如下。
$ ./tcdns.sh -v
tcdns v0.0.1
author: refraction-ray
$ ./tcdns.sh -h
tcdns: DNS test tool. Test/Check your DNS server now.
Usage: ./tcdns.sh -s [DNS server ip] <options>
Options:
-4: check A record queries (default)
-6: check AAAA record queries (can set with -4 at the same time)
-t: use tcp connection for the DNS queries
-v: show the version info
-h: show the help info
$ ./tcdns.sh -s 106.14.152.170 -4 -6
Please wait for the results, it may take some time
The settings and score of the DNS server (max: 10 for A and 6 for AAAA):
A
10
The settings and score of the DNS server (max: 10 for A and 6 for AAAA):
AAAA
6
总结这小节的内容,DNS 出现故障的原因都找到了,可能的修复方案又有哪些呢。大体上是有 host 文件,自建 DNS 服务器,使用小众的正常 DNS 服务器,使用各种加密方案请求国外的 DNS 服务器,或者使用国外的 IPv6 地址的 DNS 服务器。相应的修复措施是下一部份的内容。不过你以为拿到正确的 IP 地址就结束了?这才刚刚开始。
IP 相关
IP 封禁这个词看起来很官方的样子,可千万别多想啊,绝大多数 IP 无法 ping 通,都是由于个人设置不当和局部网络小范围波动或者个别路由的原因。封禁只是对这一现象的比喻和描述,绝对没有任何别的意思,说到底,只要调整对了自己的网络设置,那么就可以避免该问题啦,这可不是什么全局性的官方行为。
我们继续用我们的老朋友 203.98.7.65,这个家伙可是很容易出现网络异常的。
$ sudo hping3 -0 -c 3 203.98.7.65
HPING 203.98.7.65 (eno1 203.98.7.65): raw IP mode set, 20 headers + 0 data bytes
--- 203.98.7.65 hping statistic ---
3 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
$ sudo hping3 -1 -c 3 203.98.7.65
HPING 203.98.7.65 (eno1 203.98.7.65): icmp mode set, 28 headers + 0 data bytes
--- 203.98.7.65 hping statistic ---
3 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
$ sudo hping3 -2 -c 3 203.98.7.65
HPING 203.98.7.65 (eno1 203.98.7.65): udp mode set, 28 headers + 0 data bytes
--- 203.98.7.65 hping statistic ---
3 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
$ sudo hping3 -c 3 203.98.7.65
HPING 203.98.7.65 (eno1 203.98.7.65): NO FLAGS are set, 40 headers + 0 data bytes
--- 203.98.7.65 hping statistic ---
3 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
$ sudo hping3 -S -c 3 203.98.7.65
HPING 203.98.7.65 (eno1 203.98.7.65): S set, 40 headers + 0 data bytes
--- 203.98.7.65 hping statistic ---
3 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
你看吧无论是 ICMP,UDP 还是 TCP,能够连上算我输。遇到这种情况,要么是对方服务器关机了,要么就是遇到所谓的 IP banning 了。吓得我赶紧 trace 一下,下面前九跳已经省略。第12跳之后已经是全 * 了,UDP 包都淹没在了太平洋里了大概。使用 TCP 包或 ICMP 包做 traceroute,结果基本相同。
$ sudo traceroute -d -q 1 203.98.7.65
traceroute to 203.98.7.65 (203.98.7.65), 30 hops max, 60 byte packets
10 101.4.117.150 (101.4.117.150) 39.767 ms
11 61.8.59.37 (61.8.59.37) 40.988 ms
12 xe0-3-1.gw1.hkg3.10026.telstraglobal.net (61.14.157.81) 40.678 ms
13 *
14 *
第11和12跳的 IP 就是迷,第10跳大体是 CERNET 的出口,后两跳查询不同的 IP 数据库可能会给出香港,新加坡,日本等多个位置。但从第10跳(BEIJING)到上面说的这些位置,没有一个可以在1ms左右的时间内完成 (事实上,经过多次尝试,有一些试验中11和10跳的时间间隔短至0.5ms),至多150km的物理距离,使得我相信第11和12跳离北京也远不到哪里去(应该就和出口路由在一起啦)。第12跳有一个域名,赶紧 whois 一发,咦,似乎是个袋鼠国的通讯公司注册的呢,我是信了。你看有的时候自己网络设置的小问题,可能会导致路由回复给你的超时包 IP 有些异常呢。不过这次试验至少部分说明 CERNET 里的路由没有一上来就对指定 IP 丢包,还是到出口附近才丢的。不过测试源离出口也太近了,很难讲如果在远一点会不会中间就有些路由开始动手动脚了。网络环境这么复杂,也得允许人家路由抽个风嘛。
总之,不同的运营商有不同的可能性,只要自己网络没设置正常,有些 IP 的包,总是会被丢包的,这可能大多数发生在地方的骨干路由,我猜的,这就可以有效缓解国际出口的数据处理压力。当然这些都是网络设置异常看到的假象,实际就是你的电脑可能中毒了才看到这些,调好设置,就没有 IP ping不通这码子事了呀。当然修复到 TCP 和 UDP 能通就可以,没有几个人关心 ICMP,管它 ping 不 ping 的通呢,能用就行,要啥自行车。
不过这病毒对于 IPv6 的 IP,好像没什么作用,比如下图。 而且这不是特例,对于原生 IPv6,和正确的 IPv6 服务器地址,我没有遇到过 IP 被封禁的实例(截止2018.01.08)。
$ ping6 -c 5 ipv6.google.com
PING ipv6.google.com(encrypted-tbn1.google.com) 56 data bytes
64 bytes from encrypted-tbn1.google.com: icmp_seq=1 ttl=41 time=182 ms
64 bytes from encrypted-tbn1.google.com: icmp_seq=2 ttl=41 time=188 ms
64 bytes from encrypted-tbn1.google.com: icmp_seq=3 ttl=41 time=184 ms
64 bytes from encrypted-tbn1.google.com: icmp_seq=4 ttl=41 time=154 ms
64 bytes from encrypted-tbn1.google.com: icmp_seq=5 ttl=41 time=159 ms
--- ipv6.google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 154.889/174.125/188.816/13.992 ms
那如果是 DNS 给出的错误的 IPv6 地址,会发生啥呢,正好上节我们收到一个 facebook 的假 IP,赶紧来一发。
$ mtr 200:2:f3b9:bb27::
Packets Pings
Host Loss% Snt Last Avg Best Wrst StDev
1. ???
2. ???
3. ???
4. ???
5. cernet2.net 0.0% 64 1.6 1.6 1.1 4.5 0.4
6. cernet2.net 0.0% 64 2.8 2.6 1.8 9.3 1.1
7. 2001:da8:1:501::1 87.1% 63 32.4 37.2 30.9 48.6 7.2
8. ???
那位第7跳教育网出口附近的老哥路由脾气很暴躁嘛。也是,这里再不丢包,让人家往哪里下一跳转发嘛。所以不管出于什么机制,假的 IPv6 IP 自然是不通的,这好像是废话。
综上, 就是 IP 封禁相关的网络异常。可能的解决方案有哪些呢。这个。。。除了各种加密方式在不同协议层把发包用隧道花式转发出去以外,就只能用 IPv6 了。
丢包相关
除了某些 IP 出现的完全丢包,由于个人的错误设置,导致 https 协议的数据包,也会出现较高的丢包率,这就是随缘丢包,其他使用加密协议的数据也难以幸免丢包。当然网络环境非常复杂,丢包还真是不好说是谁的锅,反正每个月总有那么几天有些路由心情不太好。随便试试。
$ mtr -p 443 -c 10 --tcp --report cn.nytimes.com
HOST: Loss% Snt Last Avg Best Wrst StDev
5.|-- cernet2.net 0.0% 10 1.3 1.3 1.0 2.0 0.0
6.|-- cernet2.net 0.0% 10 2.3 4.7 2.0 20.7 5.7
7.|-- 2001:da8:1:501::1 40.0% 10 1115. 920.1 33.2 3118. 1190.7
8.|-- cernet2.net 50.0% 10 7236. 7207. 7181. 7248. 32.6
9.|-- cernet2.net 0.0% 10 111.9 99.1 80.0 125.6 13.3
10.|-- cernet2.net 0.0% 9 3179. 928.1 126.0 3186. 1323.0
11.|-- amazon2-lacp-100g.hkix.ne 0.0% 9 148.5 141.0 126.3 164.0 13.5
12.|-- ??? 100.0 8 0.0 0.0 0.0 0.0 0.0
13.|-- 2400:6500:0:3::2 0.0% 8 152.5 140.4 120.9 159.1 15.8
14.|-- 2600:9000:eee::1c3 0.0% 3 157.7 155.7 149.6 159.8 5.3
15.|-- cn.nytimes.com 0.0% 3 201.2 201.3 201.2 201.4 0.0
$ sudo hping3 -S -c 10 -p 443 www.github.com
HPING www.github.com (eno1 192.30.253.113): S set, 40 headers + 0 data bytes
len=46 ip=192.30.253.113 ttl=42 DF id=0 sport=443 flags=SA seq=0 win=28720 rtt=239.8 ms
len=46 ip=192.30.253.113 ttl=42 DF id=0 sport=443 flags=SA seq=1 win=28720 rtt=235.8 ms
len=46 ip=192.30.253.113 ttl=41 DF id=0 sport=443 flags=SA seq=2 win=28720 rtt=239.7 ms
len=46 ip=192.30.253.113 ttl=41 DF id=0 sport=443 flags=SA seq=3 win=28720 rtt=239.6 ms
len=46 ip=192.30.253.113 ttl=41 DF id=0 sport=443 flags=SA seq=4 win=28720 rtt=235.5 ms
len=46 ip=192.30.253.113 ttl=41 DF id=0 sport=443 flags=SA seq=5 win=28720 rtt=235.5 ms
len=46 ip=192.30.253.113 ttl=44 DF id=0 sport=443 flags=SA seq=6 win=28720 rtt=367.4 ms
len=46 ip=192.30.253.113 ttl=44 DF id=0 sport=443 flags=SA seq=7 win=28720 rtt=359.3 ms
len=46 ip=192.30.253.113 ttl=41 DF id=0 sport=443 flags=SA seq=8 win=28720 rtt=243.2 ms
len=46 ip=192.30.253.113 ttl=41 DF id=0 sport=443 flags=SA seq=9 win=28720 rtt=235.1 ms
--- www.github.com hping statistic ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 235.1/263.1/367.4 ms
有一些出口处大佬路由看起来还是喜欢随意丢些包呢,当然也不排除是试验当时网络波动的影响。TCP 握手 client [SYN] 阶段倒是挺稳的,不过下一步就可能被 reset 我会乱说?
此外,由于个人的设置问题(数据包没有尊贵的付费会员标记,咳咳),运营商也会进行丢包,尤其是 UDP 包很可能成为被 QOS 的重灾区。丢包的解决方案?别逗了,加钱啊,世界加钱可及,开个企业专线还不是美滋滋。
端口相关
进行稍微严肃一点的网络通讯,为了维持和区分多条连接,在传输层协议就需要引入端口的概念(ICMP:嗯?)。比如每一条 TCP 连接由两个 IP 加两个端口号唯一确定。鉴于大多数服务的端口号较固定,那么也许不需要封 IP,而是把特定 IP 的特定端口堵住杀伤范围更精确一点。有些封端口的行为是不分 IP 的,比如说国内入户 IP 是无法通过 80 口提供服务的。另一种就是具体 IP 具体端口的封禁,常见于一大波不知道什么协议的加密流量从路由出口跑过后。鉴于出口路由大多脾气不太好,既然看不太懂,干脆不让你走,堵住口子算了。这时的状态,就是你换个端口(如果服务端是你能控制的话)还能用,不过随着又一大波奇怪流量跑过,你可能发现 IP 不太 ping 的通了。总之,所谓的封端口或是封 IP,都是对发往指定 IP 或指定端口或是指定 IP 的制定端口的包进行确定性丢包的行为。
TCP 相关
对于 TCP 这种更加精密的传输层协议,其连接协议的严谨也成为中间人阻断的弱点。因此如果网络配置不正确的话,那句您的链接已被重置将会显示在浏览器上,这就是最著名的 TCP RST。RST 包本来被设计在接收的包出现无效等问题时发送,暴力关闭这条 TCP 连接,而不需要像 FIN 一样四次挥手确认。这正给了中间人攻击的绝佳机会。有些不想看到的 TCP 连接被捕获时,中间路由冒充双方分别向链接的对方发送一个 TCP RST,搞定,连接断开,一切都好像没发生过。
比如浏览器访问 twitter.com,wireshark抓包结果如下
30 1.919397 my.ip 104.244.42.193 TCP 78 52709 → 443 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=801788372 TSecr=0 SACK_PERM=1
31 1.922474 104.244.42.193 my.ip TCP 60 443 → 52709 [RST, ACK] Seq=1 Ack=1 Win=2099 Len=0
32 1.922477 104.244.42.193 my.ip TCP 60 443 → 52709 [RST, ACK] Seq=1 Ack=1 Win=2099 Len=0
33 1.922478 104.244.42.193 my.ip TCP 60 443 → 52709 [RST, ACK] Seq=1 Ack=1 Win=2099 Len=0
IP 确实是 twitter 的 IP,而且这个 IP 是 ping 不通的,属于 IP 封禁那一类。但同时为了防止你苦苦等待连接建立,还有好心人主动发一个 TCP RST 三连给你,多善良,唯恐一个 RST 你收不到。之后你就能在浏览器看到连接被重置或是拒绝连接(因为在浏览器看来连 TCP 握手都没成功)。这往返时间差只有 3ms,还以为 twitter 在国内也有服务器呢。这个 RST 包 TTL 长的吓人,一看就非我族类。当然,如果你有耐心在你能控制的服务端也抓下包,看看那边 TCP RST 的 TTL,两边一算就知道某些故障点离你有几跳了,当然不排除向双方发的 RST 包初始 TTL 不同的可能性。不过前边随便一 trace,大概的故障位置已经比较清楚了。此外三连 RST 的 IP 头, identification 都是36945,这好像不讲规矩呢。说好的每发一次包就变一变呢,造假包连这个都懒得改了。
利用这个简单触发的 RST 可以做一些有趣的事情,除了按上一段服务器客户端两侧 RST 包的 TTL 值来分析故障位置,更精确的还可以利用 IP 包头 TTL 值的特性,通过巧妙的构造实现如下。
$ sudo scapy
Welcome to Scapy (3.0.0) using IPython 2.4.1
In [1]: response = sr1(IP(dst='104.244.42.129',ttl=11)/TCP(dport=443,sport=56742,flags="S",seq=0,window=65535))
Begin emission:
...Finished to send 1 packets.
*
Received 4 packets, got 1 answers, remaining 0 packets
In [2]: response.show()
###[ IP ]###
version= 4
ihl= 5
tos= 0x0
len= 40
id= 46790
flags= DF
frag= 0
ttl= 142
proto= tcp
chksum= 0x4bc4
src= 104.244.42.129
dst= my.ip
\options\
###[ TCP ]###
sport= https
dport= 56742
seq= 0
ack= 1
dataofs= 5
reserved= 0
flags= RA
window= 1511
chksum= 0xe041
urgptr= 0
options= {}
In [3]: response = sr1(IP(dst='104.244.42.129',ttl=10)/TCP(dport=443,sport=56742,flags="S",seq=0,window=65535))
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
In [4]: response.show()
###[ IP ]###
version= 4
ihl= 5
tos= 0xc0
len= 56
id= 23220
flags=
frag= 0
ttl= 246
proto= icmp
chksum= 0xa3a
src= 202.112.61.214
dst= my.ip
\options\
###[ ICMP ]###
type= time-exceeded
code= ttl-zero-during-transit
chksum= 0x159e
unused= 0
###[ IP in ICMP ]###
version= 4
ihl= 5
tos= 0x0
len= 40
id= 1
flags=
frag= 0
ttl= 1
proto= tcp
chksum= 0xcf8a
src= my.ip
dst= 104.244.42.129
\options\
###[ TCP in ICMP ]###
sport= 56742
dport= https
seq= 0
ack= 0
dataofs= None
reserved= 0
flags= S
window= 8192
chksum= None
urgptr= 0
options= {}
无比清晰,第11跳就是故障所在处,也就是 202.112.61.214 的下一个路由,当然这个 IP 本身可能就是故障的“屏风”,下一跳则是被该 IP “分光”拿到了对应的处理装置。事实上只需要 google 一下 202.112.61.214 这个 IP,就知道有多“知名”。mtr 追踪一下,也可以发现结果相吻合。
$ curl ipinfo.io/202.112.61.214
{
"ip": "202.112.61.214",
"city": "Beijing",
"region": "Beijing",
"country": "CN",
"loc": "39.9289,116.3883",
"org": "AS4538 China Education and Research Network Center"
}
不利用 scapy 构造,直接用现成的 hping 也能得到相同结果。
$ sudo hping3 -S -t 11 -c 2 -p 443 104.244.42.129
HPING 104.244.42.129 (eno1 104.244.42.129): S set, 40 headers + 0 data bytes
len=46 ip=104.244.42.129 ttl=143 DF id=53922 sport=443 flags=RA seq=0 win=913 rtt=3.9 ms
DUP! len=46 ip=104.244.42.129 ttl=143 DF id=53922 sport=443 flags=RA seq=0 win=913 rtt=3.9 ms
--- 104.244.42.129 hping statistic ---
1 packets transmitted, 2 packets received, -100% packet loss
round-trip min/avg/max = 3.9/3.9/3.9 ms
$ sudo hping3 -S -t 10 -c 2 -p 443 104.244.42.129
HPING 104.244.42.129 (eno1 104.244.42.129): S set, 40 headers + 0 data bytes
TTL 0 during transit from ip=202.112.61.214 name=UNKNOWN
len=46 ip=104.244.42.129 ttl=67 DF id=47905 sport=443 flags=RA seq=0 win=1437 rtt=21.8 ms
--- 104.244.42.129 hping statistic ---
1 packets transmitted, 2 packets received, -100% packet loss
round-trip min/avg/max = 21.8/21.8/21.8 ms
小试一下这台神秘路由,只是草草看一眼,大试就太作死了。当然有人愿意对故障点做大规模 syn flood 的话我也没意见。nmap 结果只节选一部分在下面。反正是 Cisco 的锅没错了,噗。
$ sudo nmap -O -sV -sC -v 202.112.61.214
Nmap scan report for 202.112.61.214
Host is up (0.018s latency).
Not shown: 992 closed ports
PORT STATE SERVICE VERSION
23/tcp open telnet Cisco IOS telnetd
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
445/tcp filtered microsoft-ds
593/tcp filtered http-rpc-epmap
1025/tcp filtered NFS-or-IIS
4444/tcp filtered krb524
6129/tcp filtered unknown
Aggressive OS guesses: Cisco Catalyst 2960, 3550, or 3560 switch (IOS 12.2) (99%), Cisco 3750 or 6500 switch (IOS 12.2) (97%), Cisco Catalyst 2960, 3560, or 3750 switch (IOS 12.2) (97%), Cisco Catalyst Express 500 or 520 switch (97%), Cisco 2950, 2960, 3000-series, 4510R, 6000-series, or 6500-series switch (IOS 12.1 or 12.2) (97%), Cisco Catalyst Blade Switch 3020 (IOS 12.2) (96%), Cisco 1812, 3640, or 3700 router (IOS 12.4) (95%), Cisco 800-series, 1801, 2000-series, 3800, 4000, or 7000-series router; or 1100 or 1242G WAP (IOS 12.2 - 12.4) (95%), Cisco uBR10012 broadband router (95%), Cisco Catalyst 3550 switch (IOS 12.2) (95%)
No exact OS matches for host (test conditions non-ideal).
类似的我们可以对最早提到的 DNS 污染进行类似的测试,将会有更有趣的结果出现。
In [2]: sr1(IP(dst="104.244.42.129",ttl=8)/UDP(dport=53,sport=52435)/DNS(rd=1,id=0x5f32,qd=DNSQR(qname="www.twitter.com"),qr=0))
Begin emission:
Finished to send 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
Out[2]: <IP version=4 ihl=5 tos=0x0 len=96 id=46863 flags= frag=0 ttl=249 proto=icmp chksum=0xd9bc src=101.4.117.21 dst=my.ip options=[] |<ICMP type=time-exceeded code=ttl-zero-during-transit chksum=0xdf55 unused=1114112 |<IPerror version=4 ihl=5 tos=0x0 len=61 id=1 flags= frag=0 ttl=1 proto=udp chksum=0xcf83 src=my.ip dst=104.244.42.129 options=[] |<UDPerror sport=52435 dport=domain len=41 chksum=0x9fb0 |<DNS id=24370 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=<DNSQR qname='www.twitter.com.' qtype=A qclass=IN |> an=None ns=None ar=None |<Padding load='\x00\x00\x00\x00\x00\x00\x00' |>>>>>>
In [3]: sr1(IP(dst="104.244.42.129",ttl=9)/UDP(dport=53,sport=52435)/DNS(rd=1,id=0x5f32,qd=DNSQR(qname="www.twitter.com"),qr=0))
Begin emission:
..Finished to send 1 packets.
.*
Received 4 packets, got 1 answers, remaining 0 packets
Out[3]: <IP version=4 ihl=5 tos=0x0 len=77 id=33622 flags=DF frag=0 ttl=69 proto=udp chksum=0xc81d src=104.244.42.129 dst=my.ip options=[] |<UDP sport=domain dport=52435 len=57 chksum=0x1a21 |<DNS id=24370 qr=1 opcode=QUERY aa=0 tc=0 rd=1 ra=1 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=<DNSQR qname='www.twitter.com.' qtype=A qclass=IN |> an=<DNSRR rrname='www.twitter.com.' type=A rclass=IN ttl=177 rdata='74.86.226.234' |> ns=None ar=None |>>>
上边 ttl 为9的 DNS 查询就能收到 DNS 污染的结果,那个 74.86.226.234 就是最近升级的 DNS 污染系统的成果。它对应 SoftLayer 公司,返回 IP 不再只是以前可以有维基列表那种少数固定错误 IP 的方式,这就极大增加了验证 DNS 结果是否正确的难度。为了对比故障的位置,我们构造的 DNS 查询,依旧用了刚才 twitter 的地址而非真实的 DNS 服务器,这丝毫不影响有好心人告诉我们结果。最有趣的部分在于 DNS 污染的故障点要比 TCP RST 的故障点提前,这些机制并没部署在同一台或同一战线深度的路由上,这可能是为了分摊不通路由的处理压力。结合一轮完整的 traceroute,我们就可以清晰的看到各个故障点的位置了。
$ traceroute -q 10 104.244.42.129
traceroute to 104.244.42.129 (104.244.42.129), 64 hops max, 52 byte packets
6 202.112.38.9 (202.112.38.9) 2.823 ms 2.532 ms 3.231 ms 2.702 ms 1.983 ms 2.917 ms 3.115 ms 2.896 ms 2.706 ms 2.063 ms
7 101.4.112.198 (101.4.112.198) 1.692 ms 1.397 ms 2.243 ms 1.499 ms 2.145 ms 1.397 ms 1.318 ms 1.280 ms 1.317 ms 1.297 ms
8 101.4.117.173 (101.4.117.173) 2.665 ms
101.4.117.162 (101.4.117.162) 3.082 ms
101.4.113.110 (101.4.113.110) 1.601 ms
101.4.117.173 (101.4.117.173) 5.867 ms 2.513 ms 4.483 ms
101.4.117.162 (101.4.117.162) 3.811 ms 2.611 ms
101.4.117.173 (101.4.117.173) 3.577 ms
101.4.117.162 (101.4.117.162) 4.132 ms
9 101.4.118.122 (101.4.118.122) 12.460 ms 4.030 ms
101.4.117.254 (101.4.117.254) 5.646 ms
101.4.115.10 (101.4.115.10) 3.174 ms
101.4.118.122 (101.4.118.122) 5.877 ms 4.279 ms 3.787 ms
101.4.117.254 (101.4.117.254) 5.299 ms
101.4.118.122 (101.4.118.122) 7.161 ms 2.700 ms
10 101.4.118.122 (101.4.118.122) 4.080 ms
101.4.117.201 (101.4.117.201) 6.106 ms 5.161 ms 4.015 ms 4.173 ms 3.486 ms 4.463 ms 3.821 ms 3.717 ms 4.131 ms
11 202.112.61.214 (202.112.61.214) 1.503 ms
101.4.117.201 (101.4.117.201) 2.240 ms
202.112.61.214 (202.112.61.214) 1.823 ms 3.093 ms 1.783 ms 1.679 ms
101.4.117.201 (101.4.117.201) 4.938 ms 2.505 ms
202.112.61.214 (202.112.61.214) 1.622 ms 1.636 ms
12 * 202.112.61.214 (202.112.61.214) 1.969 ms * 1.840 ms * * * 32.574 ms * *
13 * * * * * * *^C
对应于 TCP RST 机制的故障点,如刚才所述,在12跳(202.112.61.214)背后,这应该也是实现 IP 封锁,进行 IP 丢包的实际位置。而进行 DNS 污染抢答的故障点则在第9跳处,明显更靠前,也许说明地方级的骨干路由就存在 DNS 污染的局部故障。同时注意到第8,9跳处,路由选择格外丰富,也许是需要额外处理些什么,所以加了些路由做做负载均衡。
回到 RST 的话题,并不是所有网站都能享受到推特的待遇。更多的网站可能好心人就忘了给你发 TCP RST,但 IP 已经是封锁的,效果就如同访问 onedrive.live.com 那样,浏览器漫长的等待后,告诉你响应时间过长,ERR_CONNECTION_TIMED_OUT,然后就结束了。还不如加上 TCP RST 三连套餐呢。浪费时间,三连套餐3ms就解决战斗了,这个等了我好几秒,影响用户体验哟。
TCP RST 有什么调整余地和绕过办法呢,如果服务端自己能控制,当然随便魔改下,强行两边无视 RST 包,或是像某些方案一样,用一些独特的发包顺序或发包内容,有可能触及网络故障点的知识盲区,从而顺利连接。当然这些方案都比较脆弱,可能只是帮助人家报修 bug 呢。
判定机制
前面我们已经总结了影响正常上网可能的故障类型,这部分我们就来讨论,到底是什么会触发这些故障。或者说这些故障是用什么机制诱导出的效果。
手动黑名单
这个是最常见,也是最简单粗暴的机制,由于自己网络设置的偏差,可能导致不知不觉产生了一份黑名单,并且越来越长。针对黑名单上的域名给予 DNS 污染和劫持,针对黑名单上的 IP 进行丢包封锁,还有些有贵宾待遇的 IP 丢包不过瘾还附赠 TCP RST 三连。简单有效,就解释了90%网络故障的原因。进入黑名单的原因?额,都说了是你自己设置失误,可能是你认为不该看到的网站和总有未知流量进出的服务器吧。该名单可能有人工在维护,且服务器有几个月后被解封的可能性。绕过方法:用橡皮擦掉名单上的 IP。
HTTP 明文审查
仅仅两三年前,互联网上大部分还是 http 流量,而没有 tls 层进行加密保护。这就把互联网通信的所有内容以明文的形式暴露给了所有能拦截 TCP 包的人,当然路由本来就用来转发数据包,拿到全部内容毫无压力。于是才有了 Google 搜索胡萝卜会断开链接这种古老传说。这种方式一旦在 http 内容中嗅探到敏感词,将会直接发 RST到两边,强制断开链接。不过由于现在国外大网站基本都是全站强制 https 协议,这种古老的方式也要退出历史舞台了。
虽然古老,但我们仍旧可以找一些不强制使用 http 的网站,对这一触发机制测试一下。比如访问 http://www.hping.org。如果我们请求的 URL 是 /some_regular_thing,网站会显示 404,表明该服务器上没有对应地址的内容,不过如果请求的 URL 是 /some_sensitive_words,浏览器会报错,著名的 ERR_CONNECTION_RESET,表明连接已经收到了 RST。打开 wireshark,可以发现建立 TCP 链接三次握手成功后,发出的明文 GET /sensitive_urls 之后,就会在2ms后收到若干 RST 包,似乎是两类 RST 包(细节不同),也许是触发了不同的判别机制造成的。当访问 http://www.he.net 时,同样的 URL 就不会收到 RST。原因在于 he.net 是有 AAAA 记录的,也就是可以通过 IPv6 访问。也就是说如同 IPv6 没有部署 IP 丢包一样,其同样没有部署明文敏感词的审查。
绕过方法:尽可能使用 https,使用非文本如图片等方式传递信息。
SNI 嗅探
开启 https 之后,所有内容甚至包括访问的 URL 路径都会被加密。不过由于要解决一台服务器一个 IP 地址一个端口上同时实现多个网站服务的问题,当时的 TLS 规范加入了服务器名称指示 SNI 的设定。SNI 通过让客户端发送虚拟域名的名称作为TLS协商的一部分来解决同端口多个合法 https 网站的问题。但问题就出在这个 SNI 信息是在 client hello 时发送的,此时 TLS 连接还没协商 (很正常,因为 SNI 就是用来获取对应的合法证书的,这之前当然还没开始加密),因此信息是明文的。这会发生什么,请看下面浏览器访问 linux.dropbox.com 时的情况。
23 2.007714 my.ip 52.85.83.246 TCP 78 56694 → 443 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=813985776 TSecr=0 SACK_PERM=1
29 2.209486 52.85.83.246 my.ip TCP 74 443 → 56694 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=209483103 TSecr=813985776 WS=256
30 2.209561 my.ip 52.85.83.246 TCP 66 56694 → 443 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=813985975 TSecr=209483103
31 2.209751 my.ip 52.85.83.246 TLSv1 271 Client Hello
32 2.212692 52.85.83.246 my.ip TCP 60 443 → 56694 [RST, ACK] Seq=1 Ack=206 Win=760320 Len=0
33 2.212696 52.85.83.246 my.ip TCP 60 443 → 56694 [RST, ACK] Seq=1 Ack=206 Win=760320 Len=0
34 2.212698 52.85.83.246 my.ip TCP 60 443 → 56694 [RST, ACK] Seq=1 Ack=206 Win=760320 Len=0
40 2.411968 52.85.83.246 my.ip TCP 66 [TCP Window Update] 443 → 56694 [ACK] Seq=1 Ack=206 Win=30208 Len=0 TSval=209483123 TSecr=813985975
41 2.412026 my.ip 52.85.83.246 TCP 54 56694 → 443 [RST] Seq=206 Win=0 Len=0
anyway,首先我们得到 linux.dropbox.com 的真实 IP,52.85.83.246 。幸运的是,这个 IP 没有在手工黑名单里,是个漏网之鱼,因此 IP 并没有被封禁。很好,然后通过23,29,30这三步,我们成功和 dropbox 完成了 TCP 连接的三次握手,一切都很顺利。TCP 连接建立后,下一步就是建立应用层的 https 连接。第一步就是传说中携带了 SNI 信息的 client hello,然后。。。就一切都结束了,恭喜你在仅仅不到3ms之后,收到了来自同城的 RST 三连,连接断开。而我们与 dropbox 的正常通信需要的往返时间则应该是23与29之间的时间间隔,即200ms左右。果不其然在 client hello 结束后200ms左右我们才收到可怜的 dropbox 的真实回复(40),而此时客户端的连接已经收到 RST 并断开了,自然认为这个 Ack=206 的 TCP 包没头没尾,因此果断发出了 RST 断开这个无效链接(41)。
问题的关键就出在 TLS 第一步的 client hello,我们好好的连接就是在这个包后收到了 RST 三连大礼。那么这个 client hello 长什么样呢。将这个原始的包以 printable text 打印出来。
h46;mE@@oU4USv">:
0p|u_p nagr@R;}D^@;R+/,0/5
linux.dropbox.com#
h2http/1.1uP
没错,这个 SNI 也就是访问的域名直接明文传递了,而 dropbox 这个词将自动触发 TCP RST。
好消息是这个 SNI 嗅探从而 RST 的机制似乎只适用于 google,facebook,twitter,dropbox,onedrive 等“少数”几个网站。这一方式,直接阻断了用正确 IP 访问这些网站的可能(比如修改 host),因为无论如何都需要先建立 https 连接,而 SNI 就在这一过程中直接暴露了。
这时我们想起了最早在 DNS 部分提到的例子,强制 TCP 查询 8.8.8.8 的话,会收到 TCP reset,我们抓包看一下这个过程。
130 1.485454 my.ip 8.8.8.8 TCP 78 57667 → 53 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=819237576 TSecr=0 SACK_PERM=1
139 1.651991 8.8.8.8 my.ip TCP 74 53 → 57667 [SYN, ACK] Seq=0 Ack=1 Win=42408 Len=0 MSS=1380 SACK_PERM=1 TSval=972367305 TSecr=819237576 WS=256
140 1.652106 my.ip 8.8.8.8 TCP 66 57667 → 53 [ACK] Seq=1 Ack=1 Win=131328 Len=0 TSval=819237743 TSecr=972367305
141 1.652191 my.ip 8.8.8.8 DNS 97 Standard query 0x649e A youtube.com
142 1.656662 8.8.8.8 my.ip TCP 54 53 → 57667 [RST, ACK] Seq=1 Ack=32 Win=654592 Len=0
143 1.656662 8.8.8.8 my.ip TCP 54 53 → 57667 [RST, ACK] Seq=1 Ack=32 Win=654592 Len=0
144 1.656662 8.8.8.8 my.ip TCP 54 53 → 57667 [RST, ACK] Seq=1 Ack=32 Win=654592 Len=0
161 1.821522 8.8.8.8 my.ip TCP 66 [TCP Window Update] 53 → 57667 [ACK] Seq=1 Ack=32 Win=42496 Len=0 TSval=972367473 TSecr=819237743
162 1.821576 my.ip 8.8.8.8 TCP 54 57667 → 53 [RST] Seq=32 Win=0 Len=0
可以发现抓包情况和 SNI 嗅探阻断极其相似,也是先和 8.8.8.8 顺利建立了 TCP 连接(130,139,140)。之后141 像往常一样客户端从 TCP 发出了 DNS query,询问 youtube.com 的地址,然后,3ms后 RST 三连大礼包如约而至,滞后的 8.8.8.8 的正常回复无力回天,因为连接已经直接 RST 掉了。这里唯一的不同是,我们发的不是建立 TLS 连接的 client hello,而是 DNS query,共同点是这里面也有 youtube.com 的明文,原始包如下。
Kk*Y46;mESd@@TehC5u=t7@
0o9%dyoutubecom
所以与其说是单独的 SNI 阻断,还不如说是 TCP 连接的任何明文地方嗅探到了域名关键词,比如 youtube,那就直接发送 RST,而不管是在 client hello 里还是 DNS query 里。
为了进一步证实这个猜想,看如下试验,回复正常。很明显,跨境 DNS TCP 查询被 RST 并不是因为查询的是 8.8.8.8,而是因为查询的内容里有了嗅探关键字 youtube 而已。
$ dig +tcp +short @8.8.8.8 baidu.com
123.125.114.144
220.181.57.217
111.13.101.208
因此通过跨境 DNS TCP 查询这一方式,我们可以快速摸清现在可以触发 TCP RST 的关键字和规则。其中 www.google.com,twitter.com, facebook.com, onedrive.live.com,dropbox.com 均是关键字,但巧合的是,google.com 并不是关键字,和前述 DNS 劫持与污染的相关行为一致。初步推测可能是由于谷歌翻译和地图等也在用这个域名,所以没有从该级域名一网打尽。这一关键字嗅探系统基本只嗅探敏感域名并进行 RST,和上面对 http 内容审查的关键字系统不同。一个证据就是你可以dig +tcp @8.8.8.8 敏感词
,可以顺利得到返回结果,不会被重置。此外我测试的触发关键词域名还包括:一些涉及成年内容或邪教内容的网站,tumblr.com,disqus.com,nytimes.com,medium.com,instagram.com,shadowsocks.org,zh.wikipedia.org 等。
需要注意的是,这份 TCP RST 域名嗅探名单,和通过dig @1.1.1.1 domian
获得的 DNS 污染名单高度一致,有理由怀疑这就是一份 list。可能做 TCP RST 时,直接偷懒把以前 DNS 投毒的名单拿来用了。为此我又写了一个验证 DNS over TCP RST 和 DNS poison 名单是否完全一致的脚本,通过读取海量的 GFWlist 域名名单,进行一一测试,可以非常确定的说,这两种机制的名单完全吻合。详情请见我的 gist.
最后,注意以下例子,懂正则表达式的各位自然很容易想象该嗅探的真实匹配规则。
$ dig +tcp +short @8.8.8.8 twitteroo.com
184.168.221.104
$ dig +tcp +short @8.8.8.8 oo.twitter.com
;; communications error to 8.8.8.8#53: connection reset
$ dig +tcp +short @8.8.8.8 ootwitter.com
;; communications error to 8.8.8.8#53: connection reset
$ dig +tcp +short @8.8.8.8 twitter.comoo
# no answer due to wrong domain but no tcp rst here
绕过方法:等待 TLS 协议规范的升级,据悉 TLS 1.3 将在较新的 draft 里实现 SNI 的加密传递。就像 https 的普及使得明文审查成为历史一样,相信新版本 TLS 协议的制定和普及,将使得 SNI 嗅探这种方式成为历史。当然,SNI 嗅探的实施历史很短暂,由于众所周知的原因,大概在17年10月之后才启用。对于 DNS query over TCP 里的嗅探,换别的 DNS 解决方案就好了。最后这一嗅探 IPv6 同样不受影响。也就是说 IPv6 网络除了 DNS 动过手脚以外,其他均未部署(至少教育网截止2018.1)。
特征识别
既然网络这么容易出故障,聪明的人们当然就会想出一些方式绕过故障。比如虚拟专用网络或是网络代理甚至 ssh 隧道。不管哪种方式,运行在网络的哪一层。最基本的思想都一样。怎么绕过故障,那就先把信息发给外面某个没被盯上的服务器,通过这个服务器转发客户端请求,并送回该中转服务器接收的内容。这样我们和国外的交流就集中在和这个历史清白的 IP 之间,那些容易出问题的网站我们都用这个中转服务器转发,这岂不是一劳永逸。
不过流量总是有特征的,为了锻炼大家的脑力,怎么能这么简单就一劳永逸呢,于是各种花式特征识别就出炉了。简单的一类,基本思想和上一节的 SNI 嗅探差不多。也即我们和中转服务器建立连接时,可能会有一些明显的特征,比如特定端口,尤其是握手的时候,又可能暴露证书或更多流量特征,这就有了出故障的机会。一旦发现这些特征,如 OpenVPN 的握手,如果是 TCP,就先 TCP RST 伺候。还不管用的先堵端口,如果换了端口继续干,那就祭出封 IP,至此,游戏结束。可以和 VPS 供应商换 IP 去了。
更复杂的一些代理协议,可能没有那么明显而清晰的特征判定,这时候深度包检测技术就登场了。通过流量,包长分布等特征,甚至结合机器学习的训练,一段时间之后,还是有可能积累足够多的信息来判定这些流量可能是代理流量。这种类型的隧道不会被秒封,不过通常是用过一段时间后,经常断断续续,最后发现端口或 IP 还是失联了。
可能有些协议,专门为解决这类网路设置故障而生,因此隐蔽性更强,混淆选项更多,可以活过深度包检测级别的特征识别,这时还有最后一个大招,只要是不知道什么流量跑过了,数量一大就是特征,这就是有罪推定。凡是无法确定是否正常的流量都判定为异常。这样,几乎所有的隧道在网络波动严重时都会废掉。
总之特征识别范围宽泛,不同网络状况遭遇的故障的范围和类型都不太统一,是网络波动里最玄学也是谣言最多的部分。隔一阵就会有某某协议已被完全识别的留言传出。考虑到深度包检测可能需要的更大的算力,特征识别究竟能走到多远值得思考。
除了识别代理协议外,诸如 p2p 分享和收发邮件等相关”高危“的网络协议都有一些干扰和重点关照。
绕过方法:隧道协议不是伪装成,而是真的做成 https 流量,符合 http 和 TLS 的所有规范。暂时缓解,多个 IP 和端口做负载均衡进行代理。
端口白名单
现阶段发生过的最大的网络波动,是端口白名单。这大概有封掉所有境外高位端口(>1024?),封掉 22 端口(ssh 默认端口)和封掉除了80,443 (http 和 https 默认端口)之外的所有端口等变种。这些在众所周知的时间段里部分地区发生了一次。
绕过方法:国内用未被限制端口地区的服务器中转,使用残存还能用的端口。
由于设置的错误,还可能发生更严重的故障吗,也许有一天,可能出现 IP 白名单的网络局部异常,除了允许的境外 IP 其他全部丢包,绕过方法:很难,但只要不彻底拔掉网线,还是有办法。比如指望大型 CDN 的 IP 在白名单上,通过 CDN 中转搭载在合法 https 请求上的隧道连接,或者指望 telex 这样的项目,虽然实现可能性极其渺茫。
至此,本系列第一篇,关于网络有可能出现的不正常之处就算讲的差不多了。等有时间在谈谈应对各个故障及故障触发机制的解决办法和综合的解决方案。
EOF