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:
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+10
-9
@@ -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"
|
||||
)));
|
||||
}
|
||||
@@ -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;
|
||||
+5
-5
@@ -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?;
|
||||
+27
-27
@@ -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(¤t_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(¤t, 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}"
|
||||
))
|
||||
})?;
|
||||
+5
-5
@@ -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);
|
||||
+6
-6
@@ -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 ¤t.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)]
|
||||
+4
-4
@@ -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")
|
||||
))
|
||||
+5
-5
@@ -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.",
|
||||
Reference in New Issue
Block a user