mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-08 17:19:10 -10:00
Play with friends: the host attaches from any port when its direct port times out
The host attaches every relayed session from its direct port, so each attach to a relay reuses one address pair. The relay closes a finished session first and keeps that pair in TIME_WAIT; a relay on Windows then ignores the host's next attach from the same port (Linux takes it), the connect times out instead of reporting the address in use, and connect_from's fallback never runs. The second guest within a couple of minutes got HostUnreachable. connect_relay now gives the relay's addresses that timed out from the direct port one more try from any port, after every address had its try from the direct port (a dead IPv6 route doesn't eat the relay's attach window twice). An attach from another port no longer offers what the relay saw of it as a direct candidate: nothing listens there. The direct race's dials keep their port. Fixes #6342 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
62bdd29b23
commit
f3de190aaf
1 file changed
+27
-4
@@ -383,9 +383,29 @@ fn carry(shared: &Arc<Shared>, session: u64, (r, w): session::Secure, direct: bo
|
||||
// ----- The relay (optional) ---------------------------------------------------------------------
|
||||
|
||||
fn connect_relay(relay: &str, port: Option<u16>) -> io::Result<(TcpStream, SocketAddr)> {
|
||||
let wait = Duration::from_secs(5);
|
||||
let mut last = io::Error::other("no address");
|
||||
let mut timed_out = Vec::new();
|
||||
for address in resolve_relay(relay)? {
|
||||
match direct::connect_from(port, address, Duration::from_secs(5)) {
|
||||
match direct::connect_from(port, address, wait) {
|
||||
Ok(s) => {
|
||||
let _ = s.set_nodelay(true);
|
||||
return Ok((s, address));
|
||||
}
|
||||
Err(e) => {
|
||||
if port.is_some() && e.kind() == io::ErrorKind::TimedOut {
|
||||
timed_out.push(address);
|
||||
}
|
||||
last = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
// A relay on Windows ignores a new connection from the address of one it
|
||||
// closed less than its TIME_WAIT ago (the last guest's attach from the
|
||||
// direct port), where Linux takes it: those get one more try from any
|
||||
// port, once every address had its try from the direct port.
|
||||
for address in timed_out {
|
||||
match direct::connect_from(None, address, wait) {
|
||||
Ok(s) => {
|
||||
let _ = s.set_nodelay(true);
|
||||
return Ok((s, address));
|
||||
@@ -513,6 +533,9 @@ fn serve_relayed(
|
||||
let Ok((mut stream, _)) = connect_relay(relay_url, port) else {
|
||||
return;
|
||||
};
|
||||
// What the relay sees of an attach from another port is no address of
|
||||
// this host's: nothing listens there.
|
||||
let from_port = stream.local_addr().ok().map(|a| a.port()) == port;
|
||||
let _ = stream.set_read_timeout(Some(Duration::from_secs(15)));
|
||||
let attach = ToRelay::Attach {
|
||||
v: wire::VERSION,
|
||||
@@ -523,9 +546,9 @@ fn serve_relayed(
|
||||
return;
|
||||
}
|
||||
let observed = match wire::recv::<_, FromRelay>(&mut stream) {
|
||||
Ok(FromRelay::Bridged { observed, .. }) => {
|
||||
observed.and_then(|o| o.parse::<SocketAddr>().ok())
|
||||
}
|
||||
Ok(FromRelay::Bridged { observed, .. }) => observed
|
||||
.filter(|_| from_port)
|
||||
.and_then(|o| o.parse::<SocketAddr>().ok()),
|
||||
_ => return,
|
||||
};
|
||||
let Ok(key) = crypto::exchange(&mut stream, &code.secret, &code.nameplate, session) else {
|
||||
|
||||
Reference in new issue
Block a user