Move the overlay interface to the system level

One agent, one interface, owned by the agent rather than by a protocol.
That is what makes several protocols able to be live at once: a packet
leaving the interface is routed to whichever peer owns its destination,
over whichever protocol has a link to that peer, and neither protocol
has to hold the address because the agent holds it.

This commit moves the pieces without changing behaviour: provisioning,
the TUN itself, IP header parsing and interface naming are now
crates/tsunagi/src/overlay, and the WireGuard module re-exports them
while its callers are moved over. They were never WireGuard-specific —
netlink, capabilities and `ip tuntap` have nothing to do with the
protocol running on top.

They also get their own error type. An interface that cannot be created
is not a plugin failing, and now that the two belong to different levels
they should not share a word for it.

Routing, addressing and the reduced plugin contract come next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tsunagi
2026-09-21 18:09:57 +01:00
co-authored by Claude Opus 5
parent 60e6b263d1
commit 5bee148497
15 changed files with 163 additions and 93 deletions
+14
View File
@@ -67,6 +67,20 @@ pub enum PluginError {
Other(String), Other(String),
} }
/// Scaffolding while the interface moves out of the plugin.
///
/// The overlay interface belongs to the system level now, so a plugin has no
/// business failing because of it. This exists only for the callers that
/// have not been moved over yet and goes when the last of them does.
impl From<crate::overlay::OverlayError> for PluginError {
fn from(err: crate::overlay::OverlayError) -> Self {
match err {
crate::overlay::OverlayError::Unavailable(reason) => PluginError::Unavailable(reason),
crate::overlay::OverlayError::Other(reason) => PluginError::Other(reason),
}
}
}
/// A request a plugin makes of the agent that owns it. /// A request a plugin makes of the agent that owns it.
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum PluginRequest { pub(crate) enum PluginRequest {
@@ -43,8 +43,8 @@ use crate::identity::NetworkId;
use super::keys::{WgPublicKey, WgSecretKey}; use super::keys::{WgPublicKey, WgSecretKey};
use super::overlay::overlay_address; use super::overlay::overlay_address;
use super::packet::IpHeader; use crate::overlay::packet::IpHeader;
use super::tun::TunDevice; use crate::overlay::tun::TunDevice;
use crate::state::Ipv4Range; use crate::state::Ipv4Range;
/// How often WireGuard's own timers are driven. /// How often WireGuard's own timers are driven.
+16 -16
View File
@@ -42,34 +42,34 @@
//! See `docs/wireguard.md` for the full picture. //! See `docs/wireguard.md` for the full picture.
pub mod announcement; pub mod announcement;
pub mod config;
pub mod device; pub mod device;
pub mod keys; pub mod keys;
pub mod overlay; pub mod overlay;
pub mod packet;
pub mod plugin; pub mod plugin;
pub mod provision;
pub mod store; pub mod store;
pub mod tun;
pub use crate::state::Ipv4Range; pub use crate::state::Ipv4Range;
pub use announcement::{ValidatedAnnouncement, WgAnnouncement}; pub use announcement::{ValidatedAnnouncement, WgAnnouncement};
pub use config::{Cidr, DEFAULT_INTERFACE_PREFIX, MAX_INTERFACE_NAME_LEN, interface_name}; // The interface, its addresses and how it is created belong to the system
pub use device::{PeerHealth, PeerStats, PeerSummary, WireguardDevice}; // level now: one agent has one interface, and no protocol owns it. Re-exported
pub use keys::{WgPublicKey, WgSecretKey}; // here while callers are moved over.
pub use overlay::{OVERLAY_PREFIX_LEN, overlay_address, overlay_address_v4, overlay_prefix}; pub use crate::overlay::provision::{
pub use packet::IpHeader;
pub use plugin::{
DEFAULT_MTU, MIN_MTU, NetworkOverview, PeerOverview, WIREGUARD_OVERHEAD, WIREGUARD_PROTOCOL,
WireguardConfig, WireguardPlugin,
};
pub use provision::{
Changes, InterfacePlan, InterfaceProvisioner, InterfaceState, LinkKind, ManagedTunFactory, Changes, InterfacePlan, InterfaceProvisioner, InterfaceState, LinkKind, ManagedTunFactory,
MockHost, MockProvisioner, Privilege, Provisioned, UnsupportedProvisioner, plan_changes, MockHost, MockProvisioner, Privilege, Provisioned, UnsupportedProvisioner, plan_changes,
probe_net_admin, probe_net_admin,
}; };
pub use crate::overlay::{
Cidr, DEFAULT_INTERFACE_PREFIX, IpHeader, MAX_INTERFACE_NAME_LEN, MemoryTun, MemoryTunFactory,
TunDevice, TunFactory, TunRequest, address_is_local, interface_name,
};
pub use device::{PeerHealth, PeerStats, PeerSummary, WireguardDevice};
pub use keys::{WgPublicKey, WgSecretKey};
pub use overlay::{OVERLAY_PREFIX_LEN, overlay_address, overlay_address_v4, overlay_prefix};
pub use plugin::{
DEFAULT_MTU, MIN_MTU, NetworkOverview, PeerOverview, WIREGUARD_OVERHEAD, WIREGUARD_PROTOCOL,
WireguardConfig, WireguardPlugin,
};
pub use store::WgKeyStore; pub use store::WgKeyStore;
pub use tun::{MemoryTun, MemoryTunFactory, TunDevice, TunFactory, TunRequest, address_is_local};
#[cfg(all(feature = "tun-device", target_os = "linux"))] #[cfg(all(feature = "tun-device", target_os = "linux"))]
pub use provision::NetlinkProvisioner; pub use crate::overlay::NetlinkProvisioner;
@@ -40,12 +40,12 @@ use crate::dataplane::{IpPlugin, PluginCapability, PluginContext, PluginError};
use crate::identity::NetworkId; use crate::identity::NetworkId;
use super::announcement::{ValidatedAnnouncement, WgAnnouncement}; use super::announcement::{ValidatedAnnouncement, WgAnnouncement};
use super::config::{DEFAULT_INTERFACE_PREFIX, interface_name};
use super::device::{PeerSummary, WireguardDevice}; use super::device::{PeerSummary, WireguardDevice};
use super::keys::{WgPublicKey, WgSecretKey}; use super::keys::{WgPublicKey, WgSecretKey};
use super::overlay::{OVERLAY_PREFIX_LEN, overlay_address, overlay_prefix}; use super::overlay::{OVERLAY_PREFIX_LEN, overlay_address, overlay_prefix};
use super::store::WgKeyStore; use super::store::WgKeyStore;
use super::tun::{TunFactory, TunRequest}; use crate::overlay::config::{DEFAULT_INTERFACE_PREFIX, interface_name};
use crate::overlay::tun::{TunFactory, TunRequest};
use crate::state::Ipv4Range; use crate::state::Ipv4Range;
/// The protocol identifier this plugin announces. /// The protocol identifier this plugin announces.
@@ -603,7 +603,7 @@ impl Worker {
// kind that has been wrong here before. // kind that has been wrong here before.
let missing_v4 = match (own_v4, interface.as_deref(), range) { let missing_v4 = match (own_v4, interface.as_deref(), range) {
(Some(address), Some(interface), Some(range)) (Some(address), Some(interface), Some(range))
if !super::tun::address_is_local(IpAddr::V4(address)) => if !crate::overlay::tun::address_is_local(IpAddr::V4(address)) =>
{ {
let already = state_reported == Some(address); let already = state_reported == Some(address);
if let Some(state) = shared.networks.get_mut(&network) { if let Some(state) = shared.networks.get_mut(&network) {
+1
View File
@@ -44,6 +44,7 @@ pub mod error;
pub mod identity; pub mod identity;
pub mod ipc; pub mod ipc;
pub mod net; pub mod net;
pub mod overlay;
pub mod proto; pub mod proto;
pub mod state; pub mod state;
pub mod storage; pub mod storage;
@@ -11,10 +11,11 @@
use std::net::{IpAddr, Ipv6Addr}; use std::net::{IpAddr, Ipv6Addr};
use crate::dataplane::PluginError;
use crate::identity::NetworkId; use crate::identity::NetworkId;
use crate::overlay::OverlayError;
use super::overlay::OVERLAY_HOST_PREFIX_LEN; /// Prefix length of a single IPv6 host address.
const HOST_PREFIX_LEN: u8 = 128;
/// Longest interface name Linux accepts, excluding the terminating NUL. /// Longest interface name Linux accepts, excluding the terminating NUL.
pub const MAX_INTERFACE_NAME_LEN: usize = 15; pub const MAX_INTERFACE_NAME_LEN: usize = 15;
@@ -33,13 +34,13 @@ pub struct Cidr {
impl Cidr { impl Cidr {
/// Builds a CIDR, rejecting an impossible prefix length. /// Builds a CIDR, rejecting an impossible prefix length.
pub fn new(addr: IpAddr, prefix_len: u8) -> Result<Self, PluginError> { pub fn new(addr: IpAddr, prefix_len: u8) -> Result<Self, OverlayError> {
let max = match addr { let max = match addr {
IpAddr::V4(_) => 32, IpAddr::V4(_) => 32,
IpAddr::V6(_) => 128, IpAddr::V6(_) => 128,
}; };
if prefix_len > max { if prefix_len > max {
return Err(PluginError::Other(format!( return Err(OverlayError::Other(format!(
"prefix length /{prefix_len} is impossible for {addr}" "prefix length /{prefix_len} is impossible for {addr}"
))); )));
} }
@@ -50,7 +51,7 @@ impl Cidr {
pub fn host(addr: Ipv6Addr) -> Self { pub fn host(addr: Ipv6Addr) -> Self {
Self { Self {
addr: IpAddr::V6(addr), addr: IpAddr::V6(addr),
prefix_len: OVERLAY_HOST_PREFIX_LEN, prefix_len: HOST_PREFIX_LEN,
} }
} }
} }
@@ -66,9 +67,9 @@ impl std::fmt::Display for Cidr {
/// The name is stable across restarts and short enough for the platform. Two /// The name is stable across restarts and short enough for the platform. Two
/// agents on the same host in the same network must be given different /// agents on the same host in the same network must be given different
/// prefixes, or they would derive the same name. /// prefixes, or they would derive the same name.
pub fn interface_name(prefix: &str, network: NetworkId) -> Result<String, PluginError> { pub fn interface_name(prefix: &str, network: NetworkId) -> Result<String, OverlayError> {
if prefix.is_empty() { if prefix.is_empty() {
return Err(PluginError::Other( return Err(OverlayError::Other(
"interface prefix must not be empty".into(), "interface prefix must not be empty".into(),
)); ));
} }
@@ -76,12 +77,12 @@ pub fn interface_name(prefix: &str, network: NetworkId) -> Result<String, Plugin
.chars() .chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()) .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
{ {
return Err(PluginError::Other( return Err(OverlayError::Other(
"interface prefix must be lowercase ASCII letters and digits".into(), "interface prefix must be lowercase ASCII letters and digits".into(),
)); ));
} }
if prefix.len() >= MAX_INTERFACE_NAME_LEN { if prefix.len() >= MAX_INTERFACE_NAME_LEN {
return Err(PluginError::Other(format!( return Err(OverlayError::Other(format!(
"interface prefix must be shorter than {MAX_INTERFACE_NAME_LEN} characters" "interface prefix must be shorter than {MAX_INTERFACE_NAME_LEN} characters"
))); )));
} }
+54
View File
@@ -0,0 +1,54 @@
//! The overlay interface: the one network interface an agent owns.
//!
//! One agent, one interface. It belongs to the system level rather than to
//! any protocol, and that is the whole point: a packet leaving it is routed
//! to whichever peer owns its destination address, over whichever protocol
//! currently has a link to that peer. Several protocols can be live at once
//! without arguing over who holds the address, because neither of them
//! holds it — the agent does.
//!
//! What lives here:
//!
//! * [`provision`] creates the interface and configures it, and removes it
//! again. Platform mechanics behind one decision function.
//! * [`tun`] is the packet interface itself, real or in memory.
//! * [`packet`] reads just enough of an IP header to route by it.
//! * [`config`] names interfaces and describes addresses.
//!
//! What does not live here: encryption, peer discovery, and how a packet
//! actually reaches another machine. Those belong to a protocol plugin,
//! which this level hands packets to and takes packets from.
/// Why the overlay interface cannot be brought into the state asked for.
///
/// Its own error rather than the plugin one it used to borrow: this level
/// owns the interface now, and a plugin failing is a different event from
/// the interface failing.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OverlayError {
/// The interface cannot be created or configured right now, with the
/// reason and, where there is one, what to do about it.
#[error("overlay unavailable: {0}")]
Unavailable(String),
/// Anything else.
#[error("{0}")]
Other(String),
}
pub mod config;
pub mod packet;
pub mod provision;
pub mod tun;
pub use config::{Cidr, DEFAULT_INTERFACE_PREFIX, MAX_INTERFACE_NAME_LEN, interface_name};
pub use packet::IpHeader;
pub use provision::{
Changes, InterfacePlan, InterfaceProvisioner, InterfaceState, LinkKind, ManagedTunFactory,
MockHost, MockProvisioner, Privilege, Provisioned, UnsupportedProvisioner, plan_changes,
probe_net_admin,
};
pub use tun::{MemoryTun, MemoryTunFactory, TunDevice, TunFactory, TunRequest, address_is_local};
#[cfg(all(feature = "tun-device", target_os = "linux"))]
pub use provision::NetlinkProvisioner;
@@ -9,14 +9,14 @@
use std::sync::Arc; use std::sync::Arc;
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
use super::super::config::Cidr; use super::super::config::Cidr;
use super::super::tun::{TunDevice, TunFactory, TunRequest}; use super::super::tun::{TunDevice, TunFactory, TunRequest};
use super::{InterfacePlan, InterfaceProvisioner}; use super::{InterfacePlan, InterfaceProvisioner};
/// Turns a [`TunRequest`] into the plan for a host interface. /// Turns a [`TunRequest`] into the plan for a host interface.
fn plan_for(request: &TunRequest) -> Result<InterfacePlan, PluginError> { fn plan_for(request: &TunRequest) -> Result<InterfacePlan, OverlayError> {
let mut addresses = vec![Cidr::new(request.address.into(), request.prefix_len)?]; let mut addresses = vec![Cidr::new(request.address.into(), request.prefix_len)?];
if let Some(address) = request.address_v4 { if let Some(address) = request.address_v4 {
addresses.push(Cidr::new(address.into(), request.prefix_len_v4)?); addresses.push(Cidr::new(address.into(), request.prefix_len_v4)?);
@@ -54,7 +54,7 @@ impl TunFactory for ManagedTunFactory {
fn create<'a>( fn create<'a>(
&'a self, &'a self,
request: TunRequest, request: TunRequest,
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, PluginError>> { ) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, OverlayError>> {
Box::pin(async move { Box::pin(async move {
let plan = plan_for(&request)?; let plan = plan_for(&request)?;
let provisioned = self.provisioner.reconcile(&plan).await?; let provisioned = self.provisioner.reconcile(&plan).await?;
@@ -67,7 +67,7 @@ impl TunFactory for ManagedTunFactory {
// Reaching here would mean the interface already existed and // Reaching here would mean the interface already existed and
// was held open by us, which cannot be true on the path that // was held open by us, which cannot be true on the path that
// creates a device. // creates a device.
PluginError::Other(format!( OverlayError::Other(format!(
"interface `{}` was reconciled but no device came back", "interface `{}` was reconciled but no device came back",
plan.name plan.name
)) ))
@@ -75,7 +75,7 @@ impl TunFactory for ManagedTunFactory {
}) })
} }
fn reconfigure<'a>(&'a self, request: TunRequest) -> BoxFuture<'a, Result<(), PluginError>> { fn reconfigure<'a>(&'a self, request: TunRequest) -> BoxFuture<'a, Result<(), OverlayError>> {
Box::pin(async move { Box::pin(async move {
let plan = plan_for(&request)?; let plan = plan_for(&request)?;
let provisioned = self.provisioner.reconcile(&plan).await?; let provisioned = self.provisioner.reconcile(&plan).await?;
@@ -50,7 +50,7 @@ use rtnetlink::packet_route::link::{InfoKind, LinkAttribute, LinkFlags, LinkInfo
use rtnetlink::{LinkMessageBuilder, LinkUnspec}; use rtnetlink::{LinkMessageBuilder, LinkUnspec};
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
use super::super::config::Cidr; use super::super::config::Cidr;
use super::super::tun::{TunDevice, TunRequest}; use super::super::tun::{TunDevice, TunRequest};
@@ -71,7 +71,7 @@ enum Command {
Stop, Stop,
} }
type Reply<T> = mpsc::Sender<Result<T, PluginError>>; type Reply<T> = mpsc::Sender<Result<T, OverlayError>>;
/// The configuration half of a reconciliation. /// The configuration half of a reconciliation.
struct Configure { struct Configure {
@@ -97,17 +97,17 @@ pub struct NetlinkProvisioner {
impl NetlinkProvisioner { impl NetlinkProvisioner {
/// Starts the netlink thread, after checking this process can use it. /// Starts the netlink thread, after checking this process can use it.
pub fn new() -> Result<Self, PluginError> { pub fn new() -> Result<Self, OverlayError> {
match probe_net_admin() { match probe_net_admin() {
Privilege::Available => {} Privilege::Available => {}
Privilege::Missing(reason) => { Privilege::Missing(reason) => {
return Err(PluginError::Unavailable(format!( return Err(OverlayError::Unavailable(format!(
"{reason}. {}", "{reason}. {}",
Privilege::how_to_grant(&current_program()) Privilege::how_to_grant(&current_program())
))); )));
} }
Privilege::Unsupported => { Privilege::Unsupported => {
return Err(PluginError::Unavailable( return Err(OverlayError::Unavailable(
"interface management is not compiled in".to_string(), "interface management is not compiled in".to_string(),
)); ));
} }
@@ -118,7 +118,7 @@ impl NetlinkProvisioner {
.name("tsunagi-netlink".to_string()) .name("tsunagi-netlink".to_string())
.spawn(move || netlink_thread(requests)) .spawn(move || netlink_thread(requests))
.map_err(|err| { .map_err(|err| {
PluginError::Unavailable(format!("cannot start the netlink thread: {err}")) OverlayError::Unavailable(format!("cannot start the netlink thread: {err}"))
})?; })?;
Ok(Self { Ok(Self {
@@ -132,13 +132,13 @@ impl NetlinkProvisioner {
fn call<T: Send + 'static>( fn call<T: Send + 'static>(
&self, &self,
make: impl FnOnce(Reply<T>) -> Command, make: impl FnOnce(Reply<T>) -> Command,
) -> Result<T, PluginError> { ) -> Result<T, OverlayError> {
let (reply_tx, reply_rx) = mpsc::channel(); let (reply_tx, reply_rx) = mpsc::channel();
self.commands self.commands
.send(make(reply_tx)) .send(make(reply_tx))
.map_err(|_| PluginError::Unavailable("the netlink thread has stopped".to_string()))?; .map_err(|_| OverlayError::Unavailable("the netlink thread has stopped".to_string()))?;
reply_rx.recv().map_err(|_| { reply_rx.recv().map_err(|_| {
PluginError::Unavailable("the netlink thread stopped mid-request".to_string()) OverlayError::Unavailable("the netlink thread stopped mid-request".to_string())
})? })?
} }
@@ -150,7 +150,7 @@ impl NetlinkProvisioner {
/// ///
/// Synchronous on purpose: the capability guard is raised and lowered /// Synchronous on purpose: the capability guard is raised and lowered
/// without an `await` in between, so it cannot outlive this thread. /// without an `await` in between, so it cannot outlive this thread.
fn create_device(&self, plan: &InterfacePlan) -> Result<Arc<dyn TunDevice>, PluginError> { fn create_device(&self, plan: &InterfacePlan) -> Result<Arc<dyn TunDevice>, OverlayError> {
let request = TunRequest::bare(plan.name.clone(), plan.mtu); let request = TunRequest::bare(plan.name.clone(), plan.mtu);
let _guard = NetAdmin::acquire()?; let _guard = NetAdmin::acquire()?;
super::super::tun::open_tun(&request) super::super::tun::open_tun(&request)
@@ -174,7 +174,7 @@ impl InterfaceProvisioner for NetlinkProvisioner {
fn reconcile<'a>( fn reconcile<'a>(
&'a self, &'a self,
plan: &'a InterfacePlan, plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>> { ) -> BoxFuture<'a, Result<Provisioned, OverlayError>> {
Box::pin(async move { Box::pin(async move {
let current = self.call(|reply| Command::Observe(plan.name.clone(), reply))?; let current = self.call(|reply| Command::Observe(plan.name.clone(), reply))?;
let changes = plan_changes(&current, plan, self.is_ours(&plan.name))?; let changes = plan_changes(&current, plan, self.is_ours(&plan.name))?;
@@ -224,7 +224,7 @@ impl InterfaceProvisioner for NetlinkProvisioner {
}) })
} }
fn remove<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<(), PluginError>> { fn remove<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<(), OverlayError>> {
Box::pin(async move { Box::pin(async move {
// Dropping the device is what removes the interface; the explicit // Dropping the device is what removes the interface; the explicit
// delete is only so it is gone by the time this returns rather // delete is only so it is gone by the time this returns rather
@@ -271,10 +271,10 @@ fn netlink_thread(requests: mpsc::Receiver<Command>) {
let message = format!("the netlink thread has no runtime: {err}"); let message = format!("the netlink thread has no runtime: {err}");
match command { match command {
Command::Observe(_, reply) => { Command::Observe(_, reply) => {
let _ = reply.send(Err(PluginError::Unavailable(message))); let _ = reply.send(Err(OverlayError::Unavailable(message)));
} }
Command::Delete(_, reply) | Command::Configure(_, reply) => { Command::Delete(_, reply) | Command::Configure(_, reply) => {
let _ = reply.send(Err(PluginError::Unavailable(message))); let _ = reply.send(Err(OverlayError::Unavailable(message)));
} }
Command::Stop => return, Command::Stop => return,
} }
@@ -311,12 +311,12 @@ fn netlink_thread(requests: mpsc::Receiver<Command>) {
} }
/// Opens a netlink connection and runs one unit of work over it. /// Opens a netlink connection and runs one unit of work over it.
async fn with_netlink<T, F>(work: impl FnOnce(rtnetlink::Handle) -> F) -> Result<T, PluginError> async fn with_netlink<T, F>(work: impl FnOnce(rtnetlink::Handle) -> F) -> Result<T, OverlayError>
where where
F: std::future::Future<Output = Result<T, PluginError>>, F: std::future::Future<Output = Result<T, OverlayError>>,
{ {
let (connection, handle, _messages) = rtnetlink::new_connection() let (connection, handle, _messages) = rtnetlink::new_connection()
.map_err(|err| PluginError::Unavailable(format!("cannot open netlink: {err}")))?; .map_err(|err| OverlayError::Unavailable(format!("cannot open netlink: {err}")))?;
let pump = tokio::spawn(connection); let pump = tokio::spawn(connection);
let result = work(handle).await; let result = work(handle).await;
pump.abort(); pump.abort();
@@ -361,7 +361,7 @@ fn link_kind(message: &LinkMessage, name: &str) -> LinkKind {
} }
} }
async fn observe(name: &str) -> Result<InterfaceState, PluginError> { async fn observe(name: &str) -> Result<InterfaceState, OverlayError> {
with_netlink(|handle| async move { with_netlink(|handle| async move {
let mut links = handle.link().get().match_name(name.to_string()).execute(); let mut links = handle.link().get().match_name(name.to_string()).execute();
let message = match links.try_next().await { let message = match links.try_next().await {
@@ -373,7 +373,7 @@ async fn observe(name: &str) -> Result<InterfaceState, PluginError> {
if !std::path::Path::new(&format!("/sys/class/net/{name}")).exists() { if !std::path::Path::new(&format!("/sys/class/net/{name}")).exists() {
return Ok(InterfaceState::absent()); return Ok(InterfaceState::absent());
} }
return Err(PluginError::Unavailable(format!( return Err(OverlayError::Unavailable(format!(
"cannot read interface `{name}`: {err}" "cannot read interface `{name}`: {err}"
))); )));
} }
@@ -397,7 +397,7 @@ async fn observe(name: &str) -> Result<InterfaceState, PluginError> {
.set_link_index_filter(index) .set_link_index_filter(index)
.execute(); .execute();
while let Some(message) = stream.try_next().await.map_err(|err| { while let Some(message) = stream.try_next().await.map_err(|err| {
PluginError::Unavailable(format!("cannot read the addresses of `{name}`: {err}")) OverlayError::Unavailable(format!("cannot read the addresses of `{name}`: {err}"))
})? { })? {
if let Some(cidr) = address_of(&message) if let Some(cidr) = address_of(&message)
&& !is_link_local(cidr.addr) && !is_link_local(cidr.addr)
@@ -440,7 +440,7 @@ fn cidr(addr: IpAddr, prefix_len: u8) -> Option<Cidr> {
Cidr::new(addr, prefix_len).ok() Cidr::new(addr, prefix_len).ok()
} }
async fn delete_link(name: &str) -> Result<(), PluginError> { async fn delete_link(name: &str) -> Result<(), OverlayError> {
with_netlink(|handle| async move { with_netlink(|handle| async move {
let mut links = handle.link().get().match_name(name.to_string()).execute(); let mut links = handle.link().get().match_name(name.to_string()).execute();
let index = match links.try_next().await { let index = match links.try_next().await {
@@ -449,13 +449,13 @@ async fn delete_link(name: &str) -> Result<(), PluginError> {
Ok(None) | Err(_) => return Ok(()), Ok(None) | Err(_) => return Ok(()),
}; };
handle.link().del(index).execute().await.map_err(|err| { handle.link().del(index).execute().await.map_err(|err| {
PluginError::Unavailable(format!("cannot remove interface `{name}`: {err}")) OverlayError::Unavailable(format!("cannot remove interface `{name}`: {err}"))
}) })
}) })
.await .await
} }
async fn configure_link(configure: &Configure) -> Result<(), PluginError> { async fn configure_link(configure: &Configure) -> Result<(), OverlayError> {
let name = configure.name.as_str(); let name = configure.name.as_str();
with_netlink(|handle| async move { with_netlink(|handle| async move {
let mut links = handle.link().get().match_name(name.to_string()).execute(); let mut links = handle.link().get().match_name(name.to_string()).execute();
@@ -466,7 +466,7 @@ async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
.flatten() .flatten()
.map(|message| message.header.index) .map(|message| message.header.index)
.ok_or_else(|| { .ok_or_else(|| {
PluginError::Unavailable(format!( OverlayError::Unavailable(format!(
"interface `{name}` disappeared before it could be configured" "interface `{name}` disappeared before it could be configured"
)) ))
})?; })?;
@@ -485,7 +485,7 @@ async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
.execute() .execute()
.await .await
.map_err(|err| { .map_err(|err| {
PluginError::Unavailable(format!("cannot configure interface `{name}`: {err}")) OverlayError::Unavailable(format!("cannot configure interface `{name}`: {err}"))
})?; })?;
} }
@@ -512,7 +512,7 @@ async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
.execute() .execute()
.await .await
.map_err(|err| { .map_err(|err| {
PluginError::Unavailable(format!( OverlayError::Unavailable(format!(
"cannot remove {cidr} from interface `{name}`: {err}" "cannot remove {cidr} from interface `{name}`: {err}"
)) ))
})?; })?;
@@ -526,7 +526,7 @@ async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
.execute() .execute()
.await .await
.map_err(|err| { .map_err(|err| {
PluginError::Unavailable(format!( OverlayError::Unavailable(format!(
"cannot add {cidr} to interface `{name}`: {err}" "cannot add {cidr} to interface `{name}`: {err}"
)) ))
})?; })?;
@@ -9,7 +9,7 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
use super::super::config::Cidr; use super::super::config::Cidr;
use super::super::tun::{MemoryTun, MemoryTunFactory, TunFactory, TunRequest}; use super::super::tun::{MemoryTun, MemoryTunFactory, TunFactory, TunRequest};
@@ -125,9 +125,9 @@ impl MockProvisioner {
} }
} }
fn apply(&self, plan: &InterfacePlan) -> Result<Changes, PluginError> { fn apply(&self, plan: &InterfacePlan) -> Result<Changes, OverlayError> {
if let Some(reason) = &self.failure { if let Some(reason) = &self.failure {
return Err(PluginError::Unavailable(reason.clone())); return Err(OverlayError::Unavailable(reason.clone()));
} }
let ours = self.owned().iter().any(|name| name == &plan.name); let ours = self.owned().iter().any(|name| name == &plan.name);
@@ -179,7 +179,7 @@ impl InterfaceProvisioner for MockProvisioner {
fn reconcile<'a>( fn reconcile<'a>(
&'a self, &'a self,
plan: &'a InterfacePlan, plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>> { ) -> BoxFuture<'a, Result<Provisioned, OverlayError>> {
Box::pin(async move { Box::pin(async move {
let changes = self.apply(plan)?; let changes = self.apply(plan)?;
let device = if changes.create_link { let device = if changes.create_link {
@@ -195,7 +195,7 @@ impl InterfaceProvisioner for MockProvisioner {
}) })
} }
fn remove<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<(), PluginError>> { fn remove<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<(), OverlayError>> {
Box::pin(async move { Box::pin(async move {
self.host.lock().remove(name); self.host.lock().remove(name);
self.owned().retain(|owned| owned != name); self.owned().retain(|owned| owned != name);
@@ -38,7 +38,7 @@ use std::collections::BTreeSet;
use std::sync::Arc; use std::sync::Arc;
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
use super::config::Cidr; use super::config::Cidr;
use super::tun::TunDevice; use super::tun::TunDevice;
@@ -203,19 +203,19 @@ pub fn plan_changes(
current: &InterfaceState, current: &InterfaceState,
plan: &InterfacePlan, plan: &InterfacePlan,
ours: bool, ours: bool,
) -> Result<Changes, PluginError> { ) -> Result<Changes, OverlayError> {
let mut changes = Changes::default(); let mut changes = Changes::default();
match &current.kind { match &current.kind {
LinkKind::Foreign(kind) => { LinkKind::Foreign(kind) => {
return Err(PluginError::Unavailable(format!( return Err(OverlayError::Unavailable(format!(
"`{}` already exists and is a {kind} interface, not one of ours. \ "`{}` already exists and is a {kind} interface, not one of ours. \
Refusing to touch it. Run with a different interface prefix.", Refusing to touch it. Run with a different interface prefix.",
plan.name plan.name
))); )));
} }
LinkKind::Tun if !ours && current.attached => { LinkKind::Tun if !ours && current.attached => {
return Err(PluginError::Unavailable(format!( return Err(OverlayError::Unavailable(format!(
"`{}` already exists and another process is attached to it. \ "`{}` already exists and another process is attached to it. \
That is most likely a second agent on this host in the same \ That is most likely a second agent on this host in the same \
network; give one of them a different interface prefix.", network; give one of them a different interface prefix.",
@@ -293,13 +293,13 @@ pub trait InterfaceProvisioner: Send + Sync + std::fmt::Debug + 'static {
fn reconcile<'a>( fn reconcile<'a>(
&'a self, &'a self,
plan: &'a InterfacePlan, plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>>; ) -> BoxFuture<'a, Result<Provisioned, OverlayError>>;
/// Removes an interface this provisioner created. /// Removes an interface this provisioner created.
/// ///
/// Removing one that is already gone succeeds: this runs on the shutdown /// Removing one that is already gone succeeds: this runs on the shutdown
/// path, where the interface having vanished is the desired outcome. /// path, where the interface having vanished is the desired outcome.
fn remove<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<(), PluginError>>; fn remove<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<(), OverlayError>>;
} }
#[cfg(test)] #[cfg(test)]
@@ -78,7 +78,7 @@ mod linux_impl {
use caps::{CapSet, Capability}; use caps::{CapSet, Capability};
use super::Privilege; use super::Privilege;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
/// Whether this thread holds `CAP_NET_ADMIN` in its permitted set. /// Whether this thread holds `CAP_NET_ADMIN` in its permitted set.
pub fn probe_net_admin() -> Privilege { pub fn probe_net_admin() -> Privilege {
@@ -108,16 +108,16 @@ mod linux_impl {
impl NetAdmin { impl NetAdmin {
/// Raises `CAP_NET_ADMIN` into the effective set. /// Raises `CAP_NET_ADMIN` into the effective set.
pub fn acquire() -> Result<Self, PluginError> { pub fn acquire() -> Result<Self, OverlayError> {
let already = caps::has_cap(None, CapSet::Effective, Capability::CAP_NET_ADMIN) let already = caps::has_cap(None, CapSet::Effective, Capability::CAP_NET_ADMIN)
.map_err(|err| { .map_err(|err| {
PluginError::Unavailable(format!("cannot read capabilities: {err}")) OverlayError::Unavailable(format!("cannot read capabilities: {err}"))
})?; })?;
if already { if already {
return Ok(Self { raised: false }); return Ok(Self { raised: false });
} }
caps::raise(None, CapSet::Effective, Capability::CAP_NET_ADMIN).map_err(|err| { caps::raise(None, CapSet::Effective, Capability::CAP_NET_ADMIN).map_err(|err| {
PluginError::Unavailable(format!( OverlayError::Unavailable(format!(
"cannot raise CAP_NET_ADMIN: {err}. {}", "cannot raise CAP_NET_ADMIN: {err}. {}",
Privilege::how_to_grant("tsunagi") Privilege::how_to_grant("tsunagi")
)) ))
@@ -7,7 +7,7 @@
//! of provisioning and says what to do instead. //! of provisioning and says what to do instead.
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
use super::{InterfacePlan, InterfaceProvisioner, Provisioned}; use super::{InterfacePlan, InterfaceProvisioner, Provisioned};
@@ -31,8 +31,8 @@ impl UnsupportedProvisioner {
} }
} }
fn refusal(&self) -> PluginError { fn refusal(&self) -> OverlayError {
PluginError::Unavailable(format!( OverlayError::Unavailable(format!(
"managing the overlay interface is not implemented on {} yet. \ "managing the overlay interface is not implemented on {} yet. \
Run with `--no-tun` until it is: the tunnels still form, they just \ Run with `--no-tun` until it is: the tunnels still form, they just \
do not reach the operating system.", do not reach the operating system.",
@@ -49,11 +49,11 @@ impl InterfaceProvisioner for UnsupportedProvisioner {
fn reconcile<'a>( fn reconcile<'a>(
&'a self, &'a self,
_plan: &'a InterfacePlan, _plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>> { ) -> BoxFuture<'a, Result<Provisioned, OverlayError>> {
Box::pin(async move { Err(self.refusal()) }) Box::pin(async move { Err(self.refusal()) })
} }
fn remove<'a>(&'a self, _name: &'a str) -> BoxFuture<'a, Result<(), PluginError>> { fn remove<'a>(&'a self, _name: &'a str) -> BoxFuture<'a, Result<(), OverlayError>> {
// Nothing was ever created, so there is nothing to clean up and no // Nothing was ever created, so there is nothing to clean up and no
// reason to fail a shutdown path. // reason to fail a shutdown path.
Box::pin(async move { Ok(()) }) Box::pin(async move { Ok(()) })
@@ -19,7 +19,7 @@ use std::sync::Arc;
use bytes::Bytes; use bytes::Bytes;
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
/// What a device should look like once created. /// What a device should look like once created.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -71,7 +71,7 @@ pub trait TunDevice: Send + Sync + std::fmt::Debug + 'static {
fn recv(&self) -> BoxFuture<'_, Option<Bytes>>; fn recv(&self) -> BoxFuture<'_, Option<Bytes>>;
/// Delivers a packet to the operating system. /// Delivers a packet to the operating system.
fn send<'a>(&'a self, packet: Bytes) -> BoxFuture<'a, Result<(), PluginError>>; fn send<'a>(&'a self, packet: Bytes) -> BoxFuture<'a, Result<(), OverlayError>>;
} }
/// Creates packet interfaces. /// Creates packet interfaces.
@@ -83,7 +83,7 @@ pub trait TunFactory: Send + Sync + std::fmt::Debug + 'static {
fn create<'a>( fn create<'a>(
&'a self, &'a self,
request: TunRequest, request: TunRequest,
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, PluginError>>; ) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, OverlayError>>;
/// Applies a changed request to an interface that already exists. /// Applies a changed request to an interface that already exists.
/// ///
@@ -94,7 +94,7 @@ pub trait TunFactory: Send + Sync + std::fmt::Debug + 'static {
/// ///
/// The default does nothing, which is right for a factory that only /// The default does nothing, which is right for a factory that only
/// attaches to an interface somebody else prepared. /// attaches to an interface somebody else prepared.
fn reconfigure<'a>(&'a self, _request: TunRequest) -> BoxFuture<'a, Result<(), PluginError>> { fn reconfigure<'a>(&'a self, _request: TunRequest) -> BoxFuture<'a, Result<(), OverlayError>> {
Box::pin(async move { Ok(()) }) Box::pin(async move { Ok(()) })
} }
@@ -162,7 +162,7 @@ impl TunDevice for MemoryTun {
Box::pin(async move { self.from_os_rx.lock().await.recv().await }) Box::pin(async move { self.from_os_rx.lock().await.recv().await })
} }
fn send<'a>(&'a self, packet: Bytes) -> BoxFuture<'a, Result<(), PluginError>> { fn send<'a>(&'a self, packet: Bytes) -> BoxFuture<'a, Result<(), OverlayError>> {
Box::pin(async move { Box::pin(async move {
let _ = self.to_os_tx.send(packet); let _ = self.to_os_tx.send(packet);
Ok(()) Ok(())
@@ -223,7 +223,7 @@ impl TunFactory for MemoryTunFactory {
fn create<'a>( fn create<'a>(
&'a self, &'a self,
request: TunRequest, request: TunRequest,
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, PluginError>> { ) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, OverlayError>> {
Box::pin(async move { Box::pin(async move {
let device = MemoryTun::new(request.name, request.mtu); let device = MemoryTun::new(request.name, request.mtu);
let mut guard = match self.created.lock() { let mut guard = match self.created.lock() {
@@ -248,7 +248,7 @@ mod system {
use super::{TunDevice, TunRequest}; use super::{TunDevice, TunRequest};
use crate::BoxFuture; use crate::BoxFuture;
use crate::dataplane::PluginError; use crate::overlay::OverlayError;
/// A real TUN interface. /// A real TUN interface.
/// ///
@@ -303,14 +303,14 @@ mod system {
}) })
} }
fn send<'a>(&'a self, packet: Bytes) -> BoxFuture<'a, Result<(), PluginError>> { fn send<'a>(&'a self, packet: Bytes) -> BoxFuture<'a, Result<(), OverlayError>> {
Box::pin(async move { Box::pin(async move {
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
let mut writer = self.writer.lock().await; let mut writer = self.writer.lock().await;
writer writer
.write_all(&packet) .write_all(&packet)
.await .await
.map_err(|err| PluginError::Other(format!("tun write failed: {err}"))) .map_err(|err| OverlayError::Other(format!("tun write failed: {err}")))
}) })
} }
} }
@@ -320,7 +320,7 @@ mod system {
/// Synchronous, and deliberately so: the caller holds a capability guard /// Synchronous, and deliberately so: the caller holds a capability guard
/// across this call, and such a guard must not span an `await` because /// across this call, and such a guard must not span an `await` because
/// Linux capabilities are per thread. /// Linux capabilities are per thread.
pub(crate) fn open_tun(request: &TunRequest) -> Result<Arc<dyn TunDevice>, PluginError> { pub(crate) fn open_tun(request: &TunRequest) -> Result<Arc<dyn TunDevice>, OverlayError> {
let mut config = tun::Configuration::default(); let mut config = tun::Configuration::default();
config.tun_name(&request.name); config.tun_name(&request.name);
config.platform_config(|platform| { config.platform_config(|platform| {
@@ -334,7 +334,7 @@ mod system {
// information, so the flags match when attaching to one. // information, so the flags match when attaching to one.
let device = tun::create_as_async(&config).map_err(|err| { let device = tun::create_as_async(&config).map_err(|err| {
PluginError::Unavailable(format!( OverlayError::Unavailable(format!(
"cannot create the TUN interface `{}`: {err}. Creating one needs \ "cannot create the TUN interface `{}`: {err}. Creating one needs \
CAP_NET_ADMIN; grant it with `setcap cap_net_admin+p`, or run with \ CAP_NET_ADMIN; grant it with `setcap cap_net_admin+p`, or run with \
`--no-tun` to keep the tunnels off the operating system.", `--no-tun` to keep the tunnels off the operating system.",