From 2ac3d01d466622e5955fb1e179caabe7b52beffa Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 18 Jun 2024 10:36:03 +0200 Subject: [PATCH] refactor(iroh-net)!: Rename Endpoint::local_endpoints to direct_addresses (#2369) ## Description This renames Endpoint::local_endpoints to Endpoint::direct_addresses. As a consequence it renames a lot of other things, e.g. config::Endpoint type becomes magicsock::DirectAddr. This is hopefully the last ambiguous use of "endpoint". The name "direct addresses" seemed to be the consensus on a discord bikeshedding thread. The entire config module is removed and the types are moved into magicsock instead. These types did not have anything to do with configuration and this was also a source of confusion. Because the visibility of these types changed some dead code was removed as well. ## Breaking Changes - iroh_net::Endpoint::local_endpoints -> iroh_net::Endpoint::direct_addresses - iroh_net::endpoint::LocalEndpointStream -> iroh_net::endpoint::DirectAddrStream - iroh_gossip::net::Gossip::update_endpoints -> iroh_gossip::net::Gossip::update_direct_addresses - iroh_net::config is removed. - iroh_net::config::Endpoint -> iroh_net::magicsock::DirectAddr - iroh_net::config::EndpointType -> iroh_net::magicsock::DirectAddrType - iroh_net::config::NetInfo -> removed - iroh_net::config::LinkInfo -> removed ## Notes & open questions ## Change checklist - [x] Self-review. - [x] Documentation updates if relevant. - ~~[ ] Tests if relevant.~~ - [x] All breaking changes documented. --- iroh-cli/src/commands/doctor.rs | 4 +- iroh-gossip/src/net.rs | 23 +-- iroh-net/examples/connect-unreliable.rs | 2 +- iroh-net/examples/connect.rs | 2 +- iroh-net/examples/listen-unreliable.rs | 2 +- iroh-net/examples/listen.rs | 2 +- iroh-net/src/config.rs | 128 -------------- iroh-net/src/endpoint.rs | 14 +- iroh-net/src/lib.rs | 1 - iroh-net/src/magicsock.rs | 211 ++++++++++++++++++------ iroh/src/node.rs | 10 +- iroh/src/node/builder.rs | 10 +- 12 files changed, 202 insertions(+), 207 deletions(-) delete mode 100644 iroh-net/src/config.rs diff --git a/iroh-cli/src/commands/doctor.rs b/iroh-cli/src/commands/doctor.rs index e30512c4f3..62d1eb69b3 100644 --- a/iroh-cli/src/commands/doctor.rs +++ b/iroh-cli/src/commands/doctor.rs @@ -669,7 +669,7 @@ async fn make_endpoint( }; let endpoint = endpoint.bind(0).await?; - tokio::time::timeout(Duration::from_secs(10), endpoint.local_endpoints().next()) + tokio::time::timeout(Duration::from_secs(10), endpoint.direct_addresses().next()) .await .context("wait for relay connection")? .context("no endpoints")?; @@ -727,7 +727,7 @@ async fn accept( ) -> anyhow::Result<()> { let endpoint = make_endpoint(secret_key.clone(), relay_map, discovery).await?; let endpoints = endpoint - .local_endpoints() + .direct_addresses() .next() .await .context("no endpoints")?; diff --git a/iroh-gossip/src/net.rs b/iroh-gossip/src/net.rs index d1a33fa962..5bbac2cf58 100644 --- a/iroh-gossip/src/net.rs +++ b/iroh-gossip/src/net.rs @@ -70,7 +70,7 @@ type ProtoMessage = proto::Message; #[derive(Debug, Clone)] pub struct Gossip { to_actor_tx: mpsc::Sender, - on_endpoints_tx: mpsc::Sender>, + on_direct_addrs_tx: mpsc::Sender>, _actor_handle: Arc>>, max_message_size: usize, } @@ -99,7 +99,7 @@ impl Gossip { to_actor_rx, in_event_rx, in_event_tx, - on_endpoints_rx, + on_direct_addr_rx: on_endpoints_rx, conns: Default::default(), conn_send_tx: Default::default(), pending_sends: Default::default(), @@ -121,7 +121,7 @@ impl Gossip { ); Self { to_actor_tx, - on_endpoints_tx, + on_direct_addrs_tx: on_endpoints_tx, _actor_handle: Arc::new(actor_handle), max_message_size, } @@ -241,16 +241,19 @@ impl Gossip { Ok(()) } - /// Set info on our local endpoints. + /// Set info on our direct addresses. /// /// This will be sent to peers on Neighbor and Join requests so that they can connect directly /// to us. /// /// This is only best effort, and will drop new events if backed up. - pub fn update_endpoints(&self, endpoints: &[iroh_net::config::Endpoint]) -> anyhow::Result<()> { - let endpoints = endpoints.to_vec(); - self.on_endpoints_tx - .try_send(endpoints) + pub fn update_direct_addresses( + &self, + addrs: &[iroh_net::endpoint::DirectAddr], + ) -> anyhow::Result<()> { + let addrs = addrs.to_vec(); + self.on_direct_addrs_tx + .try_send(addrs) .map_err(|_| anyhow!("endpoints channel dropped"))?; Ok(()) } @@ -342,7 +345,7 @@ struct Actor { /// Input events to the state (emitted from the connection loops) in_event_rx: mpsc::Receiver, /// Updates of discovered endpoint addresses - on_endpoints_rx: mpsc::Receiver>, + on_direct_addr_rx: mpsc::Receiver>, /// Queued timers timers: Timers, /// Currently opened quinn connections to peers @@ -375,7 +378,7 @@ impl Actor { } } }, - new_endpoints = self.on_endpoints_rx.recv() => { + new_endpoints = self.on_direct_addr_rx.recv() => { match new_endpoints { Some(endpoints) => { let addr = NodeAddr::from_parts( diff --git a/iroh-net/examples/connect-unreliable.rs b/iroh-net/examples/connect-unreliable.rs index 5438673557..3be041353c 100644 --- a/iroh-net/examples/connect-unreliable.rs +++ b/iroh-net/examples/connect-unreliable.rs @@ -60,7 +60,7 @@ async fn main() -> anyhow::Result<()> { println!("node id: {me}"); println!("node listening addresses:"); for local_endpoint in endpoint - .local_endpoints() + .direct_addresses() .next() .await .context("no endpoints")? diff --git a/iroh-net/examples/connect.rs b/iroh-net/examples/connect.rs index ccaffb6e54..216a4e42eb 100644 --- a/iroh-net/examples/connect.rs +++ b/iroh-net/examples/connect.rs @@ -57,7 +57,7 @@ async fn main() -> anyhow::Result<()> { println!("node id: {me}"); println!("node listening addresses:"); for local_endpoint in endpoint - .local_endpoints() + .direct_addresses() .next() .await .context("no endpoints")? diff --git a/iroh-net/examples/listen-unreliable.rs b/iroh-net/examples/listen-unreliable.rs index 5850c1727a..7dbc5e246d 100644 --- a/iroh-net/examples/listen-unreliable.rs +++ b/iroh-net/examples/listen-unreliable.rs @@ -38,7 +38,7 @@ async fn main() -> anyhow::Result<()> { println!("node listening addresses:"); let local_addrs = endpoint - .local_endpoints() + .direct_addresses() .next() .await .context("no endpoints")? diff --git a/iroh-net/examples/listen.rs b/iroh-net/examples/listen.rs index a45f300254..6f538534a4 100644 --- a/iroh-net/examples/listen.rs +++ b/iroh-net/examples/listen.rs @@ -38,7 +38,7 @@ async fn main() -> anyhow::Result<()> { println!("node listening addresses:"); let local_addrs = endpoint - .local_endpoints() + .direct_addresses() .next() .await .context("no endpoints")? diff --git a/iroh-net/src/config.rs b/iroh-net/src/config.rs deleted file mode 100644 index 8c98749810..0000000000 --- a/iroh-net/src/config.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Configuration types. - -use std::{collections::BTreeMap, fmt::Display, net::SocketAddr}; - -use crate::relay::RelayUrl; - -use super::portmapper; - -// TODO: This re-uses "Endpoint" again, a term that already means "a quic endpoint" and "a -// magicsock endpoint". this time it means "an IP address on which our local magicsock -// endpoint is listening". Name this better. -/// An endpoint IPPort and an associated type. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Endpoint { - /// The address of the endpoint. - pub addr: SocketAddr, - /// The kind of endpoint. - pub typ: EndpointType, -} - -/// Type of endpoint. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum EndpointType { - /// Endpoint kind has not been determined yet. - Unknown, - /// Endpoint is bound to a local address. - Local, - /// Endpoint has a publicly reachable address found via STUN. - Stun, - /// Endpoint uses a port mapping in the router. - Portmapped, - /// Hard NAT: STUN'ed IPv4 address + local fixed port. - Stun4LocalPort, -} - -impl Display for EndpointType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - EndpointType::Unknown => write!(f, "?"), - EndpointType::Local => write!(f, "local"), - EndpointType::Stun => write!(f, "stun"), - EndpointType::Portmapped => write!(f, "portmap"), - EndpointType::Stun4LocalPort => write!(f, "stun4localport"), - } - } -} - -/// Contains information about the host's network state. -#[derive(Debug, Clone, PartialEq)] -pub struct NetInfo { - /// Says whether the host's NAT mappings vary based on the destination IP. - pub mapping_varies_by_dest_ip: Option, - - /// If their router does hairpinning. It reports true even if there's no NAT involved. - pub hair_pinning: Option, - - /// Whether the host has IPv6 internet connectivity. - pub working_ipv6: Option, - - /// Whether the OS supports IPv6 at all, regardless of whether IPv6 internet connectivity is available. - pub os_has_ipv6: Option, - - /// Whether the host has UDP internet connectivity. - pub working_udp: Option, - - /// Whether ICMPv4 works, `None` means not checked. - pub working_icmp_v4: Option, - - /// Whether ICMPv6 works, `None` means not checked. - pub working_icmp_v6: Option, - - /// Whether we have an existing portmap open (UPnP, PMP, or PCP). - pub have_port_map: bool, - - /// Probe indicating the presence of port mapping protocols on the LAN. - pub portmap_probe: Option, - - /// This node's preferred relay server for incoming traffic. The node might be be temporarily - /// connected to multiple relay servers (to send to other nodes) - /// but PreferredRelay is the instance number that the node - /// subscribes to traffic at. Zero means disconnected or unknown. - pub preferred_relay: Option, - - /// LinkType is the current link type, if known. - pub link_type: Option, - - /// The fastest recent time to reach various relay STUN servers, in seconds. - /// - /// This should only be updated rarely, or when there's a - /// material change, as any change here also gets uploaded to the control plane. - pub relay_latency: BTreeMap, -} - -impl NetInfo { - /// reports whether `self` and `other` are basically equal, ignoring changes in relay ServerLatency & RelayLatency. - pub fn basically_equal(&self, other: &Self) -> bool { - let eq_icmp_v4 = match (self.working_icmp_v4, other.working_icmp_v4) { - (Some(slf), Some(other)) => slf == other, - _ => true, // ignore for comparison if only one report had this info - }; - let eq_icmp_v6 = match (self.working_icmp_v6, other.working_icmp_v6) { - (Some(slf), Some(other)) => slf == other, - _ => true, // ignore for comparison if only one report had this info - }; - self.mapping_varies_by_dest_ip == other.mapping_varies_by_dest_ip - && self.hair_pinning == other.hair_pinning - && self.working_ipv6 == other.working_ipv6 - && self.os_has_ipv6 == other.os_has_ipv6 - && self.working_udp == other.working_udp - && eq_icmp_v4 - && eq_icmp_v6 - && self.have_port_map == other.have_port_map - && self.portmap_probe == other.portmap_probe - && self.preferred_relay == other.preferred_relay - && self.link_type == other.link_type - } -} - -/// The type of link. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LinkType { - /// A wired link (ethernet, fiber, etc). - Wired, - /// A WiFi link. - Wifi, - /// LTE, 4G, 3G, etc. - Mobile, -} diff --git a/iroh-net/src/endpoint.rs b/iroh-net/src/endpoint.rs index cd69d67d0d..5388d59e33 100644 --- a/iroh-net/src/endpoint.rs +++ b/iroh-net/src/endpoint.rs @@ -47,8 +47,8 @@ pub use quinn::{ }; pub use super::magicsock::{ - ConnectionInfo, ConnectionType, ConnectionTypeStream, ControlMsg, DirectAddrInfo, - LocalEndpointsStream, + ConnectionInfo, ConnectionType, ConnectionTypeStream, ControlMsg, DirectAddr, DirectAddrInfo, + DirectAddrType, DirectAddrsStream, }; pub use iroh_base::node_addr::{AddrInfo, NodeAddr}; @@ -567,10 +567,10 @@ impl Endpoint { /// /// The returned [`NodeAddr`] will have the current [`RelayUrl`] and local IP endpoints /// as they would be returned by [`Endpoint::home_relay`] and - /// [`Endpoint::local_endpoints`]. + /// [`Endpoint::direct_addresses`]. pub async fn node_addr(&self) -> Result { let addrs = self - .local_endpoints() + .direct_addresses() .next() .await .ok_or(anyhow!("No IP endpoints found"))?; @@ -637,13 +637,13 @@ impl Endpoint { /// # let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); /// # rt.block_on(async move { /// let mep = Endpoint::builder().bind(0).await.unwrap(); - /// let _endpoints = mep.local_endpoints().next().await; + /// let _addrs = mep.direct_addresses().next().await; /// # }); /// ``` /// /// [STUN]: https://en.wikipedia.org/wiki/STUN - pub fn local_endpoints(&self) -> LocalEndpointsStream { - self.msock.local_endpoints() + pub fn direct_addresses(&self) -> DirectAddrsStream { + self.msock.direct_addresses() } /// Returns the local socket addresses on which the underlying sockets are bound. diff --git a/iroh-net/src/lib.rs b/iroh-net/src/lib.rs index 5cba9c3892..8e54ac70e2 100644 --- a/iroh-net/src/lib.rs +++ b/iroh-net/src/lib.rs @@ -117,7 +117,6 @@ #![recursion_limit = "256"] #![deny(missing_docs, rustdoc::broken_intra_doc_links)] -pub mod config; pub mod defaults; pub mod dialer; mod disco; diff --git a/iroh-net/src/magicsock.rs b/iroh-net/src/magicsock.rs index 435e86a841..d6e935f885 100644 --- a/iroh-net/src/magicsock.rs +++ b/iroh-net/src/magicsock.rs @@ -16,7 +16,7 @@ //! however, read any packets that come off the UDP sockets. use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, fmt::Display, io, net::{IpAddr, Ipv6Addr, SocketAddr}, @@ -51,7 +51,6 @@ use url::Url; use watchable::Watchable; use crate::{ - config, disco::{self, SendAddr}, discovery::Discovery, dns::DnsResolver, @@ -304,21 +303,22 @@ impl MagicSock { self.node_map.node_info(node_id) } - /// Returns the local endpoints as a stream. + /// Returns the direct addresses as a stream. /// - /// The [`MagicSock`] continuously monitors the local endpoints, the network addresses - /// it can listen on, for changes. Whenever changes are detected this stream will yield - /// a new list of endpoints. + /// The [`MagicSock`] continuously monitors the direct addresses, the network addresses + /// it might be able to be contacted on, for changes. Whenever changes are detected + /// this stream will yield a new list of addresses. /// /// Upon the first creation on the [`MagicSock`] it may not yet have completed a first - /// local endpoint discovery, in this case the first item of the stream will not be - /// immediately available. Once this first set of local endpoints are discovered the - /// stream will always return the first set of endpoints immediately, which are the most - /// recently discovered endpoints. + /// direct addresses discovery, in this case the first item of the stream will not be + /// immediately available. Once this first set of direct addresses are discovered the + /// stream will always return the first set of addresses immediately, which are the most + /// recently discovered addresses. /// - /// To get the current endpoints, drop the stream after the first item was received. - pub fn local_endpoints(&self) -> LocalEndpointsStream { - LocalEndpointsStream { + /// To get the current direct addresses, drop the stream after the first item was + /// received. + pub fn direct_addresses(&self) -> DirectAddrsStream { + DirectAddrsStream { initial: Some(self.endpoints.get()), inner: self.endpoints.watch().into_stream(), } @@ -1493,13 +1493,13 @@ impl Handle { /// Stream returning local endpoints as they change. #[derive(Debug)] -pub struct LocalEndpointsStream { +pub struct DirectAddrsStream { initial: Option, inner: watchable::WatcherStream, } -impl Stream for LocalEndpointsStream { - type Item = Vec; +impl Stream for DirectAddrsStream { + type Item = Vec; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = &mut *self; @@ -1582,7 +1582,7 @@ enum DiscoBoxError { type RelayRecvResult = Result<(PublicKey, quinn_udp::RecvMeta, Bytes), io::Error>; /// Reports whether x and y represent the same set of endpoints. The order doesn't matter. -fn endpoint_sets_equal(xs: &[config::Endpoint], ys: &[config::Endpoint]) -> bool { +fn endpoint_sets_equal(xs: &[DirectAddr], ys: &[DirectAddr]) -> bool { if xs.is_empty() && ys.is_empty() { return true; } @@ -1598,7 +1598,7 @@ fn endpoint_sets_equal(xs: &[config::Endpoint], ys: &[config::Endpoint]) -> bool return true; } } - let mut m: HashMap<&config::Endpoint, usize> = HashMap::new(); + let mut m: HashMap<&DirectAddr, usize> = HashMap::new(); for x in xs { *m.entry(x).or_default() |= 1; } @@ -1667,7 +1667,7 @@ struct Actor { /// When set, is an AfterFunc timer that will call MagicSock::do_periodic_stun. periodic_re_stun_timer: time::Interval, /// The `NetInfo` provided in the last call to `net_info_func`. It's used to deduplicate calls to netInfoFunc. - net_info_last: Option, + net_info_last: Option, /// Path where connection info from [`MagicSock::node_map`] is persisted. nodes_path: Option, @@ -1962,7 +1962,7 @@ impl Actor { #[allow(clippy::map_entry)] if !$already.contains_key(&$ipp) { $already.insert($ipp, $et); - $eps.push(config::Endpoint { + $eps.push(DirectAddr { addr: $ipp, typ: $et, }); @@ -1973,13 +1973,13 @@ impl Actor { let maybe_port_mapped = *portmap_watcher.borrow(); if let Some(portmap_ext) = maybe_port_mapped.map(SocketAddr::V4) { - add_addr!(already, eps, portmap_ext, config::EndpointType::Portmapped); + add_addr!(already, eps, portmap_ext, DirectAddrType::Portmapped); self.set_net_info_have_port_map().await; } if let Some(nr) = nr { if let Some(global_v4) = nr.global_v4 { - add_addr!(already, eps, global_v4.into(), config::EndpointType::Stun); + add_addr!(already, eps, global_v4.into(), DirectAddrType::Stun); // If they're behind a hard NAT and are using a fixed // port locally, assume they might've added a static @@ -1989,16 +1989,11 @@ impl Actor { if nr.mapping_varies_by_dest_ip.unwrap_or_default() && port != 0 { let mut addr = global_v4; addr.set_port(port); - add_addr!( - already, - eps, - addr.into(), - config::EndpointType::Stun4LocalPort - ); + add_addr!(already, eps, addr.into(), DirectAddrType::Stun4LocalPort); } } if let Some(global_v6) = nr.global_v6 { - add_addr!(already, eps, global_v6.into(), config::EndpointType::Stun); + add_addr!(already, eps, global_v6.into(), DirectAddrType::Stun); } } let local_addr_v4 = self.pconn4.local_addr().ok(); @@ -2056,7 +2051,7 @@ impl Actor { already, eps, SocketAddr::new(ip, port), - config::EndpointType::Local + DirectAddrType::Local ); } } @@ -2066,7 +2061,7 @@ impl Actor { already, eps, SocketAddr::new(ip, port), - config::EndpointType::Local + DirectAddrType::Local ); } } @@ -2078,7 +2073,7 @@ impl Actor { if let Some(addr) = local_addr_v4 { // Our local endpoint is bound to a particular address. // Do not offer addresses on other local interfaces. - add_addr!(already, eps, addr, config::EndpointType::Local); + add_addr!(already, eps, addr, DirectAddrType::Local); } } @@ -2086,7 +2081,7 @@ impl Actor { if let Some(addr) = local_addr_v6 { // Our local endpoint is bound to a particular address. // Do not offer addresses on other local interfaces. - add_addr!(already, eps, addr, config::EndpointType::Local); + add_addr!(already, eps, addr, DirectAddrType::Local); } } @@ -2145,7 +2140,7 @@ impl Actor { } #[instrument(level = "debug", skip_all)] - async fn call_net_info_callback(&mut self, ni: config::NetInfo) { + async fn call_net_info_callback(&mut self, ni: NetInfo) { if let Some(ref net_info_last) = self.net_info_last { if ni.basically_equal(net_info_last) { return; @@ -2220,7 +2215,7 @@ impl Actor { self.no_v4_send = !r.ipv4_can_send; let have_port_map = self.port_mapper.watch_external_address().borrow().is_some(); - let mut ni = config::NetInfo { + let mut ni = NetInfo { relay_latency: Default::default(), mapping_varies_by_dest_ip: r.mapping_varies_by_dest_ip, hair_pinning: r.hair_pinning, @@ -2232,7 +2227,6 @@ impl Actor { working_icmp_v4: r.icmpv4, working_icmp_v6: r.icmpv6, preferred_relay: r.preferred_relay.clone(), - link_type: None, }; for (rid, d) in r.relay_v4_latency.iter() { ni.relay_latency @@ -2408,7 +2402,7 @@ fn bind(port: u16) -> Result<(UdpConn, Option)> { struct DiscoveredEndpoints { /// Records the endpoints found during the previous /// endpoint discovery. It's used to avoid duplicate endpoint change notifications. - last_endpoints: Vec, + last_endpoints: Vec, /// The last time the endpoints were updated, even if there was no change. last_endpoints_time: Option, @@ -2421,18 +2415,18 @@ impl PartialEq for DiscoveredEndpoints { } impl DiscoveredEndpoints { - fn new(endpoints: Vec) -> Self { + fn new(endpoints: Vec) -> Self { Self { last_endpoints: endpoints, last_endpoints_time: Some(Instant::now()), } } - fn into_iter(self) -> impl Iterator { + fn into_iter(self) -> impl Iterator { self.last_endpoints.into_iter() } - fn iter(&self) -> impl Iterator + '_ { + fn iter(&self) -> impl Iterator + '_ { self.last_endpoints.iter() } @@ -2592,6 +2586,131 @@ fn disco_message_sent(msg: &disco::Message) { } } +/// A *direct address* on which an iroh-node might be contactable. +/// +/// Direct addresses are UDP socket addresses on which an iroh-net node could potentially be +/// contacted. These can come from various sources depending on the network topology of the +/// iroh-net node, see [`DirectAddrType`] for the several kinds of sources. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct DirectAddr { + /// The address. + pub addr: SocketAddr, + /// The origin of this direct address. + pub typ: DirectAddrType, +} + +/// The type of direct address. +/// +/// These are the various sources or origins from which an iroh-net node might have found a +/// possible [`DirectAddr`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum DirectAddrType { + /// Not yet determined.. + Unknown, + /// A locally bound socket address. + Local, + /// Public internet address discovered via STUN. + /// + /// When possible an iroh-net node will perform STUN to discover which is the address + /// from which it sends data on the public internet. This can be different from locally + /// bound addresses when the node is on a local network wich performs NAT or similar. + Stun, + /// An address assigned by the router using port mapping. + /// + /// When possible an iroh-net node will request a port mapping from the local router to + /// get a publicly routable direct address. + Portmapped, + /// Hard NAT: STUN'ed IPv4 address + local fixed port. + /// + /// It is possible to configure iroh-net to bound to a specific port and independently + /// configure the router to forward this port to the iroh-net node. This indicates a + /// situation like this, which still uses STUN to discover the public address. + Stun4LocalPort, +} + +impl Display for DirectAddrType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DirectAddrType::Unknown => write!(f, "?"), + DirectAddrType::Local => write!(f, "local"), + DirectAddrType::Stun => write!(f, "stun"), + DirectAddrType::Portmapped => write!(f, "portmap"), + DirectAddrType::Stun4LocalPort => write!(f, "stun4localport"), + } + } +} + +/// Contains information about the host's network state. +#[derive(Debug, Clone, PartialEq)] +struct NetInfo { + /// Says whether the host's NAT mappings vary based on the destination IP. + mapping_varies_by_dest_ip: Option, + + /// If their router does hairpinning. It reports true even if there's no NAT involved. + hair_pinning: Option, + + /// Whether the host has IPv6 internet connectivity. + working_ipv6: Option, + + /// Whether the OS supports IPv6 at all, regardless of whether IPv6 internet connectivity is available. + os_has_ipv6: Option, + + /// Whether the host has UDP internet connectivity. + working_udp: Option, + + /// Whether ICMPv4 works, `None` means not checked. + working_icmp_v4: Option, + + /// Whether ICMPv6 works, `None` means not checked. + working_icmp_v6: Option, + + /// Whether we have an existing portmap open (UPnP, PMP, or PCP). + have_port_map: bool, + + /// Probe indicating the presence of port mapping protocols on the LAN. + portmap_probe: Option, + + /// This node's preferred relay server for incoming traffic. + /// + /// The node might be be temporarily connected to multiple relay servers (to send to + /// other nodes) but this is the relay on which you can always contact this node. Also + /// known as home relay. + preferred_relay: Option, + + /// The fastest recent time to reach various relay STUN servers, in seconds. + /// + /// This should only be updated rarely, or when there's a + /// material change, as any change here also gets uploaded to the control plane. + relay_latency: BTreeMap, +} + +impl NetInfo { + /// Checks if this is probably still the same network as *other*. + /// + /// This tries to compare the network situation, without taking into account things + /// expected to change a little like e.g. latency to the relay server. + pub fn basically_equal(&self, other: &Self) -> bool { + let eq_icmp_v4 = match (self.working_icmp_v4, other.working_icmp_v4) { + (Some(slf), Some(other)) => slf == other, + _ => true, // ignore for comparison if only one report had this info + }; + let eq_icmp_v6 = match (self.working_icmp_v6, other.working_icmp_v6) { + (Some(slf), Some(other)) => slf == other, + _ => true, // ignore for comparison if only one report had this info + }; + self.mapping_varies_by_dest_ip == other.mapping_varies_by_dest_ip + && self.hair_pinning == other.hair_pinning + && self.working_ipv6 == other.working_ipv6 + && self.os_has_ipv6 == other.os_has_ipv6 + && self.working_udp == other.working_udp + && eq_icmp_v4 + && eq_icmp_v6 + && self.have_port_map == other.have_port_map + && self.portmap_probe == other.portmap_probe + && self.preferred_relay == other.preferred_relay + } +} + #[cfg(test)] pub(crate) mod tests { use anyhow::Context; @@ -2658,7 +2777,7 @@ pub(crate) mod tests { #[instrument(skip_all)] async fn mesh_stacks(stacks: Vec) -> Result { /// Registers endpoint addresses of a node to all other nodes. - fn update_eps(stacks: &[MagicStack], my_idx: usize, new_eps: Vec) { + fn update_direct_addrs(stacks: &[MagicStack], my_idx: usize, new_addrs: Vec) { let me = &stacks[my_idx]; for (i, m) in stacks.iter().enumerate() { if i == my_idx { @@ -2669,7 +2788,7 @@ pub(crate) mod tests { node_id: me.public(), info: crate::AddrInfo { relay_url: None, - direct_addresses: new_eps.iter().map(|ep| ep.addr).collect(), + direct_addresses: new_addrs.iter().map(|ep| ep.addr).collect(), }, }; m.endpoint.magic_sock().add_node_addr(addr); @@ -2684,10 +2803,10 @@ pub(crate) mod tests { let stacks = stacks.clone(); tasks.spawn(async move { let me = m.endpoint.node_id().fmt_short(); - let mut stream = m.endpoint.local_endpoints(); + let mut stream = m.endpoint.direct_addresses(); while let Some(new_eps) = stream.next().await { info!(%me, "conn{} endpoints update: {:?}", my_idx + 1, new_eps); - update_eps(&stacks, my_idx, new_eps); + update_direct_addrs(&stacks, my_idx, new_eps); } }); } @@ -3353,13 +3472,13 @@ pub(crate) mod tests { let ms = Handle::new(Default::default()).await.unwrap(); // See if we can get endpoints. - let mut eps0 = ms.local_endpoints().next().await.unwrap(); + let mut eps0 = ms.direct_addresses().next().await.unwrap(); eps0.sort(); println!("{eps0:?}"); assert!(!eps0.is_empty()); // Getting the endpoints again immediately should give the same results. - let mut eps1 = ms.local_endpoints().next().await.unwrap(); + let mut eps1 = ms.direct_addresses().next().await.unwrap(); eps1.sort(); println!("{eps1:?}"); assert_eq!(eps0, eps1); diff --git a/iroh/src/node.rs b/iroh/src/node.rs index e074efa7b1..d156c242d6 100644 --- a/iroh/src/node.rs +++ b/iroh/src/node.rs @@ -14,8 +14,10 @@ use iroh_base::key::PublicKey; use iroh_blobs::downloader::Downloader; use iroh_blobs::store::Store as BaoStore; use iroh_docs::engine::Engine; +use iroh_net::endpoint::DirectAddrsStream; +use iroh_net::key::SecretKey; use iroh_net::util::AbortingJoinHandle; -use iroh_net::{endpoint::LocalEndpointsStream, key::SecretKey, Endpoint}; +use iroh_net::Endpoint; use quic_rpc::transport::flume::FlumeConnection; use quic_rpc::RpcClient; use tokio::task::JoinHandle; @@ -116,8 +118,8 @@ impl Node { } /// Lists the local endpoint of this node. - pub fn local_endpoints(&self) -> LocalEndpointsStream { - self.inner.endpoint.local_endpoints() + pub fn local_endpoints(&self) -> DirectAddrsStream { + self.inner.endpoint.direct_addresses() } /// Convenience method to get just the addr part of [`Node::local_endpoints`]. @@ -185,7 +187,7 @@ impl NodeInner { async fn local_endpoint_addresses(&self) -> Result> { let endpoints = self .endpoint - .local_endpoints() + .direct_addresses() .next() .await .ok_or(anyhow!("no endpoints found"))?; diff --git a/iroh/src/node/builder.rs b/iroh/src/node/builder.rs index c2cc104070..69a9a451b4 100644 --- a/iroh/src/node/builder.rs +++ b/iroh/src/node/builder.rs @@ -522,10 +522,10 @@ where // spawn a task that updates the gossip endpoints. // TODO: track task - let mut stream = endpoint.local_endpoints(); + let mut stream = endpoint.direct_addresses(); tokio::task::spawn(async move { while let Some(eps) = stream.next().await { - if let Err(err) = gossip.update_endpoints(&eps) { + if let Err(err) = gossip.update_direct_addresses(&eps) { warn!("Failed to update gossip endpoints: {err:?}"); } } @@ -534,7 +534,7 @@ where // Wait for a single endpoint update, to make sure // we found some endpoints - tokio::time::timeout(ENDPOINT_WAIT, endpoint.local_endpoints().next()) + tokio::time::timeout(ENDPOINT_WAIT, endpoint.direct_addresses().next()) .await .context("waiting for endpoint")? .context("no endpoints")?; @@ -564,9 +564,9 @@ where // forward our initial endpoints to the gossip protocol // it may happen the the first endpoint update callback is missed because the gossip cell // is only initialized once the endpoint is fully bound - if let Some(local_endpoints) = server.local_endpoints().next().await { + if let Some(local_endpoints) = server.direct_addresses().next().await { debug!(me = ?server.node_id(), "gossip initial update: {local_endpoints:?}"); - gossip.update_endpoints(&local_endpoints).ok(); + gossip.update_direct_addresses(&local_endpoints).ok(); } loop { tokio::select! {