Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/buildomat/common.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
#!/bin/bash

# The tofino2 has 20 stages, and the current sidecar.p4 needs all 20 of them.
# The tofino2 has 20 stages, and the current sidecar.p4 needs at most 19 of them,
# depending on the features it's compiled with.
#
# Specifying the number of stages isn't strictly necessary, but it allows us to
# track when we exceed the current ceiling. The underlying intention is to grow
# deliberately and thoughtfully, given the limited space on the ASIC.
#
# Note: this now seems silly since we have maxed out the number of stages, but
# we want to leave this check and note in place should we ever find a way to
# reduce our footprint below 20 stages.
TOFINO_STAGES=20
if [ "$MULTICAST" == "" ]; then
TOFINO_STAGES=17
else
TOFINO_STAGES=19
fi
Comment on lines +9 to +13

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the limits on main were 18 for non-multicast, and 20 for multicast. I kinda wish there were a way to be alerted when we go down in stage count. Do we know when things improved here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the framing here, going back: main's limit was 20 for both builds since #201 split multicast out as a separate feature but kept the one TOFINO_STAGES argument (a hard cap, as --stages becomes p4c's --num-stages-override).

The 18/20 has been what each build actually used, respectively. #208 (closed) had gotten these down to 16/19 with its refactoring of the pipeline, but none of that landed. Moving us to 17/19 here is great! It'll be awesome to combine this with #371, which also keeps the routing tables at full occupancy with @ways(8) (the table-size being finicky here is the same issue). With that work, multicast is always "on", as the feature flag goes away there.


# These describe which version of the SDE to download and where to find it
SDE_COMMIT=2a6b33211c9675996dcb99fe939045506667ae94
Expand Down
17 changes: 16 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ cfg-if = "1"
chrono = "0.4"
clap = { version = "4.6", features = [ "derive" ] }
colored = "3"
crc = "3.4.0"
csv = "1.3"
curl = "0.4"
dropshot = "0.17.1"
Expand Down
2 changes: 1 addition & 1 deletion dpd-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ tofino_asic = ["asic/tofino_asic"]
[dependencies]
async-trait = "0.1.89"
chrono.workspace = true
crc8 = "0.1.1"
futures.workspace = true
http.workspace = true
oxnet.workspace = true
Expand All @@ -34,6 +33,7 @@ packet = { path = "../packet" }
pcap = { path = "../pcap" }
asic = { path = "../asic" }
anyhow.workspace = true
crc.workspace = true
dpd-types.workspace = true
lazy_static.workspace = true
parking_lot.workspace = true
Expand Down
45 changes: 25 additions & 20 deletions dpd-client/tests/integration_tests/attached_subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,6 @@ async fn test_api() -> TestResult {
Ok(())
}

#[derive(Debug)]
enum L4Protocol {
Tcp,
Udp,
Icmp,
}

#[derive(Debug)]
struct ExternalTest {
// packet source/destination from the vm client's perspective
Expand Down Expand Up @@ -246,17 +239,25 @@ async fn test_ingress(switch: &Switch, test: &ExternalTest) -> TestResult {
// external subnet.
let load = vec![0xaau8, 0xbb, 0xcc, 0xdd, 0xee];
let switch_mac = switch.get_port_mac(test.uplink_port).unwrap().to_string();
let ingress_pkt = common::gen_udp_packet_loaded(
Endpoint::parse(
&test.upstream_router_mac,
&test.pkt_src_ip,
test.pkt_src_port,
)
.unwrap(),
Endpoint::parse(&switch_mac, &test.pkt_dst_ip, test.pkt_dst_port)
.unwrap(),
&load,
);
let src = Endpoint::parse(
&test.upstream_router_mac,
&test.pkt_src_ip,
test.pkt_src_port,
)
.unwrap();
let dst = Endpoint::parse(&switch_mac, &test.pkt_dst_ip, test.pkt_dst_port)
.unwrap();
let (ingress_pkt, csum_generated) = match test.l4_protocol {
L4Protocol::Tcp => {
(common::gen_tcp_packet_loaded(src, dst, &load), true)
}
L4Protocol::Udp => {
(common::gen_udp_packet_loaded(src, dst, &load), true)
}
L4Protocol::Icmp => {
(common::gen_icmp_packet_loaded(src, dst, &load), false)
}
};

// Convert the packet into a binary payload that will be encapsulated into a
// geneve packet. We also rewrite the destination mac of the packet to
Expand All @@ -283,7 +284,7 @@ async fn test_ingress(switch: &Switch, test: &ExternalTest) -> TestResult {
Endpoint::parse(
&backplane_port_mac,
switch_port_ip,
geneve::GENEVE_UDP_PORT,
common::tofino_flow_hash(src, dst, test.l4_protocol),
)
.unwrap(),
Endpoint::parse(
Expand All @@ -299,7 +300,11 @@ async fn test_ingress(switch: &Switch, test: &ExternalTest) -> TestResult {

/* Adjust for transition from switch port to gimlet port */
ipv6::Ipv6Hdr::adjust_hlim(&mut forward_pkt, -1);
udp::UdpHdr::update_checksum(&mut forward_pkt);
if csum_generated {
udp::UdpHdr::update_checksum(&mut forward_pkt);
} else {
forward_pkt.hdrs.udp_hdr.as_mut().unwrap().udp_sum = 0;
}

let send = vec![TestPacket {
packet: Arc::new(ingress_pkt),
Expand Down
78 changes: 75 additions & 3 deletions dpd-client/tests/integration_tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ const TEST_PCAP_TIMEOUT_MS: i32 = 1;
pub struct PhysPort(pub u16);
pub const NO_PORT: PhysPort = PhysPort(0xffff);

/// The type of an L4 packet carried over IP
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum L4Protocol {
Tcp = ipv4::IPPROTO_TCP,
Udp = ipv4::IPPROTO_UDP,
Icmp = ipv4::IPPROTO_ICMP,
}

// On real hardware the "service port" is the PCI port, which doesn't have a
// real physical port - it's just a collection of ringbufs in PCI space. On the
// model, we have "physical port" 33 wired to carry the traffic for "tofino
Expand Down Expand Up @@ -856,15 +865,23 @@ pub fn gen_udp_packet_loaded(
Packet::generate(src, dst, udp_stack, Some(body)).unwrap()
}

// Construct a single ICMP packet with an optional payload
// Construct a single ICMP echo request packet with an optional payload.
pub fn gen_icmp_packet_loaded(
src: Endpoint,
dst: Endpoint,
body: &[u8],
) -> Packet {
let icmp_stack = match src.get_ip("src").unwrap() {
IpAddr::V4(_) => vec![ipv4::IPPROTO_ICMP.into(), eth::ETHER_IPV4],
IpAddr::V6(_) => vec![ipv6::IPPROTO_ICMPV6.into(), eth::ETHER_IPV6],
IpAddr::V4(_) => vec![
u16::from(icmp::ICMP_ECHO) << 8,
ipv4::IPPROTO_ICMP.into(),
eth::ETHER_IPV4,
],
IpAddr::V6(_) => vec![
u16::from(icmp::ICMP6_ECHO_REQUEST) << 8,
ipv6::IPPROTO_ICMPV6.into(),
eth::ETHER_IPV6,
],
};

Packet::generate(src, dst, icmp_stack, Some(body)).unwrap()
Expand Down Expand Up @@ -1441,8 +1458,63 @@ pub fn gen_arp_reply(src: Endpoint, tgt: Endpoint) -> Packet {
.unwrap()
}

/// Compute an identical flow hash to the `FlowHash` control in ingress
/// processing.
///
/// This value is currently used in encap header generation and ECMP nexthop
/// selection.
pub fn tofino_flow_hash(
src: Endpoint,
dst: Endpoint,
ip_proto: L4Protocol,
) -> u16 {
let Ok(src_ip) = src.get_ip("src") else {
return 0;
};
let Ok(dst_ip) = dst.get_ip("dst") else {
return 0;
};

let crc = crc::Crc::<u16>::new(&crc::CRC_16_ARC);
let mut digest = crc.digest();

let proto = match (dst_ip, src_ip) {
(IpAddr::V6(dip), IpAddr::V6(sip)) => {
digest.update(&dip.octets());
digest.update(&sip.octets());
if ip_proto == L4Protocol::Icmp {
ipv6::IPPROTO_ICMPV6
} else {
ip_proto as u8
}
}
(IpAddr::V4(dip), IpAddr::V4(sip)) => {
digest.update(&dip.octets());
digest.update(&sip.octets());
ip_proto as u8
}
_ => panic!("mismatched src/dst address families"),
};

// Ingress processing will compute an L3 hash when no ports
// are available.
let dst_port = dst.get_port("dst").unwrap_or(0);
let src_port = if ip_proto == L4Protocol::Icmp {
// The parser leaves l4_src_port at 0 for ICMP echo.
0
} else {
src.get_port("src").unwrap_or(0)
};

digest.update(&[proto]);
digest.update(&dst_port.to_be_bytes());
digest.update(&src_port.to_be_bytes());
digest.finalize()
}
Comment thread
FelixMcFelix marked this conversation as resolved.

pub mod prelude {
pub use super::ADMIN_LOCAL_MULTICAST_PREFIX;
pub use super::L4Protocol;
pub use super::NO_PORT;
pub use super::PhysPort;
pub use super::SERVICE_PORT;
Expand Down
6 changes: 5 additions & 1 deletion dpd-client/tests/integration_tests/mcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,11 @@ fn prepare_expected_pkt(
Endpoint::parse(
&switch_port_mac,
"::0",
geneve::GENEVE_UDP_PORT,
common::tofino_flow_hash(
send_pkt.src().unwrap(),
send_pkt.dst().unwrap(),
L4Protocol::Udp,
),
)
.unwrap(),
Endpoint::parse(
Expand Down
Loading