TCPConn_bind.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // +build !windows
  2. /*
  3. * Copyright (c) 2015, Psiphon Inc.
  4. * All rights reserved.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. package psiphon
  21. import (
  22. "errors"
  23. "fmt"
  24. "math/rand"
  25. "net"
  26. "os"
  27. "strconv"
  28. "syscall"
  29. "github.com/Psiphon-Inc/goarista/monotime"
  30. "github.com/Psiphon-Inc/goselect"
  31. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  32. )
  33. // tcpDial is the platform-specific part of interruptibleTCPDial
  34. //
  35. // To implement socket device binding, the lower-level syscall APIs are used.
  36. // The sequence of syscalls in this implementation are taken from:
  37. // https://github.com/golang/go/issues/6966
  38. // (originally: https://code.google.com/p/go/issues/detail?id=6966)
  39. func tcpDial(addr string, config *DialConfig) (net.Conn, error) {
  40. // Get the remote IP and port, resolving a domain name if necessary
  41. host, strPort, err := net.SplitHostPort(addr)
  42. if err != nil {
  43. return nil, common.ContextError(err)
  44. }
  45. port, err := strconv.Atoi(strPort)
  46. if err != nil {
  47. return nil, common.ContextError(err)
  48. }
  49. ipAddrs, err := LookupIP(host, config)
  50. if err != nil {
  51. return nil, common.ContextError(err)
  52. }
  53. if len(ipAddrs) < 1 {
  54. return nil, common.ContextError(errors.New("no IP address"))
  55. }
  56. // When configured, attempt to synthesize IPv6 addresses from
  57. // an IPv4 addresses for compatibility on DNS64/NAT64 networks.
  58. // If synthesize fails, try the original addresses.
  59. if config.IPv6Synthesizer != nil {
  60. for i, ipAddr := range ipAddrs {
  61. if ipAddr.To4() != nil {
  62. synthesizedIPAddress := config.IPv6Synthesizer.IPv6Synthesize(ipAddr.String())
  63. if synthesizedIPAddress != "" {
  64. synthesizedAddr := net.ParseIP(synthesizedIPAddress)
  65. if synthesizedAddr != nil {
  66. ipAddrs[i] = synthesizedAddr
  67. }
  68. }
  69. }
  70. }
  71. }
  72. // Iterate over a pseudorandom permutation of the destination
  73. // IPs and attempt connections.
  74. //
  75. // Only continue retrying as long as the initial ConnectTimeout
  76. // has not expired. Unlike net.Dial, we do not fractionalize the
  77. // timeout, as the ConnectTimeout is generally intended to apply
  78. // to a single attempt. So these serial retries are most useful
  79. // in cases of immediate failure, such as "no route to host"
  80. // errors when a host resolves to both IPv4 and IPv6 but IPv6
  81. // addresses are unreachable.
  82. // Retries at higher levels cover other cases: e.g.,
  83. // Controller.remoteServerListFetcher will retry its entire
  84. // operation and tcpDial will try a new permutation; or similarly,
  85. // Controller.establishCandidateGenerator will retry a candidate
  86. // tunnel server dials.
  87. permutedIndexes := rand.Perm(len(ipAddrs))
  88. lastErr := errors.New("unknown error")
  89. var deadline monotime.Time
  90. if config.ConnectTimeout != 0 {
  91. deadline = monotime.Now().Add(config.ConnectTimeout)
  92. }
  93. for iteration, index := range permutedIndexes {
  94. if iteration > 0 && deadline != 0 && monotime.Now().After(deadline) {
  95. // lastErr should be set by the previous iteration
  96. break
  97. }
  98. // Get address type (IPv4 or IPv6)
  99. var ipv4 [4]byte
  100. var ipv6 [16]byte
  101. var domain int
  102. var sockAddr syscall.Sockaddr
  103. ipAddr := ipAddrs[index]
  104. if ipAddr != nil && ipAddr.To4() != nil {
  105. copy(ipv4[:], ipAddr.To4())
  106. domain = syscall.AF_INET
  107. } else if ipAddr != nil && ipAddr.To16() != nil {
  108. copy(ipv6[:], ipAddr.To16())
  109. domain = syscall.AF_INET6
  110. } else {
  111. lastErr = common.ContextError(fmt.Errorf("invalid IP address: %s", ipAddr.String()))
  112. continue
  113. }
  114. if domain == syscall.AF_INET {
  115. sockAddr = &syscall.SockaddrInet4{Addr: ipv4, Port: port}
  116. } else if domain == syscall.AF_INET6 {
  117. sockAddr = &syscall.SockaddrInet6{Addr: ipv6, Port: port}
  118. }
  119. // Create a socket and bind to device, when configured to do so
  120. socketFd, err := syscall.Socket(domain, syscall.SOCK_STREAM, 0)
  121. if err != nil {
  122. lastErr = common.ContextError(err)
  123. continue
  124. }
  125. tcpDialSetAdditionalSocketOptions(socketFd)
  126. if config.DeviceBinder != nil {
  127. // WARNING: this potentially violates the direction to not call into
  128. // external components after the Controller may have been stopped.
  129. // TODO: rework DeviceBinder as an internal 'service' which can trap
  130. // external calls when they should not be made?
  131. err = config.DeviceBinder.BindToDevice(socketFd)
  132. if err != nil {
  133. syscall.Close(socketFd)
  134. lastErr = common.ContextError(fmt.Errorf("BindToDevice failed: %s", err))
  135. continue
  136. }
  137. }
  138. // Connect socket to the server's IP address
  139. err = syscall.SetNonblock(socketFd, true)
  140. if err != nil {
  141. syscall.Close(socketFd)
  142. lastErr = common.ContextError(err)
  143. continue
  144. }
  145. err = syscall.Connect(socketFd, sockAddr)
  146. if err != nil {
  147. if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINPROGRESS {
  148. syscall.Close(socketFd)
  149. lastErr = common.ContextError(err)
  150. continue
  151. }
  152. }
  153. fdset := &goselect.FDSet{}
  154. fdset.Set(uintptr(socketFd))
  155. timeout := config.ConnectTimeout
  156. if config.ConnectTimeout == 0 {
  157. timeout = -1
  158. }
  159. err = goselect.Select(socketFd+1, nil, fdset, nil, timeout)
  160. if err != nil {
  161. syscall.Close(socketFd)
  162. lastErr = common.ContextError(err)
  163. continue
  164. }
  165. if !fdset.IsSet(uintptr(socketFd)) {
  166. syscall.Close(socketFd)
  167. lastErr = common.ContextError(errors.New("connect timed out"))
  168. continue
  169. }
  170. err = syscall.SetNonblock(socketFd, false)
  171. if err != nil {
  172. syscall.Close(socketFd)
  173. lastErr = common.ContextError(err)
  174. continue
  175. }
  176. // Convert the socket fd to a net.Conn
  177. file := os.NewFile(uintptr(socketFd), "")
  178. netConn, err := net.FileConn(file) // net.FileConn() dups socketFd
  179. file.Close() // file.Close() closes socketFd
  180. if err != nil {
  181. lastErr = common.ContextError(err)
  182. continue
  183. }
  184. return netConn, nil
  185. }
  186. return nil, lastErr
  187. }