1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package socks provides a SOCKS version 5 client implementation.
7 // SOCKS protocol version 5 is defined in RFC 1928.
8 // Username/Password authentication for SOCKS version 5 is defined in
20 // A Command represents a SOCKS command.
23 func (cmd Command) String() string {
26 return "socks connect"
30 return "socks " + strconv.Itoa(int(cmd))
34 // An AuthMethod represents a SOCKS authentication method.
37 // A Reply represents a SOCKS command reply code.
40 func (code Reply) String() string {
45 return "general SOCKS server failure"
47 return "connection not allowed by ruleset"
49 return "network unreachable"
51 return "host unreachable"
53 return "connection refused"
57 return "command not supported"
59 return "address type not supported"
61 return "unknown code: " + strconv.Itoa(int(code))
65 // Wire protocol constants.
73 CmdConnect Command = 0x01 // establishes an active-open forward proxy connection
74 cmdBind Command = 0x02 // establishes a passive-open forward proxy connection
76 AuthMethodNotRequired AuthMethod = 0x00 // no authentication required
77 AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password
78 AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods
80 StatusSucceeded Reply = 0x00
83 // An Addr represents a SOCKS-specific address.
84 // Either Name or IP is used exclusively.
86 Name string // fully-qualified domain name
91 func (a *Addr) Network() string { return "socks" }
93 func (a *Addr) String() string {
97 port := strconv.Itoa(a.Port)
99 return net.JoinHostPort(a.Name, port)
101 return net.JoinHostPort(a.IP.String(), port)
104 // A Conn represents a forward proxy connection.
111 // BoundAddr returns the address assigned by the proxy server for
112 // connecting to the command target address from the proxy server.
113 func (c *Conn) BoundAddr() net.Addr {
120 // A Dialer holds SOCKS-specific options.
122 cmd Command // either CmdConnect or cmdBind
123 proxyNetwork string // network between a proxy server and a client
124 proxyAddress string // proxy server address
126 // ProxyDial specifies the optional dial function for
127 // establishing the transport connection.
128 ProxyDial func(context.Context, string, string) (net.Conn, error)
130 // AuthMethods specifies the list of request authention
132 // If empty, SOCKS client requests only AuthMethodNotRequired.
133 AuthMethods []AuthMethod
135 // Authenticate specifies the optional authentication
136 // function. It must be non-nil when AuthMethods is not empty.
137 // It must return an error when the authentication is failed.
138 Authenticate func(context.Context, io.ReadWriter, AuthMethod) error
141 // DialContext connects to the provided address on the provided
144 // The returned error value may be a net.OpError. When the Op field of
145 // net.OpError contains "socks", the Source field contains a proxy
146 // server address and the Addr field contains a command target
149 // See func Dial of the net package of standard library for a
150 // description of the network and address parameters.
151 func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
152 if err := d.validateTarget(network, address); err != nil {
153 proxy, dst, _ := d.pathAddrs(address)
154 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
157 proxy, dst, _ := d.pathAddrs(address)
158 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
162 if d.ProxyDial != nil {
163 c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)
166 c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)
169 proxy, dst, _ := d.pathAddrs(address)
170 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
172 a, err := d.connect(ctx, c, address)
175 proxy, dst, _ := d.pathAddrs(address)
176 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
178 return &Conn{Conn: c, boundAddr: a}, nil
181 // DialWithConn initiates a connection from SOCKS server to the target
182 // network and address using the connection c that is already
183 // connected to the SOCKS server.
185 // It returns the connection's local address assigned by the SOCKS
187 func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) {
188 if err := d.validateTarget(network, address); err != nil {
189 proxy, dst, _ := d.pathAddrs(address)
190 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
193 proxy, dst, _ := d.pathAddrs(address)
194 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
196 a, err := d.connect(ctx, c, address)
198 proxy, dst, _ := d.pathAddrs(address)
199 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
204 // Dial connects to the provided address on the provided network.
206 // Unlike DialContext, it returns a raw transport connection instead
207 // of a forward proxy connection.
209 // Deprecated: Use DialContext or DialWithConn instead.
210 func (d *Dialer) Dial(network, address string) (net.Conn, error) {
211 if err := d.validateTarget(network, address); err != nil {
212 proxy, dst, _ := d.pathAddrs(address)
213 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
217 if d.ProxyDial != nil {
218 c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress)
220 c, err = net.Dial(d.proxyNetwork, d.proxyAddress)
223 proxy, dst, _ := d.pathAddrs(address)
224 return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
226 if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil {
233 func (d *Dialer) validateTarget(network, address string) error {
235 case "tcp", "tcp6", "tcp4":
237 return errors.New("network not implemented")
240 case CmdConnect, cmdBind:
242 return errors.New("command not implemented")
247 func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
248 for i, s := range []string{d.proxyAddress, address} {
249 host, port, err := splitHostPort(s)
253 a := &Addr{Port: port}
254 a.IP = net.ParseIP(host)
267 // NewDialer returns a new Dialer that dials through the provided
268 // proxy server's network and address.
269 func NewDialer(network, address string) *Dialer {
270 return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}
274 authUsernamePasswordVersion = 0x01
275 authStatusSucceeded = 0x00
278 // UsernamePassword are the credentials for the username/password
279 // authentication method.
280 type UsernamePassword struct {
285 // Authenticate authenticates a pair of username and password with the
287 func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {
289 case AuthMethodNotRequired:
291 case AuthMethodUsernamePassword:
292 if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 {
293 return errors.New("invalid username/password")
295 b := []byte{authUsernamePasswordVersion}
296 b = append(b, byte(len(up.Username)))
297 b = append(b, up.Username...)
298 b = append(b, byte(len(up.Password)))
299 b = append(b, up.Password...)
300 // TODO(mikio): handle IO deadlines and cancelation if
302 if _, err := rw.Write(b); err != nil {
305 if _, err := io.ReadFull(rw, b[:2]); err != nil {
308 if b[0] != authUsernamePasswordVersion {
309 return errors.New("invalid username/password version")
311 if b[1] != authStatusSucceeded {
312 return errors.New("username/password authentication failed")
316 return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))