blob: b31939b88f04b611e04af08f6431119523006895 [file] [log] [blame]
// Copyright 2019 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 logic for the Network Manager.
//!
//! This crate implements the logic for the Network Manager, not including
//! the details of execution (serving FIDL, futures execution, etc).
extern crate network_manager_core_interface as interface;
#[macro_use]
extern crate log;
pub mod address;
pub mod config;
pub mod error;
pub mod hal;
pub mod lifmgr;
pub mod oir;
pub mod packet_filter;
pub mod portmgr;
mod servicemgr;
use {
crate::config::InterfaceType,
crate::lifmgr::{LIFProperties, LIFType},
crate::portmgr::PortId,
fidl_fuchsia_net_stack as stack, fidl_fuchsia_netstack as netstack,
fidl_fuchsia_router_config as netconfig,
std::sync::atomic::{AtomicUsize, Ordering},
};
// TODO(cgibson): Remove this when we have the ability to add one port at a time the bridge.
const NUMBER_OF_PORTS_IN_LAN: usize = 3;
/// `DeviceState` holds the device state.
pub struct DeviceState {
version: Version, // state version.
port_manager: portmgr::PortManager,
lif_manager: lifmgr::LIFManager,
service_manager: servicemgr::Manager,
packet_filter: packet_filter::PacketFilter,
dns_config: DnsConfig,
hal: hal::NetCfg,
config: config::Config,
lans: Vec<PortId>,
}
#[derive(Debug, Clone, Copy)]
pub enum DnsPolicy {
/// Can not be replaced by dynamically learned DNS configuration,
/// will overwrite existing configuration.
Static,
/// Can be replaced by dynamically learned DNS configuration,
/// will not overwrite existing configuration.
Replaceable,
/// Will merge with existing configuration.
Merge,
}
impl From<DnsPolicy> for netconfig::DnsPolicy {
fn from(p: DnsPolicy) -> Self {
match p {
DnsPolicy::Static => netconfig::DnsPolicy::Static,
DnsPolicy::Replaceable => netconfig::DnsPolicy::Replaceable,
DnsPolicy::Merge => netconfig::DnsPolicy::Merge,
}
}
}
impl Default for DnsPolicy {
fn default() -> Self {
DnsPolicy::Static
}
}
impl From<netconfig::DnsPolicy> for DnsPolicy {
fn from(p: netconfig::DnsPolicy) -> Self {
match p {
netconfig::DnsPolicy::Static => DnsPolicy::Static,
netconfig::DnsPolicy::Replaceable => DnsPolicy::Replaceable,
netconfig::DnsPolicy::Merge => DnsPolicy::Merge,
_ => DnsPolicy::default(),
}
}
}
#[derive(Debug)]
struct DnsConfig {
id: ElementId,
servers: Vec<fidl_fuchsia_net::IpAddress>,
domain: Option<String>,
policy: DnsPolicy,
}
impl From<&DnsConfig> for netconfig::DnsResolverConfig {
fn from(c: &DnsConfig) -> Self {
netconfig::DnsResolverConfig {
element: c.id.into(),
policy: c.policy.into(),
search: netconfig::DnsSearch {
domain_name: c.domain.clone(),
servers: c.servers.clone(),
},
}
}
}
impl DeviceState {
//! Create an empty DeviceState.
pub fn new(hal: hal::NetCfg, packet_filter: packet_filter::PacketFilter) -> Self {
let v = 0;
DeviceState {
version: v,
port_manager: portmgr::PortManager::new(),
lif_manager: lifmgr::LIFManager::new(),
service_manager: servicemgr::Manager::new(),
packet_filter,
hal,
lans: Vec::new(),
dns_config: DnsConfig {
id: ElementId::new(v),
servers: Default::default(),
domain: Default::default(),
policy: Default::default(),
},
config: config::Config::new(
"/data/user_config.json",
"/config/data/factory_config.json",
"/pkg/data/device_schema.json",
),
}
}
/// Loads the device configuration.
pub async fn load_config(&mut self) -> error::Result<()> {
if let Err(e) = self.config.load_config().await {
error!("Failed to validate config from both user and factory files: {}", e);
Err(e)
} else {
info!("Successfully loaded configuration!");
Ok(())
}
}
/// Returns the underlying event streams associated with the open channels to fuchsia.net.stack
/// and fuchsia.netstack.
pub fn take_event_streams(
&mut self,
) -> (stack::StackEventStream, netstack::NetstackEventStream) {
self.hal.take_event_streams()
}
/// Informs network manager of an external event from fuchsia.net.stack containing updates to
/// properties associated with an interface. OnInterfaceStatusChange event is raised when an
/// interface is enabled/disabled, connected/disconnected, or added/removed.
pub async fn update_state_for_stack_event(
&mut self,
event: stack::StackEvent,
) -> error::Result<()> {
match event {
stack::StackEvent::OnInterfaceStatusChange { info } => {
match self.hal.get_interface(info.id).await {
Some(iface) => self.notify_lif_added_or_updated(iface),
None => Ok(()),
}
}
}
}
/// Informs network manager of an external event from fuchsia.netstack containing updates to
/// properties associated with an interface.
pub async fn update_state_for_netstack_event(
&mut self,
event: netstack::NetstackEvent,
) -> error::Result<()> {
match event {
netstack::NetstackEvent::OnInterfacesChanged { interfaces } => {
interfaces.into_iter().try_for_each(|iface| {
let i = &iface;
self.notify_lif_added_or_updated(i.into())
})
}
}
}
/// Restores the `topo_path`'s configured packet filters.
///
/// This method uses [`portmgr::PortId`]'s to install the packet filter rule on the correct
/// interface. This means that the port must be registered with port manager before this method
/// is called otherwise the `nicid` lookup will fail.
pub async fn apply_packet_filters(&self, topo_path: &str) -> error::Result<()> {
let acls = self
.config
.get_acl_entries(topo_path)
.ok_or_else(|| error::NetworkManager::Config(error::Config::FailedToGetAclEntries))?;
let pid = self
.port_manager
.port_id(topo_path)
.ok_or_else(|| error::NetworkManager::Port(error::Port::NotFound))?;
for entry in acls {
for rule in self.packet_filter.parse_aclentry(entry).await?.iter() {
self.set_filter_on_interface(rule, pid.to_u32()).await?;
}
}
Ok(())
}
/// Restores state of global system options and services from stored configuration.
pub async fn setup_services(&mut self) -> error::Result<()> {
info!("Restoring system services state...");
self.packet_filter.clear_filters().await?;
self.hal.set_ip_forwarding(self.config.get_ip_forwarding_state()).await?;
if self.config.get_nat_state() {
self.service_manager.enable_nat();
match self.packet_filter.update_nat_config(self.service_manager.get_nat_config()).await
{
// If the result of this call was `UpdateNatPendingConfig` then NAT needs further
// configuration before it's ready to be enabled. Everything else is an error.
// Further configuration is done each time there is a change to the LIF in the
// `update_lif_properties()` method.
Ok(())
| Err(error::NetworkManager::Service(error::Service::UpdateNatPendingConfig)) => {}
Err(e) => {
error!("Failed to install NAT rules: {:?}", e);
return Err(e);
}
}
} else {
self.service_manager.disable_nat();
self.packet_filter.clear_nat_rules().await?;
}
// TODO(cgibson): Configure DHCP server.
// TODO(cgibson): Apply global packet filtering rules (i.e. rules that apply to all
// interfaces) when we support a wildcard device_id's.
Ok(())
}
/// Configures a WAN uplink from the stored configuration.
async fn configure_wan(&mut self, pid: PortId, topological_path: &str) -> error::Result<()> {
let wan_name = self.config.get_interface_name(topological_path)?;
let properties = self.config.create_wan_properties(topological_path)?;
let lif = self.create_lif(LIFType::WAN, wan_name, None, &[pid]).await?;
self.update_lif_properties(lif.id().uuid(), &properties).await?;
self.apply_packet_filters(topological_path).await?;
self.hal.set_interface_state(pid, true).await?;
info!("WAN configured: pid: {:?}, lif: {:?}, properties: {:?} ", pid, lif, properties);
Ok(())
}
/// Configures a bridge from the provided ports.
async fn configure_lan_bridge(&mut self, pids: &[PortId]) -> error::Result<()> {
let switched_vlans = pids.iter().map(|pid| {
self.port_manager
.topo_path(*pid)
.ok_or_else(|| {
error::NetworkManager::Config(error::Config::NotFound {
msg: format!("topo_path not found in port manager map: {:?}", pid),
})
})
.and_then(|t| self.config.get_switched_vlan_by_device_id(t))
});
let routed_vlan = self.config.all_ports_have_same_bridge(switched_vlans)?;
let bridge_name = self.config.get_bridge_name(&routed_vlan).to_string();
let vlan_id = routed_vlan.vlan_id;
let properties = self.config.create_routed_vlan_properties(&routed_vlan)?;
let lif = self.create_lif(LIFType::LAN, bridge_name, Some(vlan_id), pids).await?;
self.update_lif_properties(lif.id().uuid(), &properties).await?;
// TODO(cgibson): Add support for packet filtering on bridge ports.
info!("Created new LAN bridge with properties: {:?}", properties);
Ok(())
}
async fn configure_lan(
&mut self,
pids: &[PortId],
topological_path: &str,
) -> error::Result<()> {
let name = self.config.get_interface_name(topological_path)?;
let properties = self.config.create_lan_properties(topological_path)?;
let lif = self.create_lif(LIFType::LAN, name, None, pids).await?;
info!("LAN configured: pids: {:?} lif: {:?} properties: {:?} ", pids, lif, properties);
self.update_lif_properties(lif.id().uuid(), &properties).await?;
self.apply_packet_filters(topological_path).await?;
for pid in pids {
self.hal.set_interface_state(*pid, true).await?
}
Ok(())
}
/// Configures this device according the defaults provided in `default_interface`.
async fn configure_default_interface(
&mut self,
pid: PortId,
topological_path: &str,
) -> error::Result<()> {
match self.config.default_interface() {
Some(default_interface) => match default_interface.config.interface_type {
config::InterfaceType::IfUplink => self.configure_wan(pid, topological_path).await,
config::InterfaceType::IfEthernet => {
self.configure_lan(&[pid], topological_path).await
}
InterfaceType::IfAggregate
| InterfaceType::IfLoopback
| InterfaceType::IfRoutedVlan
| InterfaceType::IfSonet
| InterfaceType::IfTunnelGre4
| InterfaceType::IfTunnelGre6 => {
Err(error::NetworkManager::Config(error::Config::NotSupported {
msg: format!(
"Unsupported default interface type: {:?}",
default_interface.config.interface_type
),
}))
}
},
None => Err(error::NetworkManager::Config(error::Config::NotFound {
msg: format!(
"Trying to configure {} as a default interface but none was provided",
topological_path
),
})),
}
}
/// Applies configuration to the new port.
async fn apply_configuration_on_new_port(
&mut self,
pid: PortId,
topological_path: &str,
) -> error::Result<()> {
// TODO(51208): Figure out a better way to do this.
if self.config.device_id_is_an_uplink(topological_path) {
info!("Discovered a new uplink: {}", topological_path);
return self.configure_wan(pid, topological_path).await;
}
if self.config.device_id_is_a_downlink(topological_path) {
info!("Discovered a new downlink: {}", topological_path);
return self.configure_lan(&[pid], topological_path).await;
}
if self.config.is_unknown_device_id(topological_path) {
info!("Discovered a new unknown device: {}", topological_path);
return self.configure_default_interface(pid, topological_path).await;
}
// TODO(43251): Refactor this when we have the ability to add one port at a time to the
// bridge, currently we have to wait until we've discovered all the ports before creating
// the bridge.
self.lans.push(pid);
// If the configuration contains switched_vlan interfaces that matches this
// topological_path add it to the bridge.
if self.config.device_id_is_a_switched_vlan(topological_path) {
// TODO(43251): Need to do this until we can add individual bridge members.
if self.lans.len() == NUMBER_OF_PORTS_IN_LAN {
let lans = self.lans.clone();
info!("Creating new bridge with LAN ports: {:?}", lans);
return self.configure_lan_bridge(&lans).await;
}
}
Ok(())
}
/// Returns a [`hal::Port`] from the topological path.
pub async fn port_with_topopath(&self, path: &str) -> error::Result<Option<hal::Port>> {
let port = self.hal.ports().await?.into_iter().find(|x| x.path == path);
info!("ports {:?}", port);
Ok(port)
}
/// Informs network manager of an external event from fuchsia.netstack containing updates to
/// properties associated with an interface.
pub async fn oir_event(&mut self, event: oir::OIRInfo) -> error::Result<()> {
match event.action {
oir::Action::ADD => {
// Add to netstack if not there already.
let pid = if let Some(p) = self.port_with_topopath(&event.topological_path).await? {
info!("port already added {}: {:?}", &event.topological_path, p);
p.id
} else {
let info = event.device_information.ok_or(error::Oir::MissingInformation)?;
let port =
oir::PortDevice::new(&event.file_path, &event.topological_path, info)?;
info!("adding port {}: {:?}", &event.topological_path, port);
self.hal.add_ethernet_device(event.device_channel.unwrap(), port).await?
};
// Verify port manager is consistent.
if let Some(id) = self.port_manager.port_id(&event.topological_path) {
if pid != *id {
error!("port {:?} already exists with id {:?}", event.topological_path, id);
return Err(error::NetworkManager::Oir(error::Oir::InconsistentState));
}
return Ok(());
}
// Port manager does not know about this port, add it.
// No version change, this is just a port insertion,
// not a configuration update.
self.add_port(pid, &event.topological_path, self.version());
// Apply initial configuration.
self.apply_configuration_on_new_port(pid, &event.topological_path).await?
}
oir::Action::REMOVE => {
// Tell portmanager interface is removed. It should keep any config there.
// Tell LIF manager associated port is gone, keep config there so it can be
// reapplied if it comes back.
info!("device {:?} will be removed", event.topological_path);
// Tell netstack interface is gone.
oir::remove_interface(&event.topological_path);
}
}
Ok(())
}
/// Update internal state with information about an LIF that was added or updated externally.
/// When an interface not known before is found, it will add it.
/// If it's a know interface, it will log the new operational state.
fn notify_lif_added_or_updated(&mut self, iface: hal::Interface) -> error::Result<()> {
match self.lif_manager.lif_at_port(iface.id) {
Some(lif) => log_property_change(lif, iface),
None => self.notify_lif_added(iface)?,
};
Ok(())
}
/// Update internal state with information about an LIF that was added externally.
fn notify_lif_added(&mut self, iface: hal::Interface) -> error::Result<()> {
if iface.get_address_v4().is_none() {
return Ok(());
}
let port = iface.id;
self.port_manager.use_port(port);
let l = lifmgr::LIF::new(
self.version,
LIFType::WAN,
&iface.topo_path,
iface.id,
vec![iface.id],
0, // vlan
Some(lifmgr::LIFProperties {
dhcp: if iface.dhcp_client_enabled {
lifmgr::Dhcp::Client
} else {
lifmgr::Dhcp::None
},
dhcp_config: None,
address_v4: iface.get_address_v4(),
address_v6: iface.get_address_v6(),
enabled: true,
}),
)
.or_else(|e| {
self.port_manager.release_port(port);
Err(e)
})?;
self.lif_manager.add_lif(&l).or_else(|e| {
self.port_manager.release_port(port);
Err(e)
})?;
// TODO(guzt): This should ideally compare metrics or be manually settable when there are
// multiple WAN ports.
if let Some(addr) = iface.get_address_v4() {
self.service_manager.set_global_ip_nat(addr, port)
}
Ok(())
}
/// Populate state based on lower layers.
pub async fn populate_state(&mut self) -> error::Result<()> {
for p in self.hal.ports().await?.iter() {
self.add_port(p.id, &p.path, self.version());
}
self.hal
.interfaces()
.await?
.into_iter()
.try_for_each(|iface| self.notify_lif_added(iface))?;
self.version += 1;
Ok(())
}
/// Adds a new port.
pub fn add_port(&mut self, id: PortId, path: &str, v: Version) {
self.port_manager.add_port(portmgr::Port::new(id, path, v));
// This is a response to an event, not a configuration change. Version should not be
// incremented.
}
/// Releases the given ports.
fn release_ports(&mut self, ports: &[PortId]) {
ports.iter().for_each(|p| {
self.port_manager.release_port(*p);
});
}
/// Creates a LIF of the given type.
pub async fn create_lif(
&mut self,
lif_type: LIFType,
name: String,
vlan: Option<u16>,
ports: &[PortId],
) -> error::Result<lifmgr::LIF> {
if ports.is_empty() {
// At least one port is needed.
return Err(error::NetworkManager::Lif(error::Lif::NotSupported));
}
let reserved: Vec<PortId> = ports
.iter()
.filter_map(|p| if self.port_manager.use_port(*p) { Some(*p) } else { None })
.collect();
if reserved.len() != ports.len() {
self.release_ports(&reserved);
return Err(error::NetworkManager::Lif(error::Lif::InvalidPort));
}
// TODO(cgibson): Once we can configure VLANs, we need to handle VLAN ID 0 vs None, since
// VLAN ID 0x0000 is a reserved value in dot1q. fxb/41746.
let vid = match vlan {
Some(v) => v,
None => 0,
};
let mut l = lifmgr::LIF::new(
self.version,
lif_type,
&name,
PortId::from(0),
reserved.clone(),
vid,
Some(LIFProperties::default()),
)
.or_else(|e| {
self.release_ports(&reserved);
Err(e)
})?;
if reserved.len() > 1 {
// Multiple ports, bridge them.
let i = self.hal.create_bridge(reserved.clone()).await.or_else(|e| {
self.release_ports(&reserved);
Err(e)
})?;
// LIF ID is associated with the bridge.
l.set_pid(i.id);
} else {
l.set_pid(reserved[0]);
}
let r = self.lif_manager.add_lif(&l);
if let Err(e) = r {
self.release_ports(&reserved);
// nothing to do if delete_bridge fails, all state changes have been reverted
// already, just return an error to let caller handle it as appropriate.
self.hal.delete_bridge(l.pid()).await?;
return Err(e);
}
// all went well, increase version.
self.version += 1;
Ok(l)
}
/// Deletes the LIF of the given type.
pub async fn delete_lif(&mut self, lif_id: UUID) -> Result<(), error::NetworkManager> {
// Locate the lif to delete.
let lif = self.lif_manager.lif_mut(&lif_id);
let lif = match lif {
None => {
info!("delete_lif: lif not found {:?} ", lif_id);
return Err(error::NetworkManager::Lif(error::Lif::NotFound));
}
Some(x) => x.clone(),
};
// reset all properties, shut down LIF
self.hal.apply_properties(lif.pid(), lif.properties(), &LIFProperties::default()).await?;
// delete bridge if there is one and shut down the related ports
let ports = lif.ports();
if ports.len() > 1 {
self.hal.delete_bridge(lif.pid()).await?;
//TODO(dpradilla) shut down the ports.
}
self.release_ports(&ports.collect::<Vec<PortId>>());
// delete from database
if self.lif_manager.remove_lif(lif_id).is_none() {
return Err(error::NetworkManager::Lif(error::Lif::NotFound));
}
Ok(())
}
/// Configures an interface with the given properties, and updates the LIF information.
pub async fn update_lif_properties_fidl(
&mut self,
lif_id: UUID,
properties: &netconfig::LifProperties,
) -> error::Result<()> {
let lif = match self.lif_manager.lif_mut(&lif_id) {
None => {
info!("update_lif_properties: lif not found {:?} ", lif_id);
return Err(error::NetworkManager::Lif(error::Lif::NotFound));
}
Some(x) => x,
};
let old = lif.properties();
let lp = old.get_updated(&properties)?;
self.update_lif_properties(lif_id, &lp).await
}
/// Configures an interface with the given properties, and updates the LIF information.
pub async fn update_lif_properties(
&mut self,
lif_id: UUID,
lp: &lifmgr::LIFProperties,
) -> error::Result<()> {
let lif = match self.lif_manager.lif_mut(&lif_id) {
None => {
info!("update_lif_properties: lif not found {:?} ", lif_id);
return Err(error::NetworkManager::Lif(error::Lif::NotFound));
}
Some(x) => x,
};
let old = lif.properties();
self.hal.apply_properties(lif.pid(), &old, &lp).await?;
if let Some(addr) = lp.address_v4 {
match lif.ltype() {
LIFType::WAN => self.service_manager.set_global_ip_nat(addr.clone(), lif.pid()),
LIFType::LAN => self.service_manager.set_local_subnet_nat(addr.clone()),
_ => (),
}
}
match self.packet_filter.update_nat_config(self.service_manager.get_nat_config()).await {
// If the result of this call was `UpdateNatPendingConfig` or `NatNotEnabled`, then NAT
// is not yet ready to be enabled until we have more configuration data.
Ok(())
| Err(error::NetworkManager::Service(error::Service::UpdateNatPendingConfig))
| Err(error::NetworkManager::Service(error::Service::NatNotEnabled)) => {}
Err(e) => {
// Otherwise, this was an actual error and we should not increment the
// version number.
error!("Failed to update NAT rules: {:?}; Version not incremented.", e);
return Err(e);
}
}
lif.set_properties(self.version, lp.clone())?;
self.version += 1;
Ok(())
}
/// Returns the LIF with the given `UUID`.
pub fn lif(&self, lif_id: UUID) -> Option<&lifmgr::LIF> {
self.lif_manager.lif(&lif_id)
}
/// Returns all LIFs of the given type.
pub fn lifs(&self, t: LIFType) -> impl Iterator<Item = &lifmgr::LIF> {
self.lif_manager.lifs(t)
}
/// Returns all managed ports.
pub fn ports(&self) -> impl ExactSizeIterator<Item = &portmgr::Port> {
self.port_manager.ports()
}
/// Returns the current configuration version.
///
/// The configuration version is monotonically increased every time there is a configuration
/// change.
pub fn version(&self) -> Version {
self.version
}
/// Returns whether NAT is administratively enabled.
pub fn is_nat_enabled(&self) -> bool {
self.service_manager.is_nat_enabled()
}
/// Enables NAT.
///
/// Administratively enable Network Address Translation.
pub fn enable_nat(&mut self) {
self.service_manager.enable_nat();
}
/// Disables NAT.
///
/// Administratively disable Network Address Translation.
pub fn disable_nat(&mut self) {
self.service_manager.disable_nat();
}
/// Installs a new packet filter [`netconfig::FilterRule`] on the provided `nicid`.
pub async fn set_filter_on_interface(
&self,
rule: &netconfig::FilterRule,
nicid: u32,
) -> error::Result<()> {
match self.packet_filter.set_filter(rule, nicid).await {
Ok(_) => Ok(()),
Err(e) => {
warn!("Failed to set new filter rules: {:?}", e);
Err(error::NetworkManager::Service(error::Service::ErrorAddingPacketFilterRules))
}
}
}
/// Deletes a packet filter rule.
pub async fn delete_filter(&self, _id: netconfig::Id) -> error::Result<()> {
// TODO(44183): Currently this method deletes all of the installed filter rules.
// We need to associate each rule with an id when it is installed, so that individual filter
// rules can be removed.
warn!("Deleting numbered filter rules is not supported yet: Clearing all rules instead.");
// TODO(cgibson): Need to support tracking element numbers. fxb/44183.
match self.packet_filter.clear_filters().await {
Ok(_) => Ok(()),
Err(e) => {
warn!("Failed to clear filter rules: {:?}", e);
Err(error::NetworkManager::Service(error::Service::ErrorClearingPacketFilterRules))
}
}
}
/// Returns the currently installed packet filter rules.
pub async fn get_filters(&self) -> error::Result<Vec<netconfig::FilterRule>> {
self.packet_filter.get_filters().await.map_err(|e| {
warn!("Failed to get filter rules: {:?}", e);
error::NetworkManager::Service(error::Service::ErrorGettingPacketFilterRules)
})
}
pub async fn set_dns_resolver(
&mut self,
servers: &mut [fidl_fuchsia_net::IpAddress],
domain: Option<String>,
policy: netconfig::DnsPolicy,
) -> error::Result<ElementId> {
if domain.is_some() {
//TODO(dpradilla): lift this restriction.
warn!("setting the dns search domain name is not supported {:?}", domain);
return Err(error::NetworkManager::Service(error::Service::NotSupported));
}
let p = if policy == netconfig::DnsPolicy::NotSet {
DnsPolicy::default()
} else {
DnsPolicy::from(policy)
};
// Keeping dns_config sorted.
servers.sort();
if servers.len() == self.dns_config.servers.len()
&& servers.iter().zip(&self.dns_config.servers).all(|(a, b)| a == b)
{
// No change needed.
return Ok(self.dns_config.id);
}
// Just return the error, nothing to undo.
self.hal.set_dns_resolver(servers, domain.clone(), p).await?;
self.dns_config.id.version = self.version;
self.dns_config.servers = servers.to_vec();
self.version += 1;
Ok(self.dns_config.id)
}
/// Returns the dns resolver configuration.
pub async fn get_dns_resolver(&self) -> netconfig::DnsResolverConfig {
netconfig::DnsResolverConfig::from(&self.dns_config)
}
}
/// Log that the lif properties have changed.
///
/// This will be later use to update the operational state, but not the configuration state.
/// (we are not currently caching operational state, just querying for it).
fn log_property_change(lif: &mut lifmgr::LIF, iface: hal::Interface) {
let properties = lif.properties();
info!("log_property_change {:?} {:?}", iface.id, iface.name);
let new_properties: lifmgr::LIFProperties = iface.into();
if properties != &new_properties {
info!("Properties have changed {:?}: new properties {:?}", properties, new_properties);
}
}
/// Represents the version of the configuration associated to a device object (i.e. an
/// interface, and ACL, etc). It should only be updated when configuration state is updated. It
/// should never be updated due to operational state changes.
///
/// For example, if an interface is configured to get its IP address via DHCP, the configuration
/// is changed to enable DHCP client on the interface. The address received from DHCP is an
/// operational state change. It is not to be saved in the configuration.
///
/// Adding a static neighbor entry to the neighbor table is a configuration change. Learning an
/// entry dynamically and adding it to the neighbor table is an operational change.
///
/// For a good definition of configuration vs. operational state, please see:
/// https://tools.ietf.org/html/rfc6244#section-4.3
type Version = u64;
type UUID = u128;
static ID: AtomicUsize = AtomicUsize::new(0);
fn generate_uuid() -> UUID {
ID.fetch_add(1, Ordering::Relaxed) as UUID
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
pub struct ElementId {
uuid: UUID,
version: Version,
}
impl ElementId {
pub fn new(v: Version) -> Self {
ElementId { uuid: generate_uuid(), version: v }
}
pub fn update(&mut self, version: Version) {
self.version = version;
}
pub fn uuid(&self) -> UUID {
self.uuid
}
pub fn version(&self) -> u64 {
self.version
}
}
impl From<ElementId> for netconfig::Id {
fn from(id: ElementId) -> Self {
netconfig::Id { uuid: id.uuid.to_ne_bytes(), version: id.version }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::address::LifIpAddr;
use fidl_fuchsia_net as net;
use fuchsia_async as fasync;
use std::net::IpAddr;
#[test]
fn test_elementid() {
let uuid = generate_uuid();
let version = 4;
let new_version = 34;
let mut e = ElementId { uuid, version };
assert_eq!(e.uuid(), uuid);
assert_eq!(e.version(), version);
e.update(new_version);
assert_eq!(e.version(), new_version);
assert_eq!(
netconfig::Id::from(e),
netconfig::Id { uuid: uuid.to_ne_bytes(), version: new_version },
);
}
#[fasync::run_singlethreaded(test)]
async fn test_new_device_state() {
let d = DeviceState::new(
hal::NetCfg::new().unwrap(),
packet_filter::PacketFilter::start().unwrap(),
);
assert_eq!(d.version, 0);
assert_eq!(d.port_manager.ports().count(), 0);
assert_eq!(d.lif_manager.all_lifs().count(), 0);
}
fn net_interface_with_flags(
port: u32,
addr: [u8; 4],
dhcp: bool,
enabled: bool,
) -> netstack::NetInterface {
netstack::NetInterface {
id: port,
flags: if enabled { netstack::NET_INTERFACE_FLAG_UP } else { 0 }
| if dhcp { netstack::NET_INTERFACE_FLAG_DHCP } else { 0 },
features: 0,
configuration: 0,
name: port.to_string(),
addr: net::IpAddress::Ipv4(net::Ipv4Address { addr }),
netmask: net::IpAddress::Ipv4(net::Ipv4Address { addr: [255, 255, 255, 0] }),
broadaddr: net::IpAddress::Ipv4(net::Ipv4Address { addr: [1, 2, 3, 255] }),
ipv6addrs: vec![],
hwaddr: [1, 2, 3, 4, 5, port as u8].to_vec(),
}
}
fn net_interface(port: u32, addr: [u8; 4]) -> netstack::NetInterface {
net_interface_with_flags(port, addr, true, true)
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_update_state_for_netstack_event() {
let mut device_state = DeviceState::new(
hal::NetCfg::new().unwrap(),
packet_filter::PacketFilter::start().unwrap(),
);
// Create a few ports.
device_state.port_manager.add_port(portmgr::Port {
e_id: ElementId::new(1),
port_id: hal::PortId::from(5),
path: "path".to_string(),
});
device_state.port_manager.add_port(portmgr::Port {
e_id: ElementId::new(2),
port_id: hal::PortId::from(4),
path: "path".to_string(),
});
let ports = device_state.ports().collect::<Vec<&portmgr::Port>>();
assert_eq!(ports.len(), 2);
// Netstack informs network manager of a new interface at port 5 with ip 1.2.3.4.
assert!(device_state
.update_state_for_netstack_event(netstack::NetstackEvent::OnInterfacesChanged {
interfaces: vec![net_interface(5, [1, 2, 3, 4])],
},)
.await
.is_ok());
// Port 5 should now be marked used.
assert!(
!device_state.port_manager.port_available(&hal::PortId::from(5)).unwrap(),
"lif at port {:?}: {:?}",
5,
device_state.lif_manager.lif_at_port(5.into())
);
// Assert that network manager now knows about this new interface.
{
let lifs: Vec<&lifmgr::LIF> = device_state.lifs(LIFType::WAN).collect();
assert_eq!(lifs.len(), 1);
assert_eq!(lifs[0].pid().to_u64(), 5);
// Also make sure that it is set as the NAT global IP.
assert_eq!(
device_state.service_manager.get_nat_config().global_ip,
Some(LifIpAddr { address: IpAddr::from([1, 2, 3, 4]), prefix: 24 })
);
}
// Netstack informs network manager of a new interface at port 4.
assert!(device_state
.update_state_for_netstack_event(netstack::NetstackEvent::OnInterfacesChanged {
interfaces: vec![net_interface(4, [3, 4, 5, 6])],
},)
.await
.is_ok());
// Assert that network manager now knows about the new interface and updates to the other
// interface.
{
let lifs: Vec<&lifmgr::LIF> = device_state.lifs(LIFType::WAN).collect();
lifs.iter().find(|lif| lif.pid().to_u64() == 5).unwrap();
lifs.iter().find(|lif| lif.pid().to_u64() == 4).unwrap();
}
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_dont_update_state_for_netstack_event_without_ip() {
let mut device_state = DeviceState::new(
hal::NetCfg::new().unwrap(),
packet_filter::PacketFilter::start().unwrap(),
);
device_state.port_manager.add_port(portmgr::Port {
e_id: ElementId::new(1),
port_id: hal::PortId::from(5),
path: "path".to_string(),
});
assert!(device_state
.update_state_for_netstack_event(netstack::NetstackEvent::OnInterfacesChanged {
interfaces: vec![
net_interface_with_flags(5, [0, 0, 0, 0], false, true),
net_interface_with_flags(5, [0, 0, 0, 0], true, false),
],
},)
.await
.is_ok());
// Port should still be free.
assert!(device_state.port_manager.port_available(&hal::PortId::from(5)).unwrap());
assert_eq!(device_state.lifs(LIFType::WAN).next(), None);
}
}