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
- Read the addr and tungstenite error in the log to see who connected and why the handshake was rejected.
- Ensure the client uses a real WebSocket client (ws://, not plain HTTP fetch or https/wss) against this plaintext listener.
- Check the client's handshake headers/Sec-WebSocket-Version (must be 13) if using a custom client.
- 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
- Point clients at ws:// (not http://, https://, or wss://) for this plaintext listener.
- Never reuse the WS port for health checks or plain HTTP endpoints.
- Use standard WS client libraries that send correct Sec-WebSocket-Key/Version headers.
- Expect port-scan noise on local ports; alert only on repeated handshake failures from your own client.
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.