diff --git a/.github/buildomat/jobs/p5p.sh b/.github/buildomat/jobs/p5p.sh index 732a3765..e02998a4 100755 --- a/.github/buildomat/jobs/p5p.sh +++ b/.github/buildomat/jobs/p5p.sh @@ -34,7 +34,7 @@ source .github/buildomat/common.sh # TGT_BASE=${TGT_BASE:=/work} -REL_SRC=target/x86_64-unknown-unknown/release +REL_SRC=target/x86_64-unknown-unknown/release-lto REL_TGT=$TGT_BASE/release mkdir -p $REL_TGT @@ -43,7 +43,7 @@ cargo --version rustc --version header "build xde and opteadm (release+debug)" -ptime -m cargo xtask build +ptime -m cargo xtask build --profile all # # Inspect the kernel module for bad relocations in case the old @@ -55,7 +55,7 @@ if elfdump $REL_SRC/xde | grep GOTPCREL; then fi header "package opte" -cargo xtask package --skip-build +cargo xtask package --skip-build --profile all banner copy pfexec mkdir -p /out diff --git a/.github/buildomat/jobs/xde.sh b/.github/buildomat/jobs/xde.sh index b35c1d41..3abe2881 100755 --- a/.github/buildomat/jobs/xde.sh +++ b/.github/buildomat/jobs/xde.sh @@ -45,7 +45,7 @@ DBG_SRC=target/x86_64-unknown-unknown/debug DBG_LINK_SRC=target/i686-unknown-illumos/debug DBG_TGT=$TGT_BASE/debug -REL_SRC=target/x86_64-unknown-unknown/release +REL_SRC=target/x86_64-unknown-unknown/release-lto REL_LINK_SRC=target/i686-unknown-illumos/release REL_TGT=$TGT_BASE/release @@ -72,13 +72,13 @@ ptime -m cargo clippy -- \ --allow clippy::uninlined-format-args --allow clippy::bad_bit_mask popd +popd + header "build xde (debug)" -ptime -m ./build-debug.sh +ptime -m cargo xtask build --profile debug xde xde-link header "build xde (release)" -ptime -m ./build.sh - -popd +ptime -m cargo xtask build --profile release xde xde-link # # Inspect the kernel module for bad relocations in case the old diff --git a/Cargo.lock b/Cargo.lock index 93cad34e..857b9f89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -106,21 +91,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - [[package]] name = "base64" version = "0.22.1" @@ -163,12 +133,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - [[package]] name = "camino" version = "1.1.9" @@ -593,14 +557,13 @@ dependencies = [ [[package]] name = "dlpi" version = "0.2.0" -source = "git+https://github.com/oxidecomputer/dlpi-sys#7d1aa141836c9d1ae7729b3037c5d1dbcb41decc" +source = "git+https://github.com/oxidecomputer/dlpi-sys#065d2c64e4ba9450b14fceb917a573db19ddf632" dependencies = [ "libc", "libdlpi-sys", "num_enum", "pretty-hex", "thiserror 2.0.12", - "tokio", ] [[package]] @@ -838,12 +801,6 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "goblin" version = "0.8.2" @@ -1078,7 +1035,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libdlpi-sys" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/dlpi-sys#7d1aa141836c9d1ae7729b3037c5d1dbcb41decc" +source = "git+https://github.com/oxidecomputer/dlpi-sys#065d2c64e4ba9450b14fceb917a573db19ddf632" [[package]] name = "libfuzzer-sys" @@ -1093,7 +1050,7 @@ dependencies = [ [[package]] name = "libnet" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/netadm-sys?branch=main#7f01a5e52526e50fa889f665442c7a6a4ffac1b4" +source = "git+https://github.com/oxidecomputer/netadm-sys?branch=main#a1d64b04293b186d50dbe4a1cfecaa81529b0e18" dependencies = [ "anyhow", "cfg-if", @@ -1128,16 +1085,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.27" @@ -1178,26 +1125,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - [[package]] name = "nix" version = "0.29.0" @@ -1271,15 +1198,6 @@ name = "nvpair-sys" version = "0.4.0" source = "git+https://github.com/jmesmon/rust-libzfs?branch=master#ecd5a922247a6c5acef55d76c5b8d115572bc850" -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1426,29 +1344,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e1dc143c5a701f879552428910f357df8bd725575087cc713088fdfeafe812" -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "pcap-parser" version = "0.16.0" @@ -1718,15 +1613,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" -dependencies = [ - "bitflags 2.9.0", -] - [[package]] name = "redox_users" version = "0.4.6" @@ -1780,12 +1666,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rusticata-macros" version = "4.1.0" @@ -1847,12 +1727,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "scroll" version = "0.12.0" @@ -1952,15 +1826,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.9" @@ -2038,12 +1903,6 @@ dependencies = [ "time", ] -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - [[package]] name = "smoltcp" version = "0.11.0" @@ -2239,35 +2098,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "tokio" -version = "1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "toml" version = "0.8.22" @@ -2784,11 +2614,10 @@ dependencies = [ [[package]] name = "ztest" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/falcon?branch=main#b0652cd88af0943060485f73b718c862c8c75cc4" +source = "git+https://github.com/oxidecomputer/falcon?branch=main#e41c1f7fbabc1992ca256f17aa84f6ede9be2fe8" dependencies = [ "anyhow", "libnet", "oxnet", - "tokio", "zone", ] diff --git a/Cargo.toml b/Cargo.toml index 0f8b3619..48a2830b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ poptrie = { git = "https://github.com/oxidecomputer/poptrie", branch = "multipat [profile.release] debug = 2 +lto = "thin" [profile.release-lto] inherits = "release" diff --git a/bench/src/packet.rs b/bench/src/packet.rs index 03402cfa..b555f946 100644 --- a/bench/src/packet.rs +++ b/bench/src/packet.rs @@ -25,7 +25,6 @@ use opte_test_utils::icmp::RawHardwareAddress; use opte_test_utils::icmp::gen_icmp_echo; use opte_test_utils::icmp::gen_icmpv6_echo; use opte_test_utils::icmp::generate_ndisc; -use opte_test_utils::overlay::BOUNDARY_SERVICES_VNI; use opte_test_utils::*; pub type TestCase = (MsgBlk, Direction); diff --git a/bin/opteadm/Cargo.toml b/bin/opteadm/Cargo.toml index 199742fe..c0e5ac19 100644 --- a/bin/opteadm/Cargo.toml +++ b/bin/opteadm/Cargo.toml @@ -10,9 +10,9 @@ repository.workspace = true # XXX For the time being opteadm needs to set the engine feature to # get all the types. Once there types are move to their appropriate # place this feature flag will be replaced/removed. -opte = { workspace = true, features = ["api", "engine", "std"] } +opte = { workspace = true, features = ["api", "std"] } opte-ioctl.workspace = true -oxide-vpc = { workspace = true, features = ["api", "engine", "std"] } +oxide-vpc = { workspace = true, features = ["api", "std"] } anyhow.workspace = true cfg-if.workspace = true diff --git a/bin/opteadm/src/bin/opteadm.rs b/bin/opteadm/src/bin/opteadm.rs index cdd07ac5..1e610850 100644 --- a/bin/opteadm/src/bin/opteadm.rs +++ b/bin/opteadm/src/bin/opteadm.rs @@ -17,14 +17,16 @@ use opte::api::Ipv6Addr; use opte::api::MAJOR_VERSION; use opte::api::MacAddr; use opte::api::Vni; -use opte::engine::print::print_layer; -use opte::engine::print::print_list_layers; -use opte::engine::print::print_tcp_flows; -use opte::engine::print::print_uft; +use opte::print::print_layer; +use opte::print::print_list_layers; +use opte::print::print_tcp_flows; +use opte::print::print_uft; use opteadm::COMMIT_COUNT; use opteadm::OpteAdm; +use oxide_vpc::api::AddFwRuleReq; use oxide_vpc::api::AddRouterEntryReq; use oxide_vpc::api::Address; +use oxide_vpc::api::BOUNDARY_SERVICES_VNI; use oxide_vpc::api::ClearVirt2BoundaryReq; use oxide_vpc::api::ClearVirt2PhysReq; use oxide_vpc::api::DelRouterEntryReq; @@ -48,13 +50,13 @@ use oxide_vpc::api::RouterTarget; use oxide_vpc::api::SNat4Cfg; use oxide_vpc::api::SNat6Cfg; use oxide_vpc::api::SetExternalIpsReq; +use oxide_vpc::api::SetFwRulesReq; use oxide_vpc::api::SetVirt2BoundaryReq; use oxide_vpc::api::SetVirt2PhysReq; use oxide_vpc::api::TunnelEndpoint; use oxide_vpc::api::VpcCfg; -use oxide_vpc::engine::overlay::BOUNDARY_SERVICES_VNI; -use oxide_vpc::engine::print::print_v2b; -use oxide_vpc::engine::print::print_v2p; +use oxide_vpc::print::print_v2b; +use oxide_vpc::print::print_v2p; use std::io; use std::io::Write; use std::str::FromStr; @@ -565,7 +567,7 @@ fn print_port(t: &mut impl Write, pi: PortInfo) -> std::io::Result<()> { fn main() -> anyhow::Result<()> { let cmd = Command::parse(); - let hdl = opteadm::OpteAdm::open(OpteAdm::XDE_CTL)?; + let hdl = OpteAdm::open()?; match cmd { Command::ListPorts => { @@ -595,7 +597,7 @@ fn main() -> anyhow::Result<()> { } Command::DumpLayer { port, name } => { - let resp = &hdl.get_layer_by_name(&port, &name)?; + let resp = &hdl.dump_layer(&port, &name)?; print!("Port {port} - "); print_layer(resp)?; } @@ -605,7 +607,6 @@ fn main() -> anyhow::Result<()> { } Command::ClearLft { port, layer } => { - let hdl = opteadm::OpteAdm::open(OpteAdm::XDE_CTL)?; hdl.clear_lft(&port, &layer)?; } @@ -622,7 +623,6 @@ fn main() -> anyhow::Result<()> { } Command::DumpV2B => { - let hdl = opteadm::OpteAdm::open(OpteAdm::XDE_CTL)?; print_v2b(&hdl.dump_v2b()?)?; } @@ -633,7 +633,7 @@ fn main() -> anyhow::Result<()> { action, priority, }; - hdl.add_firewall_rule(&port, &rule)?; + hdl.add_firewall_rule(&AddFwRuleReq { port_name: port, rule })?; } Command::SetFwRules { port } => { @@ -645,8 +645,7 @@ fn main() -> anyhow::Result<()> { rules.push(r); } - let hdl = opteadm::OpteAdm::open(OpteAdm::XDE_CTL)?; - hdl.set_firewall_rules(&port, rules)?; + hdl.set_firewall_rules(&SetFwRulesReq { port_name: port, rules })?; } Command::CreateXde { @@ -742,7 +741,6 @@ fn main() -> anyhow::Result<()> { } Command::SetV2B { prefix, tunnel_endpoint } => { - let hdl = opteadm::OpteAdm::open(OpteAdm::XDE_CTL)?; let tep = tunnel_endpoint .into_iter() .map(|ip| TunnelEndpoint { @@ -755,7 +753,6 @@ fn main() -> anyhow::Result<()> { } Command::ClearV2B { prefix, tunnel_endpoint } => { - let hdl = opteadm::OpteAdm::open(OpteAdm::XDE_CTL)?; let tep = tunnel_endpoint .into_iter() .map(|ip| TunnelEndpoint { diff --git a/bin/opteadm/src/lib.rs b/bin/opteadm/src/lib.rs index f2e8a560..abab3811 100644 --- a/bin/opteadm/src/lib.rs +++ b/bin/opteadm/src/lib.rs @@ -2,328 +2,32 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 Oxide Computer Company +// Copyright 2025 Oxide Computer Company //! OPTE driver administration library -use opte::api::ClearXdeUnderlayReq; -use opte::api::Direction; -use opte::api::IpCidr; -use opte::api::NoResp; -use opte::api::OpteCmd; -use opte::api::SetXdeUnderlayReq; -use opte::engine::ioctl::{self as api}; use opte_ioctl::Error; -use opte_ioctl::run_cmd_ioctl; -use oxide_vpc::api::AddFwRuleReq; -use oxide_vpc::api::AddRouterEntryReq; -use oxide_vpc::api::AllowCidrReq; -use oxide_vpc::api::ClearVirt2BoundaryReq; -use oxide_vpc::api::ClearVirt2PhysReq; -use oxide_vpc::api::CreateXdeReq; -use oxide_vpc::api::DelRouterEntryReq; -use oxide_vpc::api::DelRouterEntryResp; -use oxide_vpc::api::DeleteXdeReq; -use oxide_vpc::api::DhcpCfg; -use oxide_vpc::api::DumpVirt2BoundaryReq; -use oxide_vpc::api::DumpVirt2BoundaryResp; -use oxide_vpc::api::DumpVirt2PhysReq; -use oxide_vpc::api::DumpVirt2PhysResp; -use oxide_vpc::api::FirewallRule; -use oxide_vpc::api::ListPortsResp; -use oxide_vpc::api::RemFwRuleReq; -use oxide_vpc::api::RemoveCidrReq; -use oxide_vpc::api::RemoveCidrResp; -use oxide_vpc::api::SetExternalIpsReq; -use oxide_vpc::api::SetFwRulesReq; -use oxide_vpc::api::SetVirt2BoundaryReq; -use oxide_vpc::api::SetVirt2PhysReq; -use oxide_vpc::api::VpcCfg; -use std::fs::File; -use std::fs::OpenOptions; -use std::os::unix::io::AsRawFd; +use opte_ioctl::OpteHdl; +use std::ops::Deref; include!(concat!(env!("OUT_DIR"), "/gen.rs")); /// The handle used to send administration commands to the OPTE /// control node. #[derive(Debug)] -pub struct OpteAdm { - device: File, -} - -impl OpteAdm { - pub const XDE_CTL: &'static str = "/dev/xde"; - - /// Add xde device - pub fn create_xde( - &self, - name: &str, - cfg: VpcCfg, - dhcp: DhcpCfg, - passthrough: bool, - ) -> Result { - use libnet::link; - - let linkid = link::create_link_id( - name, - libnet::LinkClass::Misc, - libnet::LinkFlags::Active, - )?; - - let xde_devname = name.into(); - let cmd = OpteCmd::CreateXde; - let req = CreateXdeReq { xde_devname, linkid, cfg, dhcp, passthrough }; - let res = run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)); - - if res.is_err() { - let _ = link::delete_link_id(linkid, libnet::LinkFlags::Active); - } - - res - } - - /// Delete xde device - pub fn delete_xde(&self, name: &str) -> Result { - let link_id = libnet::LinkHandle::Name(name.into()).id()?; - let req = DeleteXdeReq { xde_devname: name.into() }; - let cmd = OpteCmd::DeleteXde; - let resp = run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req))?; - libnet::link::delete_link_id(link_id, libnet::LinkFlags::Active)?; - Ok(resp) - } - - /// Set xde underlay devices - pub fn set_xde_underlay( - &self, - u1: &str, - u2: &str, - ) -> Result { - let req = SetXdeUnderlayReq { u1: u1.into(), u2: u2.into() }; - let cmd = OpteCmd::SetXdeUnderlay; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - /// Clear xde underlay devices - pub fn clear_xde_underlay(&self) -> Result { - let req = ClearXdeUnderlayReq { _unused: 0 }; - let cmd = OpteCmd::ClearXdeUnderlay; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - /// Add a firewall rule - pub fn add_firewall_rule( - &self, - port_name: &str, - rule: &FirewallRule, - ) -> Result { - let cmd = OpteCmd::AddFwRule; - let req = AddFwRuleReq { - port_name: port_name.to_string(), - rule: rule.clone(), - }; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - pub fn set_firewall_rules( - &self, - port_name: &str, - rules: Vec, - ) -> Result { - let cmd = OpteCmd::SetFwRules; - let req = SetFwRulesReq { port_name: port_name.to_string(), rules }; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - /// Return the contents of an OPTE layer. - pub fn get_layer_by_name( - &self, - port_name: &str, - name: &str, - ) -> Result { - let cmd = OpteCmd::DumpLayer; - let req = api::DumpLayerReq { - port_name: port_name.to_string(), - name: name.to_string(), - }; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } +pub struct OpteAdm(OpteHdl); - /// List all the ports. - pub fn list_ports(&self) -> Result { - run_cmd_ioctl(self.device.as_raw_fd(), OpteCmd::ListPorts, None::<&()>) - } +impl Deref for OpteAdm { + type Target = OpteHdl; - pub fn list_layers( - &self, - port: &str, - ) -> Result { - let cmd = OpteCmd::ListLayers; - run_cmd_ioctl::( - self.device.as_raw_fd(), - cmd, - Some(&api::ListLayersReq { port_name: port.to_string() }), - ) + fn deref(&self) -> &Self::Target { + &self.0 } +} +impl OpteAdm { /// Create a new handle to the OPTE control node. - pub fn open(what: &str) -> Result { - Ok(OpteAdm { - device: OpenOptions::new().read(true).write(true).open(what)?, - }) - } - - /// Remove a firewall rule. - pub fn remove_firewall_rule( - &self, - req: &RemFwRuleReq, - ) -> Result { - let cmd = OpteCmd::RemFwRule; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(req)) - } - - /// Return the TCP flows. - pub fn dump_tcp_flows( - &self, - port_name: &str, - ) -> Result { - let cmd = OpteCmd::DumpTcpFlows; - run_cmd_ioctl::( - self.device.as_raw_fd(), - cmd, - Some(&api::DumpTcpFlowsReq { port_name: port_name.to_string() }), - ) - } - - /// Clear all entries from the Unified Flow Table (UFT). - pub fn clear_uft(&self, port_name: &str) -> Result { - let cmd = OpteCmd::ClearUft; - run_cmd_ioctl( - self.device.as_raw_fd(), - cmd, - Some(&api::ClearUftReq { port_name: port_name.to_string() }), - ) - } - - /// Clear all entries from the given Layer's Flow Table (LFT). - pub fn clear_lft( - &self, - port_name: &str, - layer_name: &str, - ) -> Result { - let cmd = OpteCmd::ClearLft; - run_cmd_ioctl( - self.device.as_raw_fd(), - cmd, - Some(&api::ClearLftReq { - port_name: port_name.to_string(), - layer_name: layer_name.to_string(), - }), - ) - } - - /// Return the Unified Flow Table (UFT). - pub fn dump_uft(&self, port_name: &str) -> Result { - let cmd = OpteCmd::DumpUft; - run_cmd_ioctl::( - self.device.as_raw_fd(), - cmd, - Some(&api::DumpUftReq { port_name: port_name.to_string() }), - ) - } - - pub fn set_v2p(&self, req: &SetVirt2PhysReq) -> Result { - let cmd = OpteCmd::SetVirt2Phys; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - pub fn clear_v2p(&self, req: &ClearVirt2PhysReq) -> Result { - let cmd = OpteCmd::ClearVirt2Phys; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - /// Dump the Virtual-to-Physical mappings. - pub fn dump_v2p(&self) -> Result { - let cmd = OpteCmd::DumpVirt2Phys; - run_cmd_ioctl( - self.device.as_raw_fd(), - cmd, - Some(&DumpVirt2PhysReq { unused: 99 }), - ) - } - - pub fn set_v2b(&self, req: &SetVirt2BoundaryReq) -> Result { - let cmd = OpteCmd::SetVirt2Boundary; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - pub fn clear_v2b( - &self, - req: &ClearVirt2BoundaryReq, - ) -> Result { - let cmd = OpteCmd::ClearVirt2Boundary; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - /// Dump the Virtual-to-Boundary mappings. - pub fn dump_v2b(&self) -> Result { - let cmd = OpteCmd::DumpVirt2Boundary; - run_cmd_ioctl( - self.device.as_raw_fd(), - cmd, - Some(&DumpVirt2BoundaryReq { unused: 99 }), - ) - } - - pub fn add_router_entry( - &self, - req: &AddRouterEntryReq, - ) -> Result { - let cmd = OpteCmd::AddRouterEntry; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - pub fn del_router_entry( - &self, - req: &DelRouterEntryReq, - ) -> Result { - let cmd = OpteCmd::DelRouterEntry; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - pub fn set_external_ips( - &self, - req: &SetExternalIpsReq, - ) -> Result { - let cmd = OpteCmd::SetExternalIps; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) - } - - pub fn allow_cidr( - &self, - port_name: &str, - cidr: IpCidr, - dir: Direction, - ) -> Result { - let cmd = OpteCmd::AllowCidr; - run_cmd_ioctl( - self.device.as_raw_fd(), - cmd, - Some(&AllowCidrReq { cidr, port_name: port_name.into(), dir }), - ) - } - - pub fn remove_cidr( - &self, - port_name: &str, - cidr: IpCidr, - dir: Direction, - ) -> Result { - let cmd = OpteCmd::RemoveCidr; - run_cmd_ioctl( - self.device.as_raw_fd(), - cmd, - Some(&RemoveCidrReq { cidr, port_name: port_name.into(), dir }), - ) + pub fn open() -> Result { + OpteHdl::open().map(Self) } } diff --git a/crates/opte-api/src/cmd.rs b/crates/opte-api/src/cmd.rs index fe4ed737..6ec2848d 100644 --- a/crates/opte-api/src/cmd.rs +++ b/crates/opte-api/src/cmd.rs @@ -2,13 +2,17 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 Oxide Computer Company +// Copyright 2025 Oxide Computer Company use super::API_VERSION; +use super::RuleId; +use super::TcpState; use super::encap::Vni; use super::ip::IpCidr; use super::mac::MacAddr; use alloc::string::String; +use alloc::vec::Vec; +use core::fmt::Debug; use illumos_sys_hdrs::c_int; use illumos_sys_hdrs::size_t; use serde::Deserialize; @@ -244,7 +248,7 @@ impl OpteError { /// A marker trait indicating a success response type that is returned /// from a command and may be passed across the ioctl/API boundary. -pub trait CmdOk: core::fmt::Debug + Serialize {} +pub trait CmdOk: Debug + Serialize {} impl CmdOk for () {} @@ -255,3 +259,153 @@ pub struct NoResp { } impl CmdOk for NoResp {} + +/// Dump various information about a layer, for use in debugging or +/// administrative purposes. +#[derive(Debug, Deserialize, Serialize)] +pub struct DumpLayerReq { + /// The name of the port whose layer you want to dump. + pub port_name: String, + /// The name of the layer to dump. + pub name: String, +} + +/// The response to a [`DumpLayerReq`]. +#[derive(Debug, Deserialize, Serialize)] +pub struct DumpLayerResp { + /// The name of the layer. + pub name: String, + /// The inbound rules. + pub rules_in: Vec, + /// The outbound rules. + pub rules_out: Vec, + /// The default inbound action. + pub default_in: String, + /// The number of times the default inbound action was matched. + pub default_in_hits: u64, + /// The default outbound action. + pub default_out: String, + /// The number of times the default outbound action was matched. + pub default_out_hits: u64, + /// The inbound flow table. + pub ft_in: Vec<(Flow, ActionDescEntryDump)>, + /// The outbound flow table. + pub ft_out: Vec<(Flow, ActionDescEntryDump)>, +} + +impl CmdOk for DumpLayerResp {} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ListLayersReq { + pub port_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct LayerDesc { + /// Name of the layer. + pub name: String, + /// Number of rules inbound. + pub rules_in: usize, + /// Number of rules outbound. + pub rules_out: usize, + /// Default action inbound. + pub default_in: String, + /// Default action outbound. + pub default_out: String, + /// Number of active flows. + pub flows: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ListLayersResp { + pub layers: Vec, +} + +impl CmdOk for ListLayersResp {} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ClearUftReq { + pub port_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ClearLftReq { + pub port_name: String, + pub layer_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DumpUftReq { + pub port_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DumpUftResp { + pub in_limit: u32, + pub in_num_flows: u32, + pub in_flows: Vec<(Flow, UftEntryDump)>, + pub out_limit: u32, + pub out_num_flows: u32, + pub out_flows: Vec<(Flow, UftEntryDump)>, +} + +impl CmdOk for DumpUftResp {} + +#[derive(Debug, Deserialize, Serialize)] +pub struct UftEntryDump { + pub hits: u64, + pub summary: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DumpTcpFlowsReq { + pub port_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DumpTcpFlowsResp { + pub flows: Vec<(Flow, TcpFlowEntryDump)>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TcpFlowEntryDump { + pub hits: u64, + pub inbound_ufid: Option, + pub tcp_state: TcpFlowStateDump, + pub segs_in: u64, + pub segs_out: u64, + pub bytes_in: u64, + pub bytes_out: u64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TcpFlowStateDump { + pub tcp_state: TcpState, + pub guest_seq: Option, + pub guest_ack: Option, + pub remote_seq: Option, + pub remote_ack: Option, +} + +impl CmdOk for DumpTcpFlowsResp {} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ActionDescEntryDump { + pub hits: u64, + pub summary: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RuleTableEntryDump { + pub id: RuleId, + pub hits: u64, + pub rule: RuleDump, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RuleDump { + pub priority: u16, + pub predicates: Vec, + pub data_predicates: Vec, + pub action: String, +} diff --git a/crates/opte-api/src/lib.rs b/crates/opte-api/src/lib.rs index d930a5c9..7de24626 100644 --- a/crates/opte-api/src/lib.rs +++ b/crates/opte-api/src/lib.rs @@ -28,6 +28,7 @@ pub mod encap; pub mod ip; pub mod mac; pub mod ndp; +pub mod tcp; pub mod ulp; pub use cmd::*; @@ -37,6 +38,7 @@ pub use encap::*; pub use ip::*; pub use mac::*; pub use ndp::*; +pub use tcp::*; pub use ulp::*; /// The overall version of the API. @@ -49,7 +51,7 @@ pub use ulp::*; /// /// We rely on CI and the check-api-version.sh script to verify that /// this number is incremented anytime the oxide-api code changes. -pub const API_VERSION: u64 = 35; +pub const API_VERSION: u64 = 36; /// Major version of the OPTE package. pub const MAJOR_VERSION: u64 = 0; @@ -83,6 +85,9 @@ impl Display for Direction { } } +/// Opaque identifier for a rule within a layer. +pub type RuleId = u64; + /// Set the underlay devices used by the xde kernel module #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SetXdeUnderlayReq { diff --git a/crates/opte-api/src/tcp.rs b/crates/opte-api/src/tcp.rs new file mode 100644 index 00000000..08217005 --- /dev/null +++ b/crates/opte-api/src/tcp.rs @@ -0,0 +1,45 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2025 Oxide Computer Company + +use core::fmt; +use core::fmt::Display; +use serde::Deserialize; +use serde::Serialize; + +/// The standard TCP states. +/// +/// See Figure 13-8 of TCP/IP Illustrated Vol. 1 Ed. 2 +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum TcpState { + Closed, + Listen, + SynSent, + SynRcvd, + Established, + CloseWait, + LastAck, + FinWait1, + FinWait2, + TimeWait, +} + +impl Display for TcpState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + TcpState::Closed => "CLOSED", + TcpState::Listen => "LISTEN", + TcpState::SynSent => "SYN_SENT", + TcpState::SynRcvd => "SYN_RCVD", + TcpState::Established => "ESTABLISHED", + TcpState::CloseWait => "CLOSE_WAIT", + TcpState::LastAck => "LAST_ACK", + TcpState::FinWait1 => "FIN_WAIT_1", + TcpState::FinWait2 => "FIN_WAIT_2", + TcpState::TimeWait => "TIME_WAIT", + }; + write!(f, "{}", s) + } +} diff --git a/lib/opte-ioctl/src/lib.rs b/lib/opte-ioctl/src/lib.rs index 3dd04f43..e7df256a 100644 --- a/lib/opte-ioctl/src/lib.rs +++ b/lib/opte-ioctl/src/lib.rs @@ -5,15 +5,27 @@ // Copyright 2025 Oxide Computer Company use opte::api::API_VERSION; +use opte::api::ClearLftReq; +use opte::api::ClearUftReq; use opte::api::ClearXdeUnderlayReq; use opte::api::CmdOk; use opte::api::Direction; +use opte::api::DumpLayerReq; +use opte::api::DumpLayerResp; +use opte::api::DumpTcpFlowsReq; +use opte::api::DumpTcpFlowsResp; +use opte::api::DumpUftReq; +use opte::api::DumpUftResp; +pub use opte::api::InnerFlowId; +use opte::api::ListLayersReq; +use opte::api::ListLayersResp; use opte::api::NoResp; use opte::api::OpteCmd; use opte::api::OpteCmdIoctl; pub use opte::api::OpteError; use opte::api::SetXdeUnderlayReq; use opte::api::XDE_IOC_OPTE_CMD; +use oxide_vpc::api::AddFwRuleReq; use oxide_vpc::api::AddRouterEntryReq; use oxide_vpc::api::AllowCidrReq; use oxide_vpc::api::ClearVirt2BoundaryReq; @@ -23,10 +35,13 @@ use oxide_vpc::api::DelRouterEntryReq; use oxide_vpc::api::DelRouterEntryResp; use oxide_vpc::api::DeleteXdeReq; use oxide_vpc::api::DhcpCfg; +use oxide_vpc::api::DumpVirt2BoundaryReq; +use oxide_vpc::api::DumpVirt2BoundaryResp; use oxide_vpc::api::DumpVirt2PhysReq; use oxide_vpc::api::DumpVirt2PhysResp; use oxide_vpc::api::IpCidr; use oxide_vpc::api::ListPortsResp; +use oxide_vpc::api::RemFwRuleReq; use oxide_vpc::api::RemoveCidrReq; use oxide_vpc::api::RemoveCidrResp; use oxide_vpc::api::SetExternalIpsReq; @@ -141,19 +156,50 @@ impl OpteHdl { run_cmd_ioctl(self.device.as_raw_fd(), OpteCmd::ListPorts, None::<&()>) } - /// Create a new handle to the OPTE control node. - pub fn open(what: &str) -> Result { + /// List all layers in a given port. + pub fn list_layers(&self, port: &str) -> Result { + let cmd = OpteCmd::ListLayers; + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&ListLayersReq { port_name: port.to_string() }), + ) + } + + /// Return the contents of an OPTE layer. + pub fn dump_layer( + &self, + port_name: &str, + name: &str, + ) -> Result { + let cmd = OpteCmd::DumpLayer; + let req = DumpLayerReq { + port_name: port_name.to_string(), + name: name.to_string(), + }; + run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) + } + + /// Create a new handle to an OPTE control node on an arbitrary file. + pub fn open_on(what: &str) -> Result { Ok(OpteHdl { device: OpenOptions::new().read(true).write(true).open(what)?, }) } - pub fn dump_v2p( - &self, - req: &DumpVirt2PhysReq, - ) -> Result { + /// Create a new handle to the OPTE control node. + pub fn open() -> Result { + Self::open_on(Self::XDE_CTL) + } + + /// Dump the Virtual-to-Physical mappings. + pub fn dump_v2p(&self) -> Result { let cmd = OpteCmd::DumpVirt2Phys; - run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&DumpVirt2PhysReq { unused: 0 }), + ) } pub fn set_v2p(&self, req: &SetVirt2PhysReq) -> Result { @@ -179,6 +225,16 @@ impl OpteHdl { run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) } + /// Dump the Virtual-to-Boundary mappings. + pub fn dump_v2b(&self) -> Result { + let cmd = OpteCmd::DumpVirt2Boundary; + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&DumpVirt2BoundaryReq { unused: 99 }), + ) + } + /// Set xde underlay devices. pub fn set_xde_underlay( &self, @@ -213,7 +269,28 @@ impl OpteHdl { run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) } - pub fn set_fw_rules(&self, req: &SetFwRulesReq) -> Result { + /// Add a firewall rule + pub fn add_firewall_rule( + &self, + req: &AddFwRuleReq, + ) -> Result { + let cmd = OpteCmd::AddFwRule; + run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) + } + + /// Remove a firewall rule. + pub fn remove_firewall_rule( + &self, + req: &RemFwRuleReq, + ) -> Result { + let cmd = OpteCmd::RemFwRule; + run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(req)) + } + + pub fn set_firewall_rules( + &self, + req: &SetFwRulesReq, + ) -> Result { let cmd = OpteCmd::SetFwRules; run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req)) } @@ -253,6 +330,56 @@ impl OpteHdl { Some(&RemoveCidrReq { cidr, port_name: port_name.into(), dir }), ) } + + /// Return the TCP flows. + pub fn dump_tcp_flows( + &self, + port_name: &str, + ) -> Result { + let cmd = OpteCmd::DumpTcpFlows; + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&DumpTcpFlowsReq { port_name: port_name.to_string() }), + ) + } + + /// Clear all entries from the Unified Flow Table (UFT). + pub fn clear_uft(&self, port_name: &str) -> Result { + let cmd = OpteCmd::ClearUft; + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&ClearUftReq { port_name: port_name.to_string() }), + ) + } + + /// Clear all entries from the given Layer's Flow Table (LFT). + pub fn clear_lft( + &self, + port_name: &str, + layer_name: &str, + ) -> Result { + let cmd = OpteCmd::ClearLft; + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&ClearLftReq { + port_name: port_name.to_string(), + layer_name: layer_name.to_string(), + }), + ) + } + + /// Return the Unified Flow Table (UFT). + pub fn dump_uft(&self, port_name: &str) -> Result { + let cmd = OpteCmd::DumpUft; + run_cmd_ioctl( + self.device.as_raw_fd(), + cmd, + Some(&DumpUftReq { port_name: port_name.to_string() }), + ) + } } pub fn run_cmd_ioctl( diff --git a/lib/opte-test-utils/src/lib.rs b/lib/opte-test-utils/src/lib.rs index 001c07bb..f3eac140 100644 --- a/lib/opte-test-utils/src/lib.rs +++ b/lib/opte-test-utils/src/lib.rs @@ -64,6 +64,7 @@ pub use opte::ingot::types::EmitDoesNotRelyOnBufContents; pub use opte::ingot::types::HeaderLen; pub use opte::ingot::udp::Udp; pub use oxide_vpc::api::AddFwRuleReq; +pub use oxide_vpc::api::BOUNDARY_SERVICES_VNI; pub use oxide_vpc::api::DhcpCfg; pub use oxide_vpc::api::ExternalIpCfg; pub use oxide_vpc::api::GW_MAC_ADDR; @@ -84,7 +85,6 @@ pub use oxide_vpc::engine::firewall; pub use oxide_vpc::engine::gateway; pub use oxide_vpc::engine::nat; pub use oxide_vpc::engine::overlay; -pub use oxide_vpc::engine::overlay::BOUNDARY_SERVICES_VNI; pub use oxide_vpc::engine::overlay::TUNNEL_ENDPOINT_MAC; pub use oxide_vpc::engine::overlay::Virt2Boundary; pub use oxide_vpc::engine::overlay::Virt2Phys; diff --git a/lib/opte-test-utils/src/port_state.rs b/lib/opte-test-utils/src/port_state.rs index 4a5d775f..3a233f58 100644 --- a/lib/opte-test-utils/src/port_state.rs +++ b/lib/opte-test-utils/src/port_state.rs @@ -8,10 +8,10 @@ use super::*; use opte::engine::port::*; -use opte::engine::print::*; +use opte::print::*; use oxide_vpc::engine::VpcNetwork; use oxide_vpc::engine::overlay::VpcMappings; -use oxide_vpc::engine::print::*; +use oxide_vpc::print::*; use std::collections::BTreeMap; use std::io::Write; diff --git a/lib/opte/Cargo.toml b/lib/opte/Cargo.toml index 4aa8fc9a..90f0ab5f 100644 --- a/lib/opte/Cargo.toml +++ b/lib/opte/Cargo.toml @@ -9,8 +9,17 @@ repository.workspace = true [features] default = ["api", "std"] api = [] -engine = ["api", "dep:crc32fast", "dep:derror-macro", "dep:heapless", "dep:itertools", "dep:zerocopy"] -kernel = ["illumos-sys-hdrs/kernel"] +engine = [ + "api", + "dep:cfg-if", + "dep:crc32fast", + "dep:derror-macro", + "dep:heapless", + "dep:itertools", + "dep:smoltcp", + "dep:zerocopy" +] +kernel = ["illumos-sys-hdrs/kernel", "dep:cfg-if"] # This feature indicates that OPTE is being built with std. This is # mostly useful to consumers of the API, providing convenient methods # for working with the API types in a std context. @@ -30,7 +39,7 @@ opte-api.workspace = true ingot.workspace = true bitflags = { workspace = true , features = ["serde"] } -cfg-if.workspace = true +cfg-if = { workspace = true, optional = true } crc32fast = { workspace = true, optional = true } dyn-clone.workspace = true heapless = { workspace = true, optional = true } @@ -43,6 +52,7 @@ zerocopy = { workspace = true, optional = true } [dependencies.smoltcp] workspace = true +optional = true default-features = false # # TODO Would defmt be of any use? diff --git a/lib/opte/src/api.rs b/lib/opte/src/api.rs new file mode 100644 index 00000000..02fe5d99 --- /dev/null +++ b/lib/opte/src/api.rs @@ -0,0 +1,156 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2025 Oxide Computer Company + +pub use opte_api::*; + +use core::fmt::Display; +use core::fmt::{self}; +#[cfg(feature = "engine")] +use core::hash::Hash; +#[cfg(feature = "engine")] +use crc32fast::Hasher; +use serde::Deserialize; +use serde::Serialize; + +const AF_INET: i32 = 2; +const AF_INET6: i32 = 26; + +pub static FLOW_ID_DEFAULT: InnerFlowId = InnerFlowId { + proto: 255, + addrs: AddrPair::V4 { src: Ipv4Addr::ANY_ADDR, dst: Ipv4Addr::ANY_ADDR }, + src_port: 0, + dst_port: 0, +}; + +/// The flow identifier. +/// +/// In this case the flow identifier is the 5-tuple of the inner IP +/// packet. +/// +/// NOTE: This should not be defined in `opte`. Rather, the engine +/// should be generic in regards to the flow identifier, and it should +/// be up to the `NetworkImpl` to define it. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +#[repr(C, align(4))] +pub struct InnerFlowId { + // Using a `u8` here for `proto` hides the enum repr from SDTs. + pub proto: u8, + // We could also theoretically get to a 38B packing if we reduce + // AddrPair's repr from `u16` to `u8`. However, on the dtrace/illumos + // side `union addrs` is 4B aligned -- in6_addr_t has a 4B alignment. + // So, this layout has to match that constraint -- placing addrs at + // offset 0x2 with `u16` discriminant sets up 4B alignment for the + // enum variant data (and this struct itself is 4B aligned). + pub addrs: AddrPair, + pub src_port: u16, + pub dst_port: u16, +} + +impl InnerFlowId { + #[cfg(feature = "engine")] + pub fn crc32(&self) -> u32 { + let mut hasher = Hasher::new(); + self.hash(&mut hasher); + hasher.finalize() + } +} + +impl Default for InnerFlowId { + fn default() -> Self { + FLOW_ID_DEFAULT + } +} + +/// Tagged union of a source-dest IP address pair, used to avoid +/// duplicating the discriminator. +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +#[repr(C, u16)] +pub enum AddrPair { + V4 { src: Ipv4Addr, dst: Ipv4Addr } = AF_INET as u16, + V6 { src: Ipv6Addr, dst: Ipv6Addr } = AF_INET6 as u16, +} + +impl AddrPair { + pub fn mirror(self) -> Self { + match self { + Self::V4 { src, dst } => Self::V4 { src: dst, dst: src }, + Self::V6 { src, dst } => Self::V6 { src: dst, dst: src }, + } + } +} + +impl InnerFlowId { + /// Swap IP source and destination as well as ULP port source and + /// destination. + pub fn mirror(self) -> Self { + Self { + proto: self.proto, + addrs: self.addrs.mirror(), + src_port: self.dst_port, + dst_port: self.src_port, + } + } + + pub fn src_ip(&self) -> IpAddr { + match self.addrs { + AddrPair::V4 { src, .. } => src.into(), + AddrPair::V6 { src, .. } => src.into(), + } + } + + pub fn dst_ip(&self) -> IpAddr { + match self.addrs { + AddrPair::V4 { dst, .. } => dst.into(), + AddrPair::V6 { dst, .. } => dst.into(), + } + } + + pub fn protocol(&self) -> Protocol { + Protocol::from(self.proto) + } +} + +impl Display for InnerFlowId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}:{}:{}:{}:{}", + self.protocol(), + self.src_ip(), + self.src_port, + self.dst_ip(), + self.dst_port, + ) + } +} + +// Convenience `Dump` types while `InnerFlowId` is the only flowkey allowed. +pub type DumpLayerResp = opte_api::DumpLayerResp; +pub type DumpUftResp = opte_api::DumpUftResp; +pub type DumpTcpFlowsResp = opte_api::DumpTcpFlowsResp; +pub type TcpFlowEntryDump = opte_api::TcpFlowEntryDump; diff --git a/lib/opte/src/engine/headers.rs b/lib/opte/src/engine/headers.rs index e19dd986..8e0fc568 100644 --- a/lib/opte/src/engine/headers.rs +++ b/lib/opte/src/engine/headers.rs @@ -43,9 +43,6 @@ use serde::Serialize; use zerocopy::ByteSlice; use zerocopy::ByteSliceMut; -pub const AF_INET: i32 = 2; -pub const AF_INET6: i32 = 26; - pub trait PushAction { fn push(&self) -> HdrM; } diff --git a/lib/opte/src/engine/ioctl.rs b/lib/opte/src/engine/ioctl.rs deleted file mode 100644 index 0a731fbc..00000000 --- a/lib/opte/src/engine/ioctl.rs +++ /dev/null @@ -1,185 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -// Copyright 2023 Oxide Computer Company - -//! The ioctl interface. -//! -//! XXX This stuff needs to be moved to oxide-api. -use super::layer::RuleId; -use super::packet::InnerFlowId; -use super::port::Port; -use super::predicate::DataPredicate; -use super::tcp::TcpState; -use alloc::string::String; -use alloc::vec::Vec; -use core::fmt::Debug; -use opte_api::CmdOk; -use opte_api::OpteError; -use serde::Deserialize; -use serde::Serialize; - -/// Dump various information about a layer, for use in debugging or -/// administrative purposes. -#[derive(Debug, Deserialize, Serialize)] -pub struct DumpLayerReq { - /// The name of the port whose layer you want to dump. - pub port_name: String, - /// The name of the layer to dump. - pub name: String, -} - -/// The response to a [`DumpLayerReq`]. -#[derive(Debug, Deserialize, Serialize)] -pub struct DumpLayerResp { - /// The name of the layer. - pub name: String, - /// The inbound rules. - pub rules_in: Vec, - /// The outbound rules. - pub rules_out: Vec, - /// The default inbound action. - pub default_in: String, - /// The number of times the default inbound action was matched. - pub default_in_hits: u64, - /// The default outbound action. - pub default_out: String, - /// The number of times the default outbound action was matched. - pub default_out_hits: u64, - /// The inbound flow table. - pub ft_in: Vec<(InnerFlowId, ActionDescEntryDump)>, - /// The outbound flow table. - pub ft_out: Vec<(InnerFlowId, ActionDescEntryDump)>, -} - -impl CmdOk for DumpLayerResp {} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ListLayersReq { - pub port_name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct LayerDesc { - /// Name of the layer. - pub name: String, - /// Number of rules inbound. - pub rules_in: usize, - /// Number of rules outbound. - pub rules_out: usize, - /// Default action inbound. - pub default_in: String, - /// Default action outbound. - pub default_out: String, - /// Number of active flows. - pub flows: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ListLayersResp { - pub layers: Vec, -} - -impl CmdOk for ListLayersResp {} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ClearUftReq { - pub port_name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ClearLftReq { - pub port_name: String, - pub layer_name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct DumpUftReq { - pub port_name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct DumpUftResp { - pub in_limit: u32, - pub in_num_flows: u32, - pub in_flows: Vec<(InnerFlowId, UftEntryDump)>, - pub out_limit: u32, - pub out_num_flows: u32, - pub out_flows: Vec<(InnerFlowId, UftEntryDump)>, -} - -impl CmdOk for DumpUftResp {} - -#[derive(Debug, Deserialize, Serialize)] -pub struct UftEntryDump { - pub hits: u64, - pub summary: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct DumpTcpFlowsReq { - pub port_name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct DumpTcpFlowsResp { - pub flows: Vec<(InnerFlowId, TcpFlowEntryDump)>, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct TcpFlowEntryDump { - pub hits: u64, - pub inbound_ufid: Option, - pub tcp_state: TcpFlowStateDump, - pub segs_in: u64, - pub segs_out: u64, - pub bytes_in: u64, - pub bytes_out: u64, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct TcpFlowStateDump { - pub tcp_state: TcpState, - pub guest_seq: Option, - pub guest_ack: Option, - pub remote_seq: Option, - pub remote_ack: Option, -} - -impl CmdOk for DumpTcpFlowsResp {} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ActionDescEntryDump { - pub hits: u64, - pub summary: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct RuleTableEntryDump { - pub id: RuleId, - pub hits: u64, - pub rule: super::ioctl::RuleDump, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct RuleDump { - pub priority: u16, - pub predicates: Vec, - pub data_predicates: Vec, - pub action: String, -} - -pub fn dump_layer( - port: &Port, - req: &DumpLayerReq, -) -> Result { - port.dump_layer(&req.name) -} - -pub fn dump_tcp_flows( - port: &Port, - _req: &DumpTcpFlowsReq, -) -> Result { - port.dump_tcp_flows() -} diff --git a/lib/opte/src/engine/layer.rs b/lib/opte/src/engine/layer.rs index ef853134..bc664e6b 100644 --- a/lib/opte/src/engine/layer.rs +++ b/lib/opte/src/engine/layer.rs @@ -11,8 +11,6 @@ use super::flow_table::FLOW_DEF_EXPIRE_SECS; use super::flow_table::FlowEntry; use super::flow_table::FlowTable; use super::flow_table::FlowTableDump; -use super::ioctl; -use super::ioctl::ActionDescEntryDump; use super::packet::BodyTransformError; use super::packet::FLOW_ID_DEFAULT; use super::packet::InnerFlowId; @@ -32,6 +30,7 @@ use super::rule::Rule; use super::rule::ht_probe; use crate::ExecCtx; use crate::LogLevel; +use crate::api::DumpLayerResp; use crate::d_error::DError; #[cfg(all(not(feature = "std"), not(test)))] use crate::d_error::LabelBlock; @@ -52,7 +51,11 @@ use core::num::NonZeroU32; use core::result; use illumos_sys_hdrs::c_char; use illumos_sys_hdrs::uintptr_t; +use opte_api::ActionDescEntryDump; use opte_api::Direction; +use opte_api::RuleDump; +use opte_api::RuleId; +use opte_api::RuleTableEntryDump; #[derive(Debug)] pub enum LayerError { @@ -142,8 +145,6 @@ impl Display for LayerResult { } } -pub type RuleId = u64; - pub enum Error { RuleNotFound { id: RuleId }, } @@ -548,11 +549,11 @@ impl Layer { /// Dump the contents of this layer. This is used for presenting /// the layer state in a human-friendly manner. - pub(crate) fn dump(&self) -> ioctl::DumpLayerResp { + pub(crate) fn dump(&self) -> DumpLayerResp { let rules_in = self.rules_in.dump(); let rules_out = self.rules_out.dump(); let ftd = self.ft.dump(); - ioctl::DumpLayerResp { + DumpLayerResp { name: self.name.to_string(), ft_in: ftd.ft_in, ft_out: ftd.ft_out, @@ -1533,13 +1534,9 @@ struct RuleTableEntry { rule: Rule, } -impl From<&RuleTableEntry> for ioctl::RuleTableEntryDump { +impl From<&RuleTableEntry> for RuleTableEntryDump { fn from(rte: &RuleTableEntry) -> Self { - Self { - id: rte.id, - hits: rte.hits, - rule: super::ioctl::RuleDump::from(&rte.rule), - } + Self { id: rte.id, hits: rte.hits, rule: RuleDump::from(&rte.rule) } } } @@ -1579,10 +1576,10 @@ impl RuleTable { self.next_id += 1; } - fn dump(&self) -> Vec { + fn dump(&self) -> Vec { let mut dump = Vec::new(); for rte in &self.rules { - dump.push(ioctl::RuleTableEntryDump::from(rte)); + dump.push(RuleTableEntryDump::from(rte)); } dump } diff --git a/lib/opte/src/engine/mod.rs b/lib/opte/src/engine/mod.rs index b1437db6..603f51b3 100644 --- a/lib/opte/src/engine/mod.rs +++ b/lib/opte/src/engine/mod.rs @@ -18,7 +18,6 @@ pub mod geneve; #[macro_use] pub mod headers; pub mod icmp; -pub mod ioctl; pub mod ip; pub mod layer; pub mod nat; @@ -27,8 +26,6 @@ pub mod packet; pub mod parse; pub mod port; pub mod predicate; -#[cfg(any(feature = "std", test))] -pub mod print; pub mod rule; pub mod snat; #[macro_use] diff --git a/lib/opte/src/engine/packet.rs b/lib/opte/src/engine/packet.rs index 03aca751..46c259cf 100644 --- a/lib/opte/src/engine/packet.rs +++ b/lib/opte/src/engine/packet.rs @@ -13,20 +13,15 @@ use super::checksum::Checksum; use super::ether::Ethernet; use super::ether::EthernetPacket; use super::ether::ValidEthernet; -use super::headers::AF_INET; -use super::headers::AF_INET6; use super::headers::EncapMeta; use super::headers::EncapPush; -use super::headers::IpAddr; use super::headers::IpPush; use super::headers::SizeHoldingEncap; use super::headers::ValidEncapMeta; use super::ip::L3; use super::ip::L3Repr; -use super::ip::v4::Ipv4Addr; use super::ip::v4::Ipv4Packet; use super::ip::v4::Ipv4Ref; -use super::ip::v4::Protocol; use super::ip::v6::Ipv6Addr; use super::ip::v6::Ipv6Packet; use super::ip::v6::Ipv6Ref; @@ -38,6 +33,9 @@ use super::rule::CompiledEncap; use super::rule::CompiledTransform; use super::rule::HdrTransform; use super::rule::HdrTransformError; +pub use crate::api::AddrPair; +pub use crate::api::FLOW_ID_DEFAULT; +pub use crate::api::InnerFlowId; use crate::d_error::DError; use crate::ddi::mblk::MsgBlk; use crate::ddi::mblk::MsgBlkIterMut; @@ -51,13 +49,11 @@ use alloc::vec::Vec; use core::cell::Cell; use core::ffi::CStr; use core::fmt; -use core::fmt::Display; use core::hash::Hash; use core::ptr::NonNull; use core::result; use core::sync::atomic::AtomicPtr; use core::sync::atomic::Ordering; -use crc32fast::Hasher; use dyn_clone::DynClone; use illumos_sys_hdrs::mblk_t; use illumos_sys_hdrs::uintptr_t; @@ -86,142 +82,10 @@ use ingot::udp::UdpMut; use ingot::udp::UdpPacket; use ingot::udp::UdpRef; use opte_api::Vni; -use serde::Deserialize; -use serde::Serialize; use zerocopy::ByteSlice; use zerocopy::ByteSliceMut; use zerocopy::IntoBytes; -pub static FLOW_ID_DEFAULT: InnerFlowId = InnerFlowId { - proto: 255, - addrs: AddrPair::V4 { src: Ipv4Addr::ANY_ADDR, dst: Ipv4Addr::ANY_ADDR }, - src_port: 0, - dst_port: 0, -}; - -/// The flow identifier. -/// -/// In this case the flow identifier is the 5-tuple of the inner IP -/// packet. -/// -/// NOTE: This should not be defined in `opte`. Rather, the engine -/// should be generic in regards to the flow identifier, and it should -/// be up to the `NetworkImpl` to define it. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -#[repr(C, align(4))] -pub struct InnerFlowId { - // Using a `u8` here for `proto` hides the enum repr from SDTs. - pub proto: u8, - // We could also theoretically get to a 38B packing if we reduce - // AddrPair's repr from `u16` to `u8`. However, on the dtrace/illumos - // side `union addrs` is 4B aligned -- in6_addr_t has a 4B alignment. - // So, this layout has to match that constraint -- placing addrs at - // offset 0x2 with `u16` discriminant sets up 4B alignment for the - // enum variant data (and this struct itself is 4B aligned). - pub addrs: AddrPair, - pub src_port: u16, - pub dst_port: u16, -} - -impl InnerFlowId { - pub fn crc32(&self) -> u32 { - let mut hasher = Hasher::new(); - self.hash(&mut hasher); - hasher.finalize() - } -} - -impl Default for InnerFlowId { - fn default() -> Self { - FLOW_ID_DEFAULT - } -} - -/// Tagged union of a source-dest IP address pair, used to avoid -/// duplicating the discriminator. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -#[repr(C, u16)] -pub enum AddrPair { - V4 { src: Ipv4Addr, dst: Ipv4Addr } = AF_INET as u16, - V6 { src: Ipv6Addr, dst: Ipv6Addr } = AF_INET6 as u16, -} - -impl AddrPair { - pub fn mirror(self) -> Self { - match self { - Self::V4 { src, dst } => Self::V4 { src: dst, dst: src }, - Self::V6 { src, dst } => Self::V6 { src: dst, dst: src }, - } - } -} - -impl InnerFlowId { - /// Swap IP source and destination as well as ULP port source and - /// destination. - pub fn mirror(self) -> Self { - Self { - proto: self.proto, - addrs: self.addrs.mirror(), - src_port: self.dst_port, - dst_port: self.src_port, - } - } - - pub fn src_ip(&self) -> IpAddr { - match self.addrs { - AddrPair::V4 { src, .. } => src.into(), - AddrPair::V6 { src, .. } => src.into(), - } - } - - pub fn dst_ip(&self) -> IpAddr { - match self.addrs { - AddrPair::V4 { dst, .. } => dst.into(), - AddrPair::V6 { dst, .. } => dst.into(), - } - } - - pub fn protocol(&self) -> Protocol { - Protocol::from(self.proto) - } -} - -impl Display for InnerFlowId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}:{}:{}:{}:{}", - self.protocol(), - self.src_ip(), - self.src_port, - self.dst_ip(), - self.dst_port, - ) - } -} - pub trait PacketState {} /// A packet body transformation. @@ -1866,6 +1730,7 @@ mod test { use ingot::tcp::TcpRef; use ingot::types::HeaderLen; use ingot::udp::Udp; + use opte_api::Ipv4Addr; use opte_api::Ipv6Addr; use opte_api::MacAddr; diff --git a/lib/opte/src/engine/port/mod.rs b/lib/opte/src/engine/port/mod.rs index 78ed89e3..aaec912c 100644 --- a/lib/opte/src/engine/port/mod.rs +++ b/lib/opte/src/engine/port/mod.rs @@ -19,10 +19,6 @@ use super::headers::EncapPush; use super::headers::HeaderAction; use super::headers::IpPush; use super::headers::UlpHeaderAction; -use super::ioctl; -use super::ioctl::TcpFlowEntryDump; -use super::ioctl::TcpFlowStateDump; -use super::ioctl::UftEntryDump; use super::ip::L3Repr; use super::ip::v4::Ipv4; use super::ip::v6::Ipv6; @@ -31,7 +27,6 @@ use super::layer::Layer; use super::layer::LayerError; use super::layer::LayerResult; use super::layer::LayerStatsSnap; -use super::layer::RuleId; use super::packet::BodyTransform; use super::packet::BodyTransformError; use super::packet::FLOW_ID_DEFAULT; @@ -51,10 +46,13 @@ use super::rule::Rule; use super::rule::TransformFlags; use super::tcp::KEEPALIVE_EXPIRE_TTL; use super::tcp::TIME_WAIT_EXPIRE_TTL; -use super::tcp::TcpState; use super::tcp_state::TcpFlowState; use super::tcp_state::TcpFlowStateError; use crate::ExecCtx; +use crate::api::DumpLayerResp; +use crate::api::DumpTcpFlowsResp; +use crate::api::DumpUftResp; +use crate::api::TcpFlowEntryDump; use crate::d_error::DError; #[cfg(all(not(feature = "std"), not(test)))] use crate::d_error::LabelBlock; @@ -97,8 +95,14 @@ use ingot::types::Read; use ingot::udp::Udp; use meta::ActionMeta; use opte_api::Direction; +use opte_api::LayerDesc; +use opte_api::ListLayersResp; use opte_api::MacAddr; use opte_api::OpteError; +use opte_api::RuleId; +use opte_api::TcpFlowStateDump; +use opte_api::TcpState; +use opte_api::UftEntryDump; use zerocopy::ByteSlice; use zerocopy::ByteSliceMut; @@ -373,12 +377,12 @@ impl PortBuilder { } /// List each [`Layer`] under this port. - pub fn list_layers(&self) -> ioctl::ListLayersResp { + pub fn list_layers(&self) -> ListLayersResp { let mut tmp = vec![]; let lock = self.layers.lock(); for layer in lock.iter() { - tmp.push(ioctl::LayerDesc { + tmp.push(LayerDesc { name: layer.name().to_string(), rules_in: layer.num_rules(Direction::In), rules_out: layer.num_rules(Direction::Out), @@ -388,7 +392,7 @@ impl PortBuilder { }); } - ioctl::ListLayersResp { layers: tmp } + ListLayersResp { layers: tmp } } /// Return the name of the port. @@ -963,7 +967,7 @@ impl Port { /// # States /// /// This command is valid for any [`PortState`]. - pub fn dump_layer(&self, name: &str) -> Result { + pub fn dump_layer(&self, name: &str) -> Result { let data = self.data.read(); for l in &data.layers { @@ -984,14 +988,14 @@ impl Port { /// * [`PortState::Running`] /// * [`PortState::Paused`] /// * [`PortState::Restored`] - pub fn dump_tcp_flows(&self) -> Result { + pub fn dump_tcp_flows(&self) -> Result { let data = self.data.read(); check_state!( data.state, [PortState::Running, PortState::Paused, PortState::Restored] )?; - Ok(ioctl::DumpTcpFlowsResp { flows: data.tcp_flows.dump() }) + Ok(DumpTcpFlowsResp { flows: data.tcp_flows.dump() }) } /// Clear all entries from the Unified Flow Table (UFT). @@ -1037,7 +1041,7 @@ impl Port { /// * [`PortState::Running`] /// * [`PortState::Paused`] /// * [`PortState::Restored`] - pub fn dump_uft(&self) -> Result { + pub fn dump_uft(&self) -> Result { let data = self.data.read(); check_state!( @@ -1053,7 +1057,7 @@ impl Port { let out_num_flows = data.uft_out.num_flows(); let out_flows = data.uft_out.dump(); - Ok(ioctl::DumpUftResp { + Ok(DumpUftResp { in_limit, in_num_flows, in_flows, @@ -1178,12 +1182,12 @@ impl Port { /// # States /// /// This command is valid for any [`PortState`]. - pub fn list_layers(&self) -> ioctl::ListLayersResp { + pub fn list_layers(&self) -> ListLayersResp { let data = self.data.read(); let mut tmp = vec![]; for layer in &data.layers { - tmp.push(ioctl::LayerDesc { + tmp.push(LayerDesc { name: layer.name().to_string(), rules_in: layer.num_rules(Direction::In), rules_out: layer.num_rules(Direction::Out), @@ -1193,7 +1197,7 @@ impl Port { }); } - ioctl::ListLayersResp { layers: tmp } + ListLayersResp { layers: tmp } } /// Return the MAC address of this port. @@ -2990,7 +2994,7 @@ impl Display for TcpFlowEntryState { impl Dump for TcpFlowEntryStateInner { type DumpVal = TcpFlowEntryDump; - fn dump(&self, hits: u64) -> TcpFlowEntryDump { + fn dump(&self, hits: u64) -> Self::DumpVal { TcpFlowEntryDump { hits, inbound_ufid: self.inbound_ufid, @@ -3006,7 +3010,7 @@ impl Dump for TcpFlowEntryStateInner { impl Dump for TcpFlowEntryState { type DumpVal = TcpFlowEntryDump; - fn dump(&self, hits: u64) -> TcpFlowEntryDump { + fn dump(&self, hits: u64) -> Self::DumpVal { let inner = self.inner.lock(); inner.dump(hits) } diff --git a/lib/opte/src/engine/rule.rs b/lib/opte/src/engine/rule.rs index 5745b4b4..9ce1030f 100644 --- a/lib/opte/src/engine/rule.rs +++ b/lib/opte/src/engine/rule.rs @@ -65,6 +65,7 @@ use ingot::types::InlineHeader; use ingot::types::Read; use ingot::udp::UdpMut; use opte_api::Direction; +use opte_api::RuleDump; use serde::Deserialize; use serde::Serialize; use zerocopy::ByteSliceMut; @@ -1117,18 +1118,16 @@ impl Rule { } } -impl From<&Rule> for super::ioctl::RuleDump { +impl From<&Rule> for RuleDump { fn from(rule: &Rule) -> Self { let predicates = rule.state.preds.as_ref().map_or(vec![], |rp| { rp.hdr_preds.iter().map(ToString::to_string).collect() }); - let data_predicates = rule - .state - .preds - .as_ref() - .map_or(vec![], |rp| rp.data_preds.clone()); + let data_predicates = rule.state.preds.as_ref().map_or(vec![], |rp| { + rp.data_preds.iter().map(|v| v.to_string()).collect() + }); - super::ioctl::RuleDump { + RuleDump { priority: rule.priority, predicates, data_predicates, diff --git a/lib/opte/src/engine/tcp.rs b/lib/opte/src/engine/tcp.rs index 5517002f..a4875539 100644 --- a/lib/opte/src/engine/tcp.rs +++ b/lib/opte/src/engine/tcp.rs @@ -2,13 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 Oxide Computer Company +// Copyright 2025 Oxide Computer Company //! TCP headers. use super::flow_table::Ttl; -use core::fmt; -use core::fmt::Display; use serde::Deserialize; use serde::Serialize; @@ -36,41 +34,6 @@ pub const KEEPALIVE_EXPIRE_SECS: u64 = 8_000; pub const TIME_WAIT_EXPIRE_TTL: Ttl = Ttl::new_seconds(TIME_WAIT_EXPIRE_SECS); pub const KEEPALIVE_EXPIRE_TTL: Ttl = Ttl::new_seconds(KEEPALIVE_EXPIRE_SECS); -// The standard TCP states. -// -// See Figure 13-8 of TCP/IP Illustrated Vol. 1 Ed. 2 -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum TcpState { - Closed, - Listen, - SynSent, - SynRcvd, - Established, - CloseWait, - LastAck, - FinWait1, - FinWait2, - TimeWait, -} - -impl Display for TcpState { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - TcpState::Closed => "CLOSED", - TcpState::Listen => "LISTEN", - TcpState::SynSent => "SYN_SENT", - TcpState::SynRcvd => "SYN_RCVD", - TcpState::Established => "ESTABLISHED", - TcpState::CloseWait => "CLOSE_WAIT", - TcpState::LastAck => "LAST_ACK", - TcpState::FinWait1 => "FIN_WAIT_1", - TcpState::FinWait2 => "FIN_WAIT_2", - TcpState::TimeWait => "TIME_WAIT", - }; - write!(f, "{}", s) - } -} - #[derive( Clone, Copy, diff --git a/lib/opte/src/engine/tcp_state.rs b/lib/opte/src/engine/tcp_state.rs index 01e6cf35..c8d25b99 100644 --- a/lib/opte/src/engine/tcp_state.rs +++ b/lib/opte/src/engine/tcp_state.rs @@ -7,7 +7,6 @@ //! Basic TCP state machine. use super::packet::InnerFlowId; -use super::tcp::TcpState; use core::ffi::CStr; use core::fmt; use core::fmt::Display; @@ -16,6 +15,8 @@ use illumos_sys_hdrs::uintptr_t; use ingot::tcp::TcpFlags as IngotTcpFlags; use ingot::tcp::TcpRef; use opte_api::Direction; +use opte_api::TcpFlowStateDump; +use opte_api::TcpState; use zerocopy::ByteSlice; /// An error processing a TCP flow. @@ -95,7 +96,7 @@ pub struct TcpFlowState { remote_ack: Option, } -impl From for super::ioctl::TcpFlowStateDump { +impl From for TcpFlowStateDump { fn from(tfs: TcpFlowState) -> Self { Self { tcp_state: tfs.tcp_state, diff --git a/lib/opte/src/lib.rs b/lib/opte/src/lib.rs index 42ec1668..1adb074c 100644 --- a/lib/opte/src/lib.rs +++ b/lib/opte/src/lib.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 Oxide Computer Company +// Copyright 2025 Oxide Computer Company #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::len_without_is_empty)] @@ -20,6 +20,7 @@ #[cfg_attr(feature = "engine", macro_use)] extern crate alloc; +#[cfg(any(feature = "engine", feature = "kernel"))] #[macro_use] extern crate cfg_if; @@ -34,9 +35,7 @@ use core::fmt::Display; pub use ingot; #[cfg(any(feature = "api", test))] -pub mod api { - pub use opte_api::*; -} +pub mod api; #[cfg(any(feature = "engine", test))] pub mod d_error; #[cfg(any(feature = "engine", test))] @@ -45,6 +44,8 @@ pub mod ddi; pub mod dynamic; #[cfg(any(feature = "engine", test))] pub mod engine; +#[cfg(any(feature = "std", test))] +pub mod print; /// Return value with `bit` set. /// diff --git a/lib/opte/src/engine/print.rs b/lib/opte/src/print.rs similarity index 95% rename from lib/opte/src/engine/print.rs rename to lib/opte/src/print.rs index 8802f652..b8c2d9af 100644 --- a/lib/opte/src/engine/print.rs +++ b/lib/opte/src/print.rs @@ -2,22 +2,22 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 Oxide Computer Company +// Copyright 2025 Oxide Computer Company //! Print comannd responses in human-friendly manner. //! //! This is mostly just a place to hang printing routines so that they //! can be used by both opteadm and integration tests. -use super::ioctl::ActionDescEntryDump; -use super::ioctl::DumpLayerResp; -use super::ioctl::DumpTcpFlowsResp; -use super::ioctl::DumpUftResp; -use super::ioctl::ListLayersResp; -use super::ioctl::RuleDump; -use super::ioctl::TcpFlowEntryDump; -use super::ioctl::UftEntryDump; -use super::packet::InnerFlowId; +use crate::api::DumpLayerResp; +use crate::api::DumpTcpFlowsResp; +use crate::api::DumpUftResp; +use crate::api::InnerFlowId; +use crate::api::TcpFlowEntryDump; +use opte_api::ActionDescEntryDump; +use opte_api::ListLayersResp; +use opte_api::RuleDump; +use opte_api::UftEntryDump; use std::collections::VecDeque; use std::io::Write; use std::string::String; diff --git a/lib/oxide-vpc/Cargo.toml b/lib/oxide-vpc/Cargo.toml index a4c96c72..56516554 100644 --- a/lib/oxide-vpc/Cargo.toml +++ b/lib/oxide-vpc/Cargo.toml @@ -14,7 +14,7 @@ repository.workspace = true [features] default = ["api", "std"] api = ["opte/api"] -engine = ["api", "opte/engine"] +engine = ["api", "opte/engine", "dep:poptrie", "dep:smoltcp"] kernel = ["opte/kernel"] std = ["dep:tabwriter","opte/std"] # @@ -34,9 +34,9 @@ illumos-sys-hdrs.workspace = true opte.workspace = true cfg-if.workspace = true -poptrie.workspace = true +poptrie = { workspace = true, optional = true } serde.workspace = true -smoltcp.workspace = true +smoltcp = { workspace = true, optional = true } tabwriter = { workspace = true, optional = true } uuid.workspace = true zerocopy.workspace = true diff --git a/lib/oxide-vpc/src/api.rs b/lib/oxide-vpc/src/api.rs index 7f8464f5..7396b80d 100644 --- a/lib/oxide-vpc/src/api.rs +++ b/lib/oxide-vpc/src/api.rs @@ -22,6 +22,9 @@ use uuid::Uuid; /// This is the MAC address that OPTE uses to act as the virtual gateway. pub const GW_MAC_ADDR: MacAddr = MacAddr::from_const([0xA8, 0x40, 0x25, 0xFF, 0x77, 0x77]); +/// The default VNI ID which OPTE uses for outbound packets directed at a +/// tunnel endpoint. +pub const BOUNDARY_SERVICES_VNI: u32 = 99u32; /// Description of Boundary Services, the endpoint used to route traffic /// to external networks. diff --git a/lib/oxide-vpc/src/engine/mod.rs b/lib/oxide-vpc/src/engine/mod.rs index 5820e33d..c8f6fbf8 100644 --- a/lib/oxide-vpc/src/engine/mod.rs +++ b/lib/oxide-vpc/src/engine/mod.rs @@ -8,8 +8,6 @@ pub mod firewall; pub mod gateway; pub mod nat; pub mod overlay; -#[cfg(any(feature = "std", test))] -pub mod print; pub mod router; use crate::cfg::VpcCfg; diff --git a/lib/oxide-vpc/src/engine/overlay.rs b/lib/oxide-vpc/src/engine/overlay.rs index c22352d6..8a7cc1ec 100644 --- a/lib/oxide-vpc/src/engine/overlay.rs +++ b/lib/oxide-vpc/src/engine/overlay.rs @@ -555,7 +555,6 @@ pub struct Virt2Boundary { pt6: KRwLock>>, } -pub const BOUNDARY_SERVICES_VNI: u32 = 99u32; pub const TUNNEL_ENDPOINT_MAC: [u8; 6] = [0xA8, 0x40, 0x25, 0x77, 0x77, 0x77]; impl Virt2Boundary { diff --git a/lib/oxide-vpc/src/lib.rs b/lib/oxide-vpc/src/lib.rs index 8dd59954..e3d7b3aa 100644 --- a/lib/oxide-vpc/src/lib.rs +++ b/lib/oxide-vpc/src/lib.rs @@ -38,3 +38,6 @@ pub mod engine; #[cfg(any(feature = "engine", test))] pub mod cfg; + +#[cfg(any(feature = "std", test))] +pub mod print; diff --git a/lib/oxide-vpc/src/engine/print.rs b/lib/oxide-vpc/src/print.rs similarity index 98% rename from lib/oxide-vpc/src/engine/print.rs rename to lib/oxide-vpc/src/print.rs index 621d701b..c6a46ef3 100644 --- a/lib/oxide-vpc/src/engine/print.rs +++ b/lib/oxide-vpc/src/print.rs @@ -15,8 +15,8 @@ use crate::api::GuestPhysAddr; use crate::api::Ipv4Addr; use crate::api::Ipv6Addr; use opte::api::IpCidr; -use opte::engine::geneve::Vni; -use opte::engine::print::*; +use opte::api::Vni; +use opte::print::*; use std::io::Write; use tabwriter::TabWriter; diff --git a/lib/oxide-vpc/tests/firewall_tests.rs b/lib/oxide-vpc/tests/firewall_tests.rs index 22a4fa98..0be752fe 100644 --- a/lib/oxide-vpc/tests/firewall_tests.rs +++ b/lib/oxide-vpc/tests/firewall_tests.rs @@ -2,13 +2,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 Oxide Computer Company +// Copyright 2025 Oxide Computer Company use opte::ddi::mblk::MsgBlk; use opte_test_utils as common; use common::*; -use oxide_vpc::engine::overlay::BOUNDARY_SERVICES_VNI; +use oxide_vpc::api::BOUNDARY_SERVICES_VNI; #[test] fn firewall_replace_rules() { diff --git a/lib/oxide-vpc/tests/integration_tests.rs b/lib/oxide-vpc/tests/integration_tests.rs index dc1c1e6d..08c173a5 100644 --- a/lib/oxide-vpc/tests/integration_tests.rs +++ b/lib/oxide-vpc/tests/integration_tests.rs @@ -17,6 +17,7 @@ use common::icmp::*; use common::*; use opte::api::MacAddr; use opte::api::OpteError; +use opte::api::TcpState; use opte::ddi::mblk::MsgBlk; use opte::ddi::time::Moment; use opte::engine::Direction; @@ -44,7 +45,6 @@ use opte::engine::packet::Packet; use opte::engine::parse::ValidUlp; use opte::engine::port::ProcessError; use opte::engine::tcp::TIME_WAIT_EXPIRE_SECS; -use opte::engine::tcp::TcpState; use opte::ingot::geneve::GeneveRef; use opte::ingot::icmp::IcmpV6Ref; use opte::ingot::tcp::TcpRef; @@ -54,11 +54,11 @@ use opte::ingot::types::HeaderParse; use opte::ingot::udp::Udp; use opte::ingot::udp::UdpRef; use opte_test_utils as common; +use oxide_vpc::api::BOUNDARY_SERVICES_VNI; use oxide_vpc::api::ExternalIpCfg; use oxide_vpc::api::FirewallRule; use oxide_vpc::api::RouterClass; use oxide_vpc::api::VpcCfg; -use oxide_vpc::engine::overlay::BOUNDARY_SERVICES_VNI; use pcap::*; use smoltcp::phy::ChecksumCapabilities as CsumCapab; use smoltcp::wire::Icmpv4Packet; diff --git a/xde-tests/src/lib.rs b/xde-tests/src/lib.rs index f0ba1915..295ea6b8 100644 --- a/xde-tests/src/lib.rs +++ b/xde-tests/src/lib.rs @@ -6,6 +6,7 @@ use anyhow::Result; use opteadm::OpteAdm; +use oxide_vpc::api::AddFwRuleReq; use oxide_vpc::api::AddRouterEntryReq; use oxide_vpc::api::Address; use oxide_vpc::api::DhcpCfg; @@ -108,14 +109,14 @@ impl OptePort { vni: Vni::new(1701u32).unwrap(), phys_ip: phys_ip.parse().unwrap(), }; - let adm = OpteAdm::open(OpteAdm::XDE_CTL)?; + let adm = OpteAdm::open()?; adm.create_xde(name, cfg.clone(), DhcpCfg::default(), false)?; Ok(OptePort { name: name.into(), cfg }) } /// Add an overlay routing entry to this port. pub fn add_router_entry(&self, dest: &str) -> Result<()> { - let adm = OpteAdm::open(OpteAdm::XDE_CTL)?; + let adm = OpteAdm::open()?; adm.add_router_entry(&AddRouterEntryReq { port_name: self.name.clone(), dest: IpCidr::Ip4(format!("{}/32", dest).parse().unwrap()), @@ -127,19 +128,20 @@ impl OptePort { /// Allow all traffic through the overlay firewall. pub fn fw_allow_all(&self) -> Result<()> { - let adm = OpteAdm::open(OpteAdm::XDE_CTL)?; + let adm = OpteAdm::open()?; let mut filters = Filters::new(); filters.set_hosts(Address::Any); filters.set_ports(Ports::Any); - adm.add_firewall_rule( - &self.name, - &FirewallRule { + adm.add_firewall_rule(&AddFwRuleReq { + port_name: self.name.clone(), + rule: FirewallRule { direction: Direction::In, action: FirewallAction::Allow, priority: 0, filters, }, - )?; + })?; + Ok(()) } @@ -165,7 +167,7 @@ impl OptePort { impl Drop for OptePort { /// When this port is dropped, remove it from the underlying xde device. fn drop(&mut self) { - let adm = match OpteAdm::open(OpteAdm::XDE_CTL) { + let adm = match OpteAdm::open() { Ok(adm) => adm, Err(e) => { eprintln!("failed to open xde device on drop: {}", e); @@ -187,14 +189,14 @@ pub struct Xde {} impl Xde { /// Set the underlay data links that all OPTE ports will use. fn set_xde_underlay(dev0: &str, dev1: &str) -> Result<()> { - let adm = OpteAdm::open(OpteAdm::XDE_CTL)?; + let adm = OpteAdm::open()?; adm.set_xde_underlay(dev0, dev1)?; Ok(()) } /// Set the virtual to physical port mappings that all OPTE ports will use. fn set_v2p(vip: &str, ether: &str, ip: &str) -> Result<()> { - let adm = OpteAdm::open(OpteAdm::XDE_CTL)?; + let adm = OpteAdm::open()?; adm.set_v2p(&SetVirt2PhysReq { vip: vip.parse().unwrap(), phys: PhysNet { @@ -212,7 +214,7 @@ impl Drop for Xde { fn drop(&mut self) { // The module can no longer be successfully removed until the underlay // has been cleared. This may not have been done, so this is fallible. - if let Ok(adm) = OpteAdm::open(OpteAdm::XDE_CTL) { + if let Ok(adm) = OpteAdm::open() { let _ = adm.clear_xde_underlay(); } diff --git a/xde/README.md b/xde/README.md index 84d60e7b..4a4ad55d 100644 --- a/xde/README.md +++ b/xde/README.md @@ -19,22 +19,22 @@ libraries `dladm` links to.** ## Building The following builds the `xde` loadable kernel module and places it at -`target/x86_64-unknown-unknown/release/`. +`target/x86_64-unknown-unknown/release-lto/` – this command should be run from the workspace root. ``` -./build.sh +cargo xtask build xde ``` The file src/ip.rs is autogenerated, to generate it use `ip-bindgen.sh`. ## Installation -Copy this to `/kernel/drv/amd64/` on the platform you're testing on. Then -copy `xde.conf` to `/kernel/drv/`. Then add the driver. +OPTE can be installed using the following command from the workspace root: ``` -add_drv xde +cargo xtask install ``` +The above module file can alternately be copied to `/kernel/drv/amd64/` on the platform you're testing on after the first installation. ## Usage diff --git a/xde/build-debug.sh b/xde/build-debug.sh deleted file mode 100755 index e6ed4f7f..00000000 --- a/xde/build-debug.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -DBG_DIR=../target/x86_64-unknown-unknown/debug/ - -cargo -v build - -ld -ztype=kmod \ - -N"drv/dld" \ - -N"drv/ip" \ - -N"misc/dls" \ - -N"misc/mac" \ - -z allextract $DBG_DIR/xde.a \ - -o $DBG_DIR/xde.dbg - -# Also build devfsadm plugin -pushd xde-link -cargo -v build \ No newline at end of file diff --git a/xde/build.sh b/xde/build.sh deleted file mode 100755 index b6a2b8a5..00000000 --- a/xde/build.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -set -xe - -REL_DIR=../target/x86_64-unknown-unknown/release/ - -cargo -v build --release - -ld -ztype=kmod \ - -N"drv/dld" \ - -N"drv/ip" \ - -N"misc/dls" \ - -N"misc/mac" \ - -z allextract $REL_DIR/xde.a \ - -o $REL_DIR/xde - -# Also build devfsadm plugin -pushd xde-link -cargo -v build --release - - -# We don't want to panic in the devfsadm plugin but enforcing that -# is a bit tricky. For now, just manually verify w/ nm: -set +e -nm ../../target/i686-unknown-illumos/release/libxde_link.so | grep panicking -if [ $? -eq 0 ]; then - echo "ERROR: devfsadm plugin may panic!" - exit 1 -fi -popd diff --git a/xde/src/xde.rs b/xde/src/xde.rs index 4ebc306f..96c6c3eb 100644 --- a/xde/src/xde.rs +++ b/xde/src/xde.rs @@ -63,9 +63,19 @@ use ingot::ip::IpProtocol; use ingot::types::HeaderLen; use ingot::udp::Udp; use opte::ExecCtx; +use opte::api::ClearLftReq; +use opte::api::ClearUftReq; use opte::api::ClearXdeUnderlayReq; use opte::api::CmdOk; use opte::api::Direction; +use opte::api::DumpLayerReq; +use opte::api::DumpLayerResp; +use opte::api::DumpTcpFlowsReq; +use opte::api::DumpTcpFlowsResp; +use opte::api::DumpUftReq; +use opte::api::DumpUftResp; +use opte::api::ListLayersReq; +use opte::api::ListLayersResp; use opte::api::NoResp; use opte::api::OpteCmd; use opte::api::OpteCmdIoctl; @@ -89,7 +99,6 @@ use opte::engine::ether::Ethernet; use opte::engine::ether::EthernetRef; use opte::engine::geneve::Vni; use opte::engine::headers::IpAddr; -use opte::engine::ioctl::{self as api}; use opte::engine::ip::v6::Ipv6; use opte::engine::ip::v6::Ipv6Addr; use opte::engine::packet::InnerFlowId; @@ -2309,8 +2318,8 @@ fn dump_v2b_hdlr( #[unsafe(no_mangle)] fn list_layers_hdlr( env: &mut IoctlEnvelope, -) -> Result { - let req: api::ListLayersReq = env.copy_in_req()?; +) -> Result { + let req: ListLayersReq = env.copy_in_req()?; let devs = xde_devs().read(); let Some(dev) = devs.get_by_name(&req.port_name) else { return Err(OpteError::PortNotFound(req.port_name)); @@ -2321,7 +2330,7 @@ fn list_layers_hdlr( #[unsafe(no_mangle)] fn clear_uft_hdlr(env: &mut IoctlEnvelope) -> Result { - let req: api::ClearUftReq = env.copy_in_req()?; + let req: ClearUftReq = env.copy_in_req()?; let devs = xde_devs().read(); let Some(dev) = devs.get_by_name(&req.port_name) else { return Err(OpteError::PortNotFound(req.port_name)); @@ -2333,7 +2342,7 @@ fn clear_uft_hdlr(env: &mut IoctlEnvelope) -> Result { #[unsafe(no_mangle)] fn clear_lft_hdlr(env: &mut IoctlEnvelope) -> Result { - let req: api::ClearLftReq = env.copy_in_req()?; + let req: ClearLftReq = env.copy_in_req()?; let devs = xde_devs().read(); let Some(dev) = devs.get_by_name(&req.port_name) else { return Err(OpteError::PortNotFound(req.port_name)); @@ -2344,10 +2353,8 @@ fn clear_lft_hdlr(env: &mut IoctlEnvelope) -> Result { } #[unsafe(no_mangle)] -fn dump_uft_hdlr( - env: &mut IoctlEnvelope, -) -> Result { - let req: api::DumpUftReq = env.copy_in_req()?; +fn dump_uft_hdlr(env: &mut IoctlEnvelope) -> Result { + let req: DumpUftReq = env.copy_in_req()?; let devs = xde_devs().read(); let Some(dev) = devs.get_by_name(&req.port_name) else { return Err(OpteError::PortNotFound(req.port_name)); @@ -2359,27 +2366,27 @@ fn dump_uft_hdlr( #[unsafe(no_mangle)] fn dump_layer_hdlr( env: &mut IoctlEnvelope, -) -> Result { - let req: api::DumpLayerReq = env.copy_in_req()?; +) -> Result { + let req: DumpLayerReq = env.copy_in_req()?; let devs = xde_devs().read(); let Some(dev) = devs.get_by_name(&req.port_name) else { return Err(OpteError::PortNotFound(req.port_name)); }; - api::dump_layer(&dev.port, &req) + dev.port.dump_layer(&req.name) } #[unsafe(no_mangle)] fn dump_tcp_flows_hdlr( env: &mut IoctlEnvelope, -) -> Result { - let req: api::DumpTcpFlowsReq = env.copy_in_req()?; +) -> Result { + let req: DumpTcpFlowsReq = env.copy_in_req()?; let devs = xde_devs().read(); let Some(dev) = devs.get_by_name(&req.port_name) else { return Err(OpteError::PortNotFound(req.port_name)); }; - api::dump_tcp_flows(&dev.port, &req) + dev.port.dump_tcp_flows() } #[unsafe(no_mangle)] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d0cd3cba..00d2cedc 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -9,6 +9,9 @@ use anyhow::Result; use cargo_metadata::Metadata; use clap::Args; use clap::Parser; +use clap::ValueEnum; +use std::collections::BTreeSet; +use std::fmt; use std::io::Write; use std::path::PathBuf; use std::process::Command; @@ -27,7 +30,14 @@ const LINK_TARGET: &str = "i686-unknown-illumos"; #[derive(Debug, Parser)] enum Xtask { /// Build the XDE kernel module. - Build(BuildOptions), + Build { + /// The artefacts to be built. + #[clap(default_values_t = [BuildTarget::All])] + targets: Vec, + + #[command(flatten)] + build: BuildOptions, + }, /// Build and install the XDE kernel module. Install { @@ -68,15 +78,15 @@ enum Xtask { struct BuildOptions { /// Disable building/packaging debug bits for both `opteadm` /// and the XDE module. - #[arg(long)] - release_only: bool, + #[arg(long, default_value_t = Profile::Release)] + profile: Profile, } fn main() -> anyhow::Result<()> { let cmd = Xtask::parse(); // TODO: gate some of these to illumos only. match cmd { - Xtask::Build(b) => cmd_build(b.release_only), + Xtask::Build { targets, build } => cmd_build(targets, build.profile), Xtask::Install { from_package, force_package_unfreeze, @@ -84,11 +94,11 @@ fn main() -> anyhow::Result<()> { build, } => { if !skip_build { - cmd_build(!from_package || build.release_only)?; + cmd_build(vec![BuildTarget::All], build.profile)?; } let pkg_info = if from_package { - Some(cmd_package(!skip_build, build.release_only)?) + Some(cmd_package(!skip_build, build.profile)?) } else { None }; @@ -128,10 +138,10 @@ fn main() -> anyhow::Result<()> { } Xtask::Package { build, skip_build } => { if !skip_build { - cmd_build(build.release_only)?; + cmd_build(vec![BuildTarget::All], build.profile)?; } - let (p_path, _) = cmd_package(true, build.release_only)?; + let (p_path, _) = cmd_package(true, build.profile)?; println!( "Successfully built package {}.", @@ -215,17 +225,24 @@ fn elevate(operation: &str, extra_args: &[&str]) -> Result { } } -fn cmd_build(release_only: bool) -> Result<()> { - let modes = if release_only { - &[PkgProfile::Release][..] - } else { - &[PkgProfile::Release, PkgProfile::Debug] +fn cmd_build(targets: Vec, profile: Profile) -> Result<()> { + let modes: &[_] = match profile { + Profile::All => &[PkgProfile::Release, PkgProfile::Debug], + Profile::Release => &[PkgProfile::Release], + Profile::Debug => &[PkgProfile::Debug], }; + let mut unique_targets: BTreeSet<_> = targets.into_iter().collect(); + if unique_targets.remove(&BuildTarget::All) { + unique_targets.insert(BuildTarget::OpteAdm); + unique_targets.insert(BuildTarget::Xde); + unique_targets.insert(BuildTarget::XdeLink); + } + for release_mode in modes { - BuildTarget::OpteAdm.build(*release_mode)?; - BuildTarget::Xde.build(*release_mode)?; - BuildTarget::XdeLink.build(*release_mode)?; + for target in &unique_targets { + target.build(*release_mode)?; + } } Ok(()) @@ -233,17 +250,23 @@ fn cmd_build(release_only: bool) -> Result<()> { fn cmd_package( do_package: bool, - release_only: bool, + profile: Profile, ) -> Result<(PathBuf, String)> { let meta = cargo_meta(); let pkg_dir = meta.workspace_root.join("pkg"); + if profile == Profile::Debug { + anyhow::bail!( + "`package` only supports the profiles 'release' or 'all'" + ); + } + if do_package { // XXX: I'm happy today for this to remain as a bash script, // given that it would be very verbose to xtask-ify. let mut cmd = Command::new("bash"); - if release_only { + if profile == Profile::Release { cmd.env("RELEASE_ONLY", "1"); } @@ -320,6 +343,25 @@ fn raw_install() -> Result<()> { Ok(()) } +#[derive( + Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, ValueEnum, +)] +enum Profile { + All, + Debug, + Release, +} + +impl fmt::Display for Profile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::All => "all", + Self::Debug => "debug", + Self::Release => "release", + }) + } +} + #[derive(Copy, Clone, Debug)] enum PkgProfile { Debug, @@ -351,13 +393,27 @@ impl RustProfile { } } -#[derive(Copy, Clone, Debug)] +#[derive( + Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, ValueEnum, +)] enum BuildTarget { + All, OpteAdm, Xde, XdeLink, } +impl fmt::Display for BuildTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::All => "all", + Self::OpteAdm => "opteadm", + Self::Xde => "xde", + Self::XdeLink => "xde-link", + }) + } +} + fn build_cargo_bin( target: &[&str], profile: &str, @@ -420,9 +476,15 @@ impl BuildTarget { let p_name = rust_profile.name(); let p_folder = rust_profile.folder(); match self { + Self::All => anyhow::bail!("'all' should have been filtered"), Self::OpteAdm => { println!("Building opteadm ({p_name})."); - build_cargo_bin(&["--bin", "opteadm"], p_name, None, true) + build_cargo_bin( + &["--bin", "opteadm"], + p_name, + Some("bin/opteadm"), + true, + ) } Self::Xde => { println!("Building xde ({p_name}).");