Make the protocol a crate of its own

tsunagi-wg-quic. The line between a protocol and the system level is now
drawn by the compiler: nothing in it can reach into tsunagi beyond what
tsunagi makes public, and it carries its own version — which is not the
version peers compare.

Two things the compiler found the moment the boundary was real. The key
store was reaching into the core's `pub(crate)` file-permission helpers;
those are a legitimate service of the system level, because a protocol
keeping keys on disk has the same obligation the agent does, so they are
public now with that said. And the test harness was about to be copied
into a second crate, which is how two copies start to drift; it is a
`testing` feature of the core instead, which is also what anybody writing
a protocol would need.

The bridges put up while things were moving are gone: the error
conversion between the two levels, and the re-exports of the system
level's types from the protocol crate. Imports now say which level they
come from, which is the point.

One deliberate deviation, stated rather than hidden. The authenticated
transport stayed in the core. Moving it would have meant handing a
protocol the network's keys so it could prove membership itself, and a
plugin that can authenticate on the control plane is a worse trade than
a module boundary is worth. So the core proves who is at the other end
and the protocol owns what is said over it — the same separation, without
the secret crossing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tsunagi
2026-09-21 19:55:30 +01:00
co-authored by Claude Opus 5
parent ff7e235414
commit 142fdf995c
31 changed files with 289 additions and 234 deletions
+1
View File
@@ -13,6 +13,7 @@ path = "src/main.rs"
[dependencies]
tsunagi = { path = "../tsunagi", version = "0.1.0", features = ["tun-device", "dns-publish"] }
tsunagi-wg-quic = { path = "../tsunagi-wg-quic", version = "0.1.0" }
iroh.workspace = true
tokio = { version = "1.53", features = ["rt", "rt-multi-thread", "sync", "time", "macros", "signal"] }
tracing.workspace = true
+8 -11
View File
@@ -15,14 +15,13 @@ use clap::{Args, Parser, Subcommand, ValueEnum};
use tsunagi::agent::Event;
use tsunagi::config::{AgentConfig, StoragePaths, TransportPolicy};
use tsunagi::dataplane::IpPlugin;
use tsunagi::dataplane::wireguard::{
MemoryTunFactory, TunFactory, WireguardConfig, WireguardPlugin,
};
use tsunagi::discovery::{CompositeDiscovery, NetworkDiscovery, StaticBootstrap};
use tsunagi::identity::{NetworkName, NetworkSecret};
use tsunagi::iroh_types::EndpointAddr;
use tsunagi::overlay::{MemoryTunFactory, TunFactory};
use tsunagi::state::Ipv4Range;
use tsunagi::{Agent, NetworkId};
use tsunagi_wg_quic::{WireguardConfig, WireguardPlugin};
/// A small agent for private mesh networks.
#[derive(Debug, Parser)]
@@ -775,8 +774,8 @@ struct ProtocolSpec {
/// Every protocol this build has.
const PROTOCOLS: &[ProtocolSpec] = &[ProtocolSpec {
name: tsunagi::dataplane::wireguard::WIREGUARD_PROTOCOL,
version: tsunagi::dataplane::wireguard::ANNOUNCEMENT_VERSION,
name: tsunagi_wg_quic::WIREGUARD_PROTOCOL,
version: tsunagi_wg_quic::ANNOUNCEMENT_VERSION,
summary: "WireGuard's cryptography carried in iroh's QUIC datagrams, so it \
crosses NAT and survives where plain WireGuard is blocked",
options: WireguardPlugin::OPTIONS,
@@ -1509,7 +1508,7 @@ fn host_section() -> report::Section {
});
}
use tsunagi::dataplane::wireguard::{Privilege, probe_net_admin};
use tsunagi::overlay::{Privilege, probe_net_admin};
match probe_net_admin() {
Privilege::Available => {
host.push(Row::new(Health::Good, "privileges", "CAP_NET_ADMIN held"));
@@ -2064,16 +2063,14 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
} else {
system_tun_factory()?
};
let mtu = args
.mtu
.unwrap_or(tsunagi::dataplane::wireguard::DEFAULT_MTU);
let mtu = args.mtu.unwrap_or(tsunagi_wg_quic::DEFAULT_MTU);
config = config.with_interface(tun_factory, args.interface.clone(), mtu);
}
for spec in &wanted {
let options = settings_for(spec, &settings);
match spec.name {
tsunagi::dataplane::wireguard::WIREGUARD_PROTOCOL => {
tsunagi_wg_quic::WIREGUARD_PROTOCOL => {
let mut wg = WireguardConfig::new(paths.state_dir.join("wg-quic"));
if let Some(mtu) = args.mtu {
wg = wg.with_mtu(mtu);
@@ -2381,7 +2378,7 @@ async fn stop_signal() -> &'static str {
/// goes away.
#[cfg(target_os = "linux")]
fn system_tun_factory() -> Result<Arc<dyn TunFactory>, Box<dyn std::error::Error>> {
use tsunagi::dataplane::wireguard::{ManagedTunFactory, NetlinkProvisioner};
use tsunagi::overlay::{ManagedTunFactory, NetlinkProvisioner};
let provisioner = NetlinkProvisioner::new()?;
Ok(Arc::new(ManagedTunFactory::new(Arc::new(provisioner))))
}