blob: d49a210da4cd5357c4d475752a951cc5a8ec6777 [file] [log] [blame]
// Copyright 2022 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.
//! LoWPAN OpenThread Driver
#![warn(rust_2018_idioms)]
use anyhow::Error;
use fidl_fuchsia_factory_lowpan::{FactoryRegisterMarker, FactoryRegisterProxyInterface};
use fidl_fuchsia_lowpan_driver::{RegisterMarker, RegisterProxyInterface};
use fidl_fuchsia_lowpan_spinel::{
DeviceMarker as SpinelDeviceMarker, DeviceProxy as SpinelDeviceProxy,
DeviceSetupMarker as SpinelDeviceSetupMarker,
};
use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at};
use lowpan_driver_common::net::*;
use lowpan_driver_common::spinel::SpinelDeviceSink;
use lowpan_driver_common::{register_and_serve_driver, register_and_serve_driver_factory};
use openthread_fuchsia::Platform as OtPlatform;
use config::Config;
use fidl::endpoints::create_proxy;
use crate::driver::OtDriver;
use crate::prelude::*;
use fuchsia as _;
use std::ffi::CString;
use std::num::NonZeroU32;
mod bootstrap;
mod config;
mod convert_ext;
mod driver;
#[macro_use]
mod prelude {
#![allow(unused_imports)]
pub use crate::convert_ext::FromExt as _;
pub use crate::convert_ext::IntoExt as _;
pub use crate::Result;
pub use anyhow::{bail, format_err, Context as _};
pub use fasync::TimeoutExt as _;
pub use fidl_fuchsia_net_ext as fnet_ext;
pub use fuchsia_async as fasync;
pub use fuchsia_zircon as fz;
pub use fuchsia_zircon_status::Status as ZxStatus;
pub use futures::future::BoxFuture;
pub use futures::stream::BoxStream;
pub use lowpan_driver_common::pii::MarkPii;
pub use lowpan_driver_common::ZxResult;
pub use net_declare::{fidl_ip, fidl_ip_v6};
pub use std::convert::TryInto;
pub use std::fmt::Debug;
pub use tracing::{debug, error, info, trace, warn};
pub use futures::prelude::*;
pub use openthread::prelude::*;
}
pub type Result<T = (), E = anyhow::Error> = std::result::Result<T, E>;
const MAX_EXPONENTIAL_BACKOFF_DELAY_SEC: i64 = 180;
const RESET_EXPONENTIAL_BACKOFF_TIMER_MIN: i64 = 5;
impl Config {
async fn open_spinel_device_proxy(&self) -> Result<SpinelDeviceProxy, Error> {
use std::path::Path;
let path = self.ot_radio_path.as_deref().unwrap_or("/dev/class/ot-radio");
// If we are just given a directory, try to infer the full path.
let spinel_device_setup_proxy = if Path::new(path).is_dir() {
let directory_proxy =
fuchsia_fs::directory::open_in_namespace(path, fuchsia_fs::OpenFlags::empty())?;
let entries = fuchsia_fs::directory::readdir(&directory_proxy).await?;
// Should have 1 device that implements OT_RADIO
match entries.as_slice() {
[entry] => {
info!("Attempting to use Spinel RCP at {}/{}", path, entry.name.as_str());
fuchsia_component::client::connect_to_named_protocol_at_dir_root::<
SpinelDeviceSetupMarker,
>(&directory_proxy, entry.name.as_str())
}
ot_radio_devices => {
return Err(format_err!(
"There are {} devices in {}, expecting only one",
ot_radio_devices.len(),
path
));
}
}
} else {
info!("Attempting to use Spinel RCP at {}", path);
fuchsia_component::client::connect_to_protocol_at_path::<SpinelDeviceSetupMarker>(path)
}
.context("Error opening Spinel RCP")?;
let (client_side, server_side) = fidl::endpoints::create_endpoints::<SpinelDeviceMarker>();
spinel_device_setup_proxy
.set_channel(server_side)
.await?
.map_err(ZxStatus::from_raw)
.context(
"Unable to set server-side FIDL channel via spinel_device_setup_proxy.set_channel()",
)?;
Ok(client_side.into_proxy()?)
}
fn get_backbone_netif_index_by_config(&self) -> Option<ot::NetifIndex> {
if self.backbone_name.as_ref().unwrap().is_empty() {
info!("Backbone interface is disabled");
return None;
}
let c_name = CString::new(self.backbone_name.as_ref().unwrap().as_bytes().to_vec())
.expect("Invalid backbone interface name");
// SAFETY: Calling `if_name_toindex` is safe assuming that the C-string pointer
// being passed into it is valid, which is guaranteed by `CString::as_ptr()`.
let index = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
if index == 0 {
error!("Unable to look up index of interface {:?}", self.backbone_name);
return None;
}
info!("Backbone interface is {:?} (index {})", self.backbone_name, index);
Some(index)
}
fn get_backbone_netif_index_by_wlan_availability(&self) -> Option<ot::NetifIndex> {
let state = connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("error connecting to StateMarker");
let (watcher_client, watcher_server) =
create_proxy::<fidl_fuchsia_net_interfaces::WatcherMarker>()
.expect("error connecting to WatcherMarker");
state
.get_watcher(&fidl_fuchsia_net_interfaces::WatcherOptions::default(), watcher_server)
.expect("error getting interface watcher");
let get_nicid_fut = async move {
loop {
match watcher_client.watch().await.expect("") {
fidl_fuchsia_net_interfaces::Event::Existing(
fidl_fuchsia_net_interfaces::Properties {
id,
name,
online,
device_class,
..
},
) => {
info!(
"NICID: {:?}, name: {:?}, online: {:?}, device_class: {:?}",
id, name, online, device_class
);
if let (
Some(fidl_fuchsia_net_interfaces::DeviceClass::Device(
fidl_fuchsia_hardware_network::DeviceClass::Wlan,
)),
Some(true),
) = (device_class, online)
{
return Some(id.unwrap_or(0) as ot::NetifIndex);
}
}
fidl_fuchsia_net_interfaces::Event::Idle(
fidl_fuchsia_net_interfaces::Empty {},
) => {
break;
}
_ => {}
}
}
None
};
futures::executor::block_on(get_nicid_fut)
}
fn get_backbone_netif_index(&self) -> Option<ot::NetifIndex> {
if self.backbone_name.is_none() {
self.get_backbone_netif_index_by_wlan_availability()
} else {
self.get_backbone_netif_index_by_config()
}
}
/// Async method which returns the future that runs the driver.
async fn prepare_to_run(&self) -> Result<impl Future<Output = Result<(), Error>>, Error> {
let spinel_device_proxy = self.open_spinel_device_proxy().await?;
debug!("Spinel device proxy initialized");
let spinel_sink = SpinelDeviceSink::new(spinel_device_proxy);
let spinel_stream = spinel_sink.take_stream();
let netif = TunNetworkInterface::try_new(Some(self.name.clone()))
.await
.context("Unable to start TUN driver")?;
let mut builder = OtPlatform::build().thread_netif_index(
netif
.get_index()
.try_into()
.expect("Network interface index is too large for OpenThread"),
);
netif
.set_ipv6_forwarding_enabled(true)
.await
.expect("Unable to enable ipv6 packet forwarding on lowpan interface");
netif
.set_ipv4_forwarding_enabled(true)
.await
.expect("Unable to enable ipv4 packet forwarding on lowpan interface");
let backbone_netif_index = self.get_backbone_netif_index();
let backbone_if = BackboneNetworkInterface::new(backbone_netif_index.unwrap_or(0).into());
if let Some(index) = backbone_netif_index {
builder = builder.backbone_netif_index(index);
}
let ot_instance = ot::Instance::new(builder.init(spinel_sink, spinel_stream));
// TODO: might switch to check if the infra_if instance is constructed successfully
if let Some(index) = backbone_netif_index.and_then(NonZeroU32::new) {
ot_instance
.border_routing_init(index.get(), true)
.context("Unable to initialize OpenThread border routing")?;
ot_instance.border_routing_set_enabled(true).context("border_routing_set_enabled")?;
ot_instance.set_backbone_router_enabled(true);
} else {
warn!("Backbone interface not set, border routing not supported");
}
let driver_future = run_driver(
self.name.clone(),
connect_to_protocol_at::<RegisterMarker>(self.service_prefix.as_str())
.context("Failed to connect to Lowpan Registry service")?,
connect_to_protocol_at::<FactoryRegisterMarker>(self.service_prefix.as_str()).ok(),
ot_instance,
netif,
backbone_if,
);
Ok(driver_future)
}
}
async fn run_driver<N, RP, RFP, NI, BI>(
name: N,
registry: RP,
factory_registry: Option<RFP>,
ot_instance: OtInstanceBox,
net_if: NI,
backbone_if: BI,
) -> Result<(), Error>
where
N: AsRef<str>,
RP: RegisterProxyInterface,
RFP: FactoryRegisterProxyInterface,
NI: NetworkInterface + Debug,
BI: BackboneInterface,
{
let name = name.as_ref();
let mut driver = OtDriver::new(ot_instance, net_if, backbone_if);
driver.start_multicast_routing_manager();
let driver_ref = &driver;
let lowpan_device_task = register_and_serve_driver(name, registry, driver_ref);
info!("Registered OpenThread LoWPAN device {}", name);
let lowpan_device_factory_task = async move {
if let Some(factory_registry) = factory_registry {
if let Err(err) =
register_and_serve_driver_factory(name, factory_registry, driver_ref).await
{
warn!("Unable to register and serve factory commands for {}: {:?}", name, err);
}
}
// If the factory interface throws an error, don't kill the driver;
// just let the rest keep running.
futures::future::pending::<Result<(), Error>>().await
};
// All three of these tasks will run indefinitely
// as long as there are no irrecoverable problems.
//
// We use `stream::select_all` here so that only the
// futures that actually need to be polled get polled.
futures::stream::select_all([
driver.main_loop_stream().boxed(),
lowpan_device_task.into_stream().boxed(),
lowpan_device_factory_task.into_stream().boxed(),
])
.try_collect::<()>()
.await?;
info!("OpenThread LoWPAN device {} has shutdown.", name);
Ok(())
}
// The OpenThread platform implementation currently requires a multithreaded executor.
#[fasync::run(10)]
async fn main() -> Result<(), Error> {
use std::path::Path;
let config = Config::try_new().context("Config::try_new")?;
// Use the diagnostics_log library directly rather than e.g. the #[fuchsia::main] macro on
// the main function, so that we can specify the logging severity level at runtime based on a
// command line argument.
diagnostics_log::initialize(
diagnostics_log::PublishOptions::default().minimum_severity(config.log_level),
)?;
// Make sure OpenThread is logging at a similar level as the rest of the system.
ot::set_logging_level(openthread_fuchsia::logging::ot_log_level_from(config.log_level));
if Path::new("/config/data/bootstrap_config.json").exists() {
warn!("Bootstrapping thread. Skipping ot-driver loop.");
return bootstrap::bootstrap_thread().await;
}
let mut attempt_count = 0;
loop {
info!("Starting LoWPAN OT Driver");
let driver_future = config
.prepare_to_run()
.inspect_err(|e| error!("main:prepare_to_run: {:?}", e))
.await
.context("main:prepare_to_run")?
.boxed();
let start_timestamp = fasync::Time::now();
let ret = driver_future.await.context("main:driver_task");
if (fasync::Time::now() - start_timestamp).into_minutes()
>= RESET_EXPONENTIAL_BACKOFF_TIMER_MIN
{
// If the past run has been running for `RESET_EXPONENTIAL_BACKOFF_TIMER_MIN`
// minutes or longer, then we go ahead and reset the attempt count.
attempt_count = 0;
}
if config.max_auto_restarts <= attempt_count {
panic!("Failed {} attempts to restart OpenThread: {ret:?}", config.max_auto_restarts);
}
// Implement an exponential backoff for restarts.
let delay = (1 << attempt_count).min(MAX_EXPONENTIAL_BACKOFF_DELAY_SEC);
if ret
.as_ref()
.map_err(|err| err.is::<driver::ResetRequested>() || err.is::<BackboneNetworkChanged>())
.err()
.unwrap_or(false)
{
// This is an expected OpenThread reset.
warn!("OpenThread Reset: {:?}", ret);
} else {
error!("Unexpected shutdown: {:?}", ret);
warn!("Will attempt to restart in {} seconds.", delay);
fasync::Timer::new(fasync::Time::after(fz::Duration::from_seconds(delay))).await;
attempt_count += 1;
info!("Restart attempt {} ({} max)", attempt_count, config.max_auto_restarts);
}
}
}