Files
mfahampshire a70e68c7bd Max/smolmix docs (#6716)
* Smolmix documentation

* Add smolmix docs: landing page, tutorials, and developer page links

* Add Exit Gateway services page (NR vs IPR) and link from existing docs

* Update auto-generated command and API outputs

* Reorg of tutorials and architecture pages

* License information + remove TODO from docs.rs visibile comment + reorg
readme

* Add versions file for doc-wide versioning

* Relative -> absolute links

* Relative -> absolute links

* Update license + add old tutorial code as examples

* Streamline smolmix docs

* Clippy

* Clean up doc comments

* Last pass

* Add larger file download to list

* set new versions

* Clippy

* Remove blake pin from docs + add version range to root Cargo.toml

* Format example logging

* Remove crate blocked component

* Loose whitespace

* Add doc verification script for inline mdx

* Formatting

* Components regen

* Reorg + tighten text

* Voicing cohesion pass + remove bloated examples

* Voicing cont.

* Reduce max download size

* Small suggested clarifications

* Max/docs voicing consistency (#6769)

* Reduce max download size

* voicing consistency across docs

* New landing order w smolmix

* Tweaks

* Final tweaks
2026-05-13 11:19:44 +00:00

109 lines
3.6 KiB
Rust

// Copyright 2024-2026 - Nym Technologies SA <contact@nymtech.net>
//! DNS lookup through the Nym mixnet.
//!
//! Resolves `example.com` twice: once via the system path with
//! `hickory-resolver`, and once by sending a raw DNS query to Cloudflare's
//! 1.1.1.1 over a `smolmix::UdpSocket`. The resolved IPs and timings are
//! printed for comparison.
//!
//! ```text
//! DNS query / response (application-layer UDP)
//! └─ smolmix::UdpSocket (UDP over mixnet)
//! └─ smoltcp (userspace IP stack)
//! └─ Nym mixnet → IPR exit gateway → internet
//! ```
//!
//! ```sh
//! cargo run -p smolmix --example udp
//! cargo run -p smolmix --example udp -- --ipr <IPR_ADDRESS>
//! ```
use std::net::Ipv4Addr;
use hickory_proto::{
op::{Message, Query},
rr::{Name, RData, RecordType},
};
use hickory_resolver::TokioResolver;
use smolmix::Tunnel;
use tracing::info;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[tokio::main]
async fn main() -> Result<(), BoxError> {
nym_bin_common::logging::setup_tracing_logger();
let domain = "example.com";
info!("Clearnet DNS lookup for '{domain}'...");
let resolver = TokioResolver::builder_tokio()?.build()?;
let clearnet_start = tokio::time::Instant::now();
let lookup = resolver.lookup_ip(domain).await?;
let clearnet_ips: Vec<Ipv4Addr> = lookup
.iter()
.filter_map(|ip| match ip {
std::net::IpAddr::V4(v4) => Some(v4),
_ => None,
})
.collect();
let clearnet_duration = clearnet_start.elapsed();
info!("Clearnet: {:?} in {:?}", clearnet_ips, clearnet_duration);
// hickory-proto (not hickory-resolver) so the raw UDP query goes through
// the tunnel directly, instead of routing back to the system resolver
// and out over clearnet.
let args: Vec<String> = std::env::args().collect();
let ipr_addr = args
.iter()
.position(|a| a == "--ipr")
.and_then(|i| args.get(i + 1));
let mut builder = Tunnel::builder();
if let Some(addr) = ipr_addr {
builder = builder.ipr_address(addr.parse().expect("invalid IPR address"));
}
let tunnel = builder.build().await?;
let udp = tunnel.udp_socket().await?;
let mut query = Message::query();
query.metadata.recursion_desired = true;
query.add_query(Query::query(Name::from_ascii(domain)?, RecordType::A));
let query_bytes = query.to_vec()?;
// UDP has no handshake; just send_to + recv_from.
info!("Sending DNS query via mixnet...");
let mixnet_start = tokio::time::Instant::now();
udp.send_to(&query_bytes, "1.1.1.1:53".parse()?).await?;
info!("Query sent ({:?})", mixnet_start.elapsed());
let mut buf = vec![0u8; 1500];
let (n, _from) = udp.recv_from(&mut buf).await?;
let mixnet_duration = mixnet_start.elapsed();
info!("Response received ({} bytes, {:?})", n, mixnet_duration);
let response = Message::from_vec(&buf[..n])?;
let mixnet_ips: Vec<Ipv4Addr> = response
.answers
.iter()
.filter_map(|r| match r.data {
RData::A(a) => Some(a.0),
_ => None,
})
.collect();
info!("Clearnet: {:?} ({:?})", clearnet_ips, clearnet_duration);
info!("Mixnet: {:?} ({:?})", mixnet_ips, mixnet_duration);
let ips_match = !mixnet_ips.is_empty() && mixnet_ips.iter().all(|ip| clearnet_ips.contains(ip));
info!("IPs match: {ips_match}");
let slowdown = mixnet_duration.as_millis() as f64 / clearnet_duration.as_millis().max(1) as f64;
info!("Slowdown: {slowdown:.1}x");
tunnel.shutdown().await;
Ok(())
}