├── LICENSE ├── README.md └── socks ├── addr.go ├── conn.go └── dial.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Samuel Stauffer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the author nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SOCKS5 Proxy Package for Go 2 | =========================== 3 | 4 | Documentation: 5 | 6 | License 7 | ------- 8 | 9 | 3-clause BSD. See LICENSE file. 10 | -------------------------------------------------------------------------------- /socks/addr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package socks 6 | 7 | import "fmt" 8 | 9 | type proxiedAddr struct { 10 | net string 11 | host string 12 | port int 13 | } 14 | 15 | func (a *proxiedAddr) Network() string { 16 | return a.net 17 | } 18 | 19 | func (a *proxiedAddr) String() string { 20 | return fmt.Sprintf("%s:%d", a.host, a.port) 21 | } 22 | -------------------------------------------------------------------------------- /socks/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package socks 6 | 7 | import ( 8 | "net" 9 | "time" 10 | ) 11 | 12 | type proxiedConn struct { 13 | conn net.Conn 14 | remoteAddr *proxiedAddr 15 | boundAddr *proxiedAddr 16 | } 17 | 18 | func (c *proxiedConn) Read(b []byte) (int, error) { 19 | return c.conn.Read(b) 20 | } 21 | 22 | func (c *proxiedConn) Write(b []byte) (int, error) { 23 | return c.conn.Write(b) 24 | } 25 | 26 | func (c *proxiedConn) Close() error { 27 | return c.conn.Close() 28 | } 29 | 30 | func (c *proxiedConn) LocalAddr() net.Addr { 31 | if c.boundAddr != nil { 32 | return c.boundAddr 33 | } 34 | return c.LocalAddr() 35 | } 36 | 37 | func (c *proxiedConn) RemoteAddr() net.Addr { 38 | if c.remoteAddr != nil { 39 | return c.remoteAddr 40 | } 41 | return c.RemoteAddr() 42 | } 43 | 44 | func (c *proxiedConn) SetDeadline(t time.Time) error { 45 | return c.conn.SetDeadline(t) 46 | } 47 | 48 | func (c *proxiedConn) SetReadDeadline(t time.Time) error { 49 | return c.conn.SetReadDeadline(t) 50 | } 51 | 52 | func (c *proxiedConn) SetWriteDeadline(t time.Time) error { 53 | return c.conn.SetWriteDeadline(t) 54 | } 55 | -------------------------------------------------------------------------------- /socks/dial.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Current limitations: 7 | 8 | - GSS-API authentication is not supported 9 | - only SOCKS version 5 is supported 10 | - TCP bind and UDP not yet supported 11 | 12 | Example http client over SOCKS5: 13 | 14 | proxy := &socks.Proxy{"127.0.0.1:1080"} 15 | tr := &http.Transport{ 16 | Dial: proxy.Dial, 17 | } 18 | client := &http.Client{Transport: tr} 19 | resp, err := client.Get("https://example.com") 20 | */ 21 | package socks 22 | 23 | import ( 24 | "errors" 25 | "io" 26 | "net" 27 | "strconv" 28 | ) 29 | 30 | const ( 31 | protocolVersion = 5 32 | 33 | defaultPort = 1080 34 | 35 | authNone = 0 36 | authGssApi = 1 37 | authUsernamePassword = 2 38 | authUnavailable = 0xff 39 | 40 | commandTcpConnect = 1 41 | commandTcpBind = 2 42 | commandUdpAssociate = 3 43 | 44 | addressTypeIPv4 = 1 45 | addressTypeDomain = 3 46 | addressTypeIPv6 = 4 47 | 48 | statusRequestGranted = 0 49 | statusGeneralFailure = 1 50 | statusConnectionNotAllowed = 2 51 | statusNetworkUnreachable = 3 52 | statusHostUnreachable = 4 53 | statusConnectionRefused = 5 54 | statusTtlExpired = 6 55 | statusCommandNotSupport = 7 56 | statusAddressTypeNotSupported = 8 57 | ) 58 | 59 | var ( 60 | ErrAuthFailed = errors.New("authentication failed") 61 | ErrInvalidProxyResponse = errors.New("invalid proxy response") 62 | ErrNoAcceptableAuthMethod = errors.New("no acceptable authentication method") 63 | 64 | statusErrors = map[byte]error{ 65 | statusGeneralFailure: errors.New("general failure"), 66 | statusConnectionNotAllowed: errors.New("connection not allowed by ruleset"), 67 | statusNetworkUnreachable: errors.New("network unreachable"), 68 | statusHostUnreachable: errors.New("host unreachable"), 69 | statusConnectionRefused: errors.New("connection refused by destination host"), 70 | statusTtlExpired: errors.New("TTL expired"), 71 | statusCommandNotSupport: errors.New("command not supported / protocol error"), 72 | statusAddressTypeNotSupported: errors.New("address type not supported"), 73 | } 74 | ) 75 | 76 | type Proxy struct { 77 | Addr string 78 | Username string 79 | Password string 80 | } 81 | 82 | func (p *Proxy) Dial(network, addr string) (net.Conn, error) { 83 | host, strPort, err := net.SplitHostPort(addr) 84 | if err != nil { 85 | return nil, err 86 | } 87 | port, err := strconv.Atoi(strPort) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | conn, err := net.Dial("tcp", p.Addr) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | buf := make([]byte, 32+len(host)+len(p.Username)+len(p.Password)) 98 | 99 | // Initial greeting 100 | 101 | buf[0] = protocolVersion 102 | if p.Username != "" { 103 | buf = buf[:4] 104 | buf[1] = 2 // num auth methods 105 | buf[2] = authNone 106 | buf[3] = authUsernamePassword 107 | } else { 108 | buf = buf[:3] 109 | buf[1] = 1 // num auth methods 110 | buf[2] = authNone 111 | } 112 | 113 | _, err = conn.Write(buf) 114 | if err != nil { 115 | conn.Close() 116 | return nil, err 117 | } 118 | 119 | // Server's auth choice 120 | 121 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 122 | conn.Close() 123 | return nil, err 124 | } 125 | if buf[0] != protocolVersion { 126 | conn.Close() 127 | return nil, ErrInvalidProxyResponse 128 | } 129 | err = nil 130 | switch buf[1] { 131 | default: 132 | err = ErrInvalidProxyResponse 133 | case authUnavailable: 134 | err = ErrNoAcceptableAuthMethod 135 | case authGssApi: 136 | err = ErrNoAcceptableAuthMethod 137 | case authUsernamePassword: 138 | buf = buf[:3+len(p.Username)+len(p.Password)] 139 | buf[0] = 1 // version 140 | buf[1] = byte(len(p.Username)) 141 | copy(buf[2:], p.Username) 142 | buf[2+len(p.Username)] = byte(len(p.Password)) 143 | copy(buf[3+len(p.Username):], p.Password) 144 | if _, err = conn.Write(buf); err != nil { 145 | conn.Close() 146 | return nil, err 147 | } 148 | if _, err = io.ReadFull(conn, buf[:2]); err != nil { 149 | conn.Close() 150 | return nil, err 151 | } 152 | if buf[0] != 1 { // version 153 | err = ErrInvalidProxyResponse 154 | } else if buf[1] != 0 { // 0 = succes, else auth failed 155 | err = ErrAuthFailed 156 | } 157 | case authNone: 158 | // Do nothing 159 | } 160 | if err != nil { 161 | conn.Close() 162 | return nil, err 163 | } 164 | 165 | // Command / connection request 166 | 167 | buf = buf[:7+len(host)] 168 | buf[0] = protocolVersion 169 | buf[1] = commandTcpConnect 170 | buf[2] = 0 // reserved 171 | buf[3] = addressTypeDomain 172 | buf[4] = byte(len(host)) 173 | copy(buf[5:], host) 174 | buf[5+len(host)] = byte(port >> 8) 175 | buf[6+len(host)] = byte(port & 0xff) 176 | if _, err := conn.Write(buf); err != nil { 177 | conn.Close() 178 | return nil, err 179 | } 180 | 181 | // Server response 182 | 183 | if _, err := io.ReadFull(conn, buf[:4]); err != nil { 184 | conn.Close() 185 | return nil, err 186 | } 187 | 188 | if buf[0] != protocolVersion { 189 | conn.Close() 190 | return nil, ErrInvalidProxyResponse 191 | } 192 | 193 | if buf[1] != statusRequestGranted { 194 | conn.Close() 195 | err := statusErrors[buf[1]] 196 | if err == nil { 197 | err = ErrInvalidProxyResponse 198 | } 199 | return nil, err 200 | } 201 | 202 | paddr := &proxiedAddr{net: network} 203 | 204 | switch buf[3] { 205 | default: 206 | conn.Close() 207 | return nil, ErrInvalidProxyResponse 208 | case addressTypeIPv4: 209 | if _, err := io.ReadFull(conn, buf[:4]); err != nil { 210 | conn.Close() 211 | return nil, err 212 | } 213 | paddr.host = net.IP(buf).String() 214 | case addressTypeIPv6: 215 | if _, err := io.ReadFull(conn, buf[:16]); err != nil { 216 | conn.Close() 217 | return nil, err 218 | } 219 | paddr.host = net.IP(buf).String() 220 | case addressTypeDomain: 221 | if _, err := io.ReadFull(conn, buf[:1]); err != nil { 222 | conn.Close() 223 | return nil, err 224 | } 225 | domainLen := buf[0] 226 | if _, err := io.ReadFull(conn, buf[:domainLen]); err != nil { 227 | conn.Close() 228 | return nil, err 229 | } 230 | paddr.host = string(buf[:domainLen]) 231 | } 232 | 233 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 234 | conn.Close() 235 | return nil, err 236 | } 237 | paddr.port = int(buf[0])<<8 | int(buf[1]) 238 | 239 | return &proxiedConn{ 240 | conn: conn, 241 | boundAddr: paddr, 242 | remoteAddr: &proxiedAddr{network, host, port}, 243 | }, nil 244 | } 245 | --------------------------------------------------------------------------------