From 007700b3d3b108e4a22b3dadda87046092169466 Mon Sep 17 00:00:00 2001 From: Nicolas Kagami Date: Sat, 17 Oct 2026 11:18:49 -0300 Subject: [PATCH 1/2] key route index tables on (router_id, prefix) --- Cargo.lock | 28 ++++----- p4/sidecar-lite.p4 | 15 ++++- p4/softnpu.p4 | 1 + scadm/src/main.rs | 106 ++++++++++++++++++++++++--------- softnpu/src/test.rs | 142 ++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 242 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d36f51..eff4225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,7 +370,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -508,7 +508,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -763,7 +763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1421,7 +1421,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi 0.5.2", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1950,7 +1950,7 @@ dependencies = [ [[package]] name = "p4" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/p4?branch=main#077d5339d64ef3583147094142e75f05fc64fd1c" +source = "git+https://github.com/oxidecomputer/p4?branch=main#4645231880ccae40efd2bc583b643944ab0f0a20" dependencies = [ "colored", "regex", @@ -1959,7 +1959,7 @@ dependencies = [ [[package]] name = "p4-macro" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/p4?branch=main#077d5339d64ef3583147094142e75f05fc64fd1c" +source = "git+https://github.com/oxidecomputer/p4?branch=main#4645231880ccae40efd2bc583b643944ab0f0a20" dependencies = [ "p4", "p4-rust", @@ -1972,7 +1972,7 @@ dependencies = [ [[package]] name = "p4-rust" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/p4?branch=main#077d5339d64ef3583147094142e75f05fc64fd1c" +source = "git+https://github.com/oxidecomputer/p4?branch=main#4645231880ccae40efd2bc583b643944ab0f0a20" dependencies = [ "p4", "prettyplease", @@ -1985,7 +1985,7 @@ dependencies = [ [[package]] name = "p4rs" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/p4?branch=main#077d5339d64ef3583147094142e75f05fc64fd1c" +source = "git+https://github.com/oxidecomputer/p4?branch=main#4645231880ccae40efd2bc583b643944ab0f0a20" dependencies = [ "bitvec", "num", @@ -2578,7 +2578,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3084,7 +3084,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.117", @@ -3262,7 +3262,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3271,7 +3271,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "tests" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/p4?branch=main#077d5339d64ef3583147094142e75f05fc64fd1c" +source = "git+https://github.com/oxidecomputer/p4?branch=main#4645231880ccae40efd2bc583b643944ab0f0a20" dependencies = [ "anyhow", "bitvec", @@ -3967,7 +3967,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/p4/sidecar-lite.p4 b/p4/sidecar-lite.p4 index 20c6a78..427f732 100644 --- a/p4/sidecar-lite.p4 +++ b/p4/sidecar-lite.p4 @@ -308,7 +308,10 @@ control local( } action nonlocal() { is_local = false; } - action local() { is_local = true; } + action local(bit<8> rid) { + is_local = true; + ingress.router_id = rid; + } } control attached( @@ -429,7 +432,10 @@ control router_v4_idx( Checksum() csum; table rtr { - key = { dst_addr: lpm; } + key = { + ingress.router_id: exact; + dst_addr: lpm; + } actions = { drop; index; } default_action = drop; } @@ -487,7 +493,10 @@ control router_v6_idx( Checksum() csum; table rtr { - key = { dst_addr: lpm; } + key = { + ingress.router_id: exact; + dst_addr: lpm; + } actions = { drop; index; } default_action = drop; } diff --git a/p4/softnpu.p4 b/p4/softnpu.p4 index 6db8a0b..e4bb86d 100644 --- a/p4/softnpu.p4 +++ b/p4/softnpu.p4 @@ -9,6 +9,7 @@ struct ingress_metadata_t { bit<16> port; bit<16> nat_id; bit<16> path_idx; + bit<8> router_id; bool forward_needed; bool lldp; diff --git a/scadm/src/main.rs b/scadm/src/main.rs index 0d1e77b..ea15942 100644 --- a/scadm/src/main.rs +++ b/scadm/src/main.rs @@ -51,6 +51,10 @@ enum Commands { /// Next Hop nexthop: Ipv4Addr, + + /// Router id performing the lookup. + #[clap(long, default_value_t = 0)] + rid: u8, }, /// Remove a route from the routing table. @@ -60,6 +64,10 @@ enum Commands { /// Subnet mask for the destination. mask: u8, + + /// Router id performing the lookup. + #[clap(long, default_value_t = 0)] + rid: u8, }, /// Add an IPv6 route to the routing table. @@ -75,6 +83,10 @@ enum Commands { /// Next Hop nexthop: Ipv6Addr, + + /// Router id performing the lookup. + #[clap(long, default_value_t = 0)] + rid: u8, }, /// Remove a route from the routing table. @@ -84,12 +96,20 @@ enum Commands { /// Subnet mask for the destination. mask: u8, + + /// Router id performing the lookup. + #[clap(long, default_value_t = 0)] + rid: u8, }, /// Add an IPv6 address to the router. AddAddress6 { /// Address to add. address: Ipv6Addr, + + /// Router id claiming the address. + #[clap(long, default_value_t = 0)] + rid: u8, }, /// Remove an IPv6 address from the router. @@ -232,7 +252,7 @@ enum RouteType { } struct RouteData { - data: BTreeMap, + data: BTreeMap<(u8, Cidr), u16>, } impl RouteData { @@ -240,22 +260,22 @@ impl RouteData { fn extract_route_idx( typ: RouteType, full: &BTreeMap>, - ) -> BTreeMap { + ) -> BTreeMap<(u8, Cidr), u16> { let mut data = BTreeMap::new(); let table = match typ { RouteType::V4 => ROUTER_V4_IDX, RouteType::V6 => ROUTER_V6_IDX, }; for e in full.get(table).unwrap() { - let subnet = match get_addr_subnet(&e.keyset_data) { - Some((address, mask)) => Cidr { address, mask }, + let (rid, subnet) = match get_rid_addr_subnet(&e.keyset_data) { + Some((rid, address, mask)) => (rid, Cidr { address, mask }), _ => continue, }; let idx = match get_idx_slots(&e.parameter_data) { Some((idx, _slots)) => idx, None => continue, }; - data.insert(subnet, idx); + data.insert((rid, subnet), idx); } data } @@ -286,9 +306,9 @@ impl RouteData { } // Given a route destination, find the corresponding table index - pub fn lookup(&self, address: IpAddr, mask: u8) -> Option { + pub fn lookup(&self, rid: u8, address: IpAddr, mask: u8) -> Option { let cidr = Cidr { address, mask }; - self.data.get(&cidr).copied() + self.data.get(&(rid, cidr)).copied() } // Find an open slot in the route table @@ -312,6 +332,7 @@ async fn main() { mask, port, nexthop, + rid, } => { let table = RouteData::load(RouteType::V4, &cli).await; let idx = table.find_available(); @@ -335,8 +356,9 @@ async fn main() { ) .await; - // Now add cidr->idx to the index table - let mut keyset_data: Vec = destination.octets().into(); + // Now add (rid, cidr)->idx to the index table + let mut keyset_data: Vec = vec![rid]; + keyset_data.extend_from_slice(&destination.octets()); keyset_data.push(mask); // Hardcoded slot count. @@ -354,11 +376,17 @@ async fn main() { ) .await; } - Commands::RemoveRoute4 { destination, mask } => { + Commands::RemoveRoute4 { + destination, + mask, + rid, + } => { let table = RouteData::load(RouteType::V4, &cli).await; - if let Some(idx) = table.lookup(IpAddr::V4(destination), mask) { - // Remove the entry from the subnet->idx table - let mut keyset_data: Vec = destination.octets().into(); + if let Some(idx) = table.lookup(rid, IpAddr::V4(destination), mask) + { + // Remove the entry from the (rid, subnet)->idx table + let mut keyset_data: Vec = vec![rid]; + keyset_data.extend_from_slice(&destination.octets()); keyset_data.push(mask); send( @@ -389,6 +417,7 @@ async fn main() { mask, port, nexthop, + rid, } => { let table = RouteData::load(RouteType::V6, &cli).await; let idx = table.find_available(); @@ -412,8 +441,9 @@ async fn main() { ) .await; - // Now add cidr->idx to the index table - let mut keyset_data: Vec = destination.octets().into(); + // Now add (rid, cidr)->idx to the index table + let mut keyset_data: Vec = vec![rid]; + keyset_data.extend_from_slice(&destination.octets()); keyset_data.push(mask); // Hardcoded slot count. @@ -431,11 +461,17 @@ async fn main() { ) .await; } - Commands::RemoveRoute6 { destination, mask } => { + Commands::RemoveRoute6 { + destination, + mask, + rid, + } => { let table = RouteData::load(RouteType::V6, &cli).await; - if let Some(idx) = table.lookup(IpAddr::V6(destination), mask) { - // Remove the entry from the subnet->idx table - let mut keyset_data: Vec = destination.octets().into(); + if let Some(idx) = table.lookup(rid, IpAddr::V6(destination), mask) + { + // Remove the entry from the (rid, subnet)->idx table + let mut keyset_data: Vec = vec![rid]; + keyset_data.extend_from_slice(&destination.octets()); keyset_data.push(mask); send( @@ -576,7 +612,7 @@ async fn main() { table: LOCAL_V4.into(), action: "local".into(), keyset_data, - ..Default::default() + parameter_data: vec![0], }), &cli, ) @@ -595,7 +631,7 @@ async fn main() { .await; } - Commands::AddAddress6 { address } => { + Commands::AddAddress6 { address, rid } => { let mut keyset_data: Vec = address.octets().into(); keyset_data.reverse(); send( @@ -603,7 +639,7 @@ async fn main() { table: LOCAL_V6.into(), action: "local".into(), keyset_data, - ..Default::default() + parameter_data: vec![rid], }), &cli, ) @@ -861,7 +897,12 @@ fn dump_tables(table: &BTreeMap>) { println!("local v6:"); for e in table.get(LOCAL_V6).unwrap() { if let Some(a) = get_addr(&e.keyset_data, false) { - println!("{a}") + let rid = e + .parameter_data + .first() + .map(|r| r.to_string()) + .unwrap_or_else(|| "?".into()); + println!("{a} (rid {rid})") } } println!("local v4:"); @@ -873,8 +914,8 @@ fn dump_tables(table: &BTreeMap>) { println!("router v6_idx:"); for e in table.get(ROUTER_V6_IDX).unwrap() { - let tgt = match get_addr_subnet(&e.keyset_data) { - Some((a, m)) => format!("{a}/{m}"), + let tgt = match get_rid_addr_subnet(&e.keyset_data) { + Some((rid, a, m)) => format!("rid {rid}: {a}/{m}"), None => "?".into(), }; let idx = match get_idx_slots(&e.parameter_data) { @@ -908,8 +949,8 @@ fn dump_tables(table: &BTreeMap>) { println!("router v4_idx:"); for e in table.get(ROUTER_V4_IDX).unwrap() { - let tgt = match get_addr_subnet(&e.keyset_data) { - Some((a, m)) => format!("{a}/{m}"), + let tgt = match get_rid_addr_subnet(&e.keyset_data) { + Some((rid, a, m)) => format!("rid {rid}: {a}/{m}"), None => "?".into(), }; let idx = match get_idx_slots(&e.parameter_data) { @@ -1154,6 +1195,17 @@ fn get_addr_subnet(data: &[u8]) -> Option<(IpAddr, u8)> { } } +fn get_rid_addr_subnet(data: &[u8]) -> Option<(u8, IpAddr, u8)> { + match data.len() { + 6 => Some((data[0], get_addr(&data[1..5], true)?, data[5])), + 18 => Some((data[0], get_addr(&data[1..17], true)?, data[17])), + _ => { + println!("expected [rid, address, subnet], found: {data:x?}"); + None + } + } +} + fn get_addr_vni_mac(data: &[u8]) -> Option<(IpAddr, u32, [u8; 6])> { match data.len() { 13 => Some(( diff --git a/softnpu/src/test.rs b/softnpu/src/test.rs index 9210e25..aa67e3a 100644 --- a/softnpu/src/test.rs +++ b/softnpu/src/test.rs @@ -88,7 +88,7 @@ fn pipeline_init(pipeline: &mut main_pipeline) { ); // boundary services loopback ip entry - let (key_buf, param_buf) = local6_entry("fd00:99::1"); + let (key_buf, param_buf) = local6_entry(0, "fd00:99::1"); pipeline.add_ingress_local_local_v6_entry("local", &key_buf, ¶m_buf, 0); // resolver entry for upstream gateway @@ -576,9 +576,21 @@ fn router_idx_entry( idx: u16, slots: u8, ) -> (Vec, Vec) { - let mut key_buf = match dst.parse().unwrap() { - IpAddr::V4(a) => a.octets().to_vec(), - IpAddr::V6(a) => a.octets().to_vec(), + router_idx_entry_rid(0, dst, prefix_len, idx, slots) +} + +// Create an entry for the multipath cidr -> index table of a specific router +fn router_idx_entry_rid( + rid: u8, + dst: &str, + prefix_len: u8, + idx: u16, + slots: u8, +) -> (Vec, Vec) { + let mut key_buf = vec![rid]; + match dst.parse().unwrap() { + IpAddr::V4(a) => key_buf.extend_from_slice(&a.octets()), + IpAddr::V6(a) => key_buf.extend_from_slice(&a.octets()), }; key_buf.push(prefix_len); @@ -633,12 +645,12 @@ fn nat4_entry( (key_buf, param_buf) } -fn local6_entry(addr: &str) -> (Vec, Vec) { +fn local6_entry(rid: u8, addr: &str) -> (Vec, Vec) { let addr: Ipv6Addr = addr.parse().unwrap(); let mut key_buf = addr.octets().to_vec(); key_buf.reverse(); - (key_buf, Vec::new()) + (key_buf, vec![rid]) } fn resolver4_entry(addr: &str, mac: [u8; 6]) -> (Vec, Vec) { @@ -663,3 +675,121 @@ fn mac_rewrite_entry(port: u16, mac: [u8; 6]) -> (Vec, Vec) { (key_buf, param_buf) } + +// Build a geneve-encapped ipv4 packet destined to the given TEP address, +// returning the outer ipv6 packet bytes. +fn geneve_encapped_v4(tep: &str, inner_dst_addr: &str) -> Vec { + let mut n = 8; + let mut icmp_data: Vec = vec![0; n]; + let mut icmp = MutableIcmpPacket::new(&mut icmp_data).unwrap(); + icmp.set_payload([0x04, 0x17, 0x00, 0x00].as_slice()); + + n += 20; + let mut inner_ip_data: Vec = vec![0; n]; + let inner_src: Ipv4Addr = "1.2.3.4".parse().unwrap(); + let inner_dst: Ipv4Addr = inner_dst_addr.parse().unwrap(); + let mut inner_ip = MutableIpv4Packet::new(&mut inner_ip_data).unwrap(); + inner_ip.set_version(4); + inner_ip.set_source(inner_src); + inner_ip.set_header_length(5); + inner_ip.set_destination(inner_dst); + inner_ip.set_next_level_protocol(IpNextHeaderProtocol::new(17)); + inner_ip.set_total_length(20 + icmp_data.len() as u16); + inner_ip.set_payload(&icmp_data); + + n += 14; + let mut eth_data: Vec = vec![0; n]; + let mut eth = MutableEthernetPacket::new(&mut eth_data).unwrap(); + eth.set_destination(MacAddr::new(0x11, 0x11, 0x11, 0x22, 0x22, 0x22)); + eth.set_source(MacAddr::new(0x33, 0x33, 0x33, 0x44, 0x44, 0x44)); + eth.set_ethertype(EtherType(0x0800)); + eth.set_payload(&inner_ip_data); + + n += 8; + let mut geneve_data: Vec = + vec![0x00, 0x00, 0x65, 0x58, 0x11, 0x11, 0x11, 0x00]; + geneve_data.extend_from_slice(ð_data); + + n += 8; + let mut udp_data: Vec = vec![0; n]; + let mut udp = MutableUdpPacket::new(&mut udp_data).unwrap(); + udp.set_source(100); + udp.set_destination(6081); + udp.set_checksum(0x1701); + udp.set_payload(&geneve_data); + + n += 40; + let mut ip_data: Vec = vec![0; n]; + let mut ip = MutableIpv6Packet::new(&mut ip_data).unwrap(); + ip.set_version(6); + ip.set_source("fd00:1::1".parse::().unwrap()); + ip.set_destination(tep.parse::().unwrap()); + ip.set_payload_length(udp_data.len() as u16); + ip.set_payload(&udp_data); + ip.set_next_header(IpNextHeaderProtocol::new(17)); + + ip_data +} + +#[test] +fn router_id_selects_route_table() -> Result<(), anyhow::Error> { + let mut pipeline = main_pipeline::new(3); + pipeline_init(&mut pipeline); + + // router 0: a more specific path for 8.8.8.0/24, reusing the slot 2 + // forwarding entry (gw=1.2.3.1, port=1) + let (key_buf, param_buf) = router_idx_entry("8.8.8.0", 24, 2, 1); + pipeline + .add_ingress_router_v4_idx_rtr_entry("index", &key_buf, ¶m_buf, 0); + + // router 2: tunnel endpoint fd00:99::2 + let (key_buf, param_buf) = local6_entry(2, "fd00:99::2"); + pipeline.add_ingress_local_local_v6_entry("local", &key_buf, ¶m_buf, 0); + + // router 2: a single path for 0.0.0.0/0 pointing at data in slot 4 + let (key_buf, param_buf) = router_idx_entry_rid(2, "0.0.0.0", 0, 4, 1); + pipeline + .add_ingress_router_v4_idx_rtr_entry("index", &key_buf, ¶m_buf, 0); + + // At slot 4, add a forwarding entry gw=1.2.3.9, port=2 + let (key_buf, param_buf) = router_forward_entry(4, "1.2.3.9", 2); + pipeline.add_ingress_router_v4_route_rtr_entry( + "forward", &key_buf, ¶m_buf, 0, + ); + + // resolver entry for router 2's gateway + let (key_buf, param_buf) = + resolver4_entry("1.2.3.9", [8, 9, 10, 11, 12, 14]); + pipeline.add_ingress_resolver_resolver_v4_entry( + "rewrite_dst", + &key_buf, + ¶m_buf, + 0, + ); + + // mac rewrite table + let (key_buf, param_buf) = mac_rewrite_entry(2, [1, 2, 3, 4, 5, 7]); + pipeline + .add_ingress_mac_mac_rewrite_entry("rewrite", &key_buf, ¶m_buf, 0); + + let mut npu = SoftNpu::new(3, pipeline, false); + let phy0 = npu.phy(0); + let phy1 = npu.phy(1); + let phy2 = npu.phy(2); + + npu.run(); + + // The same inner destination to each tunnel endpoint. Router 0's /24 + // would win on prefix length if the lookup ignored the router id. + let pkt = geneve_encapped_v4("fd00:99::1", "8.8.8.8"); + phy0.send(&[TxFrame::new(phy1.mac, 0x86dd, &pkt)])?; + let fs = phy1.recv(); + assert_eq!(fs.len(), 1); + + let pkt = geneve_encapped_v4("fd00:99::2", "8.8.8.8"); + phy0.send(&[TxFrame::new(phy2.mac, 0x86dd, &pkt)])?; + let fs = phy2.recv(); + assert_eq!(fs.len(), 1); + + Ok(()) +} From 70e2f889058c7ebb443a285360e1b382ac06630a Mon Sep 17 00:00:00 2001 From: Nicolas Kagami Date: Tue, 3 Nov 2026 19:40:08 -0300 Subject: [PATCH 2/2] minor improvements --- scadm/src/main.rs | 10 +-- softnpu/src/test.rs | 155 ++++++++++++++++++++++---------------------- 2 files changed, 82 insertions(+), 83 deletions(-) diff --git a/scadm/src/main.rs b/scadm/src/main.rs index d29629d..6f4f5ff 100644 --- a/scadm/src/main.rs +++ b/scadm/src/main.rs @@ -52,7 +52,7 @@ enum Commands { /// Next Hop nexthop: Ipv4Addr, - /// Router id performing the lookup. + /// Router id to which it belongs. #[clap(long, default_value_t = 0)] rid: u8, }, @@ -65,7 +65,7 @@ enum Commands { /// Subnet mask for the destination. mask: u8, - /// Router id performing the lookup. + /// Router id to which it belongs. #[clap(long, default_value_t = 0)] rid: u8, }, @@ -84,7 +84,7 @@ enum Commands { /// Next Hop nexthop: Ipv6Addr, - /// Router id performing the lookup. + /// Router id to which it belongs. #[clap(long, default_value_t = 0)] rid: u8, }, @@ -97,7 +97,7 @@ enum Commands { /// Subnet mask for the destination. mask: u8, - /// Router id performing the lookup. + /// Router id to which it belongs. #[clap(long, default_value_t = 0)] rid: u8, }, @@ -769,6 +769,8 @@ async fn main() { table: LOCAL_V4.into(), action: "local".into(), keyset_data, + // IPv4 addresses are never tunnel endpoints, so they + // always use the default router 0. parameter_data: vec![0], }), &cli, diff --git a/softnpu/src/test.rs b/softnpu/src/test.rs index afe86bc..732d2b5 100644 --- a/softnpu/src/test.rs +++ b/softnpu/src/test.rs @@ -959,7 +959,7 @@ fn ipv6_ttl1_dropped() -> Result<(), anyhow::Error> { Ok(()) } -fn boundary_services_v6(inner_dst: &str, hop_limit: u8) -> Vec { +fn boundary_services_v6(tep: &str, inner_dst: &str, hop_limit: u8) -> Vec { let inner_ip = v6_probe("fd00:2::1", inner_dst, hop_limit); let mut eth_data = vec![0u8; 14 + inner_ip.len()]; let mut eth = MutableEthernetPacket::new(&mut eth_data).unwrap(); @@ -967,7 +967,7 @@ fn boundary_services_v6(inner_dst: &str, hop_limit: u8) -> Vec { eth.set_source(MacAddr::new(0x33, 0x33, 0x33, 0x44, 0x44, 0x44)); eth.set_ethertype(EtherType(ETHERTYPE_IPV6)); eth.set_payload(&inner_ip); - wrap_geneve("fd00:1::1", "fd00:99::1", ð_data, None) + wrap_geneve("fd00:1::1", tep, ð_data, None) } #[test] @@ -981,7 +981,7 @@ fn boundary_services_hop_limit0_to_scrimlet() -> Result<(), anyhow::Error> { npu.run(); - let ip_data = boundary_services_v6("fd00:1::5", 0); + let ip_data = boundary_services_v6("fd00:99::1", "fd00:1::5", 0); phy1.send(&[TxFrame::new( l2_dst(phy0.mac, ETHERTYPE_IPV6, &ip_data), ETHERTYPE_IPV6, @@ -1033,7 +1033,7 @@ fn boundary_services_hop_limit0_nonzero_port_dropped( npu.run(); - let ip_data = boundary_services_v6("fd00:3::99", 0); + let ip_data = boundary_services_v6("fd00:99::1", "fd00:3::99", 0); phy0.send(&[TxFrame::new( l2_dst(phy1.mac, ETHERTYPE_IPV6, &ip_data), ETHERTYPE_IPV6, @@ -3940,101 +3940,82 @@ fn geneve_mcast_v6_inner_pkt( // Build a geneve-encapped ipv4 packet destined to the given TEP address, // returning the outer ipv6 packet bytes. -fn geneve_encapped_v4(tep: &str, inner_dst_addr: &str) -> Vec { - let mut n = 8; - let mut icmp_data: Vec = vec![0; n]; - let mut icmp = MutableIcmpPacket::new(&mut icmp_data).unwrap(); - icmp.set_payload([0x04, 0x17, 0x00, 0x00].as_slice()); - - n += 20; - let mut inner_ip_data: Vec = vec![0; n]; - let inner_src: Ipv4Addr = "1.2.3.4".parse().unwrap(); - let inner_dst: Ipv4Addr = inner_dst_addr.parse().unwrap(); - let mut inner_ip = MutableIpv4Packet::new(&mut inner_ip_data).unwrap(); - inner_ip.set_version(4); - inner_ip.set_source(inner_src); - inner_ip.set_header_length(5); - inner_ip.set_destination(inner_dst); - inner_ip.set_next_level_protocol(IpNextHeaderProtocol::new(17)); - inner_ip.set_total_length(20 + icmp_data.len() as u16); - inner_ip.set_ttl(64); - inner_ip.set_payload(&icmp_data); +fn geneve_encapped_v4(tep: &str, inner_dst: &str) -> Vec { + let mut ip_data = vec![0u8; 28]; + let mut ip = MutableIpv4Packet::new(&mut ip_data).unwrap(); + ip.set_version(4); + ip.set_header_length(5); + ip.set_total_length(28); + ip.set_source("1.2.3.4".parse().unwrap()); + ip.set_destination(inner_dst.parse().unwrap()); + ip.set_next_level_protocol(IpNextHeaderProtocol::new(17)); + ip.set_ttl(64); - n += 14; - let mut eth_data: Vec = vec![0; n]; + let mut eth_data = vec![0u8; 14 + ip_data.len()]; let mut eth = MutableEthernetPacket::new(&mut eth_data).unwrap(); eth.set_destination(MacAddr::new(0x11, 0x11, 0x11, 0x22, 0x22, 0x22)); eth.set_source(MacAddr::new(0x33, 0x33, 0x33, 0x44, 0x44, 0x44)); - eth.set_ethertype(EtherType(0x0800)); - eth.set_payload(&inner_ip_data); - - n += 8; - let mut geneve_data: Vec = - vec![0x00, 0x00, 0x65, 0x58, 0x11, 0x11, 0x11, 0x00]; - geneve_data.extend_from_slice(ð_data); - - n += 8; - let mut udp_data: Vec = vec![0; n]; - let mut udp = MutableUdpPacket::new(&mut udp_data).unwrap(); - udp.set_source(100); - udp.set_destination(6081); - udp.set_checksum(0x1701); - udp.set_payload(&geneve_data); - - n += 40; - let mut ip_data: Vec = vec![0; n]; - let mut ip = MutableIpv6Packet::new(&mut ip_data).unwrap(); - ip.set_version(6); - ip.set_source("fd00:1::1".parse::().unwrap()); - ip.set_destination(tep.parse::().unwrap()); - ip.set_payload_length(udp_data.len() as u16); - ip.set_payload(&udp_data); - ip.set_next_header(IpNextHeaderProtocol::new(17)); - ip.set_hop_limit(64); - - ip_data + eth.set_ethertype(EtherType(ETHERTYPE_IPV4)); + eth.set_payload(&ip_data); + wrap_geneve("fd00:1::1", tep, ð_data, None) } #[test] -fn router_id_selects_route_table() -> Result<(), anyhow::Error> { +fn endpoint_selects_route_table() -> Result<(), anyhow::Error> { let mut pipeline = main_pipeline::new(3); - pipeline_init(&mut pipeline); - // router 0: a more specific path for 8.8.8.0/24, reusing the slot 2 - // forwarding entry (gw=1.2.3.1, port=1) - let (key_buf, param_buf) = router_idx_entry("8.8.8.0", 24, 2, 1); + // router 1: endpoint fd00:99::2, 8.8.8.0/24 and 2001:db8::/64 out port 1 + let (key_buf, param_buf) = local6_entry(1, "fd00:99::2"); + pipeline.add_ingress_local_local_v6_entry("local", &key_buf, ¶m_buf, 0); + let (key_buf, param_buf) = router_idx_entry_rid(1, "8.8.8.0", 24, 4, 1); pipeline .add_ingress_router_v4_idx_rtr_entry("index", &key_buf, ¶m_buf, 0); + let (key_buf, param_buf) = router_forward_entry(4, "1.2.3.1", 1); + pipeline.add_ingress_router_v4_route_rtr_entry( + "forward", &key_buf, ¶m_buf, 0, + ); + let (key_buf, param_buf) = router_idx_entry_rid(1, "2001:db8::", 64, 4, 1); + pipeline + .add_ingress_router_v6_idx_rtr_entry("index", &key_buf, ¶m_buf, 0); + let (key_buf, param_buf) = router_forward_entry(4, "fe80::1", 1); + pipeline.add_ingress_router_v6_route_rtr_entry( + "forward", &key_buf, ¶m_buf, 0, + ); - // router 2: tunnel endpoint fd00:99::2 - let (key_buf, param_buf) = local6_entry(2, "fd00:99::2"); + // router 2: endpoint fd00:99::3, 0.0.0.0/0 and ::/0 out port 2 + let (key_buf, param_buf) = local6_entry(2, "fd00:99::3"); pipeline.add_ingress_local_local_v6_entry("local", &key_buf, ¶m_buf, 0); - - // router 2: a single path for 0.0.0.0/0 pointing at data in slot 4 - let (key_buf, param_buf) = router_idx_entry_rid(2, "0.0.0.0", 0, 4, 1); + let (key_buf, param_buf) = router_idx_entry_rid(2, "0.0.0.0", 0, 5, 1); pipeline .add_ingress_router_v4_idx_rtr_entry("index", &key_buf, ¶m_buf, 0); - - // At slot 4, add a forwarding entry gw=1.2.3.9, port=2 - let (key_buf, param_buf) = router_forward_entry(4, "1.2.3.9", 2); + let (key_buf, param_buf) = router_forward_entry(5, "1.2.3.1", 2); pipeline.add_ingress_router_v4_route_rtr_entry( "forward", &key_buf, ¶m_buf, 0, ); + let (key_buf, param_buf) = router_idx_entry_rid(2, "::", 0, 5, 1); + pipeline + .add_ingress_router_v6_idx_rtr_entry("index", &key_buf, ¶m_buf, 0); + let (key_buf, param_buf) = router_forward_entry(5, "fe80::1", 2); + pipeline.add_ingress_router_v6_route_rtr_entry( + "forward", &key_buf, ¶m_buf, 0, + ); - // resolver entry for router 2's gateway let (key_buf, param_buf) = - resolver4_entry("1.2.3.9", [8, 9, 10, 11, 12, 14]); + resolver4_entry("1.2.3.1", [8, 9, 10, 11, 12, 13]); pipeline.add_ingress_resolver_resolver_v4_entry( "rewrite_dst", &key_buf, ¶m_buf, 0, ); - - // mac rewrite table - let (key_buf, param_buf) = mac_rewrite_entry(2, [1, 2, 3, 4, 5, 7]); - pipeline - .add_ingress_mac_mac_rewrite_entry("rewrite", &key_buf, ¶m_buf, 0); + let (key_buf, param_buf) = + resolver6_entry("fe80::1", [20, 21, 22, 23, 24, 25]); + pipeline.add_ingress_resolver_resolver_v6_entry( + "rewrite_dst", + &key_buf, + ¶m_buf, + 0, + ); let mut npu = SoftNpu::new(3, pipeline, false); let phy0 = npu.phy(0); @@ -4043,15 +4024,31 @@ fn router_id_selects_route_table() -> Result<(), anyhow::Error> { npu.run(); - // The same inner destination to each tunnel endpoint. Router 0's /24 - // would win on prefix length if the lookup ignored the router id. - let pkt = geneve_encapped_v4("fd00:99::1", "8.8.8.8"); - phy0.send(&[TxFrame::new(phy1.mac, 0x86dd, &pkt)])?; + // 8.8.8.8 through router 1 should come out phy1 + let pkt = geneve_encapped_v4("fd00:99::2", "8.8.8.8"); + phy0.send(&[TxFrame::new(phy1.mac, ETHERTYPE_IPV6, &pkt)])?; + wait_for(|| phy1.recv_buffer_len() > 0, "router 1 frame on port 1"); let fs = phy1.recv(); assert_eq!(fs.len(), 1); - let pkt = geneve_encapped_v4("fd00:99::2", "8.8.8.8"); - phy0.send(&[TxFrame::new(phy2.mac, 0x86dd, &pkt)])?; + // 8.8.8.8 through router 2 should come out phy2 + let pkt = geneve_encapped_v4("fd00:99::3", "8.8.8.8"); + phy0.send(&[TxFrame::new(phy2.mac, ETHERTYPE_IPV6, &pkt)])?; + wait_for(|| phy2.recv_buffer_len() > 0, "router 2 frame on port 2"); + let fs = phy2.recv(); + assert_eq!(fs.len(), 1); + + // 2001:db8::1 through router 1 should come out phy1 + let pkt = boundary_services_v6("fd00:99::2", "2001:db8::1", 64); + phy0.send(&[TxFrame::new(phy1.mac, ETHERTYPE_IPV6, &pkt)])?; + wait_for(|| phy1.recv_buffer_len() > 0, "router 1 v6 frame on port 1"); + let fs = phy1.recv(); + assert_eq!(fs.len(), 1); + + // 2001:db8::1 through router 2 should come out phy2 + let pkt = boundary_services_v6("fd00:99::3", "2001:db8::1", 64); + phy0.send(&[TxFrame::new(phy2.mac, ETHERTYPE_IPV6, &pkt)])?; + wait_for(|| phy2.recv_buffer_len() > 0, "router 2 v6 frame on port 2"); let fs = phy2.recv(); assert_eq!(fs.len(), 1);