Skip to content

Commit d0a41f9

Browse files
[multicast] Bitmap-based multicast replication for sidecar-lite
This adds multicast replication to sidecar-lite, emulating DPD's sidecar.p4 We manage 2 bitmaps per multicast destination (external, underlay), in which replication is driven via the `Replicate` extern (from p4rs #240). The ingress control block suppresses (or doesn't) these bitmaps based on the geneve multicast option tag, and then merges both into a single replication bitmap. This tag is read-only, matching DPD's sidecar.p4. OPTE (would) stamp it before the packet reaches the switch. Egress decap is gated on the reserved underlay subnet (ff04::/64, per RFC 7346 admin-local scope + Omicron) and tag=2 (Both), matching DPD's egress mcast_tag_check. Ports in the decap table get geneve stripped, others keep encapsulation for OPTE to handle on the receiving sled. Multicast dst MAC derivation (RFC 1112 for IPv4, RFC 2464 for IPv6) and per-port source MAC rewrite run on every replicated copy. Egress validates the outer IP multicast range before any mcast processing. ### References This work depends on the P4(rs) work in [#240](oxidecomputer/p4#240).
1 parent 264408f commit d0a41f9

8 files changed

Lines changed: 3152 additions & 168 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ 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" }

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: 36 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,29 @@ 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+
// Interface-local (ff01) must not be forwarded.
55+
if (hdr.ipv6.dst[127:112] == 16w0xff01) { transition reject; }
56+
57+
// Link-local (ff02) is forwarded to scrimlet.
58+
// Allow hop_limit == 1 (link-local scope) but still reject expired
59+
// packets.
60+
if (hdr.ipv6.dst[127:112] == 16w0xff02) {
61+
if (hdr.ipv6.hop_limit == 8w0) { transition reject; }
62+
transition ipv6_proto;
63+
}
64+
65+
// Non-link-local multicast requires hop_limit > 1.
66+
if (hdr.ipv6.hop_limit == 8w0) { transition reject; }
67+
if (hdr.ipv6.hop_limit == 8w1) { transition reject; }
68+
transition ipv6_proto;
69+
}
70+
71+
state ipv6_proto {
4972
if (hdr.ipv6.next_hdr == 8w0xdd) { transition ddm; }
5073
if (hdr.ipv6.next_hdr == 8w58) { transition icmp; }
5174
if (hdr.ipv6.next_hdr == 8w17) { transition udp; }
@@ -82,6 +105,18 @@ parser parse(
82105

83106
state ipv4 {
84107
pkt.extract(hdr.ipv4);
108+
if (hdr.ipv4.dst[31:28] == 4w0xe) { transition ipv4_mcast; }
109+
transition ipv4_proto;
110+
}
111+
112+
state ipv4_mcast {
113+
// Multicast with TTL <= 1 must not be forwarded (RFC 1112).
114+
if (hdr.ipv4.ttl == 8w0) { transition reject; }
115+
if (hdr.ipv4.ttl == 8w1) { transition reject; }
116+
transition ipv4_proto;
117+
}
118+
119+
state ipv4_proto {
85120
if (hdr.ipv4.protocol == 8w17) { transition udp; }
86121
if (hdr.ipv4.protocol == 8w6) { transition tcp; }
87122
if (hdr.ipv4.protocol == 8w1) { transition icmp; }

0 commit comments

Comments
 (0)