1#![allow(clippy::uninlined_format_args)]
4
5use colored::*;
6use num_enum::TryFromPrimitiveError;
7use oxnet::IpNet;
8use std::collections::BTreeMap;
9use std::fmt::{Display, Formatter};
10use std::net::IpAddr;
11use std::str::FromStr;
12use thiserror::Error;
13use tracing::debug;
14
15pub mod ip;
18
19pub mod link;
22
23pub mod route;
26
27pub mod sys;
29
30pub mod pf_key;
32
33mod ioctl;
34mod kstat;
35mod ndpd;
36mod nvlist;
37
38#[derive(thiserror::Error, Debug)]
40pub enum Error {
41 #[error("not implemented")]
42 NotImplemented,
43 #[error("kstat: {0}")]
44 Kstat(String),
45 #[error("ioctl: {0}")]
46 Ioctl(String),
47 #[error("io err: {0}")]
48 Io(#[from] std::io::Error),
49 #[error("encoding error: {0}")]
50 Encoding(#[from] std::str::Utf8Error),
51 #[error("array conversion error: {0}")]
52 Conversion(#[from] std::array::TryFromSliceError),
53 #[error("dlmgmtd: {0}")]
54 Dlmgmtd(String),
55 #[error("ipmgmtd: {0}")]
56 Ipmgmtd(String),
57 #[error("bad argument: {0}")]
58 BadArgument(String),
59 #[error("not found: {0}")]
60 NotFound(String),
61 #[error("already exists: {0}")]
62 AlreadyExists(String),
63 #[error("nvpair: {0}")]
64 NvPair(String),
65 #[error("route error: {0}")]
66 Route(#[from] route::Error),
67 #[error("ndp error: {0}")]
68 Ndp(String),
69 #[error("unrecognized neighbor state: {0}")]
70 NeighborState(#[from] TryFromPrimitiveError<ioctl::NeighborState>),
71}
72
73#[derive(Copy, Clone, Debug, PartialEq, Eq)]
77#[repr(u32)]
78pub enum LinkFlags {
79 Active = 0x1,
80 Persistent = 0x2,
81 ActivePersistent = 0x3,
82}
83
84impl Display for LinkFlags {
85 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
86 match self {
87 LinkFlags::Active => write!(f, "{}", "active".bright_green()),
88 LinkFlags::Persistent => {
89 write!(f, "{}", "persistent".bright_blue())
90 }
91 LinkFlags::ActivePersistent => write!(
92 f,
93 "{} {}",
94 "active".bright_green(),
95 "persistent".bright_blue(),
96 ),
97 }
98 }
99}
100
101#[derive(Debug, PartialEq, Eq, Clone)]
103#[repr(C)]
104pub enum LinkClass {
105 Phys = 0x01,
106 Vlan = 0x02,
107 Aggr = 0x04,
108 Vnic = 0x08,
109 Etherstub = 0x10,
110 Simnet = 0x20,
111 Bridge = 0x40,
112 IPtun = 0x80,
113 Part = 0x100,
114 Overlay = 0x200,
115 Misc = 0x400,
116 Tfport = 0x800,
117 All = 0xfff,
118}
119
120impl Display for LinkClass {
121 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
122 match self {
123 LinkClass::Phys => write!(f, "physical"),
124 LinkClass::Vlan => write!(f, "vlan"),
125 LinkClass::Aggr => write!(f, "aggr"),
126 LinkClass::Vnic => write!(f, "vnic"),
127 LinkClass::Etherstub => write!(f, "etherstub"),
128 LinkClass::Simnet => write!(f, "simnet"),
129 LinkClass::Bridge => write!(f, "bridge"),
130 LinkClass::IPtun => write!(f, "iptun"),
131 LinkClass::Part => write!(f, "part"),
132 LinkClass::Misc => write!(f, "misc"),
133 LinkClass::Overlay => write!(f, "overlay"),
134 LinkClass::Tfport => write!(f, "tfport"),
135 LinkClass::All => write!(f, "all"),
136 }
137 }
138}
139
140#[derive(Debug, PartialEq, Eq, Clone)]
142pub enum LinkState {
143 Unknown,
144 Down,
145 Up,
146}
147
148impl Display for LinkState {
149 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
150 match self {
151 LinkState::Up => {
152 write!(f, "{}", "up".bright_green())
153 }
154 LinkState::Down => {
155 write!(f, "{}", "down".bright_red())
156 }
157 LinkState::Unknown => {
158 write!(f, "{}", "unknown".bright_red())
159 }
160 }
161 }
162}
163
164#[derive(Debug, PartialEq, Eq, Clone)]
166pub struct LinkInfo {
167 pub id: u32,
168 pub name: String,
169 pub flags: LinkFlags,
170 pub class: LinkClass,
171 pub state: LinkState,
172 pub mac: [u8; 6],
173 pub mtu: Option<u32>,
174 pub over: u32,
175}
176
177impl LinkInfo {
178 pub fn handle(&self) -> LinkHandle {
180 LinkHandle::Id(self.id)
181 }
182 pub fn update(&mut self) -> Result<(), Error> {
184 *self = get_link(&self.handle())?;
185 Ok(())
186 }
187}
188
189#[derive(Debug, Clone)]
191pub enum LinkHandle {
192 Id(u32),
193 Name(String),
194}
195
196impl LinkHandle {
197 pub fn id(&self) -> Result<u32, Error> {
198 Ok(match self {
199 LinkHandle::Id(id) => *id,
200 LinkHandle::Name(name) => linkname_to_id(name)?,
201 })
202 }
203}
204
205impl FromStr for LinkHandle {
206 type Err = Error;
207
208 fn from_str(s: &str) -> Result<Self, Self::Err> {
209 Ok(match s.parse::<u32>() {
210 Ok(id) => Self::Id(id),
211 Err(_) => Self::Name(s.into()),
212 })
213 }
214}
215
216pub fn get_links() -> Result<Vec<LinkInfo>, Error> {
218 crate::link::get_links()
219}
220
221pub fn get_link(handle: &LinkHandle) -> Result<LinkInfo, Error> {
223 crate::link::get_link(handle.id()?)
224}
225
226pub fn linkname_to_id(name: &str) -> Result<u32, Error> {
228 crate::link::linkname_to_id(name)
229}
230
231pub fn create_simnet_link(
236 name: &str,
237 flags: LinkFlags,
238) -> Result<LinkInfo, Error> {
239 debug!("creating simnet link {}", name);
240 crate::link::create_simnet_link(name, flags)
241}
242
243pub fn create_tfport_link(
252 name: &str,
253 over: &str,
254 port: u16,
255 mac: Option<String>,
256 flags: LinkFlags,
257) -> Result<LinkInfo, Error> {
258 debug!("creating tfport link {}", name);
259 crate::link::create_tfport_link(name, over, port, mac, flags)
260}
261
262pub struct TfportInfo {
264 pub name: String,
265 pub pkt_src: String,
266 pub port: u16,
267 pub mac: String,
268}
269
270pub fn get_tfport_info(link: &LinkHandle) -> Result<TfportInfo, Error> {
272 let link = get_link(link)?;
273 if link.class != LinkClass::Tfport {
274 return Err(Error::BadArgument(format!(
275 "{} is not a tfport",
276 link.name
277 )));
278 }
279
280 let info = crate::ioctl::get_tfport_info(link.id).map_err(|_| {
281 Error::Ioctl(format!("failed to get link details for {}", link.name))
282 })?;
283
284 let pkt_src = match crate::link::get_link(info.pktsrc_id) {
285 Ok(l) => l.name,
286 Err(_) => "unknown".to_string(),
287 };
288
289 let mac = {
290 let m = &info.mac_addr;
291 format!(
292 "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
293 m[0], m[1], m[2], m[3], m[4], m[5]
294 )
295 };
296
297 Ok(TfportInfo {
298 name: link.name,
299 pkt_src,
300 port: info.port,
301 mac,
302 })
303}
304
305pub fn create_vnic_link(
310 name: &str,
311 link: &LinkHandle,
312 mac: Option<Vec<u8>>,
313 flags: LinkFlags,
314) -> Result<LinkInfo, Error> {
315 crate::link::create_vnic_link(name, link.id()?, mac, flags)
316}
317
318pub fn delete_link(handle: &LinkHandle, flags: LinkFlags) -> Result<(), Error> {
320 let id = match handle.id() {
321 Err(Error::NotFound(_)) => return Ok(()),
322 Err(e) => return Err(e),
323 Ok(id) => id,
324 };
325
326 crate::link::delete_link(id, flags)
327}
328
329pub fn connect_simnet_peers(
333 a: &LinkHandle,
334 b: &LinkHandle,
335) -> Result<(), Error> {
336 let a_id = a.id()?;
337 let b_id = b.id()?;
338
339 crate::ioctl::connect_simnet_peers(a_id, b_id)
340 }
345
346#[derive(Debug, PartialEq, Eq, Clone)]
350#[repr(i32)]
351pub enum IpState {
352 Disabled = 0,
353 Duplicate = 1,
354 Down = 2,
355 Tentative = 3,
356 OK = 4,
357 Inaccessible = 5,
358}
359
360#[derive(Debug, PartialEq, Eq, Clone)]
362pub struct IpInfo {
363 pub ifname: String,
364 pub index: i32,
365 pub addr: IpAddr,
366 pub mask: u32,
367 pub family: u16,
368 pub state: IpState,
369}
370
371impl IpInfo {
372 pub fn obj(&self) -> Result<(String, String), Error> {
378 match crate::ip::ifname_to_addrobj(&self.ifname, self.family) {
379 Ok((name, kind)) => Ok((name, kind)),
380 Err(e) => Err(Error::Ipmgmtd(e)),
381 }
382 }
383}
384
385pub fn get_ipaddrs() -> Result<BTreeMap<String, Vec<IpInfo>>, Error> {
390 crate::ioctl::get_ipaddrs()
391 }
395
396pub use crate::ioctl::get_ipaddr_info;
398
399pub use crate::ioctl::get_neighbor;
401
402pub fn create_ipaddr(name: impl AsRef<str>, addr: IpNet) -> Result<(), Error> {
407 crate::ioctl::create_ipaddr(name, addr)
408}
409
410pub fn enable_v6_link_local(
412 ifname: impl AsRef<str>,
413 addrname: impl AsRef<str>,
414) -> Result<(), Error> {
415 crate::ioctl::enable_v6_link_local(ifname.as_ref(), addrname.as_ref())
416}
417
418pub fn delete_ipaddr(name: impl AsRef<str>) -> Result<(), Error> {
420 crate::ioctl::delete_ipaddr(name)
421}
422
423pub fn ipaddr_exists(name: impl AsRef<str>) -> Result<bool, Error> {
425 crate::ioctl::ipaddr_exists(name)
426}
427
428pub fn get_routes() -> Result<Vec<crate::route::Route>, Error> {
432 Ok(crate::route::get_routes()?)
433}
434
435pub use crate::route::add_route;
437
438pub use crate::route::get_route;
440
441pub use crate::route::ensure_route_present;
446
447pub use crate::route::delete_route;
449
450pub struct DropLink {
453 pub info: LinkInfo,
454}
455impl DropLink {
456 pub fn handle(&self) -> LinkHandle {
457 self.info.handle()
458 }
459 pub fn update(&mut self) -> Result<(), Error> {
460 self.info.update()
461 }
462}
463impl Drop for DropLink {
464 fn drop(&mut self) {
465 if let Err(e) = delete_link(&self.info.handle(), self.info.flags) {
466 println!("deleting {} failed: {}", self.info.name, e);
467 }
468 }
469}
470impl From<LinkInfo> for DropLink {
471 fn from(info: LinkInfo) -> Self {
472 Self { info }
473 }
474}
475
476pub struct DropIp {
479 pub info: IpInfo,
480}
481impl Drop for DropIp {
482 fn drop(&mut self) {
483 let name = match self.info.obj() {
484 Ok((name, _)) => name,
485 Err(e) => {
486 println!("delete {:?}: obj() failed: {}", self.info, e);
487 return;
488 }
489 };
490 if let Err(e) = delete_ipaddr(name) {
491 println!("delete {:?} failed: {}", self.info, e);
492 }
493 }
494}
495impl From<IpInfo> for DropIp {
496 fn from(info: IpInfo) -> Self {
497 Self { info }
498 }
499}
500
501pub struct DropLinkLocal {
504 pub name: String,
505}
506impl Drop for DropLinkLocal {
507 fn drop(&mut self) {
508 if let Err(e) = delete_ipaddr(&self.name) {
509 println!("delete link-local {:?} failed: {}", self.name, e);
510 }
511 }
512}