我们经常遇到这种情况,一夜过去,某一个境外网站便突然无法访问了。这个网站是被GFW屏蔽了吗?如果是的话,那么GFW是使用了哪种方法屏蔽的。
本文将简单介绍「如何系统性的判断某网站是否被GFW屏蔽」。
因笔者的操作系统原因,所有示例均在Linux系统下操作。
浏览器访问网页的基本过程如下:
DNS解析
TCP连接
HTTP请求
故本文也以此顺序讲解。
DNS解析
要想访问网页正确的DNS解析必不可少,因此DNS投毒 (DNS poisoning)是GFW常用的屏蔽手段。
此处以P站为例进行讲解。
当发现浏览器无法访问P站时,首先查看DNS解析是否正确。
$ dig www.pixiv.net ; <<>> DiG 9.14.8 <<>> www.pixiv.net ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59236 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 512 ;; QUESTION SECTION: ;www.pixiv.net. IN A ;; ANSWER SECTION: www.pixiv.net. 32 IN A 31.13.78.66 ;; Query time: 22 msec ;; SERVER: 114.114.114.114#53(114.114.114.114) ;; WHEN: Thu Nov 28 20:00:38 CST 2019 ;; MSG SIZE rcvd: 58
此处可结合IP地理位置及国外无污染DNS解析结果进行对比。
#使用 ipip.net 网站数据查询IP地理位置 $ curl http://freeapi.ipip.net/31.13.78.66 ["新加坡","新加坡","","",""]% #使用 DoH 获得纯净解析结果 $ curl -H 'accept: application/dns-json' "https://cloudflare-dns.com/dns-query?name=www.pixiv.net&type=A" --no-progress-meter | jq { "Status": 0, "TC": false, "RD": true, "RA": true, "AD": false, "CD": false, "Question": [ { "name": "www.pixiv.net.", "type": 1 } ], "Answer": [ { "name": "www.pixiv.net.", "type": 5, "TTL": 126, "data": "pixiv.net." }, { "name": "pixiv.net.", "type": 1, "TTL": 36, "data": "210.140.131.219" }, { "name": "pixiv.net.", "type": 1, "TTL": 36, "data": "210.140.131.221" }, { "name": "pixiv.net.", "type": 1, "TTL": 36, "data": "210.140.131.224" } ] } $ curl http://freeapi.ipip.net/210.140.131.221 ["日本","福岛县","白河","","idcf.jp"]%
通过以上结果可知,国内、国外DNS解析相差较大,该网站可能被DNS投毒,因此进行下一步测试。
$ dig www.pixiv.net @example.com ; <<>> DiG 9.14.8 <<>> www.pixiv.net @example.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19483 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.pixiv.net. IN A ;; ANSWER SECTION: www.pixiv.net. 195 IN A 69.63.176.15 ;; Query time: 4 msec ;; SERVER: 93.184.216.34#53(93.184.216.34) ;; WHEN: Thu Nov 28 20:13:10 CST 2019 ;; MSG SIZE rcvd: 47 $ dig www.pixiv.net txt @example.com ; <<>> DiG 9.14.8 <<>> www.pixiv.net txt @example.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49789 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.pixiv.net. IN TXT ;; ANSWER SECTION: www.pixiv.net. 72 IN A 67.228.74.123 ;; Query time: 10 msec ;; SERVER: 93.184.216.34#53(93.184.216.34) ;; WHEN: Thu Nov 28 20:15:31 CST 2019 ;; MSG SIZE rcvd: 47 $ dig www.pixiv.net aaaa @example.com ; <<>> DiG 9.14.8 <<>> www.pixiv.net aaaa @example.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26497 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.pixiv.net. IN AAAA ;; ANSWER SECTION: www.pixiv.net. 208 IN A 69.171.224.12 ;; Query time: 13 msec ;; SERVER: 93.184.216.34#53(93.184.216.34) ;; WHEN: Thu Nov 28 20:15:45 CST 2019 ;; MSG SIZE rcvd: 47
向境外未开放53端口的主机发送DNS查询请求,本不应收到查询结果,但收到错误的A纪录。可确定该网站已被DNS投毒。
除此而外,使用全局ping工具(如站长之家ping检测、IPIP ping工具) 可获得更直观的结果。
境外检测点DNS解析结果正确,境内DNS解析结果错误。
TCP连接
对于部分网站(如google,twitter等)的IP,GFW会直接在三层进行屏蔽,不给任何访问机会。
$ curl -H 'accept: application/dns-json' "https://cloudflare-dns.com/dns-query?name=www.google.com&type=A" --no-progress-meter | jq { "Status": 0, "TC": false, "RD": true, "RA": true, "AD": false, "CD": false, "Question": [ { "name": "www.google.com.", "type": 1 } ], "Answer": [ { "name": "www.google.com.", "type": 1, "TTL": 73, "data": "172.217.0.36" } ] }
通过DoH查询出正确的IP地址
$ telnet 172.217.0.36 80 Trying 172.217.0.36... ^C $ telnet 172.217.0.36 443 Trying 172.217.0.36... ^C
无法连通相应IP的80、443端口
$ mtr 172.217.0.36 --report Start: 2019-11-28T20:32:34+0800 HOST: localhost Loss% Snt Last Avg Best Wrst StDev 1.|-- _gateway 0.0% 10 5.3 5.7 3.2 12.7 3.1 2.|-- 10.1.2.21 50.0% 10 89.6 22.0 3.4 89.6 37.8 3.|-- 10.254.2.2 0.0% 10 50.8 10.5 2.7 50.8 14.6 4.|-- 202.204.[MASK].[MASK] 0.0% 10 10.8 7.8 4.1 17.3 4.1 5.|-- 101.4.117.49 10.0% 10 4.7 17.8 4.7 85.3 25.5 6.|-- ??? 100.0 10 0.0 0.0 0.0 0.0 0.0 7.|-- 101.4.113.109 90.0% 10 5.7 5.7 5.7 5.7 0.0 8.|-- 101.4.114.194 0.0% 10 5.6 7.9 5.6 14.1 2.6 9.|-- 101.4.117.254 0.0% 10 5.4 19.6 5.4 105.5 30.5 10.|-- ??? 100.0 10 0.0 0.0 0.0 0.0 0.0
跟踪路由可发现,数据包丢失在骨干网。
此外,您可以使用IPIP提供的traceroute工具,查看全国各地的路由情况。
HTTP请求
GFW除了上述屏蔽方法,其还可以分析HTTP请求及SNI头对特定网站进行阻断。
HTTP阻断
此节以草榴为例。
注意,草榴已被DNS投毒,以下演示均使用了防污染DNS。
$ curl -v http://t66y.com * Trying 104.26.10.160:80... * TCP_NODELAY set * Connected to t66y.com (104.26.10.160) port 80 (#0) > GET / HTTP/1.1 > Host: t66y.com > User-Agent: curl/7.67.0 > Accept: */* > * Recv failure: Connection reset by peer * Closing connection 0 curl: (56) Recv failure: Connection reset by peer
访问草榴HTTP版本,访问出错,提示:链接被重置。
$ curl -v https://t66y.com * Trying 104.26.11.160:443... * TCP_NODELAY set * Trying 2606:4700:20::681a:aa0:443... * TCP_NODELAY set * Connected to t66y.com (104.26.11.160) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * ALPN, server accepted to use h2 * Server certificate: * subject: C=US; ST=CA; L=San Francisco; O=Cloudflare, Inc.; CN=sni.cloudflaressl.com * start date: Nov 21 00:00:00 2019 GMT * expire date: Oct 9 12:00:00 2020 GMT * subjectAltName: host "t66y.com" matched cert's "t66y.com" * issuer: C=US; ST=CA; L=San Francisco; O=CloudFlare, Inc.; CN=CloudFlare Inc ECC CA-2 * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x55702c251810) > GET / HTTP/2 > Host: t66y.com > user-agent: curl/7.67.0 > accept: */* > * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing * Connection state changed (MAX_CONCURRENT_STREAMS == 256)! < HTTP/2 200 < date: Thu, 28 Nov 2019 12:52:19 GMT < content-type: text/html < content-length: 1338 < set-cookie: __cfduid=d976a8685fe1c5351a8a9467c0fdb5fb11574945539; expires=Sat, 28-Dec-19 12:52:19 GMT; path=/; domain=.t66y.com; HttpOnly < x-powered-by: PHP/5.6.40 < cf-cache-status: DYNAMIC < expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" < server: cloudflare < cf-ray: 53cc7a77bb57eb4d-LAX < <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> …… ……
而草榴HTTPS版本则可以正常访问。
为了进一步验证屏蔽是基于HTTP阻断,我们可以进行如下curl测试。
$ curl -v --connect-to ::example.com: http://t66y.com * Connecting to hostname: example.com * Trying 93.184.216.34:80... * TCP_NODELAY set * Trying 2606:2800:220:1:248:1893:25c8:1946:80... * TCP_NODELAY set * Connected to example.com (93.184.216.34) port 80 (#0) > GET / HTTP/1.1 > Host: t66y.com > User-Agent: curl/7.67.0 > Accept: */* > * Recv failure: Connection reset by peer * Closing connection 0 curl: (56) Recv failure: Connection reset by peer
上面的测试连接到 example.com(93.184.216.34),但尝试请求 http://t66y.com。我们可以看到该连接同样的阻断了。
相反,如下所示,如果我们向t66y.com(104.26.11.160) 请求 http://example.com,可发现请求成功。
$ curl -v --resolve 't66y.com:80:104.26.11.160' --connect-to ::t66y.com: http://example.com * Added t66y.com:80:104.26.11.160 to DNS cache * Connecting to hostname: t66y.com * Hostname t66y.com was found in DNS cache * Trying 104.26.11.160:80... * TCP_NODELAY set * Connected to t66y.com (104.26.11.160) port 80 (#0) > GET / HTTP/1.1 > Host: example.com > User-Agent: curl/7.67.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 409 Conflict < Date: Thu, 28 Nov 2019 13:16:29 GMT < Content-Type: text/plain; charset=UTF-8 < Transfer-Encoding: chunked < Connection: close < Set-Cookie: __cfduid=d23e69c40d4692b519f5d5f9a54307e081574946989; expires=Sat, 28-Dec-19 13:16:29 GMT; path=/; domain=.example.com; HttpOnly < Cache-Control: max-age=6 < Expires: Thu, 28 Nov 2019 13:16:35 GMT < Server: cloudflare < CF-RAY: 53cc9ddc0ebcebad-LAX < * Closing connection 0 error code: 1001%
SNI过滤
此节以维基百科为例。
首先,确认维基百科DNS解析正确。
$ dig www.wikipedia.org @114.114.114.114 ; <<>> DiG 9.14.8 <<>> www.wikipedia.org @114.114.114.114 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20468 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;www.wikipedia.org. IN A ;; ANSWER SECTION: www.wikipedia.org. 42366 IN CNAME dyna.wikimedia.org. dyna.wikimedia.org. 33 IN A 198.35.26.96 ;; Query time: 28 msec ;; SERVER: 114.114.114.114#53(114.114.114.114) ;; WHEN: Thu Nov 28 21:46:01 CST 2019 ;; MSG SIZE rcvd: 91 $ curl -H 'accept: application/dns-json' "https://cloudflare-dns.com/dns-query?name=www.wikipedia.org&type=A" {"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "www.wikipedia.org.", "type": 1}],"Answer":[{"name": "www.wikipedia.org.", "type": 5, "TTL": 10359, "data": "dyna.wikimedia.org."},{"name": "dyna.wikimedia.org.", "type": 1, "TTL": 77, "data": "198.35.26.96"}]}%
但仍然无法直接访问,这又是为什么?
$ curl -v https://www.wikipedia.org * Trying 198.35.26.96:443... * TCP_NODELAY set * Trying 2620:0:863:ed1a::1:443... * TCP_NODELAY set * Connected to www.wikipedia.org (198.35.26.96) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.wikipedia.org:443 * Closing connection 0 curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.wikipedia.org:443
抓包可见发出 TLS handshake, Client hello 后,连接即被阻断。
为了进一步验证屏蔽是基于SNI过滤的假设,我们可以进行如下curl测试。
$ curl -v --connect-to ::example.com: https://www.wikipedia.org * Connecting to hostname: example.com * Trying 93.184.216.34:443... * TCP_NODELAY set * Trying 2606:2800:220:1:248:1893:25c8:1946:443... * TCP_NODELAY set * connect to 2606:2800:220:1:248:1893:25c8:1946 port 443 failed: Network is unreachable * Connected to example.com (93.184.216.34) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.wikipedia.org:443 * Closing connection 0 curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.wikipedia.org:443
上面的curl测试连接到example.com(IP 93.184.216.34),但尝试使用www.wikipedia.org的SNI进行TLS握手。 正如我们从上面的输出中看到的,TLS handshake, Client hello一发出,连接就中断了。
相反,如下所示,如果我们在与www.wikipedia.org进行TLS握手时尝试使用example.com的SNI(我们使用--resolve选项跳过DNS解析),请求是成功的并且能够完成TLS握手。
$ curl -v --resolve 'www.wikipedia.org:443:198.35.26.96' --connect-to ::www.wikipedia.org: https://example.com * Added www.wikipedia.org:443:198.35.26.96 to DNS cache * Connecting to hostname: www.wikipedia.org * Hostname www.wikipedia.org was found in DNS cache * Trying 198.35.26.96:443... * TCP_NODELAY set * Connected to www.wikipedia.org (198.35.26.96) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305 * ALPN, server accepted to use h2 * Server certificate: * subject: C=US; ST=California; L=San Francisco; O=Wikimedia Foundation, Inc.; CN=*.wikipedia.org * start date: Nov 8 10:47:06 2019 GMT * expire date: Nov 22 07:59:59 2020 GMT * subjectAltName does not match example.com * SSL: no alternative certificate subject name matches target host name 'example.com' * Closing connection 0 * TLSv1.2 (OUT), TLS alert, close notify (256): curl: (60) SSL: no alternative certificate subject name matches target host name 'example.com' More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above.
总结
本文大致介绍了判断网站是否被墙的检测方法。
当然除此而外,你也可以使用诸如 greatfire 之类的检测网站进行检测。