diff --git a/easytier/src/connector/dns_connector.rs b/easytier/src/connector/dns_connector.rs index 2220df24..813d5f36 100644 --- a/easytier/src/connector/dns_connector.rs +++ b/easytier/src/connector/dns_connector.rs @@ -194,11 +194,11 @@ impl super::TunnelConnector for DnsTunnelConnector { TunnelInfo { local_addr: info.local_addr.clone(), remote_addr: Some(self.addr.clone().into()), - tunnel_type: format!( - "{}-{}", - self.addr.scheme(), - info.remote_addr.unwrap_or_default() - ), + resolved_remote_addr: info + .resolved_remote_addr + .clone() + .or(info.remote_addr.clone()), + tunnel_type: format!("{}-{}", self.addr.scheme(), info.tunnel_type), }, ))) } diff --git a/easytier/src/connector/http_connector.rs b/easytier/src/connector/http_connector.rs index bcd35192..dc133ae1 100644 --- a/easytier/src/connector/http_connector.rs +++ b/easytier/src/connector/http_connector.rs @@ -229,11 +229,11 @@ impl super::TunnelConnector for HttpTunnelConnector { TunnelInfo { local_addr: info.local_addr.clone(), remote_addr: Some(self.addr.clone().into()), - tunnel_type: format!( - "{:?}-{}", - self.redirect_type, - info.remote_addr.unwrap_or_default() - ), + resolved_remote_addr: info + .resolved_remote_addr + .clone() + .or(info.remote_addr.clone()), + tunnel_type: format!("{}-{}", self.addr.scheme(), info.tunnel_type), }, ))) } @@ -353,6 +353,8 @@ mod tests { let info = t.info().unwrap(); let remote_addr = info.remote_addr.unwrap(); assert_eq!(remote_addr, test_url.into()); + let resolved_remote_addr = info.resolved_remote_addr.unwrap(); + assert_eq!(resolved_remote_addr.url, "tcp://127.0.0.1:25888"); tokio::join!(task).0.unwrap(); } diff --git a/easytier/src/easytier-cli.rs b/easytier/src/easytier-cli.rs index 3f96e5af..f4555f1f 100644 --- a/easytier/src/easytier-cli.rs +++ b/easytier/src/easytier-cli.rs @@ -1404,7 +1404,7 @@ impl<'a> CommandHandler<'a> { "remote_addr: {}, rx_bytes: {}, tx_bytes: {}, latency_us: {}", conn.tunnel .as_ref() - .map(|t| t.remote_addr.clone().unwrap_or_default()) + .and_then(|t| t.display_remote_addr()) .unwrap_or_default(), conn.stats.as_ref().map(|s| s.rx_bytes).unwrap_or_default(), conn.stats.as_ref().map(|s| s.tx_bytes).unwrap_or_default(), diff --git a/easytier/src/instance/dns_server/tests.rs b/easytier/src/instance/dns_server/tests.rs index 2bf237c2..2e9bb2e7 100644 --- a/easytier/src/instance/dns_server/tests.rs +++ b/easytier/src/instance/dns_server/tests.rs @@ -232,6 +232,7 @@ async fn test_magic_dns_update_replaces_records_for_same_client() { remote_addr: Some(crate::proto::common::Url { url: "tcp://127.0.0.1:54321".to_string(), }), + resolved_remote_addr: None, })); dns_server_inst @@ -299,6 +300,7 @@ async fn test_magic_dns_update_replaces_records_for_same_client() { remote_addr: Some(crate::proto::common::Url { url: "tcp://127.0.0.1:54321".to_string(), }), + resolved_remote_addr: None, })); dns_server_inst diff --git a/easytier/src/proto/api.rs b/easytier/src/proto/api.rs index de31b767..73bedce0 100644 --- a/easytier/src/proto/api.rs +++ b/easytier/src/proto/api.rs @@ -149,23 +149,8 @@ pub mod instance { ret } - fn is_tunnel_ipv6(tunnel_info: &super::super::common::TunnelInfo) -> bool { - let Some(local_addr) = &tunnel_info.local_addr else { - return false; - }; - - let u: url::Url = local_addr.clone().into(); - u.host() - .map(|h| matches!(h, url::Host::Ipv6(_))) - .unwrap_or(false) - } - fn get_tunnel_proto_str(tunnel_info: &super::super::common::TunnelInfo) -> String { - if Self::is_tunnel_ipv6(tunnel_info) { - format!("{}6", tunnel_info.tunnel_type) - } else { - tunnel_info.tunnel_type.clone() - } + tunnel_info.display_tunnel_type() } pub fn get_conn_protos(&self) -> Option> { diff --git a/easytier/src/proto/common.proto b/easytier/src/proto/common.proto index a49396b9..91e33d35 100644 --- a/easytier/src/proto/common.proto +++ b/easytier/src/proto/common.proto @@ -201,6 +201,7 @@ message TunnelInfo { string tunnel_type = 1; common.Url local_addr = 2; common.Url remote_addr = 3; + common.Url resolved_remote_addr = 4; } message StunInfo { diff --git a/easytier/src/proto/common.rs b/easytier/src/proto/common.rs index 7c05292e..33a3e016 100644 --- a/easytier/src/proto/common.rs +++ b/easytier/src/proto/common.rs @@ -5,8 +5,9 @@ use std::{ use anyhow::Context; use base64::{prelude::BASE64_STANDARD, Engine as _}; +use strum::VariantArray; -use crate::tunnel::packet_def::CompressorAlgo; +use crate::tunnel::{packet_def::CompressorAlgo, IpScheme}; include!(concat!(env!("OUT_DIR"), "/common.rs")); @@ -284,6 +285,105 @@ impl fmt::Display for Url { } } +fn split_tunnel_scheme(raw_scheme: &str) -> Option<(&str, &'static str, bool)> { + for scheme in IpScheme::VARIANTS { + let scheme: &'static str = scheme.into(); + if let Some(base) = raw_scheme.strip_suffix('6') { + if let Some(prefix) = base.strip_suffix(scheme) { + if prefix.is_empty() || prefix.ends_with('-') { + return Some((prefix, scheme, true)); + } + } + } + + if let Some(prefix) = raw_scheme.strip_suffix(scheme) { + if prefix.is_empty() || prefix.ends_with('-') { + return Some((prefix, scheme, false)); + } + } + } + + None +} + +fn normalize_tunnel_scheme(raw_scheme: &str, is_ipv6: bool) -> Option { + let (prefix, scheme, had_ipv6_suffix) = split_tunnel_scheme(raw_scheme)?; + let suffix = if is_ipv6 || had_ipv6_suffix { "6" } else { "" }; + Some(format!("{prefix}{scheme}{suffix}")) +} + +fn infer_tunnel_ipv6(raw: &str) -> Option { + let (_, rest) = raw.split_once("://")?; + if rest.starts_with('[') { + return Some(true); + } + + match url::Url::parse(raw).ok()?.host() { + Some(url::Host::Ipv4(_)) => Some(false), + Some(url::Host::Ipv6(_)) => Some(true), + Some(url::Host::Domain(_)) | None => None, + } +} + +fn normalize_tunnel_port(raw_port: &str, is_ipv6: bool) -> Option { + if let Ok(port) = raw_port.parse::() { + return Some(port); + } + + if is_ipv6 && raw_port.ends_with('6') { + return raw_port[..raw_port.len() - 1].parse::().ok(); + } + + None +} + +fn normalize_tunnel_url(raw: &str, fallback_ipv6: Option) -> Option { + let (raw_scheme, rest) = raw.split_once("://")?; + + if let Some(rest) = rest.strip_prefix('[') { + let (host, remainder) = rest.split_once(']')?; + let scheme = normalize_tunnel_scheme(raw_scheme, true)?; + + if remainder.is_empty() { + return Some(format!("{scheme}://[{host}]")); + } + + let raw_port = remainder.strip_prefix(':')?; + let port = normalize_tunnel_port(raw_port, true)?; + return Some(format!("{scheme}://[{host}]:{port}")); + } + + let is_ipv6 = infer_tunnel_ipv6(raw).or(fallback_ipv6).unwrap_or(false); + let scheme = normalize_tunnel_scheme(raw_scheme, is_ipv6)?; + + if let Ok(url) = url::Url::parse(raw) { + let host = match url.host()? { + url::Host::Ipv4(host) => host.to_string(), + url::Host::Ipv6(host) => format!("[{host}]"), + url::Host::Domain(host) => host.to_string(), + }; + + return Some(match url.port_or_known_default() { + Some(port) => format!("{scheme}://{host}:{port}"), + None => format!("{scheme}://{host}"), + }); + } + + let (host, raw_port) = rest.rsplit_once(':')?; + let port = normalize_tunnel_port(raw_port, is_ipv6)?; + Some(format!("{scheme}://{host}:{port}")) +} + +impl Url { + pub fn is_ipv6_tunnel_endpoint(&self) -> bool { + infer_tunnel_ipv6(&self.url).unwrap_or(false) + } + + pub fn normalized_tunnel_display(&self) -> String { + normalize_tunnel_url(&self.url, None).unwrap_or_else(|| self.url.clone()) + } +} + impl From for SocketAddr { fn from(value: std::net::SocketAddr) -> Self { match value { @@ -325,6 +425,38 @@ impl Display for SocketAddr { } } +impl TunnelInfo { + pub fn effective_remote_addr(&self) -> Option<&Url> { + self.resolved_remote_addr + .as_ref() + .or(self.remote_addr.as_ref()) + } + + pub fn display_tunnel_type(&self) -> String { + let is_ipv6 = infer_tunnel_ipv6(&self.tunnel_type).or_else(|| { + self.resolved_remote_addr + .as_ref() + .or(self.local_addr.as_ref()) + .or(self.remote_addr.as_ref()) + .map(Url::is_ipv6_tunnel_endpoint) + }); + + if self.tunnel_type.contains("://") { + normalize_tunnel_url(&self.tunnel_type, is_ipv6) + .unwrap_or_else(|| self.tunnel_type.clone()) + } else { + is_ipv6 + .and_then(|is_ipv6| normalize_tunnel_scheme(&self.tunnel_type, is_ipv6)) + .unwrap_or_else(|| self.tunnel_type.clone()) + } + } + + pub fn display_remote_addr(&self) -> Option { + self.effective_remote_addr() + .map(Url::normalized_tunnel_display) + } +} + impl TryFrom for CompressorAlgo { type Error = anyhow::Error; @@ -397,3 +529,149 @@ impl SecureModeConfig { Ok(x25519_dalek::PublicKey::from(k)) } } + +#[cfg(test)] +mod tests { + use super::{normalize_tunnel_url, TunnelInfo, Url}; + + fn assert_ipv6_tunnel_normalization(scheme: &str, port: u16) { + let expected = format!("{scheme}6://[2001:db8::1]:{port}"); + assert_eq!( + normalize_tunnel_url(&format!("{scheme}://[2001:db8::1]:{port}"), None).as_deref(), + Some(expected.as_str()) + ); + } + + #[test] + fn normalize_plain_ipv6_tunnel_url() { + let url = Url { + url: "tcp://[2001:db8::1]:11010".to_string(), + }; + + assert_eq!( + url.normalized_tunnel_display(), + "tcp6://[2001:db8::1]:11010" + ); + assert!(url.is_ipv6_tunnel_endpoint()); + } + + #[test] + fn normalize_all_enabled_ipv6_tunnel_urls() { + assert_ipv6_tunnel_normalization("tcp", 11010); + assert_ipv6_tunnel_normalization("udp", 11010); + + #[cfg(feature = "wireguard")] + assert_ipv6_tunnel_normalization("wg", 11011); + + #[cfg(feature = "quic")] + assert_ipv6_tunnel_normalization("quic", 11012); + + #[cfg(feature = "websocket")] + assert_ipv6_tunnel_normalization("ws", 80); + + #[cfg(feature = "websocket")] + assert_ipv6_tunnel_normalization("wss", 443); + + #[cfg(feature = "faketcp")] + assert_ipv6_tunnel_normalization("faketcp", 11013); + } + + #[test] + fn normalize_composite_ipv6_tunnel_url() { + assert_eq!( + normalize_tunnel_url("txt-tcp://[2001:db8::1]:11010", None).as_deref(), + Some("txt-tcp6://[2001:db8::1]:11010") + ); + } + + #[test] + fn recover_malformed_composite_ipv6_tunnel_url() { + assert_eq!( + normalize_tunnel_url("txt-tcp://[2001:db8::1]:110106", None).as_deref(), + Some("txt-tcp6://[2001:db8::1]:11010") + ); + } + + #[test] + fn keep_normalized_ipv6_tunnel_url_stable() { + assert_eq!( + normalize_tunnel_url("tcp6://[2001:db8::1]:11010", None).as_deref(), + Some("tcp6://[2001:db8::1]:11010") + ); + } + + #[test] + fn normalize_ipv6_tunnel_url_without_explicit_port() { + assert_eq!( + normalize_tunnel_url("tcp://[2001:db8::1]", None).as_deref(), + Some("tcp6://[2001:db8::1]") + ); + } + + #[test] + fn keep_domain_host_unbracketed_when_ipv6_falls_back() { + assert_eq!( + normalize_tunnel_url("tcp://localhost:11010", Some(true)).as_deref(), + Some("tcp6://localhost:11010") + ); + } + + #[test] + fn tunnel_info_display_tunnel_type_preserves_composite_prefix() { + let tunnel = TunnelInfo { + tunnel_type: "txt-tcp://[2001:db8::2]:110106".to_string(), + local_addr: None, + remote_addr: Some(Url { + url: "txt://et.example.com".to_string(), + }), + resolved_remote_addr: None, + }; + + assert_eq!( + tunnel.display_tunnel_type(), + "txt-tcp6://[2001:db8::2]:11010" + ); + } + + #[test] + fn tunnel_info_display_tunnel_type_uses_remote_addr_fallback() { + let tunnel = TunnelInfo { + tunnel_type: "tcp".to_string(), + local_addr: None, + remote_addr: Some(Url { + url: "tcp://[2001:db8::2]:11010".to_string(), + }), + resolved_remote_addr: None, + }; + + assert_eq!(tunnel.display_tunnel_type(), "tcp6"); + assert_eq!( + tunnel.display_remote_addr().as_deref(), + Some("tcp6://[2001:db8::2]:11010") + ); + } + + #[test] + fn tunnel_info_prefers_resolved_remote_addr() { + let tunnel = TunnelInfo { + tunnel_type: "txt-tcp".to_string(), + local_addr: None, + remote_addr: Some(Url { + url: "txt://et.example.com".to_string(), + }), + resolved_remote_addr: Some(Url { + url: "tcp://[2001:db8::3]:11010".to_string(), + }), + }; + + assert_eq!(tunnel.display_tunnel_type(), "txt-tcp6"); + assert_eq!( + tunnel.display_remote_addr().as_deref(), + Some("tcp6://[2001:db8::3]:11010") + ); + assert_eq!( + tunnel.effective_remote_addr().map(|url| url.url.as_str()), + Some("tcp://[2001:db8::3]:11010") + ); + } +} diff --git a/easytier/src/tunnel/fake_tcp/mod.rs b/easytier/src/tunnel/fake_tcp/mod.rs index 9ec1ff1a..87b38be1 100644 --- a/easytier/src/tunnel/fake_tcp/mod.rs +++ b/easytier/src/tunnel/fake_tcp/mod.rs @@ -249,6 +249,13 @@ impl TunnelListener for FakeTcpTunnelListener { ) .into(), ), + resolved_remote_addr: Some( + crate::tunnel::build_url_from_socket_addr( + &socket.remote_addr().to_string(), + "faketcp", + ) + .into(), + ), }; // We treat the fake tcp socket as a datagram tunnel directly @@ -366,6 +373,10 @@ impl TunnelConnector for FakeTcpTunnelConnector { .into(), ), remote_addr: Some(self.addr.clone().into()), + resolved_remote_addr: Some( + crate::tunnel::build_url_from_socket_addr(&remote_addr.to_string(), "faketcp") + .into(), + ), }; let socket = Arc::new(socket); diff --git a/easytier/src/tunnel/mod.rs b/easytier/src/tunnel/mod.rs index 3a75a709..ca169327 100644 --- a/easytier/src/tunnel/mod.rs +++ b/easytier/src/tunnel/mod.rs @@ -11,7 +11,7 @@ use derive_more::{From, TryInto}; use futures::{Sink, Stream}; use socket2::Protocol; use std::fmt::Debug; -use strum::{Display, EnumString, VariantArray}; +use strum::{Display, EnumString, IntoStaticStr, VariantArray}; use tokio::time::error::Elapsed; use self::packet_def::ZCPacket; @@ -284,7 +284,7 @@ struct IpSchemeAttributes { port_offset: u16, } -#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantArray)] +#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, IntoStaticStr, VariantArray)] #[strum(serialize_all = "lowercase")] pub enum IpScheme { Tcp, diff --git a/easytier/src/tunnel/quic.rs b/easytier/src/tunnel/quic.rs index 48a72fba..b211d733 100644 --- a/easytier/src/tunnel/quic.rs +++ b/easytier/src/tunnel/quic.rs @@ -175,6 +175,9 @@ impl QuicTunnelListener { remote_addr: Some( super::build_url_from_socket_addr(&remote_addr.to_string(), "quic").into(), ), + resolved_remote_addr: Some( + super::build_url_from_socket_addr(&remote_addr.to_string(), "quic").into(), + ), }; Ok(Box::new(TunnelWrapper::new( @@ -280,6 +283,10 @@ impl TunnelConnector for QuicTunnelConnector { super::build_url_from_socket_addr(&local_addr.to_string(), "quic").into(), ), remote_addr: Some(self.addr.clone().into()), + resolved_remote_addr: Some( + super::build_url_from_socket_addr(&connection.remote_address().to_string(), "quic") + .into(), + ), }; let arc_conn = Arc::new(ConnWrapper { conn: connection }); diff --git a/easytier/src/tunnel/ring.rs b/easytier/src/tunnel/ring.rs index 8fd35527..0459c393 100644 --- a/easytier/src/tunnel/ring.rs +++ b/easytier/src/tunnel/ring.rs @@ -214,6 +214,9 @@ fn get_tunnel_for_client(conn: Arc) -> impl Tunnel { tunnel_type: "ring".to_owned(), local_addr: Some(build_url_from_socket_addr(&conn.client.id.into(), "ring").into()), remote_addr: Some(build_url_from_socket_addr(&conn.server.id.into(), "ring").into()), + resolved_remote_addr: Some( + build_url_from_socket_addr(&conn.server.id.into(), "ring").into(), + ), }), ) } @@ -226,6 +229,9 @@ fn get_tunnel_for_server(conn: Arc) -> impl Tunnel { tunnel_type: "ring".to_owned(), local_addr: Some(build_url_from_socket_addr(&conn.server.id.into(), "ring").into()), remote_addr: Some(build_url_from_socket_addr(&conn.client.id.into(), "ring").into()), + resolved_remote_addr: Some( + build_url_from_socket_addr(&conn.client.id.into(), "ring").into(), + ), }), ) } diff --git a/easytier/src/tunnel/tcp.rs b/easytier/src/tunnel/tcp.rs index 9a86eef7..70fd13b4 100644 --- a/easytier/src/tunnel/tcp.rs +++ b/easytier/src/tunnel/tcp.rs @@ -41,6 +41,9 @@ impl TcpTunnelListener { remote_addr: Some( super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(), ), + resolved_remote_addr: Some( + super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(), + ), }; let (r, w) = stream.into_split(); @@ -117,6 +120,9 @@ fn get_tunnel_with_tcp_stream( super::build_url_from_socket_addr(&stream.local_addr()?.to_string(), "tcp").into(), ), remote_addr: Some(remote_url.into()), + resolved_remote_addr: Some( + super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(), + ), }; let (r, w) = stream.into_split(); @@ -277,6 +283,34 @@ mod tests { _tunnel_pingpong(listener, connector).await; } + #[tokio::test] + async fn connector_keeps_source_addr_and_reports_resolved_addr() { + let mut listener = TcpTunnelListener::new("tcp://127.0.0.1:0".parse().unwrap()); + listener.listen().await.unwrap(); + + let port = listener.local_url().port().unwrap(); + let source_url: url::Url = format!("tcp://localhost:{port}").parse().unwrap(); + let mut connector = TcpTunnelConnector::new(source_url.clone()); + connector.set_ip_version(IpVersion::V4); + + let accept_task = tokio::spawn(async move { listener.accept().await.unwrap() }); + let tunnel = connector.connect().await.unwrap(); + let accepted_tunnel = accept_task.await.unwrap(); + + let info = tunnel.info().unwrap(); + assert_eq!(info.remote_addr.unwrap().url, source_url.to_string()); + + let resolved_remote_addr: url::Url = info.resolved_remote_addr.unwrap().into(); + assert_eq!(resolved_remote_addr.host_str(), Some("127.0.0.1")); + assert_eq!(resolved_remote_addr.port(), Some(port)); + + let accepted_info = accepted_tunnel.info().unwrap(); + assert_eq!( + accepted_info.remote_addr, + accepted_info.resolved_remote_addr, + ); + } + #[tokio::test] async fn test_alloc_port() { // v4 diff --git a/easytier/src/tunnel/udp.rs b/easytier/src/tunnel/udp.rs index f1b1c385..b191b7b3 100644 --- a/easytier/src/tunnel/udp.rs +++ b/easytier/src/tunnel/udp.rs @@ -428,6 +428,9 @@ impl UdpTunnelListenerData { remote_addr: Some( build_url_from_socket_addr(&remote_addr.to_string(), "udp").into(), ), + resolved_remote_addr: Some( + build_url_from_socket_addr(&remote_addr.to_string(), "udp").into(), + ), }), )); @@ -772,6 +775,9 @@ impl UdpTunnelConnector { build_url_from_socket_addr(&socket.local_addr()?.to_string(), "udp").into(), ), remote_addr: Some(self.addr.clone().into()), + resolved_remote_addr: Some( + build_url_from_socket_addr(&dst_addr.to_string(), "udp").into(), + ), }), ))) } diff --git a/easytier/src/tunnel/unix.rs b/easytier/src/tunnel/unix.rs index a2f0e4ed..6c8415d2 100644 --- a/easytier/src/tunnel/unix.rs +++ b/easytier/src/tunnel/unix.rs @@ -43,7 +43,8 @@ impl UnixSocketTunnelListener { let info = TunnelInfo { tunnel_type: "unix".to_owned(), local_addr: Some(self.local_url().into()), - remote_addr: remote_addr.map(Into::into), + remote_addr: remote_addr.clone().map(Into::into), + resolved_remote_addr: remote_addr.map(Into::into), }; let (r, w) = stream.into_split(); @@ -122,6 +123,7 @@ impl super::TunnelConnector for UnixSocketTunnelConnector { tunnel_type: "unix".to_owned(), local_addr: local_addr.map(Into::into), remote_addr: Some(self.addr.clone().into()), + resolved_remote_addr: Some(self.addr.clone().into()), }; let (r, w) = stream.into_split(); diff --git a/easytier/src/tunnel/websocket.rs b/easytier/src/tunnel/websocket.rs index 5dbbd49a..1f1394ce 100644 --- a/easytier/src/tunnel/websocket.rs +++ b/easytier/src/tunnel/websocket.rs @@ -143,11 +143,13 @@ impl WsTunnelListener { } let (write, read) = stream.split(); + let remote_addr: crate::proto::common::Url = remote_addr.into(); let info = TunnelInfo { tunnel_type: self.addr.scheme().to_owned(), local_addr: Some(self.local_url().into()), - remote_addr: Some(remote_addr.into()), + remote_addr: Some(remote_addr.clone()), + resolved_remote_addr: Some(remote_addr), }; Ok(Box::new(TunnelWrapper::new( @@ -235,6 +237,9 @@ impl WsTunnelConnector { .into(), ), remote_addr: Some(addr.clone().into()), + resolved_remote_addr: Some( + super::build_url_from_socket_addr(&socket_addr.to_string(), addr.scheme()).into(), + ), }; let c = ClientBuilder::from_uri(http::Uri::try_from(addr.to_string()).unwrap()); diff --git a/easytier/src/tunnel/wireguard.rs b/easytier/src/tunnel/wireguard.rs index d93eec8d..e8833973 100644 --- a/easytier/src/tunnel/wireguard.rs +++ b/easytier/src/tunnel/wireguard.rs @@ -538,6 +538,9 @@ impl WgTunnelListener { remote_addr: Some( build_url_from_socket_addr(&addr.to_string(), "wg").into(), ), + resolved_remote_addr: Some( + build_url_from_socket_addr(&addr.to_string(), "wg").into(), + ), }), )); if let Err(e) = conn_sender.send(tunnel) { @@ -685,6 +688,9 @@ impl WgTunnelConnector { tunnel_type: "wg".to_owned(), local_addr: Some(super::build_url_from_socket_addr(&local_addr, "wg").into()), remote_addr: Some(addr_url.into()), + resolved_remote_addr: Some( + super::build_url_from_socket_addr(&addr.to_string(), "wg").into(), + ), }), Some(Box::new(wg_peer)), ));