client.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. // Package controlhttp implements the Tailscale 2021 control protocol
  5. // base transport over HTTP.
  6. //
  7. // This tunnels the protocol in control/controlbase over HTTP with a
  8. // variety of compatibility fallbacks for handling picky or deep
  9. // inspecting proxies.
  10. //
  11. // In the happy path, a client makes a single cleartext HTTP request
  12. // to the server, the server responds with 101 Switching Protocols,
  13. // and the control base protocol takes place over plain TCP.
  14. //
  15. // In the compatibility path, the client does the above over HTTPS,
  16. // resulting in double encryption (once for the control transport, and
  17. // once for the outer TLS layer).
  18. package controlhttp
  19. import (
  20. "context"
  21. "crypto/tls"
  22. "encoding/base64"
  23. "errors"
  24. "fmt"
  25. "io"
  26. "log"
  27. "net"
  28. "net/http"
  29. "net/http/httptrace"
  30. "net/url"
  31. "tailscale.com/control/controlbase"
  32. "tailscale.com/net/dnscache"
  33. "tailscale.com/net/dnsfallback"
  34. "tailscale.com/net/netns"
  35. "tailscale.com/net/tlsdial"
  36. "tailscale.com/net/tshttpproxy"
  37. "tailscale.com/types/key"
  38. )
  39. // upgradeHeader is the value of the Upgrade HTTP header used to
  40. // indicate the Tailscale control protocol.
  41. const (
  42. upgradeHeaderValue = "tailscale-control-protocol"
  43. handshakeHeaderName = "X-Tailscale-Handshake"
  44. )
  45. // Dial connects to the HTTP server at addr, requests to switch to the
  46. // Tailscale control protocol, and returns an established control
  47. // protocol connection.
  48. //
  49. // If Dial fails to connect using addr, it also tries to tunnel over
  50. // TLS to <addr's host>:443 as a compatibility fallback.
  51. func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic) (*controlbase.Conn, error) {
  52. host, port, err := net.SplitHostPort(addr)
  53. if err != nil {
  54. return nil, err
  55. }
  56. a := &dialParams{
  57. ctx: ctx,
  58. host: host,
  59. httpPort: port,
  60. httpsPort: "443",
  61. machineKey: machineKey,
  62. controlKey: controlKey,
  63. proxyFunc: tshttpproxy.ProxyFromEnvironment,
  64. }
  65. return a.dial()
  66. }
  67. type dialParams struct {
  68. ctx context.Context
  69. host string
  70. httpPort string
  71. httpsPort string
  72. machineKey key.MachinePrivate
  73. controlKey key.MachinePublic
  74. proxyFunc func(*http.Request) (*url.URL, error) // or nil
  75. // For tests only
  76. insecureTLS bool
  77. }
  78. func (a *dialParams) dial() (*controlbase.Conn, error) {
  79. init, cont, err := controlbase.ClientDeferred(a.machineKey, a.controlKey)
  80. if err != nil {
  81. return nil, err
  82. }
  83. u := &url.URL{
  84. Scheme: "http",
  85. Host: net.JoinHostPort(a.host, a.httpPort),
  86. Path: "/switch",
  87. }
  88. conn, httpErr := a.tryURL(u, init)
  89. if httpErr == nil {
  90. ret, err := cont(a.ctx, conn)
  91. if err != nil {
  92. conn.Close()
  93. return nil, err
  94. }
  95. return ret, nil
  96. }
  97. // Connecting over plain HTTP failed, assume it's an HTTP proxy
  98. // being difficult and see if we can get through over HTTPS.
  99. u.Scheme = "https"
  100. u.Host = net.JoinHostPort(a.host, a.httpsPort)
  101. init, cont, err = controlbase.ClientDeferred(a.machineKey, a.controlKey)
  102. if err != nil {
  103. return nil, err
  104. }
  105. conn, tlsErr := a.tryURL(u, init)
  106. if tlsErr == nil {
  107. ret, err := cont(a.ctx, conn)
  108. if err != nil {
  109. conn.Close()
  110. return nil, err
  111. }
  112. return ret, nil
  113. }
  114. return nil, fmt.Errorf("all connection attempts failed (HTTP: %v, HTTPS: %v)", httpErr, tlsErr)
  115. }
  116. func (a *dialParams) tryURL(u *url.URL, init []byte) (net.Conn, error) {
  117. dns := &dnscache.Resolver{
  118. Forward: dnscache.Get().Forward,
  119. LookupIPFallback: dnsfallback.Lookup,
  120. UseLastGood: true,
  121. }
  122. dialer := netns.NewDialer(log.Printf)
  123. tr := http.DefaultTransport.(*http.Transport).Clone()
  124. defer tr.CloseIdleConnections()
  125. tr.Proxy = a.proxyFunc
  126. tshttpproxy.SetTransportGetProxyConnectHeader(tr)
  127. tr.DialContext = dnscache.Dialer(dialer.DialContext, dns)
  128. // Disable HTTP2, since h2 can't do protocol switching.
  129. tr.TLSClientConfig.NextProtos = []string{}
  130. tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
  131. tr.TLSClientConfig = tlsdial.Config(a.host, tr.TLSClientConfig)
  132. if a.insecureTLS {
  133. tr.TLSClientConfig.InsecureSkipVerify = true
  134. tr.TLSClientConfig.VerifyConnection = nil
  135. }
  136. tr.DialTLSContext = dnscache.TLSDialer(dialer.DialContext, dns, tr.TLSClientConfig)
  137. tr.DisableCompression = true
  138. // (mis)use httptrace to extract the underlying net.Conn from the
  139. // transport. We make exactly 1 request using this transport, so
  140. // there will be exactly 1 GotConn call. Additionally, the
  141. // transport handles 101 Switching Protocols correctly, such that
  142. // the Conn will not be reused or kept alive by the transport once
  143. // the response has been handed back from RoundTrip.
  144. //
  145. // In theory, the machinery of net/http should make it such that
  146. // the trace callback happens-before we get the response, but
  147. // there's no promise of that. So, to make sure, we use a buffered
  148. // channel as a synchronization step to avoid data races.
  149. //
  150. // Note that even though we're able to extract a net.Conn via this
  151. // mechanism, we must still keep using the eventual resp.Body to
  152. // read from, because it includes a buffer we can't get rid of. If
  153. // the server never sends any data after sending the HTTP
  154. // response, we could get away with it, but violating this
  155. // assumption leads to very mysterious transport errors (lockups,
  156. // unexpected EOFs...), and we're bound to forget someday and
  157. // introduce a protocol optimization at a higher level that starts
  158. // eagerly transmitting from the server.
  159. connCh := make(chan net.Conn, 1)
  160. trace := httptrace.ClientTrace{
  161. GotConn: func(info httptrace.GotConnInfo) {
  162. connCh <- info.Conn
  163. },
  164. }
  165. ctx := httptrace.WithClientTrace(a.ctx, &trace)
  166. req := &http.Request{
  167. Method: "POST",
  168. URL: u,
  169. Header: http.Header{
  170. "Upgrade": []string{upgradeHeaderValue},
  171. "Connection": []string{"upgrade"},
  172. handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
  173. },
  174. }
  175. req = req.WithContext(ctx)
  176. resp, err := tr.RoundTrip(req)
  177. if err != nil {
  178. return nil, err
  179. }
  180. if resp.StatusCode != http.StatusSwitchingProtocols {
  181. return nil, fmt.Errorf("unexpected HTTP response: %s", resp.Status)
  182. }
  183. // From here on, the underlying net.Conn is ours to use, but there
  184. // is still a read buffer attached to it within resp.Body. So, we
  185. // must direct I/O through resp.Body, but we can still use the
  186. // underlying net.Conn for stuff like deadlines.
  187. var switchedConn net.Conn
  188. select {
  189. case switchedConn = <-connCh:
  190. default:
  191. }
  192. if switchedConn == nil {
  193. resp.Body.Close()
  194. return nil, fmt.Errorf("httptrace didn't provide a connection")
  195. }
  196. if next := resp.Header.Get("Upgrade"); next != upgradeHeaderValue {
  197. resp.Body.Close()
  198. return nil, fmt.Errorf("server switched to unexpected protocol %q", next)
  199. }
  200. rwc, ok := resp.Body.(io.ReadWriteCloser)
  201. if !ok {
  202. resp.Body.Close()
  203. return nil, errors.New("http Transport did not provide a writable body")
  204. }
  205. return &wrappedConn{switchedConn, rwc}, nil
  206. }
  207. type wrappedConn struct {
  208. net.Conn
  209. rwc io.ReadWriteCloser
  210. }
  211. func (w *wrappedConn) Read(bs []byte) (int, error) {
  212. return w.rwc.Read(bs)
  213. }
  214. func (w *wrappedConn) Write(bs []byte) (int, error) {
  215. return w.rwc.Write(bs)
  216. }
  217. func (w *wrappedConn) Close() error {
  218. return w.rwc.Close()
  219. }