[netcfg] Cleanup DHCPv6 state on non-fatal errors
When a non-fatal error was hit when stopping the DHCPv6 client, DHCPv6
state was not properly cleared for an interface.
This change clears DHCPv6 state for an inteface when stopping a DHCPv6
client was successful and when non-fatal errors are hit.
Fixed: 68305
Test: fx test netcfg-tests -- \
test_stopping_dhcpv6_with_down_lookup_admin
Change-Id: Icb6adbda71b4079d1e0f6cec48405ba09f1f1d21
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/474753
Reviewed-by: Tamir Duberstein <tamird@google.com>
Reviewed-by: Arthur Sfez <asfez@google.com>
Commit-Queue: Ghanan Gowripalan <ghanan@google.com>
diff --git a/src/connectivity/network/netcfg/src/main.rs b/src/connectivity/network/netcfg/src/main.rs
index 704bffa..10b0482 100644
--- a/src/connectivity/network/netcfg/src/main.rs
+++ b/src/connectivity/network/netcfg/src/main.rs
@@ -804,7 +804,7 @@
// Stop DHCPv6 client if interface went down.
if !online {
- let sockaddr = match dhcpv6_client_addr {
+ let sockaddr = match dhcpv6_client_addr.take() {
Some(s) => s,
None => return Ok(()),
};
@@ -817,22 +817,19 @@
sockaddr.display_ext(),
);
- let () = dhcpv6::stop_client(
+ return dhcpv6::stop_client(
&self.lookup_admin,
&mut self.dns_servers,
id,
watchers,
)
.await
- .map(|()| *dhcpv6_client_addr = None)
.with_context(|| {
format!(
"error stopping DHCPv6 client on down interface {} (id={})",
name, id
)
- })?;
-
- return Ok(());
+ });
}
// Stop the DHCPv6 client if its address can no longer be found on the
@@ -847,6 +844,9 @@
addr == fnet::IpAddress::Ipv6(address)
},
) {
+ let sockaddr = *sockaddr;
+ *dhcpv6_client_addr = None;
+
info!(
"stopping DHCPv6 client on host interface {} (id={}) \
w/ removed sockaddr = {}",
@@ -870,7 +870,6 @@
name, id, sockaddr.display_ext()
)
})?;
- *dhcpv6_client_addr = None;
}
}
@@ -1562,88 +1561,77 @@
))
}
- #[fuchsia_async::run_singlethreaded(test)]
- async fn test_dhcpv6() -> Result<(), anyhow::Error> {
- const INTERFACE_ID: u64 = 1;
- const DHCPV6_DNS_SOURCE: DnsServersUpdateSource =
- DnsServersUpdateSource::Dhcpv6 { interface_id: INTERFACE_ID };
- const LINK_LOCAL_SOCKADDR1: fnet::Ipv6SocketAddress = fnet::Ipv6SocketAddress {
- address: fidl_ip_v6!("fe80::1"),
- port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
- zone_index: INTERFACE_ID,
- };
- const LINK_LOCAL_SOCKADDR2: fnet::Ipv6SocketAddress = fnet::Ipv6SocketAddress {
- address: fidl_ip_v6!("fe80::2"),
- port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
- zone_index: INTERFACE_ID,
- };
- const GLOBAL_ADDR: fnet::Subnet =
- fnet::Subnet { addr: fidl_ip!("2000::1"), prefix_len: 64 };
- const DNS_SERVER1: fnet::SocketAddress =
- fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
- address: fidl_ip_v6!("2001::1"),
- port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
- zone_index: 0,
- });
- const DNS_SERVER2: fnet::SocketAddress =
- fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
- address: fidl_ip_v6!("2001::2"),
- port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
- zone_index: 0,
- });
+ const INTERFACE_ID: u64 = 1;
+ const DHCPV6_DNS_SOURCE: DnsServersUpdateSource =
+ DnsServersUpdateSource::Dhcpv6 { interface_id: INTERFACE_ID };
+ const LINK_LOCAL_SOCKADDR1: fnet::Ipv6SocketAddress = fnet::Ipv6SocketAddress {
+ address: fidl_ip_v6!("fe80::1"),
+ port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
+ zone_index: INTERFACE_ID,
+ };
+ const LINK_LOCAL_SOCKADDR2: fnet::Ipv6SocketAddress = fnet::Ipv6SocketAddress {
+ address: fidl_ip_v6!("fe80::2"),
+ port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
+ zone_index: INTERFACE_ID,
+ };
+ const GLOBAL_ADDR: fnet::Subnet = fnet::Subnet { addr: fidl_ip!("2000::1"), prefix_len: 64 };
+ const DNS_SERVER1: fnet::SocketAddress = fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
+ address: fidl_ip_v6!("2001::1"),
+ port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
+ zone_index: 0,
+ });
+ const DNS_SERVER2: fnet::SocketAddress = fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
+ address: fidl_ip_v6!("2001::2"),
+ port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
+ zone_index: 0,
+ });
- fn ipv6addrs(a: Option<fnet::Ipv6SocketAddress>) -> Vec<fnet_interfaces::Address> {
- // The DHCPv6 client will only use a link-local address but we include a global address
- // and expect it to not be used.
- std::iter::once(fnet_interfaces::Address {
- addr: Some(GLOBAL_ADDR),
+ fn ipv6addrs(a: Option<fnet::Ipv6SocketAddress>) -> Vec<fnet_interfaces::Address> {
+ // The DHCPv6 client will only use a link-local address but we include a global address
+ // and expect it to not be used.
+ std::iter::once(fnet_interfaces::Address {
+ addr: Some(GLOBAL_ADDR),
+ ..fnet_interfaces::Address::EMPTY
+ })
+ .chain(a.map(|fnet::Ipv6SocketAddress { address, port: _, zone_index: _ }| {
+ fnet_interfaces::Address {
+ addr: Some(fnet::Subnet { addr: fnet::IpAddress::Ipv6(address), prefix_len: 64 }),
..fnet_interfaces::Address::EMPTY
- })
- .chain(a.map(|fnet::Ipv6SocketAddress { address, port: _, zone_index: _ }| {
- fnet_interfaces::Address {
- addr: Some(fnet::Subnet {
- addr: fnet::IpAddress::Ipv6(address),
- prefix_len: 64,
- }),
- ..fnet_interfaces::Address::EMPTY
- }
- }))
- .collect()
- }
+ }
+ }))
+ .collect()
+ }
- /// Handle receving a netstack interface changed event.
- async fn handle_interface_changed_event(
- netcfg: &mut NetCfg<'_>,
- dns_watchers: &mut DnsServerWatchers<'_>,
- online: Option<bool>,
- addresses: Option<Vec<fnet_interfaces::Address>>,
- ) -> Result<(), anyhow::Error> {
- let event = fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
- id: Some(INTERFACE_ID),
- name: None,
- device_class: None,
- online,
- addresses,
- has_default_ipv4_route: None,
- has_default_ipv6_route: None,
- ..fnet_interfaces::Properties::EMPTY
- });
- netcfg.handle_interface_watcher_event(event, dns_watchers).await.map_err(Into::into)
- }
+ /// Handle receving a netstack interface changed event.
+ async fn handle_interface_changed_event(
+ netcfg: &mut NetCfg<'_>,
+ dns_watchers: &mut DnsServerWatchers<'_>,
+ online: Option<bool>,
+ addresses: Option<Vec<fnet_interfaces::Address>>,
+ ) -> Result<(), errors::Error> {
+ let event = fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
+ id: Some(INTERFACE_ID),
+ name: None,
+ device_class: None,
+ online,
+ addresses,
+ has_default_ipv4_route: None,
+ has_default_ipv6_route: None,
+ ..fnet_interfaces::Properties::EMPTY
+ });
+ netcfg.handle_interface_watcher_event(event, dns_watchers).await
+ }
- /// Make sure that a new DHCPv6 client was requested, and verify its parameters.
- async fn check_new_client(
- server: &mut fnet_dhcpv6::ClientProviderRequestStream,
- sockaddr: fnet::Ipv6SocketAddress,
- dns_watchers: &mut DnsServerWatchers<'_>,
- ) -> Result<fnet_dhcpv6::ClientRequestStream, anyhow::Error> {
- let evt = server
- .try_next()
- .await
- .context("error getting next dhcpv6 client provider event")?;
- let client_server = match evt
- .ok_or(anyhow::anyhow!("expected dhcpv6 client provider request"))?
- {
+ /// Make sure that a new DHCPv6 client was requested, and verify its parameters.
+ async fn check_new_client(
+ server: &mut fnet_dhcpv6::ClientProviderRequestStream,
+ sockaddr: fnet::Ipv6SocketAddress,
+ dns_watchers: &mut DnsServerWatchers<'_>,
+ ) -> Result<fnet_dhcpv6::ClientRequestStream, anyhow::Error> {
+ let evt =
+ server.try_next().await.context("error getting next dhcpv6 client provider event")?;
+ let client_server =
+ match evt.ok_or(anyhow::anyhow!("expected dhcpv6 client provider request"))? {
fnet_dhcpv6::ClientProviderRequest::NewClient {
params,
request,
@@ -1670,10 +1658,131 @@
request.into_stream().context("error converting client server end to stream")?
}
};
- assert!(dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should have a watcher");
- Ok(client_server)
- }
+ assert!(dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should have a watcher");
+ Ok(client_server)
+ }
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn test_stopping_dhcpv6_with_down_lookup_admin() -> Result<(), anyhow::Error> {
+ let (mut netcfg, ServerEnds { lookup_admin, mut dhcpv6_client_provider }) =
+ test_netcfg().context("error creating test netcfg")?;
+ let mut dns_watchers = DnsServerWatchers::empty();
+
+ // Mock a new interface being discovered by NetCfg (we only need to make NetCfg aware of a
+ // NIC with ID `INTERFACE_ID` to test DHCPv6).
+ matches::assert_matches!(
+ netcfg.interface_states.insert(INTERFACE_ID, InterfaceState::new_host()),
+ None
+ );
+
+ // Should start the DHCPv6 client when we get an interface changed event that shows the
+ // interface as up with an link-local address.
+ let () = netcfg
+ .handle_interface_watcher_event(
+ fnet_interfaces::Event::Added(fnet_interfaces::Properties {
+ id: Some(INTERFACE_ID),
+ name: Some("testif01".to_string()),
+ device_class: Some(fnet_interfaces::DeviceClass::Loopback(
+ fnet_interfaces::Empty {},
+ )),
+ online: Some(true),
+ addresses: Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR1))),
+ has_default_ipv4_route: Some(false),
+ has_default_ipv6_route: Some(false),
+ ..fnet_interfaces::Properties::EMPTY
+ }),
+ &mut dns_watchers,
+ )
+ .await
+ .context("error handling interface added event with interface up and sockaddr1")
+ .map_err::<anyhow::Error, _>(Into::into)?;
+ let _: fnet_dhcpv6::ClientRequestStream =
+ check_new_client(&mut dhcpv6_client_provider, LINK_LOCAL_SOCKADDR1, &mut dns_watchers)
+ .await
+ .context("error checking for new client with sockaddr1")?;
+
+ // Drop the server-end of the lookup admin to simulate a down lookup admin service.
+ let () = std::mem::drop(lookup_admin);
+
+ // Not having any more link local IPv6 addresses should terminate the client.
+ let () = handle_interface_changed_event(
+ &mut netcfg,
+ &mut dns_watchers,
+ None,
+ Some(ipv6addrs(None)),
+ )
+ .await
+ .context("error handling interface changed event with sockaddr1 removed")
+ .or_else(|e| match e {
+ errors::Error::NonFatal(e) => {
+ println!("non-fatal error: {:?}", e);
+ Ok(())
+ }
+ errors::Error::Fatal(e) => Err(e),
+ })?;
+
+ // Another update without any link-local IPv6 addresses should do nothing
+ // since the DHCPv6 client was already stopped.
+ let () =
+ handle_interface_changed_event(&mut netcfg, &mut dns_watchers, None, Some(Vec::new()))
+ .await
+ .context("error handling interface changed event with sockaddr1 removed")
+ .or_else(|e| match e {
+ errors::Error::NonFatal(e) => {
+ println!("non-fatal error: {:?}", e);
+ Ok(())
+ }
+ errors::Error::Fatal(e) => Err(e),
+ })?;
+
+ // Update interface with a link-local address to create a new DHCPv6 client.
+ let () = handle_interface_changed_event(
+ &mut netcfg,
+ &mut dns_watchers,
+ None,
+ Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR1))),
+ )
+ .await
+ .context("error handling interface changed event with sockaddr1 removed")
+ .or_else(|e| match e {
+ errors::Error::NonFatal(e) => {
+ println!("non-fatal error: {:?}", e);
+ Ok(())
+ }
+ errors::Error::Fatal(e) => Err(e),
+ })?;
+ let _: fnet_dhcpv6::ClientRequestStream =
+ check_new_client(&mut dhcpv6_client_provider, LINK_LOCAL_SOCKADDR1, &mut dns_watchers)
+ .await
+ .context("error checking for new client with sockaddr1")?;
+
+ // Update offline status to down to stop DHCPv6 client.
+ let () = handle_interface_changed_event(&mut netcfg, &mut dns_watchers, Some(false), None)
+ .await
+ .context("error handling interface changed event with sockaddr1 removed")
+ .or_else(|e| match e {
+ errors::Error::NonFatal(e) => {
+ println!("non-fatal error: {:?}", e);
+ Ok(())
+ }
+ errors::Error::Fatal(e) => Err(e),
+ })?;
+
+ // Update interface with new addresses but leave offline status as down.
+ handle_interface_changed_event(&mut netcfg, &mut dns_watchers, None, Some(ipv6addrs(None)))
+ .await
+ .context("error handling interface changed event with sockaddr1 removed")
+ .or_else(|e| match e {
+ errors::Error::NonFatal(e) => {
+ println!("non-fatal error: {:?}", e);
+ Ok(())
+ }
+ errors::Error::Fatal(e) => Err(e),
+ })
+ }
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn test_dhcpv6() -> Result<(), anyhow::Error> {
/// Waits for a `SetDnsServers` request with the specified servers.
async fn run_lookup_admin_once(
server: &mut fnet_name::LookupAdminRequestStream,
@@ -1808,7 +1917,8 @@
Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR2))),
)
.await
- .context("error handling netstack event with sockaddr2 added")?;
+ .context("error handling netstack event with sockaddr2 added")
+ .map_err(Into::<anyhow::Error>::into)?;
let mut client_server = check_new_client(
&mut servers.dhcpv6_client_provider,
LINK_LOCAL_SOCKADDR2,
@@ -1825,7 +1935,8 @@
Some(false), /* down */
None,
)
- .map(|r| r.context("error handling interface changed event with interface down")),
+ .map(|r| r.context("error handling interface changed event with interface down"))
+ .map_err(Into::into),
run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers)
.map(|r| r.context("error running lookup admin")),
)
@@ -1843,7 +1954,8 @@
None,
)
.await
- .context("error handling interface up event")?;
+ .context("error handling interface up event")
+ .map_err(Into::<anyhow::Error>::into)?;
let mut client_server = check_new_client(
&mut servers.dhcpv6_client_provider,
LINK_LOCAL_SOCKADDR2,
@@ -1865,7 +1977,8 @@
r.context(
"error handling interface change event with sockaddr1 replacing sockaddr2",
)
- }),
+ })
+ .map_err(Into::into),
run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers)
.map(|r| r.context("error running lookup admin")),
)
@@ -1898,7 +2011,8 @@
Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR2))),
)
.await
- .context("error handling interface change event with sockaddr2 replacing sockaddr1")?;
+ .context("error handling interface change event with sockaddr2 replacing sockaddr1")
+ .map_err(Into::<anyhow::Error>::into)?;
let mut client_server = check_new_client(
&mut servers.dhcpv6_client_provider,
LINK_LOCAL_SOCKADDR2,