blob: 2b566358a91220170e599afae77713bb5b23a13c [file] [log] [blame]
// Copyright 2021 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.
//! Fuchsia netdevice client tun test
use assert_matches::assert_matches;
use fidl::endpoints;
use fuchsia_component::client::connect_to_protocol;
use futures::future::{Future, FutureExt as _};
use futures::stream::TryStreamExt as _;
use netdevice_client::{Client, Error, Port, Session};
use std::convert::TryInto as _;
use std::io::{Read as _, Write as _};
use {
fidl_fuchsia_hardware_network as netdev, fidl_fuchsia_net_tun as tun, fuchsia_async as fasync,
fuchsia_zircon as zx,
};
const DEFAULT_PORT_ID: u8 = 2;
const DEFAULT_MTU: u32 = 1500;
const DATA_BYTE: u8 = 42;
const DATA_LEN: usize = 4;
const SESSION_BUFFER_LEN: usize = 2048;
#[fasync::run_singlethreaded(test)]
async fn test_rx() {
let (tun, _tun_port, port) = create_tun_device_and_port().await;
let client = create_netdev_client(&tun);
let () = with_netdev_session(client, port, "test_rx", |session, _client| async move {
let frame = tun::Frame {
frame_type: Some(netdev::FrameType::Ethernet),
data: Some(vec![DATA_BYTE; DATA_LEN]),
port: Some(DEFAULT_PORT_ID),
..Default::default()
};
let () = tun.write_frame(&frame).await.unwrap().expect("failed to write frame");
let buff = session.recv().await.expect("failed to recv buffer");
let mut bytes = [0u8; DATA_LEN];
buff.read_at(0, &mut bytes[..]).expect("failed to read from the buffer");
for i in bytes.iter() {
assert_eq!(*i, DATA_BYTE);
}
})
.await;
}
#[fasync::run_singlethreaded(test)]
async fn test_tx() {
let (tun, _tun_port, port) = create_tun_device_and_port().await;
let client = create_netdev_client(&tun);
let () = with_netdev_session(client, port, "test_tx", |session, _client| async move {
let mut buffer =
session.alloc_tx_buffer(DATA_LEN).await.expect("failed to alloc tx buffer");
assert_eq!(
buffer.write(&[DATA_BYTE; DATA_LEN][..]).expect("failed to write into the buffer"),
DATA_LEN
);
buffer.set_port(port);
buffer.set_frame_type(netdev::FrameType::Ethernet);
session.send(buffer).expect("failed to send the buffer");
let frame = tun
.read_frame()
.await
.unwrap()
.map_err(zx::Status::from_raw)
.expect("failed to read frame from the tun device");
assert_eq!(frame.data, Some(vec![DATA_BYTE; DATA_LEN]));
assert_eq!(frame.frame_type, Some(netdev::FrameType::Ethernet));
assert_eq!(frame.port, Some(DEFAULT_PORT_ID));
})
.await;
}
// Receives buffer from session and echoes back. It copies the content from
// half of the buffers, round robin on index.
async fn echo(session: Session, port: Port, frame_count: u32) {
for i in 0..frame_count {
let mut buffer = session.recv().await.expect("failed to recv from session");
assert_eq!(buffer.cap(), DATA_LEN);
let mut bytes = [0u8; DATA_LEN];
assert_eq!(buffer.read(&mut bytes[..]).unwrap(), DATA_LEN);
assert_eq!(u32::from_le_bytes(bytes), i);
if i % 2 == 0 {
let buffer = buffer.into_tx().await;
session.send(buffer).expect("failed to send the buffer back on the zero-copy path");
} else {
let mut buffer =
session.alloc_tx_buffer(DATA_LEN).await.expect("no tx buffer available");
buffer.set_frame_type(netdev::FrameType::Ethernet);
buffer.set_port(port);
assert_eq!(buffer.write(&bytes).unwrap(), DATA_LEN);
session.send(buffer).expect("failed to send the buffer back on the copying path");
}
}
}
#[fasync::run_singlethreaded(test)]
async fn test_echo_tun() {
const FRAME_TOTAL_COUNT: u32 = 512;
let (tun, _tun_port, port) = create_tun_device_and_port().await;
let client = create_netdev_client(&tun);
with_netdev_session(client, port, "test_echo_tun", |session, _client| async {
let echo_fut = echo(session, port, FRAME_TOTAL_COUNT);
let main_fut = async move {
for i in 0..FRAME_TOTAL_COUNT {
let frame = tun::Frame {
frame_type: Some(netdev::FrameType::Ethernet),
data: Some(Vec::from(i.to_le_bytes())),
port: Some(DEFAULT_PORT_ID),
..Default::default()
};
let () = tun
.write_frame(&frame)
.await
.unwrap()
.map_err(zx::Status::from_raw)
.expect("cannot write frame");
let frame = tun
.read_frame()
.await
.unwrap()
.map_err(zx::Status::from_raw)
.expect("failed to read frame");
let data = frame.data.unwrap();
assert_eq!(data.len(), DATA_LEN);
let bytes: [u8; DATA_LEN] = data.try_into().unwrap();
assert_eq!(u32::from_le_bytes(bytes), i);
}
};
let ((), ()) = futures::join!(echo_fut, main_fut);
})
.await;
}
#[fasync::run_singlethreaded(test)]
async fn test_echo_pair() {
const FRAME_TOTAL_COUNT: u32 = 512;
let pair = create_tun_device_pair();
let (client1, port1, client2, port2) = create_netdev_client_pair(&pair).await;
let () =
with_netdev_session(client1, port1, "test_echo_pair_1", |session1, client1| async move {
let () = with_netdev_session(
client2,
port2,
"test_echo_pair_2",
|session2, client2| async move {
// Wait for the ports to be online before we send anything.
assert_matches!(
client1.wait_online(port1).await,
Ok(netdevice_client::PortStatus {
flags: netdev::StatusFlags::ONLINE,
mtu: DEFAULT_MTU
})
);
assert_matches!(
client2.wait_online(port2).await,
Ok(netdevice_client::PortStatus {
flags: netdev::StatusFlags::ONLINE,
mtu: DEFAULT_MTU
})
);
let echo_fut = echo(session1, port1, FRAME_TOTAL_COUNT);
let main_fut = async {
for i in 0..FRAME_TOTAL_COUNT {
let mut buffer = session2
.alloc_tx_buffer(DATA_LEN)
.await
.expect("failed to alloc tx buffer");
buffer.set_frame_type(netdev::FrameType::Ethernet);
buffer.set_port(port2);
let mut bytes = i.to_le_bytes();
assert_eq!(
buffer.write(&bytes[..]).expect("failed to write into the buffer"),
DATA_LEN
);
session2.send(buffer).expect("failed to send the buffer");
let mut buffer =
session2.recv().await.expect("failed to recv from the session");
assert_eq!(
buffer
.read(&mut bytes[..])
.expect("failed to read from the buffer"),
DATA_LEN
);
assert_eq!(u32::from_le_bytes(bytes), i);
}
};
futures::join!(echo_fut, main_fut);
},
)
.await;
})
.await;
}
#[test]
fn test_session_task_dropped() {
let mut executor = fasync::TestExecutor::new();
let session = executor.run_singlethreaded(async {
let (tun, _tun_port, port) = create_tun_device_and_port().await;
let client = create_netdev_client(&tun);
let (session, _task) = client
.primary_session("test_session_task_dropped", SESSION_BUFFER_LEN)
.await
.expect("failed to create session");
let () = session
.attach(port, &[netdev::FrameType::Ethernet])
.await
.expect("failed to attach session");
session
});
executor.run_singlethreaded(async move {
assert_matches!(
session
.alloc_tx_buffer(DATA_LEN)
.await
.expect_err("alloc_tx_buffer should fail after the session is detached"),
Error::NoProgress
);
assert_matches!(
session.recv().await.expect_err("recv should fail after the session is detached"),
Error::NoProgress
);
})
}
#[fasync::run_singlethreaded(test)]
async fn test_status_stream() {
const TOGGLE_COUNT: usize = 3;
let (tun, tun_port, port) = create_tun_device_and_port().await;
let client = create_netdev_client(&tun);
let mut watcher = client.port_status_stream(port).expect("failed to create a status watcher");
for _ in 0..TOGGLE_COUNT {
assert_matches!(
watcher.try_next().await,
Ok(Some(netdevice_client::PortStatus {
flags: netdev::StatusFlags::ONLINE,
mtu: DEFAULT_MTU
}))
);
tun_port.set_online(false).await.expect("failed to flip online flag");
assert_eq!(
watcher.try_next().await.expect("failed to get next status update"),
Some(netdevice_client::PortStatus {
flags: netdev::StatusFlags::empty(),
mtu: DEFAULT_MTU
})
);
tun_port.set_online(true).await.expect("failed to flip online flag");
}
}
#[fasync::run_singlethreaded(test)]
async fn test_port_stream() {
let (_tun, mut stream, port) = {
let (tun, _tun_port, port) = create_tun_device_and_port().await;
let client = create_netdev_client(&tun);
let mut stream = client.device_port_event_stream().expect("failed to create port stream");
assert_matches!(
stream.try_next().await.expect("failed to get next event"),
Some(netdev::DevicePortEvent::Existing(p)) if p == port.into()
);
assert_matches!(
stream.try_next().await.expect("failed to get next event"),
Some(netdev::DevicePortEvent::Idle(netdev::Empty { .. }))
);
(tun, stream, port)
};
assert_matches!(
stream.try_next().await.expect("failed to get next event"),
Some(netdev::DevicePortEvent::Removed(p)) if p == port.into()
);
}
fn default_base_port_config() -> tun::BasePortConfig {
tun::BasePortConfig {
id: Some(DEFAULT_PORT_ID),
mtu: Some(DEFAULT_MTU),
rx_types: Some(vec![netdev::FrameType::Ethernet]),
tx_types: Some(vec![netdev::FrameTypeSupport {
type_: netdev::FrameType::Ethernet,
features: netdev::FRAME_FEATURES_RAW,
supported_flags: netdev::TxFlags::empty(),
}]),
..Default::default()
}
}
async fn create_tun_device_and_port() -> (tun::DeviceProxy, tun::PortProxy, Port) {
let ctrl =
connect_to_protocol::<tun::ControlMarker>().expect("failed to connect to tun.Control");
let (device, server) = endpoints::create_proxy::<tun::DeviceMarker>()
.expect("failed to create proxy for tun::Device");
ctrl.create_device(&tun::DeviceConfig { blocking: Some(true), ..Default::default() }, server)
.expect("failed to create device");
let (port, server) =
endpoints::create_proxy::<tun::PortMarker>().expect("failed to create proxy for tun::Port");
device
.add_port(
&tun::DevicePortConfig {
base: Some(default_base_port_config()),
online: Some(true),
..Default::default()
},
server,
)
.expect("failed to add port to device");
let (device_port, server) =
endpoints::create_proxy::<netdev::PortMarker>().expect("failed to create endpoints");
port.get_port(server).expect("get device port");
let id = device_port.get_info().await.expect("getting port info").id.expect("missing port id");
(device, port, id.try_into().expect("bad port id"))
}
fn create_tun_device_pair() -> tun::DevicePairProxy {
let ctrl =
connect_to_protocol::<tun::ControlMarker>().expect("failed to connect to tun.Control");
let (pair, server) = endpoints::create_proxy::<tun::DevicePairMarker>()
.expect("failed to create proxy for tun::DevicePair");
ctrl.create_pair(&tun::DevicePairConfig::default(), server).expect("create device pair");
pair
}
fn create_netdev_client_and_server() -> (Client, endpoints::ServerEnd<netdev::DeviceMarker>) {
let (device, server) =
endpoints::create_proxy::<netdev::DeviceMarker>().expect("failed to create proxy");
(Client::new(device), server)
}
fn create_netdev_client(tun: &tun::DeviceProxy) -> Client {
let (client, server) = create_netdev_client_and_server();
tun.get_device(server).expect("failed to connect device to tun");
client
}
async fn create_netdev_client_pair(pair: &tun::DevicePairProxy) -> (Client, Port, Client, Port) {
let (device_left, left) = endpoints::create_proxy::<netdev::DeviceMarker>()
.expect("failed to create left device proxy");
let (device_right, right) = endpoints::create_proxy::<netdev::DeviceMarker>()
.expect("failed to create right device proxy");
pair.get_left(left).expect("failed to connect left");
pair.get_right(right).expect("failed to connect right");
pair.add_port(&tun::DevicePairPortConfig {
base: Some(default_base_port_config()),
..Default::default()
})
.await
.unwrap()
.expect("failed to create the default logical port");
async fn get_port_id<F: FnOnce(fidl::endpoints::ServerEnd<netdev::PortMarker>)>(f: F) -> Port {
let (port, server) =
fidl::endpoints::create_proxy::<netdev::PortMarker>().expect("create endpoints");
f(server);
port.get_info()
.await
.expect("getting info")
.id
.expect("missing id")
.try_into()
.expect("bad port id")
}
let port_left =
get_port_id(|server| pair.get_left_port(DEFAULT_PORT_ID, server).expect("get left port"))
.await;
let port_right =
get_port_id(|server| pair.get_right_port(DEFAULT_PORT_ID, server).expect("get right port"))
.await;
(Client::new(device_left), port_left, Client::new(device_right), port_right)
}
async fn with_netdev_session<F, Fut>(client: Client, port: Port, name: &str, f: F)
where
F: FnOnce(Session, Client) -> Fut,
Fut: Future<Output = ()>,
{
let (session, task) =
client.primary_session(name, SESSION_BUFFER_LEN).await.expect("failed to create session");
let () = session
.attach(port, &[netdev::FrameType::Ethernet])
.await
.expect("failed to attach session");
futures::select! {
() = f(session, client).fuse() => {},
res = task.fuse() => panic!("the background task for session terminated with {:?}", res),
}
}