Files
nym/smolmix/core/examples/udp.rs
mfahampshire 7ceaf9a40e Max/mixtcp (#6321)
* Add mixtcp crate 

Components:
- NymIprDevice: smoltcp::phy::Device impl using channel-based I/O
- NymIprBridge: async task bridging the device to IpMixStream
- create_device(): helper to set up the complete stack

* - Cleanup
- Add graceful shutdown
- Declutter logging - move a lot of bridge info! -> trace!
- Move rustls, nym-bin-common, bytes to dev-dependencies
- Extract TlsOverTcp to mod.rs
- Make timing more granular
- Update readme

* Add UDP example

* Add UDP example to readme

* rename mixtcp -> smolmix

* Add Tunnel API with TcpStream and UdpSocket over tokio-smoltcp

* Re-export Tunnel API and add init_logging convenience function

* Remove raw smoltcp path, flatten tunnel module

* Clean up bridge, device, and tunnel code

* Consolidate architecture docs, tidy examples and README

- Add src/ARCHITECTURE.md as single source of truth for architecture
- Include in docs.rs via doc = include_str!
- Strip duplicated diagrams from tunnel.rs, device.rs, README
- Extract tls_connector() helper in HTTPS example to match websocket example
- Use consistent 'smolmix' casing in README

* Update smolmix imports for ipr_wrapper API

- stream_wrapper::{IpMixStream, NetworkEnvironment} → ipr_wrapper::
- connect_tunnel() → check_connected()
- disconnect_stream() → disconnect()
- allocated_ips() returns &IpPair directly (no Option)

* Add Tunnel::new_with_ipr, re-export IpPair/Recipient, tidy examples

- Add Tunnel::new_with_ipr() for targeting a specific exit node
- Re-export IpPair and Recipient so users don't need direct deps
- Add DNS leak warning to WebSocket example
- Await hyper connection task in HTTPS example

* Restructure smolmix into multi-crate workspace

- Move core tunnel code to smolmix/core/- Rewrite examples for each crate with clearnet/mixnet comparisons

* Add workspace README with architecture overview

* Update nym-sdk README module descriptions

- Replace stale stream_wrapper description with ipr_wrapper + mixnet::stream
- Remove TODO comment

* Remove companion crates, scope to smolmix-core

* Comment out additional components on -core branch README.md

* Cargo.lock fix for compilation issue

* Downgrade accidentally bumped dependencies in Cargo lock + change
smolmix dependencies to import from workspace

* Fix workspace deps + move nym-bin-common to dev-deps

* PR review changes + fix Sink delegation

* Fix borked merge + update README.md

* Fix up stale docs + rewrite examples to use proper imports and timing
logs

* Update readmes + architecture file

* Impl Drop for BridgeShutdownHandle + update comment
2026-04-14 20:13:12 +00:00

97 lines
3.1 KiB
Rust

//! DNS lookup through the Nym mixnet.
//!
//! Resolves `example.com` via clearnet (hickory-resolver) and via the mixnet
//! (hickory-proto UDP query to Cloudflare 1.1.1.1), then compares resolved
//! IPs and timing.
//!
//!
//! Run with:
//! 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};
use hickory_proto::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";
// Clearnet baseline via hickory-resolver
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);
// Mixnet: hickory-proto query over smolmix UDP
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::new();
query.set_recursion_desired(true);
query.add_query(Query::query(Name::from_ascii(domain)?, RecordType::A));
let query_bytes = query.to_vec()?;
// UDP is connectionless — no setup phase, just send/recv
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();
// Results
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(())
}