科學上網基本工作原理

知其所以然

本文不是教程向,傾向於分析科學上網的一些原理。知其所以然,才能更好地使用工具,也可以創作出自己的工具。

科學上網的工具很多,八仙過海,各顯神通,而且綜合了各種技術。嘗試從以下四個方面來解析一些其中的原理。大致先原理,再工具的順序。

  • dns
  • http/https proxy
  • vpn
  • socks proxy

一個http請求發生了什麼?

這個是個比較流行的面試題,從中可以引出很多的內容。大致分為下面四個步驟:

  • dns解析,得到IP
  • 向目標IP發起TCP請求
  • 發送http request
  • 伺服器回應,瀏覽器解析

還有很多細節,更多參考:

http://fex.baidu.com/blog/2014/05/what-happen/

http://stackoverflow.com/questions/2092527/what-happens-when-you-type-in-a-url-in-browser

http://div.io/topic/609?page=1 從FE的角度上再看輸入url後都發生了什麼

DNS/域名解析

可以看到dns解析是最初的一步,也是最重要的一步。比如訪問親友,要知道他的正確的住址,才能正確地上門拜訪。

dns有兩種協議,一種是UDP(默認),一種是TCP。

udp 方式,先回應的數據包被當做有效數據

在linux下可以用dig來檢測dns。國內的DNS伺服器通常不會返回正常的結果。
下面以google的8.8.8.8 dns伺服器來做測試,並用wireshark來抓包,分析結果。

1
dig @8.8.8.8  www.youtube.com

dns-udp-youtube

從wireshark的結果,可以看到返回了三個結果,前面兩個是錯誤的,後面的是正確的

但是,對於dns客戶端來說,它只會取最快回應的的結果,後面的正確結果被丟棄掉了。因為中間被插入了污染包,所以即使我們配置了正確的dns伺服器,也解析不到正確的IP。

tcp 方式,有時有效,可能被rest

再用TCP下的DNS來測試下:

1
dig @8.8.8.8 +tcp   www.youtube.com

dns-tcp-youtube-reset

從wireshark的結果,可以看出在TCP三次握手成功時,本地發出了一個查詢www.youtube.com的dns請求,結果,很快收到了一個RST回應。而RST回應是在TCP連接斷開時,才會發出的。所以可以看出,**TCP通訊受到了干擾,DNS客戶端因為收到RST回應,認為對方斷開了連接,因此也無法收到後面正確的回應數據包了。**

再來看下解析twitter的結果:

1
dig @8.8.8.8 +tcp  www.twitter.com

結果:

1
2
3
4
5
www.twitter.com.        590     IN      CNAME   twitter.com.
twitter.com.            20      IN      A       199.59.150.7 80
twitter.com.            20      IN      A       199.59.150.7
twitter.com.            20      IN      A       199.59.149.230
twitter.com.            20      IN      A       199.59.150.39

這次返回的IP是正確的。但是嘗試用telnet 去連接時,會發現連接不上。

1
telnet 199.59.150.7 80

但是,在國外伺服器去連接時,可以正常連接,完成一個http請求。可見一些IP的訪問被禁止了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ telnet 199.59.150.7 80
Trying 199.59.150.7...
Connected to 199.59.150.7.
Escape character is '^]'.
GET / HTTP/1.0
HOST:www.twitter.com

HTTP/1.0 301 Moved Permanently
content-length: 0
date: Sun, 08 Feb 2015 06:28:08 UTC
location: https://www.twitter.com/
server: tsa_a
set-cookie: guest_id=v1%3A142337688883648506; Domain=.twitter.com; Path=/; Expires=Tue, 07-Feb-2017 06:28:08 UTC
x-connection-hash: 0f5eab0ea2d6309109f15447e1da6b13
x-response-time: 2

黑名單/白名單

想要獲取到正確的IP,自然的黑名單/白名單兩種思路。

下面列出一些相關的項目:

1
2
3
https://github.com/holmium/dnsforwarder
https://code.google.com/p/huhamhire-hosts/
https://github.com/felixonmars/dnsmasq-china-list

本地DNS軟體

  • 修改hosts文件
    相信大家都很熟悉,也有一些工具可以自動更新hosts文件的。
  • 瀏覽器pac文件
    主流瀏覽器或者其插件,都可以配置pac文件。pac文件實際上是一個JS文件,可以通過編程的方式來控制dns解析結果。其效果類似hosts文件,不過pac文件通常都是由插件控制自動更新的。只能控制瀏覽器的dns解析。
  • 本地dns伺服器,dnsmasq
    在linux下,可以自己配置一個dnsmasq伺服器,然後自己管理dns。不過比較高級,也比較麻煩。

順便提一下,實際上,kubuntu的NetworkManager會自己啟動一個私有的dnsmasq進程來做dns解析。不過它偵聽的是127.0.1.1,所以並不會造成衝突。

1
/usr/sbin/dnsmasq --no-resolv --keep-in-foreground --no-hosts --bind-interfaces --pid-file=/run/sendsigs.omit.d/network-manager.dnsmasq.pid --listen-address=127.0.1.1 --conf-file=/var/run/NetworkManager/dnsmasq.conf

路由器智能DNS

基於OpenWRT/Tomoto的路由器可以在上面配置dns server,從而實現在路由器級別智能dns解析。現在國內的一些路由器是基於OpenWRT的,因此支持配置dns伺服器。
參考項目:

1
https://github.com/clowwindy/ChinaDNS

http proxy

http proxy請求和沒有proxy的請求的區別

在chrome里沒有設置http proxy的請求頭信息是這樣的:

1
2
GET /nocache/fesplg/s.gif
Host: www.baidu.com

在設置了http proxy之後,發送的請求頭是這樣的:

1
2
3
GET http://www.baidu.com//nocache/fesplg/s.gif
Host: www.baidu.com
Proxy-Connection: keep-alive

區別是配置http proxy之後,會在請求里發送完整的url。

client在發送請求時,如果沒有proxy,則直接發送path,如果有proxy,則要發送完整的url。

實際上http proxy server可以處理兩種情況,即使客戶端沒有發送完整的url,因為host欄位里,已經有host信息了。

為什麼請求里要有完整的url?

歷史原因。

目標伺服器能否感知到http proxy的存在?

當我們使用http proxy時,有個問題可能會關心的:目標伺服器能否感知到http proxy的存在?

一個配置了proxy的瀏覽器請求頭:

1
2
3
GET http://55.75.138.79:9999/ HTTP/1.1
Host: 55.75.138.79:9999
Proxy-Connection: keep-alive

實際上目標伺服器接收到的信息是這樣子的:

1
2
3
GET / HTTP/1.1
Host: 55.75.138.79:9999
Connection: keep-alive

可見,http proxy伺服器並沒有把proxy相關信息發送到目標伺服器上。

因此,目標伺服器是沒有辦法知道用戶是否使用了http proxy。

http proxy keep-alive

實際上Proxy-Connection: keep-alive這個請求頭是錯誤的,不在標準里:

因為http1.1 默認就是Connection: keep-alive

如果client想要http proxy在請求之後關閉connection,可以用Proxy-Connection: close 來指明。

http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html

http proxy authentication

當http proxy需要密碼時:

第一次請求沒有密碼,則會回應

1
2
HTTP/1.1 407 Proxy authentication required
Proxy-Authenticate: Basic realm="Polipo"

瀏覽器會彈出窗口,要求輸入密碼。
如果密碼錯誤的話,回應頭是:

1
HTTP/1.1 407 Proxy authentication incorrect

如果是配置了密碼,發送的請求頭則是:

1
2
3
4
GET http://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Proxy-Connection: keep-alive
Proxy-Authorization: Basic YWRtaW46YWRtaW4=

Proxy-Authorization實際是Base64編碼。

1
base64("admin:admin") == "YWRtaW46YWRtaW4="

http proxy對於不認識的header和方法的處理:

http proxy通常會盡量原樣發送,因為很多程序都擴展了http method,如果不支持,很多程序都不能正常工作。

客戶端用OPTIONS 請求可以探測伺服器支持的方法。但是意義不大。

https proxy

當訪問一個https網站時,https://github.com

先發送connect method,如果支持,會返回200

1
2
3
4
5
6
CONNECT github.com:443 HTTP/1.1
Host: github.com
Proxy-Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

HTTP/1.1 200 OK

http tunnel

http://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_tunneling

通過connect method,http proxy server實際上充當tcp轉發的中間人。
比如,用nc 通過http proxy來連42埠:

1
$ nc -x10.2.3.4:8080 -Xconnect host.example.com 42

原理是利用CONNECT方法,讓http proxy伺服器充當中間人。

https proxy的安全性?

proxy server可以拿到什麼信息?

通過一個http proxy去訪問支付寶是否安全?

  • 可以知道host,即要訪問的是哪個網站
  • 拿不到url信息
  • https協議保證不會泄露通信內容
  • TLS(Transport Layer Security) 在握手時,生成強度足夠的隨機數
  • TLS 每一個record都要有一個sequence number,每發一個增加一個,並且是不能翻轉的。
  • TLS 保證不會出現重放攻擊

TLS的內容很多,這裡說到關於安全的一些關鍵點。

注意事項:

  • 確保是https訪問
  • 確保訪問網站的證書沒有問題

是否真的安全了?更強的攻擊者!

流量劫持 —— 浮層登錄框的隱患

http://fex.baidu.com/blog/2014/06/danger-behind-popup-login-dialog/

所以,盡量不要使用來路不明的http/https proxy,使用公開的wifi也要小心。

goagent工作原理

  • local http/https proxy
  • 偽造https證書,導入瀏覽器信任列表裡
  • 瀏覽器配置http/https proxy
  • 解析出http/https request的內容。然後把這些請求內容打包,發給GAE伺服器
  • 與GAE通信通過http/https,內容用RC4演算法加密
  • GAE伺服器,再調用google提供的 urlfetch,來獲得請求的回應,然後再把回應打包,返回給客戶端。
  • 客戶端把回應傳給瀏覽器
  • 自帶dns解析伺服器
  • 在local/certs/ 目錄下可以找到緩存的偽造的證書

fiddler抓取https數據包是同樣原理。

goagent會為每一個https網站偽造一個證書,並緩存起來。比如下面這個github的證書:
goagent-github-cert.png

goagent的代碼在3.0之後,支持了很多其它功能,變得有點混亂了。

以3.2.0 版本為例:

主要的代碼是在server/gae/gae.py 里。
https://github.com/goagent/goagent/blob/v3.2.0/server/gae/gae.py#L107

一些代碼實現的細節:

1
2
@staticmethod
def _get_cert(commonname, sans=()):

為什麼goagent可以看視頻?

因為很多網站都是http協議的。有少部分是rmtp協議的,也有是rmtp over http的。

在youku看視頻的一個請求數據:

1
2
3
4
5
6
7
8
9
http://14.152.72.22/youku/65748B784A636820C5A81B41C7/030002090454919F64A167032DBBC7EE242548-46C9-EB9D-916D-D8BA8D5159D3.flv?&start=158
response:
Connection:close
Content-Length:7883513
Content-Type:video/x-flv
Date:Wed, 17 Dec 2014 17:55:24 GMT
ETag:"316284225"
Last-Modified:Wed, 17 Dec 2014 15:21:26 GMT
Server:YOUKU.GZ

可以看到,有ETag,有長度信息等。

goagent缺點

  • 只是http proxy,不能代理其它協議
  • google的IP經常失效
  • 不支持websocket協議
  • 配置複雜

vpn

流行的vpn類型

  • PPTP,linux pptpd,安全性低,不能保證數據完整性或者來源,MPPE加密暴力破解
  • L2TP,linux xl2tpd,預共享密鑰可以保證安全性
  • SSTP,基於HTTPS,微軟提出。linux開源實現SoftEther VPN
  • OPENVPN,基於SSL,預共享密鑰可以保證安全性
  • 所謂的SSL VPN,各家廠商有自己的實現,沒有統一的標準
  • 新型的staless VPN,像sigmavpn/ShadowVPN等

現狀:

  • PPTP/L2TP 可用,但可能會不管用
  • SoftEther VPN/OPENVPN 可能會導致伺服器被封IP,連不上,慎用
  • ShadowVPN可用,sigmavpn沒有測試

猜測下為什麼PPTP,L2TP這些方案容易被檢測到?

可能是因為它們的協議都有明顯的標頭:

  • 轉發的是ppp協議數據,握手有特徵
  • PPTP協議有GRE標頭和PPP標頭
  • L2TP有L2TP標頭和PPP標頭
  • L2TP要用到IPsec

參考:

https://technet.microsoft.com/zh-cn/library/cc771298(v=ws.10).aspx

網頁版的SSL VPN

有些企業,或者學校里,會有這種VPN:

  • 網頁登陸帳號
  • 設置IE代理,為遠程伺服器地址
  • 通過代理瀏覽內部網頁

這種SSL VPN原理很簡單,就是一個登陸驗證的http proxy,其實並不能算是VPN?

新型的staless vpnVPN,sigmavpn/ShadowVPN

這種新型VPN的原理是,利用虛擬的網路設備TUN和TAP,把請求數據先發給虛擬設備,然後把數據加密轉發到遠程伺服器。(VPN都這原理?)

1
you <-> local <-> protocol <-> remote <-> ...... <-> remote <-> protocol <-> local <-> peer

這種新型VPN的特點是很輕量,沒有傳統VPN那麼複雜的握手加密控制等,而向個人,而非企業。SigmaVPN號稱只有幾百行代碼。

參考:

http://zh.wikipedia.org/wiki/TUN%E4%B8%8ETAP

https://code.google.com/p/sigmavpn/wiki/Introduction

ubuntu pptp vpn server安裝

ubuntu官方參考文檔:
https://help.ubuntu.com/community/PPTPServer

  • vps 要開啟ppp和nat網路轉發的功能
  • 設置MTU,建議設置為1200以下,因為中間網路可能很複雜,MTU太大可能導致連接失敗
    1
    iptables -A FORWARD -p tcp --syn -s 192.168.0.0/24 -j TCPMSS --set-mss 1200

socks proxy

socks5支持udp,所以如果客戶端把dns查詢也走socks的話,那麼就可以直接解決dns的問題了。

socks proxy 握手的過程

socks5流程

  • 客戶端查詢伺服器支持的認證方式
  • 伺服器回應支持的認證方式
  • 客戶端發送認證信息,伺服器回應
  • 如果通過,客戶端直接發送TCP/UDP的原始數據,以後proxy只單純轉發數據流,不做任何處理了
  • socks proxy 自身沒有加密機制,簡單的TCP/UDP forward

socks協議其實是相當簡單的,用wireshark抓包,結合netty-codec-socks,很容易可以理解其工作過程。
https://github.com/netty/netty/tree/master/codec-socks

ssh socks proxy

如果有一個外國的伺服器,可以通過ssh連接登陸,那麼可以很簡單地搭建一個本地的socks5代理。

XShell可以通過「轉移規則」來配置本地socks伺服器,putty也有類似的配置:
xshell-sock5-proxy.png

linux下命令行啟動一個本地sock5伺服器:

1
ssh -D 1080 user@romoteHost

ssh還有一些埠轉發的技巧,這對於測試網路程序,繞過防火牆也是很有幫助的。

參考:http://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/

shadowsocks的工作原理

shadowsocks是非常流行的一個代理工具,其原理非常簡單。

  • 客戶端伺服器預共享密碼
  • 本地socks5 proxy server(有沒有想起在學校時用的ccproxy?)
  • 軟體/瀏覽器配置本地socks代理
  • 本地socks server把數據包裝,AES256加密,發送到遠程伺服器
  • 遠程伺服器解密,轉發給對應的伺服器
1
2
3
app => local socks server(encrypt) => shadowsocks server(decrypt) => real host

app <= (decrypt) local socks server <= (encrypt) shadowsocks server <= real host

其它的一些東東:

  • 一個埠一個密碼,沒有用戶的概念
  • 支持多個worker並發
  • 協議簡單,比socks協議還要簡單,抽取了socks協議的部分

shadowsoks的優點

  • 中間沒有任何握手的環節,直接是TCP數據流
  • 速度快

shadowsocks的安全性

  • 伺服器可以解出所有的TCP/UDP數據
  • 中間人攻擊,重放攻擊

所以,對於第三方shadow socks伺服器,要慎重使用。

在使用shadowsocks的情況下,https通迅是安全的,但是仍然有危險,參見上面http proxy安全的內容。

vpn和socks代理的區別

從原理上來說,socks代理會更快,因為轉發的數據更少。

因為vpn轉發的是ppp數據包,ppp協議是數據鏈路層(data link layer)的協議。socks轉發的是TCP/UDP數據,是傳輸(transport)層。

VPN的優點是很容易配置成全局的,這對於很多不能配置代理的程序來說很方便。而配置全局的socks proxy比較麻煩,目前貌似還沒有簡單的方案。

linux下一些軟體配置代理的方法

  • bash/shell

對於shell,最簡單的辦法是在命令的前面設置下http_porxy的環境變數。

1
http_proxy=http://127.0.0.1:8123 wget http://test.com

推薦的做法是在~/.bashrc 文件里設置兩個命令,開關http proxy:

1
2
alias proxyOn='export https_proxy=http://127.0.0.1:8123 && http_proxy=http://127.0.0.1:8123'
alias proxyOff='unset https_proxy && unset  http_proxy'

注意,如果想sudo的情況下,http proxy仍然有效,要配置env_keep。

在/etc/sudoers.d/目錄下增加一個env_keep的文件,內容是:

1
Defaults env_keep += " http_proxy https_proxy ftp_proxy "

參考:
https://help.ubuntu.com/community/AptGet/Howto#Setting_up_apt-get_to_use_a_http-proxy

  • GUI軟體

現在大部分軟體都可以設置代理。
gnome和kde都可以設置全局的代理。

linux下不支持代理的程序使用socks代理:tsocks

tsocks利用LD_PRELOAD機制,代理程序里的connect函數,然後就可以代理所有的TCP請求了。
不過dns請求,默認是通過udp來發送的,所以tsocks不能代理dns請求。

默認情況下,tsocks會先載入~/.tsocks.conf,如果沒有,再載入/etc/tsocks.conf。對於local ip不會代理。

使用方法:

1
2
sudo apt-get install tsocks
LD_PRELOAD=/usr/lib/libtsocks.so wget http://www.facebook.com

基於路由器的方案

基於路由器的方案有很多,原理和本機的方案是一樣的,只不過把這些措施前移到路由器里。

路由器的方案的優點是很明顯的:

  • 手機/平板不用設置
  • 公司/區域網級的代理

但是需要專門的路由器,刷固件等。

shadowsocks, shadowvpn都可以跑在路由器上。

一些項目收集:

https://github.com/lifetyper/FreeRouter_V2

https://gist.github.com/wen-long/8644243

https://github.com/ashi009/bestroutetb

推薦的辦法

完全免費

  • chrome + switchsharp/SwitchyOmega + http proxy
  • goagent

程序員的推薦

  • chrome + switchsharp/SwitchyOmega + socks5 proxy
  • aws免費一年的伺服器/其它國外免費雲主機,節點位置決定速度,推薦東京機房
  • shadowsocks

第三方免費的伺服器

  • shadowsocks伺服器,微信公眾號:pennyjob

手機軟體:

  • fqrouter
  • shadowsocks client

商業軟體安全性自己考慮

總結

  • 新技術層出不窮
  • 越流行,越容易失效
  • 實現一個proxy其實相當簡單
  • 知其所以然,更好使用工具,也可以創作出自己的工具。