Skip to content

Commit d3c0ac8

Browse files
[review] Address comments and update XDE test infra
Includes: - geneve_verify: Add `assert_geneve_packet` helper and multi-packet parsing - geneve: Fix `packet_length()` for known options where body is consumed - Add `HeaderLen` supertrait bound to `OptionCast` - Implement `HeaderLen` for `Known<T>` delegating to T for known variants - `GeneveOptionParse::packet_length()` now uses `option.packet_length()` instead of relying solely on `body_remainder` which is empty for known options after parsing consumes the body - oxide-vpc geneve: Add `HeaderLen` impl for `ValidOxideOption` - overlay: Rewrite inner dest MAC to RFC-compliant multicast MAC for Tx - dhcpv6: Compute proper UDP checksum for IPv6 - ip: Add `multicast_mac()` methods with RFC 1112/2464 citations - opteadm: Add `set-m2p`/`clear-m2p` commands for multicast-to-physical mappings - xde-tests: Use typed `Ipv4Addr`/`Ipv6Addr` instead of `String` in dualstack setup - xde-tests: Simplify topology helpers and naming conventions (tests run single-threaded) - test.sh: Exercise driver teardown with `rem_drv` after tests complete - xde: Normalize inner dst MAC on Rx - drop non‑multicast inner - add mcast_rx_bad_inner_dst stat - xde: Initialize `mcast_fwd` from token and add `RefreshScope` and use scoped refresh across create/delete/subscribe/unsubscribe
1 parent c9f7957 commit d3c0ac8

File tree

18 files changed

+2114
-777
lines changed

18 files changed

+2114
-777
lines changed

.github/buildomat/jobs/test.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@ pfexec /input/xde/work/test/multicast_multi_sub --nocapture --test-threads=1
9494

9595
pfexec chmod +x /input/xde/work/test/multicast_validation
9696
pfexec /input/xde/work/test/multicast_validation --nocapture --test-threads=1
97+
98+
banner "teardown"
99+
# Ensure full driver teardown is exercised after tests complete
100+
pfexec rem_drv xde

bin/opteadm/src/bin/opteadm.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use oxide_vpc::api::AddFwRuleReq;
2828
use oxide_vpc::api::AddRouterEntryReq;
2929
use oxide_vpc::api::Address;
3030
use oxide_vpc::api::BOUNDARY_SERVICES_VNI;
31+
use oxide_vpc::api::ClearMcast2PhysReq;
3132
use oxide_vpc::api::ClearMcastForwardingReq;
3233
use oxide_vpc::api::ClearVirt2BoundaryReq;
3334
use oxide_vpc::api::ClearVirt2PhysReq;
@@ -59,6 +60,7 @@ use oxide_vpc::api::SNat4Cfg;
5960
use oxide_vpc::api::SNat6Cfg;
6061
use oxide_vpc::api::SetExternalIpsReq;
6162
use oxide_vpc::api::SetFwRulesReq;
63+
use oxide_vpc::api::SetMcast2PhysReq;
6264
use oxide_vpc::api::SetMcastForwardingReq;
6365
use oxide_vpc::api::SetVirt2BoundaryReq;
6466
use oxide_vpc::api::SetVirt2PhysReq;
@@ -236,6 +238,33 @@ enum Command {
236238
/// Clear a virtual-to-boundary mapping
237239
ClearV2B { prefix: IpCidr, tunnel_endpoint: Vec<Ipv6Addr> },
238240

241+
/// Set a multicast-to-physical (M2P) mapping
242+
///
243+
/// Maps an overlay multicast group address to an underlay IPv6 multicast
244+
/// address. This mapping is required before ports can subscribe to the
245+
/// group. Subscriptions use overlay addresses while OPTE uses underlay
246+
/// addresses for actual packet delivery.
247+
///
248+
/// All multicast groups use the fleet-wide DEFAULT_MULTICAST_VNI (77).
249+
SetM2P {
250+
/// The overlay multicast group address (IPv4 or IPv6)
251+
group: IpAddr,
252+
/// The underlay IPv6 multicast address (admin-local scope ff04::/16)
253+
underlay: MulticastUnderlay,
254+
},
255+
256+
/// Clear a multicast-to-physical (M2P) mapping
257+
///
258+
/// Removes the mapping from an overlay multicast group to its underlay
259+
/// address. After clearing, ports can no longer subscribe to this group
260+
/// (but existing subscriptions will succeed as no-ops on unsubscribe).
261+
ClearM2P {
262+
/// The overlay multicast group address (IPv4 or IPv6)
263+
group: IpAddr,
264+
/// The underlay IPv6 multicast address (admin-local scope ff04::/16)
265+
underlay: MulticastUnderlay,
266+
},
267+
239268
/// Set a multicast forwarding entry
240269
///
241270
/// Adds or updates a next hop for the specified underlay multicast address.
@@ -862,6 +891,16 @@ fn main() -> anyhow::Result<()> {
862891
hdl.clear_v2b(&req)?;
863892
}
864893

894+
Command::SetM2P { group, underlay } => {
895+
let req = SetMcast2PhysReq { group, underlay };
896+
hdl.set_m2p(&req)?;
897+
}
898+
899+
Command::ClearM2P { group, underlay } => {
900+
let req = ClearMcast2PhysReq { group, underlay };
901+
hdl.clear_m2p(&req)?;
902+
}
903+
865904
Command::SetMcastFwd { underlay, next_hop, replication } => {
866905
// OPTE routes to the next hop's unicast address to determine which
867906
// underlay port to use via the illumos routing table and DDM.

crates/opte-api/src/ip.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,20 @@ impl IpAddr {
314314
IpAddr::Ip6(v6) => v6.is_multicast(),
315315
}
316316
}
317+
318+
/// Return the multicast MAC address associated with this multicast IP address.
319+
/// If the IP address is not multicast, None will be returned.
320+
///
321+
/// See [RFC 1112 §6.4] for IPv4 and [RFC 2464 §7] for IPv6.
322+
///
323+
/// [RFC 1112 §6.4]: https://www.rfc-editor.org/rfc/rfc1112#section-6.4
324+
/// [RFC 2464 §7]: https://www.rfc-editor.org/rfc/rfc2464
325+
pub const fn multicast_mac(&self) -> Option<MacAddr> {
326+
match self {
327+
IpAddr::Ip4(v4) => v4.multicast_mac(),
328+
IpAddr::Ip6(v6) => v6.multicast_mac(),
329+
}
330+
}
317331
}
318332

319333
impl From<Ipv4Addr> for IpAddr {
@@ -444,6 +458,38 @@ impl Ipv4Addr {
444458
pub const fn is_multicast(&self) -> bool {
445459
matches!(self.inner[0], 224..240)
446460
}
461+
462+
/// Return the multicast MAC address associated with this multicast IPv4
463+
/// address. If the IPv4 address is not multicast, None will be returned.
464+
///
465+
/// See [RFC 1112 §6.4] for details.
466+
///
467+
/// [RFC 1112 §6.4]: https://www.rfc-editor.org/rfc/rfc1112#section-6.4
468+
pub const fn multicast_mac(&self) -> Option<MacAddr> {
469+
if self.is_multicast() {
470+
Some(self.unchecked_multicast_mac())
471+
} else {
472+
None
473+
}
474+
}
475+
476+
/// Return the multicast MAC address associated with this multicast IPv4
477+
/// address, without checking if this IP address is a multicast address.
478+
///
479+
/// See [RFC 1112 §6.4] for details.
480+
///
481+
/// [RFC 1112 §6.4]: https://www.rfc-editor.org/rfc/rfc1112#section-6.4
482+
pub const fn unchecked_multicast_mac(&self) -> MacAddr {
483+
let bytes = &self.inner;
484+
MacAddr::from_const([
485+
0x01,
486+
0x00,
487+
0x5e,
488+
bytes[1] & 0x7f, // Mask bit 24 to get lower 23 bits
489+
bytes[2],
490+
bytes[3],
491+
])
492+
}
447493
}
448494

449495
impl From<core::net::Ipv4Addr> for Ipv4Addr {
@@ -1601,6 +1647,19 @@ mod test {
16011647
);
16021648
}
16031649

1650+
fn to_ipv4(s: &str) -> Ipv4Addr {
1651+
s.parse().unwrap()
1652+
}
1653+
1654+
#[test]
1655+
fn test_ipv4_multicast_mac() {
1656+
assert!(to_ipv4("192.168.1.1").multicast_mac().is_none());
1657+
assert_eq!(
1658+
to_ipv4("224.0.0.251").multicast_mac().unwrap(),
1659+
MacAddr::from([0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb]),
1660+
);
1661+
}
1662+
16041663
#[test]
16051664
fn test_ipv6_solicited_node_multicast() {
16061665
let addr = to_ipv6("fd00:abcd:abcd:abcd:abcd:abcd:abcd:abcd");

0 commit comments

Comments
 (0)