Skip to content

Commit a7f2fb6

Browse files
committed
Relay: bound the host connect; reset backoff only after a stable link
tungstenite::connect had no timeout for TCP, TLS or the HTTP upgrade, so a proxy that accepted the connection and went silent blocked the relay thread for good (relay_set(false) included). Connect with a 10s timeout on each step. The backoff went back to 1s as soon as the relay said ready, so a relay that dropped the host right after authenticating was retried every second; it now resets only after the link lasted 30s.
1 parent ed29dc5 commit a7f2fb6

1 file changed

Lines changed: 65 additions & 5 deletions

File tree

‎crates/daemon/src/relay.rs‎

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use std::{
2424
Arc, Mutex,
2525
},
2626
thread,
27-
time::Duration,
27+
time::{Duration, Instant},
2828
};
2929
use tungstenite::{stream::MaybeTlsStream, Message, WebSocket};
3030

@@ -265,9 +265,7 @@ fn host_connection(
265265
backoff: &mut Duration,
266266
) -> Result<(), String> {
267267
let base = hub.relay.url().unwrap_or_default();
268-
let (mut socket, _) =
269-
tungstenite::connect(format!("{base}/host")).map_err(|error| error.to_string())?;
270-
set_read_timeout(&socket, HANDSHAKE_TIMEOUT)?;
268+
let mut socket = connect_host(&format!("{base}/host"))?;
271269
let challenge = read_json(&mut socket)?;
272270
if challenge["t"] != "challenge" {
273271
return Err(format!("expected a challenge, got {challenge}"));
@@ -294,7 +292,13 @@ fn host_connection(
294292
}
295293
set_read_timeout(&socket, POLL)?;
296294
hub.relay.connected.store(true, Ordering::SeqCst);
297-
*backoff = Duration::from_secs(1);
295+
// Back to the short backoff only once this connection proved stable: a
296+
// relay that accepts the host and drops it right away (another daemon
297+
// with this identity, say) would otherwise be retried every second.
298+
let _reset = ResetWhenStable {
299+
backoff,
300+
since: Instant::now(),
301+
};
298302
jucode_agent_core::log_info!("daemon", "relay connected", host = identity.host_id());
299303

300304
// Stream threads send finished frames here; this thread writes them.
@@ -357,6 +361,62 @@ fn host_connection(
357361
}
358362
}
359363

364+
/// How long a host connection must last before the backoff starts over.
365+
const STABLE: Duration = Duration::from_secs(30);
366+
367+
struct ResetWhenStable<'a> {
368+
backoff: &'a mut Duration,
369+
since: Instant,
370+
}
371+
372+
impl Drop for ResetWhenStable<'_> {
373+
fn drop(&mut self) {
374+
if self.since.elapsed() >= STABLE {
375+
*self.backoff = Duration::from_secs(1);
376+
}
377+
}
378+
}
379+
380+
/// Opens the host WebSocket with every step bounded: TCP connect, TLS and the
381+
/// HTTP upgrade (a proxy that accepts the connection and then says nothing
382+
/// would otherwise block this thread for good, `relay_set(false)` included).
383+
fn connect_host(url: &str) -> Result<HostSocket, String> {
384+
use std::net::ToSocketAddrs;
385+
use tungstenite::client::IntoClientRequest;
386+
let request = url
387+
.into_client_request()
388+
.map_err(|error| error.to_string())?;
389+
let uri = request.uri();
390+
let host = uri.host().ok_or("relay url without a host")?.to_string();
391+
let port = uri
392+
.port_u16()
393+
.unwrap_or(if uri.scheme_str() == Some("wss") {
394+
443
395+
} else {
396+
80
397+
});
398+
let mut last = format!("could not resolve {host}");
399+
let mut tcp = None;
400+
for addr in (host.as_str(), port)
401+
.to_socket_addrs()
402+
.map_err(|error| error.to_string())?
403+
{
404+
match TcpStream::connect_timeout(&addr, HANDSHAKE_TIMEOUT) {
405+
Ok(stream) => {
406+
tcp = Some(stream);
407+
break;
408+
}
409+
Err(error) => last = error.to_string(),
410+
}
411+
}
412+
let tcp = tcp.ok_or(last)?;
413+
tcp.set_read_timeout(Some(HANDSHAKE_TIMEOUT))
414+
.and_then(|_| tcp.set_write_timeout(Some(HANDSHAKE_TIMEOUT)))
415+
.map_err(|error| error.to_string())?;
416+
let (socket, _) = tungstenite::client_tls(request, tcp).map_err(|error| error.to_string())?;
417+
Ok(socket)
418+
}
419+
360420
fn set_read_timeout(socket: &HostSocket, timeout: Duration) -> Result<(), String> {
361421
let tcp = match socket.get_ref() {
362422
MaybeTlsStream::Plain(tcp) => tcp,

0 commit comments

Comments
 (0)