Skip to content

Commit caee0a3

Browse files
committed
feat(flofi): Implement Display for new flow-filter table
Signed-off-by: Quentin Monnet <qmo@qmon.net>
1 parent bac71f8 commit caee0a3

3 files changed

Lines changed: 197 additions & 0 deletions

File tree

acl/src/reference/table.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ impl<K: MatchKey, A> ReferenceTable<K, A> {
6363
pub fn is_empty(&self) -> bool {
6464
self.rules.is_empty()
6565
}
66+
67+
#[must_use]
68+
pub fn rules(&self) -> &[RefRule<A>] {
69+
&self.rules
70+
}
71+
6672
fn pack(key: &K) -> Option<[u8; MAX_KEY_BYTES]> {
6773
if K::KEY_SIZE > MAX_KEY_BYTES {
6874
return None;

flofi/src/context/display.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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+
}

flofi/src/context/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use config::external::overlay::ValidatedOverlay;
1111
use net::ip::NextHeader;
1212
use net::packet::VpcDiscriminant;
1313

14+
mod display;
1415
mod tables;
1516

1617
use tables::PeeringTables;

0 commit comments

Comments
 (0)