Skip to main content

netcore/
connection.rs

1//! Layer 2 — what users think a network connection *is*.
2//!
3//! A [`Connection`] joins a [`Link`](crate::Link), its addresses, its
4//! default-route gateway (with L2 status), its resolvers, and any
5//! NetworkManager profile into one coherent unit. This is the primary public
6//! surface: `jip` (no args) renders a list of these.
7
8use std::net::IpAddr;
9use std::time::Duration;
10
11use serde::{Deserialize, Serialize};
12
13use crate::link::{Addr, Link, MacAddr, NeighState};
14
15/// Stable identifier for a connection. When a NetworkManager profile is
16/// present, this is the profile name (e.g. `"Wired connection 1"`). Otherwise
17/// it is the link name.
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct ConnectionId(pub String);
20
21impl std::fmt::Display for ConnectionId {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.write_str(&self.0)
24    }
25}
26
27impl From<&str> for ConnectionId {
28    fn from(s: &str) -> Self {
29        ConnectionId(s.to_owned())
30    }
31}
32
33/// Address family.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[serde(rename_all = "lowercase")]
36pub enum Family {
37    /// IPv4.
38    V4,
39    /// IPv6.
40    V6,
41}
42
43impl Family {
44    /// Return the [`Family`] for a given IP address.
45    pub fn of(ip: IpAddr) -> Self {
46        match ip {
47            IpAddr::V4(_) => Family::V4,
48            IpAddr::V6(_) => Family::V6,
49        }
50    }
51}
52
53/// A user-facing network connection: one link plus its interpreted state.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Connection {
56    /// Stable identifier, preferring the NM profile name over the link name.
57    pub id: ConnectionId,
58    /// What kind of connection this is (Ethernet, Wifi, VPN, etc.).
59    pub medium: Medium,
60    /// The underlying kernel link, preserved for detail views and `jip raw`.
61    pub link: Link,
62    /// Full list of addresses. Rendering uses [`Connection::primary_v4`] /
63    /// [`Connection::primary_v6`] by default and collapses the rest.
64    pub addresses: Vec<Addr>,
65    /// The single IPv4 to show in the default view.
66    pub primary_v4: Option<IpAddr>,
67    /// The single IPv6 to show in the default view. Non-deprecated, global.
68    pub primary_v6: Option<IpAddr>,
69    /// Present when the IPv4 address is dynamic (DHCP).
70    pub v4_lease: Option<DhcpLease>,
71    /// L3 gateway and its L2 reachability state.
72    pub gateway: Option<Gateway>,
73    /// Per-link resolvers (from `resolvectl`). Falls back to the stub when
74    /// per-link info is unavailable.
75    pub dns: Vec<IpAddr>,
76    /// True when the default route egresses through this connection.
77    pub is_default: bool,
78    /// Metric of the default route via this connection, if any. Lower = preferred.
79    pub default_metric: Option<u32>,
80    /// NetworkManager profile metadata, when one is attached.
81    pub profile: Option<Profile>,
82}
83
84/// What kind of connection this is from the user's perspective.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(tag = "type", rename_all = "snake_case")]
87pub enum Medium {
88    /// Wired Ethernet (physical NIC).
89    Ethernet,
90    /// IEEE 802.11 wireless interface.
91    Wifi {
92        /// SSID of the associated BSS, if currently connected.
93        ssid: Option<String>,
94        signal: Option<WifiSignal>,
95        security: Option<WifiSecurity>,
96    },
97    /// docker0, bridges, veth, tap, etc. — not a VPN, not a physical NIC.
98    Virtual { kind: VirtualKind },
99    /// VPN tunnel.
100    Vpn { kind: VpnKind },
101    /// Mobile broadband (LTE/5G).
102    Cellular {
103        /// Carrier name, when the modem reports it.
104        operator: Option<String>,
105    },
106    /// Loopback interface.
107    Loopback,
108}
109
110/// Subtype of a virtual (`Medium::Virtual`) link.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113pub enum VirtualKind {
114    /// Docker-managed bridge (name starts with `docker` or `br-`).
115    Docker,
116    /// Generic Linux bridge.
117    Bridge,
118    /// Virtual Ethernet pair.
119    Veth,
120    /// TAP device (L2 virtual interface).
121    Tap,
122    /// Unrecognised virtual interface.
123    Other,
124}
125
126/// Subtype of a VPN (`Medium::Vpn`) link.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub enum VpnKind {
130    /// WireGuard tunnel.
131    Wireguard,
132    /// OpenVPN tunnel.
133    OpenVpn,
134    /// Generic point-to-point tunnel.
135    Tun,
136    /// L2 tunnel (some VPNs create a tap device).
137    Tap,
138    /// Unrecognised VPN type.
139    Other,
140}
141
142/// Signal strength and rate information for an associated wifi BSS.
143#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
144pub struct WifiSignal {
145    /// dBm, typically -30 (great) to -90 (unusable).
146    pub rssi_dbm: i32,
147    /// Optional signal quality as a 0–100 percentage if the driver reports it.
148    pub quality_pct: Option<u8>,
149    /// Link rate in Mbps, if known.
150    pub rate_mbps: Option<u32>,
151}
152
153impl Eq for WifiSignal {}
154
155/// Security mode of the associated wifi BSS.
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158pub enum WifiSecurity {
159    /// No authentication.
160    Open,
161    /// WEP (deprecated).
162    Wep,
163    Wpa2Personal,
164    Wpa2Enterprise,
165    Wpa3Personal,
166    Wpa3Enterprise,
167    /// An unrecognised security mode; the raw string is preserved.
168    Other(String),
169}
170
171/// A nearby wifi access point from an NM scan.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct AccessPoint {
174    /// Network name. Empty string when the SSID is hidden.
175    pub ssid: String,
176    /// BSSID (MAC address of the AP).
177    pub bssid: String,
178    /// Signal strength.
179    pub signal: WifiSignal,
180    /// Channel frequency in MHz (e.g. 2412, 5180).
181    pub frequency_mhz: u32,
182    /// Security classification decoded from NM's WpaFlags/RsnFlags.
183    pub security: WifiSecurity,
184    /// `true` when this is the currently associated AP.
185    pub in_use: bool,
186}
187
188/// The L3 default-route next-hop plus its L2 ARP/ND state. Having both lets
189/// the diagnostician say "your route points at 192.168.1.1 but it's not
190/// answering ARP" rather than just "unreachable".
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192pub struct Gateway {
193    pub ip: IpAddr,
194    pub lladdr: Option<MacAddr>,
195    pub l2_state: NeighState,
196    pub is_router: bool,
197}
198
199/// Metadata from NetworkManager (or any future connection manager).
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
201pub struct Profile {
202    pub name: String,
203    /// NM connection UUID.
204    pub uuid: String,
205    pub autoconnect: bool,
206    /// NM's raw type string: `"802-3-ethernet"`, `"wifi"`, `"vpn"`, `"bridge"`, ...
207    pub kind: String,
208    /// Interface this profile binds to, if any. `None` for VPN and unbound profiles.
209    pub iface: Option<String>,
210    /// Whether this profile is currently active (has an active connection in NM).
211    pub active: bool,
212}
213
214/// Summary of the active DHCP lease on the IPv4 address.
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216pub struct DhcpLease {
217    /// Remaining lease time. Computed from `valid_lft` at inventory time.
218    pub expires_in: Duration,
219    /// The DHCP server that granted the lease, when known. Not surfaced by
220    /// `ip -j`; requires NM or reading dhclient leases.
221    pub server: Option<IpAddr>,
222}