Skip to content

Commit 09d1d59

Browse files
[multicast] adds multicast replication for sidecar-lite
This PR adds multicast packet replication to sidecar-lite's P4 pipeline, emulating Dendrite's tofino sidecar implementation. Packets arriving inside geneve with a multicast inner destination are source-filtered, replicated to external and underlay groups, and tagged via the oxg_mcast geneve option so downstream hops suppress already-served groups. ## TTL==1 route dropping Packets with TTL==1 are dropped at the route table level via a compound key (path_idx, route_ttl_is_1) so the ttl_exceeded action fires instead of forward, preventing replication of expiring packets. The control plane programs paired entries for both TTL values. ## Parser IPv6 multicast scope validation now matches Dendrite's sidecar: ff01 (interface-local) rejected, ff02 (link-local) forwarded to scrimlet with hop_limit >= 1, other multicast requires hop_limit > 1. IPv4 multicast (224.0.0.0/4) TTL==0 and TTL==1 rejected. ## Ingress Source filtering for geneve-encapsulated multicast is executed via mcast_source_filter_v4/v6 tables (inner src LPM + inner dst exact). Replication tables set both grp_a (external) and grp_b (underlay) unconditionally, while per-packet tag handling suppresses the group whose stage is already done (0 = external, 1 = underlay, 2 = both). Non-encapsulated multicast bypasses source filtering. ## Egress The multicast tag is stamped into geneve options on every replicated copy. Port reflection filter drops copies where egress == ingress port. Decap occurs on bifurcated replication, or tag=2 (both groups served), with inner TTL validation and IPv4 header checksum update (RFC 1624). Also included: - A VLAN-aware decap variant that inserts 802.1Q tag with configured VLAN ID. - Multicast dst MAC derivation from IP destination on all copies, matching Dendrite's MulticastMacRewrite. - Per-port src MAC rewritten via the `mcast_src_mac` table. ## Dependencies p4 crates are currently updated to the `zl/multicast` branch for multicast replication support in p4rs/softnpu and LHS bit-slice assignment in x4c (used for multicast MAC derivation).
1 parent 69fae2a commit 09d1d59

8 files changed

Lines changed: 3828 additions & 452 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ members = [
66
resolver = "2"
77

88
[workspace.dependencies]
9-
p4rs = { git = "https://github.com/oxidecomputer/p4", branch = "main" }
10-
p4-macro = { git = "https://github.com/oxidecomputer/p4", branch = "main" }
11-
p4-test = { package = "tests", git = "https://github.com/oxidecomputer/p4", branch = "main" }
9+
p4rs = { git = "https://github.com/oxidecomputer/p4", branch = "zl/multicast" }
10+
p4-macro = { git = "https://github.com/oxidecomputer/p4", branch = "zl/multicast" }
11+
p4-test = { package = "tests", git = "https://github.com/oxidecomputer/p4", branch = "zl/multicast" }
1212
usdt = { git = "https://github.com/oxidecomputer/usdt" }
1313

1414
base64 = { version = "0.22" }
15-
clap = { version = "4.5.54", features = ["derive", "unstable-styles"] }
15+
clap = { version = "4.5.60", features = ["derive", "unstable-styles"] }
1616
anstyle = "1.0.13"
1717
libc = "0.2"
1818
serde = "1.0"
@@ -22,7 +22,7 @@ errno = "0.3"
2222
num = { version = "0.4.3", features = ["num-bigint"] }
2323
macaddr = { git = "https://github.com/luqmana/rust-macaddr", branch = "leading-zeros", version = "1.0.1" }
2424
bitvec = "1.0"
25-
colored = "3.0"
25+
colored = "3.1"
2626
anyhow = "1"
2727
pnet = "0.35"
2828
pnet_macros = "0.35"

p4/headers.p4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025 Oxide Computer Company
1+
// Copyright 2026 Oxide Computer Company
22

33
header sidecar_h {
44
bit<8> sc_code;
@@ -96,7 +96,7 @@ header geneve_opt_h {
9696
}
9797

9898
header oxg_opt_multicast_h {
99-
bit<2> replication;
99+
bit<2> mcast_tag;
100100
bit<30> reserved;
101101
}
102102

p4/parser.p4

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Oxide Computer Company
1+
// Copyright 2026 Oxide Computer Company
22

33
parser parse(
44
packet_in pkt,
@@ -46,6 +46,30 @@ parser parse(
4646

4747
state ipv6 {
4848
pkt.extract(hdr.ipv6);
49+
if (hdr.ipv6.dst[127:120] == 8w0xff) { transition ipv6_mcast; }
50+
transition ipv6_proto;
51+
}
52+
53+
state ipv6_mcast {
54+
ingress.is_mcast = true;
55+
// Interface-local (ff01) must not be forwarded.
56+
if (hdr.ipv6.dst[127:112] == 16w0xff01) { transition reject; }
57+
58+
// Link-local (ff02) is forwarded to scrimlet.
59+
// Allow hop_limit == 1 (link-local scope) but still reject expired
60+
// packets.
61+
if (hdr.ipv6.dst[127:112] == 16w0xff02) {
62+
if (hdr.ipv6.hop_limit == 8w0) { transition reject; }
63+
transition ipv6_proto;
64+
}
65+
66+
// Non-link-local multicast requires hop_limit > 1.
67+
if (hdr.ipv6.hop_limit == 8w0) { transition reject; }
68+
if (hdr.ipv6.hop_limit == 8w1) { transition reject; }
69+
transition ipv6_proto;
70+
}
71+
72+
state ipv6_proto {
4973
if (hdr.ipv6.next_hdr == 8w0xdd) { transition ddm; }
5074
if (hdr.ipv6.next_hdr == 8w58) { transition icmp; }
5175
if (hdr.ipv6.next_hdr == 8w17) { transition udp; }
@@ -82,6 +106,19 @@ parser parse(
82106

83107
state ipv4 {
84108
pkt.extract(hdr.ipv4);
109+
if (hdr.ipv4.dst[31:28] == 4w0xe) { transition ipv4_mcast; }
110+
transition ipv4_proto;
111+
}
112+
113+
state ipv4_mcast {
114+
ingress.is_mcast = true;
115+
// Multicast with TTL <= 1 must not be forwarded (RFC 1112).
116+
if (hdr.ipv4.ttl == 8w0) { transition reject; }
117+
if (hdr.ipv4.ttl == 8w1) { transition reject; }
118+
transition ipv4_proto;
119+
}
120+
121+
state ipv4_proto {
85122
if (hdr.ipv4.protocol == 8w17) { transition udp; }
86123
if (hdr.ipv4.protocol == 8w6) { transition tcp; }
87124
if (hdr.ipv4.protocol == 8w1) { transition icmp; }

0 commit comments

Comments
 (0)