From fad3e24b3f2698ce6c1fa3fdad54201bec668298 Mon Sep 17 00:00:00 2001 From: Divma <26765164+divagant-martian@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:19:01 -0500 Subject: [PATCH] refactor(iroh-net): portmapper and network monitor are crates (#2855) ## Description The non-controversial part of the big network-utils refactor as described in the title. ## Breaking Changes
Full cargo public-api diff report ``` Removed items from the public API ================================= -pub struct iroh_net::metrics::PortmapMetrics -pub iroh_net::metrics::PortmapMetrics::external_address_updated: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::local_port_updates: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::mapping_attempts: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::mapping_failures: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::pcp_available: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::pcp_probes: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::probes_started: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::upnp_available: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::upnp_gateway_updated: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::upnp_probes: iroh_metrics::core::Counter -pub iroh_net::metrics::PortmapMetrics::upnp_probes_failed: iroh_metrics::core::Counter -impl core::default::Default for iroh_net::metrics::PortmapMetrics -impl core::default::Default for iroh_net::metrics::PortmapMetrics -pub fn iroh_net::metrics::PortmapMetrics::default() -> Self -pub fn iroh_net::metrics::PortmapMetrics::default() -> Self -impl iroh_metrics::core::Metric for iroh_net::metrics::PortmapMetrics -impl iroh_metrics::core::Metric for iroh_net::metrics::PortmapMetrics -pub fn iroh_net::metrics::PortmapMetrics::name() -> &'static str -pub fn iroh_net::metrics::PortmapMetrics::name() -> &'static str -impl struct_iterable_internal::Iterable for iroh_net::metrics::PortmapMetrics -impl struct_iterable_internal::Iterable for iroh_net::metrics::PortmapMetrics -pub fn iroh_net::metrics::PortmapMetrics::iter<'a>(&'a self) -> alloc::vec::into_iter::IntoIter<(&'static str, &'a dyn core::any::Any)> -pub fn iroh_net::metrics::PortmapMetrics::iter<'a>(&'a self) -> alloc::vec::into_iter::IntoIter<(&'static str, &'a dyn core::any::Any)> -pub mod iroh_net::net -pub mod iroh_net::net::ip -pub struct iroh_net::net::ip::LocalAddresses -pub iroh_net::net::ip::LocalAddresses::loopback: alloc::vec::Vec -pub iroh_net::net::ip::LocalAddresses::regular: alloc::vec::Vec -impl iroh_net::net::ip::LocalAddresses -pub fn iroh_net::net::ip::LocalAddresses::new() -> Self -impl core::default::Default for iroh_net::net::ip::LocalAddresses -pub fn iroh_net::net::ip::LocalAddresses::default() -> Self -pub const fn iroh_net::net::ip::is_unicast_link_local(addr: core::net::ip_addr::Ipv6Addr) -> bool -pub mod iroh_net::net::netmon -pub struct iroh_net::net::netmon::CallbackToken(_) -pub struct iroh_net::net::netmon::Monitor -impl iroh_net::net::netmon::Monitor -pub async fn iroh_net::net::netmon::Monitor::network_change(&self) -> anyhow::Result<()> -pub async fn iroh_net::net::netmon::Monitor::new() -> anyhow::Result -pub async fn iroh_net::net::netmon::Monitor::subscribe(&self, callback: F) -> anyhow::Result where F: core::ops::function::Fn(bool) -> futures_lite::future::Boxed<()> + 'static + core::marker::Sync + core::marker::Send -pub async fn iroh_net::net::netmon::Monitor::unsubscribe(&self, token: iroh_net::net::netmon::CallbackToken) -> anyhow::Result<()> -impl core::ops::drop::Drop for iroh_net::net::netmon::Monitor -pub fn iroh_net::net::netmon::Monitor::drop(&mut self) -pub enum iroh_net::net::IpFamily -pub iroh_net::net::IpFamily::V4 -pub iroh_net::net::IpFamily::V6 -impl iroh_net::net::IpFamily -pub fn iroh_net::net::IpFamily::local_addr(&self) -> core::net::ip_addr::IpAddr -pub fn iroh_net::net::IpFamily::unspecified_addr(&self) -> core::net::ip_addr::IpAddr -impl core::convert::From for iroh_net::net::IpFamily -pub fn iroh_net::net::IpFamily::from(value: core::net::ip_addr::IpAddr) -> Self -impl core::convert::From for socket2::Domain -pub fn socket2::Domain::from(value: iroh_net::net::IpFamily) -> Self -pub struct iroh_net::net::UdpSocket(_) -impl iroh_net::net::UdpSocket -pub fn iroh_net::net::UdpSocket::bind(network: iroh_net::net::IpFamily, port: u16) -> anyhow::Result -pub fn iroh_net::net::UdpSocket::bind_full(addr: impl core::convert::Into) -> anyhow::Result -pub fn iroh_net::net::UdpSocket::bind_local(network: iroh_net::net::IpFamily, port: u16) -> anyhow::Result -pub fn iroh_net::net::UdpSocket::bind_local_v4(port: u16) -> anyhow::Result -pub fn iroh_net::net::UdpSocket::bind_local_v6(port: u16) -> anyhow::Result -pub fn iroh_net::net::UdpSocket::bind_v4(port: u16) -> anyhow::Result -pub fn iroh_net::net::UdpSocket::bind_v6(port: u16) -> anyhow::Result -impl core::convert::From for iroh_net::net::UdpSocket -pub fn iroh_net::net::UdpSocket::from(socket: tokio::net::udp::UdpSocket) -> Self -impl core::ops::deref::Deref for iroh_net::net::UdpSocket -pub type iroh_net::net::UdpSocket::Target = tokio::net::udp::UdpSocket -pub fn iroh_net::net::UdpSocket::deref(&self) -> &Self::Target -impl core::ops::drop::Drop for iroh_net::net::UdpSocket -pub fn iroh_net::net::UdpSocket::drop(&mut self) -impl std::os::fd::owned::AsFd for iroh_net::net::UdpSocket -pub fn iroh_net::net::UdpSocket::as_fd(&self) -> std::os::fd::owned::BorrowedFd<'_> -pub mod iroh_net::portmapper -pub struct iroh_net::portmapper::Client -impl iroh_net::portmapper::Client -pub fn iroh_net::portmapper::Client::deactivate(&self) -pub fn iroh_net::portmapper::Client::new(config: iroh_net::portmapper::Config) -> Self -pub fn iroh_net::portmapper::Client::probe(&self) -> tokio::sync::oneshot::Receiver> -pub fn iroh_net::portmapper::Client::procure_mapping(&self) -pub fn iroh_net::portmapper::Client::update_local_port(&self, local_port: core::num::nonzero::NonZeroU16) -pub fn iroh_net::portmapper::Client::watch_external_address(&self) -> tokio::sync::watch::Receiver> -impl core::default::Default for iroh_net::portmapper::Client -pub fn iroh_net::portmapper::Client::default() -> Self -pub struct iroh_net::portmapper::Config -pub iroh_net::portmapper::Config::enable_nat_pmp: bool -pub iroh_net::portmapper::Config::enable_pcp: bool -pub iroh_net::portmapper::Config::enable_upnp: bool -impl core::default::Default for iroh_net::portmapper::Config -pub fn iroh_net::portmapper::Config::default() -> Self -pub struct iroh_net::portmapper::Metrics -pub iroh_net::portmapper::Metrics::external_address_updated: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::local_port_updates: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::mapping_attempts: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::mapping_failures: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::pcp_available: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::pcp_probes: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::probes_started: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::upnp_available: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::upnp_gateway_updated: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::upnp_probes: iroh_metrics::core::Counter -pub iroh_net::portmapper::Metrics::upnp_probes_failed: iroh_metrics::core::Counter -pub struct iroh_net::portmapper::ProbeOutput -pub iroh_net::portmapper::ProbeOutput::nat_pmp: bool -pub iroh_net::portmapper::ProbeOutput::pcp: bool -pub iroh_net::portmapper::ProbeOutput::upnp: bool -impl iroh_net::portmapper::ProbeOutput -pub fn iroh_net::portmapper::ProbeOutput::all_available(&self) -> bool -pub struct iroh_net::portmapper::Service Changed items in the public API =============================== -pub async fn iroh_net::netcheck::Client::get_report(&mut self, dm: iroh_net::relay::RelayMap, stun_conn4: core::option::Option>, stun_conn6: core::option::Option>) -> anyhow::Result> +pub async fn iroh_net::netcheck::Client::get_report(&mut self, dm: iroh_net::relay::RelayMap, stun_conn4: core::option::Option>, stun_conn6: core::option::Option>) -> anyhow::Result> -pub async fn iroh_net::netcheck::Client::get_report_channel(&mut self, dm: iroh_net::relay::RelayMap, stun_conn4: core::option::Option>, stun_conn6: core::option::Option>) -> anyhow::Result>>> +pub async fn iroh_net::netcheck::Client::get_report_channel(&mut self, dm: iroh_net::relay::RelayMap, stun_conn4: core::option::Option>, stun_conn6: core::option::Option>) -> anyhow::Result>>> -pub fn iroh_net::netcheck::Client::new(port_mapper: core::option::Option, dns_resolver: iroh_net::dns::DnsResolver) -> anyhow::Result +pub fn iroh_net::netcheck::Client::new(port_mapper: core::option::Option, dns_resolver: hickory_resolver::async_resolver::TokioAsyncResolver) -> anyhow::Result -pub iroh_net::netcheck::Report::portmap_probe: core::option::Option +pub iroh_net::netcheck::Report::portmap_probe: core::option::Option Added items to the public API ============================= +pub use iroh_net::metrics::PortmapMetrics ```
- `iroh_net::net` is removed. The unchanged functionality can be found in the published crate `netwatch` which the `n0 team` will keep maintaining. This has been moved to allow serving a more general public. - `iroh_net::portmapper` is removed. The unchanged functionality can be found in the published crate `portmapper` which the `n0 team` will keep maintaining. This has been moved to allow serving a more general public. - The origin of `iroh_net::metrics::PortmapMetrics` is changed. The type is no longer internal to `iroh-net` but re-exported from `portmapper` - Some `netcheck` functions now require `netwatcher` and `portmapper` parameters that are _not_ currently re-exported. `netcheck` will be moved to it's own crate as well so this intermediate state is acceptable for the time being ## Notes & open questions - **what used to be called net** has a very small API that might not be as useful to the general public. It only gives a `subscribe` to changes that only reports if the change "is major" without any description of what being major is or the origin of the change. Providing a more detailed API and better docs is something we can do later, but it's worth noticing. - **portmapper** might be good to start doing the move to `thiserror` here since it's very clearly isolated. Maybe turn on/off as features specific protocols. All this, again, to make it more attractive/useful to a wider audience. - there is `netwatcher`, which I found by looking for what I thought was a natural name to this (`netwatch`) and it has the API I'd imagine this crate should provide. https://crates.io/crates/netwatcher ## Change checklist - [x] Self-review. - [x] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [x] Tests if relevant. - [x] All breaking changes documented. --- .github/workflows/ci.yml | 2 +- .github/workflows/tests.yaml | 2 +- Cargo.lock | 62 ++++++++++++++++++- Cargo.toml | 2 + iroh-cli/Cargo.toml | 1 + iroh-cli/src/commands/doctor.rs | 2 +- iroh-net/Cargo.toml | 9 +-- iroh-net/src/defaults.rs | 9 --- iroh-net/src/lib.rs | 2 - iroh-net/src/magicsock.rs | 4 +- iroh-net/src/magicsock/node_map/node_state.rs | 2 +- iroh-net/src/magicsock/udp_conn.rs | 6 +- iroh-net/src/metrics.rs | 7 +-- iroh-net/src/netcheck.rs | 10 ++- iroh-net/src/netcheck/reportgen.rs | 3 +- iroh-net/src/netcheck/reportgen/hairpin.rs | 2 +- iroh-net/src/netcheck/reportgen/probes.rs | 2 +- net-tools/netwatch/Cargo.toml | 49 +++++++++++++++ net-tools/netwatch/README.md | 24 +++++++ .../netwatch/src}/interfaces.rs | 40 ++++-------- .../netwatch/src}/interfaces/bsd.rs | 0 .../netwatch/src}/interfaces/bsd/freebsd.rs | 0 .../netwatch/src}/interfaces/bsd/macos.rs | 0 .../netwatch/src}/interfaces/bsd/netbsd.rs | 0 .../netwatch/src}/interfaces/bsd/openbsd.rs | 0 .../netwatch/src}/interfaces/linux.rs | 0 .../netwatch/src}/interfaces/windows.rs | 0 .../src/net => net-tools/netwatch/src}/ip.rs | 0 .../netwatch/src}/ip_family.rs | 0 .../net.rs => net-tools/netwatch/src/lib.rs | 2 +- .../net => net-tools/netwatch/src}/netmon.rs | 0 .../netwatch/src}/netmon/actor.rs | 2 +- .../netwatch/src}/netmon/android.rs | 0 .../netwatch/src}/netmon/bsd.rs | 4 +- .../netwatch/src}/netmon/linux.rs | 2 +- .../netwatch/src}/netmon/windows.rs | 0 .../src/net => net-tools/netwatch/src}/udp.rs | 0 net-tools/portmapper/Cargo.toml | 49 +++++++++++++++ net-tools/portmapper/README.md | 24 +++++++ .../portmapper/src}/current_mapping.rs | 0 .../portmapper/src/lib.rs | 16 ++++- .../portmapper/src}/mapping.rs | 0 .../portmapper/src}/metrics.rs | 0 .../portmapper/src}/nat_pmp.rs | 3 +- .../portmapper/src}/nat_pmp/protocol.rs | 0 .../src}/nat_pmp/protocol/request.rs | 0 .../src}/nat_pmp/protocol/response.rs | 0 .../portmapper/src}/pcp.rs | 3 +- .../portmapper/src}/pcp/protocol.rs | 0 .../src}/pcp/protocol/opcode_data.rs | 0 .../portmapper/src}/pcp/protocol/request.rs | 0 .../portmapper/src}/pcp/protocol/response.rs | 0 .../portmapper/src}/upnp.rs | 2 +- net-tools/portmapper/src/util.rs | 30 +++++++++ 54 files changed, 301 insertions(+), 76 deletions(-) create mode 100644 net-tools/netwatch/Cargo.toml create mode 100644 net-tools/netwatch/README.md rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces.rs (90%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/bsd.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/bsd/freebsd.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/bsd/macos.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/bsd/netbsd.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/bsd/openbsd.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/linux.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/interfaces/windows.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/ip.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/ip_family.rs (100%) rename iroh-net/src/net.rs => net-tools/netwatch/src/lib.rs (83%) rename {iroh-net/src/net => net-tools/netwatch/src}/netmon.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/netmon/actor.rs (99%) rename {iroh-net/src/net => net-tools/netwatch/src}/netmon/android.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/netmon/bsd.rs (96%) rename {iroh-net/src/net => net-tools/netwatch/src}/netmon/linux.rs (99%) rename {iroh-net/src/net => net-tools/netwatch/src}/netmon/windows.rs (100%) rename {iroh-net/src/net => net-tools/netwatch/src}/udp.rs (100%) create mode 100644 net-tools/portmapper/Cargo.toml create mode 100644 net-tools/portmapper/README.md rename {iroh-net/src/portmapper => net-tools/portmapper/src}/current_mapping.rs (100%) rename iroh-net/src/portmapper.rs => net-tools/portmapper/src/lib.rs (98%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/mapping.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/metrics.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/nat_pmp.rs (98%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/nat_pmp/protocol.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/nat_pmp/protocol/request.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/nat_pmp/protocol/response.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/pcp.rs (98%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/pcp/protocol.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/pcp/protocol/opcode_data.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/pcp/protocol/request.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/pcp/protocol/response.rs (100%) rename {iroh-net/src/portmapper => net-tools/portmapper/src}/upnp.rs (98%) create mode 100644 net-tools/portmapper/src/util.rs 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, + } + } +}