├── .gitignore ├── LICENSE ├── README.md ├── auth.go ├── client.go ├── doc.go ├── examples ├── client │ ├── README.md │ └── client.go └── server │ ├── .gitignore │ ├── README.md │ └── server.go ├── go.mod ├── socks.go ├── socks4.go ├── socks5.go └── socks_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *~ 3 | *bak 4 | *.swp 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | socks-go 2 | ======= 3 | 4 | A socks proxy protocol implemented by golang, support socks 4, 4a and 5. 5 | 6 | Only CONNECT command support now. 7 | 8 | usage example 9 | ============= 10 | 11 | server: 12 | 13 | // listen 1080 and act as socks server 14 | // support socks4, socks4a and socks5 15 | 16 | package main 17 | 18 | import ( 19 | socks "github.com/fangdingjun/socks-go" 20 | "log" 21 | "net" 22 | "time" 23 | ) 24 | 25 | func main() { 26 | conn, err := net.Listen("tcp", ":1080") 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | for { 32 | c, err := conn.Accept() 33 | if err != nil { 34 | log.Println(err) 35 | continue 36 | } 37 | 38 | log.Printf("connected from %s", c.RemoteAddr()) 39 | 40 | d := net.Dialer{Timeout: 10 * time.Second} 41 | s := socks.Conn{Conn: c, Dial: d.Dial} 42 | go s.Serve() 43 | } 44 | } 45 | 46 | 47 | 48 | 49 | client: 50 | 51 | // visit https://www.google.com through socks proxy server 52 | 53 | package main 54 | 55 | import ( 56 | "bufio" 57 | "crypto/tls" 58 | socks "github.com/fangdingjun/socks-go" 59 | "io" 60 | "log" 61 | "net" 62 | "net/http" 63 | "os" 64 | ) 65 | 66 | func main() { 67 | // connect to socks server 68 | c, err := net.Dial("tcp", "localhost:1080") 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | defer c.Close() 73 | 74 | sc := &socks.Client{Conn: c} 75 | 76 | // connect to remote server 77 | if err := sc.Connect("www.google.com", 443); err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | // tls 82 | conn := tls.Client(sc, &tls.Config{ServerName: "www.google.com"}) 83 | if err := conn.Handshake(); err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | // send http request 88 | req, err := http.NewRequest("GET", "https://www.google.com/", nil) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | req.Write(conn) 93 | 94 | bio := bufio.NewReader(conn) 95 | 96 | // read response 97 | res, err := http.ReadResponse(bio, req) 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | defer res.Body.Close() 102 | 103 | io.Copy(os.Stdout, res.Body) 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import "net" 4 | 5 | // AuthService the service to authenticate the user for socks5 6 | type AuthService interface { 7 | // Authenticate auth the user 8 | // return true means ok, false means no access 9 | Authenticate(username, password string, addr net.Addr) bool 10 | } 11 | 12 | // default password auth service 13 | type PasswordAuth struct { 14 | Username string 15 | Password string 16 | } 17 | 18 | func (pa *PasswordAuth) Authenticate(username, password string, addr net.Addr) bool { 19 | if username == pa.Username && password == pa.Password { 20 | return true 21 | } 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "net" 8 | "strconv" 9 | ) 10 | 11 | // Client is a net.Conn with socks5 support 12 | type Client struct { 13 | net.Conn 14 | // socks5 username 15 | Username string 16 | // socks5 password 17 | Password string 18 | handshakeDone bool 19 | connected bool 20 | closed bool 21 | } 22 | 23 | func (sc *Client) handShake() error { 24 | if sc.handshakeDone { 25 | return nil 26 | } 27 | 28 | // password auth or none 29 | if _, err := sc.Conn.Write([]byte{socks5Version, 0x02, 0x00, 0x02}); err != nil { 30 | return err 31 | } 32 | 33 | buf := make([]byte, 512) 34 | 35 | if _, err := io.ReadFull(sc.Conn, buf[:2]); err != nil { 36 | return err 37 | } 38 | 39 | if buf[0] != socks5Version { 40 | return fmt.Errorf("error socks version %d", buf[0]) 41 | } 42 | 43 | if buf[1] != 0x00 && buf[1] != 0x02 { 44 | return fmt.Errorf("server return with code %d", buf[1]) 45 | } 46 | 47 | if buf[1] == 0x00 { 48 | sc.handshakeDone = true 49 | return nil 50 | } 51 | 52 | // password auth 53 | 54 | l := 3 + len(sc.Username) + len(sc.Password) 55 | 56 | buf[0] = 0x01 // auth protocol version 57 | buf[1] = byte(len(sc.Username)) // username length 58 | copy(buf[2:], []byte(sc.Username)) // username 59 | buf[2+len(sc.Username)] = byte(len(sc.Password)) // password length 60 | copy(buf[3+len(sc.Username):], []byte(sc.Password)) //password 61 | 62 | if _, err := sc.Conn.Write(buf[:l]); err != nil { 63 | return err 64 | } 65 | 66 | if _, err := sc.Conn.Read(buf[:2]); err != nil { 67 | return err 68 | } 69 | 70 | if buf[0] != 0x01 { 71 | return fmt.Errorf("unexpected auth protocol version %v", buf[0]) 72 | } 73 | 74 | // password auth success 75 | if buf[1] == 0x00 { 76 | sc.handshakeDone = true 77 | return nil 78 | } 79 | 80 | return fmt.Errorf("password rejected") 81 | } 82 | 83 | // Dial dial to the addr from socks server, 84 | // this is net.Dial style, 85 | // can call sc.Connect instead 86 | func (sc *Client) Dial(network, addr string) (net.Conn, error) { 87 | switch network { 88 | case "tcp": 89 | default: 90 | return nil, fmt.Errorf("unsupported network type: %s", network) 91 | } 92 | 93 | host, port, err := net.SplitHostPort(addr) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | p, err := strconv.Atoi(port) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | if err = sc.Connect(host, uint16(p)); err != nil { 104 | return nil, err 105 | } 106 | return sc, nil 107 | } 108 | 109 | // Connect handshakes with the socks server and request the 110 | // server to connect to the target host and port 111 | func (sc *Client) Connect(host string, port uint16) error { 112 | if !sc.handshakeDone { 113 | if err := sc.handShake(); err != nil { 114 | return err 115 | } 116 | } 117 | 118 | if sc.connected { 119 | return fmt.Errorf("only one connection allowed") 120 | } 121 | 122 | buf := make([]byte, 512) 123 | 124 | l := 4 + len(host) + 1 + 2 125 | buf[0] = socks5Version 126 | buf[1] = cmdConnect 127 | buf[2] = 0x00 128 | buf[3] = addrTypeDomain 129 | buf[4] = byte(len(host)) 130 | 131 | copy(buf[5:5+len(host)], []byte(host)) 132 | 133 | binary.BigEndian.PutUint16(buf[l-2:l], port) 134 | 135 | if _, err := sc.Conn.Write(buf[:l]); err != nil { 136 | return err 137 | } 138 | 139 | if _, err := io.ReadAtLeast(sc.Conn, buf, 10); err != nil { 140 | return err 141 | } 142 | 143 | if buf[0] != socks5Version { 144 | return fmt.Errorf("error socks version %d", buf[0]) 145 | } 146 | 147 | if buf[1] != 0x00 { 148 | return fmt.Errorf("server error code %d", buf[1]) 149 | } 150 | 151 | sc.connected = true 152 | return nil 153 | } 154 | 155 | // Read read from the underlying connection 156 | func (sc *Client) Read(b []byte) (int, error) { 157 | if !sc.connected { 158 | return 0, fmt.Errorf("call connect first") 159 | } 160 | return sc.Conn.Read(b) 161 | } 162 | 163 | // Write write data to underlying connection 164 | func (sc *Client) Write(b []byte) (int, error) { 165 | if !sc.connected { 166 | return 0, fmt.Errorf("call connect first") 167 | } 168 | return sc.Conn.Write(b) 169 | } 170 | 171 | // Close close the underlying connection 172 | func (sc *Client) Close() error { 173 | if !sc.closed { 174 | sc.closed = true 175 | return sc.Conn.Close() 176 | } 177 | return nil 178 | } 179 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package socks implement a socks4/4a/5 protocol server and client. 3 | 4 | only support CONNECT command now 5 | 6 | server example: 7 | 8 | // listen 1080 and act as socks server 9 | // support socks4, socks4a and socks5 10 | 11 | package main 12 | 13 | import ( 14 | socks "github.com/fangdingjun/socks-go" 15 | "log" 16 | "net" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | conn, err := net.Listen("tcp", ":1080") 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | for { 27 | c, err := conn.Accept() 28 | if err != nil { 29 | log.Println(err) 30 | continue 31 | } 32 | 33 | log.Printf("connected from %s", c.RemoteAddr()) 34 | 35 | d := net.Dialer{Timeout: 10 * time.Second} 36 | s := socks.Conn{Conn: c, Dial: d.Dial} 37 | go s.Serve() 38 | } 39 | } 40 | 41 | 42 | 43 | 44 | client example: 45 | 46 | // visit https://www.google.com through socks proxy server 47 | 48 | package main 49 | 50 | import ( 51 | "bufio" 52 | "crypto/tls" 53 | socks "github.com/fangdingjun/socks-go" 54 | "io" 55 | "log" 56 | "net" 57 | "net/http" 58 | "os" 59 | ) 60 | 61 | func main() { 62 | // connect to socks server 63 | c, err := net.Dial("tcp", "localhost:1080") 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | defer c.Close() 68 | 69 | sc := &socks.Client{Conn: c} 70 | 71 | // connect to remote server 72 | if err := sc.Connect("www.google.com", 443); err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | // tls 77 | conn := tls.Client(sc, &tls.Config{ServerName: "www.google.com"}) 78 | if err := conn.Handshake(); err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | // send http request 83 | req, err := http.NewRequest("GET", "https://www.google.com/", nil) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | req.Write(conn) 88 | 89 | bio := bufio.NewReader(conn) 90 | 91 | // read response 92 | res, err := http.ReadResponse(bio, req) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | defer res.Body.Close() 97 | 98 | io.Copy(os.Stdout, res.Body) 99 | } 100 | */ 101 | package socks 102 | -------------------------------------------------------------------------------- /examples/client/README.md: -------------------------------------------------------------------------------- 1 | 2 | this is a socks client example 3 | 4 | usage 5 | 6 | run server first, command 7 | 8 | go run $GOPATH/src/github.com/fandingjun/socks-go/examples/server/server.go 9 | 10 | then 11 | 12 | go run client.go 13 | -------------------------------------------------------------------------------- /examples/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os" 11 | 12 | socks "github.com/fangdingjun/socks-go" 13 | ) 14 | 15 | func main() { 16 | // connect to socks server 17 | c, err := net.Dial("tcp", "localhost:1080") 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | defer c.Close() 22 | 23 | sc := &socks.Client{Conn: c, Username: "admin", Password: "passwd"} 24 | 25 | // connect to remote server 26 | if err := sc.Connect("httpbin.org", 443); err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | // tls 31 | conn := tls.Client(sc, &tls.Config{ServerName: "httpbin.org"}) 32 | if err := conn.Handshake(); err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | // send http request 37 | req, err := http.NewRequest("GET", "https://httpbin.org/get", nil) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | req.Write(conn) 42 | 43 | bio := bufio.NewReader(conn) 44 | 45 | // read response 46 | res, err := http.ReadResponse(bio, req) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | defer res.Body.Close() 51 | 52 | io.Copy(os.Stdout, res.Body) 53 | } 54 | -------------------------------------------------------------------------------- /examples/server/.gitignore: -------------------------------------------------------------------------------- 1 | server 2 | -------------------------------------------------------------------------------- /examples/server/README.md: -------------------------------------------------------------------------------- 1 | socks-server 2 | ============ 3 | 4 | this is a socks server example. 5 | 6 | run it 7 | 8 | go run server.go 9 | 10 | test it 11 | 12 | curl --socks4 127.0.0.1:1080 http://www.google.com/ 13 | curl --socks4a 127.0.0.1:1080 http://www.google.com/ 14 | curl -U admin:passwd --socks5 127.0.0.1:1080 http://www.google.com/ 15 | curl -U admin:passwd --socks5-hostname 127.0.0.1:1080 http://www.google.com/ 16 | -------------------------------------------------------------------------------- /examples/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | socks "github.com/fangdingjun/socks-go" 9 | ) 10 | 11 | func main() { 12 | conn, err := net.Listen("tcp", ":1080") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | a := socks.PasswordAuth{Username: "admin", Password: "passwd"} 17 | for { 18 | c, err := conn.Accept() 19 | if err != nil { 20 | log.Println(err) 21 | continue 22 | } 23 | 24 | log.Printf("connected from %s", c.RemoteAddr()) 25 | 26 | d := net.Dialer{Timeout: 10 * time.Second} 27 | s := socks.Conn{Conn: c, Dial: d.Dial, Auth: &a} 28 | go s.Serve() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fangdingjun/socks-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /socks.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "time" 8 | ) 9 | 10 | const ( 11 | socks4Version = 0x04 12 | socks5Version = 0x05 13 | cmdConnect = 0x01 14 | addrTypeIPv4 = 0x01 15 | addrTypeDomain = 0x03 16 | addrTypeIPv6 = 0x04 17 | ) 18 | 19 | // DialFunc the function dial to remote 20 | type DialFunc func(network, addr string) (net.Conn, error) 21 | 22 | // Conn present a socks connection 23 | type Conn struct { 24 | net.Conn 25 | // the function to dial to upstream server 26 | // when nil, use net.Dial 27 | Dial DialFunc 28 | // Auth the auth service to authenticate the user for socks5 29 | Auth AuthService 30 | } 31 | 32 | // Serve serve the client 33 | func (s *Conn) Serve() { 34 | buf := make([]byte, 512) 35 | 36 | // read version 37 | n, err := io.ReadAtLeast(s.Conn, buf, 1) 38 | if err != nil { 39 | log.Println(err) 40 | return 41 | } 42 | 43 | dial := s.Dial 44 | if s.Dial == nil { 45 | d := net.Dialer{Timeout: 10 * time.Second} 46 | dial = d.Dial 47 | } 48 | 49 | switch buf[0] { 50 | case socks4Version: 51 | s4 := socks4Conn{clientConn: s.Conn, dial: dial} 52 | s4.Serve(buf, n) 53 | case socks5Version: 54 | s5 := socks5Conn{clientConn: s.Conn, dial: dial, auth: s.Auth} 55 | s5.Serve(buf, n) 56 | default: 57 | log.Printf("unknown socks version 0x%x", buf[0]) 58 | s.Conn.Close() 59 | } 60 | } 61 | 62 | func forward(c1, c2 io.ReadWriter) { 63 | 64 | c := make(chan struct{}, 2) 65 | 66 | go func() { 67 | io.Copy(c1, c2) 68 | c <- struct{}{} 69 | }() 70 | 71 | go func() { 72 | io.Copy(c2, c1) 73 | c <- struct{}{} 74 | }() 75 | 76 | <-c 77 | } 78 | -------------------------------------------------------------------------------- /socks4.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | //"log" 8 | "net" 9 | ) 10 | 11 | /* 12 | socks4 protocol 13 | 14 | request 15 | byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | 16 | |0x04|cmd| port | ip | user\0 | 17 | 18 | reply 19 | byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7| 20 | |0x00|status| | | 21 | 22 | 23 | socks4a protocol 24 | 25 | request 26 | byte | 0 | 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | ... |... | 27 | |0x04|cmd| port | 0.0.0.x | user\0 |domain\0| 28 | 29 | reply 30 | byte | 0 | 1 | 2 | 3 | 4 | 5 | 6| 7 | 31 | |0x00|staus| port | ip | 32 | 33 | */ 34 | type socks4Conn struct { 35 | serverConn net.Conn 36 | clientConn net.Conn 37 | dial DialFunc 38 | } 39 | 40 | func (s4 *socks4Conn) Serve(b []byte, n int) (err error) { 41 | defer s4.Close() 42 | 43 | if err = s4.processRequest(b, n); err != nil { 44 | //log.Println(err) 45 | return 46 | } 47 | return 48 | } 49 | 50 | func (s4 *socks4Conn) Close() { 51 | if s4.clientConn != nil { 52 | s4.clientConn.Close() 53 | } 54 | 55 | if s4.serverConn != nil { 56 | s4.serverConn.Close() 57 | } 58 | } 59 | 60 | func (s4 *socks4Conn) processRequest(buf []byte, n int) (err error) { 61 | // process command and target here 62 | 63 | if n < 8 { 64 | n1, err := io.ReadAtLeast(s4.clientConn, buf[n:], 8-n) 65 | if err != nil { 66 | return err 67 | } 68 | n += n1 69 | } 70 | 71 | buf = buf[1:n] 72 | 73 | // command only support connect 74 | if buf[0] != cmdConnect { 75 | return fmt.Errorf("error command %d", buf[0]) 76 | } 77 | 78 | // get port 79 | port := binary.BigEndian.Uint16(buf[1:3]) 80 | 81 | // get ip 82 | ip := net.IP(buf[3:7]) 83 | 84 | // NULL-terminated user string 85 | // jump to NULL character 86 | var j int 87 | for j = 7; j < n-1; j++ { 88 | if buf[j] == 0x00 { 89 | break 90 | } 91 | } 92 | 93 | host := ip.String() 94 | 95 | // socks4a 96 | // 0.0.0.x 97 | if ip[0] == 0x00 && ip[1] == 0x00 && ip[2] == 0x00 && ip[3] != 0x00 { 98 | j++ 99 | var i = j 100 | 101 | // jump to the end of hostname 102 | for j = i; j < n-1; j++ { 103 | if buf[j] == 0x00 { 104 | break 105 | } 106 | } 107 | host = string(buf[i:j]) 108 | } 109 | 110 | target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) 111 | 112 | //log.Printf("connecting to %s\r\n", target) 113 | 114 | // connect to the target 115 | s4.serverConn, err = s4.dial("tcp", target) 116 | if err != nil { 117 | // connection failed 118 | s4.clientConn.Write([]byte{0x00, 0x5b, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}) 119 | return err 120 | } 121 | 122 | // connection success 123 | s4.clientConn.Write([]byte{0x00, 0x5a, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}) 124 | 125 | // enter data exchange 126 | forward(s4.clientConn, s4.serverConn) 127 | 128 | return nil 129 | } 130 | -------------------------------------------------------------------------------- /socks5.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | 8 | //"log" 9 | "net" 10 | //"strconv" 11 | "encoding/binary" 12 | ) 13 | 14 | /* 15 | socks5 protocol 16 | 17 | initial 18 | 19 | byte | 0 | 1 | 2 | ...... | n | 20 | |0x05|num auth| auth methods | 21 | 22 | 23 | reply 24 | 25 | byte | 0 | 1 | 26 | |0x05| auth| 27 | 28 | 29 | username/password auth request 30 | 31 | byte | 0 | 1 | | 1 byte | | 32 | |0x01|username_len| username | password_len | password | 33 | 34 | username/password auth reponse 35 | 36 | byte | 0 | 1 | 37 | |0x01|status| 38 | 39 | request 40 | 41 | byte | 0 | 1 | 2 | 3 | 4 | .. | n-2 | n-1| n | 42 | |0x05|cmd|0x00|addrtype| addr | port | 43 | 44 | response 45 | byte |0 | 1 | 2 | 3 | 4 | .. | n-2 | n-1 | n | 46 | |0x05|status|0x00|addrtype| addr | port | 47 | 48 | */ 49 | 50 | // Socks5AuthRequired means socks5 server need auth or not 51 | 52 | type socks5Conn struct { 53 | //addr string 54 | clientConn net.Conn 55 | serverConn net.Conn 56 | dial DialFunc 57 | auth AuthService 58 | } 59 | 60 | func (s5 *socks5Conn) Serve(b []byte, n int) (err error) { 61 | defer s5.Close() 62 | 63 | if err = s5.handshake(b, n); err != nil { 64 | //log.Println(err) 65 | return 66 | } 67 | 68 | if err = s5.processRequest(); err != nil { 69 | //log.Println(err) 70 | return 71 | } 72 | return 73 | } 74 | 75 | func (s5 *socks5Conn) handshake(buf []byte, n int) (err error) { 76 | 77 | // read auth methods 78 | if n < 2 { 79 | n1, err := io.ReadAtLeast(s5.clientConn, buf[1:], 1) 80 | if err != nil { 81 | return err 82 | } 83 | n += n1 84 | } 85 | 86 | l := int(buf[1]) 87 | if n != (l + 2) { 88 | // read remains data 89 | n1, err := io.ReadFull(s5.clientConn, buf[n:l+2+1]) 90 | if err != nil { 91 | return err 92 | } 93 | n += n1 94 | } 95 | 96 | if s5.auth == nil { 97 | // no auth required 98 | s5.clientConn.Write([]byte{0x05, 0x00}) 99 | return nil 100 | } 101 | 102 | hasPassAuth := false 103 | var passAuth byte = 0x02 104 | 105 | // check auth method 106 | // only password(0x02) supported 107 | for i := 2; i < n; i++ { 108 | if buf[i] == passAuth { 109 | hasPassAuth = true 110 | break 111 | } 112 | } 113 | 114 | if !hasPassAuth { 115 | s5.clientConn.Write([]byte{0x05, 0xff}) 116 | return errors.New("no supported auth method") 117 | } 118 | 119 | err = s5.passwordAuth() 120 | return err 121 | } 122 | 123 | func (s5 *socks5Conn) passwordAuth() error { 124 | buf := make([]byte, 32) 125 | 126 | // username/password required 127 | s5.clientConn.Write([]byte{0x05, 0x02}) 128 | n, err := io.ReadAtLeast(s5.clientConn, buf, 2) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | //log.Printf("%+v", buf[:n]) 134 | 135 | // check auth version 136 | if buf[0] != 0x01 { 137 | return errors.New("unsupported auth version") 138 | } 139 | 140 | usernameLen := int(buf[1]) 141 | 142 | p0 := 2 143 | p1 := p0 + usernameLen 144 | 145 | for n < p1 { 146 | n1, err := s5.clientConn.Read(buf[n:]) 147 | if err != nil { 148 | return err 149 | } 150 | n += n1 151 | } 152 | 153 | username := buf[p0:p1] 154 | passwordLen := int(buf[p1]) 155 | 156 | p3 := p1 + 1 157 | p4 := p3 + passwordLen 158 | 159 | for n < p4 { 160 | n1, err := s5.clientConn.Read(buf[n:]) 161 | if err != nil { 162 | return err 163 | } 164 | n += n1 165 | } 166 | 167 | password := buf[p3:p4] 168 | 169 | // log.Printf("get username: %s, password: %s", username, password) 170 | 171 | if s5.auth != nil { 172 | ret := s5.auth.Authenticate( 173 | string(username), string(password), 174 | s5.clientConn.RemoteAddr()) 175 | if ret { 176 | s5.clientConn.Write([]byte{0x01, 0x00}) 177 | return nil 178 | } 179 | s5.clientConn.Write([]byte{0x01, 0x01}) 180 | 181 | return errors.New("access denied") 182 | } 183 | 184 | return errors.New("no auth method") 185 | } 186 | 187 | func (s5 *socks5Conn) processRequest() error { 188 | buf := make([]byte, 258) 189 | 190 | // read header 191 | n, err := io.ReadAtLeast(s5.clientConn, buf, 10) 192 | if err != nil { 193 | return err 194 | } 195 | 196 | if buf[0] != socks5Version { 197 | return fmt.Errorf("error version %d", buf[0]) 198 | } 199 | 200 | // command only support connect 201 | if buf[1] != cmdConnect { 202 | return fmt.Errorf("unsupported command %d", buf[1]) 203 | } 204 | 205 | hlen := 0 // target address length 206 | host := "" // target address 207 | msglen := 0 // header length 208 | 209 | switch buf[3] { 210 | case addrTypeIPv4: 211 | hlen = 4 212 | case addrTypeDomain: 213 | hlen = int(buf[4]) + 1 214 | case addrTypeIPv6: 215 | hlen = 16 216 | } 217 | 218 | msglen = 6 + hlen 219 | 220 | if n < msglen { 221 | // read remains header 222 | _, err := io.ReadFull(s5.clientConn, buf[n:msglen]) 223 | if err != nil { 224 | return err 225 | } 226 | } 227 | 228 | // get target address 229 | addr := buf[4 : 4+hlen] 230 | if buf[3] == addrTypeDomain { 231 | host = string(addr[1:]) 232 | } else { 233 | host = net.IP(addr).String() 234 | } 235 | 236 | // get target port 237 | port := binary.BigEndian.Uint16(buf[msglen-2 : msglen]) 238 | 239 | // target address 240 | target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) 241 | 242 | //log.Printf("connecing to %s\r\n", target) 243 | 244 | // connect to the target 245 | s5.serverConn, err = s5.dial("tcp", target) 246 | if err != nil { 247 | // connection failed 248 | s5.clientConn.Write([]byte{0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}) 249 | return err 250 | } 251 | 252 | // connection success 253 | s5.clientConn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}) 254 | 255 | // enter data exchange 256 | forward(s5.clientConn, s5.serverConn) 257 | 258 | return nil 259 | } 260 | 261 | func (s5 *socks5Conn) Close() { 262 | if s5.serverConn != nil { 263 | s5.serverConn.Close() 264 | } 265 | if s5.clientConn != nil { 266 | s5.clientConn.Close() 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /socks_test.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | //"bytes" 5 | //"fmt" 6 | "errors" 7 | "net" 8 | "testing" 9 | ) 10 | 11 | func TestSocks(t *testing.T) { 12 | if err := testSocks(t, "u1", "p1", true); err != nil { 13 | t.Error(err) 14 | } 15 | if err := testSocks(t, "", "", false); err != nil { 16 | t.Error(err) 17 | } 18 | if err := testSocks(t, "u3", "p3", true); err != nil { 19 | t.Error(err) 20 | } 21 | 22 | if err := testSocks(t, "u3", "p3", false); err != nil { 23 | t.Log(err) 24 | } else { 25 | t.Error("password not active") 26 | } 27 | 28 | if err := testSocks(t, "u3", "", false); err != nil { 29 | t.Log(err) 30 | } else { 31 | t.Error("password not active") 32 | } 33 | } 34 | 35 | func testSocks(t *testing.T, user, pass string, auth bool) error { 36 | l, err := net.Listen("tcp", "127.0.0.1:0") 37 | if err != nil { 38 | return err 39 | } 40 | defer l.Close() 41 | 42 | l1, err := net.Listen("tcp", "127.0.0.1:0") 43 | if err != nil { 44 | return err 45 | } 46 | defer l1.Close() 47 | 48 | addr := l.Addr().String() 49 | 50 | addr1 := l1.Addr() 51 | 52 | go func() { 53 | conn, err := l.Accept() 54 | if err != nil { 55 | return 56 | } 57 | t.Logf("connected from %s", conn.RemoteAddr()) 58 | s := Conn{Conn: conn, Auth: &PasswordAuth{user, pass}} 59 | s.Serve() 60 | }() 61 | 62 | go func() { 63 | conn, err := l1.Accept() 64 | if err != nil { 65 | return 66 | } 67 | t.Logf("server 2 accept connection from %s", conn.RemoteAddr()) 68 | defer conn.Close() 69 | buf := make([]byte, 512) 70 | n, err := conn.Read(buf) 71 | if err != nil { 72 | return 73 | } 74 | conn.Write(buf[:n]) 75 | }() 76 | 77 | c, err := net.Dial("tcp", addr) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | defer c.Close() 83 | var sc Client 84 | if auth { 85 | sc = Client{Conn: c, Username: user, Password: pass} 86 | } else { 87 | sc = Client{Conn: c} 88 | } 89 | if err = sc.Connect("localhost", uint16(addr1.(*net.TCPAddr).Port)); err != nil { 90 | return err 91 | } 92 | 93 | t.Logf("connect success") 94 | 95 | str := "hello1234" 96 | buf := make([]byte, 512) 97 | 98 | if _, err := sc.Write([]byte(str)); err != nil { 99 | return err 100 | } 101 | 102 | n, err := sc.Read(buf) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | if string(buf[:n]) != str { 108 | return errors.New("socks test failed") 109 | } 110 | return nil 111 | } 112 | --------------------------------------------------------------------------------