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
This commit is contained in:
@@ -15,11 +15,19 @@ use hyper::Request;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use rustls::pki_types::ServerName;
|
||||
use smolmix::{NetworkEnvironment, Tunnel};
|
||||
use tokio_rustls::TlsConnector;
|
||||
use tracing::info;
|
||||
|
||||
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
fn tls_connector() -> tokio_rustls::TlsConnector {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let config = rustls::ClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
tokio_rustls::TlsConnector::from(Arc::new(config))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), BoxError> {
|
||||
smolmix::init_logging();
|
||||
@@ -45,12 +53,7 @@ async fn main() -> Result<(), BoxError> {
|
||||
let mixnet_start = tokio::time::Instant::now();
|
||||
let tcp = tunnel.tcp_connect("1.1.1.1:443".parse()?).await?;
|
||||
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let tls_config = rustls::ClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
let connector = TlsConnector::from(Arc::new(tls_config));
|
||||
let connector = tls_connector();
|
||||
let domain = ServerName::try_from(host)?.to_owned();
|
||||
let tls = connector.connect(domain, tcp).await?;
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ const WS_HOST: &str = "ws.postman-echo.com";
|
||||
const WS_PATH: &str = "/raw";
|
||||
const ECHO_MSG: &str = "Hello from the Nym mixnet!";
|
||||
|
||||
/// Build a reusable TLS config with the system root certificates.
|
||||
fn tls_config() -> tokio_rustls::TlsConnector {
|
||||
fn tls_connector() -> tokio_rustls::TlsConnector {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let config = rustls::ClientConfig::builder()
|
||||
@@ -55,7 +54,7 @@ async fn main() -> Result<(), BoxError> {
|
||||
.ok_or("DNS resolution failed")?;
|
||||
info!("Resolved {WS_HOST} -> {addr}");
|
||||
|
||||
let connector = tls_config();
|
||||
let connector = tls_connector();
|
||||
let domain = ServerName::try_from(WS_HOST)?.to_owned();
|
||||
|
||||
// --- Clearnet baseline: tokio TCP → rustls → tungstenite ---
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Architecture
|
||||
|
||||
smolmix is a TCP/UDP tunnel over the Nym mixnet. It gives you standard
|
||||
`TcpStream` and `UdpSocket` types that work transparently with the async Rust
|
||||
ecosystem (tokio-rustls, hyper, tokio-tungstenite, etc.) while routing all
|
||||
traffic through the mixnet for metadata privacy.
|
||||
|
||||
## Stack
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ User code │
|
||||
│ tunnel.tcp_connect() → TcpStream (AsyncRead + AsyncWrite) │
|
||||
│ tunnel.udp_socket() → UdpSocket (send_to / recv_from) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ tokio-smoltcp::Net │
|
||||
│ Owns the smoltcp Interface + SocketSet + async poll loop. │
|
||||
│ Manages TCP state machines, retransmits, port allocation. │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ NymAsyncDevice (device.rs) │
|
||||
│ Stream + Sink adapter for raw IP packets over mpsc channels. │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ NymIprBridge (bridge.rs) │
|
||||
│ Background task shuttling packets between channels and the │
|
||||
│ mixnet. Bundles outgoing packets with MultiIpPacketCodec │
|
||||
│ (required by the IPR protocol). │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ IpMixStream → MixnetClient → Nym mixnet → IPR exit node │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data flow
|
||||
|
||||
```text
|
||||
outgoing: smoltcp → NymAsyncDevice (Sink) → channel → NymIprBridge → IpMixStream → mixnet
|
||||
incoming: mixnet → IpMixStream → NymIprBridge → channel → NymAsyncDevice (Stream) → smoltcp
|
||||
```
|
||||
|
||||
tokio-smoltcp handles all the hard parts (smoltcp polling, TCP state machines,
|
||||
port allocation, waker management). We just give it a device that produces and
|
||||
consumes raw IP packets — `NymAsyncDevice` wraps the mpsc channel ends in the
|
||||
`Stream`/`Sink` traits that tokio-smoltcp requires.
|
||||
|
||||
## Key design decisions
|
||||
|
||||
- **Single async device adapter.** All traffic flows through one
|
||||
`NymAsyncDevice`. If you need a new transport type (e.g. ICMP), add a method
|
||||
to `Tunnel` rather than introducing a separate device — the device and bridge
|
||||
don't need to change. smoltcp already supports ICMP sockets; you'd enable
|
||||
the `socket-icmp` feature in `Cargo.toml`, add a method like
|
||||
`Tunnel::icmp_socket()` that calls the appropriate `Net` method, and expose
|
||||
the socket type via a re-export in `lib.rs`.
|
||||
|
||||
- **Tokio-only.** The bridge, SDK (`IpMixStream`, `MixnetClient`), and channels
|
||||
are all tokio-based. An earlier version had a sync smoltcp `Device` adapter
|
||||
for use without tokio-smoltcp, but it still required a tokio runtime
|
||||
underneath (for the bridge and SDK), so it provided no real runtime
|
||||
independence — just duplicated the bridging logic. If alternative-runtime
|
||||
support is ever needed, it would require swapping out the bridge, SDK, and
|
||||
channel layers — a separate crate, not a feature flag on this one.
|
||||
|
||||
- **Unbounded channels.** The channels between the device and bridge are
|
||||
unbounded. Backpressure is handled at the mixnet layer (IPR protocol), not
|
||||
at the channel level. If that assumption changes, consider bounded channels
|
||||
with a drop policy.
|
||||
|
||||
- **`Medium::Ip` (no Ethernet framing).** Raw IP packets go in and out,
|
||||
matching what the IPR protocol expects.
|
||||
Reference in New Issue
Block a user