[dhcp] Add reset_*() to fuchsia.net.dhcp.Server

Add reset_options() and reset_parameters() FIDL methods to
fuchsia.net.dhcp.Server, implement these methods in dhcpd, use them
in dhcpd-cli, and add tests covering this functionality.

Test: - fx run-test dhcp_tests
  - fx run-test dhcpd_tests
  - fx run-test dhcpd_cli_tests
Bug: 109
Change-Id: If2609991c1ac913079089c85f53736125fb1da30
diff --git a/sdk/fidl/fuchsia.net.dhcp/fuchsia.net.dhcp.api b/sdk/fidl/fuchsia.net.dhcp/fuchsia.net.dhcp.api
index 47be32e..02d004c 100644
--- a/sdk/fidl/fuchsia.net.dhcp/fuchsia.net.dhcp.api
+++ b/sdk/fidl/fuchsia.net.dhcp/fuchsia.net.dhcp.api
@@ -1,5 +1,5 @@
 {
   "fidl/fuchsia.net.dhcp/client.fidl": "ed712ac30e34dd3900d88e730a346ce6",
   "fidl/fuchsia.net.dhcp/options.fidl": "dab189ad92092bfaa3c910fe34f9609d",
-  "fidl/fuchsia.net.dhcp/server.fidl": "5d4d1dd038043a108f08bcca7aa00924"
+  "fidl/fuchsia.net.dhcp/server.fidl": "f85984b657e00e24424697f7ce414ee8"
 }
\ No newline at end of file
diff --git a/sdk/fidl/fuchsia.net.dhcp/server.fidl b/sdk/fidl/fuchsia.net.dhcp/server.fidl
index 1f912bc..8b30bf2 100644
--- a/sdk/fidl/fuchsia.net.dhcp/server.fidl
+++ b/sdk/fidl/fuchsia.net.dhcp/server.fidl
@@ -106,37 +106,61 @@
     /// * error a zx.status indicating why the value could not be retrieved.
     GetParameter(ParameterName name) -> (Parameter value) error zx.status;
 
-    /// Sets the Option to the argument. Each SetOption call is treated as its own atomic
-    /// transaction. On success, a SetOption will take effect immediately.
+    /// Sets the Option to the argument. On success, a SetOption will take
+    /// effect immediately.
     ///
-    /// + request `value` an Option whose value will be set to the value of this argument.
+    /// + request `value` an Option whose value will be set to the value of this
+    /// argument.
     /// * error a zx.status indicating the cause of failure.
     SetOption(Option value) -> () error zx.status;
 
-    /// Sets the Parameter to the argument. Each SetParameter call is treated as its own atomic
-    /// transaction. On success, a SetParameter will take effect immediately.
+    /// Sets the Parameter to the argument. On success, the new parameter value
+    /// can be queried by GetParameter or ListParameter immediately. However,
+    /// the server may require a restart in order for the new Parameter value to
+    /// take effect. To ensure expected operation, administrators should restart
+    /// the server after mutating its parameters with SetParameter or
+    /// ResetParameters.
     ///
-    /// + request `value` a Parameter whose value will be set to the value of this argument.
+    /// + request `value` a Parameter whose value will be set to the value of
+    /// this argument.
     /// * error a zx.status indicating the cause of failure.
     SetParameter(Parameter value) -> () error zx.status;
 
-    /// Lists all DHCP options for which the Server has a value. Any option which does
-    /// not have a value will be omitted from the returned list. ListOptions provides administrators
-    /// a means to print a server's configuration as opposed to querying the value of a single
-    /// Option.
+    /// Lists all DHCP options for which the Server has a value. Any option
+    /// which does not have a value will be omitted from the returned list.
+    /// ListOptions provides administrators a means to print a server's
+    /// configuration as opposed to querying the value of a single Option.
     ///
-    /// - response `options` a vector containing all of the options for which the Server has a
-    /// value. Bounded to 256 as options are identified by a 1 octet code and 256 is the maximum
-    /// number of such codes.
+    /// - response `options` a vector containing all of the options for which
+    /// the Server has a value. Bounded to 256 as options are identified by a 1
+    /// octet code and 256 is the maximum number of such codes.
     /// * error a zx.status indicating the cause of failure.
     ListOptions() -> (vector<Option>:256 options) error zx.status;
 
-    /// Lists all DHCP server parameters. ListParameters provides administrators a means to print a
-    /// server's configuration as opposed to querying the value of a single Parameter.
+    /// Lists all DHCP server parameters. ListParameters provides administrators
+    /// a means to print a server's configuration as opposed to querying the
+    /// value of a single Parameter.
     ///
-    /// - response `parameter` a vector containing the values of all of the Server's parameters.
-    /// Bounded to 256 to provide a generous upper limit on the number of server parameters while
-    /// being of the same size as ListOptions.
+    /// - response `parameter` a vector containing the values of all of the
+    /// Server's parameters. Bounded to 256 to provide a generous upper limit
+    /// on the number of server parameters while being of the same size as
+    /// ListOptions.
     /// * error a zx.status indicating the cause of failure.
     ListParameters() -> (vector<Parameter>:256 parameters) error zx.status;
+
+    /// Resets all DHCP options to have no value. On success, ResetOptions will
+    /// take effect immediately.
+    ///
+    /// * error a zx.status indicating the cause of failure.
+    ResetOptions() -> () error zx.status;
+
+    /// Resets all DHCP server parameters to their default value. On success,
+    /// the reset parameter values can be queried immediately with GetParameter
+    /// or ListParameters. However, the server must be restarted before all new
+    /// parameter values take effect. To ensure expected operation,
+    /// administrators should restart the server after mutating its parameters
+    /// with SetParameter or ResetParameters.
+    ///
+    /// * error a zx.status indicating the cause of failure.
+    ResetParameters() -> () error zx.status;
 };
diff --git a/src/connectivity/network/dhcp/src/configuration.rs b/src/connectivity/network/dhcp/src/configuration.rs
index 2638b05..e0d1f2e 100644
--- a/src/connectivity/network/dhcp/src/configuration.rs
+++ b/src/connectivity/network/dhcp/src/configuration.rs
@@ -21,7 +21,7 @@
 }
 
 /// A collection of the basic configuration parameters needed by the server.
-#[derive(Debug, Deserialize, PartialEq, Serialize)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ServerParameters {
     /// The IPv4 addresses of the host running the server.
     pub server_ips: Vec<Ipv4Addr>,
diff --git a/src/connectivity/network/dhcp/src/main.rs b/src/connectivity/network/dhcp/src/main.rs
index 6f2876d..b8c3f3a 100644
--- a/src/connectivity/network/dhcp/src/main.rs
+++ b/src/connectivity/network/dhcp/src/main.rs
@@ -14,7 +14,6 @@
     },
     fuchsia_async::{self as fasync, net::UdpSocket, Interval},
     fuchsia_component::server::ServiceFs,
-    fuchsia_syslog::{self as fx_syslog, fx_log_err, fx_log_info},
     fuchsia_zircon::DurationNum,
     futures::{Future, FutureExt, StreamExt, TryFutureExt, TryStreamExt},
     net2::unix::UnixUdpBuilderExt,
@@ -58,16 +57,17 @@
 
 #[fasync::run_singlethreaded]
 async fn main() -> Result<(), Error> {
-    fx_syslog::init_with_tags(&["dhcpd"])?;
+    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 params = stash.load_parameters().await.or_else(|e| {
-        fx_syslog::fx_log_warn!("failed to load parameters from stash: {}", e);
-        configuration::load_server_params_from_file(&config)
-            .context("failed to load server parameters from configuration file")
-    })?;
+    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 socks = if params.bound_device_names.len() > 0 {
         params.bound_device_names.iter().map(String::as_str).try_fold::<_, _, Result<_, Error>>(
             Vec::new(),
@@ -81,14 +81,14 @@
         vec![create_socket(None)?]
     };
     if socks.len() == 0 {
-        return Err(anyhow::format_err!("no valid sockets to receive messages from"));
+        return Err(anyhow::Error::msg("no valid sockets to receive messages from"));
     }
     let options = stash.load_options().await.unwrap_or_else(|e| {
-        fx_syslog::fx_log_warn!("failed to load options from stash: {}", e);
+        log::warn!("failed to load options from stash: {:?}", e);
         std::collections::HashMap::new()
     });
     let cache = stash.load_client_configs().await.unwrap_or_else(|e| {
-        fx_syslog::fx_log_warn!("failed to load cached client config from stash: {}", e);
+        log::warn!("failed to load cached client config from stash: {:?}", e);
         std::collections::HashMap::new()
     });
     let server = RefCell::new(Server::new(stash, params, options, cache));
@@ -100,21 +100,23 @@
         fs.then(futures::future::ok).try_for_each_concurrent(None, |incoming_service| async {
             match incoming_service {
                 IncomingService::Server(stream) => {
-                    run_server(stream, &server).inspect_err(|e| log::info!("{:?}", e)).await?;
+                    run_server(stream, &server, &default_params)
+                        .inspect_err(|e| log::warn!("run_server failed: {:?}", e))
+                        .await?;
                     Ok(())
                 }
             }
         });
 
     if !server.borrow().is_serving() {
-        fx_log_info!("starting server in configuration only mode");
+        log::info!("starting server in configuration only mode");
         let () = admin_fut.await?;
     } else {
         let msg_loops = socks
             .into_iter()
             .map(|sock| define_msg_handling_loop_future(sock, &server).boxed_local());
         let lease_expiration_handler = define_lease_expiration_handler_future(&server);
-        fx_log_info!("starting server");
+        log::info!("starting server");
         let (_void, (), ()) = futures::try_join!(
             futures::future::select_ok(msg_loops),
             admin_fut,
@@ -163,18 +165,18 @@
     loop {
         let (received, mut sender) =
             sock.recv_from(&mut buf).await.context("failed to read from socket")?;
-        fx_log_info!("received message from: {}", sender);
+        log::info!("received message from: {}", sender);
         let msg = Message::from_buffer(&buf[..received])?;
-        fx_log_info!("parsed message: {:?}", msg);
+        log::info!("parsed message: {:?}", msg);
 
         // This call should not block because the server is single-threaded.
         let result = server.borrow_mut().dispatch(msg);
         match result {
-            Err(e) => fx_log_err!("error processing client message: {}", e),
-            Ok(ServerAction::AddressRelease(addr)) => fx_log_info!("released address: {}", addr),
-            Ok(ServerAction::AddressDecline(addr)) => fx_log_info!("allocated address: {}", addr),
+            Err(e) => log::error!("error processing client message: {:?}", e),
+            Ok(ServerAction::AddressRelease(addr)) => log::info!("released address: {}", addr),
+            Ok(ServerAction::AddressDecline(addr)) => log::info!("allocated address: {}", addr),
             Ok(ServerAction::SendResponse(message, dest)) => {
-                fx_log_info!("generated response: {:?}", message);
+                log::info!("generated response: {:?}", message);
 
                 // Check if server returned an explicit destination ip.
                 if let Some(addr) = dest {
@@ -183,7 +185,7 @@
 
                 let response_buffer = message.serialize();
                 sock.send_to(&response_buffer, sender).await.context("unable to send response")?;
-                fx_log_info!("response sent to: {}", sender);
+                log::info!("response sent to: {}", sender);
             }
         }
     }
@@ -202,6 +204,7 @@
 async fn run_server<S: ServerDispatcher>(
     stream: fidl_fuchsia_net_dhcp::Server_RequestStream,
     server: &RefCell<S>,
+    default_params: &dhcp::configuration::ServerParameters,
 ) -> Result<(), fidl::Error> {
     stream
         .try_for_each(|request| async {
@@ -229,6 +232,15 @@
                 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()
+                        .dispatch_reset_parameters(&default_params)
+                        .map_err(|e| e.into_raw()),
+                ),
             }
         })
         .await
@@ -237,6 +249,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use std::convert::TryFrom;
 
     struct CannedDispatcher {}
 
@@ -280,6 +293,38 @@
         ) -> 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 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(
+                std::collections::HashMap::new(),
+            ),
+            arp_probe: false,
+            bound_device_names: vec![],
+        }
     }
 
     #[fasync::run_singlethreaded(test)]
@@ -288,11 +333,11 @@
             fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?;
         let server = RefCell::new(CannedDispatcher {});
 
-        let res = proxy.get_option(fidl_fuchsia_net_dhcp::OptionCode::SubnetMask);
-        fasync::spawn_local(async move {
-            let () = run_server(stream, &server).await.unwrap_or(());
-        });
-        let res = res.await?;
+        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).fuse() => Err(anyhow::Error::msg("server finished before request")),
+        }?;
 
         let expected_result =
             Ok(fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_fuchsia_net::Ipv4Address {
@@ -308,12 +353,11 @@
             fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?;
         let server = RefCell::new(CannedDispatcher {});
 
-        let res = proxy.get_parameter(fidl_fuchsia_net_dhcp::ParameterName::LeaseLength);
-        fasync::spawn_local(async move {
-            let () = run_server(stream, &server).await.unwrap_or(());
-        });
-        let res = res.await?;
-
+        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).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,
@@ -329,14 +373,13 @@
             fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?;
         let server = RefCell::new(CannedDispatcher {});
 
-        let res = proxy.set_option(&mut fidl_fuchsia_net_dhcp::Option_::SubnetMask(
+        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] },
-        ));
-        fasync::spawn_local(async move {
-            let () = run_server(stream, &server).await.unwrap_or(());
-        });
-        let res = res.await?;
-
+        )).fuse() => res.context("set_option failed"),
+            server_fut = run_server(stream, &server, &defaults).fuse() => Err(anyhow::Error::msg("server finished before request")),
+        }?;
         assert_eq!(res, Ok(()));
         Ok(())
     }
@@ -347,14 +390,13 @@
             fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?;
         let server = RefCell::new(CannedDispatcher {});
 
-        let res = proxy.set_parameter(&mut fidl_fuchsia_net_dhcp::Parameter::Lease(
+        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 },
-        ));
-        fasync::spawn_local(async move {
-            let () = run_server(stream, &server).await.unwrap_or(());
-        });
-        let res = res.await?;
-
+        )).fuse() => res.context("set_parameter failed"),
+            server_fut = run_server(stream, &server, &defaults).fuse() => Err(anyhow::Error::msg("server finished before request")),
+        }?;
         assert_eq!(res, Ok(()));
         Ok(())
     }
@@ -365,12 +407,11 @@
             fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?;
         let server = RefCell::new(CannedDispatcher {});
 
-        let res = proxy.list_options();
-        fasync::spawn_local(async move {
-            let () = run_server(stream, &server).await.unwrap_or(());
-        });
-        let res = res.await?;
-
+        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).fuse() => Err(anyhow::Error::msg("server finished before request")),
+        }?;
         assert_eq!(res, Ok(vec![]));
         Ok(())
     }
@@ -381,13 +422,44 @@
             fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_dhcp::Server_Marker>()?;
         let server = RefCell::new(CannedDispatcher {});
 
-        let res = proxy.list_parameters();
-        fasync::spawn_local(async move {
-            let () = run_server(stream, &server).await.unwrap_or(());
-        });
-        let res = res.await?;
-
+        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).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(CannedDispatcher {});
+
+        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).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(CannedDispatcher {});
+
+        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).fuse() => Err(anyhow::Error::msg("server finished before request")),
+        }?;
+
+        assert_eq!(res, Ok(()));
+        Ok(())
+    }
 }
diff --git a/src/connectivity/network/dhcp/src/server.rs b/src/connectivity/network/dhcp/src/server.rs
index b858143..52ac266 100644
--- a/src/connectivity/network/dhcp/src/server.rs
+++ b/src/connectivity/network/dhcp/src/server.rs
@@ -655,6 +655,10 @@
     fn dispatch_list_options(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, Status>;
     /// Retrieves all of the stored DHCP parameter values.
     fn dispatch_list_parameters(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, Status>;
+    /// Resets all DHCP options to have no value.
+    fn dispatch_reset_options(&mut self) -> Result<(), Status>;
+    /// Resets all DHCP server parameters to their default values in `defaults`.
+    fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status>;
 }
 
 impl ServerDispatcher for Server {
@@ -844,6 +848,25 @@
             fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(bound_device_names.clone()),
         ])
     }
+
+    fn dispatch_reset_options(&mut self) -> Result<(), Status> {
+        let () = self.options_repo.clear();
+        let opts: Vec<DhcpOption> = self.options_repo.values().cloned().collect();
+        let () = self.stash.store_options(&opts).map_err(|e| {
+            log::warn!("store_options({:?}) in stash failed: {}", opts, e);
+            fuchsia_zircon::Status::INTERNAL
+        })?;
+        Ok(())
+    }
+
+    fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status> {
+        self.params = defaults.clone();
+        let () = self.stash.store_parameters(&self.params).map_err(|e| {
+            log::warn!("store_parameters({:?}) in stash failed: {}", self.params, e);
+            fuchsia_zircon::Status::INTERNAL
+        })?;
+        Ok(())
+    }
 }
 
 /// A cache mapping clients to their configuration data.
@@ -3040,4 +3063,42 @@
         assert!(result.contains(&expected));
         Ok(())
     }
+
+    #[fuchsia_async::run_singlethreaded(test)]
+    async fn test_server_dispatcher_reset_options() -> Result<(), Error> {
+        let mut server = new_test_minimal_server().await?;
+        let empty_map = HashMap::new();
+        assert_ne!(empty_map, server.options_repo);
+        let () = server.dispatch_reset_options()?;
+        assert_eq!(empty_map, server.options_repo);
+        let stored_opts = server.stash.load_options().await?;
+        assert_eq!(empty_map, stored_opts);
+        Ok(())
+    }
+
+    #[fuchsia_async::run_singlethreaded(test)]
+    async fn test_server_dispatcher_reset_parameters() -> Result<(), Error> {
+        let mut server = new_test_minimal_server().await?;
+        let default_params = ServerParameters {
+            server_ips: vec![Ipv4Addr::from([192, 168, 0, 1])],
+            lease_length: LeaseLength { default_seconds: 86400, max_seconds: 86400 },
+            managed_addrs: crate::configuration::ManagedAddresses {
+                network_id: Ipv4Addr::from([192, 168, 0, 0]),
+                broadcast: Ipv4Addr::from([192, 168, 0, 128]),
+                mask: crate::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: crate::configuration::PermittedMacs(vec![]),
+            static_assignments: crate::configuration::StaticAssignments(HashMap::new()),
+            arp_probe: false,
+            bound_device_names: vec![],
+        };
+        assert_ne!(default_params, server.params);
+        let () = server.dispatch_reset_parameters(&default_params)?;
+        assert_eq!(default_params, server.params);
+        let stored_params = server.stash.load_parameters().await?;
+        assert_eq!(default_params, stored_params);
+        Ok(())
+    }
 }
diff --git a/src/connectivity/network/dhcpd-cli/src/args.rs b/src/connectivity/network/dhcpd-cli/src/args.rs
index 5e2b535..d33108c 100644
--- a/src/connectivity/network/dhcpd-cli/src/args.rs
+++ b/src/connectivity/network/dhcpd-cli/src/args.rs
@@ -25,6 +25,8 @@
     Set(Set),
     /// a primary command to list the values of all DHCP options or server parameters.
     List(List),
+    /// a primary command to reset the values of all DHCP options or server parameters.
+    Reset(Reset),
 }
 
 /// A primary command to retrieve the value of a DHCP option or server parameter.
@@ -51,6 +53,14 @@
     pub arg: ListArg,
 }
 
+/// A primary command to reset the values of all DHCP options or server parameters.
+#[derive(Debug, FromArgs)]
+#[argh(subcommand, name = "reset")]
+pub struct Reset {
+    #[argh(subcommand)]
+    pub arg: ResetArg,
+}
+
 #[derive(Debug, FromArgs)]
 #[argh(subcommand)]
 pub enum GetArg {
@@ -75,6 +85,14 @@
     Parameter(ParameterToken),
 }
 
+/// A primary command argument to reset the values of all DHCP options or server parameters.
+#[derive(Debug, FromArgs)]
+#[argh(subcommand)]
+pub enum ResetArg {
+    Option(OptionToken),
+    Parameter(ParameterToken),
+}
+
 /// A secondary command indicating a DHCP option argument.
 #[derive(Debug, FromArgs)]
 #[argh(subcommand, name = "option")]
diff --git a/src/connectivity/network/dhcpd-cli/src/lib.rs b/src/connectivity/network/dhcpd-cli/src/lib.rs
index fcf616e..37831f2 100644
--- a/src/connectivity/network/dhcpd-cli/src/lib.rs
+++ b/src/connectivity/network/dhcpd-cli/src/lib.rs
@@ -234,3 +234,77 @@
     }])
     .await
 }
+
+#[fuchsia_async::run_singlethreaded(test)]
+async fn test_reset_option() {
+    test_cli(vec![
+        Command {
+            args: vec!["set", "option", "subnet-mask", "--mask", "255.255.255.0"],
+            expected_stdout: "",
+            expected_stderr: "",
+        },
+        Command {
+            args: vec!["list", "option"],
+            expected_stdout: r#"[
+    SubnetMask(
+        Ipv4Address {
+            addr: [
+                255,
+                255,
+                255,
+                0,
+            ],
+        },
+    ),
+]
+"#,
+            expected_stderr: "",
+        },
+        Command { args: vec!["reset", "option"], expected_stdout: "", expected_stderr: "" },
+        Command { args: vec!["list", "option"], expected_stdout: "[]\n", expected_stderr: "" },
+    ])
+    .await
+}
+
+#[fuchsia_async::run_singlethreaded(test)]
+async fn test_reset_parameter() {
+    test_cli(vec![
+        Command {
+            args: vec!["set", "parameter", "lease-length", "--default", "42"],
+            expected_stdout: "",
+            expected_stderr: "",
+        },
+        Command {
+            args: vec!["get", "parameter", "lease-length"],
+            expected_stdout: r#"Lease(
+    LeaseLength {
+        default: Some(
+            42,
+        ),
+        max: Some(
+            42,
+        ),
+    },
+)
+"#,
+            expected_stderr: "",
+        },
+        Command { args: vec!["reset", "parameter"], expected_stdout: "", expected_stderr: "" },
+        Command {
+            args: vec!["get", "parameter", "lease-length"],
+            expected_stdout: r#"Lease(
+    LeaseLength {
+        default: Some(
+            86400,
+        ),
+        max: Some(
+            86400,
+        ),
+    },
+)
+"#,
+            expected_stderr: "",
+        },
+    ])
+    .await
+}
diff --git a/src/connectivity/network/dhcpd-cli/src/main.rs b/src/connectivity/network/dhcpd-cli/src/main.rs
index 925e1eb..2287012 100644
--- a/src/connectivity/network/dhcpd-cli/src/main.rs
+++ b/src/connectivity/network/dhcpd-cli/src/main.rs
@@ -20,6 +20,7 @@
         Cli { cmd: Command::Get(get_arg) } => do_get(get_arg, server).await?,
         Cli { cmd: Command::Set(set_arg) } => do_set(set_arg, server).await?,
         Cli { cmd: Command::List(list_arg) } => do_list(list_arg, server).await?,
+        Cli { cmd: Command::Reset(reset_arg) } => do_reset(reset_arg, server).await?,
     };
 
     Ok(())
@@ -74,7 +75,7 @@
                 .list_options()
                 .await?
                 .map_err(|e| fuchsia_zircon::Status::from_raw(e))
-                .with_context(|| "list_options() failed")?;
+                .context("list_options() failed")?;
 
             println!("{:#?}", res);
         }
@@ -83,9 +84,29 @@
                 .list_parameters()
                 .await?
                 .map_err(|e| fuchsia_zircon::Status::from_raw(e))
-                .with_context(|| "list_parameters() failed")?;
+                .context("list_parameters() failed")?;
             println!("{:#?}", res);
         }
     };
     Ok(())
 }
+
+async fn do_reset(reset_arg: Reset, server: Server_Proxy) -> Result<(), Error> {
+    match reset_arg.arg {
+        ResetArg::Option(OptionToken {}) => {
+            let () = server
+                .reset_options()
+                .await?
+                .map_err(fuchsia_zircon::Status::from_raw)
+                .context("reset_options() failed")?;
+        }
+        ResetArg::Parameter(ParameterToken {}) => {
+            let () = server
+                .reset_parameters()
+                .await?
+                .map_err(fuchsia_zircon::Status::from_raw)
+                .context("reset_parameters() failed")?;
+        }
+    };
+    Ok(())
+}