proxy_socks4.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. * Copyright (c) 2014, Yawning Angel <yawning at schwanenlied dot me>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * * Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. *
  11. * * Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  19. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  20. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  21. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  22. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  23. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  24. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  25. * POSSIBILITY OF SUCH DAMAGE.
  26. *
  27. * This is inspired by go.net/proxy/socks5.go:
  28. *
  29. * Copyright 2011 The Go Authors. All rights reserved.
  30. * Use of this source code is governed by a BSD-style
  31. * license that can be found in the LICENSE file.
  32. */
  33. package main
  34. import (
  35. "errors"
  36. "fmt"
  37. "io"
  38. "net"
  39. "net/url"
  40. "strconv"
  41. "golang.org/x/net/proxy"
  42. )
  43. // socks4Proxy is a SOCKS4 proxy.
  44. type socks4Proxy struct {
  45. hostPort string
  46. username string
  47. forward proxy.Dialer
  48. }
  49. const (
  50. socks4Version = 0x04
  51. socks4CommandConnect = 0x01
  52. socks4Null = 0x00
  53. socks4ReplyVersion = 0x00
  54. socks4Granted = 0x5a
  55. socks4Rejected = 0x5b
  56. socks4RejectedIdentdFailed = 0x5c
  57. socks4RejectedIdentdMismatch = 0x5d
  58. )
  59. func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
  60. s := new(socks4Proxy)
  61. s.hostPort = uri.Host
  62. s.forward = forward
  63. if uri.User != nil {
  64. s.username = uri.User.Username()
  65. }
  66. return s, nil
  67. }
  68. func (s *socks4Proxy) Dial(network, addr string) (net.Conn, error) {
  69. if network != "tcp" && network != "tcp4" {
  70. return nil, errors.New("invalid network type")
  71. }
  72. // Deal with the destination address/string.
  73. ipStr, portStr, err := net.SplitHostPort(addr)
  74. if err != nil {
  75. return nil, err
  76. }
  77. ip := net.ParseIP(ipStr)
  78. if ip == nil {
  79. return nil, errors.New("failed to parse destination IP")
  80. }
  81. ip4 := ip.To4()
  82. if ip4 == nil {
  83. return nil, errors.New("destination address is not IPv4")
  84. }
  85. port, err := strconv.ParseUint(portStr, 10, 16)
  86. if err != nil {
  87. return nil, err
  88. }
  89. // Connect to the proxy.
  90. c, err := s.forward.Dial("tcp", s.hostPort)
  91. if err != nil {
  92. return nil, err
  93. }
  94. // Make/write the request:
  95. // +----+----+----+----+----+----+----+----+----+----+....+----+
  96. // | VN | CD | DSTPORT | DSTIP | USERID |NULL|
  97. // +----+----+----+----+----+----+----+----+----+----+....+----+
  98. req := make([]byte, 0, 9+len(s.username))
  99. req = append(req, socks4Version)
  100. req = append(req, socks4CommandConnect)
  101. req = append(req, byte(port>>8), byte(port))
  102. req = append(req, ip4...)
  103. if s.username != "" {
  104. req = append(req, s.username...)
  105. }
  106. req = append(req, socks4Null)
  107. _, err = c.Write(req)
  108. if err != nil {
  109. c.Close()
  110. return nil, err
  111. }
  112. // Read the response:
  113. // +----+----+----+----+----+----+----+----+
  114. // | VN | CD | DSTPORT | DSTIP |
  115. // +----+----+----+----+----+----+----+----+
  116. var resp [8]byte
  117. _, err = io.ReadFull(c, resp[:])
  118. if err != nil {
  119. c.Close()
  120. return nil, err
  121. }
  122. if resp[0] != socks4ReplyVersion {
  123. c.Close()
  124. return nil, errors.New("proxy returned invalid SOCKS4 version")
  125. }
  126. if resp[1] != socks4Granted {
  127. c.Close()
  128. return nil, fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1]))
  129. }
  130. return c, nil
  131. }
  132. func socks4ErrorToString(code byte) string {
  133. switch code {
  134. case socks4Rejected:
  135. return "request rejected or failed"
  136. case socks4RejectedIdentdFailed:
  137. return "request rejected because SOCKS server cannot connect to identd on the client"
  138. case socks4RejectedIdentdMismatch:
  139. return "request rejected because the client program and identd report different user-ids"
  140. default:
  141. return fmt.Sprintf("unknown failure code %x", code)
  142. }
  143. }
  144. func init() {
  145. // Despite the scheme name, this really is SOCKS4.
  146. proxy.RegisterDialerType("socks4a", newSOCKS4)
  147. }