Shadowsocks 原始碼解釋
https://yveschan.github.io/blog/shadowsocks-analysis/
Preface
去年shadowsocks在V2EX剛釋出的時候,我就已經開始留意這個專案。當時還在用Goagent,但有時候速度確實不咋的,而且重新配置的話會比較麻煩。9月份入手VPS之後開始折騰PPTP VPN,效果相當不穩定。不久後OpenVPN也開始受到干擾了。看來必須尋找比較小眾的方式,避免躺槍。因此,初試shadowsocks(python版),速度或者穩定性都相當好,一直用到現在,未出現過什麼問題。配置也很簡單,唯一的門檻就是需要國外的Linux 主機(VPS)。現在shadowsocks專案已經發展到多語言跨平臺了,社群也比較活躍,主要原因是專案架構簡單,程式碼精簡易於維護。
一週前開始學習python,主要是想用python寫一個爬蟲。大概用了4天的課餘時間把 《Dive Into Python3》過了一遍,瞭解基本語法。結合文件看大牛的原始碼是很好的學習方式,所謂learn by doing嘛。不得不說,shadowsocks的原始碼真心簡潔,再看一下SOCK5協議的報文格式,並沒有花很多時間。貌似說了不少廢話,現在入正題= =!
Socks5
首先介紹一下socks5協議:SOCKS協議位於傳輸層(TCP/UDP等)與應用層之間,其工作流程為
- client向proxy發出請求資訊,用以協商傳輸方式
- proxy作出應答
- client接到應答後向proxy傳送目的主機(destination server)的ip和port
- proxy評估該目的主機地址,返回自身IP和port,此時C/P連線建立。
- proxy與dst server連線
- proxy將client發出的資訊傳到server,將server返回的資訊轉發到client。代理完成
client連線proxy的第一個報文資訊,進行認證機制協商
version | nmethod | methods |
1 Bytes | 1 Bytes | 1 to 255 Bytes |
一般是 hex: 05 01 00即:版本5,1種認證方式,NO AUTHENTICATION REQUIRED(無需認證 0x00)
proxy從METHODS欄位中選中一個位元組(一種認證機制),並向Client傳送響應報文:
version | methods |
1 | 1 |
一般是 hex: 05 00即:版本5,無需認證
認證機制相關的子協商完成後,Client提交轉發請求:
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
1 | 1 | 0x00 | 1 | variable | 2 |
前3個一般是 hex: 05 01 00地址型別可以是* 0x01 IPv4地址* 0x03 FQDN(全稱域名)* 0x04 IPv6地址
對應不同的地址型別,地址資訊格式也不同:* IPv4地址,這裡是big-endian序的4位元組資料* FQDN,比如”www.nsfocus.net”,這裡將是:0F 77 77 77 2E 6E 73 66 6F 63 75 73 2E 6E 65 74。注意,第一位元組是長度域* IPv6地址,這裡是16位元組資料。
proxy評估來自Client的轉發請求併發送響應報文
VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
1 | 1(response) | 0x00 | 1 | variable | 2 |
Proxy可以靠DST.ADDR、DST.PORT、SOCKSCLIENT.ADDR、SOCKSCLIENT.PORT進行評估,以決定建立到轉發目的地的TCP連線還是拒絕轉發。若允許則響應包的REP為0,非0則表示失敗(拒絕轉發或未能成功建立到轉發目的地的TCP連線)。
shadowsocks source code
原始碼方面,主要是由socks5轉發模組和加密解密模組組成
轉發模組感覺比較簡單,但是個人覺得有幾點需要注意的地方,或者說我自己不太明白。(python菜,請諒解)
1 2 3 4 5 6 |
|
client
- 從main開始,讀取配置。這裡為什麼要用二進位制的方式開啟json檔案呢?
- 解釋命令列引數
optlist, args = getopt.getopt(sys.argv[1:], 's:p:k:l:')
,這個跟Linux的getopt函式差不多,可以自己man一下。 - 設定logging等級和資訊,生成密文表(包括加密解密)。
- 執行 ThreadingTCPServer 例項。從定義看,
class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer)
子類化ThreadingTCPServer(多重繼承繼承ThreadingMixIn類和TCPServer類),即使用多執行緒處理TCP請求(Mix-in class to handle each request in a new thread),同時設定其類屬性allow_reuse_address。這裡繫結的地址是(”,PORT),意思是該套接字對於本機的任何地址都是可達的。BaseRequestHandler 則由 Socks5Server 子類實現。由於 SocketServer module 包含了很多socket programming的細節,所以程式碼看起來相當簡潔。 - 每當有請求到達,呼叫 handle 函式,主要是建立proxy到client和server的連線,然後呼叫 handle_tcp 函式來轉發TCP資料包(包括client to server或相反方向),對於server來說,proxy是完全透明的。當然,這裡client和proxy之間的資料互動需要通過加密傳輸。
- 對於handle_tcp而言,它需要同時處理兩個socket(client & server),這裡使用了I/O multiplexing的方式,選擇select作為實現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
對於高效能併發伺服器而言,這是一種非常重要的手段。當然還是其他實現方式,例如poll,epoll,kqueue等。這裡由於檔案描述符數量較小,所以分別也不大了。更詳細的資訊可以看 C10K problem
server
服務端程式碼與客戶端差不多,主要是資料報文的解釋和轉發問題。主要是處理好在client端傳送過來的自定資料格式,轉發到目的地址server,再將返回的資料轉發給client。
更多的細節我都在原始碼上註釋了,有興趣可以看看。
整個架構圖大概這樣:(就不要吐槽畫的有多醜了= =)
Reference: