知其所以然
本文不是教程向,傾向於分析科學上網的一些原理。知其所以然,才能更好地使用工具,也可以創作出自己的工具。
科學上網的工具很多,八仙過海,各顯神通,而且綜合了各種技術。嘗試從以下四個方面來解析一些其中的原理。大致先原理,再工具的順序。
- 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
|
從wireshark的結果,可以看到返回了三個結果,前面兩個是錯誤的,後面的是正確的。
但是,對於dns客戶端來說,它只會取最快回應的的結果,後面的正確結果被丟棄掉了。因為中間被插入了污染包,所以即使我們配置了正確的dns伺服器,也解析不到正確的IP。
tcp 方式,有時有效,可能被rest
再用TCP下的DNS來測試下:
1
|
dig @8.8.8.8 +tcp www.youtube.com
|
從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的代碼在3.0之後,支持了很多其它功能,變得有點混亂了。
以3.2.0 版本為例:
主要的代碼是在server/gae/gae.py 里。
https://github.com/goagent/goagent/blob/v3.2.0/server/gae/gae.py#L107
一些代碼實現的細節:
- 支持最長的url是2083,因為gae sdk的限制。
https://github.com/AppScale/gae_sdk/blob/master/google/appengine/api/taskqueue/taskqueue.py#L241 - 如果回應的內容是/text, json, javascript,且 > 512會用gzip來壓縮
- 處理一些Content-Range 的回應內容。Content-Range 的代碼雖然只有一點點,但是如果是不熟悉的話,要花上不少工夫。
- goagent的生成證書的代碼在 local/proxylib.py的這個函數里:
1 2 |
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
- rfc 文檔: http://tools.ietf.org/html/rfc1928
- wiki上的簡介: http://en.wikipedia.org/wiki/SOCKS#SOCKS5
- socks4/socks4a 已經過時
- socks5
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也有類似的配置:
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其實相當簡單
- 知其所以然,更好使用工具,也可以創作出自己的工具。