| // 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. |
| |
| #![deny(warnings)] |
| |
| use { |
| anyhow::{Context as _, Error}, |
| argh::FromArgs, |
| dhcp::{ |
| configuration, |
| protocol::{Message, SERVER_PORT}, |
| server::{ |
| get_server_id_from, Server, ServerAction, ServerDispatcher, ServerError, |
| DEFAULT_STASH_ID, DEFAULT_STASH_PREFIX, |
| }, |
| }, |
| fuchsia_async::{self as fasync, net::UdpSocket, Interval}, |
| fuchsia_component::server::ServiceFs, |
| fuchsia_zircon::DurationNum, |
| futures::{Future, SinkExt, StreamExt, TryFutureExt, TryStreamExt}, |
| net2::unix::UnixUdpBuilderExt, |
| std::{ |
| cell::RefCell, |
| collections::hash_map::Entry, |
| collections::HashMap, |
| net::{IpAddr, Ipv4Addr}, |
| os::unix::io::AsRawFd, |
| }, |
| void::Void, |
| }; |
| |
| /// A buffer size in excess of the maximum allowable DHCP message size. |
| const BUF_SZ: usize = 1024; |
| const DEFAULT_CONFIG_PATH: &str = "/pkg/data/config.json"; |
| /// The rate in seconds at which expiration DHCP leases are recycled back into the managed address |
| /// pool. The current value of 5 is meant to facilitate manual testing. |
| // TODO(atait): Replace with Duration type after it has been updated to const fn. |
| const EXPIRATION_INTERVAL_SECS: i64 = 5; |
| |
| enum IncomingService { |
| Server(fidl_fuchsia_net_dhcp::Server_RequestStream), |
| } |
| |
| /// The Fuchsia DHCP server. |
| #[derive(Debug, FromArgs)] |
| #[argh(name = "dhcpd")] |
| pub struct Args { |
| /// the identifier used to access fuchsia.stash.Store. dhcpd will attempt to access its |
| /// configuration parameters, saved DHCP option values, and saved leases from the |
| /// fuchsia.stash.Store instance specified by this identifier. If there are no configuration |
| /// parameters etc. at the specified fuchsia.stash.Store instance, then dhcpd will fallback to |
| /// parameters stored in the default configuration file. |
| #[argh(option, default = "DEFAULT_STASH_ID.to_string()")] |
| pub stash: String, |
| |
| /// the path to the default configuration file consumed by dhcpd if it was unable to access a |
| /// fuchsia.stash.Store instance. |
| #[argh(option, default = "DEFAULT_CONFIG_PATH.to_string()")] |
| pub config: String, |
| } |
| |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| fuchsia_syslog::init_with_tags(&["dhcpd"])?; |
| |
| let Args { config, stash } = argh::from_env(); |
| let stash = dhcp::stash::Stash::new(&stash, DEFAULT_STASH_PREFIX) |
| .context("failed to instantiate stash")?; |
| let default_params = configuration::load_server_params_from_file(&config) |
| .context("failed to load default server parameters from configuration file")?; |
| let params = stash.load_parameters().await.unwrap_or_else(|e| { |
| log::warn!("failed to load parameters from stash: {:?}", e); |
| default_params.clone() |
| }); |
| |
| let options = stash.load_options().await.unwrap_or_else(|e| { |
| log::warn!("failed to load options from stash: {:?}", e); |
| HashMap::new() |
| }); |
| let cache = stash.load_client_configs().await.unwrap_or_else(|e| { |
| log::warn!("failed to load cached client config from stash: {:?}", e); |
| HashMap::new() |
| }); |
| let server = |
| RefCell::new(ServerDispatcherRuntime::new(Server::new(stash, params, options, cache))); |
| |
| let mut fs = ServiceFs::new_local(); |
| fs.dir("svc").add_fidl_service(IncomingService::Server); |
| fs.take_and_serve_directory_handle()?; |
| |
| let (mut socket_sink, socket_stream) = |
| futures::channel::mpsc::channel::<ServerSocketCollection<UdpSocket>>(1); |
| |
| // Attempt to enable the server on startup. |
| // NOTE(brunodalbo): Enabling the server on startup should be an explicit |
| // configuration loaded from default configs and stash. For now, just mimic |
| // existing behavior and try to enable. It'll fail if we don't have a valid |
| // configuration from stash/config. |
| match server.borrow_mut().enable() { |
| Ok(None) => unreachable!("server can't be enabled already"), |
| Ok(Some(socket_collection)) => { |
| // Sending here should never fail; we just created the stream above. |
| let () = socket_sink.try_send(socket_collection)?; |
| } |
| Err(e) => log::error!("Failed to start server on startup: {:?}", e), |
| } |
| |
| let admin_fut = |
| fs.then(futures::future::ok).try_for_each_concurrent(None, |incoming_service| async { |
| match incoming_service { |
| IncomingService::Server(stream) => { |
| run_server(stream, &server, &default_params, socket_sink.clone()) |
| .inspect_err(|e| log::warn!("run_server failed: {:?}", e)) |
| .await?; |
| Ok(()) |
| } |
| } |
| }); |
| |
| let server_fut = define_running_server_fut(&server, socket_stream); |
| |
| log::info!("running"); |
| let ((), ()) = futures::try_join!(server_fut, admin_fut)?; |
| |
| Ok(()) |
| } |
| |
| trait SocketServerDispatcher: ServerDispatcher { |
| type Socket; |
| |
| fn create_socket(name: Option<&str>, src: Ipv4Addr) -> Result<Self::Socket, Error>; |
| fn dispatch_message(&mut self, msg: Message) -> Result<ServerAction, ServerError>; |
| } |
| |
| impl SocketServerDispatcher for Server { |
| type Socket = UdpSocket; |
| |
| fn create_socket(name: Option<&str>, src: Ipv4Addr) -> Result<Self::Socket, Error> { |
| let sock = net2::UdpBuilder::new_v4()?; |
| // Since dhcpd may listen to multiple interfaces, we must enable |
| // SO_REUSEPORT so that binding the same (address, port) pair to each |
| // interface can still succeed. |
| let sock = sock.reuse_port(true)?; |
| if let Some(name) = name { |
| // There are currently no safe Rust interfaces to set SO_BINDTODEVICE, |
| // so we must set it through libc. |
| if unsafe { |
| libc::setsockopt( |
| sock.as_raw_fd(), |
| libc::SOL_SOCKET, |
| libc::SO_BINDTODEVICE, |
| name.as_ptr() as *const libc::c_void, |
| name.len() as libc::socklen_t, |
| ) |
| } == -1 |
| { |
| return Err(anyhow::format_err!( |
| "setsockopt(SO_BINDTODEVICE) failed for {}: {}", |
| name, |
| std::io::Error::last_os_error() |
| )); |
| } |
| } |
| let sock = sock.bind((src, SERVER_PORT))?; |
| let () = sock.set_broadcast(true)?; |
| Ok(UdpSocket::from_socket(sock)?) |
| } |
| |
| fn dispatch_message(&mut self, msg: Message) -> Result<ServerAction, ServerError> { |
| self.dispatch(msg) |
| } |
| } |
| |
| /// A wrapper around a [`ServerDispatcher`] that keeps information about the |
| /// server status through a [`futures::future::AbortHandle`]. |
| struct ServerDispatcherRuntime<S> { |
| abort_handle: Option<futures::future::AbortHandle>, |
| server: S, |
| } |
| |
| impl<S> std::ops::Deref for ServerDispatcherRuntime<S> { |
| type Target = S; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.server |
| } |
| } |
| |
| impl<S> std::ops::DerefMut for ServerDispatcherRuntime<S> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.server |
| } |
| } |
| |
| impl<S: SocketServerDispatcher> ServerDispatcherRuntime<S> { |
| /// Creates a new runtime with `server`. |
| fn new(server: S) -> Self { |
| Self { abort_handle: None, server } |
| } |
| |
| /// Disables the server. |
| /// |
| /// `disable` will cancel the previous |
| /// [`futures::future::AbortRegistration`] returned by `enable`. |
| /// |
| /// If the server is already disabled, `disable` is a no-op. |
| fn disable(&mut self) { |
| if let Some(abort_handle) = self.abort_handle.take() { |
| let () = abort_handle.abort(); |
| } |
| } |
| |
| /// Enables the server. |
| /// |
| /// Attempts to enable the server, returning a new |
| /// [`ServerSocketCollection`] on success. The returned collection contains |
| /// the list of sockets where the server can listen on and an abort |
| /// registration that is used to cancel the future that listen on the |
| /// sockets when [`ServerDispatcherRuntime::disable`] is called. |
| /// |
| /// Returns an error if the server couldn't be started or if the closure |
| /// fails, maintaining the server in the disabled state. |
| /// |
| /// If the server is already enabled, `enable` returns `Ok(None)`. |
| fn enable( |
| &mut self, |
| ) -> Result<Option<ServerSocketCollection<S::Socket>>, fuchsia_zircon::Status> { |
| if self.abort_handle.is_some() { |
| // Server already running. |
| return Ok(None); |
| } |
| let params = self.server.try_validate_parameters()?; |
| // Provide the closure with an AbortRegistration and a ref to |
| // parameters. |
| let (abort_handle, abort_registration) = futures::future::AbortHandle::new_pair(); |
| |
| let sockets = create_sockets_from_params::<S>(params).map_err(|e| { |
| log::error!("Failed to create server sockets: {}", e); |
| fuchsia_zircon::Status::IO |
| })?; |
| if sockets.is_empty() { |
| log::error!("No sockets to run server on"); |
| return Err(fuchsia_zircon::Status::INVALID_ARGS); |
| } |
| self.abort_handle = Some(abort_handle); |
| Ok(Some(ServerSocketCollection { sockets, abort_registration })) |
| } |
| |
| /// Runs the closure `f` only if the server is currently disabled. |
| /// |
| /// Returns `BAD_STATE` error otherwise. |
| fn if_disabled<R, F: FnOnce(&mut S) -> Result<R, fuchsia_zircon::Status>>( |
| &mut self, |
| f: F, |
| ) -> Result<R, fuchsia_zircon::Status> { |
| if self.abort_handle.is_none() { |
| f(&mut self.server) |
| } else { |
| Err(fuchsia_zircon::Status::BAD_STATE) |
| } |
| } |
| } |
| |
| fn create_sockets_from_params<S: SocketServerDispatcher>( |
| params: &configuration::ServerParameters, |
| ) -> Result<Vec<S::Socket>, Error> { |
| Ok(if params.bound_device_names.len() > 0 { |
| params.bound_device_names.iter().map(String::as_str).try_fold::<_, _, Result<_, Error>>( |
| Vec::new(), |
| |mut acc, name| { |
| let sock = S::create_socket(Some(name), Ipv4Addr::UNSPECIFIED)?; |
| let () = acc.push(sock); |
| Ok(acc) |
| }, |
| )? |
| } else { |
| vec![S::create_socket(None, Ipv4Addr::UNSPECIFIED)?] |
| }) |
| } |
| |
| /// Helper struct to handle buffer data from sockets. |
| struct MessageHandler<'a, S: SocketServerDispatcher> { |
| server: &'a RefCell<ServerDispatcherRuntime<S>>, |
| send_socks: HashMap<Ipv4Addr, S::Socket>, |
| } |
| |
| impl<'a, S: SocketServerDispatcher> MessageHandler<'a, S> { |
| /// Creates a new `MessageHandler` for `server`. |
| fn new(server: &'a RefCell<ServerDispatcherRuntime<S>>) -> Self { |
| Self { server, send_socks: HashMap::new() } |
| } |
| |
| /// Handles `buf` from `sender`. |
| /// |
| /// Returns `Ok(Some(sock, dst, data))` if a `data` must be sent to `dst` |
| /// over `sock`. |
| /// |
| /// Returns `Ok(None)` if no action is required and the handler is ready to |
| /// receive more messages. |
| /// |
| /// Returns `Err` if an unrecoverable error occurs and the server must stop |
| /// serving. |
| fn handle_from_sender( |
| &mut self, |
| buf: &[u8], |
| mut sender: std::net::SocketAddr, |
| ) -> Result<Option<(&S::Socket, std::net::SocketAddr, Vec<u8>)>, Error> { |
| log::info!("received message from: {}", sender); |
| let msg = match Message::from_buffer(buf) { |
| Ok(msg) => msg, |
| Err(e) => { |
| log::error!("failed to parse message from {}: {}", sender, e); |
| return Ok(None); |
| } |
| }; |
| log::info!("parsed message: {:?}", msg); |
| |
| // This call should not block because the server is single-threaded. |
| let result = self.server.borrow_mut().dispatch_message(msg); |
| match result { |
| Err(e) => { |
| log::error!("error processing client message: {:?}", e); |
| Ok(None) |
| } |
| Ok(ServerAction::AddressRelease(addr)) => { |
| log::info!("released address: {}", addr); |
| Ok(None) |
| } |
| Ok(ServerAction::AddressDecline(addr)) => { |
| log::info!("allocated address: {}", addr); |
| Ok(None) |
| } |
| Ok(ServerAction::SendResponse(message, dest)) => { |
| log::info!("generated response: {:?}", message); |
| |
| // Check if server returned an explicit destination ip. |
| if let Some(addr) = dest { |
| sender.set_ip(IpAddr::V4(addr)); |
| } |
| let src = |
| get_server_id_from(&message).ok_or(ServerError::MissingServerIdentifier)?; |
| let response_buffer = message.serialize(); |
| let sock = match self.send_socks.entry(src) { |
| Entry::Occupied(entry) => entry.into_mut(), |
| Entry::Vacant(entry) => entry.insert(S::create_socket(None, src)?), |
| }; |
| Ok(Some((sock, sender, response_buffer))) |
| } |
| } |
| } |
| } |
| |
| async fn define_msg_handling_loop_future( |
| sock: UdpSocket, |
| server: &RefCell<ServerDispatcherRuntime<Server>>, |
| ) -> Result<Void, Error> { |
| let mut handler = MessageHandler::new(server); |
| let mut buf = vec![0u8; BUF_SZ]; |
| loop { |
| let (received, sender) = |
| sock.recv_from(&mut buf).await.context("failed to read from socket")?; |
| if let Some((sock, dst, response)) = handler |
| .handle_from_sender(&buf[..received], sender) |
| .context("failed to handle buffer")? |
| { |
| sock.send_to(&response, dst).await.context("unable to send response")?; |
| log::info!("response sent to: {}", dst); |
| } |
| } |
| } |
| |
| fn define_lease_expiration_handler_future<'a>( |
| server: &'a RefCell<ServerDispatcherRuntime<Server>>, |
| ) -> impl Future<Output = Result<(), Error>> + 'a { |
| let expiration_interval = Interval::new(EXPIRATION_INTERVAL_SECS.seconds()); |
| expiration_interval |
| .map(move |()| server.borrow_mut().release_expired_leases()) |
| .map(|_| Ok(())) |
| .try_collect::<()>() |
| } |
| |
| fn define_running_server_fut<'a, S>( |
| server: &'a RefCell<ServerDispatcherRuntime<Server>>, |
| socket_stream: S, |
| ) -> impl Future<Output = Result<(), Error>> + 'a |
| where |
| S: futures::Stream<Item = ServerSocketCollection<<Server as SocketServerDispatcher>::Socket>> |
| + 'static, |
| { |
| socket_stream.map(Ok).try_for_each(move |socket_collection| async move { |
| let ServerSocketCollection { sockets, abort_registration } = socket_collection; |
| let msg_loops = futures::future::try_join_all( |
| sockets.into_iter().map(|sock| define_msg_handling_loop_future(sock, server)), |
| ); |
| let lease_expiration_handler = define_lease_expiration_handler_future(server); |
| |
| let fut = futures::future::try_join(msg_loops, lease_expiration_handler); |
| |
| match futures::future::Abortable::new(fut, abort_registration).await { |
| Ok(Ok((_void, ()))) => Err(anyhow::anyhow!("Server futures finished unexpectedly")), |
| Ok(Err(error)) => { |
| // There was an error handling the server sockets or lease |
| // expiration. Disable the server. |
| log::error!("Server encountered an error: {}. Stopping server.", error); |
| let () = server.borrow_mut().disable(); |
| Ok(()) |
| } |
| Err(futures::future::Aborted {}) => { |
| log::info!("Server stopped"); |
| Ok(()) |
| } |
| } |
| }) |
| } |
| |
| struct ServerSocketCollection<S> { |
| sockets: Vec<S>, |
| abort_registration: futures::future::AbortRegistration, |
| } |
| |
| async fn run_server<S, C>( |
| stream: fidl_fuchsia_net_dhcp::Server_RequestStream, |
| server: &RefCell<ServerDispatcherRuntime<S>>, |
| default_params: &dhcp::configuration::ServerParameters, |
| socket_sink: C, |
| ) -> Result<(), fidl::Error> |
| where |
| S: SocketServerDispatcher, |
| C: futures::sink::Sink<ServerSocketCollection<S::Socket>> + Unpin, |
| C::Error: std::fmt::Debug, |
| { |
| stream |
| .try_fold(socket_sink, |mut socket_sink, request| async move { |
| match request { |
| fidl_fuchsia_net_dhcp::Server_Request::StartServing { responder } => { |
| responder.send( |
| &mut match server.borrow_mut().enable() { |
| Ok(Some(socket_collection)) => { |
| socket_sink.send(socket_collection).await.map_err(|e| { |
| log::error!("Failed to send sockets to sink: {:?}", e); |
| // Disable the server again to keep a consistent state. |
| let () = server.borrow_mut().disable(); |
| fuchsia_zircon::Status::INTERNAL |
| }) |
| } |
| Ok(None) => { |
| log::info!("Server already running"); |
| Ok(()) |
| } |
| Err(status) => Err(status), |
| } |
| .map_err(fuchsia_zircon::Status::into_raw), |
| ) |
| } |
| fidl_fuchsia_net_dhcp::Server_Request::StopServing { responder } => { |
| let () = server.borrow_mut().disable(); |
| responder.send() |
| } |
| fidl_fuchsia_net_dhcp::Server_Request::GetOption { code: c, responder: r } => { |
| r.send(&mut server.borrow().dispatch_get_option(c).map_err(|e| e.into_raw())) |
| } |
| fidl_fuchsia_net_dhcp::Server_Request::GetParameter { name: n, responder: r } => { |
| r.send(&mut server.borrow().dispatch_get_parameter(n).map_err(|e| e.into_raw())) |
| } |
| fidl_fuchsia_net_dhcp::Server_Request::SetOption { value: v, responder: r } => r |
| .send( |
| &mut server.borrow_mut().dispatch_set_option(v).map_err(|e| e.into_raw()), |
| ), |
| fidl_fuchsia_net_dhcp::Server_Request::SetParameter { value: v, responder: r } => r |
| .send( |
| &mut server |
| .borrow_mut() |
| .if_disabled(|s| s.dispatch_set_parameter(v)) |
| .map_err(|e| e.into_raw()), |
| ), |
| fidl_fuchsia_net_dhcp::Server_Request::ListOptions { responder: r } => { |
| r.send(&mut server.borrow().dispatch_list_options().map_err(|e| e.into_raw())) |
| } |
| fidl_fuchsia_net_dhcp::Server_Request::ListParameters { responder: r } => r.send( |
| &mut server.borrow().dispatch_list_parameters().map_err(|e| e.into_raw()), |
| ), |
| fidl_fuchsia_net_dhcp::Server_Request::ResetOptions { responder: r } => r.send( |
| &mut server.borrow_mut().dispatch_reset_options().map_err(|e| e.into_raw()), |
| ), |
| fidl_fuchsia_net_dhcp::Server_Request::ResetParameters { responder: r } => r.send( |
| &mut server |
| .borrow_mut() |
| .if_disabled(|s| s.dispatch_reset_parameters(&default_params)) |
| .map_err(|e| e.into_raw()), |
| ), |
| fidl_fuchsia_net_dhcp::Server_Request::ClearLeases { responder: r } => r.send( |
| &mut server |
| .borrow_mut() |
| .dispatch_clear_leases() |
| .map_err(fuchsia_zircon::Status::into_raw), |
| ), |
| } |
| .map(|()| socket_sink) |
| }) |
| .await |
| // Discard the socket sink. |
| .map(|_socket_sink: C| ()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use dhcp::configuration::ServerParameters; |
| use futures::{sink::drain, FutureExt}; |
| use std::convert::TryFrom; |
| |
| #[derive(Debug, Eq, PartialEq)] |
| struct CannedSocket { |
| name: Option<String>, |
| src: Ipv4Addr, |
| } |
| |
| struct CannedDispatcher { |
| params: Option<ServerParameters>, |
| mock_leases: u32, |
| } |
| |
| impl CannedDispatcher { |
| fn new() -> Self { |
| Self { params: None, mock_leases: 0 } |
| } |
| } |
| |
| impl SocketServerDispatcher for CannedDispatcher { |
| type Socket = CannedSocket; |
| |
| fn create_socket(name: Option<&str>, src: Ipv4Addr) -> Result<Self::Socket, Error> { |
| Ok(CannedSocket { name: name.map(|s| s.to_string()), src }) |
| } |
| |
| fn dispatch_message(&mut self, _msg: Message) -> Result<ServerAction, ServerError> { |
| Ok(ServerAction::SendResponse(Message::new(), None)) |
| } |
| } |
| |
| impl ServerDispatcher for CannedDispatcher { |
| fn try_validate_parameters(&self) -> Result<&ServerParameters, fuchsia_zircon::Status> { |
| self.params.as_ref().ok_or(fuchsia_zircon::Status::INVALID_ARGS) |
| } |
| |
| fn dispatch_get_option( |
| &self, |
| _code: fidl_fuchsia_net_dhcp::OptionCode, |
| ) -> Result<fidl_fuchsia_net_dhcp::Option_, fuchsia_zircon::Status> { |
| Ok(fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_fuchsia_net::Ipv4Address { |
| addr: [0, 0, 0, 0], |
| })) |
| } |
| fn dispatch_get_parameter( |
| &self, |
| _name: fidl_fuchsia_net_dhcp::ParameterName, |
| ) -> Result<fidl_fuchsia_net_dhcp::Parameter, fuchsia_zircon::Status> { |
| Ok(fidl_fuchsia_net_dhcp::Parameter::Lease(fidl_fuchsia_net_dhcp::LeaseLength { |
| default: None, |
| max: None, |
| })) |
| } |
| fn dispatch_set_option( |
| &mut self, |
| _value: fidl_fuchsia_net_dhcp::Option_, |
| ) -> Result<(), fuchsia_zircon::Status> { |
| Ok(()) |
| } |
| fn dispatch_set_parameter( |
| &mut self, |
| _value: fidl_fuchsia_net_dhcp::Parameter, |
| ) -> Result<(), fuchsia_zircon::Status> { |
| Ok(()) |
| } |
| fn dispatch_list_options( |
| &self, |
| ) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, fuchsia_zircon::Status> { |
| Ok(vec![]) |
| } |
| fn dispatch_list_parameters( |
| &self, |
| ) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, fuchsia_zircon::Status> { |
| Ok(vec![]) |
| } |
| fn dispatch_reset_options(&mut self) -> Result<(), fuchsia_zircon::Status> { |
| Ok(()) |
| } |
| fn dispatch_reset_parameters( |
| &mut self, |
| _defaults: &dhcp::configuration::ServerParameters, |
| ) -> Result<(), fuchsia_zircon::Status> { |
| Ok(()) |
| } |
| fn dispatch_clear_leases(&mut self) -> Result<(), fuchsia_zircon::Status> { |
| self.mock_leases = 0; |
| Ok(()) |
| } |
| } |
| |
| fn default_params() -> dhcp::configuration::ServerParameters { |
| dhcp::configuration::ServerParameters { |
| server_ips: vec![Ipv4Addr::from([192, 168, 0, 1])], |
| lease_length: dhcp::configuration::LeaseLength { |
| default_seconds: 86400, |
| max_seconds: 86400, |
| }, |
| managed_addrs: dhcp::configuration::ManagedAddresses { |
| network_id: Ipv4Addr::from([192, 168, 0, 0]), |
| broadcast: Ipv4Addr::from([192, 168, 0, 128]), |
| mask: dhcp::configuration::SubnetMask::try_from(25).unwrap(), |
| pool_range_start: Ipv4Addr::from([192, 168, 0, 0]), |
| pool_range_stop: Ipv4Addr::from([192, 168, 0, 0]), |
| }, |
| permitted_macs: dhcp::configuration::PermittedMacs(vec![]), |
| static_assignments: dhcp::configuration::StaticAssignments(HashMap::new()), |
| arp_probe: false, |
| bound_device_names: vec![], |
| } |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn get_option_with_subnet_mask_returns_subnet_mask() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.get_option(fidl_fuchsia_net_dhcp::OptionCode::SubnetMask).fuse() => res.context("get_option failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| |
| let expected_result = |
| Ok(fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_fuchsia_net::Ipv4Address { |
| addr: [0, 0, 0, 0], |
| })); |
| assert_eq!(res, expected_result); |
| Ok(()) |
| } |
| |
| #[fasync::run_until_stalled(test)] |
| async fn get_parameter_with_lease_length_returns_lease_length() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.get_parameter(fidl_fuchsia_net_dhcp::ParameterName::LeaseLength).fuse() => res.context("get_parameter failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| let expected_result = |
| Ok(fidl_fuchsia_net_dhcp::Parameter::Lease(fidl_fuchsia_net_dhcp::LeaseLength { |
| default: None, |
| max: None, |
| })); |
| assert_eq!(res, expected_result); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn set_option_with_subnet_mask_returns_unit() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.set_option(&mut fidl_fuchsia_net_dhcp::Option_::SubnetMask( |
| fidl_fuchsia_net::Ipv4Address { addr: [0, 0, 0, 0] }, |
| )).fuse() => res.context("set_option failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| assert_eq!(res, Ok(())); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn set_parameter_with_lease_length_returns_unit() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.set_parameter(&mut fidl_fuchsia_net_dhcp::Parameter::Lease( |
| fidl_fuchsia_net_dhcp::LeaseLength { default: None, max: None }, |
| )).fuse() => res.context("set_parameter failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| assert_eq!(res, Ok(())); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn list_options_returns_empty_vec() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.list_options().fuse() => res.context("list_options failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| assert_eq!(res, Ok(vec![])); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn list_parameters_returns_empty_vec() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.list_parameters().fuse() => res.context("list_parameters failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| assert_eq!(res, Ok(vec![])); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn reset_options_returns_unit() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.reset_options().fuse() => res.context("reset_options failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| |
| assert_eq!(res, Ok(())); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn reset_parameters_returns_unit() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.reset_parameters().fuse() => res.context("reset_parameters failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| |
| assert_eq!(res, Ok(())); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn clear_leases_returns_unit() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.clear_leases().fuse() => res.context("clear_leases failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| |
| assert_eq!(res, Ok(())); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn start_stop_server() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let (socket_sink, mut socket_stream) = |
| futures::channel::mpsc::channel::<ServerSocketCollection<CannedSocket>>(1); |
| |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| // Set default parameters to the server so we can create sockets. |
| server.borrow_mut().params = Some(default_params()); |
| |
| let defaults = default_params(); |
| |
| // Set mock leases that should not change when the server is disabled. |
| server.borrow_mut().mock_leases = 1; |
| |
| let test_fut = async { |
| for _ in 0..3 { |
| let () = proxy |
| .start_serving() |
| .await |
| .context("start_serving failed")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("start_serving returned an error")?; |
| |
| let socket_collection = socket_stream |
| .next() |
| .await |
| .ok_or_else(|| anyhow::anyhow!("Socket stream ended unexpectedly"))?; |
| |
| // Assert that the sockets that would be created are correct. |
| assert_eq!( |
| socket_collection.sockets, |
| vec![CannedSocket { name: None, src: Ipv4Addr::UNSPECIFIED }] |
| ); |
| |
| // Create a dummy future that should be aborted when we disable the |
| // server. |
| let dummy_fut = futures::future::Abortable::new( |
| futures::future::pending::<()>(), |
| socket_collection.abort_registration, |
| ); |
| |
| let () = proxy.stop_serving().await.context("stop_serving failed")?; |
| |
| // Dummy future was aborted. |
| assert_eq!(dummy_fut.await, Err(futures::future::Aborted {})); |
| // Leases were not cleared. |
| assert_eq!(server.borrow().mock_leases, 1); |
| } |
| |
| Ok::<(), Error>(()) |
| }; |
| |
| let () = futures::select! { |
| res = test_fut.fuse() => res.context("test future failed"), |
| server_fut = run_server(stream, &server, &defaults, socket_sink).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn start_server_fails_on_bad_params() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| |
| let defaults = default_params(); |
| let res = futures::select! { |
| res = proxy.start_serving().fuse() => res.context("start_serving failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?.map_err(fuchsia_zircon::Status::from_raw); |
| |
| // Must have failed to start the server. |
| assert_eq!(res, Err(fuchsia_zircon::Status::INVALID_ARGS)); |
| // No abort handler must've been set. |
| assert!(server.borrow().abort_handle.is_none()); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn disallow_change_parameters_if_enabled() -> Result<(), Error> { |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?; |
| |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| // Set default parameters to the server so we can create sockets. |
| server.borrow_mut().params = Some(default_params()); |
| |
| let defaults = default_params(); |
| |
| let test_fut = async { |
| let () = proxy |
| .start_serving() |
| .await |
| .context("start_serving failed")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("start_serving returned an error")?; |
| |
| // SetParameter disallowed when the server is enabled. |
| assert_eq!( |
| proxy |
| .set_parameter(&mut fidl_fuchsia_net_dhcp::Parameter::Lease( |
| fidl_fuchsia_net_dhcp::LeaseLength { default: None, max: None }, |
| )) |
| .await |
| .context("set_parameter FIDL failure")? |
| .map_err(fuchsia_zircon::Status::from_raw), |
| Err(fuchsia_zircon::Status::BAD_STATE) |
| ); |
| |
| // ResetParameters disallowed when the server is enabled. |
| assert_eq!( |
| proxy |
| .reset_parameters() |
| .await |
| .context("reset_parameters FIDL failure")? |
| .map_err(fuchsia_zircon::Status::from_raw), |
| Err(fuchsia_zircon::Status::BAD_STATE) |
| ); |
| |
| Ok::<(), Error>(()) |
| }; |
| |
| let () = futures::select! { |
| res = test_fut.fuse() => res.context("test future failed"), |
| server_fut = run_server(stream, &server, &defaults, drain()).fuse() => Err(anyhow::Error::msg("server finished before request")), |
| }?; |
| |
| Ok(()) |
| } |
| |
| /// Test that a malformed message does not cause MessageHandler to return an |
| /// error. |
| #[test] |
| fn test_handle_failed_parse() { |
| let server = RefCell::new(ServerDispatcherRuntime::new(CannedDispatcher::new())); |
| let mut handler = MessageHandler::new(&server); |
| matches::assert_matches!( |
| handler.handle_from_sender( |
| &[0xFF, 0x00, 0xBA, 0x03], |
| std::net::SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0), |
| ), |
| Ok(None) |
| ); |
| } |
| } |