pub mod device;
pub mod frame_stream;
pub mod queues;
pub mod stack;
pub mod tcp_listeners;
pub mod tcp_relay;
use std::fmt;
use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::os::fd::RawFd;
use std::thread::JoinHandle;
use std::time::SystemTime;
use frame_stream::{start_frame_stream_bridge, FrameStreamBridge};
use queues::{NetworkFrameQueues, DEFAULT_FRAME_QUEUE_CAPACITY};
use stack::{start_network_stack, VirtioPollConfig};
use tcp_listeners::{create_tcp_channel, TcpPortListeners};
pub const DEFAULT_DNS_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PortMapping {
pub host: u16,
pub guest: u16,
}
impl PortMapping {
pub const fn new(host: u16, guest: u16) -> Self {
Self { host, guest }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GuestNetworkConfig {
pub guest_ip: Ipv4Addr,
pub gateway_ip: Ipv4Addr,
pub prefix_len: u8,
pub guest_mac: [u8; 6],
pub gateway_mac: [u8; 6],
pub dns_server: Ipv4Addr,
}
impl GuestNetworkConfig {
pub const fn default() -> Self {
Self {
guest_ip: Ipv4Addr::new(100, 96, 0, 2),
gateway_ip: Ipv4Addr::new(100, 96, 0, 1),
prefix_len: 30,
guest_mac: [0x02, 0x53, 0x4d, 0x00, 0x00, 0x02],
gateway_mac: [0x02, 0x53, 0x4d, 0x00, 0x00, 0x01],
dns_server: Ipv4Addr::new(100, 96, 0, 1),
}
}
}
fn format_network_log_line(timestamp: SystemTime, message: &str) -> String {
format!(
"[{}]: {}",
humantime::format_rfc3339_seconds(timestamp),
message
)
}
pub(crate) fn emit_network_log_line(message: fmt::Arguments<'_>) {
eprintln!(
"{}",
format_network_log_line(SystemTime::now(), &message.to_string())
);
}
macro_rules! virtio_net_log {
($($arg:tt)*) => {
$crate::emit_network_log_line(format_args!($($arg)*))
};
}
pub(crate) use virtio_net_log;
pub struct VirtioNetworkRuntime {
queues: std::sync::Arc<NetworkFrameQueues>,
_frame_bridge: FrameStreamBridge,
published_ports: Option<TcpPortListeners>,
poll_handle: Option<JoinHandle<()>>,
}
pub fn start_virtio_network(
host_fd: RawFd,
guest_network: GuestNetworkConfig,
published_ports: &[PortMapping],
) -> io::Result<VirtioNetworkRuntime> {
virtio_net_log!(
"virtio-net: starting runtime host_fd={} guest_ip={} gateway_ip={} dns_server={}",
host_fd,
guest_network.guest_ip,
guest_network.gateway_ip,
guest_network.dns_server
);
let queues = NetworkFrameQueues::shared(DEFAULT_FRAME_QUEUE_CAPACITY);
let frame_bridge = start_frame_stream_bridge(host_fd, queues.clone())?;
let (tcp_sender, tcp_receiver) = create_tcp_channel();
let tcp_listeners = if published_ports.is_empty() {
None
} else {
Some(TcpPortListeners::start(
published_ports,
tcp_sender,
queues.relay_wake.clone(),
)?)
};
let poll_handle = start_network_stack(
queues.clone(),
VirtioPollConfig {
gateway_mac: guest_network.gateway_mac,
guest_mac: guest_network.guest_mac,
gateway_ipv4: guest_network.gateway_ip,
guest_ipv4: guest_network.guest_ip,
mtu: 1500,
},
tcp_listeners.as_ref().map(|_| tcp_receiver),
)?;
Ok(VirtioNetworkRuntime {
queues,
_frame_bridge: frame_bridge,
published_ports: tcp_listeners,
poll_handle: Some(poll_handle),
})
}
impl Drop for VirtioNetworkRuntime {
fn drop(&mut self) {
self.queues.begin_shutdown();
self.published_ports = None;
if let Some(handle) = self.poll_handle.take() {
let _ = handle.join();
}
}
}
#[cfg(test)]
mod tests {
use super::format_network_log_line;
use std::time::UNIX_EPOCH;
#[test]
fn formats_timestamped_network_log_prefix() {
let line = format_network_log_line(UNIX_EPOCH, "virtio-net: smoke test");
assert_eq!(line, "[1970-01-01T00:00:00Z]: virtio-net: smoke test");
}
}