control/controlclient: remove x/net/http2, use net/http

Saves 352 KB, removing one of our two HTTP/2 implementations linked
into the binary.

Fixes #17305
Updates #15015

Change-Id: I53a04b1f2687dca73c8541949465038b69aa6ade
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2025-10-01 08:53:48 -07:00
committed by Brad Fitzpatrick
parent c45f8813b4
commit 1d93bdce20
17 changed files with 467 additions and 586 deletions
+18 -39
View File
@@ -13,10 +13,8 @@ import (
"encoding/json"
"errors"
"io"
"net/http"
"sync"
"golang.org/x/net/http2"
"tailscale.com/control/controlbase"
"tailscale.com/tailcfg"
)
@@ -27,11 +25,11 @@ import (
// the pool when the connection is closed, properly handles an optional "early
// payload" that's sent prior to beginning the HTTP/2 session, and provides a
// way to return a connection to a pool when the connection is closed.
//
// Use [NewConn] to build a new Conn if you want [Conn.GetEarlyPayload] to work.
// Otherwise making a Conn directly, only setting Conn, is fine.
type Conn struct {
*controlbase.Conn
id int
onClose func(int)
h2cc *http2.ClientConn
readHeaderOnce sync.Once // guards init of reader field
reader io.Reader // (effectively Conn.Reader after header)
@@ -40,31 +38,18 @@ type Conn struct {
earlyPayloadErr error
}
// New creates a new Conn that wraps the given controlbase.Conn.
// NewConn creates a new Conn that wraps the given controlbase.Conn.
//
// h2t is the HTTP/2 transport to use for the connection; a new
// http2.ClientConn will be created that reads from the returned Conn.
//
// connID should be a unique ID for this connection. When the Conn is closed,
// the onClose function will be called with the connID if it is non-nil.
func New(conn *controlbase.Conn, h2t *http2.Transport, connID int, onClose func(int)) (*Conn, error) {
ncc := &Conn{
func NewConn(conn *controlbase.Conn) *Conn {
return &Conn{
Conn: conn,
id: connID,
onClose: onClose,
earlyPayloadReady: make(chan struct{}),
}
h2cc, err := h2t.NewClientConn(ncc)
if err != nil {
return nil, err
}
ncc.h2cc = h2cc
return ncc, nil
}
// RoundTrip implements the http.RoundTripper interface.
func (c *Conn) RoundTrip(r *http.Request) (*http.Response, error) {
return c.h2cc.RoundTrip(r)
}
// GetEarlyPayload waits for the early Noise payload to arrive.
@@ -74,6 +59,15 @@ func (c *Conn) RoundTrip(r *http.Request) (*http.Response, error) {
// early Noise payload is ready (if any) and will return the same result for
// the lifetime of the Conn.
func (c *Conn) GetEarlyPayload(ctx context.Context) (*tailcfg.EarlyNoise, error) {
if c.earlyPayloadReady == nil {
return nil, errors.New("Conn was not created with NewConn; early payload not supported")
}
select {
case <-c.earlyPayloadReady:
return c.earlyPayload, c.earlyPayloadErr
default:
go c.readHeaderOnce.Do(c.readHeader)
}
select {
case <-c.earlyPayloadReady:
return c.earlyPayload, c.earlyPayloadErr
@@ -82,12 +76,6 @@ func (c *Conn) GetEarlyPayload(ctx context.Context) (*tailcfg.EarlyNoise, error)
}
}
// CanTakeNewRequest reports whether the underlying HTTP/2 connection can take
// a new request, meaning it has not been closed or received or sent a GOAWAY.
func (c *Conn) CanTakeNewRequest() bool {
return c.h2cc.CanTakeNewRequest()
}
// The first 9 bytes from the server to client over Noise are either an HTTP/2
// settings frame (a normal HTTP/2 setup) or, as we added later, an "early payload"
// header that's also 9 bytes long: 5 bytes (EarlyPayloadMagic) followed by 4 bytes
@@ -122,7 +110,9 @@ func (c *Conn) Read(p []byte) (n int, err error) {
// c.earlyPayload, closing c.earlyPayloadReady, and initializing c.reader for
// future reads.
func (c *Conn) readHeader() {
defer close(c.earlyPayloadReady)
if c.earlyPayloadReady != nil {
defer close(c.earlyPayloadReady)
}
setErr := func(err error) {
c.reader = returnErrReader{err}
@@ -156,14 +146,3 @@ func (c *Conn) readHeader() {
}
c.reader = c.Conn
}
// Close closes the connection.
func (c *Conn) Close() error {
if err := c.Conn.Close(); err != nil {
return err
}
if c.onClose != nil {
c.onClose(c.id)
}
return nil
}