diff --git a/src/client.rs b/src/client.rs index c1809e1..a559967 100644 --- a/src/client.rs +++ b/src/client.rs @@ -155,18 +155,19 @@ impl DoshClientBuilder { .cloned() .unwrap_or_default(); let raw_server = host_config.ssh.clone().unwrap_or_else(|| self.host.clone()); - let udp_host = self - .udp_host - .clone() - .or_else(|| host_config.dosh_host.clone()) - .or_else(|| self.client.config.dosh_host.clone()) - .unwrap_or_else(|| destination_host(&raw_server)); + let ssh_port = host_config.ssh_port.or(self.client.config.ssh_port); + let ssh_config = load_sdk_ssh_config(host_config.ssh_config.as_deref(), ssh_port)?; + let udp_host = selected_sdk_udp_host( + self.udp_host.as_deref(), + &host_config, + &self.client.config, + &raw_server, + &ssh_config, + )?; let udp_port = self .udp_port .or(host_config.port) .unwrap_or(self.client.config.dosh_port); - let ssh_port = host_config.ssh_port.or(self.client.config.ssh_port); - let ssh_config = load_sdk_ssh_config(host_config.ssh_config.as_deref(), ssh_port)?; let requested_user = self .user .clone() @@ -740,6 +741,38 @@ fn destination_host(destination: &str) -> String { .to_string() } +fn selected_sdk_udp_host( + explicit: Option<&str>, + host: &HostConfig, + config: &ClientConfig, + raw_server: &str, + ssh_config: &SdkSshConfig, +) -> Result { + let configured = explicit + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToString::to_string) + .or_else(|| host.dosh_host.clone()) + .or_else(|| config.dosh_host.clone()); + let ssh_host = || { + ssh_config + .hostname + .clone() + .unwrap_or_else(|| destination_host(raw_server)) + }; + let Some(raw) = configured else { + return Ok(ssh_host()); + }; + match raw.trim().to_ascii_lowercase().as_str() { + "ssh" | "auto" => Ok(ssh_host()), + "localhost" => Ok("127.0.0.1".to_string()), + "any" => bail!( + "dosh_host={raw:?} is a server bind policy, not a client destination; use ssh/auto or a host/IP" + ), + _ => Ok(raw), + } +} + fn user_from_destination(destination: &str) -> Option { destination .rsplit_once('@') @@ -827,6 +860,65 @@ mod tests { ); } + #[test] + fn sdk_udp_host_follows_ssh_config_hostname_by_default() { + let ssh_config = SdkSshConfig { + hostname: Some("10.0.0.5".to_string()), + ..SdkSshConfig::default() + }; + + assert_eq!( + selected_sdk_udp_host( + None, + &HostConfig::default(), + &ClientConfig::default(), + "prod", + &ssh_config, + ) + .unwrap(), + "10.0.0.5" + ); + } + + #[test] + fn sdk_udp_host_honors_cli_compatible_special_values() { + let ssh_config = SdkSshConfig { + hostname: Some("10.0.0.5".to_string()), + ..SdkSshConfig::default() + }; + let host = HostConfig { + dosh_host: Some("ssh".to_string()), + ..HostConfig::default() + }; + + assert_eq!( + selected_sdk_udp_host(None, &host, &ClientConfig::default(), "prod", &ssh_config) + .unwrap(), + "10.0.0.5" + ); + assert_eq!( + selected_sdk_udp_host( + Some("localhost"), + &host, + &ClientConfig::default(), + "prod", + &ssh_config, + ) + .unwrap(), + "127.0.0.1" + ); + assert!( + selected_sdk_udp_host( + Some("any"), + &host, + &ClientConfig::default(), + "prod", + &ssh_config, + ) + .is_err() + ); + } + #[test] fn sdk_parses_ssh_config_identity_settings() { let parsed = parse_sdk_ssh_config(