jlcodes99/cockpit-tools · warning

[WS] 握手失败 {}: {}

Error message

[WS] 握手失败 {}: {}

What it means

Logged in handle_connection when tokio_tungstenite::accept_async(stream) fails, i.e. the TCP connection could not be upgraded to a WebSocket. The WebSocket handshake (HTTP 101 Upgrade with Sec-WebSocket-* headers) was rejected as invalid, so the connection is dropped and the handler returns. The server itself keeps running for other clients.

Source

Thrown at crates/cockpit-core/src/modules/websocket.rs:526

    let server = get_server();

    while let Ok((stream, addr)) = listener.accept().await {
        if !is_allowed_remote_client(&addr) {
            crate::modules::logger::log_warn(&format!("[WS] 鎷掔粷闈炵櫧鍚嶅崟鏉ユ簮: {}", addr));
            continue;
        }
        let server_clone = Arc::clone(server);
        tokio::spawn(handle_connection(server_clone, stream, addr));
    }
}

/// 处理单个客户端连接
async fn handle_connection(server: Arc<WsServer>, stream: TcpStream, addr: SocketAddr) {
    let ws_stream = match tokio_tungstenite::accept_async(stream).await {
        Ok(ws) => ws,
        Err(e) => {
            crate::modules::logger::log_error(&format!("[WS] 握手失败 {}: {}", addr, e));
            return;
        }
    };

    crate::modules::logger::log_info(&format!("[WS] 新连接: {}", addr));

    // 添加客户端
    {
        let mut clients = server.clients.write().await;
        clients.insert(addr, Client { _addr: addr });
    }

    let (mut ws_sender, mut ws_receiver) = ws_stream.split();

    // 发送 Ready 消息
    let ready_msg = WsMessage::Ready {
        version: env!("CARGO_PKG_VERSION").to_string(),
    };

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Read the addr and tungstenite error in the log to see who connected and why the handshake was rejected.
  2. Ensure the client uses a real WebSocket client (ws://, not plain HTTP fetch or https/wss) against this plaintext listener.
  3. Check the client's handshake headers/Sec-WebSocket-Version (must be 13) if using a custom client.
  4. Ignore routine scanner/probe noise if the log shows non-WebSocket bytes from unknown local processes.

Example fix

// before: browser fetch against the WS port causes handshake failure
fetch("http://127.0.0.1:9100/")
// after: use a WebSocket client
const ws = new WebSocket("ws://127.0.0.1:9100");
Defensive patterns

Strategy: validation

Validate before calling

// client side: only speak WebSocket to the WS port
const url = new URL(endpoint);
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
  throw new Error(`expected ws:// endpoint, got ${url.protocol}`);
}
const ws = new WebSocket(url); // real handshake, not plain HTTP

Try / catch

match tokio_tungstenite::accept_async(stream).await {
    Ok(ws) => { /* serve connection */ }
    Err(e) => {
        log_error(&format!("[WS] 握手失败 {}: {}", addr, e));
        // drop the socket; do not crash the accept loop
    }
}

Prevention

When it happens

Trigger: A client connects to ws://127.0.0.1:<port> but sends data that is not a valid WebSocket opening handshake: a plain HTTP GET without Upgrade headers, garbage bytes (e.g. a port probe), an invalid Sec-WebSocket-Key/version, or a TLS ClientHello sent to the plaintext port.

Common situations: A health-check or browser fetch() hitting the WebSocket port with a normal HTTP request; security/port scanners probing 127.0.0.1; a client configured for wss:// connecting to a non-TLS listener; a malformed or third-party WS client with bad handshake headers.

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/c661b1315286ff8c. Report an issue: GitHub.