Skip to content
Open
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
402 changes: 393 additions & 9 deletions dpd-client/tests/integration_tests/mcast.rs

Large diffs are not rendered by default.

56 changes: 39 additions & 17 deletions dpd/p4/sidecar.p4
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,16 @@ control NatIngress (
}

// Separate table for IPv4 multicast packets that need to be encapsulated.
// Groups without VLAN match untagged only. Groups with VLAN match both
// untagged (for decapsulated Geneve from underlay) and correctly tagged.
// Packets with wrong VLAN miss and are not NAT encapsulated.
// When hdr.vlan.isValid()==false, vlan_id matches as 0.
table ingress_ipv4_mcast {
key = { hdr.ipv4.dst_addr : exact; }
key = {
hdr.ipv4.dst_addr : exact;
hdr.vlan.isValid() : exact;
hdr.vlan.vlan_id : exact;
}
actions = { mcast_forward_ipv4_to; }
const size = IPV4_MULTICAST_TABLE_SIZE;
counters = mcast_ipv4_ingress_ctr;
Expand All @@ -630,8 +638,16 @@ control NatIngress (
}

// Separate table for IPv6 multicast packets that need to be encapsulated.
// Groups without VLAN match untagged only. Groups with VLAN match both
// untagged (for decapsulated Geneve from underlay) and correctly tagged.
// Packets with wrong VLAN miss and are not NAT encapsulated.
// When hdr.vlan.isValid()==false, vlan_id matches as 0.
table ingress_ipv6_mcast {
key = { hdr.ipv6.dst_addr : exact; }
key = {
hdr.ipv6.dst_addr : exact;
hdr.vlan.isValid() : exact;
hdr.vlan.vlan_id : exact;
}
actions = { mcast_forward_ipv6_to; }
const size = IPV6_MULTICAST_TABLE_SIZE;
counters = mcast_ipv6_ingress_ctr;
Expand Down Expand Up @@ -1273,9 +1289,7 @@ control MulticastRouter4(
}

table tbl {
key = {
hdr.ipv4.dst_addr : exact;
}
key = { hdr.ipv4.dst_addr : exact; }
actions = { forward; forward_vlan; unreachable; }
default_action = unreachable;
const size = IPV4_MULTICAST_TABLE_SIZE;
Expand All @@ -1284,16 +1298,18 @@ control MulticastRouter4(

apply {
// If the packet came in with a VLAN tag, we need to invalidate
// the VLAN header before we do the lookup. The VLAN header
// will be re-attached if set in the forward_vlan action.
// the VLAN header before we do the lookup. The VLAN header
// will be re-attached if set in the forward_vlan action (or
// untagged for groups without VLAN). This prevents unintended
// VLAN translation.
if (hdr.vlan.isValid()) {
hdr.ethernet.ether_type = hdr.vlan.ether_type;
hdr.vlan.setInvalid();
}

if (!tbl.apply().hit) {
icmp_error(ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE);
meta.drop_reason = DROP_IPV6_UNROUTEABLE;
icmp_error(ICMP_DEST_UNREACH, ICMP_DST_UNREACH_NET);
meta.drop_reason = DROP_IPV4_UNROUTEABLE;
// Dont set meta.dropped because we want an error packet
// to go out.
} else if (hdr.ipv4.ttl == 1 && !meta.service_routed) {
Expand Down Expand Up @@ -1411,9 +1427,7 @@ control MulticastRouter6 (
}

table tbl {
key = {
hdr.ipv6.dst_addr : exact;
}
key = { hdr.ipv6.dst_addr : exact; }
actions = { forward; forward_vlan; unreachable; }
default_action = unreachable;
const size = IPV6_MULTICAST_TABLE_SIZE;
Expand All @@ -1422,8 +1436,10 @@ control MulticastRouter6 (

apply {
// If the packet came in with a VLAN tag, we need to invalidate
// the VLAN header before we do the lookup. The VLAN header
// will be re-attached if set in the forward_vlan action.
// the VLAN header before we do the lookup. The VLAN header
// will be re-attached if set in the forward_vlan action (or
// untagged for groups without VLAN). This prevents unintended
// VLAN translation.
if (hdr.vlan.isValid()) {
hdr.ethernet.ether_type = hdr.vlan.ether_type;
hdr.vlan.setInvalid();
Expand Down Expand Up @@ -2251,12 +2267,18 @@ control Egress(

if (is_link_local_ipv6_mcast) {
link_local_mcast_ctr.count(eg_intr_md.egress_port);
} else if (hdr.geneve.isValid()) {
external_mcast_ctr.count(eg_intr_md.egress_port);
} else if (hdr.geneve.isValid() &&
hdr.geneve_opts.oxg_mcast.isValid() &&
hdr.geneve_opts.oxg_mcast.mcast_tag == MULTICAST_TAG_UNDERLAY) {
hdr.geneve_opts.oxg_mcast.mcast_tag != MULTICAST_TAG_EXTERNAL) {
// Encapsulated multicast going to sleds. Includes both
// MULTICAST_TAG_UNDERLAY and MULTICAST_TAG_UNDERLAY_EXTERNAL
// packets that were not decapped for this egress port.
underlay_mcast_ctr.count(eg_intr_md.egress_port);
} else {
// Decapped external multicast going to front panel ports.
// Either originally tagged EXTERNAL, or UNDERLAY_EXTERNAL
// packets that were decapped based on port bitmap.
external_mcast_ctr.count(eg_intr_md.egress_port);
}
} else {
// non-multicast packets should bypass the egress
Expand Down
9 changes: 6 additions & 3 deletions dpd/src/counters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/
//
// Copyright 2025 Oxide Computer Company
// Copyright 2026 Oxide Computer Company

/// This module contains the support for reading the indirect counters defined
/// by the p4 program. While direct counters are attached to an existing table,
Expand Down Expand Up @@ -284,6 +284,7 @@ enum DropReason {
GeneveOptionsTooLong,
GeneveOptionMalformed,
GeneveOptionUnknown,
VlanMismatch,
}

impl TryFrom<u8> for DropReason {
Expand Down Expand Up @@ -317,6 +318,7 @@ impl TryFrom<u8> for DropReason {
23 => Ok(DropReason::GeneveOptionsTooLong),
24 => Ok(DropReason::GeneveOptionMalformed),
25 => Ok(DropReason::GeneveOptionUnknown),
26 => Ok(DropReason::VlanMismatch),
x => Err(format!("Unrecognized drop reason: {x}")),
}
}
Expand All @@ -343,8 +345,8 @@ fn reason_label(ctr: u8) -> Result<Option<String>, String> {
DropReason::Ipv4TtlExceeded => "ipv4_ttl_exceeded".to_string(),
DropReason::Ipv6TtlInvalid => "ipv6_ttl_invalid".to_string(),
DropReason::Ipv6TtlExceeded => "ipv6_ttl_exceeded".to_string(),
DropReason::Ipv4Unrouteable => "ipv6_unrouteable".to_string(),
DropReason::Ipv6Unrouteable => "ipv4_unrouteable".to_string(),
DropReason::Ipv4Unrouteable => "ipv4_unrouteable".to_string(),
DropReason::Ipv6Unrouteable => "ipv6_unrouteable".to_string(),
DropReason::NatIngressMiss => "nat_ingress_miss".to_string(),
DropReason::MulticastNoGroup => "multicast_no_group".to_string(),
DropReason::MulticastInvalidMac => "multicast_invalid_mac".to_string(),
Expand All @@ -362,6 +364,7 @@ fn reason_label(ctr: u8) -> Result<Option<String>, String> {
"geneve_option_malformed".to_string()
}
DropReason::GeneveOptionUnknown => "geneve_option_unknown".to_string(),
DropReason::VlanMismatch => "vlan_mismatch".to_string(),
};
Ok(Some(label))
}
Expand Down
Loading