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),
}
/// 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.
#[derive(Debug)]
pub(crate) enum PluginRequest {
@@ -43,8 +43,8 @@ use crate::identity::NetworkId;
use super::keys::{WgPublicKey, WgSecretKey};
use super::overlay::overlay_address;
use super::packet::IpHeader;
use super::tun::TunDevice;
use crate::overlay::packet::IpHeader;
use crate::overlay::tun::TunDevice;
use crate::state::Ipv4Range;
/// How often WireGuard's own timers are driven.
+16 -16
View File
@@ -42,34 +42,34 @@
//! See `docs/wireguard.md` for the full picture.
pub mod announcement;
pub mod config;
pub mod device;
pub mod keys;
pub mod overlay;
pub mod packet;
pub mod plugin;
pub mod provision;
pub mod store;
pub mod tun;
pub use crate::state::Ipv4Range;
pub use announcement::{ValidatedAnnouncement, WgAnnouncement};
pub use config::{Cidr, DEFAULT_INTERFACE_PREFIX, MAX_INTERFACE_NAME_LEN, 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 packet::IpHeader;
pub use plugin::{
DEFAULT_MTU, MIN_MTU, NetworkOverview, PeerOverview, WIREGUARD_OVERHEAD, WIREGUARD_PROTOCOL,
WireguardConfig, WireguardPlugin,
};
pub use provision::{
// The interface, its addresses and how it is created belong to the system
// level now: one agent has one interface, and no protocol owns it. Re-exported
// here while callers are moved over.
pub use crate::overlay::provision::{
Changes, InterfacePlan, InterfaceProvisioner, InterfaceState, LinkKind, ManagedTunFactory,
MockHost, MockProvisioner, Privilege, Provisioned, UnsupportedProvisioner, plan_changes,
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 tun::{MemoryTun, MemoryTunFactory, TunDevice, TunFactory, TunRequest, address_is_local};
#[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 super::announcement::{ValidatedAnnouncement, WgAnnouncement};
use super::config::{DEFAULT_INTERFACE_PREFIX, interface_name};
use super::device::{PeerSummary, WireguardDevice};
use super::keys::{WgPublicKey, WgSecretKey};
use super::overlay::{OVERLAY_PREFIX_LEN, overlay_address, overlay_prefix};
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;
/// The protocol identifier this plugin announces.
@@ -603,7 +603,7 @@ impl Worker {
// kind that has been wrong here before.
let missing_v4 = match (own_v4, interface.as_deref(), 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);
if let Some(state) = shared.networks.get_mut(&network) {
+1
View File
@@ -44,6 +44,7 @@ pub mod error;
pub mod identity;
pub mod ipc;
pub mod net;
pub mod overlay;
pub mod proto;
pub mod state;
pub mod storage;
@@ -11,10 +11,11 @@
use std::net::{IpAddr, Ipv6Addr};
use crate::dataplane::PluginError;
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.
pub const MAX_INTERFACE_NAME_LEN: usize = 15;
@@ -33,13 +34,13 @@ pub struct Cidr {
impl Cidr {
/// 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 {
IpAddr::V4(_) => 32,
IpAddr::V6(_) => 128,
};
if prefix_len > max {
return Err(PluginError::Other(format!(
return Err(OverlayError::Other(format!(
"prefix length /{prefix_len} is impossible for {addr}"
)));
}
@@ -50,7 +51,7 @@ impl Cidr {
pub fn host(addr: Ipv6Addr) -> Self {
Self {
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
/// agents on the same host in the same network must be given different
/// 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() {
return Err(PluginError::Other(
return Err(OverlayError::Other(
"interface prefix must not be empty".into(),
));
}
@@ -76,12 +77,12 @@ pub fn interface_name(prefix: &str, network: NetworkId) -> Result<String, Plugin
.chars()
.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(),
));
}
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"
)));
}
+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 crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
use super::super::config::Cidr;
use super::super::tun::{TunDevice, TunFactory, TunRequest};
use super::{InterfacePlan, InterfaceProvisioner};
/// 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)?];
if let Some(address) = request.address_v4 {
addresses.push(Cidr::new(address.into(), request.prefix_len_v4)?);
@@ -54,7 +54,7 @@ impl TunFactory for ManagedTunFactory {
fn create<'a>(
&'a self,
request: TunRequest,
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, PluginError>> {
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, OverlayError>> {
Box::pin(async move {
let plan = plan_for(&request)?;
let provisioned = self.provisioner.reconcile(&plan).await?;
@@ -67,7 +67,7 @@ impl TunFactory for ManagedTunFactory {
// Reaching here would mean the interface already existed and
// was held open by us, which cannot be true on the path that
// creates a device.
PluginError::Other(format!(
OverlayError::Other(format!(
"interface `{}` was reconciled but no device came back",
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 {
let plan = plan_for(&request)?;
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 crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
use super::super::config::Cidr;
use super::super::tun::{TunDevice, TunRequest};
@@ -71,7 +71,7 @@ enum Command {
Stop,
}
type Reply<T> = mpsc::Sender<Result<T, PluginError>>;
type Reply<T> = mpsc::Sender<Result<T, OverlayError>>;
/// The configuration half of a reconciliation.
struct Configure {
@@ -97,17 +97,17 @@ pub struct NetlinkProvisioner {
impl NetlinkProvisioner {
/// 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() {
Privilege::Available => {}
Privilege::Missing(reason) => {
return Err(PluginError::Unavailable(format!(
return Err(OverlayError::Unavailable(format!(
"{reason}. {}",
Privilege::how_to_grant(&current_program())
)));
}
Privilege::Unsupported => {
return Err(PluginError::Unavailable(
return Err(OverlayError::Unavailable(
"interface management is not compiled in".to_string(),
));
}
@@ -118,7 +118,7 @@ impl NetlinkProvisioner {
.name("tsunagi-netlink".to_string())
.spawn(move || netlink_thread(requests))
.map_err(|err| {
PluginError::Unavailable(format!("cannot start the netlink thread: {err}"))
OverlayError::Unavailable(format!("cannot start the netlink thread: {err}"))
})?;
Ok(Self {
@@ -132,13 +132,13 @@ impl NetlinkProvisioner {
fn call<T: Send + 'static>(
&self,
make: impl FnOnce(Reply<T>) -> Command,
) -> Result<T, PluginError> {
) -> Result<T, OverlayError> {
let (reply_tx, reply_rx) = mpsc::channel();
self.commands
.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(|_| {
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
/// 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 _guard = NetAdmin::acquire()?;
super::super::tun::open_tun(&request)
@@ -174,7 +174,7 @@ impl InterfaceProvisioner for NetlinkProvisioner {
fn reconcile<'a>(
&'a self,
plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>> {
) -> BoxFuture<'a, Result<Provisioned, OverlayError>> {
Box::pin(async move {
let current = self.call(|reply| Command::Observe(plan.name.clone(), reply))?;
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 {
// Dropping the device is what removes the interface; the explicit
// 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}");
match command {
Command::Observe(_, reply) => {
let _ = reply.send(Err(PluginError::Unavailable(message)));
let _ = reply.send(Err(OverlayError::Unavailable(message)));
}
Command::Delete(_, reply) | Command::Configure(_, reply) => {
let _ = reply.send(Err(PluginError::Unavailable(message)));
let _ = reply.send(Err(OverlayError::Unavailable(message)));
}
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.
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
F: std::future::Future<Output = Result<T, PluginError>>,
F: std::future::Future<Output = Result<T, OverlayError>>,
{
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 result = work(handle).await;
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 {
let mut links = handle.link().get().match_name(name.to_string()).execute();
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() {
return Ok(InterfaceState::absent());
}
return Err(PluginError::Unavailable(format!(
return Err(OverlayError::Unavailable(format!(
"cannot read interface `{name}`: {err}"
)));
}
@@ -397,7 +397,7 @@ async fn observe(name: &str) -> Result<InterfaceState, PluginError> {
.set_link_index_filter(index)
.execute();
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)
&& !is_link_local(cidr.addr)
@@ -440,7 +440,7 @@ fn cidr(addr: IpAddr, prefix_len: u8) -> Option<Cidr> {
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 {
let mut links = handle.link().get().match_name(name.to_string()).execute();
let index = match links.try_next().await {
@@ -449,13 +449,13 @@ async fn delete_link(name: &str) -> Result<(), PluginError> {
Ok(None) | Err(_) => return Ok(()),
};
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
}
async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
async fn configure_link(configure: &Configure) -> Result<(), OverlayError> {
let name = configure.name.as_str();
with_netlink(|handle| async move {
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()
.map(|message| message.header.index)
.ok_or_else(|| {
PluginError::Unavailable(format!(
OverlayError::Unavailable(format!(
"interface `{name}` disappeared before it could be configured"
))
})?;
@@ -485,7 +485,7 @@ async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
.execute()
.await
.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()
.await
.map_err(|err| {
PluginError::Unavailable(format!(
OverlayError::Unavailable(format!(
"cannot remove {cidr} from interface `{name}`: {err}"
))
})?;
@@ -526,7 +526,7 @@ async fn configure_link(configure: &Configure) -> Result<(), PluginError> {
.execute()
.await
.map_err(|err| {
PluginError::Unavailable(format!(
OverlayError::Unavailable(format!(
"cannot add {cidr} to interface `{name}`: {err}"
))
})?;
@@ -9,7 +9,7 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
use super::super::config::Cidr;
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 {
return Err(PluginError::Unavailable(reason.clone()));
return Err(OverlayError::Unavailable(reason.clone()));
}
let ours = self.owned().iter().any(|name| name == &plan.name);
@@ -179,7 +179,7 @@ impl InterfaceProvisioner for MockProvisioner {
fn reconcile<'a>(
&'a self,
plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>> {
) -> BoxFuture<'a, Result<Provisioned, OverlayError>> {
Box::pin(async move {
let changes = self.apply(plan)?;
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 {
self.host.lock().remove(name);
self.owned().retain(|owned| owned != name);
@@ -38,7 +38,7 @@ use std::collections::BTreeSet;
use std::sync::Arc;
use crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
use super::config::Cidr;
use super::tun::TunDevice;
@@ -203,19 +203,19 @@ pub fn plan_changes(
current: &InterfaceState,
plan: &InterfacePlan,
ours: bool,
) -> Result<Changes, PluginError> {
) -> Result<Changes, OverlayError> {
let mut changes = Changes::default();
match &current.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. \
Refusing to touch it. Run with a different interface prefix.",
plan.name
)));
}
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. \
That is most likely a second agent on this host in the same \
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>(
&'a self,
plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>>;
) -> BoxFuture<'a, Result<Provisioned, OverlayError>>;
/// Removes an interface this provisioner created.
///
/// Removing one that is already gone succeeds: this runs on the shutdown
/// 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)]
@@ -78,7 +78,7 @@ mod linux_impl {
use caps::{CapSet, Capability};
use super::Privilege;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
/// Whether this thread holds `CAP_NET_ADMIN` in its permitted set.
pub fn probe_net_admin() -> Privilege {
@@ -108,16 +108,16 @@ mod linux_impl {
impl NetAdmin {
/// 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)
.map_err(|err| {
PluginError::Unavailable(format!("cannot read capabilities: {err}"))
OverlayError::Unavailable(format!("cannot read capabilities: {err}"))
})?;
if already {
return Ok(Self { raised: false });
}
caps::raise(None, CapSet::Effective, Capability::CAP_NET_ADMIN).map_err(|err| {
PluginError::Unavailable(format!(
OverlayError::Unavailable(format!(
"cannot raise CAP_NET_ADMIN: {err}. {}",
Privilege::how_to_grant("tsunagi")
))
@@ -7,7 +7,7 @@
//! of provisioning and says what to do instead.
use crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
use super::{InterfacePlan, InterfaceProvisioner, Provisioned};
@@ -31,8 +31,8 @@ impl UnsupportedProvisioner {
}
}
fn refusal(&self) -> PluginError {
PluginError::Unavailable(format!(
fn refusal(&self) -> OverlayError {
OverlayError::Unavailable(format!(
"managing the overlay interface is not implemented on {} yet. \
Run with `--no-tun` until it is: the tunnels still form, they just \
do not reach the operating system.",
@@ -49,11 +49,11 @@ impl InterfaceProvisioner for UnsupportedProvisioner {
fn reconcile<'a>(
&'a self,
_plan: &'a InterfacePlan,
) -> BoxFuture<'a, Result<Provisioned, PluginError>> {
) -> BoxFuture<'a, Result<Provisioned, OverlayError>> {
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
// reason to fail a shutdown path.
Box::pin(async move { Ok(()) })
@@ -19,7 +19,7 @@ use std::sync::Arc;
use bytes::Bytes;
use crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
/// What a device should look like once created.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -71,7 +71,7 @@ pub trait TunDevice: Send + Sync + std::fmt::Debug + 'static {
fn recv(&self) -> BoxFuture<'_, Option<Bytes>>;
/// 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.
@@ -83,7 +83,7 @@ pub trait TunFactory: Send + Sync + std::fmt::Debug + 'static {
fn create<'a>(
&'a self,
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.
///
@@ -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
/// 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(()) })
}
@@ -162,7 +162,7 @@ impl TunDevice for MemoryTun {
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 {
let _ = self.to_os_tx.send(packet);
Ok(())
@@ -223,7 +223,7 @@ impl TunFactory for MemoryTunFactory {
fn create<'a>(
&'a self,
request: TunRequest,
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, PluginError>> {
) -> BoxFuture<'a, Result<Arc<dyn TunDevice>, OverlayError>> {
Box::pin(async move {
let device = MemoryTun::new(request.name, request.mtu);
let mut guard = match self.created.lock() {
@@ -248,7 +248,7 @@ mod system {
use super::{TunDevice, TunRequest};
use crate::BoxFuture;
use crate::dataplane::PluginError;
use crate::overlay::OverlayError;
/// 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 {
use tokio::io::AsyncWriteExt;
let mut writer = self.writer.lock().await;
writer
.write_all(&packet)
.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
/// across this call, and such a guard must not span an `await` because
/// 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();
config.tun_name(&request.name);
config.platform_config(|platform| {
@@ -334,7 +334,7 @@ mod system {
// information, so the flags match when attaching to one.
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 \
CAP_NET_ADMIN; grant it with `setcap cap_net_admin+p`, or run with \
`--no-tun` to keep the tunnels off the operating system.",