From eca680eff0841af8383467fbed6b7c6dbe9668ad Mon Sep 17 00:00:00 2001 From: Seiya Nuta Date: Tue, 14 Dec 2021 23:38:02 +0900 Subject: [PATCH] Make network parameters and allowed PCI devices configurable via cmdline These cmdline parameters are used for running Kerla on DigitalOcean. --- kernel/main.rs | 8 +++- kernel/net/mod.rs | 77 ++++++++++++++++++++++++++++++------ libs/kerla_api/driver/mod.rs | 21 +++++++++- libs/kerla_api/driver/pci.rs | 13 +++++- libs/kerla_api/kernel_ops.rs | 10 +++-- runtime/bootinfo.rs | 9 +++++ runtime/x64/bootinfo.rs | 61 +++++++++++++++++++++++++++- 7 files changed, 177 insertions(+), 22 deletions(-) diff --git a/kernel/main.rs b/kernel/main.rs index e828fd1b..36c5da66 100644 --- a/kernel/main.rs +++ b/kernel/main.rs @@ -182,11 +182,15 @@ pub fn boot_kernel(#[cfg_attr(debug_assertions, allow(unused))] bootinfo: &BootI profiler.lap_time("virtio_net init"); // Initialize device drivers. - kerla_api::kernel_ops::init_drivers(bootinfo.pci_enabled, &bootinfo.virtio_mmio_devices); + kerla_api::kernel_ops::init_drivers( + bootinfo.pci_enabled, + &bootinfo.pci_allowlist, + &bootinfo.virtio_mmio_devices, + ); profiler.lap_time("drivers init"); // Connect to the network. - net::init_and_start_dhcp_discover(); + net::init_and_start_dhcp_discover(bootinfo); profiler.lap_time("net init"); // Prepare the root file system. diff --git a/kernel/net/mod.rs b/kernel/net/mod.rs index ab443638..143dbc46 100644 --- a/kernel/net/mod.rs +++ b/kernel/net/mod.rs @@ -8,6 +8,7 @@ use alloc::vec::Vec; use atomic_refcell::AtomicRefCell; use crossbeam::queue::ArrayQueue; use kerla_api::driver::net::EthernetDriver; +use kerla_runtime::bootinfo::BootInfo; use kerla_runtime::spinlock::SpinLock; use kerla_utils::once::Once; use smoltcp::wire::{self, EthernetAddress, IpCidr}; @@ -60,6 +61,7 @@ impl From for Instant { pub(self) static SOCKETS: Once> = Once::new(); static INTERFACE: Once>> = Once::new(); static DHCP_CLIENT: Once> = Once::new(); +static DHCP_ENABLED: Once = Once::new(); pub(self) static SOCKET_WAIT_QUEUE: Once = Once::new(); pub fn process_packets() { @@ -180,12 +182,60 @@ pub fn use_ethernet_driver) -> R, R>(f: F) -> f(driver.as_ref().expect("no ethernet drivers")) } -pub fn init_and_start_dhcp_discover() { +#[derive(Debug)] +struct IPv4AddrParseError; + +/// Parses an IPv4 address (e.g. "10.123.123.123"). +fn parse_ipv4_addr(addr: &str) -> Result { + let mut iter = addr.splitn(4, '.'); + let mut octets = [0; 4]; + for octet in &mut octets { + *octet = iter + .next() + .and_then(|s| s.parse().ok()) + .ok_or(IPv4AddrParseError)?; + } + + Ok(wire::Ipv4Address::from_bytes(&octets)) +} + +/// Parses an IPv4 address with the prefix length (e.g. "10.123.123.123/24"). +fn parse_ipv4_addr_with_prefix_len( + addr: &str, +) -> Result<(wire::Ipv4Address, u8), IPv4AddrParseError> { + let mut iter = addr.splitn(2, '/'); + let ip = parse_ipv4_addr(iter.next().unwrap())?; + let prefix_len = iter + .next() + .ok_or(IPv4AddrParseError)? + .parse() + .map_err(|_| IPv4AddrParseError)?; + + Ok((ip, prefix_len)) +} +pub fn init_and_start_dhcp_discover(bootinfo: &BootInfo) { + let ip_addrs = match &bootinfo.ip4 { + Some(ip4_str) => { + let (ip4, prefix_len) = parse_ipv4_addr_with_prefix_len(ip4_str) + .expect("bootinfo.ip4 should be formed as 10.0.0.1/24"); + info!("net: using a static IPv4 address: {}/{}", ip4, prefix_len); + [IpCidr::new(ip4.into(), prefix_len)] + } + None => [IpCidr::new(wire::Ipv4Address::UNSPECIFIED.into(), 0)], + }; + + let mut routes = Routes::new(BTreeMap::new()); + if let Some(gateway_ip4_str) = &bootinfo.gateway_ip4 { + let gateway_ip4 = parse_ipv4_addr(gateway_ip4_str) + .expect("bootinfo.gateway_ip4 should be formed as 10.0.0.1"); + info!("net: using a static gateway IPv4 address: {}", gateway_ip4); + routes.add_default_ipv4_route(gateway_ip4).unwrap(); + }; + let neighbor_cache = NeighborCache::new(BTreeMap::new()); + let mac_addr = use_ethernet_driver(|driver| driver.mac_addr()); let ethernet_addr = EthernetAddress(mac_addr.as_array()); - let ip_addrs = [IpCidr::new(wire::Ipv4Address::UNSPECIFIED.into(), 0)]; - let routes = Routes::new(BTreeMap::new()); let iface = EthernetInterfaceBuilder::new(OurDevice) .ethernet_addr(ethernet_addr) .neighbor_cache(neighbor_cache) @@ -194,20 +244,23 @@ pub fn init_and_start_dhcp_discover() { .finalize(); let mut sockets = SocketSet::new(vec![]); - let dhcp_rx_buffer = RawSocketBuffer::new([RawPacketMetadata::EMPTY; 4], vec![0; 2048]); - let dhcp_tx_buffer = RawSocketBuffer::new([RawPacketMetadata::EMPTY; 4], vec![0; 2048]); - let dhcp = Dhcpv4Client::new( - &mut sockets, - dhcp_rx_buffer, - dhcp_tx_buffer, - read_monotonic_clock().into(), - ); + DHCP_ENABLED.init(|| bootinfo.dhcp_enabled); + if *DHCP_ENABLED { + let dhcp_rx_buffer = RawSocketBuffer::new([RawPacketMetadata::EMPTY; 4], vec![0; 2048]); + let dhcp_tx_buffer = RawSocketBuffer::new([RawPacketMetadata::EMPTY; 4], vec![0; 2048]); + let dhcp = Dhcpv4Client::new( + &mut sockets, + dhcp_rx_buffer, + dhcp_tx_buffer, + read_monotonic_clock().into(), + ); + DHCP_CLIENT.init(|| SpinLock::new(dhcp)); + } RX_PACKET_QUEUE.init(|| SpinLock::new(ArrayQueue::new(128))); SOCKET_WAIT_QUEUE.init(WaitQueue::new); INTERFACE.init(|| SpinLock::new(iface)); SOCKETS.init(|| SpinLock::new(sockets)); - DHCP_CLIENT.init(|| SpinLock::new(dhcp)); process_packets(); } diff --git a/libs/kerla_api/driver/mod.rs b/libs/kerla_api/driver/mod.rs index b304726c..f4d92868 100644 --- a/libs/kerla_api/driver/mod.rs +++ b/libs/kerla_api/driver/mod.rs @@ -10,7 +10,7 @@ pub mod pci; pub use kerla_runtime::bootinfo::VirtioMmioDevice; use alloc::boxed::Box; -use kerla_runtime::spinlock::SpinLock; +use kerla_runtime::{bootinfo::AllowedPciDevice, spinlock::SpinLock}; use self::pci::PciDevice; @@ -33,10 +33,27 @@ pub fn attach_irq(irq: u8, f: F) { kernel_ops().attach_irq(irq, Box::new(f)) } -pub fn init(pci_enabled: bool, mmio_devices: &[VirtioMmioDevice]) { +pub fn init( + pci_enabled: bool, + pci_allowlist: &[AllowedPciDevice], + mmio_devices: &[VirtioMmioDevice], +) { // Scan PCI devices. if pci_enabled { for device in pci::enumerate_pci_devices() { + if !pci_allowlist.is_empty() + && !pci_allowlist + .iter() + .any(|e| e.bus == device.bus() && e.slot == device.slot()) + { + trace!( + "pci: skipping not allowed device: id={:04x}:{:04x}", + device.config().vendor_id(), + device.config().device_id(), + ); + continue; + } + trace!( "pci: found a device: id={:04x}:{:04x}, bar0={:016x?}, irq={}", device.config().vendor_id(), diff --git a/libs/kerla_api/driver/pci.rs b/libs/kerla_api/driver/pci.rs index 11581cbf..88273dad 100644 --- a/libs/kerla_api/driver/pci.rs +++ b/libs/kerla_api/driver/pci.rs @@ -111,6 +111,14 @@ impl PciDevice { ); } + pub fn bus(&self) -> u8 { + self.bus + } + + pub fn slot(&self) -> u8 { + self.slot + } + pub fn capabilities(&self) -> &[PciCapability] { &self.capabilities } @@ -214,13 +222,14 @@ impl Iterator for PciScanner { } let config = self.bus.read_device_config(self.bus_no, self.slot); + let slot = self.slot; self.slot += 1; if let Some(config) = config { - let capabilities = self.bus.read_capabilities(self.bus_no, self.slot - 1); + let capabilities = self.bus.read_capabilities(self.bus_no, slot); return Some(PciDevice { bus: self.bus_no, - slot: self.slot - 1, + slot, config, capabilities, }); diff --git a/libs/kerla_api/kernel_ops.rs b/libs/kerla_api/kernel_ops.rs index 29b724e7..5c665062 100644 --- a/libs/kerla_api/kernel_ops.rs +++ b/libs/kerla_api/kernel_ops.rs @@ -1,6 +1,6 @@ //! Internal APIs exposed for kerla_kernel crate. **Don't use from your kernel extensions!** use alloc::boxed::Box; -use kerla_runtime::bootinfo::VirtioMmioDevice; +use kerla_runtime::bootinfo::{AllowedPciDevice, VirtioMmioDevice}; use kerla_utils::static_cell::StaticCell; use crate::driver::{self, net::EthernetDriver}; @@ -33,6 +33,10 @@ pub fn init(ops: &'static dyn KernelOps) { set_kernel_ops(ops); } -pub fn init_drivers(pci_enabled: bool, mmio_devices: &[VirtioMmioDevice]) { - driver::init(pci_enabled, mmio_devices); +pub fn init_drivers( + pci_enabled: bool, + pci_allowlist: &[AllowedPciDevice], + mmio_devices: &[VirtioMmioDevice], +) { + driver::init(pci_enabled, pci_allowlist, mmio_devices); } diff --git a/runtime/bootinfo.rs b/runtime/bootinfo.rs index 5085fc4c..c99fd155 100644 --- a/runtime/bootinfo.rs +++ b/runtime/bootinfo.rs @@ -12,10 +12,19 @@ pub struct VirtioMmioDevice { pub irq: u8, } +pub struct AllowedPciDevice { + pub bus: u8, + pub slot: u8, +} + pub struct BootInfo { pub ram_areas: ArrayVec, pub virtio_mmio_devices: ArrayVec, pub log_filter: ArrayString<64>, pub pci_enabled: bool, + pub pci_allowlist: ArrayVec, pub use_second_serialport: bool, + pub dhcp_enabled: bool, + pub ip4: Option>, + pub gateway_ip4: Option>, } diff --git a/runtime/x64/bootinfo.rs b/runtime/x64/bootinfo.rs index 2cef686d..3cbdd3da 100644 --- a/runtime/x64/bootinfo.rs +++ b/runtime/x64/bootinfo.rs @@ -1,5 +1,5 @@ use crate::address::{PAddr, VAddr}; -use crate::bootinfo::{BootInfo, RamArea, VirtioMmioDevice}; +use crate::bootinfo::{AllowedPciDevice, BootInfo, RamArea, VirtioMmioDevice}; use arrayvec::{ArrayString, ArrayVec}; use core::cmp::max; use core::mem::size_of; @@ -129,6 +129,10 @@ struct Cmdline { pub virtio_mmio_devices: ArrayVec, pub log_filter: ArrayString<64>, pub use_second_serialport: bool, + pub dhcp_enabled: bool, + pub ip4: Option>, + pub gateway_ip4: Option>, + pub pci_allowlist: ArrayVec, } impl Cmdline { @@ -137,17 +141,38 @@ impl Cmdline { info!("cmdline: {}", if s.is_empty() { "(empty)" } else { s }); let mut pci_enabled = true; + let mut pci_allowlist = ArrayVec::new(); let mut virtio_mmio_devices = ArrayVec::new(); let mut log_filter = ArrayString::new(); let mut use_second_serialport = false; + let mut dhcp_enabled = true; + let mut ip4 = None; + let mut gateway_ip4 = None; if !s.is_empty() { for config in s.split(' ') { + if config.is_empty() { + continue; + } + let mut words = config.splitn(2, '='); match (words.next(), words.next()) { (Some("pci"), Some("off")) => { warn!("bootinfo: PCI disabled"); pci_enabled = false; } + (Some("pci_device"), Some(bus_and_slot)) => { + warn!("bootinfo: allowed PCI device: {}", bus_and_slot); + let mut iter = bus_and_slot.splitn(2, ':'); + let bus = iter + .next() + .and_then(|w| w.parse().ok()) + .expect("bootinfo.bus_and_slot must be formed as bus:slot"); + let slot = iter + .next() + .and_then(|w| w.parse().ok()) + .expect("bootinfo.bus_and_slot must be formed as bus:slot"); + pci_allowlist.push(AllowedPciDevice { bus, slot }); + } (Some("serial1"), Some("on")) => { info!("bootinfo: secondary serial port enabled"); use_second_serialport = true; @@ -176,6 +201,24 @@ impl Cmdline { irq, }) } + (Some("dhcp"), Some("off")) => { + warn!("bootinfo: DHCP disabled"); + dhcp_enabled = false; + } + (Some("ip4"), Some(value)) => { + let mut s = ArrayString::new(); + if s.try_push_str(value).is_err() { + warn!("bootinfo: ip4 is too long"); + } + ip4 = Some(s); + } + (Some("gateway_ip4"), Some(value)) => { + let mut s = ArrayString::new(); + if s.try_push_str(value).is_err() { + warn!("bootinfo: gateway_ip4 is too long"); + } + gateway_ip4 = Some(s); + } (Some(path), None) if path.starts_with('/') => { // QEMU appends a kernel image path. Just ignore it. } @@ -188,9 +231,13 @@ impl Cmdline { Cmdline { pci_enabled, + pci_allowlist, virtio_mmio_devices, log_filter, use_second_serialport, + dhcp_enabled, + ip4, + gateway_ip4, } } } @@ -287,9 +334,13 @@ unsafe fn parse_multiboot2_info(header: &Multiboot2InfoHeader) -> BootInfo { BootInfo { ram_areas, pci_enabled: cmdline.pci_enabled, + pci_allowlist: cmdline.pci_allowlist, virtio_mmio_devices: cmdline.virtio_mmio_devices, log_filter: cmdline.log_filter, use_second_serialport: cmdline.use_second_serialport, + dhcp_enabled: cmdline.dhcp_enabled, + ip4: cmdline.ip4, + gateway_ip4: cmdline.gateway_ip4, } } @@ -328,9 +379,13 @@ unsafe fn parse_multiboot_legacy_info(info: &MultibootLegacyInfo) -> BootInfo { BootInfo { ram_areas, pci_enabled: cmdline.pci_enabled, + pci_allowlist: cmdline.pci_allowlist, virtio_mmio_devices: cmdline.virtio_mmio_devices, log_filter: cmdline.log_filter, use_second_serialport: cmdline.use_second_serialport, + dhcp_enabled: cmdline.dhcp_enabled, + ip4: cmdline.ip4, + gateway_ip4: cmdline.gateway_ip4, } } @@ -359,9 +414,13 @@ unsafe fn parse_linux_boot_params(boot_params: PAddr) -> BootInfo { BootInfo { ram_areas, pci_enabled: cmdline.pci_enabled, + pci_allowlist: cmdline.pci_allowlist, virtio_mmio_devices: cmdline.virtio_mmio_devices, log_filter: cmdline.log_filter, use_second_serialport: cmdline.use_second_serialport, + dhcp_enabled: cmdline.dhcp_enabled, + ip4: cmdline.ip4, + gateway_ip4: cmdline.gateway_ip4, } }