Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iroh-net-report): Support wasm32 building & running #3139

Merged
merged 13 commits into from
Feb 14, 2025
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 15 additions & 7 deletions iroh-net-report/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,39 @@ workspace = true
anyhow = "1"
bytes = "1.7"
derive_more = { version = "1.0.0", features = ["display"] }
hickory-resolver = "=0.25.0-alpha.4"
iroh-base = { version = "0.32.0", path = "../iroh-base", default-features = false, features = ["relay"] }
iroh-metrics = { version = "0.31", default-features = false }
iroh-relay = { version = "0.32", path = "../iroh-relay" }
iroh-relay = { version = "0.32", default-features = false, path = "../iroh-relay" }
n0-future = "0.1.2"
netwatch = { version = "0.3" }
portmapper = { version = "0.3", default-features = false }
quinn = { package = "iroh-quinn", version = "0.13.0" }
quinn = { package = "iroh-quinn", version = "0.13.0", default-features = false }
rand = "0.8"
reqwest = { version = "0.12", default-features = false }
reqwest = { version = "0.12", default-features = false, features = ["stream"] }
rustls = { version = "0.23", default-features = false }
surge-ping = "0.8.0"
thiserror = "2"
tokio = { version = "1", default-features = false, features = ["sync", "time", "macros", "rt"] }
tokio-util = { version = "0.7.12", default-features = false }
tracing = "0.1"
url = { version = "2.4" }

# non-wasm-in-browser dependencies
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies]
hickory-resolver = "=0.25.0-alpha.4"
netwatch = { version = "0.3" }
portmapper = { version = "0.3", default-features = false }
surge-ping = "0.8.0"

[dev-dependencies]
futures-lite = "2.3"
iroh-relay = { path = "../iroh-relay", features = ["test-utils", "server"] }
pretty_assertions = "1.4"
quinn = { package = "iroh-quinn", version = "0.13.0" }
testresult = "0.4.0"
tokio = { version = "1", default-features = false, features = ["test-util"] }
tracing-test = "0.2.5"

[build-dependencies]
cfg_aliases = { version = "0.2" }

[features]
default = ["metrics"]
metrics = ["iroh-metrics/metrics", "portmapper/metrics"]
Expand Down
9 changes: 9 additions & 0 deletions iroh-net-report/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use cfg_aliases::cfg_aliases;

fn main() {
// Setup cfg aliases
cfg_aliases! {
// Convenience aliases
wasm_browser: { all(target_family = "wasm", target_os = "unknown") },
}
}
142 changes: 119 additions & 23 deletions iroh-net-report/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,54 @@ use bytes::Bytes;
use iroh_base::RelayUrl;
#[cfg(feature = "metrics")]
use iroh_metrics::inc;
use iroh_relay::{dns::DnsResolver, protos::stun, RelayMap};
#[cfg(not(wasm_browser))]
use iroh_relay::dns::DnsResolver;
use iroh_relay::{protos::stun, RelayMap};
use n0_future::{
task::{self, AbortOnDropHandle},
time::{Duration, Instant},
};
#[cfg(not(wasm_browser))]
use netwatch::UdpSocket;
use tokio::sync::{self, mpsc, oneshot};
use tracing::{debug, error, info_span, trace, warn, Instrument};

mod defaults;
#[cfg(not(wasm_browser))]
mod dns;
#[cfg(not(wasm_browser))]
mod ip_mapped_addrs;
mod metrics;
#[cfg(not(wasm_browser))]
mod ping;
mod reportgen;

/// We "vendor" what we need of the library in browsers for simplicity.
///
/// We could consider making `portmapper` compile to wasm in the future,
/// but what we need is so little it's likely not worth it.
#[cfg(wasm_browser)]
pub mod portmapper {
/// Output of a port mapping probe.
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
#[display("portmap={{ UPnP: {upnp}, PMP: {nat_pmp}, PCP: {pcp} }}")]
pub struct ProbeOutput {
/// If UPnP can be considered available.
pub upnp: bool,
/// If PCP can be considered available.
pub pcp: bool,
/// If PMP can be considered available.
pub nat_pmp: bool,
}
}

#[cfg(not(wasm_browser))]
pub use ip_mapped_addrs::{IpMappedAddr, IpMappedAddrError, IpMappedAddresses, MAPPED_ADDR_PORT};
pub use metrics::Metrics;
use reportgen::ProbeProto;
pub use reportgen::QuicConfig;
#[cfg(not(wasm_browser))]
use reportgen::SocketState;
#[cfg(feature = "stun-utils")]
pub use stun_utils::bind_local_stun_socket;

Expand Down Expand Up @@ -216,6 +244,7 @@ impl Default for Reports {
///
/// Use [`Options::stun_v4`], [`Options::stun_v6`], and [`Options::quic_config`]
/// to enable STUN over IPv4, STUN over IPv6, and QUIC address discovery.
#[cfg(not(wasm_browser))]
#[derive(Debug, Clone)]
pub struct Options {
/// Socket to send IPv4 STUN probes from.
Expand Down Expand Up @@ -252,6 +281,7 @@ pub struct Options {
https: bool,
}

#[cfg(not(wasm_browser))]
impl Default for Options {
fn default() -> Self {
Self {
Expand All @@ -265,6 +295,7 @@ impl Default for Options {
}
}

#[cfg(not(wasm_browser))]
impl Options {
/// Create an [`Options`] that disables all probes
pub fn disabled() -> Self {
Expand Down Expand Up @@ -344,17 +375,67 @@ impl Options {
}
}

/// Options for running probes (in browsers).
///
/// Only HTTPS probes are supported in browsers.
/// These are run by default.
#[cfg(wasm_browser)]
#[derive(Debug, Clone)]
pub struct Options {
/// Enable https probes
///
/// On by default
https: bool,
}

#[cfg(wasm_browser)]
impl Default for Options {
fn default() -> Self {
Self { https: true }
}
}

#[cfg(wasm_browser)]
impl Options {
matheus23 marked this conversation as resolved.
Show resolved Hide resolved
/// Create an [`Options`] that disables all probes
pub fn disabled() -> Self {
Self { https: false }
}

/// Enable or disable https probe
pub fn https(mut self, enable: bool) -> Self {
self.https = enable;
self
}

/// Turn the options into set of valid protocols
fn to_protocols(&self) -> BTreeSet<ProbeProto> {
let mut protocols = BTreeSet::new();
if self.https {
protocols.insert(ProbeProto::Https);
}
protocols
}
}

impl Client {
/// Creates a new net_report client.
///
/// This starts a connected actor in the background. Once the client is dropped it will
/// stop running.
pub fn new(
port_mapper: Option<portmapper::Client>,
dns_resolver: DnsResolver,
ip_mapped_addrs: Option<IpMappedAddresses>,
#[cfg(not(wasm_browser))] port_mapper: Option<portmapper::Client>,
#[cfg(not(wasm_browser))] dns_resolver: DnsResolver,
#[cfg(not(wasm_browser))] ip_mapped_addrs: Option<IpMappedAddresses>,
) -> Result<Self> {
let mut actor = Actor::new(port_mapper, dns_resolver, ip_mapped_addrs)?;
let mut actor = Actor::new(
#[cfg(not(wasm_browser))]
port_mapper,
#[cfg(not(wasm_browser))]
dns_resolver,
#[cfg(not(wasm_browser))]
ip_mapped_addrs,
)?;
let addr = actor.addr();
let task = task::spawn(
async move { actor.run().await }.instrument(info_span!("net_report.actor")),
Expand Down Expand Up @@ -399,14 +480,17 @@ impl Client {
pub async fn get_report(
&mut self,
relay_map: RelayMap,
stun_sock_v4: Option<Arc<UdpSocket>>,
stun_sock_v6: Option<Arc<UdpSocket>>,
quic_config: Option<QuicConfig>,
#[cfg(not(wasm_browser))] stun_sock_v4: Option<Arc<UdpSocket>>,
#[cfg(not(wasm_browser))] stun_sock_v6: Option<Arc<UdpSocket>>,
#[cfg(not(wasm_browser))] quic_config: Option<QuicConfig>,
) -> Result<Arc<Report>> {
#[cfg(not(wasm_browser))]
let opts = Options::default()
.stun_v4(stun_sock_v4)
.stun_v6(stun_sock_v6)
.quic_config(quic_config);
#[cfg(wasm_browser)]
let opts = Options::default();
let rx = self.get_report_channel(relay_map.clone(), opts).await?;
match rx.await {
Ok(res) => res,
Expand Down Expand Up @@ -559,6 +643,7 @@ struct Actor {
///
/// The port mapper is responsible for talking to routers via UPnP and the like to try
/// and open ports.
#[cfg(not(wasm_browser))]
port_mapper: Option<portmapper::Client>,

// Actor state.
Expand All @@ -570,9 +655,11 @@ struct Actor {
current_report_run: Option<ReportRun>,

/// The DNS resolver to use for probes that need to perform DNS lookups
#[cfg(not(wasm_browser))]
dns_resolver: DnsResolver,

/// The [`IpMappedAddresses`] that allows you to do QAD in iroh
#[cfg(not(wasm_browser))]
ip_mapped_addrs: Option<IpMappedAddresses>,
}

Expand All @@ -582,20 +669,23 @@ impl Actor {
/// This does not start the actor, see [`Actor::run`] for this. You should not
/// normally create this directly but rather create a [`Client`].
fn new(
port_mapper: Option<portmapper::Client>,
dns_resolver: DnsResolver,
ip_mapped_addrs: Option<IpMappedAddresses>,
#[cfg(not(wasm_browser))] port_mapper: Option<portmapper::Client>,
#[cfg(not(wasm_browser))] dns_resolver: DnsResolver,
#[cfg(not(wasm_browser))] ip_mapped_addrs: Option<IpMappedAddresses>,
) -> Result<Self> {
// TODO: consider an instrumented flume channel so we have metrics.
let (sender, receiver) = mpsc::channel(32);
Ok(Self {
receiver,
sender,
reports: Default::default(),
#[cfg(not(wasm_browser))]
port_mapper,
in_flight_stun_requests: Default::default(),
current_report_run: None,
#[cfg(not(wasm_browser))]
dns_resolver,
#[cfg(not(wasm_browser))]
ip_mapped_addrs,
})
}
Expand Down Expand Up @@ -651,12 +741,15 @@ impl Actor {
response_tx: oneshot::Sender<Result<Arc<Report>>>,
) {
let protocols = opts.to_protocols();
let Options {
stun_sock_v4,
stun_sock_v6,
quic_config,
..
} = opts;
#[cfg(not(wasm_browser))]
let socket_state = SocketState {
port_mapper: self.port_mapper.clone(),
stun_sock4: opts.stun_sock_v4,
stun_sock6: opts.stun_sock_v6,
quic_config: opts.quic_config,
dns_resolver: self.dns_resolver.clone(),
ip_mapped_addrs: self.ip_mapped_addrs.clone(),
};
trace!("Attempting probes for protocols {protocols:#?}");
if self.current_report_run.is_some() {
response_tx
Expand Down Expand Up @@ -693,14 +786,10 @@ impl Actor {
let actor = reportgen::Client::new(
self.addr(),
self.reports.last.clone(),
self.port_mapper.clone(),
relay_map,
stun_sock_v4,
stun_sock_v6,
quic_config,
self.dns_resolver.clone(),
protocols,
self.ip_mapped_addrs.clone(),
#[cfg(not(wasm_browser))]
socket_state,
);

self.current_report_run = Some(ReportRun {
Expand Down Expand Up @@ -876,10 +965,17 @@ struct ReportRun {
}

/// Test if IPv6 works at all, or if it's been hard disabled at the OS level.
#[cfg(not(wasm_browser))]
pub fn os_has_ipv6() -> bool {
UdpSocket::bind_local_v6(0).is_ok()
}

/// Always returns false in browsers
#[cfg(wasm_browser)]
pub fn os_has_ipv6() -> bool {
false
}

#[cfg(any(test, feature = "stun-utils"))]
pub(crate) mod stun_utils {
use anyhow::Context as _;
Expand Down
2 changes: 1 addition & 1 deletion iroh-net-report/src/ping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use std::{
fmt::Debug,
net::IpAddr,
sync::{Arc, Mutex},
time::Duration,
};

use anyhow::{Context, Result};
use n0_future::time::Duration;
use surge_ping::{Client, Config, IcmpPacket, PingIdentifier, PingSequence, ICMP};
use tracing::debug;

Expand Down
Loading
Loading