Skip to content

mbedtls / websocket bug #26662

@Macho0x

Description

@Macho0x

Describe the bug

The V language's net.mbedtls module currently uses compile-time constants for SSL read timeouts, which prevents runtime configuration. This forces users to recompile their applications with -d mbedtls_client_read_timeout_ms=<value> whenever they need different timeout values for different environments or use cases.

Current Implementation

In vlib/net/mbedtls/ssl_connection.c.v:

const mbedtls_client_read_timeout_ms = $d('mbedtls_client_read_timeout_ms', 10_000)
const mbedtls_server_read_timeout_ms = $d('mbedtls_server_read_timeout_ms', 41_000)

Reproduction Steps

Example 1: WebSocket Connection Timeout

import net.websocket
import time

fn main() {
    // Attempt to connect to a WebSocket server with infrequent data
    mut ws := websocket.new_client('wss://stream.exchange.com/ws')!
    
    // Subscribe to a low-frequency feed
    ws.connect()!
    
    // If no data arrives within 10 seconds, mbedtls will timeout
    // even though the connection is healthy and the exchange will
    // eventually send data
    for {
        ws.listen() or {
            // Error: "did not receive any data within 10000ms"
            // No way to increase this at runtime!
            break
        }
    }
}

Example 2: Different Timeouts for Different Connections

import net.mbedtls
import time

fn main() {
    // I want Connection A to have 10s timeout (default)
    config_a := mbedtls.SSLConnectConfig{}
    mut conn_a := mbedtls.new_ssl_conn(config_a)!
    
    // I want Connection B to have 5min timeout (for WebSocket)
    // Currently impossible without recompiling with -d flag!
    config_b := mbedtls.SSLConnectConfig{
        // No read_timeout field available
    }
    mut conn_b := mbedtls.new_ssl_conn(config_b)!
}

Workaround Required

Users must compile with:

v -d mbedtls_client_read_timeout_ms=300000 run main.v

This sets a global 5-minute timeout for all connections, which is suboptimal when different connections need different timeouts.


Expected Behavior

Consistent API with Other Net Modules

The net.mbedtls module should follow the same pattern as net.tcp.TcpConn and net.raw.RawConn:

// TcpConn pattern (already exists)
pub struct TcpConn {
pub mut:
    read_timeout   time.Duration
    write_timeout  time.Duration
}

pub fn (mut c TcpConn) set_read_timeout(t time.Duration) {
    c.read_timeout = t
}

// RawConn pattern (already exists)
pub struct RawConn {
pub mut:
    read_timeout   time.Duration
    write_timeout  time.Duration
}

pub fn (mut c RawConn) set_read_timeout(t time.Duration) {
    c.read_timeout = t
}

// Expected mbedtls pattern (proposed)
pub struct SSLConn {
pub mut:
    read_timeout   time.Duration
}

pub fn (mut s SSLConn) set_read_timeout(t time.Duration) {
    s.read_timeout = t
    C.mbedtls_ssl_conf_read_timeout(&s.conf, u32(t.milliseconds()))
}

Per-Connection Timeout Configuration

Users should be able to configure timeouts per connection:

import net.mbedtls
import time

fn main() {
    // Connection A: Default 10s timeout
    mut conn_a := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{})!
    
    // Connection B: 5-minute timeout for WebSocket
    mut conn_b := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
        read_timeout: 300 * time.second
    })!
    
    // Connection C: Modify timeout at runtime
    mut conn_c := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{})!
    conn_c.set_read_timeout(60 * time.second)  // Change to 1 minute
}

Backward Compatibility

  • Existing code using -d mbedtls_client_read_timeout_ms=... should continue to work
  • The compile-time value becomes the default for SSLConnectConfig.read_timeout
  • No breaking changes to existing APIs

Current Behavior

Compile-Time Configuration Only

// File: vlib/net/mbedtls/ssl_connection.c.v:11-12
const mbedtls_client_read_timeout_ms = $d('mbedtls_client_read_timeout_ms', 10_000)
const mbedtls_server_read_timeout_ms = $d('mbedtls_server_read_timeout_ms', 41_000)

Usage in Connection Initialization

// File: vlib/net/mbedtls/ssl_connection.c.v:391-394
$if trace_mbedtls_timeouts ? {
    dump(mbedtls_client_read_timeout_ms)
}
C.mbedtls_ssl_conf_read_timeout(&s.conf, mbedtls_client_read_timeout_ms)

Error Message

// File: vlib/net/mbedtls/ssl_connection.c.v:587-592
C.MBEDTLS_ERR_SSL_TIMEOUT {
    return error_with_code(
        'net.mbedtls SSLConn.socket_read_into_ptr, did not receive any data within ${mbedtls_client_read_timeout_ms}ms. compile with `-d mbedtls_client_read_timeout_ms=100000` to set a higher timeout',
        res
    )
}

Limitations

  1. Single global value: All connections in the application share the same timeout
  2. Recompilation required: Changing timeout requires recompiling entire application
  3. No runtime API: No methods like set_read_timeout() or get_read_timeout()
  4. Inconsistent: Does not follow the pattern established by TcpConn and RawConn

Possible Solution

Implementation Plan

Step 1: Add read_timeout to SSLConnectConfig

File: vlib/net/mbedtls/ssl_connection.c.v
Lines: 318-329

@[params]
pub struct SSLConnectConfig {
pub:
    verify   string
    cert     string
    cert_key string
    validate bool
    in_memory_verification bool
    get_certificate ?fn (mut SSLListener, string) !&SSLCerts
    // NEW: Runtime-configurable timeout with compile-time default
    read_timeout time.Duration = $d('mbedtls_client_read_timeout_ms', 10_000) * time.millisecond
}

Step 2: Add read_timeout to SSLConn

File: vlib/net/mbedtls/ssl_connection.c.v
Lines: 139-155

pub struct SSLConn {
pub:
    config SSLConnectConfig
pub mut:
    server_fd C.mbedtls_net_context
    ssl       C.mbedtls_ssl_context
    conf      C.mbedtls_ssl_config
    certs     &SSLCerts = unsafe { nil }
    handle    int
    duration  time.Duration
    opened    bool
    ip        string
    owns_socket bool
    // NEW: Store current timeout for getter methods
    read_timeout time.Duration
}

Step 3: Initialize timeout from config

File: vlib/net/mbedtls/ssl_connection.c.v
Function: fn (mut s SSLConn) init() !
Lines: 391-394

$if trace_mbedtls_timeouts ? {
    dump(s.config.read_timeout)
}
// Use config timeout instead of compile-time constant:
C.mbedtls_ssl_conf_read_timeout(&s.conf, u32(s.config.read_timeout.milliseconds()))

s.read_timeout = s.config.read_timeout  // Store for later use

Step 4: Add getter/setter methods

File: vlib/net/mbedtls/ssl_connection.c.v
**After line 329 (after SSLConnectConfig definition)

// read_timeout returns the current SSL read timeout for this connection
pub fn (s &SSLConn) read_timeout() time.Duration {
    return s.read_timeout
}

// set_read_timeout sets the SSL read timeout for this connection.
// Note: This modifies the underlying mbedtls configuration immediately.
// The change takes effect for subsequent read operations.
pub fn (mut s SSLConn) set_read_timeout(t time.Duration) {
    s.read_timeout = t
    C.mbedtls_ssl_conf_read_timeout(&s.conf, u32(t.milliseconds()))
}

Step 5: Update error message

File: vlib/net/mbedtls/ssl_connection.c.v
Lines: 587-592

C.MBEDTLS_ERR_SSL_TIMEOUT {
    $if trace_ssl ? {
        eprintln('${@METHOD} ---> res: C.MBEDTLS_ERR_SSL_TIMEOUT')
    }
    return error_with_code(
        'net.mbedtls SSLConn.socket_read_into_ptr, did not receive any data within ${s.read_timeout.milliseconds()}ms. Use `conn.set_read_timeout(duration)` to increase timeout',
        res
    )
}

Alternative: Apply Same Pattern to SSLListener

For consistency, apply the same changes to SSLListener:

// In SSLListener struct (line ~220)
pub mut:
    read_timeout time.Duration

// In SSLConnectConfig
    server_read_timeout time.Duration = $d('mbedtls_server_read_timeout_ms', 41_000) * time.millisecond

// In SSLListener.init()
C.mbedtls_ssl_conf_read_timeout(&l.conf, u32(l.config.server_read_timeout.milliseconds()))
l.read_timeout = l.config.server_read_timeout

// Add getter/setter
pub fn (l &SSLListener) read_timeout() time.Duration { return l.read_timeout }
pub fn (mut l SSLListener) set_read_timeout(t time.Duration) { 
    l.read_timeout = t
    C.mbedtls_ssl_conf_read_timeout(&l.conf, u32(t.milliseconds()))
}

Additional Information/Context

Benefits of This Fix

1. No Longer Need Compile-Time Flag

Before:

v -d mbedtls_client_read_timeout_ms=300000 run main.v

After:

// In application code - no compile flag needed!
import net.mbedtls
import time

mut conn := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
    read_timeout: 300 * time.second
})!

2. Per-Connection Timeouts

Different connections can have different timeouts:

// API calls: Short timeout
mut api_conn := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
    read_timeout: 10 * time.second
})!

// WebSocket: Long timeout
mut ws_conn := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
    read_timeout: 300 * time.second
})!

3. Runtime Adjustability

Adjust timeouts based on runtime conditions:

mut conn := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{})!

// Later, based on user settings or network conditions:
if user_prefers_longer_timeout {
    conn.set_read_timeout(5 * time.minute)
}

4. Configuration File Support

Timeouts can now come from configuration files:

import toml

config := toml.parse_file('app.toml')!
ssl_timeout := config.value('websocket.ssl_timeout').i64()

mut conn := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
    read_timeout: ssl_timeout * time.second
})!

Comparison with Other Languages

Language SSL Timeout Configuration Runtime API
Go tls.Config.HandshakeTimeout ✅ Yes
Python ssl.SSLContext.timeout ✅ Yes
Rust native_tls::TlsConnectorBuilder.timeout() ✅ Yes
Node.js tls.connect({timeout: ...}) ✅ Yes
V (current) -d mbedtls_client_read_timeout_ms=... ❌ No
V (proposed) SSLConnectConfig{read_timeout: ...} ✅ Yes

This fix makes V's mbedtls module consistent with other net modules (TcpConn, RawConn) and industry standards. It enables:

  1. Runtime configuration without recompilation
  2. Per-connection timeouts instead of global value
  3. Configuration file support for timeouts
  4. Backward compatibility with existing code
  5. Consistent API across V's net modules

The implementation is minimal (~20 lines of changes) but provides significant flexibility improvements for applications requiring different SSL timeouts in different contexts.

V version

0.5

Environment details (OS name and version, etc.)

CachyOS

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugThis tag is applied to issues which reports bugs.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions