blob: d743fbed525bea5c8481d8b0d0324566e4a63bbf [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::HashSet;
use std::convert::TryFrom as _;
use std::pin::Pin;
use fidl::endpoints::Proxy as _;
use fidl_fuchsia_hardware_network as fhardware_network;
use fidl_fuchsia_net_debug as fnet_debug;
use fidl_fuchsia_net_dhcp as fnet_dhcp;
use fidl_fuchsia_net_interfaces as fnet_interfaces;
use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
use fidl_fuchsia_net_stack as fnet_stack;
use fidl_fuchsia_net_virtualization as fnet_virtualization;
use fidl_fuchsia_netstack as fnetstack;
use fuchsia_zircon as zx;
use anyhow::{anyhow, Context as _};
use async_trait::async_trait;
use derivative::Derivative;
use futures::{channel::oneshot, future, FutureExt as _, StreamExt as _, TryStreamExt as _};
use tracing::{debug, error, info, warn};
use crate::{
errors::{self, ContextExt as _},
DeviceClass,
};
/// Wrapper around a [`fnet_virtualization::NetworkRequest`] that allows for the
/// server to notify all existing interfaces to shut down when the client closes
/// the `Network` channel.
#[derive(Debug)]
pub(super) enum NetworkRequest {
Request(fnet_virtualization::NetworkRequest, future::Shared<oneshot::Receiver<()>>),
Finished(oneshot::Sender<()>),
}
#[derive(Derivative)]
#[derivative(Debug)]
pub(super) enum Event {
ControlRequestStream(#[derivative(Debug = "ignore")] fnet_virtualization::ControlRequestStream),
ControlRequest(fnet_virtualization::ControlRequest),
NetworkRequest(NetworkRequest),
InterfaceClose(u64),
}
pub(super) type EventStream = Pin<Box<dyn futures::stream::Stream<Item = Event>>>;
// TODO(https://fxbug.dev/88017): `guests` must always be non-empty. Explore using a non-empty set
// type to encode this invariant in the type system.
enum BridgeState {
Init,
WaitingForGuests {
// Invariants: `upstream` is not present in `upstream_candidates`, and
// must be online.
upstream: u64,
upstream_candidates: HashSet<u64>,
},
WaitingForUpstream {
guests: HashSet<u64>,
},
Bridged {
bridge_id: u64,
// Invariant: `upstream` is not present in `upstream_candidates`.
//
// Note that `upstream` going offline does not cause a state transition unlike
// `WaitingForGuests`, and thus `upstream` may be offline.
upstream: u64,
upstream_candidates: HashSet<u64>,
guests: HashSet<u64>,
},
}
impl Default for BridgeState {
fn default() -> Self {
BridgeState::Init
}
}
#[async_trait(?Send)]
pub(super) trait Handler {
async fn handle_event(
&'async_trait mut self,
event: Event,
events: &'async_trait mut futures::stream::SelectAll<EventStream>,
) -> Result<(), errors::Error>;
async fn handle_interface_update_result(
&mut self,
update_result: &fnet_interfaces_ext::UpdateResult<'_>,
) -> Result<(), errors::Error>;
}
// TODO(https://github.com/rust-lang/rust/issues/59618): Implement using
// `drain_filter` to avoid the Copy trait bound.
// Takes a single element from `set` if `set` is non-empty.
fn take_any<T: std::marker::Copy + std::cmp::Eq + std::hash::Hash>(
set: &mut HashSet<T>,
) -> Option<T> {
set.iter().copied().next().map(|elem| {
assert!(set.remove(&elem));
elem
})
}
pub(super) struct Virtualization<B: BridgeHandler> {
installer: fnet_interfaces_admin::InstallerProxy,
allowed_upstream_device_classes: HashSet<DeviceClass>,
bridge_handler: B,
bridge_state: BridgeState,
}
impl<B: BridgeHandler> Virtualization<B> {
pub fn new(
allowed_upstream_device_classes: HashSet<DeviceClass>,
bridge_handler: B,
installer: fnet_interfaces_admin::InstallerProxy,
) -> Self {
Self {
installer,
allowed_upstream_device_classes,
bridge_handler,
bridge_state: Default::default(),
}
}
fn is_device_class_allowed_for_upstream(
&self,
device_class: fnet_interfaces::DeviceClass,
) -> bool {
match device_class {
fnet_interfaces::DeviceClass::Device(device_class) => {
self.allowed_upstream_device_classes.contains(&device_class.into())
}
fnet_interfaces::DeviceClass::Loopback(fnet_interfaces::Empty {}) => false,
}
}
async fn handle_control_request(
&self,
request: fnet_virtualization::ControlRequest,
events: &mut futures::stream::SelectAll<EventStream>,
) -> Result<(), errors::Error> {
match request {
fnet_virtualization::ControlRequest::CreateNetwork {
config,
network,
control_handle: _,
} => match config {
fnet_virtualization::Config::Bridged(fnet_virtualization::Bridged { .. }) => {
info!("got a request to create a bridged network");
// Create a oneshot channel we can use when the `Network` channel is closed, to
// notify each interface task to close its corresponding `Interface` channel as
// well.
let (close_channel_tx, close_channel_rx) = oneshot::channel();
let close_channel_rx = close_channel_rx.shared();
let stream = network
.into_stream()
.expect("convert server end into stream")
.filter_map(move |request| {
future::ready(match request {
Ok(request) => {
Some(NetworkRequest::Request(request, close_channel_rx.clone()))
}
Err(e) => {
error!("network request error: {:?}", e);
None
}
})
})
.chain(futures::stream::once(futures::future::ready(
NetworkRequest::Finished(close_channel_tx),
)));
events.push(stream.map(Event::NetworkRequest).boxed());
}
config => panic!("unsupported network config type {:?}", config),
},
}
Ok(())
}
async fn handle_network_request(
&mut self,
request: NetworkRequest,
events: &mut futures::stream::SelectAll<EventStream>,
) -> Result<(), errors::Error> {
match request {
NetworkRequest::Request(request, mut network_close_rx) => {
match request {
fnet_virtualization::NetworkRequest::AddPort {
port,
interface,
control_handle: _,
} => {
// TODO(https://fxbug.dev/87111): send a terminal event on the channel if
// the device could not be added to the network due to incompatibility; for
// example, if the network is bridged and the device does not support the
// same L2 protocol as other devices on the bridge.
// Get the device this port belongs to, and install it on the netstack.
let (device, server_end) =
fidl::endpoints::create_endpoints::<fhardware_network::DeviceMarker>()
.context("create endpoints")
.map_err(errors::Error::NonFatal)?;
let port = port.into_proxy().expect("client end into proxy");
port.get_device(server_end)
.context("call get device")
.map_err(errors::Error::NonFatal)?;
let (device_control, server_end) = fidl::endpoints::create_proxy::<
fnet_interfaces_admin::DeviceControlMarker,
>()
.context("create proxy")
.map_err(errors::Error::NonFatal)?;
self.installer
.install_device(device, server_end)
.context("call install device")
.map_err(errors::Error::Fatal)?;
// Create an interface on the device, and enable it.
let fhardware_network::PortInfo { id: port_id, .. } = port
.get_info()
.await
.context("get port info")
.map_err(errors::Error::NonFatal)?;
let mut port_id = port_id
.context("port id not included in port info")
.map_err(errors::Error::NonFatal)?;
let (control, server_end) =
fnet_interfaces_ext::admin::Control::create_endpoints()
.context("create Control endpoints")
.map_err(errors::Error::NonFatal)?;
device_control
.create_interface(
&mut port_id,
server_end,
fnet_interfaces_admin::Options {
..fnet_interfaces_admin::Options::EMPTY
},
)
.context("call create interface")
.map_err(errors::Error::NonFatal)?;
let id = control
.get_id()
.await
.context("call get id")
.map_err(errors::Error::NonFatal)?;
if !control
.enable()
.await
.context("call enable")
.map_err(errors::Error::NonFatal)?
.map_err(|e| anyhow!("failed to enable interface: {:?}", e))
.map_err(errors::Error::NonFatal)?
{
warn!("added interface {} was already enabled", id);
}
// Add this interface to the existing bridge, or create one if none exists.
self.add_guest_to_bridge(id).await.context("adding interface to bridge")?;
// Wait for a signal that this interface should be removed from the bridge
// and the virtual network.
let shutdown_fut = async move {
let mut interface_closure = interface
.into_stream()
.expect("convert server end into stream")
.map(|request| {
// `fuchsia.net.virtualization/Interface` is a protocol with no
// methods, so `InterfaceRequest` is an uninstantiable enum.
// This prevents us from exhaustively matching on its variants,
// so we just drop the request here.
request
.map(|_request: fnet_virtualization::InterfaceRequest| ())
})
.try_collect::<()>();
let mut device_control_closure = device_control.on_closed().fuse();
let control_termination = control.wait_termination().fuse();
futures::pin_mut!(control_termination);
let reason = futures::select! {
// The interface channel has been closed by the client.
result = interface_closure => {
format!("interface channel closed by client: {:?}", result)
},
// The device has been detached from the netstack.
result = device_control_closure => {
match result {
Ok(zx::Signals::CHANNEL_PEER_CLOSED) => {},
result => error!(
"got unexpected result waiting for device control \
channel closure: {:?}",
result,
),
}
"device detached from netstack".to_string()
}
// The virtual network has been shut down and is notifying us to
// remove the interface.
result = network_close_rx => {
let () = result.expect("sender should not be dropped");
"network has been shut down".to_string()
},
// A terminal event was sent on the interface control channel,
// signaling that the interface was removed.
terminal_error = control_termination => {
format!(
"interface control channel closed: {:?}",
terminal_error
)
}
};
info!("interface {}: {}, removing interface", id, reason);
id
};
events.push(
futures::stream::once(shutdown_fut.map(Event::InterfaceClose)).boxed(),
);
}
}
}
NetworkRequest::Finished(network_close_tx) => {
// Close down the network.
match network_close_tx.send(()) {
Ok(()) => {}
Err(()) => {
info!("removing virtualized network with no devices attached")
}
}
}
}
Ok(())
}
async fn add_guest_to_bridge(&mut self, id: u64) -> Result<(), errors::Error> {
info!("got a request to add interface {} to bridge", id);
let Self { bridge_state, bridge_handler, .. } = self;
*bridge_state = match std::mem::take(bridge_state) {
BridgeState::Init => BridgeState::WaitingForUpstream { guests: HashSet::from([id]) },
// If a bridge doesn't exist, but we have an interface with upstream connectivity,
// create the bridge.
BridgeState::WaitingForGuests { upstream, upstream_candidates } => {
let guests = HashSet::from([id]);
let bridge_id = bridge_handler
.build_bridge(guests.iter().copied(), upstream)
.await
.context("building bridge")?;
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
}
// If a bridge doesn't exist, and we don't yet have an interface with upstream
// connectivity, just keep track of the interface to be bridged, so we can eventually
// include it in the bridge.
BridgeState::WaitingForUpstream { mut guests } => {
assert!(guests.insert(id));
// No change to bridge state.
BridgeState::WaitingForUpstream { guests }
}
// If a bridge already exists, tear it down and create a new one, re-using the interface
// that has upstream connectivity and including all the interfaces that were bridged
// previously.
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, mut guests } => {
bridge_handler.destroy_bridge(bridge_id).await.context("destroying bridge")?;
assert!(guests.insert(id));
let bridge_id = bridge_handler
.build_bridge(guests.iter().copied(), upstream)
.await
.context("building bridge")?;
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
}
};
Ok(())
}
async fn remove_guest_from_bridge(&mut self, id: u64) -> Result<(), errors::Error> {
info!("got a request to remove interface {} from bridge", id);
let Self { bridge_state, bridge_handler, .. } = self;
*bridge_state = match std::mem::take(bridge_state) {
BridgeState::Init | BridgeState::WaitingForGuests { .. } => {
panic!("cannot remove guest interface {} since it was not previously added", id)
}
BridgeState::WaitingForUpstream { mut guests } => {
assert!(guests.remove(&id));
if guests.is_empty() {
BridgeState::Init
} else {
// No change to bridge state.
BridgeState::WaitingForUpstream { guests }
}
}
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, mut guests } => {
bridge_handler.destroy_bridge(bridge_id).await.context("destroying bridge")?;
assert!(guests.remove(&id));
if guests.is_empty() {
BridgeState::WaitingForGuests { upstream, upstream_candidates }
} else {
let bridge_id = self
.bridge_handler
.build_bridge(guests.iter().copied(), upstream)
.await
.context("building bridge")?;
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
}
}
};
Ok(())
}
async fn handle_interface_online(
&mut self,
id: u64,
allowed_for_upstream: bool,
) -> Result<(), errors::Error> {
info!("interface {} (allowed for upstream: {}) is online", id, allowed_for_upstream);
let Self { bridge_state, bridge_handler, .. } = self;
*bridge_state = match std::mem::take(bridge_state) {
BridgeState::Init => {
if allowed_for_upstream {
BridgeState::WaitingForGuests {
upstream: id,
upstream_candidates: Default::default(),
}
} else {
BridgeState::Init
}
}
BridgeState::WaitingForGuests { upstream, mut upstream_candidates } => {
if allowed_for_upstream {
assert_ne!(
upstream, id,
"interface {} expected to provide upstream but was offline and came online",
id
);
assert!(
upstream_candidates.insert(id),
"upstream candidate {} already present",
id
);
}
BridgeState::WaitingForGuests { upstream, upstream_candidates }
}
BridgeState::WaitingForUpstream { guests } => {
if allowed_for_upstream && !guests.contains(&id) {
// We don't already have an upstream interface that provides connectivity. Build
// a bridge with this one.
let bridge_id = bridge_handler
.build_bridge(guests.iter().copied(), id)
.await
.context("building bridge")?;
BridgeState::Bridged {
bridge_id,
upstream: id,
upstream_candidates: Default::default(),
guests,
}
} else {
BridgeState::WaitingForUpstream { guests }
}
}
// If a bridge already exists, tear it down and create a new one, using this new
// interface to provide upstream connectivity, and including all the interfaces that
// were bridged previously.
BridgeState::Bridged { bridge_id, upstream, mut upstream_candidates, guests } => {
if id == upstream {
info!("upstream-providing interface {} went online", id);
} else if id == bridge_id {
info!("bridge interface {} went online", bridge_id);
} else if !guests.contains(&id) && allowed_for_upstream {
assert!(
upstream_candidates.insert(id),
"upstream candidate {} already present",
id
);
}
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
}
};
Ok(())
}
async fn handle_interface_offline(
&mut self,
id: u64,
allowed_for_upstream: bool,
) -> Result<(), errors::Error> {
info!("interface {} (allowed for upstream: {}) is offline", id, allowed_for_upstream);
let Self { bridge_state, .. } = self;
*bridge_state = match std::mem::take(bridge_state) {
BridgeState::Init => BridgeState::Init,
BridgeState::WaitingForUpstream { guests } => {
BridgeState::WaitingForUpstream { guests }
}
BridgeState::Bridged { bridge_id, upstream, mut upstream_candidates, guests } => {
if id == bridge_id {
warn!("bridge interface {} went offline", id);
} else if id == upstream {
// We currently ignore the situation where an interface that is providing
// upstream connectivity changes from online to offline. The only signal
// that causes us to destroy an existing bridge is if the interface providing
// upstream connectivity is removed entirely.
warn!("upstream interface {} went offline", id);
} else if !guests.contains(&id) && allowed_for_upstream {
assert!(upstream_candidates.remove(&id), "upstream candidate {} not found", id);
}
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
}
BridgeState::WaitingForGuests { upstream, mut upstream_candidates } => {
if id == upstream {
match take_any(&mut upstream_candidates) {
Some(id) => {
BridgeState::WaitingForGuests { upstream: id, upstream_candidates }
}
None => BridgeState::Init,
}
} else {
if allowed_for_upstream {
assert!(
upstream_candidates.remove(&id),
"upstream candidate {} not found",
id
);
}
BridgeState::WaitingForGuests { upstream, upstream_candidates }
}
}
};
Ok(())
}
async fn handle_interface_removed(&mut self, removed_id: u64) -> Result<(), errors::Error> {
info!("interface {} removed", removed_id);
let Self { bridge_state, bridge_handler, .. } = self;
*bridge_state = match std::mem::take(bridge_state) {
BridgeState::Init => BridgeState::Init,
BridgeState::WaitingForUpstream { guests } => {
if guests.contains(&removed_id) {
// Removal from the `guests` map will occur when the guest removal is
// actually handled in `remove_guest_from_bridge`.
info!("guest interface {} removed", removed_id);
}
BridgeState::WaitingForUpstream { guests }
}
BridgeState::WaitingForGuests { upstream, mut upstream_candidates } => {
if upstream == removed_id {
match take_any(&mut upstream_candidates) {
Some(new_upstream_id) => BridgeState::WaitingForGuests {
upstream: new_upstream_id,
upstream_candidates,
},
None => BridgeState::Init,
}
} else {
let _: bool = upstream_candidates.remove(&removed_id);
BridgeState::WaitingForGuests { upstream, upstream_candidates }
}
}
BridgeState::Bridged { bridge_id, upstream, mut upstream_candidates, guests } => {
if guests.contains(&removed_id) {
// Removal from the `guests` map will occur when the guest removal is
// actually handled in `remove_guest_from_bridge`.
info!("guest interface {} removed", removed_id);
}
if bridge_id == removed_id {
// The bridge interface installed by netcfg should not be removed by any other
// entity.
error!("bridge interface {} removed; rebuilding", bridge_id);
let bridge_id = self
.bridge_handler
.build_bridge(guests.iter().copied(), upstream)
.await
.context("building bridge")?;
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
} else if upstream == removed_id {
bridge_handler.destroy_bridge(bridge_id).await.context("destroying bridge")?;
match take_any(&mut upstream_candidates) {
Some(new_upstream_id) => {
let bridge_id = self
.bridge_handler
.build_bridge(guests.iter().copied(), new_upstream_id)
.await
.context("building bridge")?;
BridgeState::Bridged {
bridge_id,
upstream: new_upstream_id,
upstream_candidates,
guests,
}
}
None => BridgeState::WaitingForUpstream { guests },
}
} else {
let _: bool = upstream_candidates.remove(&removed_id);
BridgeState::Bridged { bridge_id, upstream, upstream_candidates, guests }
}
}
};
Ok(())
}
}
#[async_trait(?Send)]
impl<B: BridgeHandler> Handler for Virtualization<B> {
async fn handle_event(
&'async_trait mut self,
event: Event,
events: &'async_trait mut futures::stream::SelectAll<EventStream>,
) -> Result<(), errors::Error> {
match event {
Event::ControlRequestStream(stream) => {
events.push(
stream
.filter_map(|request| {
future::ready(match request {
Ok(request) => Some(Event::ControlRequest(request)),
Err(e) => {
error!("control request error: {:?}", e);
None
}
})
})
.boxed(),
);
}
Event::ControlRequest(request) => self
.handle_control_request(request, events)
.await
.context("handle control request")?,
Event::NetworkRequest(request) => self
.handle_network_request(request, events)
.await
.context("handle network request")?,
Event::InterfaceClose(id) => {
// Remove this interface from the existing bridge.
self.remove_guest_from_bridge(id)
.await
.context("removing interface from bridge")?;
}
}
Ok(())
}
async fn handle_interface_update_result(
&mut self,
update_result: &fnet_interfaces_ext::UpdateResult<'_>,
) -> Result<(), errors::Error> {
match update_result {
fnet_interfaces_ext::UpdateResult::Added(properties)
| fnet_interfaces_ext::UpdateResult::Existing(properties) => {
let fnet_interfaces_ext::Properties { id, online, device_class, .. } = **properties;
let allowed_for_upstream = self.is_device_class_allowed_for_upstream(device_class);
if online {
self.handle_interface_online(id, allowed_for_upstream)
.await
.context("handle new interface online")?;
}
}
fnet_interfaces_ext::UpdateResult::Changed {
previous: fnet_interfaces::Properties { online: previously_online, .. },
current: current_properties,
} => {
let fnet_interfaces_ext::Properties { id, online, device_class, .. } =
**current_properties;
let allowed_for_upstream = self.is_device_class_allowed_for_upstream(device_class);
match (*previously_online, online) {
(Some(false), true) => {
self.handle_interface_online(id, allowed_for_upstream)
.await
.context("handle interface online")?;
}
(Some(true), false) => {
self.handle_interface_offline(id, allowed_for_upstream)
.await
.context("handle interface offline")?;
}
(Some(true), true) | (Some(false), false) => {
error!("interface {} changed event indicates no actual change to online ({} before and after)", id, online);
}
// Online did not change; do nothing.
(None, true) => {}
(None, false) => {}
}
}
fnet_interfaces_ext::UpdateResult::Removed(fnet_interfaces_ext::Properties {
id,
..
}) => {
self.handle_interface_removed(*id).await.context("handle interface removed")?;
}
fnet_interfaces_ext::UpdateResult::NoChange => {}
}
Ok(())
}
}
pub(super) struct Stub;
#[async_trait(?Send)]
impl Handler for Stub {
async fn handle_event(
&'async_trait mut self,
event: Event,
_events: &'async_trait mut futures::stream::SelectAll<EventStream>,
) -> Result<(), errors::Error> {
panic!("stub handler requested to handle a virtualization event: {:#?}", event)
}
async fn handle_interface_update_result(
&mut self,
_update_result: &fnet_interfaces_ext::UpdateResult<'_>,
) -> Result<(), errors::Error> {
Ok(())
}
}
/// An abstraction over the logic involved to instruct the netstack to create or destroy a bridge.
///
/// Allows for testing the virtualization handler by providing an instrumented implementation of
/// `BridgeHandler` in order to observe its behavior.
#[async_trait(?Send)]
pub(super) trait BridgeHandler {
async fn build_bridge(
&self,
interfaces: impl Iterator<Item = u64> + 'async_trait,
upstream_interface: u64,
) -> Result<u64, errors::Error>;
async fn destroy_bridge(&self, id: u64) -> Result<(), errors::Error>;
}
pub(super) struct BridgeHandlerImpl {
stack: fnet_stack::StackProxy,
netstack: fnetstack::NetstackProxy,
debug: fnet_debug::InterfacesProxy,
}
impl BridgeHandlerImpl {
pub fn new(
stack: fnet_stack::StackProxy,
netstack: fnetstack::NetstackProxy,
debug: fnet_debug::InterfacesProxy,
) -> Self {
Self { stack, netstack, debug }
}
// Starts a DHCPv4 client.
async fn start_dhcpv4_client(&self, bridge_id: u32) -> Result<(), errors::Error> {
let (dhcp_client, server_end) = fidl::endpoints::create_proxy::<fnet_dhcp::ClientMarker>()
.context("create dhcp::Client endpoints")
.map_err(errors::Error::NonFatal)?;
let () = self
.netstack
.get_dhcp_client(bridge_id, server_end)
.await
.context("call get DHCPv4 client")
.map_err(errors::Error::Fatal)?
.map_err(zx::Status::from_raw)
.context("failed to get DHCPv4 client")
.map_err(errors::Error::NonFatal)?;
dhcp_client
.start()
.await
.context("call start on DHCPv4 client")
.map_err(errors::Error::Fatal)?
.map_err(zx::Status::from_raw)
.context("failed to start DHCPv4 client")
.map_err(errors::Error::NonFatal)
}
}
#[async_trait(?Send)]
impl BridgeHandler for BridgeHandlerImpl {
async fn build_bridge(
&self,
interfaces: impl Iterator<Item = u64> + 'async_trait,
upstream_interface: u64,
) -> Result<u64, errors::Error> {
let bridge_id = {
let interfaces: Vec<u32> = interfaces
.chain(std::iter::once(upstream_interface))
.map(u32::try_from)
.collect::<Result<Vec<_>, _>>()
.context("convert NIC IDs to u32")
.map_err(errors::Error::Fatal)?;
info!(
"building bridge with upstream={}, interfaces={:?}",
upstream_interface, interfaces
);
self.netstack
.bridge_interfaces(&interfaces)
.await
.map_err(anyhow::Error::new)
.and_then(|result| match result {
fnetstack::Result_::Message(message) => Err(anyhow::Error::msg(message)),
fnetstack::Result_::Nicid(id) => Ok(id),
})
.with_context(|| format!("could not bridge interfaces ({:?})", interfaces))
.map_err(errors::Error::Fatal)?
};
// Start a DHCPv4 client.
match self.start_dhcpv4_client(bridge_id).await {
Ok(()) => {}
Err(errors::Error::NonFatal(e)) => {
error!("failed to start DHCPv4 client on bridge: {}", e)
}
Err(errors::Error::Fatal(e)) => return Err(errors::Error::Fatal(e)),
}
let bridge_id = u64::from(bridge_id);
// Enable the bridge we just created.
let (control, server_end) = fnet_interfaces_ext::admin::Control::create_endpoints()
.context("create Control endpoints")
.map_err(errors::Error::NonFatal)?;
self.debug
.get_admin(bridge_id, server_end)
.context("call get admin")
.map_err(errors::Error::Fatal)?;
let did_enable = control
.enable()
.await
.context("call enable")
.map_err(errors::Error::Fatal)?
// If we created a bridge but the interface wasn't successfully enabled, the bridging
// state machine has become inconsistent with the netstack, so we return an
// unrecoverable error.
.map_err(|e| anyhow!("failed to enable interface: {:?}", e))
.map_err(errors::Error::Fatal)?;
assert!(
did_enable,
"the bridge should have been disabled on creation and then enabled by Control.Enable",
);
debug!("enabled bridge interface {}", bridge_id);
Ok(bridge_id)
}
async fn destroy_bridge(&self, id: u64) -> Result<(), errors::Error> {
debug!("tearing down bridge with id {}", id);
self.stack
.del_ethernet_interface(id)
.await
.context("call del_ethernet_interface")
.map_err(errors::Error::Fatal)?
// If the netstack failed to destroy the bridge, the bridging state machine has become
// inconsistent with the netstack, so we return an unrecoverable error.
//
// NB: This does mean that if the bridge interface was manually removed out from under
// netcfg, we'll panic.
.map_err(|e| anyhow!("failed to delete ethernet interface: {:?}", e))
.map_err(errors::Error::Fatal)
}
}
#[cfg(test)]
mod tests {
use futures::{channel::mpsc, SinkExt as _};
use test_case::test_case;
use super::*;
#[derive(Copy, Clone, Debug, PartialEq)]
enum Guest {
A,
B,
}
impl Guest {
fn id(&self) -> u64 {
match self {
Self::A => 1,
Self::B => 2,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum Upstream {
A,
B,
}
impl Upstream {
fn id(&self) -> u64 {
match self {
Self::A => 11,
Self::B => 12,
}
}
}
#[derive(Debug, PartialEq)]
enum BridgeEvent {
Destroyed,
Created { interfaces: HashSet<u64>, upstream_interface: u64 },
}
impl BridgeEvent {
fn created(interfaces: Vec<Guest>, upstream_interface: Upstream) -> Self {
Self::Created {
interfaces: interfaces.iter().map(Guest::id).collect(),
upstream_interface: upstream_interface.id(),
}
}
fn destroyed() -> Self {
Self::Destroyed
}
}
struct BridgeHandlerTestImplInner {
bridge: Option<u64>,
events: mpsc::Sender<BridgeEvent>,
}
struct BridgeHandlerTestImpl {
inner: std::cell::RefCell<BridgeHandlerTestImplInner>,
}
impl BridgeHandlerTestImpl {
fn new(events: mpsc::Sender<BridgeEvent>) -> Self {
BridgeHandlerTestImpl {
inner: std::cell::RefCell::new(BridgeHandlerTestImplInner { bridge: None, events }),
}
}
}
#[async_trait(?Send)]
impl BridgeHandler for BridgeHandlerTestImpl {
async fn destroy_bridge(&self, id: u64) -> Result<(), errors::Error> {
let BridgeHandlerTestImplInner { bridge, events } = &mut *self.inner.borrow_mut();
assert_eq!(*bridge, Some(id), "cannot destroy a non-existent bridge");
*bridge = None;
events.send(BridgeEvent::Destroyed).await.expect("send event");
Ok(())
}
async fn build_bridge(
&self,
interfaces: impl Iterator<Item = u64> + 'async_trait,
upstream_interface: u64,
) -> Result<u64, errors::Error> {
let BridgeHandlerTestImplInner { bridge, events } = &mut *self.inner.borrow_mut();
assert_eq!(
*bridge, None,
"cannot create a bridge since there is already an existing bridge",
);
const BRIDGE_IF: u64 = 99;
*bridge = Some(BRIDGE_IF);
events
.send(BridgeEvent::Created { interfaces: interfaces.collect(), upstream_interface })
.await
.expect("send event");
Ok(BRIDGE_IF)
}
}
enum Action {
AddGuest(Guest),
RemoveGuest(Guest),
UpstreamOnline(Upstream),
UpstreamOffline(Upstream),
RemoveUpstream(Upstream),
}
#[test_case(
// Verify that we wait to create a bridge until an interface is added to a virtual bridged
// network.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
];
"wait for guest"
)]
#[test_case(
// Verify that we wait to create a bridge until there is an interface that provides upstream
// connectivity.
[
(Action::AddGuest(Guest::A), vec![]),
(
Action::UpstreamOnline(Upstream::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
];
"wait for upstream"
)]
#[test_case(
// Verify that the bridge is destroyed when the upstream interface is removed and there are
// no more candidates to provide upstream connectivity.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(Action::RemoveUpstream(Upstream::A), vec![BridgeEvent::destroyed()]),
];
"destroy bridge when no upstream"
)]
#[test_case(
// Verify that when we add multiple interfaces to the virtual network, they are added to the
// bridge one by one, which is implemented by tearing down the bridge and rebuilding it
// every time an interface is added or removed.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(
Action::AddGuest(Guest::B),
vec![
BridgeEvent::destroyed(),
BridgeEvent::created([Guest::A, Guest::B].into(), Upstream::A),
],
),
(
Action::RemoveGuest(Guest::B),
vec![
BridgeEvent::destroyed(),
BridgeEvent::created([Guest::A].into(), Upstream::A),
],
),
(
Action::RemoveGuest(Guest::A),
vec![BridgeEvent::destroyed()],
),
];
"multiple interfaces"
)]
#[test_case(
// Verify that even if all guests on a network are removed and the bridge is therefore
// destroyed, if an interface is added again, the bridge is re-created with the same
// upstream.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(Action::RemoveGuest(Guest::A), vec![BridgeEvent::destroyed()]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
];
"remember upstream"
)]
#[test_case(
// Verify that the handler keeps track of which interfaces are added and removed to the
// bridged network even when there is no existing bridge due to a lack of upstream
// connectivity.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(Action::RemoveUpstream(Upstream::A), vec![BridgeEvent::destroyed()]),
(Action::AddGuest(Guest::B), vec![]),
(Action::RemoveGuest(Guest::A), vec![]),
(
Action::UpstreamOnline(Upstream::A),
vec![BridgeEvent::created([Guest::B].into(), Upstream::A)],
),
];
"remember guest interfaces"
)]
#[test_case(
// Verify that the bridge is destroyed when upstream is removed.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(Action::RemoveUpstream(Upstream::A), vec![BridgeEvent::destroyed()]),
];
"remove upstream"
)]
#[test_case(
// Verify that the upstream-providing interface going offline while a
// bridge is present does not cause the bridge to be destroyed.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(Action::UpstreamOffline(Upstream::A), vec![]),
];
"upstream offline not removed"
)]
#[test_case(
// Verify that an otherwise eligible but offline upstream interface is
// not used to create a bridge.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(Action::UpstreamOffline(Upstream::A), vec![]),
(Action::AddGuest(Guest::A), vec![]),
(
Action::UpstreamOnline(Upstream::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
];
"do not bridge with offline upstream"
)]
#[test_case(
// Verify that when we replace the interface providing upstream connectivity with another
// interface, the bridge is correctly destroyed and recreated with the new upstream.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::A)],
),
(Action::UpstreamOnline(Upstream::B), vec![]),
(
Action::RemoveUpstream(Upstream::A),
vec![
BridgeEvent::destroyed(),
BridgeEvent::created([Guest::A].into(), Upstream::B),
],
),
];
"replace upstream"
)]
#[test_case(
// Verify that upstream-providing interface changes are tracked even when there are no
// guests yet.
[
(Action::UpstreamOnline(Upstream::A), vec![]),
(Action::UpstreamOnline(Upstream::B), vec![]),
(Action::UpstreamOffline(Upstream::A), vec![]),
(
Action::AddGuest(Guest::A),
vec![BridgeEvent::created([Guest::A].into(), Upstream::B)],
),
];
"replace upstream with no guests"
)]
#[fuchsia_async::run_singlethreaded(test)]
async fn bridge(steps: impl IntoIterator<Item = (Action, Vec<BridgeEvent>)>) {
// At most 2 events will need to be sent before the test can process them: in the case that
// a bridge is modified, the bridge is destroyed and then built again.
let (events_tx, mut events_rx) = mpsc::channel(2);
let (installer, _installer_server) =
fidl::endpoints::create_proxy::<fnet_interfaces_admin::InstallerMarker>()
.expect("create endpoints");
let mut handler =
Virtualization::new(HashSet::new(), BridgeHandlerTestImpl::new(events_tx), installer);
for (action, expected_events) in steps {
match action {
Action::AddGuest(guest) => {
handler.add_guest_to_bridge(guest.id()).await.expect("add guest to bridge");
}
Action::RemoveGuest(guest) => {
handler
.remove_guest_from_bridge(guest.id())
.await
.expect("remove guest from bridge");
}
Action::UpstreamOnline(upstream) => {
handler
.handle_interface_online(upstream.id(), true)
.await
.expect("upstream interface online");
}
Action::UpstreamOffline(upstream) => {
handler
.handle_interface_offline(upstream.id(), true)
.await
.expect("upstream interface offline");
}
Action::RemoveUpstream(upstream) => {
handler
.handle_interface_removed(upstream.id())
.await
.expect("upstream interface removed");
}
}
for event in expected_events {
assert_eq!(events_rx.next().await.expect("receive event"), event);
}
let _: mpsc::TryRecvError =
events_rx.try_next().expect_err("got unexpected bridge event");
}
}
}