client.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !js
  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. "math"
  27. "net"
  28. "net/http"
  29. "net/http/httptrace"
  30. "net/netip"
  31. "net/url"
  32. "runtime"
  33. "sort"
  34. "sync/atomic"
  35. "time"
  36. "tailscale.com/control/controlbase"
  37. "tailscale.com/control/controlhttp/controlhttpcommon"
  38. "tailscale.com/envknob"
  39. "tailscale.com/health"
  40. "tailscale.com/net/dnscache"
  41. "tailscale.com/net/dnsfallback"
  42. "tailscale.com/net/netutil"
  43. "tailscale.com/net/netx"
  44. "tailscale.com/net/sockstats"
  45. "tailscale.com/net/tlsdial"
  46. "tailscale.com/net/tshttpproxy"
  47. "tailscale.com/syncs"
  48. "tailscale.com/tailcfg"
  49. "tailscale.com/tstime"
  50. "tailscale.com/util/multierr"
  51. )
  52. var stdDialer net.Dialer
  53. // Dial connects to the HTTP server at this Dialer's Host:HTTPPort, requests to
  54. // switch to the Tailscale control protocol, and returns an established control
  55. // protocol connection.
  56. //
  57. // If Dial fails to connect using HTTP, it also tries to tunnel over TLS to the
  58. // Dialer's Host:HTTPSPort as a compatibility fallback.
  59. //
  60. // The provided ctx is only used for the initial connection, until
  61. // Dial returns. It does not affect the connection once established.
  62. func (a *Dialer) Dial(ctx context.Context) (*ClientConn, error) {
  63. if a.Hostname == "" {
  64. return nil, errors.New("required Dialer.Hostname empty")
  65. }
  66. return a.dial(ctx)
  67. }
  68. func (a *Dialer) logf(format string, args ...any) {
  69. if a.Logf != nil {
  70. a.Logf(format, args...)
  71. }
  72. }
  73. func (a *Dialer) getProxyFunc() func(*http.Request) (*url.URL, error) {
  74. if a.proxyFunc != nil {
  75. return a.proxyFunc
  76. }
  77. return tshttpproxy.ProxyFromEnvironment
  78. }
  79. // httpsFallbackDelay is how long we'll wait for a.HTTPPort to work before
  80. // starting to try a.HTTPSPort.
  81. func (a *Dialer) httpsFallbackDelay() time.Duration {
  82. if v := a.testFallbackDelay; v != 0 {
  83. return v
  84. }
  85. return 500 * time.Millisecond
  86. }
  87. var _ = envknob.RegisterBool("TS_USE_CONTROL_DIAL_PLAN") // to record at init time whether it's in use
  88. func (a *Dialer) dial(ctx context.Context) (*ClientConn, error) {
  89. a.logPort80Failure.Store(true)
  90. // If we don't have a dial plan, just fall back to dialing the single
  91. // host we know about.
  92. useDialPlan := envknob.BoolDefaultTrue("TS_USE_CONTROL_DIAL_PLAN")
  93. if !useDialPlan || a.DialPlan == nil || len(a.DialPlan.Candidates) == 0 {
  94. return a.dialHost(ctx, netip.Addr{})
  95. }
  96. candidates := a.DialPlan.Candidates
  97. // Otherwise, we try dialing per the plan. Store the highest priority
  98. // in the list, so that if we get a connection to one of those
  99. // candidates we can return quickly.
  100. var highestPriority int = math.MinInt
  101. for _, c := range candidates {
  102. if c.Priority > highestPriority {
  103. highestPriority = c.Priority
  104. }
  105. }
  106. // This context allows us to cancel in-flight connections if we get a
  107. // highest-priority connection before we're all done.
  108. ctx, cancel := context.WithCancel(ctx)
  109. defer cancel()
  110. // Now, for each candidate, kick off a dial in parallel.
  111. type dialResult struct {
  112. conn *ClientConn
  113. err error
  114. addr netip.Addr
  115. priority int
  116. }
  117. resultsCh := make(chan dialResult, len(candidates))
  118. var pending atomic.Int32
  119. pending.Store(int32(len(candidates)))
  120. for _, c := range candidates {
  121. go func(ctx context.Context, c tailcfg.ControlIPCandidate) {
  122. var (
  123. conn *ClientConn
  124. err error
  125. )
  126. // Always send results back to our channel.
  127. defer func() {
  128. resultsCh <- dialResult{conn, err, c.IP, c.Priority}
  129. if pending.Add(-1) == 0 {
  130. close(resultsCh)
  131. }
  132. }()
  133. // If non-zero, wait the configured start timeout
  134. // before we do anything.
  135. if c.DialStartDelaySec > 0 {
  136. a.logf("[v2] controlhttp: waiting %.2f seconds before dialing %q @ %v", c.DialStartDelaySec, a.Hostname, c.IP)
  137. tmr, tmrChannel := a.clock().NewTimer(time.Duration(c.DialStartDelaySec * float64(time.Second)))
  138. defer tmr.Stop()
  139. select {
  140. case <-ctx.Done():
  141. err = ctx.Err()
  142. return
  143. case <-tmrChannel:
  144. }
  145. }
  146. // Now, create a sub-context with the given timeout and
  147. // try dialing the provided host.
  148. ctx, cancel := context.WithTimeout(ctx, time.Duration(c.DialTimeoutSec*float64(time.Second)))
  149. defer cancel()
  150. // This will dial, and the defer above sends it back to our parent.
  151. a.logf("[v2] controlhttp: trying to dial %q @ %v", a.Hostname, c.IP)
  152. conn, err = a.dialHost(ctx, c.IP)
  153. }(ctx, c)
  154. }
  155. var results []dialResult
  156. for res := range resultsCh {
  157. // If we get a response that has the highest priority, we don't
  158. // need to wait for any of the other connections to finish; we
  159. // can just return this connection.
  160. //
  161. // TODO(andrew): we could make this better by keeping track of
  162. // the highest remaining priority dynamically, instead of just
  163. // checking for the highest total
  164. if res.priority == highestPriority && res.conn != nil {
  165. a.logf("[v1] controlhttp: high-priority success dialing %q @ %v from dial plan", a.Hostname, res.addr)
  166. // Drain the channel and any existing connections in
  167. // the background.
  168. go func() {
  169. for _, res := range results {
  170. if res.conn != nil {
  171. res.conn.Close()
  172. }
  173. }
  174. for res := range resultsCh {
  175. if res.conn != nil {
  176. res.conn.Close()
  177. }
  178. }
  179. if a.drainFinished != nil {
  180. close(a.drainFinished)
  181. }
  182. }()
  183. return res.conn, nil
  184. }
  185. // This isn't a highest-priority result, so just store it until
  186. // we're done.
  187. results = append(results, res)
  188. }
  189. // After we finish this function, close any remaining open connections.
  190. defer func() {
  191. for _, result := range results {
  192. // Note: below, we nil out the returned connection (if
  193. // any) in the slice so we don't close it.
  194. if result.conn != nil {
  195. result.conn.Close()
  196. }
  197. }
  198. // We don't drain asynchronously after this point, so notify our
  199. // channel when we return.
  200. if a.drainFinished != nil {
  201. close(a.drainFinished)
  202. }
  203. }()
  204. // Sort by priority, then take the first non-error response.
  205. sort.Slice(results, func(i, j int) bool {
  206. // NOTE: intentionally inverted so that the highest priority
  207. // item comes first
  208. return results[i].priority > results[j].priority
  209. })
  210. var (
  211. conn *ClientConn
  212. errs []error
  213. )
  214. for i, result := range results {
  215. if result.err != nil {
  216. errs = append(errs, result.err)
  217. continue
  218. }
  219. a.logf("[v1] controlhttp: succeeded dialing %q @ %v from dial plan", a.Hostname, result.addr)
  220. conn = result.conn
  221. results[i].conn = nil // so we don't close it in the defer
  222. return conn, nil
  223. }
  224. if ctx.Err() != nil {
  225. a.logf("controlhttp: context aborted dialing")
  226. return nil, ctx.Err()
  227. }
  228. merr := multierr.New(errs...)
  229. // If we get here, then we didn't get anywhere with our dial plan; fall back to just using DNS.
  230. a.logf("controlhttp: failed dialing using DialPlan, falling back to DNS; errs=%s", merr.Error())
  231. return a.dialHost(ctx, netip.Addr{})
  232. }
  233. // The TS_FORCE_NOISE_443 envknob forces the controlclient noise dialer to
  234. // always use port 443 HTTPS connections to the controlplane and not try the
  235. // port 80 HTTP fast path.
  236. //
  237. // This is currently (2023-01-17) needed for Docker Desktop's "VPNKit" proxy
  238. // that breaks port 80 for us post-Noise-handshake, causing us to never try port
  239. // 443. Until one of Docker's proxy and/or this package's port 443 fallback is
  240. // fixed, this is a workaround. It might also be useful for future debugging.
  241. var forceNoise443 = envknob.RegisterBool("TS_FORCE_NOISE_443")
  242. // forceNoise443 reports whether the controlclient noise dialer should always
  243. // use HTTPS connections as its underlay connection (double crypto). This can
  244. // be necessary when networks or middle boxes are messing with port 80.
  245. func (d *Dialer) forceNoise443() bool {
  246. if runtime.GOOS == "plan9" {
  247. // For running demos of Plan 9 in a browser with network relays,
  248. // we want to minimize the number of connections we're making.
  249. // The main reason to use port 80 is to avoid double crypto
  250. // costs server-side but the costs are tiny and number of Plan 9
  251. // users doesn't make it worth it. Just disable this and always use
  252. // HTTPS for Plan 9. That also reduces some log spam.
  253. return true
  254. }
  255. if forceNoise443() {
  256. return true
  257. }
  258. if d.HealthTracker.LastNoiseDialWasRecent() {
  259. // If we dialed recently, assume there was a recent failure and fall
  260. // back to HTTPS dials for the subsequent retries.
  261. //
  262. // This heuristic works around networks where port 80 is MITMed and
  263. // appears to work for a bit post-Upgrade but then gets closed,
  264. // such as seen in https://github.com/tailscale/tailscale/issues/13597.
  265. if d.logPort80Failure.CompareAndSwap(true, false) {
  266. d.logf("controlhttp: forcing port 443 dial due to recent noise dial")
  267. }
  268. return true
  269. }
  270. return false
  271. }
  272. func (d *Dialer) clock() tstime.Clock {
  273. if d.Clock != nil {
  274. return d.Clock
  275. }
  276. return tstime.StdClock{}
  277. }
  278. var debugNoiseDial = envknob.RegisterBool("TS_DEBUG_NOISE_DIAL")
  279. // dialHost connects to the configured Dialer.Hostname and upgrades the
  280. // connection into a controlbase.Conn.
  281. //
  282. // If optAddr is valid, then no DNS is used and the connection will be made to the
  283. // provided address.
  284. func (a *Dialer) dialHost(ctx context.Context, optAddr netip.Addr) (*ClientConn, error) {
  285. // Create one shared context used by both port 80 and port 443 dials.
  286. // If port 80 is still in flight when 443 returns, this deferred cancel
  287. // will stop the port 80 dial.
  288. ctx, cancel := context.WithCancel(ctx)
  289. defer cancel()
  290. ctx = sockstats.WithSockStats(ctx, sockstats.LabelControlClientDialer, a.logf)
  291. // u80 and u443 are the URLs we'll try to hit over HTTP or HTTPS,
  292. // respectively, in order to do the HTTP upgrade to a net.Conn over which
  293. // we'll speak Noise.
  294. u80 := &url.URL{
  295. Scheme: "http",
  296. Host: net.JoinHostPort(a.Hostname, strDef(a.HTTPPort, "80")),
  297. Path: serverUpgradePath,
  298. }
  299. u443 := &url.URL{
  300. Scheme: "https",
  301. Host: net.JoinHostPort(a.Hostname, strDef(a.HTTPSPort, "443")),
  302. Path: serverUpgradePath,
  303. }
  304. if a.HTTPSPort == NoPort {
  305. u443 = nil
  306. }
  307. type tryURLRes struct {
  308. u *url.URL // input (the URL conn+err are for/from)
  309. conn *ClientConn // result (mutually exclusive with err)
  310. err error
  311. }
  312. ch := make(chan tryURLRes) // must be unbuffered
  313. try := func(u *url.URL) {
  314. if debugNoiseDial() {
  315. a.logf("trying noise dial (%v, %v) ...", u, optAddr)
  316. }
  317. cbConn, err := a.dialURL(ctx, u, optAddr)
  318. if debugNoiseDial() {
  319. a.logf("noise dial (%v, %v) = (%v, %v)", u, optAddr, cbConn, err)
  320. }
  321. select {
  322. case ch <- tryURLRes{u, cbConn, err}:
  323. case <-ctx.Done():
  324. if cbConn != nil {
  325. cbConn.Close()
  326. }
  327. }
  328. }
  329. forceTLS := a.forceNoise443()
  330. // Start the plaintext HTTP attempt first, unless disabled by the envknob.
  331. if !forceTLS || u443 == nil {
  332. go try(u80)
  333. }
  334. // In case outbound port 80 blocked or MITM'ed poorly, start a backup timer
  335. // to dial port 443 if port 80 doesn't either succeed or fail quickly.
  336. var try443Timer tstime.TimerController
  337. if u443 != nil {
  338. delay := a.httpsFallbackDelay()
  339. if forceTLS {
  340. delay = 0
  341. }
  342. try443Timer = a.clock().AfterFunc(delay, func() { try(u443) })
  343. defer try443Timer.Stop()
  344. }
  345. var err80, err443 error
  346. for {
  347. select {
  348. case <-ctx.Done():
  349. return nil, fmt.Errorf("connection attempts aborted by context: %w", ctx.Err())
  350. case res := <-ch:
  351. if res.err == nil {
  352. return res.conn, nil
  353. }
  354. switch res.u {
  355. case u80:
  356. // Connecting over plain HTTP failed; assume it's an HTTP proxy
  357. // being difficult and see if we can get through over HTTPS.
  358. err80 = res.err
  359. // Stop the fallback timer and run it immediately. We don't use
  360. // Timer.Reset(0) here because on AfterFuncs, that can run it
  361. // again.
  362. if try443Timer != nil && try443Timer.Stop() {
  363. go try(u443)
  364. } // else we lost the race and it started already which is what we want
  365. case u443:
  366. err443 = res.err
  367. default:
  368. panic("invalid")
  369. }
  370. if err80 != nil && err443 != nil {
  371. return nil, fmt.Errorf("all connection attempts failed (HTTP: %v, HTTPS: %v)", err80, err443)
  372. }
  373. }
  374. }
  375. }
  376. // dialURL attempts to connect to the given URL.
  377. //
  378. // If optAddr is valid, then no DNS is used and the connection will be made to the
  379. // provided address.
  380. func (a *Dialer) dialURL(ctx context.Context, u *url.URL, optAddr netip.Addr) (*ClientConn, error) {
  381. init, cont, err := controlbase.ClientDeferred(a.MachineKey, a.ControlKey, a.ProtocolVersion)
  382. if err != nil {
  383. return nil, err
  384. }
  385. netConn, err := a.tryURLUpgrade(ctx, u, optAddr, init)
  386. if err != nil {
  387. return nil, err
  388. }
  389. cbConn, err := cont(ctx, netConn)
  390. if err != nil {
  391. netConn.Close()
  392. return nil, err
  393. }
  394. return &ClientConn{
  395. Conn: cbConn,
  396. }, nil
  397. }
  398. // resolver returns a.DNSCache if non-nil or a new *dnscache.Resolver
  399. // otherwise.
  400. func (a *Dialer) resolver() *dnscache.Resolver {
  401. if a.DNSCache != nil {
  402. return a.DNSCache
  403. }
  404. return &dnscache.Resolver{
  405. Forward: dnscache.Get().Forward,
  406. LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
  407. UseLastGood: true,
  408. Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
  409. }
  410. }
  411. func isLoopback(a net.Addr) bool {
  412. if ta, ok := a.(*net.TCPAddr); ok {
  413. return ta.IP.IsLoopback()
  414. }
  415. return false
  416. }
  417. var macOSScreenTime = health.Register(&health.Warnable{
  418. Code: "macos-screen-time",
  419. Severity: health.SeverityHigh,
  420. Title: "Tailscale blocked by Screen Time",
  421. Text: func(args health.Args) string {
  422. return "macOS Screen Time seems to be blocking Tailscale. Try disabling Screen Time in System Settings > Screen Time > Content & Privacy > Access to Web Content."
  423. },
  424. ImpactsConnectivity: true,
  425. })
  426. // tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn.
  427. //
  428. // If optAddr is valid, then no DNS is used and the connection will be made to
  429. // the provided address.
  430. //
  431. // Only the provided ctx is used, not a.ctx.
  432. func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, optAddr netip.Addr, init []byte) (_ net.Conn, retErr error) {
  433. var dns *dnscache.Resolver
  434. // If we were provided an address to dial, then create a resolver that just
  435. // returns that value; otherwise, fall back to DNS.
  436. if optAddr.IsValid() {
  437. dns = &dnscache.Resolver{
  438. SingleHostStaticResult: []netip.Addr{optAddr},
  439. SingleHost: u.Hostname(),
  440. Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
  441. }
  442. } else {
  443. dns = a.resolver()
  444. }
  445. var dialer netx.DialFunc
  446. if a.Dialer != nil {
  447. dialer = a.Dialer
  448. } else {
  449. dialer = stdDialer.DialContext
  450. }
  451. // On macOS, see if Screen Time is blocking things.
  452. if runtime.GOOS == "darwin" {
  453. var proxydIntercepted atomic.Bool // intercepted by macOS webfilterproxyd
  454. origDialer := dialer
  455. dialer = func(ctx context.Context, network, address string) (net.Conn, error) {
  456. c, err := origDialer(ctx, network, address)
  457. if err != nil {
  458. return nil, err
  459. }
  460. if isLoopback(c.LocalAddr()) && isLoopback(c.RemoteAddr()) {
  461. proxydIntercepted.Store(true)
  462. }
  463. return c, nil
  464. }
  465. defer func() {
  466. if retErr != nil && proxydIntercepted.Load() {
  467. a.HealthTracker.SetUnhealthy(macOSScreenTime, nil)
  468. retErr = fmt.Errorf("macOS Screen Time is blocking network access: %w", retErr)
  469. } else {
  470. a.HealthTracker.SetHealthy(macOSScreenTime)
  471. }
  472. }()
  473. }
  474. tr := http.DefaultTransport.(*http.Transport).Clone()
  475. defer tr.CloseIdleConnections()
  476. tr.Proxy = a.getProxyFunc()
  477. tshttpproxy.SetTransportGetProxyConnectHeader(tr)
  478. tr.DialContext = dnscache.Dialer(dialer, dns)
  479. // Disable HTTP2, since h2 can't do protocol switching.
  480. tr.TLSClientConfig.NextProtos = []string{}
  481. tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
  482. tr.TLSClientConfig = tlsdial.Config(a.HealthTracker, tr.TLSClientConfig)
  483. if !tr.TLSClientConfig.InsecureSkipVerify {
  484. panic("unexpected") // should be set by tlsdial.Config
  485. }
  486. verify := tr.TLSClientConfig.VerifyConnection
  487. if verify == nil {
  488. panic("unexpected") // should be set by tlsdial.Config
  489. }
  490. // Demote all cert verification errors to log messages. We don't actually
  491. // care about the TLS security (because we just do the Noise crypto atop whatever
  492. // connection we get, including HTTP port 80 plaintext) so this permits
  493. // middleboxes to MITM their users. All they'll see is some Noise.
  494. tr.TLSClientConfig.VerifyConnection = func(cs tls.ConnectionState) error {
  495. if err := verify(cs); err != nil && a.Logf != nil && !a.omitCertErrorLogging {
  496. a.Logf("warning: TLS cert verificication for %q failed: %v", a.Hostname, err)
  497. }
  498. return nil // regardless
  499. }
  500. tr.DialTLSContext = dnscache.TLSDialer(dialer, dns, tr.TLSClientConfig)
  501. tr.DisableCompression = true
  502. // (mis)use httptrace to extract the underlying net.Conn from the
  503. // transport. The transport handles 101 Switching Protocols correctly,
  504. // such that the Conn will not be reused or kept alive by the transport
  505. // once the response has been handed back from RoundTrip.
  506. //
  507. // In theory, the machinery of net/http should make it such that
  508. // the trace callback happens-before we get the response, but
  509. // there's no promise of that. So, to make sure, we use a buffered
  510. // channel as a synchronization step to avoid data races.
  511. //
  512. // Note that even though we're able to extract a net.Conn via this
  513. // mechanism, we must still keep using the eventual resp.Body to
  514. // read from, because it includes a buffer we can't get rid of. If
  515. // the server never sends any data after sending the HTTP
  516. // response, we could get away with it, but violating this
  517. // assumption leads to very mysterious transport errors (lockups,
  518. // unexpected EOFs...), and we're bound to forget someday and
  519. // introduce a protocol optimization at a higher level that starts
  520. // eagerly transmitting from the server.
  521. var lastConn syncs.AtomicValue[net.Conn]
  522. trace := httptrace.ClientTrace{
  523. // Even though we only make a single HTTP request which should
  524. // require a single connection, the context (with the attached
  525. // trace configuration) might be used by our custom dialer to
  526. // make other HTTP requests (e.g. BootstrapDNS). We only care
  527. // about the last connection made, which should be the one to
  528. // the control server.
  529. GotConn: func(info httptrace.GotConnInfo) {
  530. lastConn.Store(info.Conn)
  531. },
  532. }
  533. ctx = httptrace.WithClientTrace(ctx, &trace)
  534. req := &http.Request{
  535. Method: "POST",
  536. URL: u,
  537. Header: http.Header{
  538. "Upgrade": []string{controlhttpcommon.UpgradeHeaderValue},
  539. "Connection": []string{"upgrade"},
  540. controlhttpcommon.HandshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
  541. },
  542. }
  543. req = req.WithContext(ctx)
  544. resp, err := tr.RoundTrip(req)
  545. if err != nil {
  546. return nil, err
  547. }
  548. if resp.StatusCode != http.StatusSwitchingProtocols {
  549. return nil, fmt.Errorf("unexpected HTTP response: %s", resp.Status)
  550. }
  551. // From here on, the underlying net.Conn is ours to use, but there
  552. // is still a read buffer attached to it within resp.Body. So, we
  553. // must direct I/O through resp.Body, but we can still use the
  554. // underlying net.Conn for stuff like deadlines.
  555. switchedConn := lastConn.Load()
  556. if switchedConn == nil {
  557. resp.Body.Close()
  558. return nil, fmt.Errorf("httptrace didn't provide a connection")
  559. }
  560. if next := resp.Header.Get("Upgrade"); next != controlhttpcommon.UpgradeHeaderValue {
  561. resp.Body.Close()
  562. return nil, fmt.Errorf("server switched to unexpected protocol %q", next)
  563. }
  564. rwc, ok := resp.Body.(io.ReadWriteCloser)
  565. if !ok {
  566. resp.Body.Close()
  567. return nil, errors.New("http Transport did not provide a writable body")
  568. }
  569. return netutil.NewAltReadWriteCloserConn(rwc, switchedConn), nil
  570. }