v / vlib / net / http / server_tls_notd_use_openssl.v
236 lines · 216 sloc · 7.04 KB · c5ce895e263322da49a8c8d16c494d78667cde48
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6import io
7import net
8import time
9import net.mbedtls
10
11const tls_accept_poll_timeout = 100 * time.millisecond
12
13// tls_handshake_timeout is the default value for Server.tls_handshake_timeout,
14// used as the fallback handshake budget when Server.accept_timeout is zero or
15// net.infinite_timeout. The handshake runs on the accept thread, so without a
16// finite bound a client that completes the TCP connect and then stalls
17// mid-handshake would wedge the accept loop forever.
18const tls_handshake_timeout = 30 * time.second
19
20fn tls_accept_timeouts(accept_timeout time.Duration, handshake_fallback time.Duration) (time.Duration, time.Duration) {
21 // A finite `accept_timeout` doubles as the handshake budget; when it is
22 // zero or net.infinite_timeout (i64.max), fall back to `handshake_fallback`
23 // so the handshake still times out and shutdown stays responsive.
24 // net.infinite_timeout is positive (i64.max), so the > 0 check alone is
25 // not enough — mbedtls's ssl_timeout_deadline treats it as an infinite
26 // deadline exactly like 0 or negative values.
27 is_finite := accept_timeout > 0 && accept_timeout != net.infinite_timeout
28 handshake_timeout := if is_finite { accept_timeout } else { handshake_fallback }
29 accept_poll_timeout := if is_finite && accept_timeout < tls_accept_poll_timeout {
30 accept_timeout
31 } else {
32 tls_accept_poll_timeout
33 }
34 return accept_poll_timeout, handshake_timeout
35}
36
37// This file implements TLS termination for net.http.Server on top of the
38// mbedtls SSL listener. It is gated to the default TLS backend; the matching
39// `server_tls_d_use_openssl.v` provides a clear-error stub when the project is
40// built with `-d use_openssl`.
41
42// listen_and_serve_tls is the TLS counterpart of listen_and_serve. It is
43// dispatched to by listen_and_serve when `s.cert` and `s.cert_key` are set.
44fn (mut s Server) listen_and_serve_tls() {
45 // Pick a default port that's distinct from the plain-HTTP default if the
46 // user hasn't overridden it.
47 addr := if s.addr == '' || s.addr == ':${default_server_port}' {
48 ':${default_https_server_port}'
49 } else {
50 s.addr
51 }
52
53 // When HTTP/2 is enabled, advertise ALPN `h2, http/1.1` on the listener.
54 // Clients that select `h2` are dispatched to the HTTP/2 driver after the
55 // handshake; clients that select `http/1.1` (or send no ALPN extension)
56 // keep the existing HTTP/1.1 worker path.
57 alpn := if s.enable_http2 { ['h2', 'http/1.1'] } else { []string{} }
58 mut listener := mbedtls.new_ssl_listener(addr, mbedtls.SSLConnectConfig{
59 cert: s.cert
60 cert_key: s.cert_key
61 in_memory_verification: s.in_memory_verification
62 validate: false // accept any client; servers don't verify clients by default
63 read_timeout: s.read_timeout
64 alpn_protocols: alpn
65 }) or {
66 eprintln('Listening TLS on ${addr} failed, err: ${err}')
67 return
68 }
69 defer {
70 listener.shutdown() or {}
71 if s.state == .stopped {
72 s.state = .closed
73 if s.on_closed != unsafe { nil } {
74 s.on_closed(mut s)
75 }
76 }
77 }
78 s.addr = addr
79
80 ch := chan &mbedtls.SSLConn{cap: s.pool_channel_slots}
81 mut idle_conns := &TlsIdleConnTracker{}
82 mut ws := []thread{cap: s.worker_num}
83 for wid in 0 .. s.worker_num {
84 ws << new_tls_handler_worker(wid, ch, s.handler, s.max_keep_alive_requests, idle_conns)
85 }
86
87 if s.show_startup_message {
88 println('Listening on https://${s.addr}/')
89 flush_stdout()
90 }
91
92 time.sleep(20 * time.millisecond)
93 s.state = .running
94 if s.on_running != unsafe { nil } {
95 s.on_running(mut s)
96 }
97 accept_poll_timeout, handshake_timeout := tls_accept_timeouts(s.accept_timeout,
98 s.tls_handshake_timeout)
99 for s.state == .running {
100 mut conn := listener.accept_with_timeouts(accept_poll_timeout, handshake_timeout) or {
101 if s.state != .running {
102 break
103 }
104 if err.code() == net.err_timed_out_code {
105 continue
106 }
107 $if debug {
108 eprintln('TLS accept failed: ${err}; skipping')
109 }
110 continue
111 }
112 if s.read_timeout > 0 {
113 conn.set_read_timeout(s.read_timeout)
114 }
115 ch <- conn
116 }
117 ch.close()
118 idle_conns.close_idle()
119 ws.wait()
120}
121
122// TlsHandlerWorker serves HTTP/1.1 requests on TLS-wrapped connections.
123struct TlsHandlerWorker {
124 id int
125 ch chan &mbedtls.SSLConn
126 max_keep_alive_requests int
127mut:
128 idle_conns &TlsIdleConnTracker = unsafe { nil }
129pub mut:
130 handler Handler
131}
132
133fn new_tls_handler_worker(wid int, ch chan &mbedtls.SSLConn, handler Handler, max_keep_alive_requests int, idle_conns &TlsIdleConnTracker) thread {
134 mut w := &TlsHandlerWorker{
135 id: wid
136 ch: ch
137 handler: handler
138 max_keep_alive_requests: max_keep_alive_requests
139 idle_conns: idle_conns
140 }
141 return spawn w.process_requests()
142}
143
144fn (mut w TlsHandlerWorker) process_requests() {
145 for {
146 mut conn := <-w.ch or { break }
147 w.handle_conn(mut conn)
148 }
149}
150
151fn (mut w TlsHandlerWorker) handle_conn(mut conn mbedtls.SSLConn) {
152 defer {
153 w.idle_conns.unmark_idle(conn.handle)
154 conn.shutdown() or {}
155 }
156 // If the TLS handshake negotiated HTTP/2 via ALPN, switch to the HTTP/2
157 // driver; otherwise fall through to the existing HTTP/1.1 path unchanged.
158 if conn.negotiated_alpn() == 'h2' {
159 serve_h2_conn_with_idle_tracker(mut conn, mut w.handler, w.idle_conns, conn.handle) or {
160 $if debug {
161 eprintln('h2 server error: ${err}')
162 }
163 }
164 return
165 }
166 mut reader := io.new_buffered_reader(reader: conn)
167 defer {
168 unsafe {
169 reader.free()
170 }
171 }
172
173 mut request_count := 0
174 for {
175 if !w.idle_conns.mark_idle(conn.handle) {
176 return
177 }
178 mut req := parse_request(mut reader) or {
179 if err !is io.Eof {
180 $if debug {
181 eprintln('error parsing TLS request: ${err}')
182 }
183 }
184 return
185 }
186 w.idle_conns.unmark_idle(conn.handle)
187 request_count++
188 // `conn.ip` is the peer's IPv4 address as populated by mbedtls'
189 // accept(); blank for IPv6, which is acceptable for keep-alive logic.
190 if conn.ip != '' {
191 req.header.add_custom('Remote-Addr', conn.ip) or {}
192 }
193
194 mut resp := w.handler.handle(req)
195 normalize_server_response(mut resp, req)
196
197 if !resp.header.contains(.content_length) {
198 resp.header.set(.content_length, '${resp.body.len}')
199 }
200
201 max_reached := w.max_keep_alive_requests > 0 && request_count >= w.max_keep_alive_requests
202 req_conn := (req.header.get(.connection) or { '' }).to_lower()
203 resp_conn := (resp.header.get(.connection) or { '' }).to_lower()
204 keep_alive := if max_reached {
205 false
206 } else if resp_conn == 'close' {
207 false
208 } else if resp_conn == 'keep-alive' {
209 true
210 } else if req_conn == 'close' {
211 false
212 } else if req_conn == 'keep-alive' {
213 true
214 } else {
215 req.version == .v1_1
216 }
217 if max_reached || !resp.header.contains(.connection) {
218 if keep_alive {
219 resp.header.set(.connection, 'keep-alive')
220 } else {
221 resp.header.set(.connection, 'close')
222 }
223 }
224
225 conn.write(resp.bytes()) or {
226 $if debug {
227 eprintln('error sending TLS response: ${err}')
228 }
229 return
230 }
231
232 if !keep_alive {
233 return
234 }
235 }
236}
237