http3: add client trace support (#4749)

Since the QUIC connection establishment process includes TLS handshake logic,
Connect and TLS handshake are called in the following order:

ConnectStart -> TLSHandshakeStart -> TLSHandshakeDone -> ConnectDone.

Notice: Wait100Continue not implemented as quic-go doesn't support handling
Expect: 100-continue.
This commit is contained in:
Roccoon
2025-01-14 12:50:16 +08:00
committed by Marten Seemann
parent 516220b0c5
commit 96ce54e83f
9 changed files with 370 additions and 18 deletions

View File

@@ -9,6 +9,7 @@ import (
"log/slog"
"net"
"net/http"
"net/http/httptrace"
"strings"
"sync"
"sync/atomic"
@@ -197,7 +198,9 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
return nil, fmt.Errorf("http3: invalid method %q", req.Method)
}
trace := httptrace.ContextClientTrace(req.Context())
hostname := authorityAddr(hostnameFromURL(req.URL))
traceGetConn(trace, hostname)
cl, isReused, err := t.getClient(req.Context(), hostname, opt.OnlyCachedConn)
if err != nil {
return nil, err
@@ -213,6 +216,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
t.removeClient(hostname)
return nil, cl.dialErr
}
traceGotConn(trace, cl.conn, isReused)
defer cl.useCount.Add(-1)
rsp, err := cl.rt.RoundTrip(req)
if err != nil {
@@ -317,14 +321,24 @@ func (t *Transport) dial(ctx context.Context, hostname string) (quic.EarlyConnec
t.transport = &quic.Transport{Conn: udpConn}
}
dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
network := "udp"
udpAddr, err := t.resolveUDPAddr(ctx, network, addr)
if err != nil {
return nil, err
}
return t.transport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
trace := httptrace.ContextClientTrace(ctx)
traceConnectStart(trace, network, udpAddr.String())
traceTLSHandshakeStart(trace)
conn, err := t.transport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
var state tls.ConnectionState
if conn != nil {
state = conn.ConnectionState().TLS
}
traceTLSHandshakeDone(trace, state, err)
traceConnectDone(trace, network, udpAddr.String(), err)
return conn, err
}
}
conn, err := dial(ctx, hostname, tlsConf, t.QUICConfig)
if err != nil {
return nil, nil, err
@@ -332,6 +346,25 @@ func (t *Transport) dial(ctx context.Context, hostname string) (quic.EarlyConnec
return conn, t.newClient(conn), nil
}
func (t *Transport) resolveUDPAddr(ctx context.Context, network, addr string) (*net.UDPAddr, error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
port, err := net.LookupPort(network, portStr)
if err != nil {
return nil, err
}
resolver := net.DefaultResolver
ipAddrs, err := resolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
addrs := addrList(ipAddrs)
ip := addrs.forResolve(network, addr)
return &net.UDPAddr{IP: ip.IP, Port: port, Zone: ip.Zone}, nil
}
func (t *Transport) removeClient(hostname string) {
t.mutex.Lock()
defer t.mutex.Unlock()