client.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright (c) 2021 Tailscale Inc & 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. //go:build !js
  5. // +build !js
  6. // Package controlhttp implements the Tailscale 2021 control protocol
  7. // base transport over HTTP.
  8. //
  9. // This tunnels the protocol in control/controlbase over HTTP with a
  10. // variety of compatibility fallbacks for handling picky or deep
  11. // inspecting proxies.
  12. //
  13. // In the happy path, a client makes a single cleartext HTTP request
  14. // to the server, the server responds with 101 Switching Protocols,
  15. // and the control base protocol takes place over plain TCP.
  16. //
  17. // In the compatibility path, the client does the above over HTTPS,
  18. // resulting in double encryption (once for the control transport, and
  19. // once for the outer TLS layer).
  20. package controlhttp
  21. import (
  22. "context"
  23. "crypto/tls"
  24. "encoding/base64"
  25. "errors"
  26. "fmt"
  27. "io"
  28. "net"
  29. "net/http"
  30. "net/http/httptrace"
  31. "net/url"
  32. "time"
  33. "tailscale.com/control/controlbase"
  34. "tailscale.com/net/dnscache"
  35. "tailscale.com/net/dnsfallback"
  36. "tailscale.com/net/netutil"
  37. "tailscale.com/net/tlsdial"
  38. "tailscale.com/net/tshttpproxy"
  39. "tailscale.com/types/key"
  40. )
  41. // Dial connects to the HTTP server at addr, requests to switch to the
  42. // Tailscale control protocol, and returns an established control
  43. // protocol connection.
  44. //
  45. // If Dial fails to connect using addr, it also tries to tunnel over
  46. // TLS to <addr's host>:443 as a compatibility fallback.
  47. //
  48. // The provided ctx is only used for the initial connection, until
  49. // Dial returns. It does not affect the connection once established.
  50. func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
  51. host, port, err := net.SplitHostPort(addr)
  52. if err != nil {
  53. return nil, err
  54. }
  55. a := &dialParams{
  56. host: host,
  57. httpPort: port,
  58. httpsPort: "443",
  59. machineKey: machineKey,
  60. controlKey: controlKey,
  61. version: protocolVersion,
  62. proxyFunc: tshttpproxy.ProxyFromEnvironment,
  63. dialer: dialer,
  64. }
  65. return a.dial(ctx)
  66. }
  67. type dialParams struct {
  68. host string
  69. httpPort string
  70. httpsPort string
  71. machineKey key.MachinePrivate
  72. controlKey key.MachinePublic
  73. version uint16
  74. proxyFunc func(*http.Request) (*url.URL, error) // or nil
  75. dialer dnscache.DialContextFunc
  76. // For tests only
  77. insecureTLS bool
  78. testFallbackDelay time.Duration
  79. }
  80. // httpsFallbackDelay is how long we'll wait for a.httpPort to work before
  81. // starting to try a.httpsPort.
  82. func (a *dialParams) httpsFallbackDelay() time.Duration {
  83. if v := a.testFallbackDelay; v != 0 {
  84. return v
  85. }
  86. return 500 * time.Millisecond
  87. }
  88. func (a *dialParams) dial(ctx context.Context) (*controlbase.Conn, error) {
  89. // Create one shared context used by both port 80 and port 443 dials.
  90. // If port 80 is still in flight when 443 returns, this deferred cancel
  91. // will stop the port 80 dial.
  92. ctx, cancel := context.WithCancel(ctx)
  93. defer cancel()
  94. // u80 and u443 are the URLs we'll try to hit over HTTP or HTTPS,
  95. // respectively, in order to do the HTTP upgrade to a net.Conn over which
  96. // we'll speak Noise.
  97. u80 := &url.URL{
  98. Scheme: "http",
  99. Host: net.JoinHostPort(a.host, a.httpPort),
  100. Path: serverUpgradePath,
  101. }
  102. u443 := &url.URL{
  103. Scheme: "https",
  104. Host: net.JoinHostPort(a.host, a.httpsPort),
  105. Path: serverUpgradePath,
  106. }
  107. type tryURLRes struct {
  108. u *url.URL // input (the URL conn+err are for/from)
  109. conn *controlbase.Conn // result (mutually exclusive with err)
  110. err error
  111. }
  112. ch := make(chan tryURLRes) // must be unbuffered
  113. try := func(u *url.URL) {
  114. cbConn, err := a.dialURL(ctx, u)
  115. select {
  116. case ch <- tryURLRes{u, cbConn, err}:
  117. case <-ctx.Done():
  118. if cbConn != nil {
  119. cbConn.Close()
  120. }
  121. }
  122. }
  123. // Start the plaintext HTTP attempt first.
  124. go try(u80)
  125. // In case outbound port 80 blocked or MITM'ed poorly, start a backup timer
  126. // to dial port 443 if port 80 doesn't either succeed or fail quickly.
  127. try443Timer := time.AfterFunc(a.httpsFallbackDelay(), func() { try(u443) })
  128. defer try443Timer.Stop()
  129. var err80, err443 error
  130. for {
  131. select {
  132. case <-ctx.Done():
  133. return nil, fmt.Errorf("connection attempts aborted by context: %w", ctx.Err())
  134. case res := <-ch:
  135. if res.err == nil {
  136. return res.conn, nil
  137. }
  138. switch res.u {
  139. case u80:
  140. // Connecting over plain HTTP failed; assume it's an HTTP proxy
  141. // being difficult and see if we can get through over HTTPS.
  142. err80 = res.err
  143. // Stop the fallback timer and run it immediately. We don't use
  144. // Timer.Reset(0) here because on AfterFuncs, that can run it
  145. // again.
  146. if try443Timer.Stop() {
  147. go try(u443)
  148. } // else we lost the race and it started already which is what we want
  149. case u443:
  150. err443 = res.err
  151. default:
  152. panic("invalid")
  153. }
  154. if err80 != nil && err443 != nil {
  155. return nil, fmt.Errorf("all connection attempts failed (HTTP: %v, HTTPS: %v)", err80, err443)
  156. }
  157. }
  158. }
  159. }
  160. // dialURL attempts to connect to the given URL.
  161. func (a *dialParams) dialURL(ctx context.Context, u *url.URL) (*controlbase.Conn, error) {
  162. init, cont, err := controlbase.ClientDeferred(a.machineKey, a.controlKey, a.version)
  163. if err != nil {
  164. return nil, err
  165. }
  166. netConn, err := a.tryURLUpgrade(ctx, u, init)
  167. if err != nil {
  168. return nil, err
  169. }
  170. cbConn, err := cont(ctx, netConn)
  171. if err != nil {
  172. netConn.Close()
  173. return nil, err
  174. }
  175. return cbConn, nil
  176. }
  177. // tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn.
  178. //
  179. // Only the provided ctx is used, not a.ctx.
  180. func (a *dialParams) tryURLUpgrade(ctx context.Context, u *url.URL, init []byte) (net.Conn, error) {
  181. dns := &dnscache.Resolver{
  182. Forward: dnscache.Get().Forward,
  183. LookupIPFallback: dnsfallback.Lookup,
  184. UseLastGood: true,
  185. }
  186. tr := http.DefaultTransport.(*http.Transport).Clone()
  187. defer tr.CloseIdleConnections()
  188. tr.Proxy = a.proxyFunc
  189. tshttpproxy.SetTransportGetProxyConnectHeader(tr)
  190. tr.DialContext = dnscache.Dialer(a.dialer, dns)
  191. // Disable HTTP2, since h2 can't do protocol switching.
  192. tr.TLSClientConfig.NextProtos = []string{}
  193. tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
  194. tr.TLSClientConfig = tlsdial.Config(a.host, tr.TLSClientConfig)
  195. if a.insecureTLS {
  196. tr.TLSClientConfig.InsecureSkipVerify = true
  197. tr.TLSClientConfig.VerifyConnection = nil
  198. }
  199. tr.DialTLSContext = dnscache.TLSDialer(a.dialer, dns, tr.TLSClientConfig)
  200. tr.DisableCompression = true
  201. // (mis)use httptrace to extract the underlying net.Conn from the
  202. // transport. We make exactly 1 request using this transport, so
  203. // there will be exactly 1 GotConn call. Additionally, the
  204. // transport handles 101 Switching Protocols correctly, such that
  205. // the Conn will not be reused or kept alive by the transport once
  206. // the response has been handed back from RoundTrip.
  207. //
  208. // In theory, the machinery of net/http should make it such that
  209. // the trace callback happens-before we get the response, but
  210. // there's no promise of that. So, to make sure, we use a buffered
  211. // channel as a synchronization step to avoid data races.
  212. //
  213. // Note that even though we're able to extract a net.Conn via this
  214. // mechanism, we must still keep using the eventual resp.Body to
  215. // read from, because it includes a buffer we can't get rid of. If
  216. // the server never sends any data after sending the HTTP
  217. // response, we could get away with it, but violating this
  218. // assumption leads to very mysterious transport errors (lockups,
  219. // unexpected EOFs...), and we're bound to forget someday and
  220. // introduce a protocol optimization at a higher level that starts
  221. // eagerly transmitting from the server.
  222. connCh := make(chan net.Conn, 1)
  223. trace := httptrace.ClientTrace{
  224. GotConn: func(info httptrace.GotConnInfo) {
  225. connCh <- info.Conn
  226. },
  227. }
  228. ctx = httptrace.WithClientTrace(ctx, &trace)
  229. req := &http.Request{
  230. Method: "POST",
  231. URL: u,
  232. Header: http.Header{
  233. "Upgrade": []string{upgradeHeaderValue},
  234. "Connection": []string{"upgrade"},
  235. handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
  236. },
  237. }
  238. req = req.WithContext(ctx)
  239. resp, err := tr.RoundTrip(req)
  240. if err != nil {
  241. return nil, err
  242. }
  243. if resp.StatusCode != http.StatusSwitchingProtocols {
  244. return nil, fmt.Errorf("unexpected HTTP response: %s", resp.Status)
  245. }
  246. // From here on, the underlying net.Conn is ours to use, but there
  247. // is still a read buffer attached to it within resp.Body. So, we
  248. // must direct I/O through resp.Body, but we can still use the
  249. // underlying net.Conn for stuff like deadlines.
  250. var switchedConn net.Conn
  251. select {
  252. case switchedConn = <-connCh:
  253. default:
  254. }
  255. if switchedConn == nil {
  256. resp.Body.Close()
  257. return nil, fmt.Errorf("httptrace didn't provide a connection")
  258. }
  259. if next := resp.Header.Get("Upgrade"); next != upgradeHeaderValue {
  260. resp.Body.Close()
  261. return nil, fmt.Errorf("server switched to unexpected protocol %q", next)
  262. }
  263. rwc, ok := resp.Body.(io.ReadWriteCloser)
  264. if !ok {
  265. resp.Body.Close()
  266. return nil, errors.New("http Transport did not provide a writable body")
  267. }
  268. return netutil.NewAltReadWriteCloserConn(rwc, switchedConn), nil
  269. }