Skip to content

Commit f6b2e53

Browse files
neildgopherbot
authored andcommitted
internal/http3: basic stream read/write operations
Read and write HTTP/3 frames from QUIC streams. The varint encoding/decoding overlaps a bit with that in the quic package, but this package operates on streams while the QUIC package operates on []bytes. For golang/go#70914 Change-Id: I31115f5b572a59b899e2c880ecc86ba3caed982e Reviewed-on: https://go-review.googlesource.com/c/net/+/641838 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 78717f9 commit f6b2e53

File tree

4 files changed

+752
-0
lines changed

4 files changed

+752
-0
lines changed

internal/http3/http3.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.24
6+
7+
package http3
8+
9+
import "fmt"
10+
11+
// Stream types.
12+
//
13+
// For unidirectional streams, the value is the stream type sent over the wire.
14+
//
15+
// For bidirectional streams (which are always request streams),
16+
// the value is arbitrary and never sent on the wire.
17+
type streamType int64
18+
19+
const (
20+
// Bidirectional request stream.
21+
// All bidirectional streams are request streams.
22+
// This stream type is never sent over the wire.
23+
//
24+
// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1
25+
streamTypeRequest = streamType(-1)
26+
27+
// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2
28+
streamTypeControl = streamType(0x00)
29+
streamTypePush = streamType(0x01)
30+
31+
// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2
32+
streamTypeEncoder = streamType(0x02)
33+
streamTypeDecoder = streamType(0x03)
34+
)
35+
36+
func (stype streamType) String() string {
37+
switch stype {
38+
case streamTypeRequest:
39+
return "request"
40+
case streamTypeControl:
41+
return "control"
42+
case streamTypePush:
43+
return "push"
44+
case streamTypeEncoder:
45+
return "encoder"
46+
case streamTypeDecoder:
47+
return "decoder"
48+
default:
49+
return "unknown"
50+
}
51+
}
52+
53+
// Frame types.
54+
type frameType int64
55+
56+
const (
57+
// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2
58+
frameTypeData = frameType(0x00)
59+
frameTypeHeaders = frameType(0x01)
60+
frameTypeCancelPush = frameType(0x03)
61+
frameTypeSettings = frameType(0x04)
62+
frameTypePushPromise = frameType(0x05)
63+
frameTypeGoaway = frameType(0x07)
64+
frameTypeMaxPushID = frameType(0x0d)
65+
)
66+
67+
func (ftype frameType) String() string {
68+
switch ftype {
69+
case frameTypeData:
70+
return "DATA"
71+
case frameTypeHeaders:
72+
return "HEADERS"
73+
case frameTypeCancelPush:
74+
return "CANCEL_PUSH"
75+
case frameTypeSettings:
76+
return "SETTINGS"
77+
case frameTypePushPromise:
78+
return "PUSH_PROMISE"
79+
case frameTypeGoaway:
80+
return "GOAWAY"
81+
case frameTypeMaxPushID:
82+
return "MAX_PUSH_ID"
83+
default:
84+
return fmt.Sprintf("UNKNOWN_%d", int64(ftype))
85+
}
86+
}

internal/http3/quic_test.go

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.24
6+
7+
package http3
8+
9+
import (
10+
"bytes"
11+
"context"
12+
"crypto/tls"
13+
"net"
14+
"net/netip"
15+
"runtime"
16+
"sync"
17+
"testing"
18+
"time"
19+
20+
"golang.org/x/net/internal/gate"
21+
"golang.org/x/net/internal/testcert"
22+
"golang.org/x/net/quic"
23+
)
24+
25+
// newLocalQUICEndpoint returns a QUIC Endpoint listening on localhost.
26+
func newLocalQUICEndpoint(t *testing.T) *quic.Endpoint {
27+
t.Helper()
28+
switch runtime.GOOS {
29+
case "plan9":
30+
t.Skipf("ReadMsgUDP not supported on %s", runtime.GOOS)
31+
}
32+
conf := &quic.Config{
33+
TLSConfig: testTLSConfig,
34+
}
35+
e, err := quic.Listen("udp", "127.0.0.1:0", conf)
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
t.Cleanup(func() {
40+
e.Close(context.Background())
41+
})
42+
return e
43+
}
44+
45+
// newQUICEndpointPair returns two QUIC endpoints on the same test network.
46+
func newQUICEndpointPair(t testing.TB) (e1, e2 *quic.Endpoint) {
47+
config := &quic.Config{
48+
TLSConfig: testTLSConfig,
49+
}
50+
tn := &testNet{}
51+
pc1 := tn.newPacketConn()
52+
e1, err := quic.NewEndpoint(pc1, config)
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
t.Cleanup(func() {
57+
e1.Close(t.Context())
58+
})
59+
pc2 := tn.newPacketConn()
60+
e2, err = quic.NewEndpoint(pc2, config)
61+
if err != nil {
62+
t.Fatal(err)
63+
}
64+
t.Cleanup(func() {
65+
e2.Close(t.Context())
66+
})
67+
return e1, e2
68+
}
69+
70+
// newQUICStreamPair returns the two sides of a bidirectional QUIC stream.
71+
func newQUICStreamPair(t testing.TB) (s1, s2 *quic.Stream) {
72+
t.Helper()
73+
config := &quic.Config{
74+
TLSConfig: testTLSConfig,
75+
}
76+
e1, e2 := newQUICEndpointPair(t)
77+
c1, err := e1.Dial(context.Background(), "udp", e2.LocalAddr().String(), config)
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
c2, err := e2.Accept(context.Background())
82+
if err != nil {
83+
t.Fatal(err)
84+
}
85+
s1, err = c1.NewStream(context.Background())
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
s1.Flush()
90+
s2, err = c2.AcceptStream(context.Background())
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
return s1, s2
95+
}
96+
97+
// A testNet is a fake network of net.PacketConns.
98+
type testNet struct {
99+
mu sync.Mutex
100+
conns map[netip.AddrPort]*testPacketConn
101+
}
102+
103+
// newPacketConn returns a new PacketConn with a unique source address.
104+
func (tn *testNet) newPacketConn() *testPacketConn {
105+
tn.mu.Lock()
106+
defer tn.mu.Unlock()
107+
if tn.conns == nil {
108+
tn.conns = make(map[netip.AddrPort]*testPacketConn)
109+
}
110+
localAddr := netip.AddrPortFrom(
111+
netip.AddrFrom4([4]byte{
112+
127, 0, 0, byte(len(tn.conns)),
113+
}),
114+
443)
115+
tc := &testPacketConn{
116+
tn: tn,
117+
localAddr: localAddr,
118+
gate: gate.New(false),
119+
}
120+
tn.conns[localAddr] = tc
121+
return tc
122+
}
123+
124+
// connForAddr returns the conn with the given source address.
125+
func (tn *testNet) connForAddr(srcAddr netip.AddrPort) *testPacketConn {
126+
tn.mu.Lock()
127+
defer tn.mu.Unlock()
128+
return tn.conns[srcAddr]
129+
}
130+
131+
// A testPacketConn is a net.PacketConn on a testNet fake network.
132+
type testPacketConn struct {
133+
tn *testNet
134+
localAddr netip.AddrPort
135+
136+
gate gate.Gate
137+
queue []testPacket
138+
closed bool
139+
}
140+
141+
type testPacket struct {
142+
b []byte
143+
src netip.AddrPort
144+
}
145+
146+
func (tc *testPacketConn) unlock() {
147+
tc.gate.Unlock(tc.closed || len(tc.queue) > 0)
148+
}
149+
150+
func (tc *testPacketConn) ReadFrom(p []byte) (n int, srcAddr net.Addr, err error) {
151+
if err := tc.gate.WaitAndLock(context.Background()); err != nil {
152+
return 0, nil, err
153+
}
154+
defer tc.unlock()
155+
if tc.closed {
156+
return 0, nil, net.ErrClosed
157+
}
158+
n = copy(p, tc.queue[0].b)
159+
srcAddr = net.UDPAddrFromAddrPort(tc.queue[0].src)
160+
tc.queue = tc.queue[1:]
161+
return n, srcAddr, nil
162+
}
163+
164+
func (tc *testPacketConn) WriteTo(p []byte, dstAddr net.Addr) (n int, err error) {
165+
tc.gate.Lock()
166+
closed := tc.closed
167+
tc.unlock()
168+
if closed {
169+
return 0, net.ErrClosed
170+
}
171+
172+
ap, err := addrPortFromAddr(dstAddr)
173+
if err != nil {
174+
return 0, err
175+
}
176+
dst := tc.tn.connForAddr(ap)
177+
if dst == nil {
178+
return len(p), nil // sent into the void
179+
}
180+
dst.gate.Lock()
181+
defer dst.unlock()
182+
dst.queue = append(dst.queue, testPacket{
183+
b: bytes.Clone(p),
184+
src: tc.localAddr,
185+
})
186+
return len(p), nil
187+
}
188+
189+
func (tc *testPacketConn) Close() error {
190+
tc.tn.mu.Lock()
191+
tc.tn.conns[tc.localAddr] = nil
192+
tc.tn.mu.Unlock()
193+
194+
tc.gate.Lock()
195+
defer tc.unlock()
196+
tc.closed = true
197+
tc.queue = nil
198+
return nil
199+
}
200+
201+
func (tc *testPacketConn) LocalAddr() net.Addr {
202+
return net.UDPAddrFromAddrPort(tc.localAddr)
203+
}
204+
205+
func (tc *testPacketConn) SetDeadline(time.Time) error { panic("unimplemented") }
206+
func (tc *testPacketConn) SetReadDeadline(time.Time) error { panic("unimplemented") }
207+
func (tc *testPacketConn) SetWriteDeadline(time.Time) error { panic("unimplemented") }
208+
209+
func addrPortFromAddr(addr net.Addr) (netip.AddrPort, error) {
210+
switch a := addr.(type) {
211+
case *net.UDPAddr:
212+
return a.AddrPort(), nil
213+
}
214+
return netip.ParseAddrPort(addr.String())
215+
}
216+
217+
var testTLSConfig = &tls.Config{
218+
InsecureSkipVerify: true,
219+
CipherSuites: []uint16{
220+
tls.TLS_AES_128_GCM_SHA256,
221+
tls.TLS_AES_256_GCM_SHA384,
222+
tls.TLS_CHACHA20_POLY1305_SHA256,
223+
},
224+
MinVersion: tls.VersionTLS13,
225+
Certificates: []tls.Certificate{testCert},
226+
NextProtos: []string{"h3"},
227+
}
228+
229+
var testCert = func() tls.Certificate {
230+
cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
231+
if err != nil {
232+
panic(err)
233+
}
234+
return cert
235+
}()

0 commit comments

Comments
 (0)