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}