diff --git a/integrationtests/self/http_shutdown_test.go b/integrationtests/self/http_shutdown_test.go index 53cbbd8e0..ae5a8aeeb 100644 --- a/integrationtests/self/http_shutdown_test.go +++ b/integrationtests/self/http_shutdown_test.go @@ -368,6 +368,7 @@ func testHTTP3ListenerClosing(t *testing.T, graceful, useApplicationListener boo tlsConf.NextProtos = []string{http3.NextProtoH3} tr := &http3.Transport{TLSClientConfig: tlsConf} defer tr.Close() + addDialCallback(t, tr) cl := &http.Client{Transport: tr} req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) require.NoError(t, err) @@ -396,7 +397,7 @@ func testHTTP3ListenerClosing(t *testing.T, graceful, useApplicationListener boo // the following values will be ignored when using ServeListener TLSConfig: tlsConf, QUICConfig: getQuicConfig(nil), - Addr: "127.0.0.1:47283", + Addr: "127.0.0.1:0", } serveChan := make(chan error, 1) @@ -411,7 +412,17 @@ func testHTTP3ListenerClosing(t *testing.T, graceful, useApplicationListener boo go func() { serveChan <- server.ServeListener(ln) }() } else { go func() { serveChan <- server.ListenAndServe() }() - host = server.Addr + // The server is listening on a random port, and the only way to get the port + // is to parse the Alt-Svc header. + var port int + require.Eventually(t, func() bool { + hdr := make(http.Header) + server.SetQUICHeaders(hdr) + altSvc := hdr.Get("Alt-Svc") + n, err := fmt.Sscanf(altSvc, `h3=":%d"`, &port) + return err == nil && n == 1 + }, time.Second, 10*time.Millisecond) + host = fmt.Sprintf("127.0.0.1:%d", port) } u := &url.URL{Scheme: "https", Host: host, Path: "/ok"} @@ -471,10 +482,7 @@ func testHTTP3ListenerClosing(t *testing.T, graceful, useApplicationListener boo for range 2 { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - err := dial(t, ctx, u) - var h3Err *http3.Error - require.ErrorAs(t, err, &h3Err) - require.Equal(t, http3.ErrCode(1337), h3Err.ErrorCode) + require.ErrorIs(t, dial(t, ctx, u), &http3.Error{ErrorCode: 1337, Remote: true}) select { case err := <-errChan: require.NoError(t, err) diff --git a/integrationtests/self/http_test.go b/integrationtests/self/http_test.go index 6e81165b0..8f496523a 100644 --- a/integrationtests/self/http_test.go +++ b/integrationtests/self/http_test.go @@ -85,6 +85,7 @@ func newHTTP3Client(t *testing.T) *http.Client { QUICConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}), DisableCompression: true, } + addDialCallback(t, tr) t.Cleanup(func() { tr.Close() }) return &http.Client{Transport: tr} } @@ -356,7 +357,13 @@ func TestHTTPDifferentOrigins(t *testing.T) { }) port := startHTTPServer(t, mux) - cl := newHTTP3Client(t) + tr := &http3.Transport{ + TLSClientConfig: getTLSClientConfigWithoutServerName(), + QUICConfig: getQuicConfig(nil), + } + t.Cleanup(func() { tr.Close() }) + cl := &http.Client{Transport: tr} + resp, err := cl.Get(fmt.Sprintf("https://localhost:%d/remote-addr", port)) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) @@ -888,6 +895,7 @@ func TestHTTP0RTT(t *testing.T) { DisableCompression: true, } defer tr.Close() + addDialCallback(t, tr) proxyPort := proxy.LocalAddr().(*net.UDPAddr).Port req, err := http.NewRequest(http3.MethodGet0RTT, fmt.Sprintf("https://localhost:%d/0rtt", proxyPort), nil) @@ -912,6 +920,7 @@ func TestHTTP0RTT(t *testing.T) { DisableCompression: true, } defer tr2.Close() + addDialCallback(t, tr2) rsp, err = tr2.RoundTrip(req) require.NoError(t, err) require.Equal(t, 200, rsp.StatusCode) @@ -949,6 +958,7 @@ func TestHTTPStreamer(t *testing.T) { require.NoError(t, err) defer conn.CloseWithError(0, "") tr := http3.Transport{} + addDialCallback(t, &tr) cc := tr.NewClientConn(conn) str, err := cc.OpenRequestStream(ctx) require.NoError(t, err) diff --git a/integrationtests/self/http_trace_test.go b/integrationtests/self/http_trace_test.go index f894495fd..aa1baaac4 100644 --- a/integrationtests/self/http_trace_test.go +++ b/integrationtests/self/http_trace_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/quic-go/quic-go/http3" "github.com/stretchr/testify/require" ) @@ -62,7 +63,13 @@ func TestHTTPClientTrace(t *testing.T) { } ctx := httptrace.WithClientTrace(context.Background(), &trace) - cl := newHTTP3Client(t) + tr := &http3.Transport{ + TLSClientConfig: getTLSClientConfigWithoutServerName(), + QUICConfig: getQuicConfig(nil), + } + t.Cleanup(func() { tr.Close() }) + cl := &http.Client{Transport: tr} + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/client-trace", port), nil) require.NoError(t, err) resp, err := cl.Do(req) diff --git a/integrationtests/self/self_test.go b/integrationtests/self/self_test.go index 6df7863b6..4a9e8a4cf 100644 --- a/integrationtests/self/self_test.go +++ b/integrationtests/self/self_test.go @@ -10,11 +10,13 @@ import ( "math/rand/v2" "net" "os" + "runtime" "strconv" "testing" "time" "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" "github.com/quic-go/quic-go/integrationtests/tools" "github.com/quic-go/quic-go/internal/protocol" "github.com/quic-go/quic-go/internal/wire" @@ -298,3 +300,23 @@ func contains0RTTPacket(data []byte) bool { } return false } + +// addDialCallback explicitly adds the http3.Transport's Dial callback. +// This is needed since dialing on dual-stack sockets is flaky on macOS, +// see https://github.com/golang/go/issues/67226. +func addDialCallback(t *testing.T, tr *http3.Transport) { + t.Helper() + + if runtime.GOOS != "darwin" { + return + } + + require.Nil(t, tr.Dial) + tr.Dial = func(ctx context.Context, addr string, tlsConf *tls.Config, conf *quic.Config) (quic.EarlyConnection, error) { + a, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + return quic.DialEarly(ctx, newUDPConnLocalhost(t), a, tlsConf, conf) + } +}