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
128 changes: 128 additions & 0 deletions dpd-client/tests/integration_tests/mcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3601,6 +3601,134 @@ async fn test_link_local_multicast_counter_attribution() -> TestResult {
Ok(())
}

async fn run_userspace_multicast_drop_test(
switch: &Switch,
src: Endpoint,
dst: Endpoint,
nat_target: types::ExternalNatTarget,
) -> TestResult {
let egress1 = PhysPort(15);
let egress2 = PhysPort(17);
let egress3 = PhysPort(19);

let internal_multicast_ip = IpAddr::V6(MULTICAST_NAT_IP);
create_test_multicast_group(
switch,
internal_multicast_ip,
Some(&TEST_TAG),
&[
(egress1, types::Direction::Underlay),
(egress3, types::Direction::Underlay),
],
None,
types::ExternalForwarding { vlan_id: None },
None,
)
.await;

let multicast_ip = dst.get_ip("destination").unwrap();
create_test_multicast_group(
switch,
multicast_ip,
Some(&TEST_TAG),
&[
(egress1, types::Direction::External),
(egress2, types::Direction::External),
],
Some(nat_target),
types::ExternalForwarding { vlan_id: None },
None,
)
.await;

let mut send = common::gen_udp_packet(src, dst);
common::add_sidecar_hdr(
switch,
&mut send,
sidecar::SC_FWD_FROM_USERSPACE,
NO_PORT,
PhysPort(10),
None,
);
let test_pkt = TestPacket { packet: Arc::new(send), port: SERVICE_PORT };

let ctr_baseline_userspace =
switch.get_counter("multicast_from_userspace", None).await.unwrap();
let ctr_baseline_no_group =
switch.get_counter("multicast_no_group", None).await.unwrap();

switch.packet_test(vec![test_pkt], Vec::new()).unwrap();

check_counter_incremented(
switch,
"multicast_from_userspace",
ctr_baseline_userspace,
1,
None,
)
.await
.unwrap();
check_counter_incremented(
switch,
"multicast_no_group",
ctr_baseline_no_group,
0,
None,
)
.await
.unwrap();

cleanup_test_group(switch, multicast_ip, &TEST_TAG).await.unwrap();
cleanup_test_group(switch, internal_multicast_ip, &TEST_TAG).await
}

#[tokio::test]
#[ignore]
async fn test_userspace_multicast_v4_dropped() -> TestResult {
let switch = &*get_switch().await;

let src =
Endpoint::parse("e0:d5:5e:67:89:ab", "10.10.10.10", 3333).unwrap();
let dst = Endpoint::parse(
"01:00:5e:00:01:00",
&MULTICAST_TEST_IPV4.to_string(),
4444,
)
.unwrap();

run_userspace_multicast_drop_test(
switch,
src,
dst,
create_nat_target_ipv4(),
)
.await
}

#[tokio::test]
#[ignore]
async fn test_userspace_multicast_v6_dropped() -> TestResult {
let switch = &*get_switch().await;

let src =
Endpoint::parse("e0:d5:5e:67:89:ab", "fd00:1122:7788:0101::4", 3333)
.unwrap();
let dst = Endpoint::parse(
"33:33:00:01:10:10",
&MULTICAST_TEST_IPV6.to_string(),
4444,
)
.unwrap();

run_userspace_multicast_drop_test(
switch,
src,
dst,
create_nat_target_ipv6(),
)
.await
}

#[tokio::test]
#[ignore]
async fn test_link_local_multicast_inbound_counter_attribution() -> TestResult {
Expand Down
3 changes: 2 additions & 1 deletion dpd/p4/constants.p4
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,6 @@ const bit<8> DROP_SCTP = 0x1B;
// TTL/hop limit of 1 in the parser, or inner TTL/hop limit of 0 or 1
// on the decapsulation path (see RFC 1812 §4.3.2.7, §5.3.1; RFC 4443 §2.4(e.3))
const bit<8> DROP_MULTICAST_TTL_EXCEEDED = 0x1C;
const bit<8> DROP_MULTICAST_FROM_USERSPACE = 0x1D;
// MAX(DROP_xxx) + 1
const bit<32> DROP_REASON_MAX = 0x1D;
const bit<32> DROP_REASON_MAX = 0x1E;
10 changes: 9 additions & 1 deletion dpd/p4/sidecar.p4
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,11 @@ control MulticastIngress (
meta.dropped = true;
}

action drop_mcast_from_userspace() {
meta.drop_reason = DROP_MULTICAST_FROM_USERSPACE;
meta.dropped = true;
}

// Drop action for IPv6 multicast packets with no group
// that is a valid admin-scoped multicast group.
action drop_mcastv6_admin_scoped_no_group() {
Expand Down Expand Up @@ -1833,7 +1838,10 @@ control MulticastIngress (

// Note: SSM tables currently take one extra stage in the pipeline (17->18).
apply {
if (hdr.geneve.isValid() && hdr.inner_ipv4.isValid()) {
if (ig_intr_md.ingress_port == USER_SPACE_SERVICE_PORT &&
hdr.sidecar.isValid()) {
drop_mcast_from_userspace();
} else if (hdr.geneve.isValid() && hdr.inner_ipv4.isValid()) {
// Check if the inner destination address is an IPv4 multicast
// address (224.0.0.0/4). Apply source filtering for both SSM
// (232.0.0.0/8) and ASM ranges.
Expand Down
5 changes: 5 additions & 0 deletions dpd/src/counters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ enum DropReason {
GeneveOptionUnknown,
DropSctp,
MulticastTtlExceeded,
MulticastFromUserspace,
}

impl TryFrom<u8> for DropReason {
Expand Down Expand Up @@ -236,6 +237,7 @@ impl TryFrom<u8> for DropReason {
26 => Ok(DropReason::GeneveOptionUnknown),
27 => Ok(DropReason::DropSctp),
28 => Ok(DropReason::MulticastTtlExceeded),
29 => Ok(DropReason::MulticastFromUserspace),
x => Err(format!("Unrecognized drop reason: {x}")),
}
}
Expand Down Expand Up @@ -286,6 +288,9 @@ fn reason_label(ctr: u8) -> Result<Option<String>, String> {
DropReason::MulticastTtlExceeded => {
"multicast_ttl_exceeded".to_string()
}
DropReason::MulticastFromUserspace => {
"multicast_from_userspace".to_string()
}
};
Ok(Some(label))
}
Expand Down
Loading