1
2
3
4
5 package http3
6
7 import (
8 "bytes"
9 "context"
10 "crypto/tls"
11 "net"
12 "net/netip"
13 "slices"
14 "sync"
15 "testing"
16 "time"
17
18 "internal/gate"
19 "net/http/internal/testcert"
20 "golang.org/x/net/quic"
21 )
22
23
24
25 func TestNewQUICConfig(t *testing.T) {
26 for _, test := range []struct {
27 name string
28 tlsConfig *tls.Config
29 }{{
30
31 name: "nil",
32 tlsConfig: nil,
33 }, {
34 name: "no NextProtos",
35 tlsConfig: &tls.Config{ServerName: "example.tld"},
36 }, {
37 name: "other NextProtos",
38 tlsConfig: &tls.Config{NextProtos: []string{"http/1.1"}},
39 }, {
40 name: "h3 already set",
41 tlsConfig: &tls.Config{NextProtos: []string{"h3"}},
42 }} {
43 t.Run(test.name, func(t *testing.T) {
44 var origNextProtos []string
45 if test.tlsConfig != nil {
46 origNextProtos = slices.Clone(test.tlsConfig.NextProtos)
47 }
48
49 config := newQUICConfig(nil, test.tlsConfig)
50 if config == nil {
51 t.Fatal("newQUICConfig returned nil config")
52 }
53 if config.TLSConfig == nil {
54 t.Fatal("newQUICConfig returned config with nil TLSConfig")
55 }
56 if got := config.TLSConfig.NextProtos; !slices.Equal(got, []string{"h3"}) {
57 t.Errorf("TLSConfig.NextProtos = %q, want [h3]", got)
58 }
59 if test.tlsConfig != nil {
60 if got := test.tlsConfig.NextProtos; !slices.Equal(got, origNextProtos) {
61 t.Errorf("newQUICConfig modified the caller's TLSConfig.NextProtos: got %q, want %q", got, origNextProtos)
62 }
63 }
64 })
65 }
66 }
67
68
69 func newLocalQUICEndpoint(t *testing.T) *quic.Endpoint {
70 t.Helper()
71 conf := &quic.Config{
72 TLSConfig: testTLSConfig,
73 }
74 e, err := quic.Listen("udp", "127.0.0.1:0", conf)
75 if err != nil {
76 t.Fatal(err)
77 }
78 t.Cleanup(func() {
79 e.Close(context.Background())
80 })
81 return e
82 }
83
84
85 func newQUICEndpointPair(t testing.TB) (e1, e2 *quic.Endpoint) {
86 config := &quic.Config{
87 TLSConfig: testTLSConfig,
88 }
89 tn := &testNet{}
90 e1 = tn.newQUICEndpoint(t, config)
91 e2 = tn.newQUICEndpoint(t, config)
92 return e1, e2
93 }
94
95
96 func newQUICStreamPair(t testing.TB) (s1, s2 *quic.Stream) {
97 t.Helper()
98 config := &quic.Config{
99 TLSConfig: testTLSConfig,
100 }
101 e1, e2 := newQUICEndpointPair(t)
102 c1, err := e1.Dial(context.Background(), "udp", e2.LocalAddr().String(), config)
103 if err != nil {
104 t.Fatal(err)
105 }
106 c2, err := e2.Accept(context.Background())
107 if err != nil {
108 t.Fatal(err)
109 }
110 s1, err = c1.NewStream(context.Background())
111 if err != nil {
112 t.Fatal(err)
113 }
114 s1.Flush()
115 s2, err = c2.AcceptStream(context.Background())
116 if err != nil {
117 t.Fatal(err)
118 }
119 return s1, s2
120 }
121
122
123 type testNet struct {
124 mu sync.Mutex
125 conns map[netip.AddrPort]*testPacketConn
126 }
127
128
129 func (tn *testNet) newPacketConn() *testPacketConn {
130 tn.mu.Lock()
131 defer tn.mu.Unlock()
132 if tn.conns == nil {
133 tn.conns = make(map[netip.AddrPort]*testPacketConn)
134 }
135 localAddr := netip.AddrPortFrom(
136 netip.AddrFrom4([4]byte{
137 127, 0, 0, byte(len(tn.conns)),
138 }),
139 443)
140 tc := &testPacketConn{
141 tn: tn,
142 localAddr: localAddr,
143 gate: gate.New(false),
144 }
145 tn.conns[localAddr] = tc
146 return tc
147 }
148
149 func (tn *testNet) newQUICEndpoint(t testing.TB, config *quic.Config) *quic.Endpoint {
150 t.Helper()
151 pc := tn.newPacketConn()
152 e, err := quic.NewEndpoint(pc, config)
153 if err != nil {
154 t.Fatal(err)
155 }
156 t.Cleanup(func() {
157 e.Close(t.Context())
158 })
159 return e
160 }
161
162
163 func (tn *testNet) connForAddr(srcAddr netip.AddrPort) *testPacketConn {
164 tn.mu.Lock()
165 defer tn.mu.Unlock()
166 return tn.conns[srcAddr]
167 }
168
169
170 type testPacketConn struct {
171 tn *testNet
172 localAddr netip.AddrPort
173
174 gate gate.Gate
175 queue []testPacket
176 closed bool
177 }
178
179 type testPacket struct {
180 b []byte
181 src netip.AddrPort
182 }
183
184 func (tc *testPacketConn) unlock() {
185 tc.gate.Unlock(tc.closed || len(tc.queue) > 0)
186 }
187
188 func (tc *testPacketConn) ReadFrom(p []byte) (n int, srcAddr net.Addr, err error) {
189 if err := tc.gate.WaitAndLock(context.Background()); err != nil {
190 return 0, nil, err
191 }
192 defer tc.unlock()
193 if tc.closed {
194 return 0, nil, net.ErrClosed
195 }
196 n = copy(p, tc.queue[0].b)
197 srcAddr = net.UDPAddrFromAddrPort(tc.queue[0].src)
198 tc.queue = tc.queue[1:]
199 return n, srcAddr, nil
200 }
201
202 func (tc *testPacketConn) WriteTo(p []byte, dstAddr net.Addr) (n int, err error) {
203 tc.gate.Lock()
204 closed := tc.closed
205 tc.unlock()
206 if closed {
207 return 0, net.ErrClosed
208 }
209
210 ap, err := addrPortFromAddr(dstAddr)
211 if err != nil {
212 return 0, err
213 }
214 dst := tc.tn.connForAddr(ap)
215 if dst == nil {
216 return len(p), nil
217 }
218 dst.gate.Lock()
219 defer dst.unlock()
220 dst.queue = append(dst.queue, testPacket{
221 b: bytes.Clone(p),
222 src: tc.localAddr,
223 })
224 return len(p), nil
225 }
226
227 func (tc *testPacketConn) Close() error {
228 tc.tn.mu.Lock()
229 tc.tn.conns[tc.localAddr] = nil
230 tc.tn.mu.Unlock()
231
232 tc.gate.Lock()
233 defer tc.unlock()
234 tc.closed = true
235 tc.queue = nil
236 return nil
237 }
238
239 func (tc *testPacketConn) LocalAddr() net.Addr {
240 return net.UDPAddrFromAddrPort(tc.localAddr)
241 }
242
243 func (tc *testPacketConn) SetDeadline(time.Time) error { panic("unimplemented") }
244 func (tc *testPacketConn) SetReadDeadline(time.Time) error { panic("unimplemented") }
245 func (tc *testPacketConn) SetWriteDeadline(time.Time) error { panic("unimplemented") }
246
247 func addrPortFromAddr(addr net.Addr) (netip.AddrPort, error) {
248 switch a := addr.(type) {
249 case *net.UDPAddr:
250 return a.AddrPort(), nil
251 }
252 return netip.ParseAddrPort(addr.String())
253 }
254
255 var testTLSConfig = &tls.Config{
256 InsecureSkipVerify: true,
257 CipherSuites: []uint16{
258 tls.TLS_AES_128_GCM_SHA256,
259 tls.TLS_AES_256_GCM_SHA384,
260 tls.TLS_CHACHA20_POLY1305_SHA256,
261 },
262 MinVersion: tls.VersionTLS13,
263 Certificates: []tls.Certificate{testCert},
264 NextProtos: []string{"h3"},
265 }
266
267 var testCert = func() tls.Certificate {
268 cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
269 if err != nil {
270 panic(err)
271 }
272 return cert
273 }()
274
View as plain text