Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
04e10f5
feat: add openssl-based https server support in net.openssl and veb
guweigang Jun 17, 2026
975c332
fix: address review feedback for OpenSSL HTTPS listener and veb
guweigang Jun 19, 2026
41d4b13
fix(net): honor OpenSSL listener family and timeouts
guweigang Jun 20, 2026
861e65d
test(net): cover OpenSSL listener family and timeout
guweigang Jun 20, 2026
1b6a63a
fix(veb): preserve handler responses through middleware
guweigang Jun 20, 2026
57a85b8
fix(net): honor OpenSSL listener memory and ALPN config
guweigang Jun 21, 2026
a83b139
fix(net): preserve OpenSSL listener certificate chains
guweigang Jun 23, 2026
5a64b92
Merge remote-tracking branch 'upstream/master' into openssl-https
guweigang Jun 23, 2026
009abcc
test(veb): make SSL tests discoverable
guweigang Jun 24, 2026
f34fa2e
Merge remote-tracking branch 'upstream/master' into openssl-https
guweigang Jun 24, 2026
7098911
net.openssl: always release SSLConn socket on shutdown
medvednikov Jun 26, 2026
89f9db3
test(veb): exclude mbedtls HTTPS test from -d use_openssl runs
medvednikov Jun 26, 2026
02dc1a4
Merge remote-tracking branch 'upstream/master' into openssl-https
medvednikov Jun 26, 2026
61636f2
net.openssl: clamp expired finite TLS deadlines to an immediate timeout
medvednikov Jun 26, 2026
df3a1b2
net.ssl: honor listener options for mbedtls
medvednikov Jun 26, 2026
d05ee16
net: address SSL listener review feedback
guweigang Jun 28, 2026
dcc2d8d
Merge remote-tracking branch 'upstream/master' into openssl-https
guweigang Jun 28, 2026
bc07637
Merge remote-tracking branch 'upstream/master' into openssl-https
guweigang Jun 30, 2026
acf577e
net: address OpenSSL listener review feedback
guweigang Jun 30, 2026
c860885
net: address SSL listener review feedback
guweigang Jul 1, 2026
ccf15c8
net: bound OpenSSL HTTPS handshakes
guweigang Jul 1, 2026
1e942fd
net: harden OpenSSL listener cleanup
guweigang Jul 9, 2026
7fd5b39
net: polish SSL listener review fixes
guweigang Jul 10, 2026
762ddef
net: tighten SSL listener edge cases
guweigang Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions vlib/net/openssl/openssl.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,16 @@ fn C.SSLv23_client_method() &C.SSL_METHOD

fn C.TLS_method() voidptr

fn C.TLS_server_method() &C.SSL_METHOD

fn C.TLSv1_2_method() voidptr

fn C.SSL_CTX_check_private_key(ctx &C.SSL_CTX) i32

fn C.SSL_accept(ssl &C.SSL) i32

fn C.ERR_print_errors_fp(fp voidptr)

fn C.v_net_openssl_init_ssl() i32

fn init() {
Expand Down
59 changes: 59 additions & 0 deletions vlib/net/openssl/openssl_sslconn_shutdown_does_not_panic_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import time
import context
import net.openssl

fn server() ! {
cfg := openssl.SSLConnectConfig{
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'
cert_key: '-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----\n'
validate: false
in_memory_verification: true
}
mut srv := openssl.new_ssl_listener('127.0.0.1:64445', cfg) or {
eprintln('Listen: ${err}')
return err
}
eprintln('[+] Listening')
mut cli := srv.accept() or {
eprintln('Accept: ${err}')
return err
}
eprintln('[+] Accepted connection')
cli.shutdown()!
}

// 用 _test.v 内的 test_ 函数
fn test_shutdown_does_not_panic() {
Comment thread
guweigang marked this conversation as resolved.
_ := spawn server()
time.sleep(1 * time.second)
mut client := openssl.new_ssl_conn(validate: false)!
client.dial('127.0.0.1', 64445) or {
eprintln('Connect: ${err}')
return
Comment thread
guweigang marked this conversation as resolved.
Outdated
}
eprintln('[+] Connected')
time.sleep(1 * time.second)
mut background := context.background()
mut ctx, cancel := context.with_timeout(mut background, 2 * time.second)
spawn fn () {
mut i := 0
for {
i++
print('\r${i}...')
flush_stdout()
time.sleep(1 * time.second)
}
}()
mut done := ctx.done()
for {
select {
_ := <-done {
break
}
}
}

eprintln('\nTimeout without panic - OK')

assert true
}
147 changes: 147 additions & 0 deletions vlib/net/openssl/ssl_listener.c.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
module openssl

import net
import time
import os

// SSLListener is the SSL listener implementation for OpenSSL.
pub struct SSLListener {
saddr string
config SSLConnectConfig
mut:
tcp_listener &net.TcpListener = unsafe { nil }
sslctx &C.SSL_CTX = unsafe { nil }
}

// new_ssl_listener creates a new SSLListener binding to `saddr` with `config`.
pub fn new_ssl_listener(saddr string, config SSLConnectConfig) !&SSLListener {
mut listener := &SSLListener{
saddr: saddr
config: config
}
listener.init()!
return listener
}

fn (mut l SSLListener) init() ! {
if l.config.cert == '' || l.config.cert_key == '' {
return error('net.openssl SSLListener.init, no certificate or key provided')
}
Comment thread
guweigang marked this conversation as resolved.

l.tcp_listener = net.listen_tcp(.ip, l.saddr, net.ListenOptions{})!
Comment thread
guweigang marked this conversation as resolved.
Outdated

l.sslctx = unsafe { C.SSL_CTX_new(C.TLS_server_method()) }
Comment thread
guweigang marked this conversation as resolved.
Outdated
if l.sslctx == 0 {
l.tcp_listener.close() or {}
return error('net.openssl SSLListener.init, could not get ssl context')
}

mut cert := l.config.cert
mut cert_key := l.config.cert_key

if l.config.in_memory_verification {
now := time.now().unix().str()
cert = os.temp_dir() + '/v_srv_cert' + now
cert_key = os.temp_dir() + '/v_srv_cert_key' + now
if l.config.cert != '' {
os.write_file(cert, l.config.cert)!
}
if l.config.cert_key != '' {
os.write_file(cert_key, l.config.cert_key)!
Comment thread
guweigang marked this conversation as resolved.
Outdated
}
}

mut res := C.SSL_CTX_use_certificate_file(voidptr(l.sslctx), &char(cert.str),
C.SSL_FILETYPE_PEM)
if res != 1 {
C.ERR_print_errors_fp(C.stderr)
l.shutdown() or {}
return error('net.openssl SSLListener.init, SSL_CTX_use_certificate_file failed')
}

res = C.SSL_CTX_use_PrivateKey_file(voidptr(l.sslctx), &char(cert_key.str), C.SSL_FILETYPE_PEM)
if res != 1 {
l.shutdown() or {}
return error('net.openssl SSLListener.init, SSL_CTX_use_PrivateKey_file failed')
}

res = C.SSL_CTX_check_private_key(voidptr(l.sslctx))
if res != 1 {
l.shutdown() or {}
return error('net.openssl SSLListener.init, SSL_CTX_check_private_key failed')
}
}

// accept accepts a new TCP connection and performs the SSL handshake.
pub fn (mut l SSLListener) accept() !&SSLConn {
mut conn := l.accept_without_handshake()!
conn.accept_handshake() or {
conn.shutdown() or {}
return err
}
return conn
}

// accept_without_handshake accepts a new TCP connection but does not perform the handshake yet.
pub fn (mut l SSLListener) accept_without_handshake() !&SSLConn {
mut tcp_conn := l.tcp_listener.accept()!

ssl := unsafe { &C.SSL(C.SSL_new(l.sslctx)) }
if ssl == 0 {
tcp_conn.close() or {}
return error('net.openssl SSLListener.accept, could not create SSL instance')
}

if C.SSL_set_fd(voidptr(ssl), tcp_conn.sock.handle) != 1 {
C.SSL_free(voidptr(ssl))
tcp_conn.close() or {}
return error('net.openssl SSLListener.accept, could not assign ssl to socket')
}

mut conn := &SSLConn{
config: l.config
sslctx: unsafe { nil } // 设为 nil 避免连接关闭时意外释放共享的 sslctx
Comment thread
guweigang marked this conversation as resolved.
Outdated
ssl: ssl
handle: tcp_conn.sock.handle
duration: tcp_conn.read_timeout()
owns_socket: true
}

return conn
}

// accept_handshake performs the SSL handshake on the connection.
pub fn (mut conn SSLConn) accept_handshake() ! {
// 执行 SSL 握手过程
deadline := ssl_timeout_deadline(conn.duration)
for {
res := C.SSL_accept(voidptr(conn.ssl))
Comment thread
guweigang marked this conversation as resolved.
Comment thread
guweigang marked this conversation as resolved.
if res == 1 {
break
}

err_res := ssl_error(res, conn.ssl)!
if err_res == .ssl_error_want_read {
conn.wait_for_read(ssl_remaining_timeout(deadline))!
continue
}
if err_res == .ssl_error_want_write {
conn.wait_for_write(ssl_remaining_timeout(deadline))!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clamp expired TLS handshake deadlines

Fresh evidence after the nonblocking fix is that the handshake still passes ssl_remaining_timeout(deadline) directly into the shared OpenSSL wait helper, and that helper treats any timeout <= 0 as infinite in vlib/net/openssl/ssl_connection.c.v. With a finite veb timeout_in_seconds, a client that sends a few TLS bytes before the deadline and then stalls can make this call receive a negative remaining timeout and park the handler indefinitely instead of timing out; return net.err_timed_out or clamp before waiting once the deadline has expired.

Useful? React with 👍 / 👎.

continue
}

// 握手失败
return error('net.openssl SSLListener.accept, SSL handshake failed: ${err_res}')
}
}

// shutdown shuts down the SSL listener and releases the context.
pub fn (mut l SSLListener) shutdown() ! {
if l.sslctx != 0 {
C.SSL_CTX_free(l.sslctx)
l.sslctx = unsafe { nil }
}
if l.tcp_listener != unsafe { nil } {
l.tcp_listener.close()!
Comment thread
guweigang marked this conversation as resolved.
}
}
6 changes: 6 additions & 0 deletions vlib/net/ssl/ssl_d_use_openssl.v
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module ssl
import net.openssl

pub type SSLConn = openssl.SSLConn
pub type SSLListener = openssl.SSLListener

@[params]
pub struct SSLConnectConfig {
Expand All @@ -13,3 +14,8 @@ pub struct SSLConnectConfig {
pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
return openssl.new_ssl_conn(config.SSLConnectConfig) or { return err }
}

// new_ssl_listener returns a new SSLListener with the given config.
pub fn new_ssl_listener(saddr string, config SSLConnectConfig) !&SSLListener {
return openssl.new_ssl_listener(saddr, config.SSLConnectConfig) or { return err }
Comment thread
guweigang marked this conversation as resolved.
Outdated
}
6 changes: 6 additions & 0 deletions vlib/net/ssl/ssl_notd_use_openssl.v
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module ssl
import net.mbedtls

pub type SSLConn = mbedtls.SSLConn
pub type SSLListener = mbedtls.SSLListener

@[params]
pub struct SSLConnectConfig {
Expand All @@ -13,3 +14,8 @@ pub struct SSLConnectConfig {
pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
return mbedtls.new_ssl_conn(config.SSLConnectConfig) or { return err }
}

// new_ssl_listener returns a new SSLListener with the given config.
pub fn new_ssl_listener(saddr string, config SSLConnectConfig) !&SSLListener {
return mbedtls.new_ssl_listener(saddr, config.SSLConnectConfig) or { return err }
}
Loading
Loading