socks.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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.
  4. // Package socks provides a SOCKS version 5 client implementation.
  5. //
  6. // SOCKS protocol version 5 is defined in RFC 1928.
  7. // Username/Password authentication for SOCKS version 5 is defined in
  8. // RFC 1929.
  9. package socks
  10. import (
  11. "context"
  12. "errors"
  13. "io"
  14. "net"
  15. "strconv"
  16. )
  17. // A Command represents a SOCKS command.
  18. type Command int
  19. func (cmd Command) String() string {
  20. switch cmd {
  21. case CmdConnect:
  22. return "socks connect"
  23. case cmdBind:
  24. return "socks bind"
  25. default:
  26. return "socks " + strconv.Itoa(int(cmd))
  27. }
  28. }
  29. // An AuthMethod represents a SOCKS authentication method.
  30. type AuthMethod int
  31. // A Reply represents a SOCKS command reply code.
  32. type Reply int
  33. func (code Reply) String() string {
  34. switch code {
  35. case StatusSucceeded:
  36. return "succeeded"
  37. case 0x01:
  38. return "general SOCKS server failure"
  39. case 0x02:
  40. return "connection not allowed by ruleset"
  41. case 0x03:
  42. return "network unreachable"
  43. case 0x04:
  44. return "host unreachable"
  45. case 0x05:
  46. return "connection refused"
  47. case 0x06:
  48. return "TTL expired"
  49. case 0x07:
  50. return "command not supported"
  51. case 0x08:
  52. return "address type not supported"
  53. default:
  54. return "unknown code: " + strconv.Itoa(int(code))
  55. }
  56. }
  57. // Wire protocol constants.
  58. const (
  59. Version5 = 0x05
  60. AddrTypeIPv4 = 0x01
  61. AddrTypeFQDN = 0x03
  62. AddrTypeIPv6 = 0x04
  63. CmdConnect Command = 0x01 // establishes an active-open forward proxy connection
  64. cmdBind Command = 0x02 // establishes a passive-open forward proxy connection
  65. AuthMethodNotRequired AuthMethod = 0x00 // no authentication required
  66. AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password
  67. AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authetication methods
  68. StatusSucceeded Reply = 0x00
  69. )
  70. // An Addr represents a SOCKS-specific address.
  71. // Either Name or IP is used exclusively.
  72. type Addr struct {
  73. Name string // fully-qualified domain name
  74. IP net.IP
  75. Port int
  76. }
  77. func (a *Addr) Network() string { return "socks" }
  78. func (a *Addr) String() string {
  79. if a == nil {
  80. return "<nil>"
  81. }
  82. port := strconv.Itoa(a.Port)
  83. if a.IP == nil {
  84. return net.JoinHostPort(a.Name, port)
  85. }
  86. return net.JoinHostPort(a.IP.String(), port)
  87. }
  88. // A Conn represents a forward proxy connection.
  89. type Conn struct {
  90. net.Conn
  91. boundAddr net.Addr
  92. }
  93. // BoundAddr returns the address assigned by the proxy server for
  94. // connecting to the command target address from the proxy server.
  95. func (c *Conn) BoundAddr() net.Addr {
  96. if c == nil {
  97. return nil
  98. }
  99. return c.boundAddr
  100. }
  101. // A Dialer holds SOCKS-specific options.
  102. type Dialer struct {
  103. cmd Command // either CmdConnect or cmdBind
  104. proxyNetwork string // network between a proxy server and a client
  105. proxyAddress string // proxy server address
  106. // ProxyDial specifies the optional dial function for
  107. // establishing the transport connection.
  108. ProxyDial func(context.Context, string, string) (net.Conn, error)
  109. // AuthMethods specifies the list of request authention
  110. // methods.
  111. // If empty, SOCKS client requests only AuthMethodNotRequired.
  112. AuthMethods []AuthMethod
  113. // Authenticate specifies the optional authentication
  114. // function. It must be non-nil when AuthMethods is not empty.
  115. // It must return an error when the authentication is failed.
  116. Authenticate func(context.Context, io.ReadWriter, AuthMethod) error
  117. }
  118. // DialContext connects to the provided address on the provided
  119. // network.
  120. //
  121. // The returned error value may be a net.OpError. When the Op field of
  122. // net.OpError contains "socks", the Source field contains a proxy
  123. // server address and the Addr field contains a command target
  124. // address.
  125. //
  126. // See func Dial of the net package of standard library for a
  127. // description of the network and address parameters.
  128. func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
  129. switch network {
  130. case "tcp", "tcp6", "tcp4":
  131. default:
  132. proxy, dst, _ := d.pathAddrs(address)
  133. return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("network not implemented")}
  134. }
  135. switch d.cmd {
  136. case CmdConnect, cmdBind:
  137. default:
  138. proxy, dst, _ := d.pathAddrs(address)
  139. return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("command not implemented")}
  140. }
  141. if ctx == nil {
  142. ctx = context.Background()
  143. }
  144. var err error
  145. var c net.Conn
  146. if d.ProxyDial != nil {
  147. c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)
  148. } else {
  149. var dd net.Dialer
  150. c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)
  151. }
  152. if 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}
  155. }
  156. a, err := d.connect(ctx, c, address)
  157. if err != nil {
  158. c.Close()
  159. proxy, dst, _ := d.pathAddrs(address)
  160. return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
  161. }
  162. return &Conn{Conn: c, boundAddr: a}, nil
  163. }
  164. // Dial connects to the provided address on the provided network.
  165. //
  166. // Deprecated: Use DialContext instead.
  167. func (d *Dialer) Dial(network, address string) (net.Conn, error) {
  168. return d.DialContext(context.Background(), network, address)
  169. }
  170. func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
  171. for i, s := range []string{d.proxyAddress, address} {
  172. host, port, err := splitHostPort(s)
  173. if err != nil {
  174. return nil, nil, err
  175. }
  176. a := &Addr{Port: port}
  177. a.IP = net.ParseIP(host)
  178. if a.IP == nil {
  179. a.Name = host
  180. }
  181. if i == 0 {
  182. proxy = a
  183. } else {
  184. dst = a
  185. }
  186. }
  187. return
  188. }
  189. // NewDialer returns a new Dialer that dials through the provided
  190. // proxy server's network and address.
  191. func NewDialer(network, address string) *Dialer {
  192. return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}
  193. }
  194. const (
  195. authUsernamePasswordVersion = 0x01
  196. authStatusSucceeded = 0x00
  197. )
  198. // UsernamePassword are the credentials for the username/password
  199. // authentication method.
  200. type UsernamePassword struct {
  201. Username string
  202. Password string
  203. }
  204. // Authenticate authenticates a pair of username and password with the
  205. // proxy server.
  206. func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {
  207. switch auth {
  208. case AuthMethodNotRequired:
  209. return nil
  210. case AuthMethodUsernamePassword:
  211. if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 {
  212. return errors.New("invalid username/password")
  213. }
  214. b := []byte{authUsernamePasswordVersion}
  215. b = append(b, byte(len(up.Username)))
  216. b = append(b, up.Username...)
  217. b = append(b, byte(len(up.Password)))
  218. b = append(b, up.Password...)
  219. // TODO(mikio): handle IO deadlines and cancelation if
  220. // necessary
  221. if _, err := rw.Write(b); err != nil {
  222. return err
  223. }
  224. if _, err := io.ReadFull(rw, b[:2]); err != nil {
  225. return err
  226. }
  227. if b[0] != authUsernamePasswordVersion {
  228. return errors.New("invalid username/password version")
  229. }
  230. if b[1] != authStatusSucceeded {
  231. return errors.New("username/password authentication failed")
  232. }
  233. return nil
  234. }
  235. return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))
  236. }