Fix TCP multi-conn auth: secondary connections skip auth
All checks were successful
CI / test (push) Successful in 1m9s
Build & Release / release (push) Successful in 2m41s

Secondary connections send [TOKEN_HI, TOKEN_LO, 0x02, 0x00, ...]
as their command — they don't do auth. Server verifies the session
token matches a pending session from the same IP, sends OK with
token, and lets them join. No auth challenge/response needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-31 15:47:40 +04:00
parent 9853d74c4a
commit 8fe4e72bb3

View File

@@ -70,43 +70,37 @@ async fn handle_client(
stream.read_exact(&mut cmd_buf).await?; stream.read_exact(&mut cmd_buf).await?;
tracing::debug!("Raw command from {}: {:02x?}", peer, cmd_buf); tracing::debug!("Raw command from {}: {:02x?}", peer, cmd_buf);
// Check if this looks like a secondary TCP connection joining a session. // Check if this is a secondary TCP connection joining a session.
// Secondary connections send the session token in bytes 1-2 of what looks // Secondary connections send the session token in bytes 0-1 of their "command":
// like a command, but with different/invalid proto+direction values. // [TOKEN_HI, TOKEN_LO, 0x02, 0x00, ...]
// They do NOT do auth — just send them AUTH_OK with the token and they join.
{ {
let map = sessions.lock().await; let mut map = sessions.lock().await;
// Try to match session token from bytes - secondary connections may let received_token = ((cmd_buf[0] as u16) << 8) | (cmd_buf[1] as u16);
// send the token we gave them in the auth OK response if let Some(session) = map.get_mut(&received_token) {
for (&token, session) in map.iter() {
if session.peer_ip == peer.ip() if session.peer_ip == peer.ip()
&& session.streams.len() < session.expected as usize && session.streams.len() < session.expected as usize
{ {
tracing::info!( tracing::info!(
"Client {} is secondary TCP connection, raw={:02x?}", "Client {} is secondary TCP connection (token={:04x})",
peer, cmd_buf, peer, received_token,
); );
drop(map);
// Secondary connection: authenticate and join session // No auth for secondary connections — just send OK with token
auth::server_authenticate( let ok = [0x01, cmd_buf[0], cmd_buf[1], 0x00];
&mut stream, stream.write_all(&ok).await?;
auth_user.as_deref(), stream.flush().await?;
auth_pass.as_deref(),
&[0x01, (token >> 8) as u8, (token & 0xFF) as u8, 0x00],
)
.await?;
let mut map = sessions.lock().await; session.streams.push(stream);
for (_t, s) in map.iter_mut() { tracing::info!(
if s.peer_ip == peer.ip() && s.streams.len() < s.expected as usize { "Secondary connection joined ({}/{})",
s.streams.push(stream); session.streams.len() + 1,
tracing::info!("Secondary connection joined ({}/{})", s.streams.len() + 1, s.expected); session.expected,
return Ok(()); );
}
}
return Ok(()); return Ok(());
} }
} }
drop(map);
} }
// Primary connection: parse the command normally // Primary connection: parse the command normally