| // Copyright 2018 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. |
| |
| //! The special-purpose event loop used by the recovery netstack. |
| //! |
| //! This event loop takes in events from all sources (currently ethernet devices, FIDL, and |
| //! timers), and handles them appropriately. It dispatches ethernet and timer events to the netstack |
| //! core, and implements handlers for FIDL calls. |
| //! |
| //! This is implemented with a single mpsc queue for all event types - `EventLoop` holds the |
| //! consumer, and any event handling that requires state within `EventLoop` holds a producer, |
| //! allowing it to delegate work to the `EventLoop` by sending a message. In this documentation, we |
| //! call anything that holds a producer a "worker". |
| //! |
| //! Having a single queue for all of the message types is beneficial, since it guarantees a FIFO |
| //! ordering for all messages - whichever messages arrive first, will be handled first. |
| //! |
| //! We'll look at each type of message, to see how each one is handled - starting with FIDL |
| //! messages, since they can be thought of as the entry point for the whole loop (as nothing happens |
| //! until a FIDL call is made). |
| //! |
| //! # FIDL Worker |
| //! |
| //! The FIDL part of the event loop implements the fuchsia.net.stack.Stack and |
| //! fuchsia.posix.socket.Provider interfaces. The type of the event loop message for a FIDL call is |
| //! simply the generated FIDL type. When the event loop starts up, we use `fuchsia_component` to |
| //! start a FIDL server that simply sends all of the events it receives to the event loop |
| //! (via the sender end of the mpsc queue). |
| //! |
| //! When `EventLoop` receives this message, it calls the |
| //! `handle_fidl_stack_request` or `handle_fidl_socket_provider_request` method, which, depending |
| //! on what the request is, either: |
| //! |
| //! * Responds with the requested information. |
| //! * Modifies the state of the netstack in the requested way. |
| //! * Adds a new ethernet device to the event loop. |
| //! |
| //! Of these, only the last one is really interesting from the perspective of how the event loop |
| //! functions - when we add a new ethernet device, we spawn a new worker to handle ethernet setup. |
| //! |
| //! # Ethernet Setup Worker |
| //! |
| //! The `EthernetSetupWorker` creates an ethernet client, and sends an `EthernetDeviceReady` |
| //! message to the event loop when the device is ready. This message contains the newly-ready |
| //! ethernet client, some data about the client, and the handler to respond to the FIDL call |
| //! requesting that this device be added. |
| //! |
| //! When `EventLoop` receives a `EthernetDeviceReady` message, it assigns the ethernet device an ID |
| //! number, adds the ethernet client to it's list of clients, and spawns an `EthernetWorker`. |
| //! |
| //! # Ethernet Worker |
| //! |
| //! The ethernet worker simply waits for ethernet messages (either new frames or status changes), |
| //! and sends a message to the event loop when an ethernet message comes in. The event loop, upon |
| //! receiving such a message, forwards it to the netstack core. |
| //! |
| //! # Timers |
| //! |
| //! The logic for timers lives in the `EventLoopInner`. Upon a timer being set, `EventLoopInner` |
| //! spawns a cancellable future, scheduled to send a message to the event loop when the timer fires. |
| //! Upon receiving the message, the event loop calls the dispatcher function in the netstack core, |
| //! which triggers the correct action in response to the timer. The future that the |
| //! `EventLoopInner` spawns can be thought of as a sort of "Timer Worker". |
| //! |
| //! The mpsc queue design was chosen, in large part, to allow the `EventLoopInner` to set a timer |
| //! without requiring access to the full netstack state - instead the future that the |
| //! `schedule_timeout` function spawns can delegate performing actions that require the full |
| //! netstack state to the outer `EventLoop` by sending a message. However, this does come with a few |
| //! drawbacks - notably, it can be difficult to reason about what exactly the behavior of the |
| //! timers is - see the comment below on race conditions. Particularly, it's a bit tricky that the |
| //! timer is not cancelled when the timer trigger message is _sent_, but when it is _received_. |
| |
| #![allow(unused)] |
| |
| #[cfg(test)] |
| mod integration_tests; |
| mod socket; |
| mod timers; |
| mod util; |
| |
| use ethernet as eth; |
| use fuchsia_async::{self as fasync, DurationExt}; |
| use fuchsia_zircon as zx; |
| |
| use std::convert::TryFrom; |
| use std::fs::File; |
| use std::marker::PhantomData; |
| use std::sync::{Arc, Mutex}; |
| use std::time::Duration; |
| |
| use failure::{bail, format_err, Error}; |
| use fidl::endpoints::{ClientEnd, RequestStream, ServiceMarker}; |
| use fidl_fuchsia_hardware_ethernet as fidl_ethernet; |
| use fidl_fuchsia_hardware_ethernet_ext::{EthernetInfo, EthernetStatus, MacAddress}; |
| use fidl_fuchsia_io; |
| use fidl_fuchsia_net as fidl_net; |
| use fidl_fuchsia_net_stack as fidl_net_stack; |
| use fidl_fuchsia_net_stack::{ |
| AdministrativeStatus, ForwardingEntry, InterfaceAddress, InterfaceInfo, InterfaceProperties, |
| PhysicalStatus, StackAddEthernetInterfaceResponder, StackAddForwardingEntryResponder, |
| StackAddInterfaceAddressResponder, StackDelEthernetInterfaceResponder, |
| StackDelForwardingEntryResponder, StackDelInterfaceAddressResponder, |
| StackDisableInterfaceResponder, StackEnableInterfaceResponder, |
| StackGetForwardingTableResponder, StackGetInterfaceInfoResponder, StackListInterfacesResponder, |
| StackMarker, StackRequest, StackRequestStream, |
| }; |
| use fidl_fuchsia_posix_socket as psocket; |
| use fidl_fuchsia_posix_socket::ProviderRequest; |
| use futures::channel::mpsc; |
| use futures::future::{AbortHandle, Abortable}; |
| use futures::prelude::*; |
| use futures::{select, TryFutureExt, TryStreamExt}; |
| #[cfg(test)] |
| use integration_tests::TestEvent; |
| use log::{debug, error, info, trace}; |
| use net_types::ethernet::Mac; |
| use net_types::ip::{AddrSubnet, AddrSubnetEither, IpAddr, IpVersion, Subnet, SubnetEither}; |
| use packet::{Buf, BufferMut, Serializer}; |
| use rand::{rngs::OsRng, Rng}; |
| use std::convert::TryInto; |
| use util::{ |
| ContextCoreCompatible, ContextFidlCompatible, ConversionContext, CoreCompatible, FidlCompatible, |
| }; |
| |
| use crate::devices::{BindingId, CommonInfo, DeviceInfo, Devices, ToggleError}; |
| |
| use netstack3_core::icmp::{IcmpConnId, IcmpEventDispatcher}; |
| use netstack3_core::{ |
| add_route, del_device_route, get_all_routes, get_ip_addr_subnet, handle_timeout, |
| initialize_device, receive_frame, set_ip_addr_subnet, Context, DeviceId, |
| DeviceLayerEventDispatcher, EntryDest, EntryEither, EventDispatcher, IpLayerEventDispatcher, |
| NetstackError, StackState, TimerId, TransportLayerEventDispatcher, UdpEventDispatcher, |
| }; |
| |
| macro_rules! stack_fidl_error { |
| ($err:tt) => { |
| fidl_net_stack::Error { type_: fidl_net_stack::ErrorType::$err } |
| }; |
| } |
| |
| macro_rules! encoded_fidl_error { |
| ($err:tt) => { |
| Some(fidl::encoding::OutOfLine(&mut stack_fidl_error!($err))) |
| }; |
| } |
| |
| /// The message that is sent to the main event loop to indicate that an |
| /// ethernet device has been set up, and is ready to be added to the event |
| /// loop. |
| #[derive(Debug)] |
| pub struct EthernetDeviceReady { |
| // We pass through the topological path for the device, so that it can later be shown to the |
| // user, should they request it via FIDL call. |
| path: String, |
| client: eth::Client, |
| info: EthernetInfo, |
| // This struct needs to contain the responder, because we don't know the ID of the device until |
| // it's been added to the netstack - thus, the `EthernetSetupWorker` can't respond to the FIDL |
| // request. |
| responder: StackAddEthernetInterfaceResponder, |
| } |
| |
| /// The worker that sets up an ethernet device, sending an |
| /// `EthernetDeviceReady` to the event loop once it has finished. |
| /// |
| /// `path` is not checked, since we do not have the required info by this point. |
| /// It must be set to the topological path of the device represented by |
| /// `DeviceProxy`. |
| pub struct EthernetSetupWorker { |
| dev: fidl_ethernet::DeviceProxy, |
| path: String, |
| responder: StackAddEthernetInterfaceResponder, |
| } |
| |
| impl EthernetSetupWorker { |
| fn spawn(mut self, sender: mpsc::UnboundedSender<Event>) { |
| fasync::spawn_local( |
| async move { |
| let vmo = zx::Vmo::create(256 * eth::DEFAULT_BUFFER_SIZE as u64)?; |
| let eth_client = await!(eth::Client::new( |
| self.dev, |
| vmo, |
| eth::DEFAULT_BUFFER_SIZE, |
| "recovery-ns", |
| ))?; |
| let info = await!(eth_client.info())?; |
| await!(eth_client.start())?; |
| let eth_device_event = Event::EthSetupEvent(EthernetDeviceReady { |
| path: self.path, |
| client: eth_client, |
| info, |
| responder: self.responder, |
| }); |
| sender.unbounded_send(eth_device_event); |
| Ok(()) |
| } |
| .unwrap_or_else(|e: Error| error!("{:?}", e)), |
| ); |
| } |
| } |
| |
| /// The worker that receives messages from the ethernet device, and passes them |
| /// on to the main event loop. |
| struct EthernetWorker { |
| id: BindingId, |
| events: eth::EventStream, |
| } |
| |
| impl EthernetWorker { |
| fn new(id: BindingId, events: eth::EventStream) -> Self { |
| EthernetWorker { id, events } |
| } |
| |
| fn spawn(mut self, sender: mpsc::UnboundedSender<Event>) { |
| let mut events = self.events; |
| let id = self.id; |
| fasync::spawn_local( |
| async move { |
| while let Some(evt) = await!(events.try_next())? { |
| sender.unbounded_send(Event::EthEvent((id, evt))); |
| } |
| Ok(()) |
| } |
| .unwrap_or_else(|e: Error| error!("{:?}", e)), |
| ); |
| } |
| } |
| |
| /// The events that can trigger an action in the event loop. |
| #[derive(Debug)] |
| pub enum Event { |
| /// A request from the fuchsia.net.stack.Stack FIDL interface. |
| FidlStackEvent(StackRequest), |
| /// A request from the fuchsia.posix.socket.Provider FIDL interface. |
| FidlSocketProviderEvent(psocket::ProviderRequest), |
| /// A request from the fuchsia.posix.socket.Control FIDL interface. |
| FidlSocketControlEvent((Arc<Mutex<socket::SocketControlWorkerInner>>, psocket::ControlRequest)), |
| /// An event from an ethernet interface. Either a status change or a frame. |
| EthEvent((BindingId, eth::Event)), |
| /// An indication that an ethernet device is ready to be used. |
| EthSetupEvent(EthernetDeviceReady), |
| /// A timer firing. |
| TimerEvent(timers::TimerEvent<TimerId>), |
| } |
| |
| impl From<timers::TimerEvent<TimerId>> for Event { |
| fn from(e: timers::TimerEvent<TimerId>) -> Self { |
| Event::TimerEvent(e) |
| } |
| } |
| |
| /// The event loop. |
| pub struct EventLoop { |
| ctx: Context<EventLoopInner>, |
| event_recv: mpsc::UnboundedReceiver<Event>, |
| } |
| |
| impl EventLoop { |
| pub fn new() -> Self { |
| let (event_send, event_recv) = futures::channel::mpsc::unbounded::<Event>(); |
| let fidl_worker = crate::fidl_worker::FidlWorker; |
| fidl_worker.spawn(event_send.clone()); |
| Self::new_with_channels(event_send, event_recv) |
| } |
| |
| fn new_with_channels( |
| event_send: futures::channel::mpsc::UnboundedSender<Event>, |
| event_recv: futures::channel::mpsc::UnboundedReceiver<Event>, |
| ) -> Self { |
| EventLoop { |
| ctx: Context::new( |
| StackState::default(), |
| EventLoopInner { |
| devices: Devices::default(), |
| timers: timers::TimerDispatcher::new(event_send.clone()), |
| // TODO(joshlf): Is unwrapping safe here? Alternatively, |
| // wait until we upgrade to rand 0.7, where OsRng is an |
| // empty struct. |
| rng: OsRng::new().unwrap(), |
| event_send: event_send.clone(), |
| #[cfg(test)] |
| test_events: None, |
| }, |
| ), |
| event_recv, |
| } |
| } |
| |
| async fn handle_event<'a>( |
| &'a mut self, |
| buf: &'a mut [u8], |
| evt: Option<Event>, |
| ) -> Result<(), Error> { |
| trace!("Handling Event: {:?}", evt); |
| match evt { |
| Some(Event::EthSetupEvent(setup)) => { |
| let (state, disp) = self.ctx.state_and_dispatcher(); |
| let client_stream = setup.client.get_stream(); |
| let eth_id = |
| state.add_ethernet_device(Mac::new(setup.info.mac.octets), setup.info.mtu); |
| match disp |
| .devices |
| .add_active_device(eth_id, CommonInfo::new(setup.path, setup.client)) |
| { |
| Some(id) => { |
| let eth_worker = EthernetWorker::new(id, client_stream); |
| eth_worker.spawn(self.ctx.dispatcher().event_send.clone()); |
| |
| initialize_device(&mut self.ctx, eth_id); |
| |
| setup.responder.send(None, id); |
| } |
| None => { |
| // Send internal error if we can't allocate an id |
| setup.responder.send(encoded_fidl_error!(Internal), 0); |
| } |
| } |
| } |
| Some(Event::FidlStackEvent(req)) => { |
| await!(self.handle_fidl_stack_request(req)); |
| } |
| Some(Event::FidlSocketProviderEvent(req)) => { |
| await!(self.handle_fidl_socket_provider_request(req)); |
| } |
| Some(Event::FidlSocketControlEvent((sock, req))) => { |
| sock.lock().unwrap().handle_request(self, req); |
| } |
| Some(Event::EthEvent((id, eth::Event::StatusChanged))) => { |
| info!("device {:?} status changed signal", id); |
| // We need to call get_status even if we don't use the output, since calling it |
| // acks the message, and prevents the device from sending more status changed |
| // messages. |
| if let Some(device) = self.ctx.dispatcher().get_device_info(id) { |
| if let Ok(status) = await!(device.client().get_status()) { |
| info!("device {:?} status changed to: {:?}", id, status); |
| #[cfg(test)] |
| self.ctx |
| .dispatcher_mut() |
| .send_test_event(TestEvent::DeviceStatusChanged { id, status }); |
| } |
| } |
| } |
| Some(Event::EthEvent((id, eth::Event::Receive(rx, _flags)))) => { |
| // TODO(wesleyac): Check flags |
| let len = rx.read(buf); |
| if let Some(id) = self.ctx.dispatcher().devices.get_core_id(id) { |
| receive_frame(&mut self.ctx, id, Buf::new(&mut buf[..len], ..)); |
| } else { |
| debug!("Received ethernet frame on disabled device: {}", id); |
| } |
| } |
| Some(Event::TimerEvent(id)) => { |
| // By reaching into the TimerDispatcher to commit the timer, we |
| // guarantee that the timer hasn't been cancelled while it was |
| // in the EventLoop's event stream. commit_timer will only |
| // return Some if the timer is still valid. |
| if let Some(id) = self.ctx.dispatcher_mut().timers.commit_timer(id) { |
| handle_timeout(&mut self.ctx, id); |
| } |
| } |
| None => bail!("Stream of events ended unexpectedly"), |
| } |
| Ok(()) |
| } |
| |
| async fn run_until<V>(&mut self, mut fut: impl Future<Output = V> + Unpin) -> Result<V, Error> { |
| let mut buf = [0; 2048]; |
| let mut fut = Some(fut); |
| loop { |
| match await!(futures::future::select(self.event_recv.next(), fut.take().unwrap())) { |
| future::Either::Left((evt, f)) => { |
| await!(self.handle_event(&mut buf, evt))?; |
| fut = Some(f); |
| } |
| future::Either::Right((result, _)) => break Ok(result), |
| _ => continue, |
| } |
| } |
| } |
| |
| pub async fn run(mut self) -> Result<(), Error> { |
| let mut buf = [0; 2048]; |
| loop { |
| let evt = await!(self.event_recv.next()); |
| await!(self.handle_event(&mut buf, evt))?; |
| } |
| Ok(()) |
| } |
| |
| async fn handle_fidl_socket_provider_request(&mut self, req: psocket::ProviderRequest) { |
| match req { |
| psocket::ProviderRequest::Socket { domain, type_, protocol, responder } => { |
| let domain = i32::from(domain); |
| let nonblock = i32::from(type_) & libc::SOCK_NONBLOCK != 0; |
| let type_ = i32::from(type_) & !(libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC); |
| let net_proto = match domain { |
| libc::AF_INET => IpVersion::V4, |
| libc::AF_INET6 => IpVersion::V6, |
| _ => { |
| responder.send(libc::EAFNOSUPPORT as i16, None); |
| return; |
| } |
| }; |
| let trans_proto = match i32::from(type_) { |
| libc::SOCK_DGRAM => socket::TransProto::UDP, |
| libc::SOCK_STREAM => socket::TransProto::TCP, |
| _ => { |
| responder.send(libc::EAFNOSUPPORT as i16, None); |
| return; |
| } |
| }; |
| |
| if let Ok((c0, c1)) = zx::Channel::create() { |
| let worker = socket::SocketControlWorker::new( |
| psocket::ControlRequestStream::from_channel( |
| fasync::Channel::from_channel(c0).unwrap(), |
| ), |
| net_proto, |
| trans_proto, |
| nonblock, |
| ); |
| if let Ok(worker) = worker { |
| worker.spawn(self.ctx.dispatcher().event_send.clone()); |
| responder.send(0, Some(ClientEnd::new(c1))); |
| } else { |
| responder.send(libc::ENOBUFS as i16, None); |
| } |
| } else { |
| responder.send(libc::ENOBUFS as i16, None); |
| } |
| } |
| } |
| } |
| |
| async fn handle_fidl_stack_request(&mut self, req: StackRequest) { |
| match req { |
| StackRequest::AddEthernetInterface { topological_path, device, responder } => { |
| self.fidl_add_ethernet_interface(topological_path, device, responder); |
| } |
| StackRequest::DelEthernetInterface { id, responder } => { |
| responder.send( |
| self.fidl_del_ethernet_interface(id).as_mut().map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::ListInterfaces { responder } => { |
| responder.send(&mut await!(self.fidl_list_interfaces()).iter_mut()); |
| } |
| StackRequest::GetInterfaceInfo { id, responder } => { |
| let (mut info, mut error) = match (await!(self.fidl_get_interface_info(id))) { |
| Ok(info) => (Some(info), None), |
| Err(error) => (None, Some(error)), |
| }; |
| responder.send( |
| info.as_mut().map(fidl::encoding::OutOfLine), |
| error.as_mut().map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::EnableInterface { id, responder } => { |
| responder.send( |
| await!(self.fidl_enable_interface(id)) |
| .err() |
| .as_mut() |
| .map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::DisableInterface { id, responder } => { |
| responder.send( |
| self.fidl_disable_interface(id).err().as_mut().map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::AddInterfaceAddress { id, addr, responder } => { |
| responder.send( |
| self.fidl_add_interface_address(id, addr) |
| .as_mut() |
| .map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::DelInterfaceAddress { id, addr, responder } => { |
| responder.send( |
| self.fidl_del_interface_address(id, addr) |
| .as_mut() |
| .map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::GetForwardingTable { responder } => { |
| responder.send(&mut self.fidl_get_forwarding_table().iter_mut()); |
| } |
| StackRequest::AddForwardingEntry { entry, responder } => { |
| responder.send( |
| self.fidl_add_forwarding_entry(entry).as_mut().map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::DelForwardingEntry { subnet, responder } => { |
| responder.send( |
| self.fidl_del_forwarding_entry(subnet).as_mut().map(fidl::encoding::OutOfLine), |
| ); |
| } |
| StackRequest::EnablePacketFilter { id, responder } => { |
| // TODO(toshik) |
| } |
| StackRequest::DisablePacketFilter { id, responder } => { |
| // TODO(toshik) |
| } |
| StackRequest::EnableIpForwarding { responder } => { |
| // TODO(toshik) |
| } |
| StackRequest::DisableIpForwarding { responder } => { |
| // TODO(toshik) |
| } |
| } |
| } |
| |
| fn fidl_add_ethernet_interface( |
| &mut self, |
| topological_path: String, |
| device: fidl::endpoints::ClientEnd<fidl_fuchsia_hardware_ethernet::DeviceMarker>, |
| responder: StackAddEthernetInterfaceResponder, |
| ) { |
| let setup = EthernetSetupWorker { |
| dev: device.into_proxy().unwrap(), |
| path: topological_path, |
| responder, |
| }; |
| setup.spawn(self.ctx.dispatcher().event_send.clone()); |
| } |
| |
| fn fidl_del_ethernet_interface(&mut self, id: u64) -> Option<fidl_net_stack::Error> { |
| match self.ctx.dispatcher_mut().devices.remove_device(id) { |
| Some(info) => { |
| // TODO(rheacock): ensure that the core client deletes all data |
| None |
| } |
| None => { |
| // Invalid device ID |
| Some(stack_fidl_error!(NotFound)) |
| } |
| } |
| } |
| |
| async fn fidl_list_interfaces(&mut self) -> Vec<fidl_net_stack::InterfaceInfo> { |
| let mut devices = vec![]; |
| for device in self.ctx.dispatcher().devices.iter_devices() { |
| // TODO(wesleyac): Cache info and status |
| let info = await!(device.client().info()); |
| let status = await!(device.client().get_status()); |
| let is_active = device.is_active(); |
| devices.push(InterfaceInfo { |
| id: device.id(), |
| properties: InterfaceProperties { |
| name: "[TBD]".to_owned(), // TODO(porce): Follow up to populate the name |
| topopath: device.path().clone(), |
| filepath: "[TBD]".to_owned(), // TODO(porce): Follow up to populate |
| mac: if let Ok(info) = &info { Some(Box::new(info.mac.into())) } else { None }, |
| mtu: if let Ok(info) = &info { info.mtu } else { 0 }, |
| features: if let Ok(info) = &info { info.features.bits() } else { 0 }, |
| administrative_status: if is_active { |
| AdministrativeStatus::Enabled |
| } else { |
| AdministrativeStatus::Disabled |
| }, |
| physical_status: match status { |
| Ok(status) => { |
| if status.contains(EthernetStatus::ONLINE) { |
| PhysicalStatus::Up |
| } else { |
| PhysicalStatus::Down |
| } |
| } |
| Err(_) => PhysicalStatus::Down, |
| }, |
| addresses: vec![], //TODO(wesleyac): this |
| }, |
| }); |
| } |
| devices |
| } |
| |
| async fn fidl_get_interface_info( |
| &mut self, |
| id: u64, |
| ) -> Result<fidl_net_stack::InterfaceInfo, fidl_net_stack::Error> { |
| let device = |
| self.ctx.dispatcher().get_device_info(id).ok_or(stack_fidl_error!(NotFound))?; |
| // TODO(wesleyac): Cache info and status |
| let info = await!(device.client().info()).map_err(|_| stack_fidl_error!(Internal))?; |
| let status = |
| await!(device.client().get_status()).map_err(|_| stack_fidl_error!(Internal))?; |
| let is_active = device.is_active(); |
| return Ok(InterfaceInfo { |
| id: device.id(), |
| properties: InterfaceProperties { |
| name: "[TBD]".to_owned(), // TODO(porce): Follow up to populate the name |
| topopath: device.path().clone(), |
| filepath: "[TBD]".to_owned(), // TODO(porce): Follow up to populate |
| mac: Some(Box::new(info.mac.into())), |
| mtu: info.mtu, |
| features: info.features.bits(), |
| administrative_status: if is_active { |
| AdministrativeStatus::Enabled |
| } else { |
| AdministrativeStatus::Disabled |
| }, |
| physical_status: if status.contains(EthernetStatus::ONLINE) { |
| PhysicalStatus::Up |
| } else { |
| PhysicalStatus::Down |
| }, |
| addresses: vec![], //TODO(wesleyac): this |
| }, |
| }); |
| } |
| |
| async fn fidl_enable_interface(&mut self, id: u64) -> Result<(), fidl_net_stack::Error> { |
| let (state, disp) = self.ctx.state_and_dispatcher(); |
| let device = disp.get_device_info(id).ok_or(stack_fidl_error!(NotFound))?; |
| let info = await!(device.client().info()).map_err(|_| stack_fidl_error!(Internal))?; |
| // TODO(rheacock, NET-2140): Handle core and driver state in two stages: add device to the |
| // core to get an id, then reach into the driver to get updated info before triggering the |
| // core to allow traffic on the interface. |
| let generate_core_id = |
| |dev_info: &DeviceInfo| state.add_ethernet_device(Mac::new(info.mac.octets), info.mtu); |
| match disp.devices.activate_device(id, generate_core_id) { |
| Ok(_) => Ok(()), |
| Err(toggle_error) => { |
| match toggle_error { |
| ToggleError::NoChange => Ok(()), |
| ToggleError::NotFound => Err(stack_fidl_error!(NotFound)), // Invalid device ID |
| } |
| } |
| } |
| } |
| |
| fn fidl_disable_interface(&mut self, id: u64) -> Result<(), fidl_net_stack::Error> { |
| match self.ctx.dispatcher_mut().devices.deactivate_device(id) { |
| Ok(_) => Ok(()), |
| // TODO(rheacock, NET-2140): handle core and driver state |
| Err(toggle_error) => { |
| match toggle_error { |
| ToggleError::NoChange => Ok(()), |
| ToggleError::NotFound => Err(stack_fidl_error!(NotFound)), // Invalid device ID |
| } |
| } |
| } |
| } |
| |
| fn fidl_add_interface_address( |
| &mut self, |
| id: u64, |
| addr: InterfaceAddress, |
| ) -> Option<fidl_net_stack::Error> { |
| let device_info = self.ctx.dispatcher().get_device_info(id); |
| if let Some(device_info) = device_info { |
| match device_info.core_id() { |
| Some(device_id) => { |
| // TODO(wesleyac): Check for address already existing. |
| // TODO(joshlf): Return an error if the address/subnet pair is invalid. |
| if let Ok(addr_sub) = addr.try_into_core() { |
| set_ip_addr_subnet(&mut self.ctx, device_id, addr_sub); |
| } |
| None |
| } |
| None => { |
| // TODO(brunodalbo): We should probably allow adding static addresses |
| // to interfaces that are not installed, return BadState for now |
| Some(stack_fidl_error!(BadState)) |
| } |
| } |
| } else { |
| Some(stack_fidl_error!(NotFound)) // Invalid device ID |
| } |
| } |
| |
| fn fidl_del_interface_address( |
| &mut self, |
| id: u64, |
| addr: fidl_net_stack::InterfaceAddress, |
| ) -> Option<fidl_net_stack::Error> { |
| // TODO(eyalsoha): Implement this. |
| None |
| } |
| |
| fn fidl_get_forwarding_table(&self) -> Vec<fidl_net_stack::ForwardingEntry> { |
| get_all_routes(&self.ctx) |
| .filter_map(|entry| match entry.try_into_fidl_with_ctx(self.ctx.dispatcher()) { |
| Ok(entry) => Some(entry), |
| Err(e) => { |
| error!("Failed to map forwarding entry into FIDL"); |
| None |
| } |
| }) |
| .collect() |
| } |
| |
| fn fidl_add_forwarding_entry( |
| &mut self, |
| entry: ForwardingEntry, |
| ) -> Option<fidl_net_stack::Error> { |
| let entry = match EntryEither::try_from_fidl_with_ctx(self.ctx.dispatcher(), entry) { |
| Ok(entry) => entry, |
| Err(e) => return Some(e.into()), |
| }; |
| match add_route(&mut self.ctx, entry) { |
| Ok(_) => None, |
| Err(NetstackError::Exists) => { |
| // Subnet already in routing table. |
| Some(fidl_net_stack::Error { type_: fidl_net_stack::ErrorType::AlreadyExists }) |
| } |
| Err(_) => unreachable!(), |
| } |
| } |
| |
| fn fidl_del_forwarding_entry( |
| &mut self, |
| subnet: fidl_net::Subnet, |
| ) -> Option<fidl_net_stack::Error> { |
| if let Ok(subnet) = subnet.try_into_core() { |
| match del_device_route(&mut self.ctx, subnet) { |
| Ok(_) => None, |
| Err(NetstackError::NotFound) => Some(stack_fidl_error!(NotFound)), |
| Err(_) => unreachable!(), |
| } |
| } else { |
| Some(stack_fidl_error!(InvalidArgs)) |
| } |
| } |
| } |
| |
| struct TimerInfo { |
| time: zx::Time, |
| id: TimerId, |
| abort_handle: AbortHandle, |
| } |
| |
| struct EventLoopInner { |
| devices: Devices, |
| timers: timers::TimerDispatcher<TimerId, Event>, |
| rng: OsRng, |
| event_send: mpsc::UnboundedSender<Event>, |
| #[cfg(test)] |
| test_events: Option<mpsc::UnboundedSender<TestEvent>>, |
| } |
| |
| impl EventLoopInner { |
| fn get_device_info(&self, id: u64) -> Option<&DeviceInfo> { |
| self.devices.get_device(id) |
| } |
| |
| /// Sends an event to a special test events listener. |
| /// |
| /// Only available for testing, use this function to expose internal events |
| /// to testing code. |
| #[cfg(test)] |
| fn send_test_event(&mut self, event: TestEvent) { |
| if let Some(evt) = self.test_events.as_mut() { |
| evt.unbounded_send(event).expect("Can't send test event data"); |
| } |
| } |
| } |
| |
| impl ConversionContext for EventLoopInner { |
| fn get_core_id(&self, binding_id: u64) -> Option<DeviceId> { |
| self.devices.get_core_id(binding_id) |
| } |
| |
| fn get_binding_id(&self, core_id: DeviceId) -> Option<u64> { |
| self.devices.get_binding_id(core_id) |
| } |
| } |
| |
| /// A thin wrapper around `zx::Time` that implements `core::Instant`. |
| #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] |
| struct ZxTime(zx::Time); |
| |
| impl netstack3_core::Instant for ZxTime { |
| fn duration_since(&self, earlier: ZxTime) -> Duration { |
| assert!(self.0 >= earlier.0); |
| // guaranteed not to panic because the assertion ensures that the |
| // difference is non-negative, and all non-negative i64 values are also |
| // valid u64 values |
| Duration::from_nanos(u64::try_from(self.0.into_nanos() - earlier.0.into_nanos()).unwrap()) |
| } |
| |
| fn checked_add(&self, duration: Duration) -> Option<ZxTime> { |
| Some(ZxTime(zx::Time::from_nanos( |
| self.0.into_nanos().checked_add(i64::try_from(duration.as_nanos()).ok()?)?, |
| ))) |
| } |
| |
| fn checked_sub(&self, duration: Duration) -> Option<ZxTime> { |
| Some(ZxTime(zx::Time::from_nanos( |
| self.0.into_nanos().checked_sub(i64::try_from(duration.as_nanos()).ok()?)?, |
| ))) |
| } |
| } |
| |
| impl EventDispatcher for EventLoopInner { |
| type Instant = ZxTime; |
| |
| fn now(&self) -> ZxTime { |
| ZxTime(zx::Time::get(zx::ClockId::Monotonic)) |
| } |
| |
| fn schedule_timeout_instant(&mut self, time: ZxTime, id: TimerId) -> Option<ZxTime> { |
| self.timers.schedule_timer(id, time) |
| } |
| |
| fn cancel_timeout(&mut self, id: TimerId) -> Option<ZxTime> { |
| self.timers.cancel_timer(&id) |
| } |
| |
| fn cancel_timeouts_with<F: FnMut(&TimerId) -> bool>(&mut self, f: F) { |
| self.timers.cancel_timers_with(f); |
| } |
| |
| type Rng = OsRng; |
| |
| fn rng(&mut self) -> &mut OsRng { |
| &mut self.rng |
| } |
| } |
| |
| impl<B: BufferMut> DeviceLayerEventDispatcher<B> for EventLoopInner { |
| fn send_frame<S: Serializer>(&mut self, device: DeviceId, frame: S) -> Result<(), S> { |
| // TODO(wesleyac): Error handling |
| let frame = frame.serialize_vec_outer().map_err(|(_, ser)| ser)?; |
| match self.devices.get_core_device_mut(device) { |
| Some(dev) => { |
| dev.client_mut().send(frame.as_ref()); |
| } |
| None => error!("Tried to send frame on device that is not listed: {:?}", device), |
| } |
| Ok(()) |
| } |
| } |
| |
| impl UdpEventDispatcher for EventLoopInner {} |
| |
| impl TransportLayerEventDispatcher for EventLoopInner {} |
| |
| impl<B: BufferMut> IcmpEventDispatcher<B> for EventLoopInner { |
| fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId, seq_num: u16, data: B) { |
| #[cfg(test)] |
| self.send_test_event(TestEvent::IcmpEchoReply { |
| conn, |
| seq_num, |
| data: data.as_ref().to_owned(), |
| }); |
| |
| // TODO(brunodalbo) implement actual handling of icmp echo replies |
| } |
| } |
| |
| impl<B: BufferMut> IpLayerEventDispatcher<B> for EventLoopInner {} |