proxy_socks4.go 5.9 KB

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