blob: 6649b9d37a3b7dd453f87c72be6387a4a20bc141 [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.
#![feature(async_await, await_macro)]
use failure::{format_err, Error, ResultExt};
use fdio;
use fidl_fuchsia_hardware_ethernet as zx_eth;
use fidl_fuchsia_net as net;
use fidl_fuchsia_net_stack::{self as netstack, StackMarker, StackProxy};
use fuchsia_async as fasync;
use fuchsia_component::{
client::AppBuilder,
server::{NestedEnvironment, ServiceFs},
};
use fuchsia_zircon as zx;
use futures::prelude::*;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use structopt::StructOpt;
const NETSTACK_URL: &'static str = "fuchsia-pkg://fuchsia.com/netstack3#meta/netstack3.cmx";
#[derive(StructOpt, Debug)]
struct Opt {
/// Path to the ethernet device to add to netstack,
/// usually somewhere in /dev/class/ethernet
#[structopt(short = "e")]
ethernet: Option<String>,
/// If ethernet is provided, also set a fixed IP address and subnet prefix
/// to it, including routing table information.
/// IP address and subnet prefix MUST be in the form [IP]/[prefix].
#[structopt(short = "i")]
ip_prefix: Option<String>,
}
struct Netstack {
stack: StackProxy,
}
fn parse_ip_addr_and_prefix(addr: &str) -> Result<(net::IpAddress, u8), Error> {
let (addr, prefix) = if let [addr, prefix] = addr.split("/").collect::<Vec<_>>()[..] {
(addr, prefix)
} else {
return Err(format_err!("Missing prefix value on {}", addr));
};
let prefix: u8 = prefix.parse()?;
let addr = match addr.parse()? {
::std::net::IpAddr::V4(ipv4) => {
net::IpAddress::Ipv4(net::Ipv4Address { addr: ipv4.octets() })
}
::std::net::IpAddr::V6(ipv6) => {
net::IpAddress::Ipv6(net::Ipv6Address { addr: ipv6.octets() })
}
};
Ok((addr, prefix))
}
fn mask_bits(bits: &mut [u8], mut prefix: u8) {
for i in 0..bits.len() {
bits[i] &= if prefix == 0 { 0 } else { 255u8 << 8u8.saturating_sub(prefix) };
prefix = prefix.saturating_sub(8);
}
}
fn mask_with_prefix(mut ip: net::IpAddress, prefix: u8) -> net::IpAddress {
match ip {
net::IpAddress::Ipv4(ref mut addr) => {
mask_bits(&mut addr.addr, prefix);
}
net::IpAddress::Ipv6(ref mut addr) => {
mask_bits(&mut addr.addr, prefix);
}
}
ip
}
fn copy_ip(ip: &net::IpAddress) -> net::IpAddress {
match ip {
net::IpAddress::Ipv4(addr) => {
net::IpAddress::Ipv4(net::Ipv4Address { addr: addr.addr.clone() })
}
net::IpAddress::Ipv6(addr) => {
net::IpAddress::Ipv6(net::Ipv6Address { addr: addr.addr.clone() })
}
}
}
impl Netstack {
fn new(env: &NestedEnvironment) -> Result<Self, Error> {
Ok(Self { stack: env.connect_to_service::<StackMarker>()? })
}
async fn add_ethernet(&self, path: String) -> Result<u64, Error> {
let dev = File::open(&path).context("failed to open device")?;
let topological_path =
fdio::device_get_topo_path(&dev).context("failed to get topological path")?;
let fd = dev.as_raw_fd();
let mut client = 0;
zx::Status::ok(unsafe { fdio::fdio_sys::fdio_get_service_handle(fd, &mut client) })
.context("failed to get fdio service handle")?;
let dev = fidl::endpoints::ClientEnd::<zx_eth::DeviceMarker>::new(
// Safe because we checked the return status above.
zx::Channel::from(unsafe { zx::Handle::from_raw(client) }),
);
let (err, id) = await!(self.stack.add_ethernet_interface(&topological_path, dev))
.context("error adding device")?;
match err {
Some(e) => Err(format_err!("Error adding interface {}: {:?}", path, e)),
None => Ok(id),
}
}
async fn set_ip(
&self,
id: u64,
ip_address: net::IpAddress,
prefix_len: u8,
) -> Result<(), Error> {
let mut fidl_addr =
netstack::InterfaceAddress { ip_address: copy_ip(&ip_address), prefix_len };
let response = await!(self.stack.add_interface_address(id, &mut fidl_addr))
.context("error setting interface address")?;
if let Some(e) = response {
return Err(format_err!("Error adding interface address {}: {:?}", id, e));
}
let response = await!(self.stack.add_forwarding_entry(&mut netstack::ForwardingEntry {
subnet: net::Subnet { addr: mask_with_prefix(ip_address, prefix_len), prefix_len },
destination: netstack::ForwardingDestination::DeviceId(id),
}))
.context("error adding forwarding entry")?;
match response {
Some(e) => Err(format_err!("Error adding forwarding entry: {:?}", e)),
None => Ok(()),
}
}
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
let options = Opt::from_args();
// create nested environment for netstack:
let mut ns_builder = AppBuilder::new(NETSTACK_URL.to_string());
let mut services = ServiceFs::new_local();
services
.add_proxy_service_to::<StackMarker, _>(ns_builder.directory_request().unwrap().clone())
.add_proxy_service_to::<net::SocketProviderMarker, _>(
ns_builder.directory_request().unwrap().clone(),
);
let env = services.create_nested_environment("netstack3-env")?;
let _netstack = ns_builder.spawn(env.launcher())?;
fasync::spawn_local(services.collect());
let stack = Netstack::new(&env)?;
if let Some(eth_path) = options.ethernet {
// open ethernet device and send to stack.
let id = await!(stack.add_ethernet(eth_path))?;
println!("Created interface with id {}", id);
if let Some(ip_prefix) = options.ip_prefix {
let (addr, prefix) = parse_ip_addr_and_prefix(&ip_prefix)?;
await!(stack.set_ip(id, addr, prefix))?;
println!("Configured interface {} for address: {}", id, ip_prefix);
}
}
println!("enclosed netstack is running...");
println!("run:");
println!("chrealm /hub/r/netstack3-env/[koid]");
println!("to shell into the tailored environment (you can use tab completions for koid)");
println!("to stop the netstack and this environment, run:");
println!("killall enclosed_runner.cmx");
println!("or CTRL^C out.");
// Await forever on an empty future.
// This will cause enclosed_runner to never exit, unless it's killed
// from outside. We want enclosed_runner to keep alive so we will have
// the test environment alive as well so we can chrealm into it, or just
// observe netstack3.
let () = await!(futures::future::empty());
Ok(())
}