|
2 | 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. |
4 | 4 |
|
5 | | -//! Mock / dummy versions of the OPTE module, for non-illumos platforms |
| 5 | +//! Mock / dummy versions of the OPTE module, for non-illumos platforms. |
| 6 | +//! |
| 7 | +//! Most methods are either `unimplemented!()` or silent no-ops. |
| 8 | +//! Multicast subscribe/unsubscribe is an exception, as it maintains real |
| 9 | +//! in-memory state because port manager tests assert on subscription contents. |
6 | 10 |
|
7 | 11 | use crate::addrobj::AddrObject; |
8 | 12 | use omicron_common::api::internal::shared::NetworkInterfaceKind; |
9 | 13 | use oxide_vpc::api::AddRouterEntryReq; |
| 14 | +use oxide_vpc::api::ClearMcast2PhysReq; |
| 15 | +use oxide_vpc::api::ClearMcastForwardingReq; |
10 | 16 | use oxide_vpc::api::ClearVirt2PhysReq; |
11 | 17 | use oxide_vpc::api::DelRouterEntryReq; |
12 | 18 | use oxide_vpc::api::DetachSubnetResp; |
13 | | -use oxide_vpc::api::Direction; |
| 19 | +use oxide_vpc::api::DumpMcast2PhysResp; |
| 20 | +use oxide_vpc::api::DumpMcastForwardingResp; |
14 | 21 | use oxide_vpc::api::DumpVirt2PhysResp; |
15 | 22 | use oxide_vpc::api::IpCfg; |
16 | 23 | use oxide_vpc::api::IpCidr; |
17 | 24 | use oxide_vpc::api::ListPortsResp; |
| 25 | +use oxide_vpc::api::McastSubscribeReq; |
| 26 | +use oxide_vpc::api::McastUnsubscribeReq; |
18 | 27 | use oxide_vpc::api::NoResp; |
19 | 28 | use oxide_vpc::api::PortInfo; |
20 | 29 | use oxide_vpc::api::RouterClass; |
21 | 30 | use oxide_vpc::api::RouterTarget; |
22 | 31 | use oxide_vpc::api::SetExternalIpsReq; |
23 | 32 | use oxide_vpc::api::SetFwRulesReq; |
| 33 | +use oxide_vpc::api::SetMcast2PhysReq; |
| 34 | +use oxide_vpc::api::SetMcastForwardingReq; |
24 | 35 | use oxide_vpc::api::SetVirt2PhysReq; |
| 36 | +use oxide_vpc::api::SourceFilter; |
25 | 37 | use oxide_vpc::api::VpcCfg; |
26 | 38 | use slog::Logger; |
27 | 39 | use std::collections::HashMap; |
@@ -76,6 +88,11 @@ pub enum Error { |
76 | 88 | "Tried to update attached subnets on non-existent port ({0}, {1:?})" |
77 | 89 | )] |
78 | 90 | AttachedSubnetUpdateMissingPort(uuid::Uuid, NetworkInterfaceKind), |
| 91 | + |
| 92 | + #[error( |
| 93 | + "address {0} is not within the underlay multicast subnet (ff04::/64)" |
| 94 | + )] |
| 95 | + InvalidMcastUnderlay(std::net::Ipv6Addr), |
79 | 96 | } |
80 | 97 |
|
81 | 98 | pub fn initialize_xde_driver( |
@@ -172,6 +189,8 @@ pub(crate) struct PortData { |
172 | 189 | pub port: PortInfo, |
173 | 190 | /// The routes for this port. This simulates the router layer. |
174 | 191 | pub routes: Vec<RouteInfo>, |
| 192 | + /// Multicast group subscriptions: group IP → source filter. |
| 193 | + pub mcast_subscriptions: HashMap<IpAddr, SourceFilter>, |
175 | 194 | } |
176 | 195 |
|
177 | 196 | #[derive(Debug)] |
@@ -237,7 +256,11 @@ impl Handle { |
237 | 256 | return Err(OpteError::DuplicatePort(entry.key().to_string())); |
238 | 257 | } |
239 | 258 | Entry::Vacant(entry) => { |
240 | | - entry.insert(PortData { port, routes: Vec::new() }); |
| 259 | + entry.insert(PortData { |
| 260 | + port, |
| 261 | + routes: Vec::new(), |
| 262 | + mcast_subscriptions: HashMap::new(), |
| 263 | + }); |
241 | 264 | } |
242 | 265 | } |
243 | 266 | Ok(NO_RESPONSE) |
@@ -270,14 +293,46 @@ impl Handle { |
270 | 293 | Ok(NO_RESPONSE) |
271 | 294 | } |
272 | 295 |
|
273 | | - /// Allow traffic to / from a CIDR block on a port. |
274 | | - pub fn allow_cidr( |
| 296 | + /// Subscribe a port to a multicast group. |
| 297 | + pub fn mcast_subscribe( |
275 | 298 | &self, |
276 | | - _: &str, |
277 | | - _: IpCidr, |
278 | | - _: Direction, |
| 299 | + req: &McastSubscribeReq, |
279 | 300 | ) -> Result<NoResp, OpteError> { |
280 | | - unimplemented!("Not yet used in tests") |
| 301 | + let mut inner = opte_state().lock().unwrap(); |
| 302 | + let Some(port_data) = inner.ports.get_mut(&req.port_name) else { |
| 303 | + return Err(OpteError::NoPort(req.port_name.clone())); |
| 304 | + }; |
| 305 | + let group_ip: IpAddr = match req.group { |
| 306 | + oxide_vpc::api::IpAddr::Ip4(v4) => { |
| 307 | + std::net::Ipv4Addr::from(v4).into() |
| 308 | + } |
| 309 | + oxide_vpc::api::IpAddr::Ip6(v6) => { |
| 310 | + std::net::Ipv6Addr::from(v6).into() |
| 311 | + } |
| 312 | + }; |
| 313 | + port_data.mcast_subscriptions.insert(group_ip, req.filter.clone()); |
| 314 | + Ok(NO_RESPONSE) |
| 315 | + } |
| 316 | + |
| 317 | + /// Unsubscribe a port from a multicast group. |
| 318 | + pub fn mcast_unsubscribe( |
| 319 | + &self, |
| 320 | + req: &McastUnsubscribeReq, |
| 321 | + ) -> Result<NoResp, OpteError> { |
| 322 | + let mut inner = opte_state().lock().unwrap(); |
| 323 | + let Some(port_data) = inner.ports.get_mut(&req.port_name) else { |
| 324 | + return Err(OpteError::NoPort(req.port_name.clone())); |
| 325 | + }; |
| 326 | + let group_ip: IpAddr = match req.group { |
| 327 | + oxide_vpc::api::IpAddr::Ip4(v4) => { |
| 328 | + std::net::Ipv4Addr::from(v4).into() |
| 329 | + } |
| 330 | + oxide_vpc::api::IpAddr::Ip6(v6) => { |
| 331 | + std::net::Ipv6Addr::from(v6).into() |
| 332 | + } |
| 333 | + }; |
| 334 | + port_data.mcast_subscriptions.remove(&group_ip); |
| 335 | + Ok(NO_RESPONSE) |
281 | 336 | } |
282 | 337 |
|
283 | 338 | /// Delete a router entry from a port. |
@@ -323,6 +378,45 @@ impl Handle { |
323 | 378 | unimplemented!("Not yet used in tests") |
324 | 379 | } |
325 | 380 |
|
| 381 | + /// Set a multicast-to-physical mapping. |
| 382 | + pub fn set_m2p(&self, _: &SetMcast2PhysReq) -> Result<NoResp, OpteError> { |
| 383 | + Ok(NO_RESPONSE) |
| 384 | + } |
| 385 | + |
| 386 | + /// Clear a multicast-to-physical mapping. |
| 387 | + pub fn clear_m2p( |
| 388 | + &self, |
| 389 | + _: &ClearMcast2PhysReq, |
| 390 | + ) -> Result<NoResp, OpteError> { |
| 391 | + Ok(NO_RESPONSE) |
| 392 | + } |
| 393 | + |
| 394 | + /// Set multicast forwarding for a port. |
| 395 | + pub fn set_mcast_fwd( |
| 396 | + &self, |
| 397 | + _: &SetMcastForwardingReq, |
| 398 | + ) -> Result<NoResp, OpteError> { |
| 399 | + Ok(NO_RESPONSE) |
| 400 | + } |
| 401 | + |
| 402 | + /// Clear multicast forwarding for a port. |
| 403 | + pub fn clear_mcast_fwd( |
| 404 | + &self, |
| 405 | + _: &ClearMcastForwardingReq, |
| 406 | + ) -> Result<NoResp, OpteError> { |
| 407 | + Ok(NO_RESPONSE) |
| 408 | + } |
| 409 | + |
| 410 | + /// Dump all multicast-to-physical mappings. |
| 411 | + pub fn dump_m2p(&self) -> Result<DumpMcast2PhysResp, OpteError> { |
| 412 | + Ok(DumpMcast2PhysResp { ip4: Vec::new(), ip6: Vec::new() }) |
| 413 | + } |
| 414 | + |
| 415 | + /// Dump all multicast forwarding entries. |
| 416 | + pub fn dump_mcast_fwd(&self) -> Result<DumpMcastForwardingResp, OpteError> { |
| 417 | + Ok(DumpMcastForwardingResp { entries: Vec::new() }) |
| 418 | + } |
| 419 | + |
326 | 420 | /// List ports on the current system. |
327 | 421 | #[allow(dead_code)] |
328 | 422 | pub(crate) fn list_ports(&self) -> Result<ListPortsResp, OpteError> { |
|
0 commit comments