blob: d67105b38c74abfbef6b326c1eb0c8dc0732a637 [file] [log] [blame]
// Copyright 2020 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 Network Tunnel Abstraction
use super::debug::*;
use super::iface::*;
use crate::prelude_internal::*;
use crate::spinel::Subnet;
use anyhow::Error;
use async_trait::async_trait;
use fidl::endpoints::{create_endpoints, create_proxy};
use fidl_fuchsia_hardware_network as fhwnet;
use fidl_fuchsia_net as fnet;
use fidl_fuchsia_net_ext as fnetext;
use fidl_fuchsia_net_interfaces_admin as fnetifadmin;
use fidl_fuchsia_net_interfaces_ext as fnetifext;
use fidl_fuchsia_net_stack as fnetstack;
use fidl_fuchsia_net_stack_ext::FidlReturn as _;
use fidl_fuchsia_net_tun as ftun;
use fuchsia_component::client::{connect_channel_to_protocol, connect_to_protocol};
use fuchsia_zircon as zx;
use futures::stream::BoxStream;
use parking_lot::Mutex;
use std::convert::TryInto;
use std::net::{Ipv6Addr, UdpSocket};
const IPV6_MIN_MTU: u32 = 1280;
const TUN_PORT_ID: u8 = 0;
#[derive(Debug)]
pub struct TunNetworkInterface {
tun_dev: ftun::DeviceProxy,
tun_port: ftun::PortProxy,
#[allow(unused)] // TODO (fxb/64704): use `control` after converting methods to async.
control: fnetifext::admin::Control,
control_sync: Mutex<fnetifadmin::ControlSynchronousProxy>,
stack_sync: Mutex<fnetstack::StackSynchronousProxy>,
mcast_socket: UdpSocket,
id: u64,
}
impl TunNetworkInterface {
pub async fn try_new(name: Option<String>) -> Result<TunNetworkInterface, Error> {
let tun_control = connect_to_protocol::<ftun::ControlMarker>()?;
let (tun_dev, req) = create_proxy::<ftun::DeviceMarker>()?;
tun_control
.create_device(
ftun::DeviceConfig { blocking: Some(true), ..ftun::DeviceConfig::EMPTY },
req,
)
.context("failed to create tun pair")?;
let (tun_port, port_req) = create_proxy::<ftun::PortMarker>()?;
tun_dev
.add_port(
ftun::DevicePortConfig {
base: Some(ftun::BasePortConfig {
id: Some(TUN_PORT_ID),
mtu: Some(IPV6_MIN_MTU),
rx_types: Some(vec![
fhwnet::FrameType::Ipv6,
// TODO(fxbug.dev/64292): Remove this once netstack doesn't require it.
fhwnet::FrameType::Ipv4,
]),
tx_types: Some(vec![
fhwnet::FrameTypeSupport {
type_: fhwnet::FrameType::Ipv6,
features: fhwnet::FRAME_FEATURES_RAW,
supported_flags: fhwnet::TxFlags::empty(),
},
// TODO(fxbug.dev/64292): Remove this once netstack doesn't require it.
fhwnet::FrameTypeSupport {
type_: fhwnet::FrameType::Ipv4,
features: fhwnet::FRAME_FEATURES_RAW,
supported_flags: fhwnet::TxFlags::empty(),
},
]),
..ftun::BasePortConfig::EMPTY
}),
..ftun::DevicePortConfig::EMPTY
},
port_req,
)
.context("failed to add device port")?;
let (device, device_req) = create_endpoints::<fhwnet::DeviceMarker>()?;
tun_dev.get_device(device_req).context("get device failed")?;
let (control, control_sync) = {
let installer = connect_to_protocol::<fnetifadmin::InstallerMarker>()?;
let (device_control, server_end) = create_proxy::<fnetifadmin::DeviceControlMarker>()?;
installer.install_device(device, server_end).context("install_device failed")?;
// Interface lifetime is already tied to us because of tun device,
// no need to keep this extra channel around.
device_control.detach().context("device control detach failed")?;
let (port, server_end) = create_proxy::<fhwnet::PortMarker>()?;
tun_port.get_port(server_end).context("get_port failed")?;
let mut port_id = port
.get_info()
.await
.context("get_info failed")?
.id
.ok_or_else(|| anyhow::anyhow!("port id missing from info"))?;
let (control_sync_client_channel, control_sync_server) = zx::Channel::create()?;
let control_sync =
fnetifadmin::ControlSynchronousProxy::new(control_sync_client_channel);
device_control
.create_interface(
&mut port_id,
control_sync_server.into(),
fnetifadmin::Options { name: name.clone(), ..fnetifadmin::Options::EMPTY },
)
.context("create_interface failed")?;
let (control, server_end) = fnetifext::admin::Control::create_endpoints()?;
device_control
.create_interface(
&mut port_id,
server_end,
fnetifadmin::Options { name, ..fnetifadmin::Options::EMPTY },
)
.context("create_interface failed")?;
(control, Mutex::new(control_sync))
};
let id = control_sync.lock().get_id(zx::Time::INFINITE).context("get_id failed")?;
let _was_disabled: bool = control_sync
.lock()
.enable(zx::Time::INFINITE)
.context("enable error")?
.map_err(|e| anyhow::anyhow!("enable failed {:?}", e))?;
let (client, server) = zx::Channel::create()?;
connect_channel_to_protocol::<fnetstack::StackMarker>(server)?;
let stack_sync = Mutex::new(fnetstack::StackSynchronousProxy::new(client));
let mcast_socket = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0)).context("UdpSocket::bind")?;
Ok(TunNetworkInterface {
tun_dev,
tun_port,
control,
control_sync,
stack_sync,
mcast_socket,
id,
})
}
}
#[async_trait]
impl NetworkInterface for TunNetworkInterface {
fn get_index(&self) -> u64 {
self.id
}
async fn outbound_packet_from_stack(&self) -> Result<Vec<u8>, Error> {
let frame = self
.tun_dev
.read_frame()
.await
.context("FIDL error on read_frame")?
.map_err(fuchsia_zircon::Status::from_raw)
.context("Error calling read_frame")?;
if let Some(packet) = frame.data.as_ref() {
fx_log_trace!(
"TunNetworkInterface: Packet arrived from stack: {:?}",
Ipv6PacketDebug(packet)
);
}
Ok(frame.data.ok_or(format_err!("data field was absent"))?)
}
async fn inbound_packet_to_stack(&self, packet: &[u8]) -> Result<(), Error> {
fx_log_trace!("TunNetworkInterface: Packet sent to stack: {:?}", Ipv6PacketDebug(packet));
Ok(self
.tun_dev
.write_frame(ftun::Frame {
port: Some(TUN_PORT_ID),
frame_type: Some(fhwnet::FrameType::Ipv6),
data: Some(packet.to_vec()),
meta: None,
..fidl_fuchsia_net_tun::Frame::EMPTY
})
.await?
.map_err(fuchsia_zircon::Status::from_raw)?)
}
async fn set_online(&self, online: bool) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Interface online: {:?}", online);
if online {
self.tun_port.set_online(true).await?;
let _was_disabled: bool = self
.control_sync
.lock()
.enable(zx::Time::INFINITE)
.context("enable error")?
.map_err(|e| anyhow::anyhow!("enable failed {:?}", e))?;
} else {
self.tun_port.set_online(false).await?;
}
Ok(())
}
async fn set_enabled(&self, enabled: bool) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Interface enabled: {:?}", enabled);
if enabled {
let _was_disabled: bool = self
.control_sync
.lock()
.enable(zx::Time::INFINITE)
.context("enable error")?
.map_err(|e| anyhow::anyhow!("enable failed {:?}", e))?;
} else {
let _was_enabled: bool = self
.control_sync
.lock()
.disable(zx::Time::INFINITE)
.context("disable error")?
.map_err(|e| anyhow::anyhow!("disable failed {:?}", e))?;
}
Ok(())
}
fn add_address(&self, addr: &Subnet) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Adding Address: {:?}", addr);
let mut device_addr = fnet::Subnet {
addr: fnetext::IpAddress(addr.addr.into()).into(),
prefix_len: addr.prefix_len,
};
let (address_state_provider, server_end) = fidl::endpoints::create_proxy::<
fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker,
>()
.expect("create proxy");
address_state_provider.detach()?;
self.control_sync.lock().add_address(
&mut device_addr,
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY,
server_end,
)?;
let mut forwarding_entry = fnetstack::ForwardingEntry {
subnet: fnetext::apply_subnet_mask(fnet::Subnet {
addr: fnetext::IpAddress(addr.addr.into()).into(),
prefix_len: addr.prefix_len,
}),
device_id: self.id,
next_hop: None,
metric: 0,
};
self.stack_sync
.lock()
.add_forwarding_entry(&mut forwarding_entry, zx::Time::INFINITE)?
.expect("add_forwarding_entry");
fx_log_info!("TunNetworkInterface: Successfully added address {:?}", addr);
Ok(())
}
fn remove_address(&self, addr: &Subnet) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Removing Address: {:?}", addr);
let mut device_addr = fnet::Subnet {
addr: fnetext::IpAddress(addr.addr.into()).into(),
prefix_len: addr.prefix_len,
};
let mut forwarding_entry = fnetstack::ForwardingEntry {
subnet: fnetext::apply_subnet_mask(fnet::Subnet {
addr: fnetext::IpAddress(addr.addr.into()).into(),
prefix_len: addr.prefix_len,
}),
device_id: self.id,
next_hop: None,
metric: 0,
};
self.stack_sync
.lock()
.del_forwarding_entry(&mut forwarding_entry, zx::Time::INFINITE)
.squash_result()?;
self.control_sync
.lock()
.remove_address(&mut device_addr, zx::Time::INFINITE)?
.expect("control_sync.remove_address");
fx_log_info!("TunNetworkInterface: Successfully removed address {:?}", addr);
Ok(())
}
fn add_external_route(&self, addr: &Subnet) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Adding external route: {:?} (CURRENTLY IGNORED)", addr);
Ok(())
}
fn remove_external_route(&self, addr: &Subnet) -> Result<(), Error> {
fx_log_info!(
"TunNetworkInterface: Removing external route: {:?} (CURRENTLY IGNORED)",
addr
);
Ok(())
}
/// Has the interface join the given multicast group.
fn join_mcast_group(&self, addr: &std::net::Ipv6Addr) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Joining multicast group: {:?}", addr);
self.mcast_socket.join_multicast_v6(addr, self.id.try_into().unwrap())?;
Ok(())
}
/// Has the interface leave the given multicast group.
fn leave_mcast_group(&self, addr: &std::net::Ipv6Addr) -> Result<(), Error> {
fx_log_info!("TunNetworkInterface: Leaving multicast group: {:?}", addr);
self.mcast_socket.leave_multicast_v6(addr, self.id.try_into().unwrap())?;
Ok(())
}
fn take_event_stream(&self) -> BoxStream<'_, Result<NetworkInterfaceEvent, Error>> {
let enabled_stream = futures::stream::try_unfold((), move |()| async move {
loop {
if let ftun::InternalState { has_session: Some(has_session), .. } =
self.tun_port.watch_state().await?
{
break Ok(Some((
NetworkInterfaceEvent::InterfaceEnabledChanged(has_session),
(),
)));
}
}
});
use fidl_fuchsia_net_interfaces::*;
use std::convert::TryInto;
struct EventState {
prev_prop: Properties,
watcher: Option<WatcherProxy>,
next_events: Vec<NetworkInterfaceEvent>,
}
let init_state =
EventState { prev_prop: Properties::EMPTY, watcher: None, next_events: Vec::default() };
let if_event_stream = futures::stream::try_unfold(init_state, move |mut state| {
async move {
if state.watcher.is_none() {
let fnif_state = connect_to_protocol::<StateMarker>()?;
let (watcher, req) = create_proxy::<WatcherMarker>()?;
fnif_state.get_watcher(WatcherOptions::EMPTY, req)?;
state.watcher = Some(watcher);
}
loop {
// Flush out any pending events
if let Some(event) = state.next_events.pop() {
return Ok(Some((event, state)));
}
match state.watcher.as_ref().unwrap().watch().await? {
Event::Existing(prop) if prop.id == Some(self.id) => {
assert!(
state.prev_prop.id == None,
"Got Event::Existing twice for same interface"
);
state.prev_prop = prop;
continue;
}
Event::Idle(_) => {
if state.prev_prop.id == None {
return Err(format_err!("Interface no longer exists"));
}
}
Event::Removed(id) if id == self.id => return Ok(None),
Event::Changed(prop) if prop.id == Some(self.id) => {
assert!(state.prev_prop.id.is_some());
traceln!("TunNetworkInterface: Got Event::Changed({:#?})", prop);
if let Some(addrs) = prop.addresses.as_ref() {
let empty_addrs = vec![];
let prev_addrs =
state.prev_prop.addresses.as_ref().unwrap_or(&empty_addrs);
state.next_events.extend(
addrs.iter().filter(|x| !prev_addrs.contains(x)).filter_map(
|Address { addr, valid_until: _, .. }| {
addr.unwrap()
.try_into()
.ok()
.map(NetworkInterfaceEvent::AddressWasAdded)
},
),
);
state.next_events.extend(
prev_addrs.iter().filter(|x| !addrs.contains(x)).filter_map(
|Address { addr, valid_until: _, .. }| {
addr.unwrap()
.try_into()
.ok()
.map(NetworkInterfaceEvent::AddressWasRemoved)
},
),
);
}
traceln!(
"TunNetworkInterface: Queued events: {:#?}",
state.next_events
);
state.prev_prop = prop;
}
_ => continue,
}
}
}
});
futures::stream::select(enabled_stream, if_event_stream).boxed()
}
async fn set_ipv6_forwarding_enabled(&self, enabled: bool) -> Result<(), Error> {
// Ignore the configuration before our change was applied.
let _: fnetifadmin::Configuration = self
.control_sync
.lock()
.set_configuration(
fnetifadmin::Configuration {
ipv6: Some(fnetifadmin::Ipv6Configuration {
forwarding: Some(enabled),
..fnetifadmin::Ipv6Configuration::EMPTY
}),
..fnetifadmin::Configuration::EMPTY
},
zx::Time::INFINITE,
)
.map_err(anyhow::Error::new)
.and_then(|res| {
res.map_err(|e: fnetifadmin::ControlSetConfigurationError| {
anyhow::anyhow!("{:?}", e)
})
})
.context("set configuration")?;
Ok(())
}
}