一個簡單的Golang實現的Socket5 Proxy

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前兩天,使用Golang實現了一個簡單的HTTP Proxy,具體實現參見 http://www.flysnow.org/2016/12/24/golang-http-proxy.html,這次使用Golang實現一個Socket5的簡單代理。Socket5和HTTP並沒有太大的不同,他們都可以完全給予TCP協議,只是請求的資訊結構不同,所以這次我們不能像上次HTTP Proxy一樣,解析請求和應答,要按照Socket的協議方式解析。

Socket協議版本

Socket協議分為Socket4和Socket5兩個版本,他們最明顯的區別是Socket5同時支援TCP和UDP兩個協議,而SOcket4隻支援TCP。目前大部分使用的是Socket5,我們這裡只簡單的介紹Socket5協議。

Socket5協議之授權認證

要想實現Socket5之間的串連會話,必須要懂SOcket5協議的實現細節和規範。這就好比我們都用普通話對話一樣,彼此說的都明白,也可以給對方聽得懂的回應。Socket5的用戶端和服務端交流也一樣,他們的語言就是Socket5協議。因為Socket5支援TCP和UDP兩種,這裡只介紹TCP這一種,UDP大同小異。

首先用戶端會給服務端發送驗證資訊,這個是建立串連的前提。比如用戶端:hi,哥們,借個火。服務端要認識它就說:好,給;如果不認識就說:你哪根蔥啊!!用戶端請求的暗號很簡單:

VER NMETHODS METHODS
1 1 1 to 255
  1. 第一個欄位VER代表Socket的版本,Soket5預設為0x05,其固定長度為1個位元組
  2. 第二個欄位NMETHODS表示第三個欄位METHODS的長度,它的長度也是1個位元組
  3. 第三個METHODS表示用戶端支援的驗證方式,可以有多種,他的嘗試是1-255個位元組。

目前支援的驗證方式一共有:

  1. X’00’ NO AUTHENTICATION REQUIRED(不需要驗證)
  2. X’01’ GSSAPI
  3. X’02’ USERNAME/PASSWORD(使用者名稱密碼)
  4. X’03’ to X’7F’ IANA ASSIGNED
  5. X’80’ to X’FE’ RESERVED FOR PRIVATE METHODS
  6. X’FF’ NO ACCEPTABLE METHODS(都不支援,沒法串連了)

以上的都是十六進位常量,比如X’00’表示十六進位0x00。

服務端收到用戶端的驗證資訊之後,就要回應用戶端,服務端需要用戶端提供哪種驗證方式的資訊。服務端的回應同樣非常簡潔。

VER METHOD
1 1
  1. 第一個欄位VER代表Socket的版本,Soket5預設為0x05,其值長度為1個位元組
  2. 第二個欄位METHOD代表需要服務端需要用戶端按照此驗證方式提供驗證資訊,其值長度為1個位元組,選擇為上面的六種驗證方式。

舉例說明,比如服務端不需要驗證的話,可以這麼回應用戶端:

VER METHOD
0x05 0x00

這就代表格服務端說:哥們,我沒啥要求,你來吧,我們使用Go實現代碼如下:

123456789101112
var b [1024]byten, err := client.Read(b[:])if err != nil {log.Println(err)return}if b[0] == 0x05 { //只處理Socket5協議//用戶端回應:Socket服務端不需要驗證方式client.Write([]byte{0x05, 0x00})n, err = client.Read(b[:])}

我們這裡以最簡單的不需要驗證的方式為例進行介紹,這種方式進行了上面的一問一答後就可以開始建立串連了。對於其他驗證方式,還需要再進行一次一問一答,主要是用戶端提供驗證資訊,服務端回應驗證是否正確,這些細節可以參考http://www.ietf.org/rfc/rfc1928.txt以及http://www.ietf.org/rfc/rfc1929.txt的Socket5協議定義。

Socket5協議之建立串連。

Socket5的用戶端和服務端進行雙方授權驗證通過之後,就開始建立串連了。串連由用戶端發起,告訴Sokcet服務端用戶端需要訪問哪個遠程伺服器,其中包含,遠程伺服器的地址和連接埠,地址可以是IP4,IP6,也可以是網域名稱。

VER CMD RSV ATYP DST.ADDR DST.PORT
1 1 X’00’ 1 Variable 2
  1. VER代表Socket協議的版本,Soket5預設為0x05,其值長度為1個位元組
  2. CMD代表用戶端請求的類型,值長度也是1個位元組,有三種類型
    • CONNECT X’01’
    • BIND X’02’
    • UDP ASSOCIATE X’03’
  3. RSV保留字,值長度為1個位元組
  4. ATYP代表請求的遠程伺服器位址類型,值長度1個位元組,有三種類型
    • IP V4 address: X’01’
    • DOMAINNAME: X’03’
    • IP V6 address: X’04’
  5. DST.ADDR代表遠程伺服器的地址,根據ATYP進行解析,值長度不定。
  6. DST.PORT代表遠程伺服器的連接埠,要訪問哪個連接埠的意思,值長度2個位元組

從協議裡解析我們需要的遠程伺服器資訊,Go代碼實現如下:

12345678910
var host,port stringswitch b[3] {case 0x01://IP V4host = net.IPv4(b[4],b[5],b[6],b[7]).String()case 0x03://網域名稱host = string(b[5:n-2])//b[4]表示網域名稱的長度case 0x04://IP V6host = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String()}port = strconv.Itoa(int(b[n-2])<<8|int(b[n-1]))

現在用戶端把要請求的遠程伺服器的資訊都告訴Socket5Proxy 伺服器了,那麼Socket5Proxy 伺服器就可以和遠程伺服器建立串連了,不管串連是否成功等,都要給用戶端回應,其回應格式為:

VER REP RSV ATYP BND.ADDR BND.PORT
1 1 X’00’ 1 Variable 2
  1. VER代表Socket協議的版本,Soket5預設為0x05,其值長度為1個位元組
  2. REP代表響應狀態代碼,值長度也是1個位元組,有以下幾種類型
    • X’00’ succeeded
    • X’01’ general SOCKS server failure
    • X’02’ connection not allowed by ruleset
    • X’03’ Network unreachable
    • X’04’ Host unreachable
    • X’05’ Connection refused
    • X’06’ TTL expired
    • X’07’ Command not supported
    • X’08’ Address type not supported
    • X’09’ to X’FF’ unassigned
  3. RSV保留字,值長度為1個位元組
  4. ATYP代表請求的遠程伺服器位址類型,值長度1個位元組,有三種類型
    • IP V4 address: X’01’
    • DOMAINNAME: X’03’
    • IP V6 address: X’04’
  5. BND.ADDR表示綁定地址,值長度不定。
  6. BND.PORT表示綁定連接埠,值長度2個位元組

Go實現的串連和應答如下:

1234567
server, err := net.Dial("tcp", net.JoinHostPort(host, port))if err != nil {log.Println(err)return}defer server.Close()client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) //響應用戶端串連成功

綁定的地址和連接埠,這兩個響應根據請求的CMD不同而不同,詳細描述參考http://www.ietf.org/rfc/rfc1928.txt

資料轉寄

建立好串連之後,就是資料傳遞轉寄,TCP協議可以直接轉寄。UDP的話需要特殊處理,具體參考協議定義,其實就是一個特殊格式的回應,和上面的一問一答差不多,更多協議細節 http://www.ietf.org/rfc/rfc1928.txt。

TCP直接轉寄非常簡單:

123
//進行轉寄go io.Copy(server, client)io.Copy(client, server)

完整代碼實現

以下是完成的代碼實現。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
package mainimport ("io""log""net""strconv")func main() {log.SetFlags(log.LstdFlags | log.Lshortfile)l, err := net.Listen("tcp", ":8081")if err != nil {log.Panic(err)}for {client, err := l.Accept()if err != nil {log.Panic(err)}go handleClientRequest(client)}}func handleClientRequest(client net.Conn) {if client == nil {return}defer client.Close()var b [1024]byten, err := client.Read(b[:])if err != nil {log.Println(err)return}if b[0] == 0x05 { //只處理Socket5協議//用戶端回應:Socket服務端不需要驗證方式client.Write([]byte{0x05, 0x00})n, err = client.Read(b[:])var host, port stringswitch b[3] {case 0x01: //IP V4host = net.IPv4(b[4], b[5], b[6], b[7]).String()case 0x03: //網域名稱host = string(b[5 : n-2]) //b[4]表示網域名稱的長度case 0x04: //IP V6host = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String()}port = strconv.Itoa(int(b[n-2])<<8 | int(b[n-1]))server, err := net.Dial("tcp", net.JoinHostPort(host, port))if err != nil {log.Println(err)return}defer server.Close()client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) //響應用戶端串連成功//進行轉寄go io.Copy(server, client)io.Copy(client, server)}}

這裡目前是一個簡易版本的Socket5 代理,還有很多沒有實現,要實現完成的Socket5,可以自己根據他定義的協議試試。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.