vxx2 / vlib / net / http / server_tls_test.v
779 lines · 752 sloc · 27.25 KB · a07c977c5bfc53b0776bb08aecc351b1bb35f154
Raw
1// vtest build: !sanitize-memory-clang
2// Hermetic TLS-termination test for net.http.Server: spin up a local HTTPS
3// server with an in-memory cert/key, hit it with http.fetch (validate: false),
4// and assert the round-trip.
5
6module main
7
8import net
9import net.http
10import net.mbedtls
11import time
12
13const server_tls_cert = '-----BEGIN CERTIFICATE-----\nMIIEOTCCAyECFG64Q2g46jZb3kRbDOJWX/BwjSp6MA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwODAyMTcyOTQyWhgPMjA1MDEyMTcx\nNzI5NDJaMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYD\nVQQHDAtMb3MgQW5nZWxlczEdMBsGA1UECgwUQ2F0YWx5c3QgRGV2ZWxvcG1lbnQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBALqAI4fqUi+QBVWcsXglouLdOML5+w0+1hSR1KdO0Q5XPdQAs/yYWJ+KUkDw\nG++rfy9DUPq7FNRBVurXQkcAtn6gXdllGUSjwUiDo/N4mMOyS/2sufBuaeww7jVi\nrppH+zwP1tUnjRd6khl6bi1Ian9VSzr3Iy9CkXIg1GU4CPXkOydLeoQfepXxWoK1\nOUNwT3VKC/stAfY3j/NIIeiJYkyuRGFCkxn/BUjN+AsXiTugRcYKEFHdIPkOuCXp\nYbhf+lLsczpxCs3rdZG9b/N6mEDCzXTmeHkmsjdPTf+1k5DZZvKzVBBrgdxCgBb7\n5RwjF5v9WmnIc33wWgfJC6FaUzj9NYxYUbPHD+jTz0rJB/jj4u/xJlM/e5NRmXdW\n70pOMKXtWjRSolLOFIPKLY1qs3KMTAZxKKWPDDF7WlMJxMRt7nnnks5yw43Nog4C\njDLk1ZgETnPpLgo3jbmJdIv+OHKTJrBlVvDq7VTyixCoS5G8KoOmyQJhaXG6NwE2\niVhH5JIKgzgCfetfDsnjxqJ/qtrFXPa8FF2TsomD0NK/GZmIcs+9OeVB75Jn5uhF\nfLHScpiTbuu5w3P/LI/MqihLRB6RRNnRzPH8fIg5bYC9b770ta/8GcFRuYE8t+UR\nGtqXJoIKixbDlqV54kal8FQzYzhETf9+NM6Kb/lKEfG/pslvAgMBAAEwDQYJKoZI\nhvcNAQELBQADggEBALI3uNiNO0QE1brA3QYFK+d9ZroB72NrJ0UNkzYHDg2Fc6xg\n4aVVfaxY08+TmKc0JlMOW+pUxeCW/+UBSngdQiR9EE9xm0k0XIrAsy9RXxRvEtPu\nM1VI2h7ayp1Y2BrnQinevTSgtqLRyS1VbOFRl1FiyVvinw2I0KsDdAMNevAPXcOa\nQ8pUgUq6f56DkhocQaj+hxD/uV8HryNxuoSXnPhvfTN3z4YRGzsaWevJ9EYJliOM\n+XugcqfFJ+W7/QCEcAHCL+Bw6OydG5NFORr3p57PXjjcL/uKmxPBrWg2Bz6uT4uR\nMhj0zttiFHLAt9jGfyk6W57UNUja1e1ggftJJhs=\n-----END CERTIFICATE-----\n'
14
15const server_tls_key = '-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----\n'
16
17struct EchoHandler {
18mut:
19 last_path string
20}
21
22fn (mut h EchoHandler) handle(req http.Request) http.Response {
23 h.last_path = req.url
24 return http.Response{
25 status_code: 200
26 body: 'tls hello ${req.url}'
27 }
28}
29
30struct BlockingHandler {
31 started chan bool
32 release chan bool
33}
34
35fn testsuite_end() {
36 http.close_idle_connections()
37}
38
39fn (mut h BlockingHandler) handle(req http.Request) http.Response {
40 h.started <- true
41 _ := <-h.release
42 return http.Response{
43 status_code: 200
44 header: http.new_header(key: .connection, value: 'close')
45 body: 'released'
46 }
47}
48
49fn pick_port() !int {
50 mut l := net.listen_tcp(.ip, '127.0.0.1:0')!
51 port := l.addr()!.port()!
52 l.close()!
53 return port
54}
55
56fn test_server_tls_round_trip() {
57 $if use_openssl ? {
58 // TLS termination for net.http.Server is not yet supported on the
59 // OpenSSL backend; the listener stub reports a clear runtime error and
60 // the test is skipped here so the suite stays green under
61 // `-d use_openssl`.
62 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
63 return
64 }
65 port := pick_port() or {
66 assert false, 'pick_port: ${err}'
67 return
68 }
69 mut srv := &http.Server{
70 addr: '127.0.0.1:${port}'
71 cert: server_tls_cert
72 cert_key: server_tls_key
73 in_memory_verification: true
74 accept_timeout: time.second
75 handler: EchoHandler{}
76 show_startup_message: false
77 }
78 t := spawn srv.listen_and_serve()
79 srv.wait_till_running() or {
80 srv.close()
81 t.wait()
82 assert false, 'server failed to start: ${err}'
83 return
84 }
85 defer {
86 srv.close()
87 t.wait()
88 }
89 // Give the listener a beat to come up.
90 time.sleep(50 * time.millisecond)
91
92 resp := http.fetch(
93 url: 'https://127.0.0.1:${port}/hello'
94 validate: false
95 ) or {
96 assert false, 'fetch failed: ${err}'
97 return
98 }
99 assert resp.status_code == 200
100 assert resp.body == 'tls hello /hello'
101}
102
103fn test_server_tls_stop() {
104 $if use_openssl ? {
105 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
106 return
107 }
108 port := pick_port() or {
109 assert false, 'pick_port: ${err}'
110 return
111 }
112 mut srv := &http.Server{
113 addr: '127.0.0.1:${port}'
114 cert: server_tls_cert
115 cert_key: server_tls_key
116 in_memory_verification: true
117 accept_timeout: 100 * time.millisecond
118 handler: EchoHandler{}
119 show_startup_message: false
120 }
121 t := spawn srv.listen_and_serve()
122 srv.wait_till_running() or {
123 srv.close()
124 t.wait()
125 assert false, 'server failed to start: ${err}'
126 return
127 }
128 srv.stop()
129 assert srv.status() == .stopped
130 t.wait()
131 assert srv.status() == .closed
132}
133
134fn test_server_tls_close_caps_default_accept_poll() {
135 $if use_openssl ? {
136 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
137 return
138 }
139 port := pick_port() or {
140 assert false, 'pick_port: ${err}'
141 return
142 }
143 mut srv := &http.Server{
144 addr: '127.0.0.1:${port}'
145 cert: server_tls_cert
146 cert_key: server_tls_key
147 in_memory_verification: true
148 handler: EchoHandler{}
149 show_startup_message: false
150 }
151 t := spawn srv.listen_and_serve()
152 srv.wait_till_running() or {
153 srv.close()
154 t.wait()
155 assert false, 'server failed to start: ${err}'
156 return
157 }
158 sw := time.new_stopwatch()
159 srv.close()
160 t.wait()
161 assert sw.elapsed() < time.second
162 assert srv.status() == .closed
163}
164
165fn test_server_tls_close_waits_for_active_request() {
166 $if use_openssl ? {
167 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
168 return
169 }
170 port := pick_port() or {
171 assert false, 'pick_port: ${err}'
172 return
173 }
174 started := chan bool{cap: 1}
175 release := chan bool{cap: 1}
176 done := chan string{cap: 1}
177 mut srv := &http.Server{
178 addr: '127.0.0.1:${port}'
179 cert: server_tls_cert
180 cert_key: server_tls_key
181 in_memory_verification: true
182 accept_timeout: time.second
183 handler: BlockingHandler{
184 started: started
185 release: release
186 }
187 show_startup_message: false
188 }
189 t := spawn srv.listen_and_serve()
190 srv.wait_till_running() or {
191 srv.close()
192 t.wait()
193 assert false, 'server failed to start: ${err}'
194 return
195 }
196 spawn fn [done, port] () {
197 resp := http.fetch(
198 url: 'https://127.0.0.1:${port}/blocked'
199 enable_http2: false
200 validate: false
201 ) or {
202 done <- 'error: ${err}'
203 return
204 }
205 done <- resp.body
206 }()
207 select {
208 _ := <-started {}
209 msg := <-done {
210 srv.close()
211 t.wait()
212 assert false, 'client finished before handler started: ${msg}'
213 return
214 }
215 2 * time.second {
216 srv.close()
217 t.wait()
218 assert false, 'timed out waiting for handler to start'
219 return
220 }
221 }
222 srv.close()
223 time.sleep(50 * time.millisecond)
224 release <- true
225 assert (<-done) == 'released'
226 t.wait()
227 assert srv.status() == .closed
228}
229
230fn test_server_tls_close_during_silent_handshake() {
231 $if use_openssl ? {
232 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
233 return
234 }
235 port := pick_port() or {
236 assert false, 'pick_port: ${err}'
237 return
238 }
239 mut srv := &http.Server{
240 addr: '127.0.0.1:${port}'
241 cert: server_tls_cert
242 cert_key: server_tls_key
243 in_memory_verification: true
244 accept_timeout: 100 * time.millisecond
245 handler: EchoHandler{}
246 show_startup_message: false
247 }
248 t := spawn srv.listen_and_serve()
249 srv.wait_till_running() or {
250 srv.close()
251 t.wait()
252 assert false, 'server failed to start: ${err}'
253 return
254 }
255 mut client := net.dial_tcp('127.0.0.1:${port}') or {
256 srv.close()
257 t.wait()
258 assert false, 'tcp dial failed: ${err}'
259 return
260 }
261 defer {
262 client.close() or {}
263 }
264 time.sleep(50 * time.millisecond)
265 sw := time.new_stopwatch()
266 srv.close()
267 t.wait()
268 assert sw.elapsed() < time.second
269 assert srv.status() == .closed
270}
271
272// test_server_tls_close_under_handshake_flood guards the case where every worker
273// is stuck in a slow TLS handshake AND the channel buffer is full of untracked
274// raw conns, so the accept thread parks on `ch <- conn`. If the accept thread
275// could not poll s.state during that send it would never reach close_idle(), and
276// close() would hang until a worker handshake timed out. Each client sends a
277// valid ClientHello then stalls, so the server's handshake blocks waiting for the
278// client's next flight (a bare connect would instead fail the handshake fast and
279// free the worker). With one worker and a one-slot buffer this fills: conn 0 ->
280// the worker, conn 1 -> the buffer, conn 2 -> the blocked send.
281fn test_server_tls_close_under_handshake_flood() {
282 $if use_openssl ? {
283 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
284 return
285 }
286 port := pick_port() or {
287 assert false, 'pick_port: ${err}'
288 return
289 }
290 // A real 165-byte TLS 1.2 ClientHello captured from V's own mbedtls client.
291 // The exact bytes (random/session id) are irrelevant; the server only needs a
292 // structurally valid first flight to start the handshake and then block
293 // waiting for the client's ClientKeyExchange, which never arrives.
294 client_hello := [u8(0x16), 0x03, 0x03, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x9c, 0x03, 0x03, 0x6a,
295 0x3d, 0x34, 0x4c, 0x8f, 0x46, 0x64, 0xd8, 0xe9, 0x2d, 0x46, 0x16, 0xd3, 0xf2, 0x87, 0xe4,
296 0xee, 0xc5, 0x57, 0x6c, 0x8e, 0xd7, 0x36, 0x19, 0x3b, 0x35, 0x7a, 0x01, 0x03, 0xde, 0x6e,
297 0x64, 0x00, 0x00, 0x24, 0xc0, 0x2c, 0xc0, 0x2b, 0xc0, 0x30, 0xc0, 0x2f, 0xc0, 0x24, 0xc0,
298 0x23, 0xc0, 0x28, 0xc0, 0x27, 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x13, 0x00, 0x9d,
299 0x00, 0x9c, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x01, 0x00, 0x00, 0x4f, 0x00,
300 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02,
301 0x01, 0x00, 0x00, 0x0d, 0x00, 0x1a, 0x00, 0x18, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04,
302 0x01, 0x05, 0x01, 0x02, 0x01, 0x04, 0x03, 0x05, 0x03, 0x02, 0x03, 0x02, 0x02, 0x06, 0x01,
303 0x06, 0x03, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32,
304 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01,
305 0x00, 0x01, 0x00]
306 mut srv := &http.Server{
307 addr: '127.0.0.1:${port}'
308 cert: server_tls_cert
309 cert_key: server_tls_key
310 in_memory_verification: true
311 // A finite accept_timeout doubles as the handshake budget (see
312 // tls_accept_timeouts), so use a large one: each stalled handshake must
313 // stay stuck long enough to keep the worker busy and form the wedge,
314 // rather than time out in milliseconds. The accept loop still polls at the
315 // 100ms tls_accept_poll_timeout cap regardless.
316 accept_timeout: 8 * time.second
317 worker_num: 1
318 pool_channel_slots: 1
319 handler: EchoHandler{}
320 show_startup_message: false
321 }
322 t := spawn srv.listen_and_serve()
323 srv.wait_till_running() or {
324 srv.close()
325 t.wait()
326 assert false, 'server failed to start: ${err}'
327 return
328 }
329 mut clients := []&net.TcpConn{}
330 for _ in 0 .. 4 {
331 mut c := net.dial_tcp('127.0.0.1:${port}') or { continue }
332 c.write(client_hello) or {}
333 clients << c
334 }
335 defer {
336 for mut c in clients {
337 c.close() or {}
338 }
339 }
340 // Let the accept thread occupy the worker and buffer and park on the send.
341 time.sleep(400 * time.millisecond)
342 sw := time.new_stopwatch()
343 srv.close()
344 t.wait()
345 // close_idle() force-closes the stuck handshake fds, so close() returns well
346 // before the 8s handshake timeout — but only if the accept thread escaped the
347 // blocked send to reach it.
348 assert sw.elapsed() < 2 * time.second
349 assert srv.status() == .closed
350}
351
352fn test_server_tls_close_interrupts_idle_keep_alive() {
353 $if use_openssl ? {
354 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
355 return
356 }
357 port := pick_port() or {
358 assert false, 'pick_port: ${err}'
359 return
360 }
361 mut srv := &http.Server{
362 addr: '127.0.0.1:${port}'
363 cert: server_tls_cert
364 cert_key: server_tls_key
365 in_memory_verification: true
366 accept_timeout: time.second
367 read_timeout: 5 * time.second
368 handler: EchoHandler{}
369 show_startup_message: false
370 }
371 t := spawn srv.listen_and_serve()
372 srv.wait_till_running() or {
373 srv.close()
374 t.wait()
375 assert false, 'server failed to start: ${err}'
376 return
377 }
378 mut client := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{}) or {
379 srv.close()
380 t.wait()
381 assert false, 'ssl client init failed: ${err}'
382 return
383 }
384 defer {
385 client.shutdown() or {}
386 }
387 client.dial('127.0.0.1', port) or {
388 srv.close()
389 t.wait()
390 assert false, 'ssl dial failed: ${err}'
391 return
392 }
393 request := 'GET /idle HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\nConnection: keep-alive\r\n\r\n'
394 client.write_string(request) or {
395 srv.close()
396 t.wait()
397 assert false, 'ssl write failed: ${err}'
398 return
399 }
400 mut buf := []u8{len: 4096}
401 mut response_bytes := []u8{cap: 4096}
402 read_sw := time.new_stopwatch()
403 for read_sw.elapsed() < 2 * time.second {
404 n := client.read(mut buf) or {
405 time.sleep(20 * time.millisecond)
406 continue
407 }
408 if n <= 0 {
409 time.sleep(20 * time.millisecond)
410 continue
411 }
412 response_bytes << buf[..n]
413 response_so_far := response_bytes.bytestr()
414 if response_so_far.contains('\r\n\r\n') && response_so_far.contains('tls hello /idle') {
415 break
416 }
417 }
418 if response_bytes.len > 0 {
419 response := response_bytes.bytestr()
420 assert response.to_lower().contains('connection: keep-alive')
421 assert response.contains('tls hello /idle')
422 }
423
424 sw := time.new_stopwatch()
425 srv.close()
426 t.wait()
427 assert sw.elapsed() < 2 * time.second
428 assert srv.status() == .closed
429}
430
431fn test_server_tls_close_interrupts_idle_h2() {
432 $if use_openssl ? {
433 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
434 return
435 }
436 port := pick_port() or {
437 assert false, 'pick_port: ${err}'
438 return
439 }
440 mut srv := &http.Server{
441 addr: '127.0.0.1:${port}'
442 cert: server_tls_cert
443 cert_key: server_tls_key
444 in_memory_verification: true
445 accept_timeout: time.second
446 read_timeout: 5 * time.second
447 enable_http2: true
448 handler: EchoHandler{}
449 show_startup_message: false
450 }
451 t := spawn srv.listen_and_serve()
452 srv.wait_till_running() or {
453 srv.close()
454 t.wait()
455 assert false, 'server failed to start: ${err}'
456 return
457 }
458 mut client := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
459 alpn_protocols: ['h2']
460 }) or {
461 srv.close()
462 t.wait()
463 assert false, 'ssl client init failed: ${err}'
464 return
465 }
466 defer {
467 client.shutdown() or {}
468 }
469 client.dial('127.0.0.1', port) or {
470 srv.close()
471 t.wait()
472 assert false, 'ssl dial failed: ${err}'
473 return
474 }
475 assert client.negotiated_alpn() == 'h2'
476 mut h2 := http.new_h2_conn(client)
477 resp := h2.do(http.H2ClientRequest{
478 method: 'GET'
479 scheme: 'https'
480 authority: '127.0.0.1:${port}'
481 path: '/h2-idle'
482 }) or {
483 srv.close()
484 t.wait()
485 assert false, 'h2 request failed: ${err}'
486 return
487 }
488 assert resp.status == 200
489 assert resp.body.bytestr() == 'tls hello /h2-idle'
490
491 sw := time.new_stopwatch()
492 srv.close()
493 t.wait()
494 assert sw.elapsed() < 2 * time.second
495 assert srv.status() == .closed
496}
497
498fn test_server_tls_close_interrupts_incomplete_h2_request() {
499 $if use_openssl ? {
500 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
501 return
502 }
503 port := pick_port() or {
504 assert false, 'pick_port: ${err}'
505 return
506 }
507 mut srv := &http.Server{
508 addr: '127.0.0.1:${port}'
509 cert: server_tls_cert
510 cert_key: server_tls_key
511 in_memory_verification: true
512 accept_timeout: time.second
513 read_timeout: 2 * time.second
514 enable_http2: true
515 handler: EchoHandler{}
516 show_startup_message: false
517 }
518 t := spawn srv.listen_and_serve()
519 srv.wait_till_running() or {
520 srv.close()
521 t.wait()
522 assert false, 'server failed to start: ${err}'
523 return
524 }
525 mut client := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
526 alpn_protocols: ['h2']
527 }) or {
528 srv.close()
529 t.wait()
530 assert false, 'ssl client init failed: ${err}'
531 return
532 }
533 defer {
534 client.shutdown() or {}
535 }
536 client.dial('127.0.0.1', port) or {
537 srv.close()
538 t.wait()
539 assert false, 'ssl dial failed: ${err}'
540 return
541 }
542 assert client.negotiated_alpn() == 'h2'
543 mut enc := http.H2HpackEncoder{}
544 block := enc.encode([
545 http.H2HeaderField{':method', 'POST'},
546 http.H2HeaderField{':scheme', 'https'},
547 http.H2HeaderField{':authority', '127.0.0.1:${port}'},
548 http.H2HeaderField{':path', '/h2-incomplete'},
549 ])
550 mut out := []u8{}
551 out << http.h2_client_preface.bytes()
552 out << http.H2Frame(http.H2SettingsFrame{}).encode()
553 out << http.H2Frame(http.H2HeadersFrame{
554 stream_id: 1
555 fragment: block
556 end_headers: true
557 end_stream: false
558 }).encode()
559 written := client.write(out) or {
560 srv.close()
561 t.wait()
562 assert false, 'ssl write failed: ${err}'
563 return
564 }
565 assert written == out.len
566 time.sleep(100 * time.millisecond)
567
568 sw := time.new_stopwatch()
569 srv.close()
570 t.wait()
571 assert sw.elapsed() < time.second
572 assert srv.status() == .closed
573}
574
575fn test_server_tls_parallel_handshakes() {
576 $if use_openssl ? {
577 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
578 return
579 }
580 $if macos {
581 eprintln('skipping: macOS mbedTLS rejects the silent TCP probes')
582 return
583 }
584 port := pick_port() or {
585 assert false, 'pick_port: ${err}'
586 return
587 }
588 // Prove handshakes run in parallel on the worker threads, not serially on the
589 // accept thread. The discriminator is placed at the HANDSHAKE layer, not at the
590 // request handler: a barrier inside the handler only proves two *handlers*
591 // overlapped, which a serial-accept-then-queue design also produces (the accept
592 // thread can complete two handshakes one after another and hand both completed
593 // connections to workers that then park together). Counting overlapping handlers
594 // would therefore miss exactly the regression we care about.
595 //
596 // Instead, occupy `stall` workers with raw TCP connections that complete the TCP
597 // connect but never send a ClientHello, so each one wedges a worker *inside*
598 // complete_handshake (blocked reading the handshake). Then fire `live` real HTTPS
599 // clients. If handshakes run per-worker in parallel, the remaining free workers
600 // service the live clients concurrently and they succeed. If the implementation
601 // regresses to handshaking serially on the accept thread, the very first stalled
602 // connection wedges that thread mid-handshake and no later connection — stalled or
603 // live — is ever accepted/serviced, so every live fetch fails. worker_num must be
604 // >= stall + live so a free worker exists for each live client.
605 worker_num := 4
606 stall := 2
607 live := 2
608 mut srv := &http.Server{
609 addr: '127.0.0.1:${port}'
610 cert: server_tls_cert
611 cert_key: server_tls_key
612 in_memory_verification: true
613 accept_timeout: 10 * time.second
614 worker_num: worker_num
615 handler: EchoHandler{}
616 show_startup_message: false
617 }
618 t := spawn srv.listen_and_serve()
619 srv.wait_till_running() or {
620 srv.close()
621 t.wait()
622 assert false, 'server failed to start: ${err}'
623 return
624 }
625 defer {
626 srv.close()
627 t.wait()
628 }
629 // Wedge `stall` workers mid-handshake with silent TCP connections.
630 mut stalled := []&net.TcpConn{}
631 for _ in 0 .. stall {
632 c := net.dial_tcp('127.0.0.1:${port}') or {
633 assert false, 'stall dial failed: ${err}'
634 return
635 }
636 stalled << c
637 }
638 // Give the accept loop time to hand each stalled connection to a worker (and, in
639 // a regressed serial-accept design, to become wedged on the first one) before the
640 // live clients connect.
641 time.sleep(500 * time.millisecond)
642 // Fire live clients while the stalled sockets occupy workers. With parallel
643 // per-worker handshakes, at least one free worker services a live client in
644 // well under a second. A transient TLS client-side handshake error is not, by
645 // itself, proof that the accept thread regressed to serial handshaking; the
646 // discriminator is that no live request can complete while the accept thread is
647 // wedged inside a stalled handshake.
648 //
649 // The discriminator is time-bounded on purpose: a serial-accept regression does
650 // eventually service the live clients — but only after the stalled handshakes hit
651 // their budget (handshake_timeout == accept_timeout == 10s here, x2 stalled), so
652 // without a bound the test would merely run slower, not fail. The bound is on the
653 // WAIT itself (a select deadline below), not the client: http.fetch cannot bound
654 // this for us because Request.ssl_do dials and completes the TLS handshake before
655 // req.read_timeout applies (and retries on socket errors), so a read_timeout would
656 // not cap a regressed handshake. The select makes the regression fail promptly at
657 // ~6s; a real parallel handshake completes in well under a second.
658 live_budget := 6 * time.second
659 results := chan string{cap: live}
660 sw := time.new_stopwatch()
661 for i in 0 .. live {
662 spawn fn [results, port, i] () {
663 resp := http.fetch(
664 url: 'https://127.0.0.1:${port}/live${i}'
665 enable_http2: false
666 validate: false
667 // One attempt, no retries: a regressed handshake should surface as a
668 // single timed-out request, not retry-amplify the teardown.
669 max_retries: 1
670 // Force a fresh TLS connection per live client. Without this, if one
671 // client finishes and returns its keep-alive connection to the shared
672 // pool before the other checks one out, the second reuses it and the
673 // test would pass after only a single live handshake — not two
674 // concurrent completing handshakes against the shared mbedtls state.
675 disable_connection_reuse: true
676 ) or {
677 results <- 'error: ${err}'
678 return
679 }
680 if resp.status_code != 200 {
681 results <- 'bad status: ${resp.status_code}'
682 return
683 }
684 results <- resp.body
685 }()
686 }
687 // Collect live results within a single `live_budget`, bounding the wait so a
688 // serialized-handshake regression fails at ~6s instead of hanging on the
689 // client's own handshake timeout.
690 mut ok := 0
691 mut received := 0
692 mut failures := []string{}
693 for received < live {
694 remaining := live_budget - sw.elapsed()
695 if remaining <= 0 {
696 break
697 }
698 select {
699 body := <-results {
700 received++
701 if body.starts_with('tls hello /live') {
702 ok++
703 } else {
704 failures << body
705 }
706 }
707 remaining {
708 break
709 }
710 }
711 }
712 assert ok > 0, 'expected at least one live handshake to complete within ${live_budget} while ${stall} workers were mid-handshake; got ${ok}/${live}, failures: ${failures} - handshakes appear serialized on the accept thread, not parallel'
713 // Release the stalled sockets; srv.close() in the defer also force-closes any the
714 // worker is still blocked on via the idle tracker.
715 for mut c in stalled {
716 c.close() or {}
717 }
718}
719
720fn test_server_tls_h2_negotiation() {
721 $if use_openssl ? {
722 eprintln('skipping: TLS server not implemented for -d use_openssl yet')
723 return
724 }
725 port := pick_port() or {
726 assert false, 'pick_port: ${err}'
727 return
728 }
729 mut srv := &http.Server{
730 addr: '127.0.0.1:${port}'
731 cert: server_tls_cert
732 cert_key: server_tls_key
733 in_memory_verification: true
734 accept_timeout: time.second
735 enable_http2: true
736 handler: EchoHandler{}
737 show_startup_message: false
738 }
739 t := spawn srv.listen_and_serve()
740 srv.wait_till_running() or {
741 srv.close()
742 t.wait()
743 assert false, 'server failed to start: ${err}'
744 return
745 }
746 defer {
747 srv.close()
748 t.wait()
749 }
750 time.sleep(50 * time.millisecond)
751
752 // Client opts into HTTP/2; server must select `h2` via ALPN and serve the
753 // request through its HTTP/2 driver.
754 resp := http.fetch(
755 url: 'https://127.0.0.1:${port}/h2'
756 enable_http2: true
757 validate: false
758 ) or {
759 assert false, 'h2 fetch failed: ${err}'
760 return
761 }
762 assert resp.version() == .v2_0
763 assert resp.status_code == 200
764 assert resp.body == 'tls hello /h2'
765
766 // With HTTP/2 disabled on the client, the server must keep speaking
767 // HTTP/1.1 to the same listener. (enable_http2 defaults to true since
768 // vlang/v#27384, so it must be opted out of explicitly here.)
769 resp_h1 := http.fetch(
770 url: 'https://127.0.0.1:${port}/h1'
771 enable_http2: false
772 validate: false
773 ) or {
774 assert false, 'h1 fetch failed: ${err}'
775 return
776 }
777 assert resp_h1.version() == .v1_1
778 assert resp_h1.status_code == 200
779}
780