diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dafda9cb78..ca06beeb06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,7 +190,7 @@ jobs: # uses: obi1kenobi/cargo-semver-checks-action@v2 uses: n0-computer/cargo-semver-checks-action@feat-baseline with: - package: iroh, iroh-base, iroh-cli, iroh-dns-server, iroh-metrics, iroh-net, iroh-net-bench, iroh-router + package: iroh, iroh-base, iroh-cli, iroh-dns-server, iroh-metrics, iroh-net, iroh-net-bench, iroh-router, netwatch, portmapper baseline-rev: ${{ env.HEAD_COMMIT_SHA }} use-cache: false diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index aa52c43b06..7ebd2ab2ce 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -23,7 +23,7 @@ env: RUSTFLAGS: -Dwarnings RUSTDOCFLAGS: -Dwarnings SCCACHE_CACHE_SIZE: "50G" - CRATES_LIST: "iroh,iroh-metrics,iroh-net,iroh-net-bench,iroh-test,iroh-cli,iroh-dns-server,iroh-router" + CRATES_LIST: "iroh,iroh-metrics,iroh-net,iroh-net-bench,iroh-test,iroh-cli,iroh-dns-server,iroh-router,netwatch,portmapper" IROH_FORCE_STAGING_RELAYS: "1" jobs: diff --git a/Cargo.lock b/Cargo.lock index 318b8ce138..66342bd5e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,6 +2618,7 @@ dependencies = [ "parking_lot", "pkarr", "portable-atomic", + "portmapper", "postcard", "quic-rpc", "rand", @@ -2837,12 +2838,14 @@ dependencies = [ "netlink-packet-core", "netlink-packet-route", "netlink-sys", + "netwatch", "ntest", "num_enum", "once_cell", "parking_lot", "pin-project", "pkarr", + "portmapper", "postcard", "pretty_assertions", "proptest", @@ -2864,7 +2867,6 @@ dependencies = [ "stun-rs", "surge-ping", "swarm-discovery", - "testdir", "testresult", "thiserror", "time", @@ -3365,6 +3367,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "netwatch" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "derive_more", + "futures-lite 2.3.0", + "futures-sink", + "futures-util", + "iroh-test", + "libc", + "netdev", + "netlink-packet-core", + "netlink-packet-route", + "netlink-sys", + "once_cell", + "rtnetlink", + "serde", + "socket2", + "thiserror", + "time", + "tokio", + "tracing", + "tracing-subscriber", + "windows 0.51.1", + "wmi", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -3971,6 +4002,35 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +[[package]] +name = "portmapper" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bytes", + "derive_more", + "futures-lite 2.3.0", + "futures-util", + "igd-next", + "iroh-metrics", + "libc", + "netwatch", + "ntest", + "num_enum", + "rand", + "rand_chacha", + "serde", + "smallvec", + "socket2", + "thiserror", + "time", + "tokio", + "tokio-util", + "tracing", + "url", +] + [[package]] name = "positioned-io" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 1cb098e60b..c069d76c1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ members = [ "iroh-net/bench", "iroh-cli", "iroh-router", + "net-tools/netwatch", + "net-tools/portmapper", ] resolver = "2" diff --git a/iroh-cli/Cargo.toml b/iroh-cli/Cargo.toml index 2fe907240f..71071696fc 100644 --- a/iroh-cli/Cargo.toml +++ b/iroh-cli/Cargo.toml @@ -46,6 +46,7 @@ iroh-metrics = { version = "0.27.0" } parking_lot = "0.12.1" pkarr = { version = "2.2.0", default-features = false } portable-atomic = "1" +portmapper = { version = "0.1.0", path = "../net-tools/portmapper" } postcard = "1.0.8" quic-rpc = { version = "0.12", features = ["flume-transport", "quinn-transport"] } rand = "0.8.5" diff --git a/iroh-cli/src/commands/doctor.rs b/iroh-cli/src/commands/doctor.rs index 27a2279d58..c01529c553 100644 --- a/iroh-cli/src/commands/doctor.rs +++ b/iroh-cli/src/commands/doctor.rs @@ -37,7 +37,7 @@ use iroh::{ endpoint::{self, Connection, ConnectionTypeStream, RecvStream, RemoteInfo, SendStream}, key::{PublicKey, SecretKey}, metrics::MagicsockMetrics, - netcheck, portmapper, + netcheck, relay::{RelayMap, RelayMode, RelayUrl}, ticket::NodeTicket, Endpoint, NodeAddr, NodeId, diff --git a/iroh-net/Cargo.toml b/iroh-net/Cargo.toml index 1e88697de1..af992ab9b8 100644 --- a/iroh-net/Cargo.toml +++ b/iroh-net/Cargo.toml @@ -17,10 +17,9 @@ workspace = true [dependencies] anyhow = { version = "1" } -base64 = "0.22.1" backoff = "0.4.0" +base64 = "0.22.1" bytes = "1.7" -netdev = "0.30.0" der = { version = "0.7", features = ["alloc", "derive"] } derive_more = { version = "1.0.0", features = ["debug", "display", "from", "try_into", "deref"] } futures-buffered = "0.2.8" @@ -40,11 +39,14 @@ hyper-util = "0.1.1" igd-next = { version = "0.15.1", features = ["aio_tokio"] } iroh-base = { version = "0.27.0", features = ["key"] } libc = "0.2.139" +netdev = "0.30.0" +netwatch = { version = "0.1.0", path = "../net-tools/netwatch" } num_enum = "0.7" once_cell = "1.18.0" parking_lot = "0.12.1" pin-project = "1" pkarr = { version = "2", default-features = false, features = ["async", "relay"] } +portmapper = { path = "../net-tools/portmapper" } postcard = { version = "1", default-features = false, features = ["alloc", "use-std", "experimental-derive"] } quinn = { package = "iroh-quinn", version = "0.11" } quinn-proto = { package = "iroh-quinn-proto", version = "0.11" } @@ -63,10 +65,10 @@ thiserror = "1" time = "0.3.20" tokio = { version = "1", features = ["io-util", "macros", "sync", "rt", "net", "fs", "io-std", "signal", "process"] } tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] } +tokio-stream = { version = "0.1.15" } tokio-tungstenite = "0.21" tokio-tungstenite-wasm = "0.3" tokio-util = { version = "0.7.12", features = ["io-util", "io", "codec", "rt"] } -tokio-stream = { version = "0.1.15" } tracing = "0.1" tungstenite = "0.21" url = { version = "2.4", features = ["serde"] } @@ -114,7 +116,6 @@ ntest = "0.9" pretty_assertions = "1.4" proptest = "1.2.0" rand_chacha = "0.3.1" -testdir = "0.9.1" tokio = { version = "1", features = ["io-util", "sync", "rt", "net", "fs", "macros", "time", "test-util"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } iroh-test = "0.27.0" diff --git a/iroh-net/src/defaults.rs b/iroh-net/src/defaults.rs index 95ab102953..fee45ea305 100644 --- a/iroh-net/src/defaults.rs +++ b/iroh-net/src/defaults.rs @@ -162,18 +162,9 @@ pub(crate) mod timeouts { /// The amount of time we wait for a hairpinned packet to come back. pub(crate) const HAIRPIN_CHECK_TIMEOUT: Duration = Duration::from_millis(100); - /// Maximum duration a UPnP search can take before timing out. - pub(crate) const UPNP_SEARCH_TIMEOUT: Duration = Duration::from_secs(1); - - /// Timeout to receive a response from a PCP server. - pub(crate) const PCP_RECV_TIMEOUT: Duration = Duration::from_millis(500); - /// Default Pinger timeout pub(crate) const DEFAULT_PINGER_TIMEOUT: Duration = Duration::from_secs(5); - /// Timeout to receive a response from a NAT-PMP server. - pub(crate) const NAT_PMP_RECV_TIMEOUT: Duration = Duration::from_millis(500); - /// Timeouts specifically used in the iroh-relay pub(crate) mod relay { use super::*; diff --git a/iroh-net/src/lib.rs b/iroh-net/src/lib.rs index 759eaefa65..bda876ccad 100644 --- a/iroh-net/src/lib.rs +++ b/iroh-net/src/lib.rs @@ -241,10 +241,8 @@ pub mod dns; pub mod endpoint; mod magicsock; pub mod metrics; -pub mod net; pub mod netcheck; pub mod ping; -pub mod portmapper; pub mod relay; pub mod stun; pub mod ticket; diff --git a/iroh-net/src/magicsock.rs b/iroh-net/src/magicsock.rs index 1a9341e18f..55c009e0e4 100644 --- a/iroh-net/src/magicsock.rs +++ b/iroh-net/src/magicsock.rs @@ -35,6 +35,7 @@ use futures_lite::{FutureExt, Stream, StreamExt}; use futures_util::stream::BoxStream; use iroh_base::key::NodeId; use iroh_metrics::{inc, inc_by}; +use netwatch::{interfaces, ip::LocalAddresses, netmon}; use quinn::AsyncUdpSocket; use rand::{seq::SliceRandom, Rng, SeedableRng}; use smallvec::{smallvec, SmallVec}; @@ -64,8 +65,7 @@ use crate::{ dns::DnsResolver, endpoint::NodeAddr, key::{PublicKey, SecretKey, SharedSecret}, - net::{interfaces, ip::LocalAddresses, netmon}, - netcheck, portmapper, + netcheck, relay::{RelayMap, RelayUrl}, stun, AddrInfo, }; diff --git a/iroh-net/src/magicsock/node_map/node_state.rs b/iroh-net/src/magicsock/node_map/node_state.rs index 3a96e49ff1..5d72f47e5c 100644 --- a/iroh-net/src/magicsock/node_map/node_state.rs +++ b/iroh-net/src/magicsock/node_map/node_state.rs @@ -6,6 +6,7 @@ use std::{ }; use iroh_metrics::inc; +use netwatch::ip::is_unicast_link_local; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; use tracing::{debug, event, info, instrument, trace, warn, Level}; @@ -22,7 +23,6 @@ use crate::{ endpoint::AddrInfo, key::PublicKey, magicsock::{ActorMessage, MagicsockMetrics, QuicMappedAddr, Timer, HEARTBEAT_INTERVAL}, - net::ip::is_unicast_link_local, relay::RelayUrl, stun, util::relay_only_mode, diff --git a/iroh-net/src/magicsock/udp_conn.rs b/iroh-net/src/magicsock/udp_conn.rs index db376521c5..c176ae8144 100644 --- a/iroh-net/src/magicsock/udp_conn.rs +++ b/iroh-net/src/magicsock/udp_conn.rs @@ -9,13 +9,12 @@ use std::{ }; use anyhow::{bail, Context as _}; +use netwatch::UdpSocket; use quinn::AsyncUdpSocket; use quinn_udp::{Transmit, UdpSockRef}; use tokio::io::Interest; use tracing::{debug, trace}; -use crate::net::UdpSocket; - /// A UDP socket implementing Quinn's [`AsyncUdpSocket`]. #[derive(Clone, Debug)] pub struct UdpConn { @@ -197,11 +196,12 @@ where #[cfg(test)] mod tests { use anyhow::Result; + use netwatch::IpFamily; use tokio::sync::mpsc; use tracing::{info_span, Instrument}; use super::*; - use crate::{key, net::IpFamily, tls}; + use crate::{key, tls}; const ALPN: &[u8] = b"n0/test/1"; diff --git a/iroh-net/src/metrics.rs b/iroh-net/src/metrics.rs index 90ebbae09e..db9e77c015 100644 --- a/iroh-net/src/metrics.rs +++ b/iroh-net/src/metrics.rs @@ -1,8 +1,7 @@ //! Co-locating all of the iroh-net metrics structs +pub use portmapper::Metrics as PortmapMetrics; + #[cfg(feature = "iroh-relay")] #[cfg_attr(iroh_docsrs, doc(cfg(feature = "iroh-relay")))] pub use crate::relay::server::Metrics as RelayMetrics; -pub use crate::{ - magicsock::Metrics as MagicsockMetrics, netcheck::Metrics as NetcheckMetrics, - portmapper::Metrics as PortmapMetrics, -}; +pub use crate::{magicsock::Metrics as MagicsockMetrics, netcheck::Metrics as NetcheckMetrics}; diff --git a/iroh-net/src/netcheck.rs b/iroh-net/src/netcheck.rs index 5c5f904a5e..5e8d54deeb 100644 --- a/iroh-net/src/netcheck.rs +++ b/iroh-net/src/netcheck.rs @@ -15,7 +15,9 @@ use std::{ use anyhow::{anyhow, Context as _, Result}; use bytes::Bytes; +use hickory_resolver::TokioAsyncResolver as DnsResolver; use iroh_metrics::inc; +use netwatch::{IpFamily, UdpSocket}; use tokio::{ sync::{self, mpsc, oneshot}, time::{Duration, Instant}, @@ -23,12 +25,8 @@ use tokio::{ use tokio_util::{sync::CancellationToken, task::AbortOnDropHandle}; use tracing::{debug, error, info_span, trace, warn, Instrument}; -use super::{portmapper, relay::RelayMap, stun}; -use crate::{ - dns::DnsResolver, - net::{IpFamily, UdpSocket}, - relay::RelayUrl, -}; +use super::{relay::RelayMap, stun}; +use crate::relay::RelayUrl; mod metrics; mod reportgen; diff --git a/iroh-net/src/netcheck/reportgen.rs b/iroh-net/src/netcheck/reportgen.rs index 007a04a410..c9cbb10610 100644 --- a/iroh-net/src/netcheck/reportgen.rs +++ b/iroh-net/src/netcheck/reportgen.rs @@ -26,6 +26,7 @@ use std::{ use anyhow::{anyhow, bail, Context, Result}; use iroh_metrics::inc; +use netwatch::{interfaces, UdpSocket}; use rand::seq::IteratorRandom; use tokio::{ sync::{mpsc, oneshot}, @@ -39,10 +40,8 @@ use super::NetcheckMetrics; use crate::{ defaults::DEFAULT_STUN_PORT, dns::{DnsResolver, ResolverExt}, - net::{interfaces, UdpSocket}, netcheck::{self, Report}, ping::{PingError, Pinger}, - portmapper, relay::{RelayMap, RelayNode, RelayUrl}, stun, util::MaybeFuture, diff --git a/iroh-net/src/netcheck/reportgen/hairpin.rs b/iroh-net/src/netcheck/reportgen/hairpin.rs index eba5b202eb..47f23e1a88 100644 --- a/iroh-net/src/netcheck/reportgen/hairpin.rs +++ b/iroh-net/src/netcheck/reportgen/hairpin.rs @@ -15,13 +15,13 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use anyhow::{bail, Context, Result}; +use netwatch::UdpSocket; use tokio::{sync::oneshot, time::Instant}; use tokio_util::task::AbortOnDropHandle; use tracing::{debug, error, info_span, trace, warn, Instrument}; use crate::{ defaults::timeouts::HAIRPIN_CHECK_TIMEOUT, - net::UdpSocket, netcheck::{self, reportgen, Inflight}, stun, }; diff --git a/iroh-net/src/netcheck/reportgen/probes.rs b/iroh-net/src/netcheck/reportgen/probes.rs index 5bf62deec9..c8bfca845a 100644 --- a/iroh-net/src/netcheck/reportgen/probes.rs +++ b/iroh-net/src/netcheck/reportgen/probes.rs @@ -7,10 +7,10 @@ use std::{collections::BTreeSet, fmt, sync::Arc}; use anyhow::{ensure, Result}; +use netwatch::interfaces; use tokio::time::Duration; use crate::{ - net::interfaces, netcheck::Report, relay::{RelayMap, RelayNode, RelayUrl}, }; diff --git a/net-tools/netwatch/Cargo.toml b/net-tools/netwatch/Cargo.toml new file mode 100644 index 0000000000..3628883cfa --- /dev/null +++ b/net-tools/netwatch/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "netwatch" +version = "0.1.0" +readme = "README.md" +description = "Cross-platform monitoring for network interface changes" +license = "MIT OR Apache-2.0" +authors = ["n0 team"] +repository = "https://github.com/n0-computer/iroh" +keywords = ["networking", "interfaces"] +edition = "2021" + +[lints] +workspace = true + +[dependencies] +anyhow = { version = "1" } +bytes = "1.7" +futures-lite = "2.3" +futures-sink = "0.3.25" +futures-util = "0.3.25" +libc = "0.2.139" +netdev = "0.30.0" +once_cell = "1.18.0" +socket2 = "0.5.3" +thiserror = "1" +time = "0.3.20" +tokio = { version = "1", features = ["io-util", "macros", "sync", "rt", "net", "fs", "io-std", "signal", "process", "time"] } +tracing = "0.1" + +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +netlink-packet-core = "0.7.0" +netlink-packet-route = "0.17.0" +netlink-sys = "0.8.5" +rtnetlink = "0.13.0" + +[target.'cfg(target_os = "windows")'.dependencies] +wmi = "0.13" +windows = { version = "0.51", features = ["Win32_NetworkManagement_IpHelper", "Win32_Foundation", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] } +serde = { version = "1", features = ["derive"] } +derive_more = { version = "1.0.0", features = ["debug"] } + +[dev-dependencies] +tokio = { version = "1", features = ["io-util", "sync", "rt", "net", "fs", "macros", "time", "test-util"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +iroh-test = "0.27.0" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "iroh_docsrs"] diff --git a/net-tools/netwatch/README.md b/net-tools/netwatch/README.md new file mode 100644 index 0000000000..e0c8f39b05 --- /dev/null +++ b/net-tools/netwatch/README.md @@ -0,0 +1,24 @@ +# Netwatch + +`netwatch` is a cross-platform library for monitoring of networking interfaces +and route changes. + +Used in [iroh](https://github.com/n0-computer/iroh), created with love by the +[n0 team](https://n0.computer/). + +# License + +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this project by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. diff --git a/iroh-net/src/net/interfaces.rs b/net-tools/netwatch/src/interfaces.rs similarity index 90% rename from iroh-net/src/net/interfaces.rs rename to net-tools/netwatch/src/interfaces.rs index 759b0925f7..f9f511d879 100644 --- a/iroh-net/src/net/interfaces.rs +++ b/net-tools/netwatch/src/interfaces.rs @@ -29,11 +29,11 @@ use self::bsd::default_route; use self::linux::default_route; #[cfg(target_os = "windows")] use self::windows::default_route; -use crate::net::ip::{is_private_v6, is_up}; +use crate::ip::{is_private_v6, is_up}; /// Represents a network interface. #[derive(Debug)] -pub(crate) struct Interface { +pub struct Interface { iface: netdev::interface::Interface, } @@ -71,7 +71,7 @@ impl Interface { } /// A list of all ip addresses of this interface. - pub(crate) fn addrs(&self) -> impl Iterator + '_ { + pub fn addrs(&self) -> impl Iterator + '_ { self.iface .ipv4 .iter() @@ -82,14 +82,7 @@ impl Interface { /// Creates a fake interface for usage in tests. /// - /// Sometimes tests want to be deterministic, e.g. [`ProbePlan`] tests rely on the - /// interface state. This allows tests to be independent of the host interfaces. - /// - /// It is rather possible that we'll want more variations of this in the future, feel - /// free to add parameters or different alternative constructors. - /// - /// [`ProbePlan`]: crate::netcheck::reportgen::probes::ProbePlan - #[cfg(test)] + /// This allows tests to be independent of the host interfaces. pub(crate) fn fake() -> Self { use std::net::Ipv4Addr; @@ -126,7 +119,7 @@ impl Interface { /// Structure of an IP network, either IPv4 or IPv6. #[derive(Clone, Debug)] -pub(crate) enum IpNet { +pub enum IpNet { /// Structure of IPv4 Network. V4(Ipv4Net), /// Structure of IPv6 Network. @@ -161,16 +154,16 @@ impl IpNet { /// Intended to store the state of the machine's network interfaces, routing table, and /// other network configuration. For now it's pretty basic. #[derive(Debug, PartialEq, Eq)] -pub(crate) struct State { +pub struct State { /// Maps from an interface name interface. - pub(crate) interfaces: HashMap, + pub interfaces: HashMap, /// Whether this machine has an IPv6 Global or Unique Local Address /// which might provide connectivity. - pub(crate) have_v6: bool, + pub have_v6: bool, /// Whether the machine has some non-localhost, non-link-local IPv4 address. - pub(crate) have_v4: bool, + pub have_v4: bool, //// Whether the current network interface is considered "expensive", which currently means LTE/etc /// instead of Wifi. This field is not populated by `get_state`. @@ -255,15 +248,8 @@ impl State { /// Creates a fake interface state for usage in tests. /// - /// Sometimes tests want to be deterministic, e.g. [`ProbePlan`] tests rely on the - /// interface state. This allows tests to be independent of the host interfaces. - /// - /// It is rather possible that we'll want more variations of this in the future, feel - /// free to add parameters or different alternative constructors. - /// - /// [`ProbePlan`]: crate::netcheck::reportgen::probes::ProbePlan - #[cfg(test)] - pub(crate) fn fake() -> Self { + /// This allows tests to be independent of the host interfaces. + pub fn fake() -> Self { let fake = Interface::fake(); let ifname = fake.iface.name.clone(); Self { @@ -341,7 +327,7 @@ pub async fn default_route_interface() -> Option { /// Likely IPs of the residentla router, and the ip address of the current /// machine using it. #[derive(Debug, Clone)] -pub(crate) struct HomeRouter { +pub struct HomeRouter { /// Ip of the router. pub gateway: IpAddr, /// Our local Ip if known. @@ -354,7 +340,7 @@ impl HomeRouter { /// In addition, it returns the IP address of the current machine on /// the LAN using that gateway. /// This is used as the destination for UPnP, NAT-PMP, PCP, etc queries. - pub(crate) fn new() -> Option { + pub fn new() -> Option { let gateway = Self::get_default_gateway()?; let my_ip = netdev::interface::get_local_ipaddr(); diff --git a/iroh-net/src/net/interfaces/bsd.rs b/net-tools/netwatch/src/interfaces/bsd.rs similarity index 100% rename from iroh-net/src/net/interfaces/bsd.rs rename to net-tools/netwatch/src/interfaces/bsd.rs diff --git a/iroh-net/src/net/interfaces/bsd/freebsd.rs b/net-tools/netwatch/src/interfaces/bsd/freebsd.rs similarity index 100% rename from iroh-net/src/net/interfaces/bsd/freebsd.rs rename to net-tools/netwatch/src/interfaces/bsd/freebsd.rs diff --git a/iroh-net/src/net/interfaces/bsd/macos.rs b/net-tools/netwatch/src/interfaces/bsd/macos.rs similarity index 100% rename from iroh-net/src/net/interfaces/bsd/macos.rs rename to net-tools/netwatch/src/interfaces/bsd/macos.rs diff --git a/iroh-net/src/net/interfaces/bsd/netbsd.rs b/net-tools/netwatch/src/interfaces/bsd/netbsd.rs similarity index 100% rename from iroh-net/src/net/interfaces/bsd/netbsd.rs rename to net-tools/netwatch/src/interfaces/bsd/netbsd.rs diff --git a/iroh-net/src/net/interfaces/bsd/openbsd.rs b/net-tools/netwatch/src/interfaces/bsd/openbsd.rs similarity index 100% rename from iroh-net/src/net/interfaces/bsd/openbsd.rs rename to net-tools/netwatch/src/interfaces/bsd/openbsd.rs diff --git a/iroh-net/src/net/interfaces/linux.rs b/net-tools/netwatch/src/interfaces/linux.rs similarity index 100% rename from iroh-net/src/net/interfaces/linux.rs rename to net-tools/netwatch/src/interfaces/linux.rs diff --git a/iroh-net/src/net/interfaces/windows.rs b/net-tools/netwatch/src/interfaces/windows.rs similarity index 100% rename from iroh-net/src/net/interfaces/windows.rs rename to net-tools/netwatch/src/interfaces/windows.rs diff --git a/iroh-net/src/net/ip.rs b/net-tools/netwatch/src/ip.rs similarity index 100% rename from iroh-net/src/net/ip.rs rename to net-tools/netwatch/src/ip.rs diff --git a/iroh-net/src/net/ip_family.rs b/net-tools/netwatch/src/ip_family.rs similarity index 100% rename from iroh-net/src/net/ip_family.rs rename to net-tools/netwatch/src/ip_family.rs diff --git a/iroh-net/src/net.rs b/net-tools/netwatch/src/lib.rs similarity index 83% rename from iroh-net/src/net.rs rename to net-tools/netwatch/src/lib.rs index a010dc235c..213fe78e00 100644 --- a/iroh-net/src/net.rs +++ b/net-tools/netwatch/src/lib.rs @@ -1,6 +1,6 @@ //! Networking related utilities -pub(crate) mod interfaces; +pub mod interfaces; pub mod ip; mod ip_family; pub mod netmon; diff --git a/iroh-net/src/net/netmon.rs b/net-tools/netwatch/src/netmon.rs similarity index 100% rename from iroh-net/src/net/netmon.rs rename to net-tools/netwatch/src/netmon.rs diff --git a/iroh-net/src/net/netmon/actor.rs b/net-tools/netwatch/src/netmon/actor.rs similarity index 99% rename from iroh-net/src/net/netmon/actor.rs rename to net-tools/netwatch/src/netmon/actor.rs index f12f44c873..46df1888f4 100644 --- a/iroh-net/src/net/netmon/actor.rs +++ b/net-tools/netwatch/src/netmon/actor.rs @@ -24,7 +24,7 @@ use super::bsd as os; use super::linux as os; #[cfg(target_os = "windows")] use super::windows as os; -use crate::net::{ +use crate::{ interfaces::{IpNet, State}, ip::is_link_local, }; diff --git a/iroh-net/src/net/netmon/android.rs b/net-tools/netwatch/src/netmon/android.rs similarity index 100% rename from iroh-net/src/net/netmon/android.rs rename to net-tools/netwatch/src/netmon/android.rs diff --git a/iroh-net/src/net/netmon/bsd.rs b/net-tools/netwatch/src/netmon/bsd.rs similarity index 96% rename from iroh-net/src/net/netmon/bsd.rs rename to net-tools/netwatch/src/netmon/bsd.rs index 20bab5aae7..41190afcf5 100644 --- a/iroh-net/src/net/netmon/bsd.rs +++ b/net-tools/netwatch/src/netmon/bsd.rs @@ -6,8 +6,8 @@ use tracing::{trace, warn}; use super::actor::NetworkMessage; #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] -use crate::net::interfaces::bsd::{RTAX_DST, RTAX_IFP}; -use crate::net::{interfaces::bsd::WireMessage, ip::is_link_local}; +use crate::interfaces::bsd::{RTAX_DST, RTAX_IFP}; +use crate::{interfaces::bsd::WireMessage, ip::is_link_local}; #[derive(Debug)] pub(super) struct RouteMonitor { diff --git a/iroh-net/src/net/netmon/linux.rs b/net-tools/netwatch/src/netmon/linux.rs similarity index 99% rename from iroh-net/src/net/netmon/linux.rs rename to net-tools/netwatch/src/netmon/linux.rs index 2380ac69d4..95fd8e35eb 100644 --- a/iroh-net/src/net/netmon/linux.rs +++ b/net-tools/netwatch/src/netmon/linux.rs @@ -13,7 +13,7 @@ use tokio::{sync::mpsc, task::JoinHandle}; use tracing::{info, trace, warn}; use super::actor::NetworkMessage; -use crate::net::ip::is_link_local; +use crate::ip::is_link_local; #[derive(Debug)] pub(super) struct RouteMonitor { diff --git a/iroh-net/src/net/netmon/windows.rs b/net-tools/netwatch/src/netmon/windows.rs similarity index 100% rename from iroh-net/src/net/netmon/windows.rs rename to net-tools/netwatch/src/netmon/windows.rs diff --git a/iroh-net/src/net/udp.rs b/net-tools/netwatch/src/udp.rs similarity index 100% rename from iroh-net/src/net/udp.rs rename to net-tools/netwatch/src/udp.rs diff --git a/net-tools/portmapper/Cargo.toml b/net-tools/portmapper/Cargo.toml new file mode 100644 index 0000000000..e00626ca11 --- /dev/null +++ b/net-tools/portmapper/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "portmapper" +version = "0.1.0" +edition = "2021" +readme = "README.md" +description = "Portmapping utilities" +license = "MIT OR Apache-2.0" +authors = ["n0 team"] +repository = "https://github.com/n0-computer/iroh" +keywords = ["portmapping", "pmp", "pcp", "upnp"] + +[lints] +workspace = true + +[dependencies] +anyhow = { version = "1" } +base64 = "0.22.1" +bytes = "1.7" +derive_more = { version = "1.0.0", features = ["debug", "display", "from", "try_into", "deref"] } +futures-lite = "2.3" +futures-util = "0.3.25" +igd-next = { version = "0.15.1", features = ["aio_tokio"] } +iroh-metrics = { version = "0.27.0", default-features = false } +libc = "0.2.139" +netwatch = { version = "0.1.0", path = "../netwatch" } +num_enum = "0.7" +rand = "0.8" +serde = { version = "1", features = ["derive", "rc"] } +smallvec = "1.11.1" +socket2 = "0.5.3" +thiserror = "1" +time = "0.3.20" +tokio = { version = "1", features = ["io-util", "macros", "sync", "rt", "net", "fs", "io-std", "signal", "process"] } +tokio-util = { version = "0.7.12", features = ["io-util", "io", "codec", "rt"] } +tracing = "0.1" +url = { version = "2.4", features = ["serde"] } + +[dev-dependencies] +ntest = "0.9" +rand_chacha = "0.3.1" +tokio = { version = "1", features = ["io-util", "sync", "rt", "net", "fs", "macros", "time", "test-util"] } + +[features] +default = ["metrics"] +metrics = ["iroh-metrics/metrics"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "iroh_docsrs"] diff --git a/net-tools/portmapper/README.md b/net-tools/portmapper/README.md new file mode 100644 index 0000000000..7f819769cc --- /dev/null +++ b/net-tools/portmapper/README.md @@ -0,0 +1,24 @@ +# Portmapper + +`portmapper` is a library to ensure a mapping for a local port is maintained +despite network changes. Provides upnp, pcp and nat-pmp protocols support. + +Used in [iroh](https://github.com/n0-computer/iroh), created with love by the +[n0 team](https://n0.computer/). + +# License + +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this project by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. diff --git a/iroh-net/src/portmapper/current_mapping.rs b/net-tools/portmapper/src/current_mapping.rs similarity index 100% rename from iroh-net/src/portmapper/current_mapping.rs rename to net-tools/portmapper/src/current_mapping.rs diff --git a/iroh-net/src/portmapper.rs b/net-tools/portmapper/src/lib.rs similarity index 98% rename from iroh-net/src/portmapper.rs rename to net-tools/portmapper/src/lib.rs index 0b54840213..708d572098 100644 --- a/iroh-net/src/portmapper.rs +++ b/net-tools/portmapper/src/lib.rs @@ -10,18 +10,30 @@ use anyhow::{anyhow, Result}; use current_mapping::CurrentMapping; use futures_lite::StreamExt; use iroh_metrics::inc; +use netwatch::interfaces::HomeRouter; use tokio::sync::{mpsc, oneshot, watch}; use tokio_util::task::AbortOnDropHandle; use tracing::{debug, info_span, trace, Instrument}; -use crate::{net::interfaces::HomeRouter, util}; - mod current_mapping; mod mapping; mod metrics; mod nat_pmp; mod pcp; mod upnp; +mod util; +mod defaults { + use std::time::Duration; + + /// Maximum duration a UPnP search can take before timing out. + pub(crate) const UPNP_SEARCH_TIMEOUT: Duration = Duration::from_secs(1); + + /// Timeout to receive a response from a PCP server. + pub(crate) const PCP_RECV_TIMEOUT: Duration = Duration::from_millis(500); + + /// Timeout to receive a response from a NAT-PMP server. + pub(crate) const NAT_PMP_RECV_TIMEOUT: Duration = Duration::from_millis(500); +} pub use metrics::Metrics; diff --git a/iroh-net/src/portmapper/mapping.rs b/net-tools/portmapper/src/mapping.rs similarity index 100% rename from iroh-net/src/portmapper/mapping.rs rename to net-tools/portmapper/src/mapping.rs diff --git a/iroh-net/src/portmapper/metrics.rs b/net-tools/portmapper/src/metrics.rs similarity index 100% rename from iroh-net/src/portmapper/metrics.rs rename to net-tools/portmapper/src/metrics.rs diff --git a/iroh-net/src/portmapper/nat_pmp.rs b/net-tools/portmapper/src/nat_pmp.rs similarity index 98% rename from iroh-net/src/portmapper/nat_pmp.rs rename to net-tools/portmapper/src/nat_pmp.rs index 911b4ace7b..a44c4aeb7e 100644 --- a/iroh-net/src/portmapper/nat_pmp.rs +++ b/net-tools/portmapper/src/nat_pmp.rs @@ -2,10 +2,11 @@ use std::{net::Ipv4Addr, num::NonZeroU16, time::Duration}; +use netwatch::UdpSocket; use tracing::{debug, trace}; use self::protocol::{MapProtocol, Request, Response}; -use crate::{defaults::timeouts::NAT_PMP_RECV_TIMEOUT as RECV_TIMEOUT, net::UdpSocket}; +use crate::defaults::NAT_PMP_RECV_TIMEOUT as RECV_TIMEOUT; mod protocol; diff --git a/iroh-net/src/portmapper/nat_pmp/protocol.rs b/net-tools/portmapper/src/nat_pmp/protocol.rs similarity index 100% rename from iroh-net/src/portmapper/nat_pmp/protocol.rs rename to net-tools/portmapper/src/nat_pmp/protocol.rs diff --git a/iroh-net/src/portmapper/nat_pmp/protocol/request.rs b/net-tools/portmapper/src/nat_pmp/protocol/request.rs similarity index 100% rename from iroh-net/src/portmapper/nat_pmp/protocol/request.rs rename to net-tools/portmapper/src/nat_pmp/protocol/request.rs diff --git a/iroh-net/src/portmapper/nat_pmp/protocol/response.rs b/net-tools/portmapper/src/nat_pmp/protocol/response.rs similarity index 100% rename from iroh-net/src/portmapper/nat_pmp/protocol/response.rs rename to net-tools/portmapper/src/nat_pmp/protocol/response.rs diff --git a/iroh-net/src/portmapper/pcp.rs b/net-tools/portmapper/src/pcp.rs similarity index 98% rename from iroh-net/src/portmapper/pcp.rs rename to net-tools/portmapper/src/pcp.rs index f911a341e5..0f2fe789f5 100644 --- a/iroh-net/src/portmapper/pcp.rs +++ b/net-tools/portmapper/src/pcp.rs @@ -2,10 +2,11 @@ use std::{net::Ipv4Addr, num::NonZeroU16, time::Duration}; +use netwatch::UdpSocket; use rand::RngCore; use tracing::{debug, trace}; -use crate::{defaults::timeouts::PCP_RECV_TIMEOUT as RECV_TIMEOUT, net::UdpSocket}; +use crate::defaults::PCP_RECV_TIMEOUT as RECV_TIMEOUT; mod protocol; diff --git a/iroh-net/src/portmapper/pcp/protocol.rs b/net-tools/portmapper/src/pcp/protocol.rs similarity index 100% rename from iroh-net/src/portmapper/pcp/protocol.rs rename to net-tools/portmapper/src/pcp/protocol.rs diff --git a/iroh-net/src/portmapper/pcp/protocol/opcode_data.rs b/net-tools/portmapper/src/pcp/protocol/opcode_data.rs similarity index 100% rename from iroh-net/src/portmapper/pcp/protocol/opcode_data.rs rename to net-tools/portmapper/src/pcp/protocol/opcode_data.rs diff --git a/iroh-net/src/portmapper/pcp/protocol/request.rs b/net-tools/portmapper/src/pcp/protocol/request.rs similarity index 100% rename from iroh-net/src/portmapper/pcp/protocol/request.rs rename to net-tools/portmapper/src/pcp/protocol/request.rs diff --git a/iroh-net/src/portmapper/pcp/protocol/response.rs b/net-tools/portmapper/src/pcp/protocol/response.rs similarity index 100% rename from iroh-net/src/portmapper/pcp/protocol/response.rs rename to net-tools/portmapper/src/pcp/protocol/response.rs diff --git a/iroh-net/src/portmapper/upnp.rs b/net-tools/portmapper/src/upnp.rs similarity index 98% rename from iroh-net/src/portmapper/upnp.rs rename to net-tools/portmapper/src/upnp.rs index 6d202eed35..ebdac4ab7a 100644 --- a/iroh-net/src/portmapper/upnp.rs +++ b/net-tools/portmapper/src/upnp.rs @@ -13,7 +13,7 @@ use super::Metrics; pub type Gateway = aigd::Gateway; -use crate::defaults::timeouts::UPNP_SEARCH_TIMEOUT as SEARCH_TIMEOUT; +use crate::defaults::UPNP_SEARCH_TIMEOUT as SEARCH_TIMEOUT; /// Seconds we ask the router to maintain the port mapping. 0 means infinite. const PORT_MAPPING_LEASE_DURATION_SECONDS: u32 = 0; diff --git a/net-tools/portmapper/src/util.rs b/net-tools/portmapper/src/util.rs new file mode 100644 index 0000000000..5dccd45c00 --- /dev/null +++ b/net-tools/portmapper/src/util.rs @@ -0,0 +1,30 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +/// Resolves to pending if the inner is `None`. +#[derive(Debug)] +pub(crate) struct MaybeFuture { + /// Future to be polled. + pub inner: Option, +} + +// NOTE: explicit implementation to bypass derive unnecessary bounds +impl Default for MaybeFuture { + fn default() -> Self { + MaybeFuture { inner: None } + } +} + +impl Future for MaybeFuture { + type Output = T::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.inner { + Some(ref mut t) => Pin::new(t).poll(cx), + None => Poll::Pending, + } + } +}