Skip to main content

net_lattice/
lib.rs

1//! Cross-platform inspection, mutation, and monitoring of operating-system
2//! networking through a strongly typed Rust API.
3//!
4//! Start with [`Lattice::connect`] to inspect interfaces, addresses, routes,
5//! DNS configuration, and neighbor tables; perform supported mutations; or
6//! subscribe to network change events.
7//!
8//! # Example
9//!
10//! ```no_run
11//! use net_lattice::{Lattice, Result};
12//!
13//! fn main() -> Result<()> {
14//!     let lattice = Lattice::connect()?;
15//!     for interface in lattice.interfaces()? {
16//!         println!("{interface:?}");
17//!     }
18//!     Ok(())
19//! }
20//! ```
21//!
22//! # Facade design
23//!
24//! Re-exports the types consumers need from `net-lattice-model` and
25//! `net-lattice-ip`, selects a default backend based on `cfg(target_os =
26//! "...")`, and enforces model convergence: `net-lattice-platform`'s generic
27//! provider traits are constrained here to Net Lattice's own model types,
28//! without `net-lattice-platform` ever depending on `net-lattice-model`. See
29//! ARCHITECTURE.md for the full rationale.
30
31/// Async event adapters, enabled by the `async` feature.
32#[cfg(feature = "async")]
33pub use net_lattice_async::EventStream;
34pub use net_lattice_core::{Error, Id, PlatformErrorCode, Result};
35pub use net_lattice_ip::{
36    Ipv4Address, Ipv4Network, Ipv4PrefixLength, Ipv6Address, Ipv6Network, Ipv6PrefixLength,
37};
38pub use net_lattice_model::dns::{DnsConfig, NewDnsConfig};
39pub use net_lattice_model::event::{ChangeKind, Event, EventDomain, EventFilter};
40pub use net_lattice_model::ifaddr::{InterfaceAddress, InterfaceAddressId, NewInterfaceAddress};
41pub use net_lattice_model::interface::{
42    AdminState, Interface, InterfaceId, InterfaceKind, OperationalState,
43};
44pub use net_lattice_model::mac::MacAddress;
45pub use net_lattice_model::neighbor::{NeighborEntry, NeighborId, NeighborState};
46pub use net_lattice_model::route::{Route, RouteId};
47pub use net_lattice_model::{IpAddress, Network};
48#[cfg(feature = "async")]
49pub use net_lattice_platform::TokioEventProvider;
50pub use net_lattice_platform::{
51    AddressMutator, AddressProvider, Capability, CapabilityProvider, DnsMutator, DnsProvider,
52    EventProvider, EventReceiver, InterfaceProvider, NeighborProvider, RouteProvider,
53};
54
55/// Contracts for implementing a third-party Net Lattice backend.
56///
57/// These traits are a supported extension API. A backend must preserve the
58/// documented read, mutation, event-delivery, and cancellation semantics of
59/// each trait it implements. The root re-exports remain available for
60/// compatibility; new backend code may import from this module.
61pub mod backend {
62    pub use crate::LatticeBackend;
63    pub use net_lattice_platform::{
64        AddressMutator, AddressProvider, CapabilityProvider, DnsMutator, DnsProvider,
65        EventProvider, EventReceiver, EventSender, InterfaceProvider, NeighborProvider,
66        RouteProvider,
67    };
68    #[cfg(feature = "async")]
69    pub use net_lattice_platform::{TokioEventProvider, TokioEventReceiver, TokioEventSender};
70}
71
72/// Bound satisfied by any backend usable with [`Lattice`].
73///
74/// This is where model convergence is enforced: a backend whose
75/// `RouteProvider::Route` (or `InterfaceProvider::Interface`,
76/// `DnsProvider::DnsConfig`, `NeighborProvider::NeighborEntry`,
77/// `AddressProvider::InterfaceAddress`, `AddressMutator`'s input/output,
78/// `EventProvider::Event`) is not
79/// literally `net_lattice_model`'s corresponding type fails to satisfy this
80/// trait and cannot be used with [`Lattice`] — a compile error at the point
81/// the backend is wired in, not a runtime surprise. See ARCHITECTURE.md's
82/// `net-lattice` section. `CapabilityProvider` has no associated type to
83/// converge — it reports plain runtime facts about the connected system,
84/// not domain objects — so it's required as-is.
85pub trait LatticeBackend:
86    RouteProvider<Route = Route>
87    + InterfaceProvider<Interface = Interface>
88    + DnsMutator<NewDnsConfig = NewDnsConfig, DnsConfig = DnsConfig>
89    + NeighborProvider<NeighborEntry = NeighborEntry>
90    + AddressProvider<InterfaceAddress = InterfaceAddress>
91    + AddressMutator<NewInterfaceAddress = NewInterfaceAddress, InterfaceAddress = InterfaceAddress>
92    + EventProvider<Event = Event, EventFilter = EventFilter>
93    + CapabilityProvider
94{
95}
96
97impl<B> LatticeBackend for B where
98    B: RouteProvider<Route = Route>
99        + InterfaceProvider<Interface = Interface>
100        + DnsMutator<NewDnsConfig = NewDnsConfig, DnsConfig = DnsConfig>
101        + NeighborProvider<NeighborEntry = NeighborEntry>
102        + AddressProvider<InterfaceAddress = InterfaceAddress>
103        + AddressMutator<
104            NewInterfaceAddress = NewInterfaceAddress,
105            InterfaceAddress = InterfaceAddress,
106        > + EventProvider<Event = Event, EventFilter = EventFilter>
107        + CapabilityProvider
108{
109}
110
111/// The top-level entry point: a connected backend for the current system.
112pub struct Lattice<B: LatticeBackend> {
113    backend: B,
114}
115
116impl<B: LatticeBackend> Lattice<B> {
117    pub fn routes(&self) -> Result<Vec<Route>> {
118        self.backend.routes()
119    }
120
121    pub fn add_route(&self, route: Route) -> Result<()> {
122        self.backend.add_route(route)
123    }
124
125    pub fn remove_route(&self, route: Route) -> Result<()> {
126        self.backend.remove_route(route)
127    }
128
129    pub fn interfaces(&self) -> Result<Vec<Interface>> {
130        self.backend.interfaces()
131    }
132
133    pub fn dns_config(&self) -> Result<DnsConfig> {
134        self.backend.dns_config()
135    }
136
137    /// Replaces resolver configuration and returns the resulting observed
138    /// resolver view.
139    pub fn set_dns_config(&self, config: NewDnsConfig) -> Result<DnsConfig> {
140        self.backend.set_dns_config(config)
141    }
142
143    pub fn neighbors(&self) -> Result<Vec<NeighborEntry>> {
144        self.backend.neighbors()
145    }
146
147    pub fn addresses(&self) -> Result<Vec<InterfaceAddress>> {
148        self.backend.addresses()
149    }
150
151    /// Assigns an address to an interface and returns the canonical record
152    /// observed from the operating system after creation.
153    pub fn add_address(&self, address: NewInterfaceAddress) -> Result<InterfaceAddress> {
154        self.backend.add_address(address)
155    }
156
157    /// Removes the observed interface address.
158    pub fn remove_address(&self, address: InterfaceAddress) -> Result<()> {
159        self.backend.remove_address(address)
160    }
161
162    /// The full set of runtime-dependent [`Capability`] flags the connected
163    /// backend currently has available.
164    pub fn capabilities(&self) -> Capability {
165        self.backend.capabilities()
166    }
167
168    /// Whether the connected backend currently has `capability` available.
169    /// Shorthand for `self.capabilities().contains(capability)`.
170    pub fn supports(&self, capability: Capability) -> bool {
171        self.backend.capabilities().contains(capability)
172    }
173
174    /// Subscribes to change notifications. See [`EventReceiver`] for how to
175    /// consume the returned events. Prefer `recv`/`try_recv`/`recv_timeout`
176    /// when errors must be handled explicitly; `Iterator` terminates on any
177    /// receiver error.
178    ///
179    /// ```no_run
180    /// use net_lattice::{Lattice, Result};
181    ///
182    /// fn main() -> Result<()> {
183    ///     let lattice = Lattice::connect()?;
184    ///     let events = lattice.watch()?;
185    ///     loop {
186    ///         println!("{:?}", events.recv()?);
187    ///     }
188    /// }
189    /// ```
190    pub fn watch(&self) -> Result<EventReceiver<Event>> {
191        self.ensure_monitoring()?;
192        self.backend.watch()
193    }
194
195    /// Subscribes to async change notifications selected by `filter`.
196    ///
197    /// This is the Stage 0.11 async watcher API. It has the same filter
198    /// semantics as [`Self::watch_filtered`].
199    ///
200    /// ```no_run
201    /// use futures::StreamExt;
202    /// use net_lattice::{EventFilter, Lattice, Result};
203    ///
204    /// async fn monitor() -> Result<()> {
205    ///     let lattice = Lattice::connect()?;
206    ///     let mut events = lattice.watch_async(EventFilter::ALL)?;
207    ///     while let Some(event) = events.next().await {
208    ///         println!("{:?}", event?);
209    ///     }
210    ///     Ok(())
211    /// }
212    /// ```
213    #[cfg(feature = "async")]
214    pub fn watch_async(&self, filter: EventFilter) -> Result<EventStream<Event>>
215    where
216        B: TokioEventProvider<Event = Event, EventFilter = EventFilter>,
217    {
218        self.ensure_monitoring()?;
219        Ok(net_lattice_async::from_tokio_receiver(
220            self.backend.watch_tokio(filter)?,
221        ))
222    }
223
224    /// Subscribes to change notifications selected by `filter`.
225    pub fn watch_filtered(&self, filter: EventFilter) -> Result<EventReceiver<Event>> {
226        self.ensure_monitoring()?;
227        self.backend.watch_filtered(filter)
228    }
229
230    fn ensure_monitoring(&self) -> Result<()> {
231        if self.supports(Capability::MONITORING) {
232            Ok(())
233        } else {
234            Err(Error::Unsupported)
235        }
236    }
237}
238
239#[cfg(target_os = "linux")]
240impl Lattice<net_lattice_backend_linux::LinuxBackend> {
241    /// Connects using the default backend for the current platform.
242    pub fn connect() -> Result<Self> {
243        Ok(Self {
244            backend: net_lattice_backend_linux::LinuxBackend::new()?,
245        })
246    }
247}
248
249#[cfg(target_os = "windows")]
250impl Lattice<net_lattice_backend_windows::WindowsBackend> {
251    /// Connects using the default backend for the current platform.
252    pub fn connect() -> Result<Self> {
253        Ok(Self {
254            backend: net_lattice_backend_windows::WindowsBackend::new()?,
255        })
256    }
257}
258
259#[cfg(target_os = "macos")]
260impl Lattice<net_lattice_backend_darwin::DarwinBackend> {
261    /// Connects using the default backend for the current platform.
262    pub fn connect() -> Result<Self> {
263        Ok(Self {
264            backend: net_lattice_backend_darwin::DarwinBackend::new()?,
265        })
266    }
267}