Skip to content

Commit f0585a2

Browse files
authored
Add support for V4 multipath (#18)
1 parent 4ba9c18 commit f0585a2

File tree

5 files changed

+250
-25
lines changed

5 files changed

+250
-25
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

p4/sidecar-lite.p4

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,7 @@ control resolver(
506506

507507
}
508508

509-
control router_v4(
510-
in bit<32> dst,
509+
control router_v4_route(
511510
inout ingress_metadata_t ingress,
512511
inout egress_metadata_t egress,
513512
) {
@@ -522,12 +521,46 @@ control router_v4(
522521

523522
table rtr {
524523
key = {
525-
dst: lpm;
524+
ingress.path_idx: exact;
526525
}
527526
actions = {
528-
drop;
529527
forward;
530528
}
529+
// should never happen, but the compiler requires a default
530+
default_action = drop;
531+
}
532+
533+
apply {
534+
rtr.apply();
535+
}
536+
}
537+
538+
control router_v4_idx(
539+
in bit<32> dst_addr,
540+
in bit<32> src_addr,
541+
inout ingress_metadata_t ingress,
542+
inout egress_metadata_t egress,
543+
) {
544+
Checksum() csum;
545+
546+
action drop() {
547+
egress.drop = true;
548+
}
549+
550+
action index(bit<16> idx, bit<16> mask) {
551+
bit<16> hash = csum.run({dst_addr, src_addr});
552+
bit<16> offset = hash & mask;
553+
ingress.path_idx = idx + offset;
554+
}
555+
556+
table rtr {
557+
key = {
558+
dst_addr: lpm;
559+
}
560+
actions = {
561+
drop;
562+
index;
563+
}
531564
default_action = drop;
532565
}
533566

@@ -574,18 +607,20 @@ control router(
574607
inout ingress_metadata_t ingress,
575608
inout egress_metadata_t egress,
576609
) {
577-
router_v4() v4;
610+
router_v4_idx() v4_idx;
611+
router_v4_route() v4_route;
578612
router_v6() v6;
579613

580614
apply {
581615
bit<16> outport = 0;
582616

583617
if (hdr.ipv4.isValid()) {
584-
v4.apply(hdr.ipv4.dst, ingress, egress);
618+
v4_idx.apply(hdr.ipv4.dst, hdr.ipv4.src, ingress, egress);
585619
if (egress.drop == true) {
586620
return;
587621
}
588622
outport = egress.port;
623+
v4_route.apply(ingress, egress);
589624
}
590625
if (hdr.ipv6.isValid()) {
591626
v6.apply(hdr.ipv6.dst, ingress, egress);
@@ -595,7 +630,6 @@ control router(
595630
outport = egress.port;
596631
}
597632
}
598-
599633
}
600634

601635
control mac_rewrite(

p4/softnpu.p4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ struct ingress_metadata_t {
44
bit<16> port;
55
bool nat;
66
bit<16> nat_id;
7+
bit<16> path_idx;
78
}
89

910
struct egress_metadata_t {

scadm/src/main.rs

Lines changed: 158 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use p4rs::TableEntry;
44
use std::collections::BTreeMap;
5+
use std::collections::BTreeSet;
56
use std::fs::OpenOptions;
67
use std::io::Read;
78
use std::io::Write;
@@ -207,7 +208,8 @@ enum Commands {
207208
},
208209
}
209210

210-
const ROUTER_V4: &str = "ingress.router.v4.rtr";
211+
const ROUTER_V4_RT: &str = "ingress.router.v4_route.rtr";
212+
const ROUTER_V4_IDX: &str = "ingress.router.v4_idx.rtr";
211213
const ROUTER_V6: &str = "ingress.router.v6.rtr";
212214
const LOCAL_V6: &str = "ingress.local.local_v6";
213215
const LOCAL_V4: &str = "ingress.local.local_v4";
@@ -218,6 +220,78 @@ const RESOLVER_V6: &str = "ingress.resolver.resolver_v6";
218220
const MAC_REWRITE: &str = "ingress.mac.mac_rewrite";
219221
const PROXY_ARP: &str = "ingress.pxarp.proxy_arp";
220222

223+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
224+
struct Ipv4Cidr {
225+
address: Ipv4Addr,
226+
mask: u8,
227+
}
228+
229+
struct V4RouteData {
230+
data: BTreeMap<Ipv4Cidr, u16>,
231+
}
232+
233+
impl V4RouteData {
234+
// Given the full set of tables, extract the data from the route->idx table
235+
fn extract_route_idx(
236+
full: &BTreeMap<String, Vec<TableEntry>>,
237+
) -> BTreeMap<Ipv4Cidr, u16> {
238+
let mut table = BTreeMap::new();
239+
for e in full.get(ROUTER_V4_IDX).unwrap() {
240+
let subnet = match get_addr_subnet(&e.keyset_data) {
241+
Some((IpAddr::V4(address), mask)) => Ipv4Cidr { address, mask },
242+
_ => continue,
243+
};
244+
let idx = match get_v4_idx_mask(&e.parameter_data) {
245+
Some((idx, _mask)) => idx,
246+
None => continue,
247+
};
248+
table.insert(subnet, idx);
249+
}
250+
table
251+
}
252+
253+
// Fetch the v4 route->idx table
254+
pub async fn load(cli: &Cli) -> Self {
255+
let data = match cli.mode {
256+
Mode::Standalone => {
257+
let (tx, rx) = std::sync::mpsc::channel();
258+
let uds = bind_uds(cli);
259+
let j = tokio::spawn(async move {
260+
if let ManagementResponse::DumpResponse(ref tables) =
261+
recv_uds(uds).await
262+
{
263+
let table = V4RouteData::extract_route_idx(tables);
264+
tx.send(table).unwrap();
265+
}
266+
});
267+
send(ManagementRequest::DumpRequest, cli).await;
268+
j.await.unwrap();
269+
rx.recv().unwrap().clone()
270+
}
271+
Mode::Propolis => {
272+
V4RouteData::extract_route_idx(&dump_tables_propolis())
273+
}
274+
};
275+
Self { data }
276+
}
277+
278+
// Given a route destination, find the corresponding table index
279+
pub fn lookup(&self, address: Ipv4Addr, mask: u8) -> Option<u16> {
280+
let cidr = Ipv4Cidr { address, mask };
281+
self.data.get(&cidr).copied()
282+
}
283+
284+
// Find an open slot in the route table
285+
pub fn find_available(&self) -> u16 {
286+
let used: BTreeSet<u16> = self.data.values().cloned().collect();
287+
let mut i = 0;
288+
while used.contains(&i) {
289+
i += 1;
290+
}
291+
i
292+
}
293+
}
294+
221295
#[tokio::main]
222296
async fn main() {
223297
let cli = Cli::parse();
@@ -229,8 +303,11 @@ async fn main() {
229303
port,
230304
nexthop,
231305
} => {
232-
let mut keyset_data: Vec<u8> = destination.octets().into();
233-
keyset_data.push(mask);
306+
let table = V4RouteData::load(&cli).await;
307+
let idx = table.find_available();
308+
309+
// Add idx->route to the route table
310+
let keyset_data: Vec<u8> = idx.to_le_bytes().to_vec();
234311

235312
let mut parameter_data = port.to_le_bytes().to_vec();
236313
let mut nexthop_data: Vec<u8> = nexthop.octets().into();
@@ -239,28 +316,63 @@ async fn main() {
239316

240317
send(
241318
ManagementRequest::TableAdd(TableAdd {
242-
table: ROUTER_V4.into(),
319+
table: ROUTER_V4_RT.into(),
243320
action: "forward".into(),
244321
keyset_data,
245322
parameter_data,
246323
}),
247324
&cli,
248325
)
249326
.await;
250-
}
251-
Commands::RemoveRoute4 { destination, mask } => {
327+
328+
// Now add cidr->idx to the index table
252329
let mut keyset_data: Vec<u8> = destination.octets().into();
253330
keyset_data.push(mask);
254331

332+
let mut parameter_data = idx.to_le_bytes().to_vec();
333+
// Hardcode a mask of 0
334+
parameter_data.extend_from_slice(&0u16.to_le_bytes());
335+
255336
send(
256-
ManagementRequest::TableRemove(TableRemove {
257-
table: ROUTER_V4.into(),
337+
ManagementRequest::TableAdd(TableAdd {
338+
table: ROUTER_V4_IDX.into(),
339+
action: "index".into(),
258340
keyset_data,
341+
parameter_data,
259342
}),
260343
&cli,
261344
)
262345
.await;
263346
}
347+
Commands::RemoveRoute4 { destination, mask } => {
348+
let table = V4RouteData::load(&cli).await;
349+
if let Some(idx) = table.lookup(destination, mask) {
350+
// Remove the entry from the subnet->idx table
351+
let mut keyset_data: Vec<u8> = destination.octets().into();
352+
keyset_data.push(mask);
353+
354+
send(
355+
ManagementRequest::TableRemove(TableRemove {
356+
table: ROUTER_V4_IDX.into(),
357+
keyset_data,
358+
}),
359+
&cli,
360+
)
361+
.await;
362+
363+
// Remove the entry from the idx->route table table
364+
let mut keyset_data: Vec<u8> = idx.to_le_bytes().to_vec();
365+
keyset_data.push(mask);
366+
send(
367+
ManagementRequest::TableRemove(TableRemove {
368+
table: ROUTER_V4_RT.into(),
369+
keyset_data,
370+
}),
371+
&cli,
372+
)
373+
.await;
374+
}
375+
}
264376

265377
Commands::AddRoute6 {
266378
destination,
@@ -724,17 +836,29 @@ fn dump_tables(table: &BTreeMap<String, Vec<TableEntry>>) {
724836
};
725837
println!("{tgt} -> {gw}");
726838
}
727-
println!("router v4:");
728-
for e in table.get(ROUTER_V4).unwrap() {
839+
println!("router v4_idx:");
840+
for e in table.get(ROUTER_V4_IDX).unwrap() {
729841
let tgt = match get_addr_subnet(&e.keyset_data) {
730842
Some((a, m)) => format!("{a}/{m}"),
731843
None => "?".into(),
732844
};
845+
let idx = match get_v4_idx_mask(&e.parameter_data) {
846+
Some((idx, _mask)) => format!("{idx}"),
847+
None => "?".into(),
848+
};
849+
println!("{tgt} -> {idx}");
850+
}
851+
println!("router v4_routes:");
852+
for e in table.get(ROUTER_V4_RT).unwrap() {
853+
let idx = match get_u16(&e.keyset_data) {
854+
Some(idx) => format!("{idx}"),
855+
None => "?".into(),
856+
};
733857
let gw = match get_port_addr(&e.parameter_data, false) {
734858
Some((a, p)) => format!("{a} ({p})"),
735859
None => "?".into(),
736860
};
737-
println!("{tgt} -> {gw}");
861+
println!("{idx} -> {gw}");
738862
}
739863

740864
println!("resolver v4:");
@@ -875,6 +999,16 @@ fn get_addr(data: &[u8], rev: bool) -> Option<IpAddr> {
875999
}
8761000
}
8771001

1002+
fn get_u16(data: &[u8]) -> Option<u16> {
1003+
match data.len() {
1004+
2 => Some(u16::from_le_bytes([data[0], data[1]])),
1005+
_ => {
1006+
println!("expected u16, found: {data:x?}");
1007+
None
1008+
}
1009+
}
1010+
}
1011+
8781012
fn get_mac(data: &[u8]) -> Option<[u8; 6]> {
8791013
match data.len() {
8801014
6 => Some(data.try_into().unwrap()),
@@ -885,6 +1019,19 @@ fn get_mac(data: &[u8]) -> Option<[u8; 6]> {
8851019
}
8861020
}
8871021

1022+
fn get_v4_idx_mask(data: &[u8]) -> Option<(u16, u16)> {
1023+
match data.len() {
1024+
4 => Some((
1025+
u16::from_le_bytes([data[0], data[1]]),
1026+
u16::from_le_bytes([data[2], data[3]]),
1027+
)),
1028+
_ => {
1029+
println!("expected [index, mask], found: {data:x?}");
1030+
None
1031+
}
1032+
}
1033+
}
1034+
8881035
fn get_addr_subnet(data: &[u8]) -> Option<(IpAddr, u8)> {
8891036
match data.len() {
8901037
5 => Some((get_addr(&data[..4], true)?, data[4])),

0 commit comments

Comments
 (0)