Resolve SSH aliases for Dosh UDP host
ci / test (push) Has been cancelled
ci / remote-bench (push) Has been cancelled

This commit is contained in:
DuProcess
2026-06-11 22:09:55 -04:00
parent 1860a0e610
commit 6580e75fbf
+82 -9
View File
@@ -98,17 +98,21 @@ async fn main() -> Result<()> {
let (cols, rows) = size().unwrap_or((80, 24));
let started = Instant::now();
let target_udp_host = args
.dosh_host
.clone()
.or_else(|| config.dosh_host.clone())
.unwrap_or_else(|| {
if args.local_auth {
let target_udp_host =
if let Some(host) = args.dosh_host.clone().or_else(|| config.dosh_host.clone()) {
host
} else if args.local_auth {
"127.0.0.1".to_string()
} else {
ssh_config_hostname(&server, ssh_port).unwrap_or_else(|err| {
log_debug(
args.verbose,
2,
&format!("dosh could not resolve SSH config hostname, using {server}: {err:#}"),
);
ssh_destination_host(&server)
}
});
})
};
let credential = if !args.no_cache {
load_cache(&cache_path).ok()
@@ -378,6 +382,40 @@ fn ssh_destination_host(server: &str) -> String {
.to_string()
}
fn ssh_config_hostname(server: &str, ssh_port: Option<u16>) -> Result<String> {
let mut command = Command::new("ssh");
command.arg("-G");
if let Some(ssh_port) = ssh_port {
command.arg("-p").arg(ssh_port.to_string());
}
let output = command
.arg(server)
.output()
.with_context(|| format!("run ssh -G {server}"))?;
if !output.status.success() {
return Err(anyhow!(
"ssh -G failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
let raw = String::from_utf8(output.stdout)?;
parse_ssh_config_hostname(&raw).ok_or_else(|| anyhow!("ssh -G did not print hostname"))
}
fn parse_ssh_config_hostname(raw: &str) -> Option<String> {
raw.lines().find_map(|line| {
let line = line.trim();
let (key, value) = line.split_once(char::is_whitespace)?;
if key.eq_ignore_ascii_case("hostname") {
let value = value.trim();
if !value.is_empty() {
return Some(value.to_string());
}
}
None
})
}
fn resolve_addr(host: &str, port: u16) -> Result<SocketAddr> {
(host, port)
.to_socket_addrs()
@@ -407,6 +445,10 @@ async fn bootstrap_attach(
let (n, _) = tokio::time::timeout(Duration::from_secs(5), socket.recv_from(&mut buf)).await??;
let packet = protocol::decode(&buf[..n])?;
if packet.header.kind != PacketKind::AttachOk {
if packet.header.kind == PacketKind::AttachReject {
let reject: AttachReject = protocol::from_body(&packet.body)?;
return Err(anyhow!("attach rejected: {}", reject.reason));
}
return Err(anyhow!("attach rejected or unexpected response"));
}
let plain = protocol::decrypt_body(&packet, &bootstrap.session_key, SERVER_TO_CLIENT)?;
@@ -471,6 +513,10 @@ async fn try_ticket_attach(
tokio::time::timeout(Duration::from_millis(700), socket.recv_from(&mut buf)).await??;
let packet = protocol::decode(&buf[..n])?;
if packet.header.kind != PacketKind::AttachOk {
if packet.header.kind == PacketKind::AttachReject {
let reject: AttachReject = protocol::from_body(&packet.body)?;
return Err(anyhow!("ticket attach rejected: {}", reject.reason));
}
return Err(anyhow!("ticket attach rejected"));
}
let envelope: TicketAttachOkEnvelope = protocol::from_body(&packet.body)?;
@@ -533,6 +579,10 @@ async fn try_resume(
tokio::time::timeout(Duration::from_millis(700), socket.recv_from(&mut buf)).await??;
let packet = protocol::decode(&buf[..n])?;
if packet.header.kind != PacketKind::ResumeOk {
if packet.header.kind == PacketKind::AttachReject {
let reject: AttachReject = protocol::from_body(&packet.body)?;
return Err(anyhow!("resume rejected: {}", reject.reason));
}
return Err(anyhow!("resume rejected"));
}
let plain = protocol::decrypt_body(&packet, &cached.session_key, SERVER_TO_CLIENT)?;
@@ -866,7 +916,7 @@ const TERMINAL_CLEANUP: &[u8] = concat!(
#[cfg(test)]
mod tests {
use super::InputNormalizer;
use super::{InputNormalizer, parse_ssh_config_hostname, ssh_destination_host};
#[test]
fn normalizes_ss3_cursor_keys_to_csi() {
@@ -890,4 +940,27 @@ mod tests {
let mut normalizer = InputNormalizer::default();
assert_eq!(normalizer.normalize(b"\x1bOP"), b"\x1bOP");
}
#[test]
fn parses_ssh_config_hostname() {
let raw = "user palav\nhostname palav.dev\nport 22\n";
assert_eq!(
parse_ssh_config_hostname(raw),
Some("palav.dev".to_string())
);
}
#[test]
fn parses_ssh_config_hostname_case_insensitively() {
let raw = "HostName 192.0.2.10\n";
assert_eq!(
parse_ssh_config_hostname(raw),
Some("192.0.2.10".to_string())
);
}
#[test]
fn strips_user_from_fallback_udp_host() {
assert_eq!(ssh_destination_host("palav@example.com"), "example.com");
}
}