diff --git a/bin/opteadm/src/bin/opteadm.rs b/bin/opteadm/src/bin/opteadm.rs index 1e610850..27f803fd 100644 --- a/bin/opteadm/src/bin/opteadm.rs +++ b/bin/opteadm/src/bin/opteadm.rs @@ -559,7 +559,7 @@ fn print_port(t: &mut impl Write, pi: PortInfo) -> std::io::Result<()> { if n_rows > 1 { // This is required over a plain \n to preserve column alignment // between all ports. - writeln!(t, "\t\t\t\t\t\t\t\t",)?; + writeln!(t, "\t\t\t\t\t\t\t\t")?; } Ok(()) diff --git a/crates/opte-api/src/cmd.rs b/crates/opte-api/src/cmd.rs index 6ec2848d..5c0f9986 100644 --- a/crates/opte-api/src/cmd.rs +++ b/crates/opte-api/src/cmd.rs @@ -11,6 +11,7 @@ use super::encap::Vni; use super::ip::IpCidr; use super::mac::MacAddr; use alloc::string::String; +use alloc::string::ToString; use alloc::vec::Vec; use core::fmt::Debug; use illumos_sys_hdrs::c_int; @@ -130,7 +131,7 @@ impl OpteCmdIoctl { match postcard::from_bytes(resp) { Ok(cmd_err) => Some(cmd_err), Err(deser_err) => { - Some(OpteError::DeserCmdErr(format!("{}", deser_err))) + Some(OpteError::DeserCmdErr(deser_err.to_string())) } } } else { diff --git a/crates/opte-api/src/ip.rs b/crates/opte-api/src/ip.rs index 59afef93..90a35710 100644 --- a/crates/opte-api/src/ip.rs +++ b/crates/opte-api/src/ip.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 use super::mac::MacAddr; use crate::DomainName; @@ -348,8 +348,8 @@ impl Default for IpAddr { impl fmt::Display for IpAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - IpAddr::Ip4(ip4) => write!(f, "{}", ip4), - IpAddr::Ip6(ip6) => write!(f, "{}", ip6), + IpAddr::Ip4(ip4) => write!(f, "{ip4}"), + IpAddr::Ip6(ip6) => write!(f, "{ip6}"), } } } @@ -402,7 +402,7 @@ impl Ipv4Addr { /// Return the address after applying the network mask. pub fn mask(mut self, mask: u8) -> Result { if mask > 32 { - return Err(format!("bad mask: {}", mask)); + return Err(format!("bad mask: {mask}")); } if mask == 0 { @@ -482,11 +482,11 @@ impl FromStr for Ipv4Addr { fn from_str(val: &str) -> result::Result { let octets: Vec = val .split('.') - .map(|s| s.parse().map_err(|e| format!("{}", e))) + .map(|s| s.parse().map_err(|e| format!("{e}"))) .collect::, _>>()?; if octets.len() != 4 { - return Err(format!("malformed ip: {}", val)); + return Err(format!("malformed ip: {val}")); } // At the time of writing there is no TryFrom impl for Vec to @@ -510,7 +510,7 @@ impl Display for Ipv4Addr { // present it in a human-friendly manner. impl Debug for Ipv4Addr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Ipv4Addr {{ inner: {} }}", self) + write!(f, "Ipv4Addr {{ inner: {self} }}") } } @@ -648,7 +648,7 @@ impl Ipv6Addr { /// Return the address after applying the network mask. pub fn mask(mut self, mask: u8) -> Result { if mask > 128 { - return Err(format!("bad mask: {}", mask)); + return Err(format!("bad mask: {mask}")); } if mask == 128 { @@ -708,7 +708,7 @@ impl Ipv6Addr { impl fmt::Display for Ipv6Addr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let sip6 = smoltcp::wire::Ipv6Address(self.bytes()); - write!(f, "{}", sip6) + write!(f, "{sip6}") } } @@ -853,8 +853,8 @@ impl IpCidr { impl fmt::Display for IpCidr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::Ip4(ip4) => write!(f, "{}", ip4), - Self::Ip6(ip6) => write!(f, "{}", ip6), + Self::Ip4(ip4) => write!(f, "{ip4}"), + Self::Ip6(ip6) => write!(f, "{ip6}"), } } } @@ -914,7 +914,7 @@ impl Ipv4PrefixLen { pub fn new(prefix_len: u8) -> Result { if prefix_len > 32 { - return Err(format!("bad IPv4 prefix length: {}", prefix_len)); + return Err(format!("bad IPv4 prefix length: {prefix_len}")); } Ok(Self(prefix_len)) @@ -967,13 +967,13 @@ impl FromStr for Ipv4Cidr { let ip = match ip_s.parse() { Ok(v) => v, - Err(e) => return Err(format!("bad IP: {}", e)), + Err(e) => return Err(format!("bad IP: {e}")), }; let raw = match prefix_s.parse::() { Ok(v) => v, Err(e) => { - return Err(format!("bad prefix length: {}", e)); + return Err(format!("bad prefix length: {e}")); } }; @@ -1076,7 +1076,7 @@ impl core::cmp::PartialOrd for Ipv6Cidr { impl fmt::Display for Ipv6Cidr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let (ip, prefix_len) = self.parts(); - write!(f, "{}/{}", ip, prefix_len.val()) + write!(f, "{ip}/{}", prefix_len.val()) } } @@ -1093,14 +1093,14 @@ impl FromStr for Ipv6Cidr { let ip = match ip_s.parse::() { Ok(v) => v.into(), Err(_) => { - return Err(format!("Bad IP address component: '{}'", ip_s)); + return Err(format!("Bad IP address component: '{ip_s}'")); } }; let prefix_len = match prefix_s.parse::() { Ok(v) => v, Err(e) => { - return Err(format!("bad prefix length: {}", e)); + return Err(format!("bad prefix length: {e}")); } }; @@ -1128,7 +1128,7 @@ impl Ipv6PrefixLen { pub fn new(prefix_len: u8) -> result::Result { if prefix_len > 128 { - return Err(format!("bad IPv6 prefix length: {}", prefix_len)); + return Err(format!("bad IPv6 prefix length: {prefix_len}")); } Ok(Self(prefix_len)) diff --git a/crates/opte-api/src/lib.rs b/crates/opte-api/src/lib.rs index 7de24626..501fd151 100644 --- a/crates/opte-api/src/lib.rs +++ b/crates/opte-api/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 #![no_std] #![deny(unreachable_patterns)] @@ -51,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 = 36; +pub const API_VERSION: u64 = 37; /// Major version of the OPTE package. pub const MAJOR_VERSION: u64 = 0; @@ -69,7 +69,7 @@ impl core::str::FromStr for Direction { match s.to_ascii_lowercase().as_str() { "in" => Ok(Direction::In), "out" => Ok(Direction::Out), - _ => Err(format!("invalid direction: {}", s)), + _ => Err(format!("invalid direction: {s}")), } } } @@ -81,7 +81,7 @@ impl Display for Direction { Direction::Out => "OUT", }; - write!(f, "{}", dirstr) + write!(f, "{dirstr}") } } diff --git a/crates/opte-api/src/mac.rs b/crates/opte-api/src/mac.rs index 48d70c93..1818a997 100644 --- a/crates/opte-api/src/mac.rs +++ b/crates/opte-api/src/mac.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 use alloc::str::FromStr; use alloc::string::String; @@ -95,8 +95,7 @@ impl FromStr for MacAddr { let octets: Vec = s .split(':') .map(|s| { - u8::from_str_radix(s, 16) - .map_err(|_| format!("bad octet: {}", s)) + u8::from_str_radix(s, 16).map_err(|_| format!("bad octet: {s}")) }) .collect::, _>>()?; @@ -133,6 +132,6 @@ impl Display for MacAddr { // present it in a human-friendly manner. impl Debug for MacAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "MacAddr {{ inner: {} }}", self) + write!(f, "MacAddr {{ inner: {self} }}") } } diff --git a/crates/opte-api/src/tcp.rs b/crates/opte-api/src/tcp.rs index 08217005..591615e3 100644 --- a/crates/opte-api/src/tcp.rs +++ b/crates/opte-api/src/tcp.rs @@ -40,6 +40,6 @@ impl Display for TcpState { TcpState::FinWait2 => "FIN_WAIT_2", TcpState::TimeWait => "TIME_WAIT", }; - write!(f, "{}", s) + write!(f, "{s}") } } diff --git a/lib/opte-ioctl/src/lib.rs b/lib/opte-ioctl/src/lib.rs index e7df256a..c99bc3e8 100644 --- a/lib/opte-ioctl/src/lib.rs +++ b/lib/opte-ioctl/src/lib.rs @@ -478,7 +478,7 @@ where libc::EPERM => "permission denied".to_string(), errno => { - format!("unexpected errno: {}", errno) + format!("unexpected errno: {errno}") } }; diff --git a/lib/opte-test-utils/src/pcap.rs b/lib/opte-test-utils/src/pcap.rs index b6180173..cfe00103 100644 --- a/lib/opte-test-utils/src/pcap.rs +++ b/lib/opte-test-utils/src/pcap.rs @@ -19,7 +19,7 @@ use std::io::Write; fn get_header(offset: &[u8]) -> (&[u8], PcapHeader) { match pcap::parse_pcap_header(offset) { Ok((new_offset, header)) => (new_offset, header), - Err(e) => panic!("failed to get header: {:?}", e), + Err(e) => panic!("failed to get header: {e:?}"), } } @@ -32,7 +32,7 @@ fn next_block(offset: &[u8]) -> (&[u8], LegacyPcapBlock) { (new_offset, block) } - Err(e) => panic!("failed to get next block: {:?}", e), + Err(e) => panic!("failed to get next block: {e:?}"), } } diff --git a/lib/opte/Cargo.toml b/lib/opte/Cargo.toml index 90f0ab5f..35ad5540 100644 --- a/lib/opte/Cargo.toml +++ b/lib/opte/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true [features] default = ["api", "std"] -api = [] +api = ["dep:zerocopy"] engine = [ "api", "dep:cfg-if", diff --git a/lib/opte/src/api.rs b/lib/opte/src/api.rs index 02fe5d99..747ff3f6 100644 --- a/lib/opte/src/api.rs +++ b/lib/opte/src/api.rs @@ -12,8 +12,15 @@ use core::fmt::{self}; use core::hash::Hash; #[cfg(feature = "engine")] use crc32fast::Hasher; +use ingot::icmp::IcmpV4Type; +use ingot::icmp::IcmpV6Type; +use ingot::ip::IpProtocol; use serde::Deserialize; use serde::Serialize; +use zerocopy::FromBytes; +use zerocopy::Immutable; +use zerocopy::IntoBytes; +use zerocopy::KnownLayout; const AF_INET: i32 = 2; const AF_INET6: i32 = 26; @@ -21,8 +28,7 @@ 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, + proto_info: [0u16; 2], }; /// The flow identifier. @@ -56,25 +62,110 @@ pub struct InnerFlowId { // 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 proto_info: [u16; 2], +} + +impl Default for InnerFlowId { + fn default() -> Self { + FLOW_ID_DEFAULT + } +} + +pub enum L4Info<'a> { + Ports(&'a PortInfo), + Icmpv4(&'a Icmpv4Info), + Icmpv6(&'a Icmpv6Info), +} + +pub enum L4InfoMut<'a> { + Ports(&'a mut PortInfo), + Icmpv4(&'a mut Icmpv4Info), + Icmpv6(&'a mut Icmpv6Info), +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + FromBytes, + Hash, + Immutable, + IntoBytes, + KnownLayout, + Ord, + PartialEq, + PartialOrd, +)] +#[repr(C)] +pub struct PortInfo { 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 From for [u16; 2] { + fn from(val: PortInfo) -> [u16; 2] { + zerocopy::transmute!(val) } } -impl Default for InnerFlowId { - fn default() -> Self { - FLOW_ID_DEFAULT +#[derive( + Clone, + Copy, + Debug, + Eq, + FromBytes, + Hash, + Immutable, + IntoBytes, + KnownLayout, + Ord, + PartialEq, + PartialOrd, +)] +#[repr(C)] +pub struct IcmpInfo { + // TODO: it seems *very* hard to convince the zerocopy derives + // that this can safely be anrbitrary `Type`, when we know that is + // either IcmpV4Type or IcmpV6Type without making this `packed`. + pub ty: u8, + pub code: u8, + pub id: u16, +} + +pub type Icmpv4Info = IcmpInfo; //; +pub type Icmpv6Info = IcmpInfo; //; + +// impl Icmpv4Info { +// pub fn echo_id(&self) -> Option { +// match self.ty { +// IcmpV4Type::ECHO_REQUEST | IcmpV4Type::ECHO_REPLY => Some(self.id), +// _ => None, +// } +// } +// } + +impl From for [u16; 2] { + fn from(val: Icmpv4Info) -> [u16; 2] { + zerocopy::transmute!(val) } } +// impl Icmpv6Info { +// pub fn echo_id(&self) -> Option { +// match self.ty { +// IcmpV6Type::ECHO_REQUEST | IcmpV6Type::ECHO_REPLY => Some(self.id), +// _ => None, +// } +// } +// } + +// impl Into<[u16; 2]> for Icmpv6Info { +// fn into(self) -> [u16; 2] { +// zerocopy::transmute!(self) +// } +// } + /// Tagged union of a source-dest IP address pair, used to avoid /// duplicating the discriminator. #[derive( @@ -108,12 +199,34 @@ 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, - } + let proto_info = match self.l4_info() { + Some(L4Info::Ports(p)) => { + PortInfo { src_port: p.dst_port, dst_port: p.src_port }.into() + } + Some(L4Info::Icmpv4(v4)) if v4.code == 0 => Icmpv4Info { + ty: match IcmpV4Type(v4.ty) { + IcmpV4Type::ECHO_REQUEST => IcmpV4Type::ECHO_REPLY, + IcmpV4Type::ECHO_REPLY => IcmpV4Type::ECHO_REQUEST, + a => a, + } + .0, + ..*v4 + } + .into(), + Some(L4Info::Icmpv6(v6)) if v6.code == 0 => Icmpv6Info { + ty: match IcmpV6Type(v6.ty) { + IcmpV6Type::ECHO_REQUEST => IcmpV6Type::ECHO_REPLY, + IcmpV6Type::ECHO_REPLY => IcmpV6Type::ECHO_REQUEST, + a => a, + } + .0, + ..*v6 + } + .into(), + _ => self.proto_info, + }; + + Self { proto: self.proto, addrs: self.addrs.mirror(), proto_info } } pub fn src_ip(&self) -> IpAddr { @@ -133,19 +246,69 @@ impl InnerFlowId { pub fn protocol(&self) -> Protocol { Protocol::from(self.proto) } + + pub fn l4_info(&self) -> Option> { + match IpProtocol(self.proto) { + IpProtocol::ICMP => { + Some(L4Info::Icmpv4(zerocopy::transmute_ref!(&self.proto_info))) + } + IpProtocol::ICMP_V6 => { + Some(L4Info::Icmpv6(zerocopy::transmute_ref!(&self.proto_info))) + } + IpProtocol::TCP | IpProtocol::UDP => { + Some(L4Info::Ports(zerocopy::transmute_ref!(&self.proto_info))) + } + _ => None, + } + } + + pub fn l4_info_mut(&mut self) -> Option> { + match IpProtocol(self.proto) { + IpProtocol::ICMP => Some(L4InfoMut::Icmpv4( + zerocopy::transmute_mut!(&mut self.proto_info), + )), + IpProtocol::ICMP_V6 => Some(L4InfoMut::Icmpv6( + zerocopy::transmute_mut!(&mut self.proto_info), + )), + IpProtocol::TCP | IpProtocol::UDP => Some(L4InfoMut::Ports( + zerocopy::transmute_mut!(&mut self.proto_info), + )), + _ => None, + } + } + + #[cfg(feature = "engine")] + pub fn crc32(&self) -> u32 { + let mut hasher = Hasher::new(); + self.hash(&mut hasher); + hasher.finalize() + } } 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, - ) + let proto = self.protocol(); + let sip = self.src_ip(); + let dip = self.dst_ip(); + + match self.l4_info() { + Some(L4Info::Ports(info)) => write!( + f, + "{proto}:{sip}:{}:{dip}:{}", + info.src_port, info.dst_port + ), + Some(L4Info::Icmpv4(info)) => write!( + f, + "{proto}/{}/{}:{sip}:{dip}:{}", + info.ty, info.code, info.id + ), + Some(L4Info::Icmpv6(info)) => write!( + f, + "{proto}/{}/{}:{sip}:{dip}:{}", + info.ty, info.code, info.id + ), + None => write!(f, "{proto}:{sip}:{dip}"), + } } } diff --git a/lib/opte/src/ddi/kstat.rs b/lib/opte/src/ddi/kstat.rs index f0388357..f6496ff9 100644 --- a/lib/opte/src/ddi/kstat.rs +++ b/lib/opte/src/ddi/kstat.rs @@ -292,7 +292,7 @@ impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::NameTooLong(name) => { - write!(f, "kstat name too long: {}", name) + write!(f, "kstat name too long: {name}") } Self::NulChar => write!(f, "kstat name contains NUL char"), diff --git a/lib/opte/src/engine/arp.rs b/lib/opte/src/engine/arp.rs index 478d11a3..9107e0c5 100644 --- a/lib/opte/src/engine/arp.rs +++ b/lib/opte/src/engine/arp.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 //! ARP headers and data. @@ -54,7 +54,7 @@ impl Display for ArpOp { ArpOp::REPLY => "Reply", _ => "Unknown", }; - write!(f, "{}", s) + write!(f, "{s}") } } diff --git a/lib/opte/src/engine/dhcp.rs b/lib/opte/src/engine/dhcp.rs index 38543fb6..3a80ad0c 100644 --- a/lib/opte/src/engine/dhcp.rs +++ b/lib/opte/src/engine/dhcp.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 //! DHCP headers, data, and actions. @@ -13,12 +13,12 @@ use super::predicate::DataPredicate; use super::predicate::EtherAddrMatch; use super::predicate::IpProtoMatch; use super::predicate::Ipv4AddrMatch; -use super::predicate::PortMatch; use super::predicate::Predicate; use super::rule::AllowOrDeny; use super::rule::GenPacketResult; use super::rule::HairpinAction; use crate::ddi::mblk::MsgBlk; +use crate::engine::predicate::Match; use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; @@ -167,9 +167,9 @@ impl Display for MessageType { Nak => "Nak".to_string(), Release => "Release".to_string(), Inform => "Inform".to_string(), - Unknown(val) => format!("Unknown: {}", val), + Unknown(val) => format!("Unknown: {val}"), }; - write!(f, "{}", s) + write!(f, "{s}") } } @@ -457,8 +457,8 @@ impl HairpinAction for DhcpAction { Ipv4Addr::LOCAL_BCAST, )]), Predicate::InnerIpProto(vec![IpProtoMatch::Exact(Protocol::UDP)]), - Predicate::InnerDstPort(vec![PortMatch::Exact(DHCP_SERVER_PORT)]), - Predicate::InnerSrcPort(vec![PortMatch::Exact(DHCP_CLIENT_PORT)]), + Predicate::InnerDstPort(vec![Match::Exact(DHCP_SERVER_PORT)]), + Predicate::InnerSrcPort(vec![Match::Exact(DHCP_CLIENT_PORT)]), ]; let data_preds = match self.reply_type { diff --git a/lib/opte/src/engine/dhcpv6/mod.rs b/lib/opte/src/engine/dhcpv6/mod.rs index a830de8e..98ffa9c7 100644 --- a/lib/opte/src/engine/dhcpv6/mod.rs +++ b/lib/opte/src/engine/dhcpv6/mod.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 //! Core implementation of DHCPv6 protocol. //! @@ -75,6 +75,7 @@ pub mod protocol; pub use protocol::MessageType; use alloc::borrow::Cow; +use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; use core::fmt::Display; @@ -161,10 +162,10 @@ impl Display for Dhcpv6Action { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let addr_list = self .addresses() - .map(|addr| format!("{}", addr)) + .map(|addr| addr.to_string()) .collect::>() .join(","); - write!(f, "DHCPv6 IA Addrs: [{}]", addr_list) + write!(f, "DHCPv6 IA Addrs: [{addr_list}]") } } diff --git a/lib/opte/src/engine/dhcpv6/protocol.rs b/lib/opte/src/engine/dhcpv6/protocol.rs index 0ce60981..3d0caa1f 100644 --- a/lib/opte/src/engine/dhcpv6/protocol.rs +++ b/lib/opte/src/engine/dhcpv6/protocol.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 //! Implementation of the main message types for DHCPv6. @@ -30,7 +30,7 @@ use crate::engine::predicate::DataPredicate; use crate::engine::predicate::EtherAddrMatch; use crate::engine::predicate::IpProtoMatch; use crate::engine::predicate::Ipv6AddrMatch; -use crate::engine::predicate::PortMatch; +use crate::engine::predicate::Match; use crate::engine::predicate::Predicate; use crate::engine::rule::AllowOrDeny; use crate::engine::rule::GenPacketResult; @@ -66,7 +66,7 @@ impl fmt::Display for MessageType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use MessageType::*; match self { - Other(x) => write!(f, "Other({})", x), + Other(x) => write!(f, "Other({x})"), other => write!( f, "{}", @@ -288,9 +288,9 @@ fn dhcpv6_server_predicates(client_mac: &MacAddr) -> Vec { // DHCPv6 runs over UDP Predicate::InnerIpProto(vec![IpProtoMatch::Exact(Protocol::UDP)]), // Request must be from the client port - Predicate::InnerSrcPort(vec![PortMatch::Exact(CLIENT_PORT)]), + Predicate::InnerSrcPort(vec![Match::Exact(CLIENT_PORT)]), // and destined to the server port - Predicate::InnerDstPort(vec![PortMatch::Exact(SERVER_PORT)]), + Predicate::InnerDstPort(vec![Match::Exact(SERVER_PORT)]), ] } diff --git a/lib/opte/src/engine/ether.rs b/lib/opte/src/engine/ether.rs index 8a27fe52..3bfa4aa6 100644 --- a/lib/opte/src/engine/ether.rs +++ b/lib/opte/src/engine/ether.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 //! Ethernet frames. @@ -101,7 +101,7 @@ impl Display for EtherType { /// [`EtherType`]. impl Debug for EtherType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self) + write!(f, "{self}") } } @@ -149,8 +149,7 @@ impl FromStr for EtherAddr { let octets: Vec = val .split(':') .map(|s| { - u8::from_str_radix(s, 16) - .map_err(|_| format!("bad octet: {}", s)) + u8::from_str_radix(s, 16).map_err(|_| format!("bad octet: {s}")) }) .collect::, _>>()?; @@ -193,7 +192,7 @@ impl Display for EtherAddr { /// EtherAddr. impl Debug for EtherAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self) + write!(f, "{self}") } } diff --git a/lib/opte/src/engine/flow_table.rs b/lib/opte/src/engine/flow_table.rs index 89dda27a..fa70cc0e 100644 --- a/lib/opte/src/engine/flow_table.rs +++ b/lib/opte/src/engine/flow_table.rs @@ -390,6 +390,7 @@ impl Dump for () { #[cfg(test)] mod test { use super::*; + use crate::api::PortInfo; use crate::engine::ip::v4::Protocol; use crate::engine::packet::AddrPair; use crate::engine::packet::FLOW_ID_DEFAULT; @@ -405,8 +406,7 @@ mod test { src: "192.168.2.10".parse().unwrap(), dst: "76.76.21.21".parse().unwrap(), }, - src_port: 37890, - dst_port: 443, + proto_info: PortInfo { src_port: 37890, dst_port: 443 }.into(), }; let mut ft = @@ -431,8 +431,7 @@ mod test { src: "192.168.2.10".parse().unwrap(), dst: "76.76.21.21".parse().unwrap(), }, - src_port: 37890, - dst_port: 443, + proto_info: PortInfo { src_port: 37890, dst_port: 443 }.into(), }; let mut ft = diff --git a/lib/opte/src/engine/headers.rs b/lib/opte/src/engine/headers.rs index 8e0fc568..00e4ef0f 100644 --- a/lib/opte/src/engine/headers.rs +++ b/lib/opte/src/engine/headers.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 //! Header metadata modifications for IP, ULP, and Encap. @@ -260,8 +260,7 @@ impl HeaderLen for EncapMeta { fn packet_length(&self) -> usize { match self { EncapMeta::Geneve(g) => { - Self::MINIMUM_LENGTH - + g.oxide_external_pkt.then_some(4).unwrap_or_default() + Self::MINIMUM_LENGTH + if g.oxide_external_pkt { 4 } else { 0 } } } } diff --git a/lib/opte/src/engine/icmp/v4.rs b/lib/opte/src/engine/icmp/v4.rs index c08780ba..4d466853 100644 --- a/lib/opte/src/engine/icmp/v4.rs +++ b/lib/opte/src/engine/icmp/v4.rs @@ -42,11 +42,12 @@ impl HairpinAction for IcmpEchoReply { self.echo_dst_ip, )]), Predicate::InnerIpProto(vec![IpProtoMatch::Exact(Protocol::ICMP)]), + Predicate::IcmpMsgType(vec![ + MessageType::from(wire::Icmpv4Message::EchoRequest).into(), + ]), ]; - let data_preds = vec![DataPredicate::IcmpMsgType( - MessageType::from(wire::Icmpv4Message::EchoRequest).into(), - )]; + let data_preds = vec![]; (hdr_preds, data_preds) } @@ -58,8 +59,7 @@ impl HairpinAction for IcmpEchoReply { // should be impossible, but we avoid panicking given the kernel // context. return Err(GenErr::Unexpected(format!( - "Expected ICMP packet metadata, but found: {:?}", - meta + "Expected ICMP packet metadata, but found: {meta:?}", ))); }; @@ -76,8 +76,7 @@ impl HairpinAction for IcmpEchoReply { // Echo Request. However, programming error could // cause this to happen -- let's not take any chances. return Err(GenErr::Unexpected(format!( - "expected an ICMPv4 Echo Request, got {} {}", - ty, code, + "expected an ICMPv4 Echo Request, got {ty} {code}", ))); } }; diff --git a/lib/opte/src/engine/icmp/v6.rs b/lib/opte/src/engine/icmp/v6.rs index ed13a25a..761f696b 100644 --- a/lib/opte/src/engine/icmp/v6.rs +++ b/lib/opte/src/engine/icmp/v6.rs @@ -101,11 +101,12 @@ impl HairpinAction for Icmpv6EchoReply { Predicate::InnerIpProto(vec![IpProtoMatch::Exact( Protocol::ICMPv6, )]), + Predicate::Icmpv6MsgType(vec![ + MessageType::from(Icmpv6Message::EchoRequest).into(), + ]), ]; - let data_preds = vec![DataPredicate::Icmpv6MsgType( - MessageType::from(Icmpv6Message::EchoRequest).into(), - )]; + let data_preds = vec![]; (hdr_preds, data_preds) } @@ -117,8 +118,7 @@ impl HairpinAction for Icmpv6EchoReply { // should be impossible, but we avoid panicking given the kernel // context. return Err(GenErr::Unexpected(format!( - "Expected ICMPv6 packet metadata, but found: {:?}", - meta + "Expected ICMPv6 packet metadata, but found: {meta:?}", ))); }; @@ -135,8 +135,7 @@ impl HairpinAction for Icmpv6EchoReply { // Echo Request. However, programming error could // cause this to happen -- let's not take any chances. return Err(GenErr::Unexpected(format!( - "expected an ICMPv6 Echo Request, got {} {}", - ty, code, + "expected an ICMPv6 Echo Request, got {ty} {code}", ))); } }; @@ -228,15 +227,14 @@ impl HairpinAction for RouterAdvertisement { Predicate::InnerIpProto(vec![IpProtoMatch::Exact( Protocol::ICMPv6, )]), - ]; - - let data_preds = vec![ // This must be a Router Solicitation message. - DataPredicate::Icmpv6MsgType( + Predicate::Icmpv6MsgType(vec![ MessageType::from(Icmpv6Message::RouterSolicit).into(), - ), + ]), ]; + let data_preds = vec![]; + (hdr_preds, data_preds) } @@ -250,8 +248,7 @@ impl HairpinAction for RouterAdvertisement { // should be impossible, but we avoid panicking given the kernel // context. return Err(GenErr::Unexpected(format!( - "Expected ICMPv6 packet metadata, but found: {:?}", - meta + "Expected ICMPv6 packet metadata, but found: {meta:?}", ))); }; @@ -260,8 +257,7 @@ impl HairpinAction for RouterAdvertisement { let Some(ip6) = meta.inner_ip6() else { // We got the ICMPv6 metadata above but no IPv6 somehow? return Err(GenErr::Unexpected(format!( - "Expected IPv6 packet metadata, but found: {:?}", - meta + "Expected IPv6 packet metadata, but found: {meta:?}", ))); }; let src_ip = IpAddress::Ipv6(Ipv6Address(ip6.source().bytes())); @@ -551,15 +547,14 @@ impl HairpinAction for NeighborAdvertisement { Predicate::InnerIpProto(vec![IpProtoMatch::Exact( Protocol::ICMPv6, )]), - ]; - - let data_preds = vec![ // This must be an actual Neighbor Solicitation message - DataPredicate::Icmpv6MsgType( + Predicate::Icmpv6MsgType(vec![ MessageType::from(Icmpv6Message::NeighborSolicit).into(), - ), + ]), ]; + let data_preds = vec![]; + (hdr_preds, data_preds) } @@ -570,8 +565,7 @@ impl HairpinAction for NeighborAdvertisement { // should be impossible, but we avoid panicking given the kernel // context. return Err(GenErr::Unexpected(format!( - "Expected ICMPv6 packet metadata, but found: {:?}", - meta + "Expected ICMPv6 packet metadata, but found: {meta:?}", ))); }; @@ -579,8 +573,7 @@ impl HairpinAction for NeighborAdvertisement { let metadata = meta.inner_ip6().ok_or_else(|| { // We got the ICMPv6 metadata above but no IPv6 somehow? GenErr::Unexpected(format!( - "Expected IPv6 packet metadata, but found: {:?}", - meta + "Expected IPv6 packet metadata, but found: {meta:?}", )) })?; diff --git a/lib/opte/src/engine/layer.rs b/lib/opte/src/engine/layer.rs index bc664e6b..e33c287a 100644 --- a/lib/opte/src/engine/layer.rs +++ b/lib/opte/src/engine/layer.rs @@ -138,7 +138,7 @@ impl Display for LayerResult { Self::Allow => write!(f, "Allow"), Self::HandlePkt => write!(f, "Handle Packet"), Self::Deny { name, reason } => { - write!(f, "Deny: layer: {}, reason: {}", name, reason) + write!(f, "Deny: layer: {name}, reason: {reason}") } Self::Hairpin(_) => write!(f, "Hairpin"), } @@ -320,13 +320,8 @@ impl LayerFlowTable { Self { count: 0, limit, - ft_in: FlowTable::new(port, &format!("{}_in", layer), limit, None), - ft_out: FlowTable::new( - port, - &format!("{}_out", layer), - limit, - None, - ), + ft_in: FlowTable::new(port, &format!("{layer}_in"), limit, None), + ft_out: FlowTable::new(port, &format!("{layer}_out"), limit, None), } } @@ -398,7 +393,7 @@ impl Display for ActionDescEntry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::NoOp => write!(f, "no-op"), - Self::Desc(desc) => write!(f, "{}", desc), + Self::Desc(desc) => write!(f, "{desc}"), } } } @@ -574,8 +569,8 @@ impl Layer { ) { cfg_if! { if #[cfg(all(not(feature = "std"), not(test)))] { - let dir_c = CString::new(format!("{}", dir)).unwrap(); - let msg_c = CString::new(format!("{:?}", err)).unwrap(); + let dir_c = CString::new(format!("{dir}")).unwrap(); + let msg_c = CString::new(format!("{err:?}")).unwrap(); __dtrace_probe_gen__desc__fail( self.port_c.as_ptr() as uintptr_t, @@ -588,7 +583,7 @@ impl Layer { let port_s = self.port_c.to_str().unwrap(); let name_s = self.name_c.to_str().unwrap(); let flow_s = flow.to_string(); - let msg_s = format!("{:?}", err); + let msg_s = format!("{err:?}"); crate::opte_provider::gen__desc__fail!( || (port_s, name_s, dir, flow_s, msg_s) @@ -607,8 +602,8 @@ impl Layer { ) { cfg_if! { if #[cfg(all(not(feature = "std"), not(test)))] { - let dir_c = CString::new(format!("{}", dir)).unwrap(); - let msg_c = CString::new(format!("{:?}", err)).unwrap(); + let dir_c = CString::new(format!("{dir}")).unwrap(); + let msg_c = CString::new(format!("{err:?}")).unwrap(); __dtrace_probe_gen__ht__fail( self.port_c.as_ptr() as uintptr_t, @@ -620,7 +615,7 @@ impl Layer { } else if #[cfg(feature = "usdt")] { let port_s = self.port_c.to_str().unwrap(); let flow_s = flow.to_string(); - let err_s = format!("{:?}", err); + let err_s = format!("{err:?}"); crate::opte_provider::gen__ht__fail!( || (port_s, &self.name, dir, flow_s, err_s) @@ -682,7 +677,7 @@ impl Layer { ), Ok(v) => (LabelBlock::from_nested(v), None), // TODO: Handle the error types in a zero-cost way. - Err(e) => (Ok(LabelBlock::new()), Some(format!("ERROR: {:?}\0", e))), + Err(e) => (Ok(LabelBlock::new()), Some(format!("ERROR: {e:?}\0"))), }; // Truncation is captured *in* the LabelBlock. @@ -718,8 +713,8 @@ impl Layer { // XXX This would probably be better as separate probes; // for now this does the trick. let res_s = match res { - Ok(v) => format!("{}", v), - Err(e) => format!("ERROR: {:?}", e), + Ok(v) => format!("{v}"), + Err(e) => format!("ERROR: {e:?}"), }; crate::opte_provider::layer__process__return!( || ((dir, port_s), &self.name, &flow_b_s, &flow_a_s, &res_s) @@ -748,7 +743,7 @@ impl Layer { // generated from the LayerStats structure. let stats = KStatNamed::new( "xde", - &format!("{}_{}", port, name), + &format!("{port}_{name}"), LayerStats::new(), ) .unwrap(); @@ -765,10 +760,10 @@ impl Layer { name_c, port_c, ft: LayerFlowTable::new(port, name, ft_limit), - ft_cstr: CString::new(format!("ft-{}", name)).unwrap(), + ft_cstr: CString::new(format!("ft-{name}")).unwrap(), rules_in: RuleTable::new(port, name, Direction::In), rules_out: RuleTable::new(port, name, Direction::Out), - rt_cstr: CString::new(format!("rt-{}", name)).unwrap(), + rt_cstr: CString::new(format!("rt-{name}")).unwrap(), stats, } } @@ -1394,8 +1389,7 @@ impl Layer { ectx.log.log( LogLevel::Note, &format!( - "failed to generate descriptor for stateful action: {} {:?}", - flow, err + "failed to generate descriptor for stateful action: {flow} {err:?}", ), ); self.gen_desc_fail_probe(dir, flow, err); @@ -1412,8 +1406,7 @@ impl Layer { ectx.log.log( LogLevel::Note, &format!( - "failed to generate HdrTransform for static action: {} {:?}", - flow, err + "failed to generate HdrTransform for static action: {flow} {err:?}", ), ); self.gen_ht_fail_probe(dir, flow, err); diff --git a/lib/opte/src/engine/packet.rs b/lib/opte/src/engine/packet.rs index 46c259cf..2c46780e 100644 --- a/lib/opte/src/engine/packet.rs +++ b/lib/opte/src/engine/packet.rs @@ -18,6 +18,7 @@ use super::headers::EncapPush; use super::headers::IpPush; use super::headers::SizeHoldingEncap; use super::headers::ValidEncapMeta; +use super::icmp::QueryEcho; use super::ip::L3; use super::ip::L3Repr; use super::ip::v4::Ipv4Packet; @@ -35,7 +36,10 @@ use super::rule::HdrTransform; use super::rule::HdrTransformError; pub use crate::api::AddrPair; pub use crate::api::FLOW_ID_DEFAULT; +use crate::api::Icmpv4Info; +use crate::api::Icmpv6Info; pub use crate::api::InnerFlowId; +use crate::api::PortInfo; use crate::d_error::DError; use crate::ddi::mblk::MsgBlk; use crate::ddi::mblk::MsgBlkIterMut; @@ -127,7 +131,7 @@ pub enum BodyTransformError { impl From for BodyTransformError { fn from(e: smoltcp::wire::Error) -> Self { - Self::ParseFailure(format!("{}", e)) + Self::ParseFailure(format!("{e}")) } } @@ -648,21 +652,31 @@ impl From<&PacketData> for InnerFlowId { None => (255, FLOW_ID_DEFAULT.addrs), }; - let (src_port, dst_port) = meta - .inner_ulp() - .map(|ulp| { - ( - ulp.true_src_port() - .or_else(|| ulp.pseudo_port()) - .unwrap_or(0), - ulp.true_dst_port() - .or_else(|| ulp.pseudo_port()) - .unwrap_or(0), - ) - }) - .unwrap_or((0, 0)); - - InnerFlowId { proto, addrs, src_port, dst_port } + let proto_info = match meta.inner_ulp() { + Some(Ulp::Tcp(t)) => { + PortInfo { src_port: t.source(), dst_port: t.destination() } + .into() + } + Some(Ulp::Udp(u)) => { + PortInfo { src_port: u.source(), dst_port: u.destination() } + .into() + } + Some(Ulp::IcmpV4(v4)) => Icmpv4Info { + ty: v4.ty().0, + code: v4.code(), + id: v4.echo_id().unwrap_or_default(), + } + .into(), + Some(Ulp::IcmpV6(v6)) => Icmpv6Info { + ty: v6.ty().0, + code: v6.code(), + id: v6.echo_id().unwrap_or_default(), + } + .into(), + _ => Default::default(), + }; + + InnerFlowId { proto, addrs, proto_info } } } diff --git a/lib/opte/src/engine/parse.rs b/lib/opte/src/engine/parse.rs index 692548bd..d0b0af5e 100644 --- a/lib/opte/src/engine/parse.rs +++ b/lib/opte/src/engine/parse.rs @@ -35,6 +35,9 @@ use super::packet::MismatchError; use super::packet::OpteMeta; use super::packet::ParseError; use super::rule::CompiledTransform; +use crate::api::Icmpv4Info; +use crate::api::Icmpv6Info; +use crate::api::PortInfo; use core::fmt; use illumos_sys_hdrs::mac::MacEtherOffloadFlags; use illumos_sys_hdrs::mac::mac_ether_offload_info_t; @@ -313,16 +316,29 @@ fn flow_id( None => (255, FLOW_ID_DEFAULT.addrs), }; - let (src_port, dst_port) = ulp - .map(|ulp| { - ( - ulp.true_src_port().or_else(|| ulp.pseudo_port()).unwrap_or(0), - ulp.true_dst_port().or_else(|| ulp.pseudo_port()).unwrap_or(0), - ) - }) - .unwrap_or((0, 0)); + let proto_info = match ulp { + Some(ValidUlp::Tcp(t)) => { + PortInfo { src_port: t.source(), dst_port: t.destination() }.into() + } + Some(ValidUlp::Udp(u)) => { + PortInfo { src_port: u.source(), dst_port: u.destination() }.into() + } + Some(ValidUlp::IcmpV4(v4)) => Icmpv4Info { + ty: v4.ty().0, + code: v4.code(), + id: v4.echo_id().unwrap_or_default(), + } + .into(), + Some(ValidUlp::IcmpV6(v6)) => Icmpv6Info { + ty: v6.ty().0, + code: v6.code(), + id: v6.echo_id().unwrap_or_default(), + } + .into(), + _ => Default::default(), + }; - InnerFlowId { proto, addrs, src_port, dst_port } + InnerFlowId { proto, addrs, proto_info } } #[derive(Parse)] @@ -678,64 +694,6 @@ fn csum_minus_hdr(ulp: &ValidUlp) -> Option { } } -impl Ulp { - #[inline] - pub fn true_src_port(&self) -> Option { - match self { - Ulp::Tcp(pkt) => Some(pkt.source()), - Ulp::Udp(pkt) => Some(pkt.source()), - _ => None, - } - } - - #[inline] - pub fn true_dst_port(&self) -> Option { - match self { - Ulp::Tcp(pkt) => Some(pkt.destination()), - Ulp::Udp(pkt) => Some(pkt.destination()), - _ => None, - } - } - - #[inline] - pub fn pseudo_port(&self) -> Option { - match self { - Ulp::IcmpV4(pkt) => pkt.echo_id(), - Ulp::IcmpV6(pkt) => pkt.echo_id(), - _ => None, - } - } -} - -impl ValidUlp { - #[inline] - pub fn true_src_port(&self) -> Option { - match self { - ValidUlp::Tcp(pkt) => Some(pkt.source()), - ValidUlp::Udp(pkt) => Some(pkt.source()), - _ => None, - } - } - - #[inline] - pub fn true_dst_port(&self) -> Option { - match self { - ValidUlp::Tcp(pkt) => Some(pkt.destination()), - ValidUlp::Udp(pkt) => Some(pkt.destination()), - _ => None, - } - } - - #[inline] - pub fn pseudo_port(&self) -> Option { - match self { - ValidUlp::IcmpV4(pkt) => pkt.echo_id(), - ValidUlp::IcmpV6(pkt) => pkt.echo_id(), - _ => None, - } - } -} - impl HasInnerCksum for Ulp { const HAS_CKSUM: bool = true; } diff --git a/lib/opte/src/engine/port/mod.rs b/lib/opte/src/engine/port/mod.rs index aaec912c..60fe7170 100644 --- a/lib/opte/src/engine/port/mod.rs +++ b/lib/opte/src/engine/port/mod.rs @@ -249,7 +249,7 @@ impl From for PortCreateError { impl Display for PortCreateError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::InitStats(e) => write!(f, "{}", e), + Self::InitStats(e) => write!(f, "{e}"), } } } @@ -305,7 +305,7 @@ impl PortBuilder { Err(OpteError::BadLayerPos { layer: new_layer.name().to_string(), - pos: format!("{:?}", pos), + pos: format!("{pos:?}"), }) } @@ -510,7 +510,7 @@ impl Display for PortState { Paused => "paused", Restored => "restored", }; - write!(f, "{}", s) + write!(f, "{s}") } } @@ -2068,7 +2068,7 @@ impl Port { ), Ok(v) => (LabelBlock::from_nested(v), None), // TODO: Handle the error types in a zero-cost way. - Err(e) => (Ok(LabelBlock::new()), Some(format!("ERROR: {:?}\0", e))), + Err(e) => (Ok(LabelBlock::new()), Some(format!("ERROR: {e:?}\0"))), }; // Truncation is captured *in* the LabelBlock. @@ -2110,8 +2110,8 @@ impl Port { let flow_b_s = flow_before.to_string(); let flow_a_s = flow_after.to_string(); let res_str = match res { - Ok(v) => format!("{:?}", v), - Err(e) => format!("ERROR: {:?}", e), + Ok(v) => format!("{v:?}"), + Err(e) => format!("ERROR: {e:?}"), }; let _ = path; @@ -2822,7 +2822,7 @@ impl Port { } } - panic!("layer not found: {}", name); + panic!("layer not found: {name}"); } } } @@ -2835,7 +2835,7 @@ impl Port { .iter() .find(|layer| layer.name() == layer_name) .map(|layer| layer.num_rules(dir)) - .unwrap_or_else(|| panic!("layer not found: {}", layer_name)) + .unwrap_or_else(|| panic!("layer not found: {layer_name}")) } } @@ -2979,7 +2979,7 @@ impl Display for TcpFlowEntryStateInner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.inbound_ufid { None => write!(f, "None {}", self.tcp_state), - Some(ufid) => write!(f, "{} {}", ufid, self.tcp_state), + Some(ufid) => write!(f, "{ufid} {}", self.tcp_state), } } } diff --git a/lib/opte/src/engine/predicate.rs b/lib/opte/src/engine/predicate.rs index e5844ac7..551f2179 100644 --- a/lib/opte/src/engine/predicate.rs +++ b/lib/opte/src/engine/predicate.rs @@ -89,7 +89,7 @@ impl Display for EtherTypeMatch { Exact(et) if *et == ETHER_TYPE_IPV4 => write!(f, "IPv4"), Exact(et) if *et == ETHER_TYPE_IPV6 => write!(f, "IPv6"), - Exact(et) => write!(f, "0x{:X}", et), + Exact(et) => write!(f, "0x{et:X}"), } } } @@ -112,7 +112,7 @@ impl Display for EtherAddrMatch { use EtherAddrMatch::*; match self { - Exact(addr) => write!(f, "{}", addr), + Exact(addr) => write!(f, "{addr}"), } } } @@ -140,8 +140,8 @@ impl Display for Ipv4AddrMatch { use Ipv4AddrMatch::*; match self { - Exact(ip) => write!(f, "{}", ip), - Prefix(cidr) => write!(f, "{}", cidr), + Exact(ip) => write!(f, "{ip}"), + Prefix(cidr) => write!(f, "{cidr}"), } } } @@ -169,8 +169,8 @@ impl Display for Ipv6AddrMatch { use Ipv6AddrMatch::*; match self { - Exact(ip) => write!(f, "{}", ip), - Prefix(cidr) => write!(f, "{}", cidr), + Exact(ip) => write!(f, "{ip}"), + Prefix(cidr) => write!(f, "{cidr}"), } } } @@ -193,7 +193,7 @@ impl Display for IpProtoMatch { use IpProtoMatch::*; match self { - Exact(proto) => write!(f, "{}", proto), + Exact(proto) => write!(f, "{proto}"), } } } @@ -206,29 +206,6 @@ impl MatchExact for u16 { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum PortMatch { - Exact(u16), -} - -impl PortMatch { - fn matches(&self, flow_port: u16) -> bool { - match self { - Self::Exact(port) => flow_port.match_exact(port), - } - } -} - -impl Display for PortMatch { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use PortMatch::*; - - match self { - Exact(port) => write!(f, "{}", port), - } - } -} - #[derive(Clone, Debug, Eq, PartialEq)] pub enum Predicate { InnerEtherType(Vec), @@ -239,9 +216,15 @@ pub enum Predicate { InnerSrcIp6(Vec), InnerDstIp6(Vec), InnerIpProto(Vec), - InnerSrcPort(Vec), - InnerDstPort(Vec), + InnerSrcPort(Vec>), + InnerDstPort(Vec>), + IcmpMsgType(Vec>), + IcmpMsgCode(Vec>), + Icmpv6MsgType(Vec>), + Icmpv6MsgCode(Vec>), Not(Box), + Any(Vec), + All(Vec), Meta(String, String), } @@ -256,7 +239,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ether.ether_type={}", s) + write!(f, "inner.ether.ether_type={s}") } InnerEtherDst(list) => { @@ -265,7 +248,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ether.dst={}", s) + write!(f, "inner.ether.dst={s}") } InnerEtherSrc(list) => { @@ -274,7 +257,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ether.src={}", s) + write!(f, "inner.ether.src={s}") } InnerIpProto(list) => { @@ -283,7 +266,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ip.proto={}", s) + write!(f, "inner.ip.proto={s}") } InnerSrcIp4(list) => { @@ -292,7 +275,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ip.src={}", s) + write!(f, "inner.ip.src={s}") } InnerDstIp4(list) => { @@ -301,7 +284,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ip.dst={}", s) + write!(f, "inner.ip.dst={s}") } InnerSrcIp6(list) => { @@ -310,7 +293,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ip6.src={}", s) + write!(f, "inner.ip6.src={s}") } InnerDstIp6(list) => { @@ -319,7 +302,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ip6.dst={}", s) + write!(f, "inner.ip6.dst={s}") } InnerSrcPort(list) => { @@ -328,7 +311,7 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ulp.src={}", s) + write!(f, "inner.ulp.src{s}") } InnerDstPort(list) => { @@ -337,16 +320,69 @@ impl Display for Predicate { .map(|v| v.to_string()) .collect::>() .join(","); - write!(f, "inner.ulp.dst={}", s) + write!(f, "inner.ulp.dst{s}") + } + + IcmpMsgType(list) => { + let s = list + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "inner.icmp.type{s}") + } + + IcmpMsgCode(list) => { + let s = list + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "inner.icmp.code{s}") + } + + Icmpv6MsgType(list) => { + let s = list + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "inner.icmp.type{s}") + } + + Icmpv6MsgCode(list) => { + let s = list + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "inner.icmp.code{s}") } Meta(key, val) => { - write!(f, "meta: {}={}", key, val) + write!(f, "meta: {key}={val}") } Not(pred) => { - write!(f, "!")?; - Display::fmt(&pred, f) + write!(f, "!{pred}") + } + + Any(list) => { + let s = list + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "any({s})") + } + + All(list) => { + let s = list + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "all({s})") } } } @@ -369,6 +405,14 @@ impl Predicate { Self::Not(pred) => return !pred.is_match(meta, action_meta), + Self::Any(list) => { + return list.iter().any(|v| v.is_match(meta, action_meta)); + } + + Self::All(list) => { + return list.iter().all(|v| v.is_match(meta, action_meta)); + } + Self::InnerEtherType(list) => { for m in list { if m.matches(EtherType::from( @@ -484,7 +528,7 @@ impl Predicate { Some(port) => { for m in list { - if m.matches(port) { + if m.is_match(&port) { return true; } } @@ -499,13 +543,65 @@ impl Predicate { Some(port) => { for m in list { - if m.matches(port) { + if m.is_match(&port) { return true; } } } } } + + Self::IcmpMsgType(list) => { + let Some(icmp) = meta.inner_icmp() else { + // This isn't an ICMPv4 packet at all + return false; + }; + + for mt in list { + if mt.is_match(&IcmpMessageType::from(icmp.ty().0)) { + return true; + } + } + } + + Self::IcmpMsgCode(list) => { + let Some(icmp) = meta.inner_icmp() else { + // This isn't an ICMPv4 packet at all + return false; + }; + + for mt in list { + if mt.is_match(&icmp.code()) { + return true; + } + } + } + + Self::Icmpv6MsgType(list) => { + let Some(icmp6) = meta.inner_icmp6() else { + // This isn't an ICMPv6 packet at all + return false; + }; + + for mt in list { + if mt.is_match(&Icmpv6MessageType::from(icmp6.ty().0)) { + return true; + } + } + } + + Self::Icmpv6MsgCode(list) => { + let Some(icmp6) = meta.inner_icmp6() else { + // This isn't an ICMPv6 packet at all + return false; + }; + + for mt in list { + if mt.is_match(&icmp6.code()) { + return true; + } + } + } } false @@ -547,17 +643,20 @@ impl From for Match { } } -impl From> for Match { +impl From> for Match { fn from(value: RangeInclusive) -> Self { - Match::Range(value) + let (start, end) = value.into_inner(); + if start == end { + Match::Exact(start) + } else { + Match::Range(start..=end) + } } } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum DataPredicate { DhcpMsgType(Match), - IcmpMsgType(Match), - Icmpv6MsgType(Match), Dhcpv6MsgType(Match), Not(Box), } @@ -571,21 +670,12 @@ impl Display for DataPredicate { write!(f, "dhcp.msg_type{mt}") } - IcmpMsgType(mt) => { - write!(f, "icmp.msg_type{mt}") - } - - Icmpv6MsgType(mt) => { - write!(f, "icmpv6.msg_type{mt}") - } - Dhcpv6MsgType(mt) => { write!(f, "dhcpv6.msg_type{mt}") } Not(pred) => { - write!(f, "!")?; - Display::fmt(&pred, f) + write!(f, "!{pred}") } } } @@ -626,24 +716,6 @@ impl DataPredicate { mt.is_match(&DhcpMessageType::from(dhcp.message_type)) } - Self::IcmpMsgType(mt) => { - let Some(icmp) = meta.inner_icmp() else { - // This isn't an ICMPv4 packet at all - return false; - }; - - mt.is_match(&IcmpMessageType::from(icmp.ty().0)) - } - - Self::Icmpv6MsgType(mt) => { - let Some(icmp6) = meta.inner_icmp6() else { - // This isn't an ICMPv6 packet at all - return false; - }; - - mt.is_match(&Icmpv6MessageType::from(icmp6.ty().0)) - } - Self::Dhcpv6MsgType(mt) => { let body = meta.body(); if body.is_empty() { diff --git a/lib/opte/src/engine/rule.rs b/lib/opte/src/engine/rule.rs index 9ce1030f..6e82fc90 100644 --- a/lib/opte/src/engine/rule.rs +++ b/lib/opte/src/engine/rule.rs @@ -781,7 +781,7 @@ pub enum GenBtError { impl From for GenBtError { fn from(e: smoltcp::wire::Error) -> Self { - Self::ParseBody(format!("{}", e)) + Self::ParseBody(format!("{e}")) } } @@ -916,10 +916,10 @@ impl fmt::Display for Action { Self::StatefulAllow => write!(f, "Stateful Allow"), Self::Deny => write!(f, "Deny"), Self::HandlePacket => write!(f, "Handle Packet"), - Self::Meta(a) => write!(f, "Meta: {}", a), - Self::Static(a) => write!(f, "Static: {}", a), - Self::Stateful(a) => write!(f, "Stateful: {}", a), - Self::Hairpin(a) => write!(f, "Hairpin: {}", a), + Self::Meta(a) => write!(f, "Meta: {a}"), + Self::Static(a) => write!(f, "Static: {a}"), + Self::Stateful(a) => write!(f, "Stateful: {a}"), + Self::Hairpin(a) => write!(f, "Hairpin: {a}"), } } } diff --git a/lib/opte/src/engine/snat.rs b/lib/opte/src/engine/snat.rs index ce7dd224..e8e4f121 100644 --- a/lib/opte/src/engine/snat.rs +++ b/lib/opte/src/engine/snat.rs @@ -28,6 +28,9 @@ use super::rule::Resource; use super::rule::ResourceEntry; use super::rule::ResourceError; use super::rule::StatefulAction; +use crate::api::IcmpInfo; +use crate::api::L4Info; +use crate::api::PortInfo; use crate::ddi::sync::KMutex; use crate::engine::icmp::QueryEcho; use alloc::collections::btree_map::BTreeMap; @@ -178,7 +181,7 @@ impl FiniteResource for NatPool { } None => { - panic!("cannot release port to unknown mapping: {}", priv_ip); + panic!("cannot release port to unknown mapping: {priv_ip}"); } } } @@ -285,14 +288,14 @@ impl Display for SNat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Here and below: all ULP-specific pools have the same SNAT mappings. let (pub_ip, ports) = self.tcp_pool.mapping(self.priv_ip).unwrap(); - write!(f, "{}:{}-{}", pub_ip, ports.start(), ports.end()) + write!(f, "{pub_ip}:{}-{}", ports.start(), ports.end()) } } impl Display for SNat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let (pub_ip, ports) = self.tcp_pool.mapping(self.priv_ip).unwrap(); - write!(f, "[{}]:{}-{}", pub_ip, ports.start(), ports.end()) + write!(f, "[{pub_ip}]:{}-{}", ports.start(), ports.end()) } } @@ -306,8 +309,15 @@ where pkt: &Packet, _meta: &mut ActionMeta, ) -> GenDescResult { - let priv_port = flow_id.src_port; let proto = flow_id.protocol(); + let priv_port = match flow_id.l4_info() { + Some(L4Info::Ports(PortInfo { src_port, .. })) => Ok(*src_port), + Some(L4Info::Icmpv4(IcmpInfo { id, .. })) => Ok(*id), + Some(L4Info::Icmpv6(IcmpInfo { id, .. })) => Ok(*id), + _ => Err(GenDescError::Unexpected { + msg: format!("SNAT pool (unexpected ULP: {proto})"), + }), + }?; let is_icmp = proto == T::MESSAGE_PROTOCOL; let pool = match proto { Protocol::TCP => &self.tcp_pool, @@ -315,7 +325,7 @@ where _ if is_icmp => &self.icmp_pool, proto => { return Err(GenDescError::Unexpected { - msg: format!("SNAT pool (unexpected ULP: {})", proto), + msg: format!("SNAT pool (unexpected ULP: {proto})"), }); } }; @@ -337,7 +347,7 @@ where } Err(ResourceError::NoMatch(ip)) => Err(GenDescError::Unexpected { - msg: format!("SNAT pool (no match: {})", ip), + msg: format!("SNAT pool (no match: {ip})"), }), } } diff --git a/lib/opte/src/engine/tcp_state.rs b/lib/opte/src/engine/tcp_state.rs index c8d25b99..da8f914c 100644 --- a/lib/opte/src/engine/tcp_state.rs +++ b/lib/opte/src/engine/tcp_state.rs @@ -58,18 +58,16 @@ impl fmt::Display for TcpFlowStateError { write!( f, "Unexpected TCP segment, \ - direction: {}, flow: {}, state: {}, \ - flags: 0x{:x}", - direction, flow_id, state, flags, + direction: {direction}, flow: {flow_id}, \ + state: {state}, flags: 0x{flags:x}", ) } TcpFlowStateError::NewFlow { direction, flow_id, state, flags } => { write!( f, "Flow was reopened early by endpoint, \ - direction: {}, flow: {}, state: {}, \ - flags: 0x{:x}", - direction, flow_id, state, flags, + direction: {direction}, flow: {flow_id}, \ + state: {state}, flags: 0x{flags:x}", ) } } diff --git a/lib/opte/src/lib.rs b/lib/opte/src/lib.rs index 1adb074c..6de57220 100644 --- a/lib/opte/src/lib.rs +++ b/lib/opte/src/lib.rs @@ -219,7 +219,7 @@ impl Display for LogLevel { Self::Warn => "[WARN]", Self::Error => "[ERROR]", }; - write!(f, "{}", level_s) + write!(f, "{level_s}") } } @@ -230,7 +230,7 @@ pub struct PrintlnLog {} #[cfg(any(feature = "std", test))] impl LogProvider for PrintlnLog { fn log(&self, level: LogLevel, msg: &str) { - println!("{} {}", level, msg); + println!("{level} {msg}"); } } diff --git a/lib/opte/src/print.rs b/lib/opte/src/print.rs index b8c2d9af..63a53ecf 100644 --- a/lib/opte/src/print.rs +++ b/lib/opte/src/print.rs @@ -13,6 +13,7 @@ use crate::api::DumpLayerResp; use crate::api::DumpTcpFlowsResp; use crate::api::DumpUftResp; use crate::api::InnerFlowId; +use crate::api::L4Info; use crate::api::TcpFlowEntryDump; use opte_api::ActionDescEntryDump; use opte_api::ListLayersResp; @@ -87,7 +88,7 @@ pub fn print_list_layers_into( resp: &ListLayersResp, ) -> std::io::Result<()> { let mut t = TabWriter::new(writer); - writeln!(t, "NAME\tRULES IN\tRULES OUT\tDEF IN\tDEF OUT\tFLOWS",)?; + writeln!(t, "NAME\tRULES IN\tRULES OUT\tDEF IN\tDEF OUT\tFLOWS")?; for desc in &resp.layers { writeln!( @@ -191,7 +192,7 @@ pub fn print_rule( /// Print the header for the [`print_lft_flow()`] output. pub fn print_lft_flow_header(t: &mut impl Write) -> std::io::Result<()> { - writeln!(t, "PROTO\tSRC IP\tSPORT\tDST IP\tDPORT\tHITS\tACTION") + writeln!(t, "PROTO\tSRC IP\tSPORT/TY\tDST IP\tDPORT\tHITS\tACTION") } /// Print information about a layer flow. @@ -200,14 +201,30 @@ pub fn print_lft_flow( flow_id: &InnerFlowId, flow_entry: &ActionDescEntryDump, ) -> std::io::Result<()> { + let sport_o; + let dport_o; + let (sport, dport) = match flow_id.l4_info() { + Some(L4Info::Ports(p)) => { + sport_o = p.src_port.to_string(); + dport_o = p.dst_port.to_string(); + (sport_o.as_str(), dport_o.as_str()) + } + Some(L4Info::Icmpv4(p)) | Some(L4Info::Icmpv6(p)) => { + sport_o = format!("{:#02x}/{:#02x}", p.ty, p.code); + dport_o = p.id.to_string(); + (sport_o.as_str(), dport_o.as_str()) + } + None => ("N/A", "N/A"), + }; + writeln!( t, "{}\t{}\t{}\t{}\t{}\t{}\t{}", flow_id.protocol(), flow_id.src_ip(), - flow_id.src_port, + sport, flow_id.dst_ip(), - flow_id.dst_port, + dport, flow_entry.hits, flow_entry.summary, ) @@ -215,7 +232,7 @@ pub fn print_lft_flow( /// Print the header for the [`print_uft_flow()`] output. pub fn print_uft_flow_header(t: &mut impl Write) -> std::io::Result<()> { - writeln!(t, "PROTO\tSRC IP\tSPORT\tDST IP\tDPORT\tHITS\tXFORMS") + writeln!(t, "PROTO\tSRC IP\tSPORT/TY\tDST IP\tDPORT\tHITS\tXFORMS") } /// Print information about a UFT entry. @@ -224,14 +241,30 @@ pub fn print_uft_flow( flow_id: &InnerFlowId, flow_entry: &UftEntryDump, ) -> std::io::Result<()> { + let sport_o; + let dport_o; + let (sport, dport) = match flow_id.l4_info() { + Some(L4Info::Ports(p)) => { + sport_o = p.src_port.to_string(); + dport_o = p.dst_port.to_string(); + (sport_o.as_str(), dport_o.as_str()) + } + Some(L4Info::Icmpv4(p)) | Some(L4Info::Icmpv6(p)) => { + sport_o = format!("{:#02x}/{:#02x}", p.ty, p.code); + dport_o = p.id.to_string(); + (sport_o.as_str(), dport_o.as_str()) + } + None => ("N/A", "N/A"), + }; + writeln!( t, "{}\t{}\t{}\t{}\t{}\t{}\t{}", flow_id.protocol(), flow_id.src_ip(), - flow_id.src_port, + sport, flow_id.dst_ip(), - flow_id.dst_port, + dport, flow_entry.hits, flow_entry.summary, ) diff --git a/lib/oxide-vpc/src/api.rs b/lib/oxide-vpc/src/api.rs index 7396b80d..609607d8 100644 --- a/lib/oxide-vpc/src/api.rs +++ b/lib/oxide-vpc/src/api.rs @@ -11,6 +11,7 @@ use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; use core::fmt::Display; +use core::ops::RangeInclusive; use core::result; use core::str::FromStr; use illumos_sys_hdrs::datalink_id_t; @@ -385,7 +386,7 @@ impl FromStr for RouterTarget { Some(("ip4", ip4s)) => { let ip4 = ip4s .parse::() - .map_err(|e| format!("bad IP: {}", e))?; + .map_err(|e| format!("bad IP: {e}"))?; Ok(Self::Ip(IpAddr::Ip4(ip4.into()))) } @@ -406,7 +407,7 @@ impl FromStr for RouterTarget { uuid.parse::().map_err(|e| e.to_string())?, ))), - _ => Err(format!("malformed router target: {}", lower)), + _ => Err(format!("malformed router target: {lower}")), }, } } @@ -417,11 +418,11 @@ impl Display for RouterTarget { match self { Self::Drop => write!(f, "Drop"), Self::InternetGateway(None) => write!(f, "ig"), - Self::InternetGateway(Some(id)) => write!(f, "ig={}", id), - Self::Ip(IpAddr::Ip4(ip4)) => write!(f, "ip4={}", ip4), - Self::Ip(IpAddr::Ip6(ip6)) => write!(f, "ip6={}", ip6), - Self::VpcSubnet(IpCidr::Ip4(sub4)) => write!(f, "sub4={}", sub4), - Self::VpcSubnet(IpCidr::Ip6(sub6)) => write!(f, "sub6={}", sub6), + Self::InternetGateway(Some(id)) => write!(f, "ig={id}"), + Self::Ip(IpAddr::Ip4(ip4)) => write!(f, "ip4={ip4}"), + Self::Ip(IpAddr::Ip6(ip6)) => write!(f, "ip6={ip6}"), + Self::VpcSubnet(IpCidr::Ip4(sub4)) => write!(f, "sub4={sub4}"), + Self::VpcSubnet(IpCidr::Ip6(sub6)) => write!(f, "sub6={sub6}"), } } } @@ -667,7 +668,7 @@ impl FromStr for FirewallRule { for token in s.to_ascii_lowercase().split(' ') { match token.split_once('=') { None => { - return Err(format!("bad token: {}", token)); + return Err(format!("bad token: {token}")); } Some(("dir", val)) => { @@ -679,9 +680,10 @@ impl FromStr for FirewallRule { } Some(("priority", val)) => { - priority = Some(val.parse::().map_err(|e| { - format!("bad priroity: '{}' {}", val, e) - })?); + priority = + Some(val.parse::().map_err(|e| { + format!("bad priroity: '{val}' {e}") + })?); } // Parse the filters. @@ -699,7 +701,7 @@ impl FromStr for FirewallRule { } Some((_, _)) => { - return Err(format!("invalid key: {}", token)); + return Err(format!("invalid key: {token}")); } } } @@ -745,7 +747,7 @@ impl FromStr for FirewallAction { match s.to_ascii_lowercase().as_str() { "allow" => Ok(FirewallAction::Allow), "deny" => Ok(FirewallAction::Deny), - _ => Err(format!("invalid action: {} ('allow' or 'deny')", s)), + _ => Err(format!("invalid action: {s} ('allow' or 'deny')")), } } } @@ -789,7 +791,7 @@ impl Filters { } pub fn protocol(&self) -> ProtoFilter { - self.protocol + self.protocol.clone() } pub fn set_hosts>(&mut self, hosts: H) -> &mut Self { @@ -843,16 +845,15 @@ impl FromStr for Address { "any" => Ok(Address::Any), addrstr => match addrstr.split_once('=') { - None => Err(format!( - "malformed address specification: {}", - addrstr, - )), + None => { + Err(format!("malformed address specification: {addrstr}")) + } Some(("ip", val)) => Ok(Address::Ip(val.parse()?)), Some(("subnet", val)) => Ok(Address::Subnet(val.parse()?)), Some(("vni", val)) => { Ok(Address::Vni(val.parse().map_err(|e| format!("{e:?}"))?)) } - Some((key, _)) => Err(format!("invalid address type: {}", key)), + Some((key, _)) => Err(format!("invalid address type: {key}")), }, } } @@ -862,35 +863,62 @@ impl Display for Address { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Address::Any => write!(f, "ANY"), - Address::Ip(val) => write!(f, "{},", val), - Address::Subnet(val) => write!(f, "{},", val), - Address::Vni(val) => write!(f, "{}", val), + Address::Ip(val) => write!(f, "{val},"), + Address::Subnet(val) => write!(f, "{val},"), + Address::Vni(val) => write!(f, "{val}"), } } } -#[derive( - Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, -)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub enum ProtoFilter { #[default] Any, Arp, - Proto(Protocol), + Tcp, + Udp, + Icmp(Option), + Icmpv6(Option), + Other(Protocol), +} + +impl ProtoFilter { + pub fn l4_protocol(&self) -> Option { + match self { + ProtoFilter::Other(protocol) => Some(*protocol), + ProtoFilter::Tcp => Some(Protocol::TCP), + ProtoFilter::Udp => Some(Protocol::UDP), + ProtoFilter::Icmp(_) => Some(Protocol::ICMP), + ProtoFilter::Icmpv6(_) => Some(Protocol::ICMPv6), + _ => None, + } + } } impl FromStr for ProtoFilter { type Err = String; fn from_str(s: &str) -> Result { - match s.to_ascii_lowercase().as_str() { - "any" => Ok(ProtoFilter::Any), - "arp" => Ok(ProtoFilter::Arp), - "icmp" => Ok(ProtoFilter::Proto(Protocol::ICMP)), - "icmp6" => Ok(ProtoFilter::Proto(Protocol::ICMPv6)), - "tcp" => Ok(ProtoFilter::Proto(Protocol::TCP)), - "udp" => Ok(ProtoFilter::Proto(Protocol::UDP)), - _ => Err(format!("unknown protocol: {}", s)), + let (ty_str, content_str) = match s.split_once(':') { + None => (s, None), + Some((lhs, rhs)) => (lhs, Some(rhs)), + }; + + match (ty_str.to_ascii_lowercase().as_str(), content_str) { + ("any", None) => Ok(ProtoFilter::Any), + ("arp", None) => Ok(ProtoFilter::Arp), + ("icmp", None) => Ok(ProtoFilter::Icmp(None)), + ("icmp", Some(spec)) => Ok(ProtoFilter::Icmp(Some(spec.parse()?))), + ("icmp6", None) => Ok(ProtoFilter::Icmpv6(None)), + ("icmp6", Some(spec)) => { + Ok(ProtoFilter::Icmpv6(Some(spec.parse()?))) + } + ("tcp", None) => Ok(ProtoFilter::Tcp), + ("udp", None) => Ok(ProtoFilter::Udp), + (lhs, None) => Err(format!("unknown protocol: {lhs}")), + (lhs, Some(_)) => { + Err(format!("cannot specify filter for protocol: {lhs}")) + } } } } @@ -900,7 +928,23 @@ impl Display for ProtoFilter { match self { ProtoFilter::Any => write!(f, "ANY"), ProtoFilter::Arp => write!(f, "ARP"), - ProtoFilter::Proto(proto) => write!(f, "{},", proto), + ProtoFilter::Tcp => write!(f, "TCP"), + ProtoFilter::Udp => write!(f, "UDP"), + ProtoFilter::Icmp(filter) => { + write!(f, "ICMP")?; + if let Some(filter) = filter { + write!(f, ":{filter}")?; + } + Ok(()) + } + ProtoFilter::Icmpv6(filter) => { + write!(f, "ICMP6")?; + if let Some(filter) = filter { + write!(f, ":{filter}")?; + } + Ok(()) + } + ProtoFilter::Other(proto) => write!(f, "{proto}"), } } } @@ -927,12 +971,12 @@ impl FromStr for Ports { .collect::, _>>()?; if ports.is_empty() { - return Err(format!("malformed ports spec: {}", s)); + return Err(format!("malformed ports spec: {s}")); } for p in ports.iter() { if *p == DYNAMIC_PORT { - return Err(format!("invalid port: {}", p)); + return Err(format!("invalid port: {p}")); } } Ok(Ports::PortList(ports)) @@ -953,6 +997,56 @@ impl Display for Ports { } } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct IcmpFilter { + pub ty: u8, + pub codes: Option>, +} + +impl FromStr for IcmpFilter { + type Err = String; + + fn from_str(s: &str) -> Result { + let (ty_str, code_str) = match s.split_once(',') { + None => (s, None), + Some((lhs, rhs)) => (lhs, Some(rhs)), + }; + + let codes = code_str + .map(|s| { + let (lhs, rhs) = match s.split_once('-') { + Some((lhs, rhs)) => (lhs, Some(rhs)), + None => (s, None), + }; + let start = lhs.parse::().map_err(|e| e.to_string())?; + let end = rhs + .map(|v| v.parse::().map_err(|e| e.to_string())) + .unwrap_or(Ok(start))?; + + Ok::<_, String>(start..=end) + }) + .transpose()?; + + Ok(Self { ty: ty_str.parse::().map_err(|e| e.to_string())?, codes }) + } +} + +impl Display for IcmpFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.ty)?; + if let Some(ref code) = self.codes { + let start = code.start(); + let end = code.end(); + if start == end { + write!(f, ",{start}")?; + } else { + write!(f, ",{start}-{end}")?; + } + } + Ok(()) + } +} + /// Add an entry to the gateway allowing a port to send or receive /// traffic on a CIDR other than its private IP. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -1037,9 +1131,21 @@ pub mod tests { #[test] fn parse_good_proto_filter() { assert_eq!("aNy".parse::().unwrap(), ProtoFilter::Any); + assert_eq!("TCp".parse::().unwrap(), ProtoFilter::Tcp); + assert_eq!( + "icmp".parse::().unwrap(), + ProtoFilter::Icmp(None) + ); + assert_eq!( + "ICMP:3".parse::().unwrap(), + ProtoFilter::Icmp(Some(IcmpFilter { ty: 3, codes: None })) + ); assert_eq!( - "TCp".parse::().unwrap(), - ProtoFilter::Proto(Protocol::TCP) + "icmp6:22,11-15".parse::().unwrap(), + ProtoFilter::Icmpv6(Some(IcmpFilter { + ty: 22, + codes: Some(11..=15) + })) ); } diff --git a/lib/oxide-vpc/src/engine/firewall.rs b/lib/oxide-vpc/src/engine/firewall.rs index 9c27b8ce..ad0bbb05 100644 --- a/lib/oxide-vpc/src/engine/firewall.rs +++ b/lib/oxide-vpc/src/engine/firewall.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 2023 Oxide Computer Company +// Copyright 2025 Oxide Computer Company //! The Oxide VPC firewall. //! @@ -19,7 +19,9 @@ pub use crate::api::ProtoFilter; use crate::api::RemFwRuleReq; use crate::api::SetFwRulesReq; use crate::engine::overlay::ACTION_META_VNI; +use alloc::collections::BTreeSet; use alloc::string::ToString; +use alloc::vec::Vec; use core::num::NonZeroU32; use opte::api::Direction; use opte::api::IpAddr; @@ -36,7 +38,7 @@ use opte::engine::predicate::EtherTypeMatch; use opte::engine::predicate::IpProtoMatch; use opte::engine::predicate::Ipv4AddrMatch; use opte::engine::predicate::Ipv6AddrMatch; -use opte::engine::predicate::PortMatch; +use opte::engine::predicate::Match; use opte::engine::predicate::Predicate; use opte::engine::rule::Action; use opte::engine::rule::Finalized; @@ -100,18 +102,16 @@ pub struct Firewall {} pub fn from_fw_rule(fw_rule: FirewallRule, action: Action) -> Rule { let addr_pred = fw_rule.filters.hosts().into_predicate(fw_rule.direction); - let proto_pred = fw_rule.filters.protocol().into_predicate(); + let proto_preds = fw_rule.filters.protocol().into_predicates(); let port_pred = fw_rule.filters.ports().into_predicate(); - if addr_pred.is_none() && proto_pred.is_none() && port_pred.is_none() { + if addr_pred.is_none() && proto_preds.is_empty() && port_pred.is_none() { return Rule::match_any(fw_rule.priority, action); } let mut rule = Rule::new(fw_rule.priority, action); - if let Some(proto_pred) = proto_pred { - rule.add_predicate(proto_pred); - } + rule.add_predicates(proto_preds); if let Some(port_pred) = port_pred { rule.add_predicate(port_pred); @@ -147,18 +147,53 @@ impl Firewall { } impl ProtoFilter { - pub fn into_predicate(self) -> Option { + pub fn into_predicates(self) -> Vec { match self { - ProtoFilter::Any => None, + // Non-L4 cases. + ProtoFilter::Any => vec![], ProtoFilter::Arp => { - Some(Predicate::InnerEtherType(vec![EtherTypeMatch::Exact( + vec![Predicate::InnerEtherType(vec![EtherTypeMatch::Exact( ETHER_TYPE_ARP, - )])) + )])] + } + + // L4 cases. + ProtoFilter::Icmp(Some(filter)) => { + let mut out = vec![ + // Match::Exact(Protocol::ICMP) is validated in msg type/code. + Predicate::IcmpMsgType(vec![Match::Exact( + filter.ty.into(), + )]), + ]; + + if let Some(codes) = filter.codes { + out.push(Predicate::IcmpMsgCode(vec![codes.into()])); + } + + out + } + + ProtoFilter::Icmpv6(Some(filter)) => { + let mut out = vec![ + // Match::Exact(Protocol::ICMP) is validated in msg type/code. + Predicate::Icmpv6MsgType(vec![Match::Exact( + filter.ty.into(), + )]), + ]; + + if let Some(codes) = filter.codes { + out.push(Predicate::Icmpv6MsgCode(vec![codes.into()])); + } + + out } - ProtoFilter::Proto(p) => { - Some(Predicate::InnerIpProto(vec![IpProtoMatch::Exact(p)])) + other => { + let proto = other + .l4_protocol() + .expect("handled all non-l4 cases above"); + vec![Predicate::InnerIpProto(vec![IpProtoMatch::Exact(proto)])] } } } @@ -215,10 +250,79 @@ impl Ports { Ports::Any => None, Ports::PortList(ports) => { - let mlist = - ports.iter().map(|p| PortMatch::Exact(*p)).collect(); + // TODO: We may want to reshape the controlplane API to make + // this more direct. We'd probably still want to optimise what + // they tell us at this stage, though. + let ports: BTreeSet<_> = ports.iter().copied().collect(); + + let mut mlist = vec![]; + let mut curr_range = None; + for port in ports { + let range = curr_range.get_or_insert(port..=port); + let end = *range.end(); + if port <= end { + // Created new. + } else if port == end + 1 { + // Extend range + *range = *range.start()..=port; + } else { + // Finalise. + let mut temp = port..=port; + core::mem::swap(&mut temp, range); + mlist.push(temp.into()); + } + } + if let Some(range) = curr_range.take() { + mlist.push(range.into()); + } Some(Predicate::InnerDstPort(mlist)) } } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn port_predicate_simplification() { + // Verify that we can correctly convert a control-plane given + // Vec port list into something a little less `O(n)`. + let simple = Ports::PortList(vec![1000, 1001, 1002, 1003, 1004]); + assert_eq!( + simple.into_predicate(), + Some(Predicate::InnerDstPort(vec![(1000..=1004).into()])) + ); + + let gappy = Ports::PortList(vec![ + 80, 443, 1000, 1001, 1002, 1003, 1004, 60_000, + ]); + assert_eq!( + gappy.into_predicate(), + Some(Predicate::InnerDstPort(vec![ + 80.into(), + 443.into(), + (1000..=1004).into(), + 60_000.into() + ])) + ); + + let dupes_order = Ports::PortList(vec![1, 2, 2, 3, 6, 5, 5, 7]); + assert_eq!( + dupes_order.into_predicate(), + Some(Predicate::InnerDstPort(vec![(1..=3).into(), (5..=7).into()])) + ); + + let reversed = Ports::PortList(vec![ + 60_000, 1004, 1003, 1002, 1001, 1000, 443, 80, + ]); + assert_eq!(reversed.into_predicate(), gappy.into_predicate()); + + let large_list: Vec = (1024..=65535).collect(); + assert_eq!( + Ports::PortList(large_list).into_predicate(), + Some(Predicate::InnerDstPort(vec![(1024..=65535).into()])) + ); + } +} diff --git a/lib/oxide-vpc/src/engine/gateway/icmpv6.rs b/lib/oxide-vpc/src/engine/gateway/icmpv6.rs index 2821325b..0009acb1 100644 --- a/lib/oxide-vpc/src/engine/gateway/icmpv6.rs +++ b/lib/oxide-vpc/src/engine/gateway/icmpv6.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 2023 Oxide Computer Company +// Copyright 2025 Oxide Computer Company //! The ICMPv6 implementation of the Virtual Gateway. @@ -16,7 +16,7 @@ use opte::engine::icmp::v6::Icmpv6EchoReply; use opte::engine::icmp::v6::NeighborAdvertisement; use opte::engine::icmp::v6::RouterAdvertisement; use opte::engine::layer::Layer; -use opte::engine::predicate::DataPredicate; +use opte::engine::predicate::Predicate; use opte::engine::rule::Action; use opte::engine::rule::Rule; use smoltcp::wire::Icmpv6Message; @@ -91,18 +91,18 @@ pub fn setup( }); // Filter any uncaught in/out-bound NDP traffic. - let pred = DataPredicate::Icmpv6MsgType( + let pred = Predicate::Icmpv6MsgType(vec![ (Icmpv6Message::RouterSolicit.into()..=Icmpv6Message::Redirect.into()) .into(), - ); + ]); let in_pred = pred.clone(); let mut ndp_filter = Rule::new(next_out_prio, Action::Deny); - ndp_filter.add_data_predicate(pred); + ndp_filter.add_predicate(pred); layer.add_rule(Direction::Out, ndp_filter.finalize()); let mut ndp_filter = Rule::new(1, Action::Deny); - ndp_filter.add_data_predicate(in_pred); + ndp_filter.add_predicate(in_pred); layer.add_rule(Direction::In, ndp_filter.finalize()); Ok(()) diff --git a/lib/oxide-vpc/src/engine/overlay.rs b/lib/oxide-vpc/src/engine/overlay.rs index 8a7cc1ec..af1a6717 100644 --- a/lib/oxide-vpc/src/engine/overlay.rs +++ b/lib/oxide-vpc/src/engine/overlay.rs @@ -228,8 +228,7 @@ impl StaticAction for EncapAction { Err(e) => { return Err(GenHtError::Unexpected { msg: format!( - "failed to parse metadata entry '{}': {}", - target_str, e + "failed to parse metadata entry '{target_str}': {e}", ), }); } diff --git a/lib/oxide-vpc/src/engine/router.rs b/lib/oxide-vpc/src/engine/router.rs index c05f7882..32d574d1 100644 --- a/lib/oxide-vpc/src/engine/router.rs +++ b/lib/oxide-vpc/src/engine/router.rs @@ -121,7 +121,7 @@ impl ActionMetaValue for RouterTargetInternal { Ok(Self::InternetGateway(Some(ig))) } - _ => Err(format!("bad router target: {}", s)), + _ => Err(format!("bad router target: {s}")), }, } } @@ -129,13 +129,13 @@ impl ActionMetaValue for RouterTargetInternal { fn as_meta(&self) -> String { match self { Self::InternetGateway(ip) => match ip { - Some(ip) => format!("ig={}", ip), + Some(ip) => format!("ig={ip}"), None => String::from("ig"), }, - Self::Ip(IpAddr::Ip4(ip4)) => format!("ip4={}", ip4), - Self::Ip(IpAddr::Ip6(ip6)) => format!("ip6={}", ip6), - Self::VpcSubnet(IpCidr::Ip4(cidr4)) => format!("sub4={}", cidr4), - Self::VpcSubnet(IpCidr::Ip6(cidr6)) => format!("sub6={}", cidr6), + Self::Ip(IpAddr::Ip4(ip4)) => format!("ip4={ip4}"), + Self::Ip(IpAddr::Ip6(ip6)) => format!("ip6={ip6}"), + Self::VpcSubnet(IpCidr::Ip4(cidr4)) => format!("sub4={cidr4}"), + Self::VpcSubnet(IpCidr::Ip6(cidr6)) => format!("sub6={cidr6}"), } } } @@ -143,11 +143,11 @@ impl ActionMetaValue for RouterTargetInternal { impl fmt::Display for RouterTargetInternal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match self { - Self::InternetGateway(addr) => format!("IG({:?})", addr), - Self::Ip(addr) => format!("IP: {}", addr), - Self::VpcSubnet(sub) => format!("Subnet: {}", sub), + Self::InternetGateway(addr) => format!("IG({addr:?})"), + Self::Ip(addr) => format!("IP: {addr}"), + Self::VpcSubnet(sub) => format!("Subnet: {sub}"), }; - write!(f, "{}", s) + write!(f, "{s}") } } @@ -166,7 +166,7 @@ impl ActionMetaValue for RouterTargetClass { "ig" => Ok(Self::InternetGateway), "ip" => Ok(Self::Ip), "subnet" => Ok(Self::VpcSubnet), - _ => Err(format!("bad router target class: {}", s)), + _ => Err(format!("bad router target class: {s}")), } } diff --git a/lib/oxide-vpc/tests/integration_tests.rs b/lib/oxide-vpc/tests/integration_tests.rs index 08c173a5..37461c8e 100644 --- a/lib/oxide-vpc/tests/integration_tests.rs +++ b/lib/oxide-vpc/tests/integration_tests.rs @@ -603,7 +603,7 @@ fn guest_to_guest() { assert_eq!(meta.outer_v6.destination(), g2_cfg.phys_ip); // Geneve entropy. - assert_eq!(meta.outer_udp.source(), 12700); + assert_eq!(meta.outer_udp.source(), 9655); assert_eq!(meta.outer_encap.vni(), g1_cfg.vni); let eth = &meta.inner_eth; @@ -814,7 +814,8 @@ fn guest_to_internet_ipv4() { pkt1.len() - (&meta.outer_eth, &meta.outer_v6).packet_length(); assert_eq!(meta.outer_v6.payload_len() as usize, len_post_v6); - assert_eq!(meta.outer_udp.source(), 24329); + // Geneve entropy. + assert_eq!(meta.outer_udp.source(), 1428); assert_eq!(meta.outer_udp.length() as usize, len_post_v6); assert_eq!(meta.inner_eth.source(), g1_cfg.guest_mac); @@ -918,7 +919,8 @@ fn guest_to_internet_ipv6() { pkt1.len() - (&meta.outer_eth, &meta.outer_v6).packet_length(); assert_eq!(meta.outer_v6.payload_len() as usize, len_post_v6); - assert_eq!(meta.outer_udp.source(), 63246); + // Geneve entropy. + assert_eq!(meta.outer_udp.source(), 22425); assert_eq!(meta.outer_udp.length() as usize, len_post_v6); assert_eq!(meta.inner_eth.source(), g1_cfg.guest_mac); diff --git a/xde/src/ioctl.rs b/xde/src/ioctl.rs index b89381da..919959dd 100644 --- a/xde/src/ioctl.rs +++ b/xde/src/ioctl.rs @@ -25,7 +25,7 @@ unsafe extern "C" { } fn dtrace_probe_copy_out_resp(resp: &T) { - let cstr = CString::new(format!("{:?}", resp)).unwrap(); + let cstr = CString::new(format!("{resp:?}")).unwrap(); unsafe { __dtrace_probe_copy__out__resp(cstr.as_ptr() as ddi::uintptr_t); } @@ -107,7 +107,7 @@ impl<'a> IoctlEnvelope<'a> { match postcard::from_bytes(&bytes) { Ok(val) => Ok(val), Err(deser_error) => { - Err(OpteError::DeserCmdReq(format!("{}", deser_error))) + Err(OpteError::DeserCmdReq(format!("{deser_error}"))) } } } @@ -123,10 +123,10 @@ impl<'a> IoctlEnvelope<'a> { dtrace_probe_copy_out_resp(resp); let ser_result = match resp { Ok(v) => postcard::to_allocvec(v) - .map_err(|e| OpteError::SerCmdResp(format!("{}", e))), + .map_err(|e| OpteError::SerCmdResp(format!("{e}"))), Err(e) => postcard::to_allocvec(e) - .map_err(|e| OpteError::SerCmdErr(format!("{}", e))), + .map_err(|e| OpteError::SerCmdErr(format!("{e}"))), }; // We failed to serialize the response, communicate this with ENOMSG. diff --git a/xde/src/lib.rs b/xde/src/lib.rs index a7c0dd40..a224d6a2 100644 --- a/xde/src/lib.rs +++ b/xde/src/lib.rs @@ -84,7 +84,7 @@ unsafe impl GlobalAlloc for KmemAlloc { #[panic_handler] fn panic_hdlr(info: &PanicInfo) -> ! { - let msg = CString::new(format!("{}", info)).expect("cstring new"); + let msg = CString::new(format!("{info}")).expect("cstring new"); unsafe { cmn_err(CE_WARN, msg.as_ptr()); panic(msg.as_ptr()); diff --git a/xde/src/xde.rs b/xde/src/xde.rs index 96c6c3eb..439f0c45 100644 --- a/xde/src/xde.rs +++ b/xde/src/xde.rs @@ -510,7 +510,7 @@ fn dtrace_probe_hdlr_resp(resp: &Result) where T: CmdOk, { - let resp_arg = CString::new(format!("{:?}", resp)).unwrap(); + let resp_arg = CString::new(format!("{resp:?}")).unwrap(); __dtrace_probe_hdlr__resp(resp_arg.as_ptr() as uintptr_t); } @@ -875,7 +875,7 @@ fn delete_xde(req: &DeleteXdeReq) -> Result { err => { return Err(OpteError::System { errno: err, - msg: format!("failed to destroy DLS devnet: {}", err), + msg: format!("failed to destroy DLS devnet: {err}"), }); } } @@ -892,7 +892,7 @@ fn delete_xde(req: &DeleteXdeReq) -> Result { }; return Err(OpteError::System { errno: err, - msg: format!("failed to unregister mac: {}", err), + msg: format!("failed to unregister mac: {err}"), }); } }