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