|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// Copyright Open Network Fabric Authors |
| 3 | + |
| 4 | +//! Display implementations for context tables. |
| 5 | +
|
| 6 | +use super::tables::{PeeringEndsTables, PeeringTables, Verdict, VpcTable}; |
| 7 | +use crate::NatRequirement; |
| 8 | +use acl::reference::table::ReferenceTable; |
| 9 | +use common::cliprovider::{CliSource, Heading}; |
| 10 | +use indenter::indented; |
| 11 | +use match_action::{FieldPredicate, MatchKey}; |
| 12 | +use net::packet::VpcDiscriminant; |
| 13 | +use std::collections::{BTreeMap, HashMap}; |
| 14 | +use std::fmt::{self, Display, Formatter, Write}; |
| 15 | +use std::net::{Ipv4Addr, Ipv6Addr}; |
| 16 | + |
| 17 | +impl CliSource for PeeringTables {} |
| 18 | + |
| 19 | +impl Display for PeeringTables { |
| 20 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 21 | + Heading("Routing context").fmt(f)?; |
| 22 | + |
| 23 | + writeln!(f, "IPv4 peering tables:")?; |
| 24 | + fmt_vpc_tables(f, &self.v4)?; |
| 25 | + |
| 26 | + writeln!(f, "IPv6 peering tables:")?; |
| 27 | + fmt_vpc_tables(f, &self.v6)?; |
| 28 | + Ok(()) |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +// Collect into a `BTreeMap` to get a deterministic order when dumping the |
| 33 | +// entries, as the underlying `HashMap` has no stable iteration order |
| 34 | +fn fmt_vpc_tables<T: MatchKey, U: MatchKey>( |
| 35 | + f: &mut Formatter<'_>, |
| 36 | + tables: &HashMap<VpcDiscriminant, VpcTable<T, U>>, |
| 37 | +) -> fmt::Result { |
| 38 | + if tables.is_empty() { |
| 39 | + return writeln!(f, " (none)"); |
| 40 | + } |
| 41 | + for (src_vpcd, table) in tables.iter().collect::<BTreeMap<_, _>>() { |
| 42 | + writeln!(f, " source VPC {src_vpcd}:")?; |
| 43 | + write!(indented(f).with_str(" "), "{table}")?; |
| 44 | + writeln!(f)?; |
| 45 | + } |
| 46 | + Ok(()) |
| 47 | +} |
| 48 | + |
| 49 | +impl<T: MatchKey, U: MatchKey> Display for VpcTable<T, U> { |
| 50 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 51 | + writeln!(f, "local ends:")?; |
| 52 | + if self.local_ends.is_empty() { |
| 53 | + writeln!(f, " (none)")?; |
| 54 | + } else { |
| 55 | + for (remote_vpcd, ends) in self.local_ends.iter().collect::<BTreeMap<_, _>>() { |
| 56 | + writeln!(f, " for remote VPC {remote_vpcd}:")?; |
| 57 | + write!(indented(f).with_str(" "), "{ends}")?; |
| 58 | + } |
| 59 | + } |
| 60 | + writeln!(f, "remote ends:")?; |
| 61 | + write!(indented(f).with_str(" "), "{}", self.remote_ends)?; |
| 62 | + match &self.default_remote_vpcd { |
| 63 | + Some(vpcd) => writeln!(f, "default remote VPC: {vpcd}"), |
| 64 | + None => writeln!(f, "default remote VPC: -"), |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +impl<T: MatchKey, U: MatchKey, V: ActionDisplay> Display for PeeringEndsTables<T, U, V> { |
| 70 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 71 | + writeln!(f, "TCP:")?; |
| 72 | + write!(indented(f).with_str(" "), "{}", Rules(&self.tcp))?; |
| 73 | + writeln!(f, "UDP:")?; |
| 74 | + write!(indented(f).with_str(" "), "{}", Rules(&self.udp))?; |
| 75 | + writeln!(f, "other:")?; |
| 76 | + write!(indented(f).with_str(" "), "{}", Rules(&self.other))?; |
| 77 | + writeln!(f, "has default expose: {}", self.has_default) |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +// Display wrapper around a reference table. The table itself lives in the `acl` crate, so we cannot |
| 82 | +// implement `Display` for it directly. |
| 83 | +struct Rules<'a, K, A>(&'a ReferenceTable<K, A>); |
| 84 | + |
| 85 | +impl<K: MatchKey, A: ActionDisplay> Display for Rules<'_, K, A> { |
| 86 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 87 | + let table = self.0; |
| 88 | + if table.is_empty() { |
| 89 | + return writeln!(f, "(no rules)"); |
| 90 | + } |
| 91 | + for rule in table.rules() { |
| 92 | + let mut first = true; |
| 93 | + for pred in rule.fields() { |
| 94 | + if first { |
| 95 | + first = false; |
| 96 | + } else { |
| 97 | + write!(f, ", ")?; |
| 98 | + } |
| 99 | + fmt_predicate(f, pred)?; |
| 100 | + } |
| 101 | + write!(f, " -> ")?; |
| 102 | + rule.action().fmt_action(f)?; |
| 103 | + writeln!(f)?; |
| 104 | + } |
| 105 | + Ok(()) |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +// Render a single ACL field predicate |
| 110 | +fn fmt_predicate(f: &mut Formatter<'_>, pred: &FieldPredicate) -> fmt::Result { |
| 111 | + if let Some((bytes, len)) = pred.as_prefix() { |
| 112 | + match bytes.len() { |
| 113 | + 4 => write!( |
| 114 | + f, |
| 115 | + "{}/{len}", |
| 116 | + Ipv4Addr::from(<[u8; 4]>::try_from(bytes).unwrap()) |
| 117 | + ), |
| 118 | + 16 => write!( |
| 119 | + f, |
| 120 | + "{}/{len}", |
| 121 | + Ipv6Addr::from(<[u8; 16]>::try_from(bytes).unwrap()) |
| 122 | + ), |
| 123 | + _ => write!(f, "{bytes:02x?}/{len}"), |
| 124 | + } |
| 125 | + } else if let Some((min, max)) = pred.as_range() { |
| 126 | + let (Some(lo), Some(hi)) = (read_u16(min), read_u16(max)) else { |
| 127 | + return write!(f, "range {min:02x?}..={max:02x?}"); |
| 128 | + }; |
| 129 | + if lo == 0 && hi == u16::MAX { |
| 130 | + write!(f, "ports *") |
| 131 | + } else if lo == hi { |
| 132 | + write!(f, "port {lo}") |
| 133 | + } else { |
| 134 | + write!(f, "ports {lo}..={hi}") |
| 135 | + } |
| 136 | + } else if let Some(bytes) = pred.as_exact() { |
| 137 | + write!(f, "{bytes:02x?}") |
| 138 | + } else if let Some((value, mask)) = pred.as_mask() { |
| 139 | + write!(f, "{value:02x?}/{mask:02x?}") |
| 140 | + } else { |
| 141 | + Ok(()) |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +fn read_u16(bytes: &[u8]) -> Option<u16> { |
| 146 | + if let [hi, lo] = bytes { |
| 147 | + Some(u16::from_be_bytes([*hi, *lo])) |
| 148 | + } else { |
| 149 | + None |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +// The action carried by a reference table rule. Use a dedicated trait |
| 154 | +// (rather than `Display`) because one of the action types is the bare alias |
| 155 | +// `Option<NatRequirement>`, for which we cannot implement `Display`. |
| 156 | +trait ActionDisplay { |
| 157 | + fn fmt_action(&self, f: &mut Formatter<'_>) -> fmt::Result; |
| 158 | +} |
| 159 | + |
| 160 | +impl ActionDisplay for Verdict { |
| 161 | + fn fmt_action(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 162 | + write!(f, "{}, NAT: ", self.dst_vpcd)?; |
| 163 | + fmt_nat_mode(f, &self.nat_mode) |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +impl ActionDisplay for Option<NatRequirement> { |
| 168 | + fn fmt_action(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 169 | + write!(f, "NAT: ")?; |
| 170 | + fmt_nat_mode(f, self) |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +fn fmt_nat_mode(f: &mut Formatter<'_>, nat: &Option<NatRequirement>) -> fmt::Result { |
| 175 | + match nat { |
| 176 | + Some(nat) => write!(f, "{nat}"), |
| 177 | + None => write!(f, "-"), |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +impl Display for NatRequirement { |
| 182 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 183 | + let label = match self { |
| 184 | + NatRequirement::Static => "static", |
| 185 | + NatRequirement::Masquerade => "masquerade", |
| 186 | + NatRequirement::PortForwarding => "port-forwarding", |
| 187 | + }; |
| 188 | + f.write_str(label) |
| 189 | + } |
| 190 | +} |
0 commit comments