From b78b9aca51644ef26e66911707397d52caef910a Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Thu, 8 Sep 2022 17:19:59 +0200 Subject: [PATCH] refactor(datafmt): use "udp" instead of "quic" (#946) This diff changes the data format to prefer "udp" to "quic" everywhere we were previously using "quic". Previously, the code inconsistently used "quic" for operations where we knew we were using "quic" and "udp" otherwise (e.g., for generic operations like ReadFrom). While it would be more correct to say that a specific HTTP request used "quic" rather than "udp", using "udp" consistently allows one to see how distinct events such as ReadFrom and an handshake all refer to the same address, port, and protocol triple. Therefore, this change makes it easier to programmatically unpack a single measurement and create endpoint stats. Before implementing this change, I discussed the problem with @hellais who mentioned that ooni/data is not currently using the "quic" string anywhere. I know that ooni/pipeline also doesn't rely on this string. The only users of this feature have been research-oriented experiments such as urlgetter, for which such a change would actually be acceptable. See https://github.com/ooni/probe/issues/2238 and https://github.com/ooni/spec/pull/262. --- internal/engine/netx/http.go | 2 +- internal/measurex/endpoint.go | 8 ++--- internal/measurex/measurement.go | 4 +-- internal/measurex/measurer.go | 8 ++--- internal/measurex/quic.go | 8 ++--- internal/measurex/utils.go | 2 +- internal/measurexlite/http.go | 2 +- internal/measurexlite/http_test.go | 8 ++--- internal/measurexlite/quic.go | 2 +- internal/measurexlite/quic_test.go | 6 ++-- internal/model/mocks/http_test.go | 2 +- internal/model/netx.go | 2 +- internal/netxlite/http3.go | 2 +- internal/netxlite/http3_test.go | 2 +- internal/tracex/archival_test.go | 2 +- .../tutorial/measurex/chapter05/README.md | 32 +++++++++---------- internal/tutorial/measurex/chapter05/main.go | 32 +++++++++---------- .../tutorial/measurex/chapter06/README.md | 2 +- internal/tutorial/measurex/chapter06/main.go | 2 +- .../tutorial/measurex/chapter08/README.md | 2 +- internal/tutorial/measurex/chapter08/main.go | 2 +- 21 files changed, 66 insertions(+), 66 deletions(-) diff --git a/internal/engine/netx/http.go b/internal/engine/netx/http.go index 9f202a1..9c5da19 100644 --- a/internal/engine/netx/http.go +++ b/internal/engine/netx/http.go @@ -55,7 +55,7 @@ var allTransportsInfo = map[bool]httpTransportInfo{ }, true: { Factory: newHTTP3Transport, - TransportName: "quic", + TransportName: "udp", }, } diff --git a/internal/measurex/endpoint.go b/internal/measurex/endpoint.go index 9d374e0..be218aa 100644 --- a/internal/measurex/endpoint.go +++ b/internal/measurex/endpoint.go @@ -19,13 +19,13 @@ const ( // NetworkTCP identifies endpoints using TCP. NetworkTCP = EndpointNetwork("tcp") - // NetworkQUIC identifies endpoints using QUIC. - NetworkQUIC = EndpointNetwork("quic") + // NetworkUDP identifies endpoints using UDP. + NetworkUDP = EndpointNetwork("udp") ) // Endpoint is an endpoint for a domain. type Endpoint struct { - // Network is the network (e.g., "tcp", "quic") + // Network is the network (e.g., "tcp", "udp") Network EndpointNetwork // Address is the endpoint address (e.g., "8.8.8.8:443") @@ -42,7 +42,7 @@ type HTTPEndpoint struct { // Domain is the endpoint domain (e.g., "dns.google"). Domain string - // Network is the network (e.g., "tcp" or "quic"). + // Network is the network (e.g., "tcp" or "udp"). Network EndpointNetwork // Address is the endpoint address (e.g., "8.8.8.8:443"). diff --git a/internal/measurex/measurement.go b/internal/measurex/measurement.go index 0cac00a..81a414e 100644 --- a/internal/measurex/measurement.go +++ b/internal/measurex/measurement.go @@ -156,7 +156,7 @@ func (m *DNSMeasurement) allQUICEndpoints(domain, port string) (out []*Endpoint) continue } for _, addr := range entry.Addrs() { - out = append(out, m.newEndpoint(addr, port, NetworkQUIC)) + out = append(out, m.newEndpoint(addr, port, NetworkUDP)) } } return @@ -186,7 +186,7 @@ func (m *DNSMeasurement) allHTTPEndpointsForURL( epnts := m.allEndpointsForDomain(domain, port) var out []*HTTPEndpoint for _, epnt := range epnts { - if URL.Scheme != "https" && epnt.Network == NetworkQUIC { + if URL.Scheme != "https" && epnt.Network == NetworkUDP { continue // we'll only use QUIC with HTTPS } out = append(out, &HTTPEndpoint{ diff --git a/internal/measurex/measurer.go b/internal/measurex/measurer.go index ff0b90b..9d4aa5c 100644 --- a/internal/measurex/measurer.go +++ b/internal/measurex/measurer.go @@ -394,7 +394,7 @@ func (mx *Measurer) QUICHandshake(ctx context.Context, address string, qconn.CloseWithError(0, "") } return &EndpointMeasurement{ - Network: NetworkQUIC, + Network: NetworkUDP, Address: address, Measurement: measurement, } @@ -502,7 +502,7 @@ func (mx *Measurer) httpEndpointGetMeasurement(ctx context.Context, epnt *HTTPEn func (mx *Measurer) HTTPEndpointGetWithDB(ctx context.Context, epnt *HTTPEndpoint, db WritableDB, jar http.CookieJar) (err error) { switch epnt.Network { - case NetworkQUIC: + case NetworkUDP: _, err = mx.httpEndpointGetQUIC(ctx, db, epnt, jar) case NetworkTCP: _, err = mx.httpEndpointGetTCP(ctx, db, epnt, jar) @@ -517,7 +517,7 @@ func (mx *Measurer) HTTPEndpointGetWithDB(ctx context.Context, epnt *HTTPEndpoin func (mx *Measurer) httpEndpointGetWithDB(ctx context.Context, epnt *HTTPEndpoint, db WritableDB, jar http.CookieJar) (resp *http.Response, err error) { switch epnt.Network { - case NetworkQUIC: + case NetworkUDP: resp, err = mx.httpEndpointGetQUIC(ctx, db, epnt, jar) case NetworkTCP: resp, err = mx.httpEndpointGetTCP(ctx, db, epnt, jar) @@ -971,7 +971,7 @@ func (mx *Measurer) doQUICFollowUp(ctx context.Context, parallelism int, for _, epnt := range epnts { quicEpnts = append(quicEpnts, &HTTPEndpoint{ Domain: epnt.Domain, - Network: NetworkQUIC, + Network: NetworkUDP, Address: epnt.Address, SNI: epnt.SNI, ALPN: []string{"h3"}, diff --git a/internal/measurex/quic.go b/internal/measurex/quic.go index 0bb396e..7112ae2 100644 --- a/internal/measurex/quic.go +++ b/internal/measurex/quic.go @@ -47,7 +47,7 @@ func (c *udpLikeConnDB) WriteTo(p []byte, addr net.Addr) (int, error) { finished := time.Since(c.begin).Seconds() c.db.InsertIntoReadWrite(&NetworkEvent{ Operation: "write_to", - Network: "quic", + Network: "udp", RemoteAddr: addr.String(), Started: started, Finished: finished, @@ -63,7 +63,7 @@ func (c *udpLikeConnDB) ReadFrom(b []byte) (int, net.Addr, error) { finished := time.Since(c.begin).Seconds() c.db.InsertIntoReadWrite(&NetworkEvent{ Operation: "read_from", - Network: "quic", + Network: "udp", RemoteAddr: addrStringIfNotNil(addr), Started: started, Finished: finished, @@ -79,7 +79,7 @@ func (c *udpLikeConnDB) Close() error { finished := time.Since(c.begin).Seconds() c.db.InsertIntoClose(&NetworkEvent{ Operation: "close", - Network: "quic", + Network: "udp", RemoteAddr: "", Started: started, Finished: finished, @@ -123,7 +123,7 @@ func (qh *quicDialerDB) DialContext(ctx context.Context, address string, } finished := time.Since(qh.begin).Seconds() qh.db.InsertIntoQUICHandshake(&QUICTLSHandshakeEvent{ - Network: "quic", + Network: "udp", RemoteAddr: address, SNI: tlsConfig.ServerName, ALPN: tlsConfig.NextProtos, diff --git a/internal/measurex/utils.go b/internal/measurex/utils.go index 29e5d48..dc97d02 100644 --- a/internal/measurex/utils.go +++ b/internal/measurex/utils.go @@ -16,7 +16,7 @@ import ( // given the network. On failure, we return a nil list. func ALPNForHTTPEndpoint(network EndpointNetwork) []string { switch network { - case NetworkQUIC: + case NetworkUDP: return []string{"h3"} case NetworkTCP: return []string{"h2", "http/1.1"} diff --git a/internal/measurexlite/http.go b/internal/measurexlite/http.go index 719f15b..27a6173 100644 --- a/internal/measurexlite/http.go +++ b/internal/measurexlite/http.go @@ -27,7 +27,7 @@ import ( // // - alpn is the negotiated ALPN or an empty string when not applicable; // -// - transport is the HTTP transport's protocol we're using ("quic" or "tcp"): this field +// - transport is the HTTP transport's protocol we're using ("udp" or "tcp"): this field // was introduced a long time ago to support QUIC measurements and we keep it for backwards // compatibility but network, address, and alpn are much more informative; // diff --git a/internal/measurexlite/http_test.go b/internal/measurexlite/http_test.go index dacce5a..da36b44 100644 --- a/internal/measurexlite/http_test.go +++ b/internal/measurexlite/http_test.go @@ -155,7 +155,7 @@ func TestNewArchivalHTTPRequestResult(t *testing.T) { network: "udp", address: "8.8.8.8:443", alpn: "h3", - transport: "quic", + transport: "udp", req: &http.Request{ Method: "GET", URL: &url.URL{ @@ -205,7 +205,7 @@ func TestNewArchivalHTTPRequestResult(t *testing.T) { }, Method: "GET", Tor: model.ArchivalHTTPTor{}, - Transport: "quic", + Transport: "udp", URL: "https://dns.google/", }, Response: model.ArchivalHTTPResponse{ @@ -243,7 +243,7 @@ func TestNewArchivalHTTPRequestResult(t *testing.T) { network: "udp", address: "8.8.8.8:443", alpn: "h3", - transport: "quic", + transport: "udp", req: &http.Request{ Method: "GET", URL: &url.URL{ @@ -301,7 +301,7 @@ func TestNewArchivalHTTPRequestResult(t *testing.T) { }, Method: "GET", Tor: model.ArchivalHTTPTor{}, - Transport: "quic", + Transport: "udp", URL: "https://dns.google/", }, Response: model.ArchivalHTTPResponse{ diff --git a/internal/measurexlite/quic.go b/internal/measurexlite/quic.go index 60515df..d409146 100644 --- a/internal/measurexlite/quic.go +++ b/internal/measurexlite/quic.go @@ -64,7 +64,7 @@ func (tx *Trace) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn case tx.quicHandshake <- NewArchivalTLSOrQUICHandshakeResult( tx.Index, started.Sub(tx.ZeroTime), - "quic", + "udp", remoteAddr, config, state, diff --git a/internal/measurexlite/quic_test.go b/internal/measurexlite/quic_test.go index 04e5148..8586802 100644 --- a/internal/measurexlite/quic_test.go +++ b/internal/measurexlite/quic_test.go @@ -137,7 +137,7 @@ func TestNewQUICDialerWithoutResolver(t *testing.T) { } expectedFailure := "unknown_failure: mocked" expect := &model.ArchivalTLSOrQUICHandshakeResult{ - Network: "quic", + Network: "udp", Address: "1.1.1.1:443", CipherSuite: "", Failure: &expectedFailure, @@ -275,7 +275,7 @@ func TestFirstQUICHandshake(t *testing.T) { zeroTime := time.Now() trace := NewTrace(0, zeroTime) expect := []*model.ArchivalTLSOrQUICHandshakeResult{{ - Network: "quic", + Network: "udp", Address: "1.1.1.1:443", CipherSuite: "", Failure: nil, @@ -287,7 +287,7 @@ func TestFirstQUICHandshake(t *testing.T) { Tags: []string{}, TLSVersion: "", }, { - Network: "quic", + Network: "udp", Address: "8.8.8.8:443", CipherSuite: "", Failure: nil, diff --git a/internal/model/mocks/http_test.go b/internal/model/mocks/http_test.go index cf6f868..67d8427 100644 --- a/internal/model/mocks/http_test.go +++ b/internal/model/mocks/http_test.go @@ -10,7 +10,7 @@ import ( func TestHTTPTransport(t *testing.T) { t.Run("Network", func(t *testing.T) { - expected := "quic" + expected := "udp" txp := &HTTPTransport{ MockNetwork: func() string { return expected diff --git a/internal/model/netx.go b/internal/model/netx.go index 790008a..0251fcb 100644 --- a/internal/model/netx.go +++ b/internal/model/netx.go @@ -158,7 +158,7 @@ type HTTPClient interface { // HTTPTransport is an http.Transport-like structure. type HTTPTransport interface { // Network returns the network used by the transport, which - // should be one of "tcp" and "quic". + // should be one of "tcp" and "udp". Network() string // RoundTrip performs the HTTP round trip. diff --git a/internal/netxlite/http3.go b/internal/netxlite/http3.go index 6a9c128..188da4a 100644 --- a/internal/netxlite/http3.go +++ b/internal/netxlite/http3.go @@ -29,7 +29,7 @@ var _ model.HTTPTransport = &http3Transport{} // Network implements HTTPTransport.Network. func (txp *http3Transport) Network() string { - return "quic" + return "udp" } // RoundTrip implements HTTPTransport.RoundTrip. diff --git a/internal/netxlite/http3_test.go b/internal/netxlite/http3_test.go index 07249fe..9c887ab 100644 --- a/internal/netxlite/http3_test.go +++ b/internal/netxlite/http3_test.go @@ -39,7 +39,7 @@ func TestHTTP3Transport(t *testing.T) { t.Run("Network", func(t *testing.T) { txp := &http3Transport{} - if txp.Network() != "quic" { + if txp.Network() != "udp" { t.Fatal("unexpected .Network return value") } }) diff --git a/internal/tracex/archival_test.go b/internal/tracex/archival_test.go index 756e3eb..2b46f5b 100644 --- a/internal/tracex/archival_test.go +++ b/internal/tracex/archival_test.go @@ -564,7 +564,7 @@ func TestNewTLSHandshakesList(t *testing.T) { Address: "131.252.210.176:443", Err: netxlite.FailureEOFError, NoTLSVerify: false, - Proto: "quic", + Proto: "udp", TLSCipherSuite: "SUITE", TLSNegotiatedProto: "h3", TLSPeerCerts: [][]byte{ diff --git a/internal/tutorial/measurex/chapter05/README.md b/internal/tutorial/measurex/chapter05/README.md index aac5593..6d4e515 100644 --- a/internal/tutorial/measurex/chapter05/README.md +++ b/internal/tutorial/measurex/chapter05/README.md @@ -89,12 +89,12 @@ Produces this JSON: ```JavaScript { // In chapter02 these two fields were similar but - // the network was "tcp" as opposed to "quic" - "network": "quic", + // the network was "tcp" as opposed to "udp" + "network": "udp", "address": "8.8.4.4:443", // This block contains I/O operations. Note that - // the protocol is "quic" and that the syscalls + // the protocol is "udp" and that the syscalls // are "read_from" and "write_to" because QUIC does // not bind/connect sockets. (The real syscalls // are actually `recvfrom` and `sendto` but here @@ -106,7 +106,7 @@ Produces this JSON: "failure": null, "num_bytes": 1252, "operation": "write_to", - "proto": "quic", + "proto": "udp", "t": 0.027184208, "started": 0.027127208, "oddity": "" @@ -116,7 +116,7 @@ Produces this JSON: "failure": null, "num_bytes": 1252, "operation": "read_from", - "proto": "quic", + "proto": "udp", "t": 0.053116458, "started": 0.025626583, "oddity": "" @@ -126,7 +126,7 @@ Produces this JSON: "failure": null, "num_bytes": 1252, "operation": "write_to", - "proto": "quic", + "proto": "udp", "t": 0.054538792, "started": 0.054517542, "oddity": "" @@ -136,7 +136,7 @@ Produces this JSON: "failure": null, "num_bytes": 1252, "operation": "read_from", - "proto": "quic", + "proto": "udp", "t": 0.069144958, "started": 0.053194208, "oddity": "" @@ -146,7 +146,7 @@ Produces this JSON: "failure": null, "num_bytes": 1252, "operation": "read_from", - "proto": "quic", + "proto": "udp", "t": 0.069183458, "started": 0.069173292, "oddity": "" @@ -156,7 +156,7 @@ Produces this JSON: "failure": null, "num_bytes": 1252, "operation": "read_from", - "proto": "quic", + "proto": "udp", "t": 0.06920225, "started": 0.069197875, "oddity": "" @@ -166,7 +166,7 @@ Produces this JSON: "failure": null, "num_bytes": 1216, "operation": "read_from", - "proto": "quic", + "proto": "udp", "t": 0.069210958, "started": 0.069206875, "oddity": "" @@ -176,7 +176,7 @@ Produces this JSON: "failure": null, "num_bytes": 65, "operation": "read_from", - "proto": "quic", + "proto": "udp", "t": 0.069220667, "started": 0.069217375, "oddity": "" @@ -186,7 +186,7 @@ Produces this JSON: "failure": null, "num_bytes": 44, "operation": "write_to", - "proto": "quic", + "proto": "udp", "t": 0.069433417, "started": 0.069417625, "oddity": "" @@ -196,7 +196,7 @@ Produces this JSON: "failure": null, "num_bytes": 44, "operation": "write_to", - "proto": "quic", + "proto": "udp", "t": 0.069677625, "started": 0.069647458, "oddity": "" @@ -206,7 +206,7 @@ Produces this JSON: "failure": null, "num_bytes": 83, "operation": "write_to", - "proto": "quic", + "proto": "udp", "t": 0.073461917, "started": 0.073432875, "oddity": "" @@ -216,7 +216,7 @@ Produces this JSON: "failure": null, "num_bytes": 33, "operation": "write_to", - "proto": "quic", + "proto": "udp", "t": 0.073559417, "started": 0.073542542, "oddity": "" @@ -253,7 +253,7 @@ Produces this JSON: ], "no_tls_verify": false, "oddity": "", - "proto": "quic", + "proto": "udp", "started": 0.025061583 } ] diff --git a/internal/tutorial/measurex/chapter05/main.go b/internal/tutorial/measurex/chapter05/main.go index f070e24..4d1bef6 100644 --- a/internal/tutorial/measurex/chapter05/main.go +++ b/internal/tutorial/measurex/chapter05/main.go @@ -90,12 +90,12 @@ func main() { // ```JavaScript // { // // In chapter02 these two fields were similar but -// // the network was "tcp" as opposed to "quic" -// "network": "quic", +// // the network was "tcp" as opposed to "udp" +// "network": "udp", // "address": "8.8.4.4:443", // // // This block contains I/O operations. Note that -// // the protocol is "quic" and that the syscalls +// // the protocol is "udp" and that the syscalls // // are "read_from" and "write_to" because QUIC does // // not bind/connect sockets. (The real syscalls // // are actually `recvfrom` and `sendto` but here @@ -107,7 +107,7 @@ func main() { // "failure": null, // "num_bytes": 1252, // "operation": "write_to", -// "proto": "quic", +// "proto": "udp", // "t": 0.027184208, // "started": 0.027127208, // "oddity": "" @@ -117,7 +117,7 @@ func main() { // "failure": null, // "num_bytes": 1252, // "operation": "read_from", -// "proto": "quic", +// "proto": "udp", // "t": 0.053116458, // "started": 0.025626583, // "oddity": "" @@ -127,7 +127,7 @@ func main() { // "failure": null, // "num_bytes": 1252, // "operation": "write_to", -// "proto": "quic", +// "proto": "udp", // "t": 0.054538792, // "started": 0.054517542, // "oddity": "" @@ -137,7 +137,7 @@ func main() { // "failure": null, // "num_bytes": 1252, // "operation": "read_from", -// "proto": "quic", +// "proto": "udp", // "t": 0.069144958, // "started": 0.053194208, // "oddity": "" @@ -147,7 +147,7 @@ func main() { // "failure": null, // "num_bytes": 1252, // "operation": "read_from", -// "proto": "quic", +// "proto": "udp", // "t": 0.069183458, // "started": 0.069173292, // "oddity": "" @@ -157,7 +157,7 @@ func main() { // "failure": null, // "num_bytes": 1252, // "operation": "read_from", -// "proto": "quic", +// "proto": "udp", // "t": 0.06920225, // "started": 0.069197875, // "oddity": "" @@ -167,7 +167,7 @@ func main() { // "failure": null, // "num_bytes": 1216, // "operation": "read_from", -// "proto": "quic", +// "proto": "udp", // "t": 0.069210958, // "started": 0.069206875, // "oddity": "" @@ -177,7 +177,7 @@ func main() { // "failure": null, // "num_bytes": 65, // "operation": "read_from", -// "proto": "quic", +// "proto": "udp", // "t": 0.069220667, // "started": 0.069217375, // "oddity": "" @@ -187,7 +187,7 @@ func main() { // "failure": null, // "num_bytes": 44, // "operation": "write_to", -// "proto": "quic", +// "proto": "udp", // "t": 0.069433417, // "started": 0.069417625, // "oddity": "" @@ -197,7 +197,7 @@ func main() { // "failure": null, // "num_bytes": 44, // "operation": "write_to", -// "proto": "quic", +// "proto": "udp", // "t": 0.069677625, // "started": 0.069647458, // "oddity": "" @@ -207,7 +207,7 @@ func main() { // "failure": null, // "num_bytes": 83, // "operation": "write_to", -// "proto": "quic", +// "proto": "udp", // "t": 0.073461917, // "started": 0.073432875, // "oddity": "" @@ -217,7 +217,7 @@ func main() { // "failure": null, // "num_bytes": 33, // "operation": "write_to", -// "proto": "quic", +// "proto": "udp", // "t": 0.073559417, // "started": 0.073542542, // "oddity": "" @@ -254,7 +254,7 @@ func main() { // ], // "no_tls_verify": false, // "oddity": "", -// "proto": "quic", +// "proto": "udp", // "started": 0.025061583 // } // ] diff --git a/internal/tutorial/measurex/chapter06/README.md b/internal/tutorial/measurex/chapter06/README.md index 2a767e7..3184b88 100644 --- a/internal/tutorial/measurex/chapter06/README.md +++ b/internal/tutorial/measurex/chapter06/README.md @@ -97,7 +97,7 @@ to need more information to characterize the endpoint. In fact, in the above definition we recognize fields we have already discussed, such as: -- `Network`, describing whether to use "tcp" or "quic"; +- `Network`, describing whether to use "tcp" or "udp"; - `Address`, which is the endpoint address. diff --git a/internal/tutorial/measurex/chapter06/main.go b/internal/tutorial/measurex/chapter06/main.go index a69b0f9..c242d10 100644 --- a/internal/tutorial/measurex/chapter06/main.go +++ b/internal/tutorial/measurex/chapter06/main.go @@ -98,7 +98,7 @@ func main() { // In fact, in the above definition we recognize fields // we have already discussed, such as: // - // - `Network`, describing whether to use "tcp" or "quic"; + // - `Network`, describing whether to use "tcp" or "udp"; // // - `Address`, which is the endpoint address. // diff --git a/internal/tutorial/measurex/chapter08/README.md b/internal/tutorial/measurex/chapter08/README.md index 700414d..4a7e236 100644 --- a/internal/tutorial/measurex/chapter08/README.md +++ b/internal/tutorial/measurex/chapter08/README.md @@ -86,7 +86,7 @@ A/AAAA lookups results but also the HTTPS lookup results. The `AllHTTPEndpointsForURL` function will recognize that we also have HTTPS lookups and, if the "h3" ALPN is -present, will _also_ build HTTP/3 endpoints using "quic" +present, will _also_ build HTTP/3 endpoints using "udp" as the `HTTPEndpoint.Network`. ```Go diff --git a/internal/tutorial/measurex/chapter08/main.go b/internal/tutorial/measurex/chapter08/main.go index 9db3a9c..6b586dd 100644 --- a/internal/tutorial/measurex/chapter08/main.go +++ b/internal/tutorial/measurex/chapter08/main.go @@ -87,7 +87,7 @@ func main() { // // The `AllHTTPEndpointsForURL` function will recognize that // we also have HTTPS lookups and, if the "h3" ALPN is - // present, will _also_ build HTTP/3 endpoints using "quic" + // present, will _also_ build HTTP/3 endpoints using "udp" // as the `HTTPEndpoint.Network`. // // ```Go