Gunnar

nordisk/tunnr-mullvad

src/lib.rs

//! **Mullvad interop.** Everything it takes to obtain a working WireGuard peer
//! set from Mullvad — and nothing else.
//!
//! ```text
//!   relay list (public, unauthenticated)  ─┐
//!   device registration (host-side, caller's transport)  ─┼─→  PeerSpec / TunnelSpec
//!   the appliance's own private key (minted on-device)   ─┘        ↓
//!                                                       wg_appliance_core::dataplane
//! ```
//!
//! There is **no packet code here and no second tunnel**. Mullvad is ordinary
//! WireGuard underneath: what is Mullvad-specific is provisioning and a set of
//! facts about their network, and both of those are data. That is why this
//! crate can exist at all without touching the data plane.
//!
//! # No code of theirs, and not for licence reasons
//!
//! Mullvad's app is GPL-3.0, and this crate takes none of it — not because the
//! licence forbids reading it, but because **there is nothing to take**. Their
//! interface to a client is a REST response and a WireGuard peer. We already
//! have a data plane, so what we need from Mullvad is *data*, not code.
//! `THIRD-PARTY.md`'s empty `tunnr-mullvad` section is a claim, and this is
//! the claim: written from a live HTTP response, not transcribed.
//!
//! # The four modules, and the one rule they share
//!
//! * [`facts`] — what is true about Mullvad's network, each fact carrying
//!   **DOCUMENTED / MEASURED / INFERRED** and the date. The grade decides
//!   whether a fact may drive a refusal or only a warning.
//! * [`relays`] — parsing the public relay list.
//! * [`device`] — the registration request and response **shapes**. No HTTP
//!   client, no account number, nowhere to put a private key.
//! * [`audit`] — the checks that only make sense because a config claims
//!   Mullvad, moved out of tunnr's payload so there is one writer.
//! * [`pq`] — the quantum-resistant tunnel: the ML-KEM-1024 half, and HQC-256
//!   as a named gap that a test keeps red.
//! * [`peers`] — the seam: a relay plus a registration becomes the appliance's
//!   own `PeerSpec`.
//! * [`net`] — the one prefix-containment primitive the ranges are checked with.
//!
//! The rule they share is that **an inferred fact may never refuse a config**.
//! Mullvad publishes no REST API reference; some of what this crate knows was
//! deduced. A deduction that rejects a config Mullvad itself issued is worse
//! than no check, because the operator has no way to tell our guess from their
//! product.
//!
//! # This crate never holds a secret
//!
//! No account number, no device token, no private key, no PSK bytes it
//! produced. [`device::RegisterDevice`] has one field and it is the *public*
//! key; [`audit::MullvadPeer`] carries `has_preshared_key: bool` rather than a
//! key. A crate with nowhere to put a secret cannot log one.
//!
//! # Trademark
//!
//! "Mullvad" is Mullvad VPN AB's. This crate is *compatible with* their
//! service; it is not a Mullvad client and implies no endorsement.

#![forbid(unsafe_code)]
#![warn(missing_docs)]

pub mod audit;
pub mod facts;
pub mod net;

#[cfg(feature = "relays")]
pub mod device;
#[cfg(feature = "relays")]
pub mod relays;

#[cfg(feature = "peerset")]
pub mod peers;

#[cfg(feature = "pq")]
pub mod pq;

pub use audit::{audit, must_refuse, worst, Finding, MullvadConfig, MullvadPeer, Severity};
pub use facts::{is_relay_port, is_resolver, is_shadowsocks_port, MEASURED_ON, RELAY_LIST_URL};
pub use net::V4Net;

#[cfg(feature = "peerset")]
pub use peers::{peer_for_relay, redacted, tunnel_for_relay, PeerError};
#[cfg(feature = "relays")]
pub use relays::{parse as parse_relay_list, Relay, RelayList};