From 80cf58e653eb57b929152d0b486f865a7034ef65 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Thu, 13 Aug 2026 16:00:24 +0200 Subject: [PATCH 1/4] refactor(network): Encode prefix length in HERMIT_IP and drop HERMIT_MASK Setting just HERMIT_IP=10.0.5.3/24 is much more convenient than HERMIT_IP=10.0.5.3 and HERMIT_MASK=255.255.255.0 and lets us avoid variable hell once we end up adding proper IPv6 support. --- src/env/mod.rs | 7 +++++-- src/executor/device.rs | 23 ++++++++++++++++------- xtask/src/ci/qemu.rs | 6 +++++- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/env/mod.rs b/src/env/mod.rs index 55fdaa4898..d95bd96f0c 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -73,8 +73,11 @@ impl Default for Cli { env_vars.insert(Cow::Borrowed("HERMIT_IP"), ip); } "-mask" => { - let mask = expect_arg(words.next(), word); - env_vars.insert(Cow::Borrowed("HERMIT_MASK"), mask); + // Ignore the argument value + drop(words.next()); + warn!( + "The -mask bootarg was removed in favor of including the prefix length in the -ip parameter" + ); } "-gateway" => { let gateway = expect_arg(words.next(), word); diff --git a/src/executor/device.rs b/src/executor/device.rs index 42d1886e48..7950c0105f 100644 --- a/src/executor/device.rs +++ b/src/executor/device.rs @@ -1,4 +1,5 @@ use alloc::boxed::Box; +use alloc::string::ToString; use core::str::FromStr; use smoltcp::iface::{Config, Interface, SocketSet}; @@ -115,20 +116,28 @@ impl<'a> NetworkInterface<'a> { sockets.add(dhcpv4::Socket::new()) }; + if hermit_var!("HERMIT_MASK").is_some() { + warn!( + "HERMIT_MASK was removed in favor of including the prefix length in HERMIT_IP and has no effect anymore" + ); + } + if !cfg!(feature = "dhcpv4") || hermit_var!("HERMIT_IP").is_some() { - let myip = Ipv4Address::from_str(hermit_var_or!("HERMIT_IP", "10.0.5.3")).unwrap(); - let mygw = Ipv4Address::from_str(hermit_var_or!("HERMIT_GATEWAY", "10.0.5.1")).unwrap(); - let mymask = - Ipv4Address::from_str(hermit_var_or!("HERMIT_MASK", "255.255.255.0")).unwrap(); + let ip_and_prefix_len = hermit_var_or!("HERMIT_IP", "10.0.5.3/24").to_string(); + let mut parts = ip_and_prefix_len.split('/'); + let ip = Ipv4Address::from_str(parts.next().unwrap()).unwrap(); + let prefix_len = parts.next().unwrap().parse().unwrap(); + let gw = Ipv4Address::from_str(hermit_var_or!("HERMIT_GATEWAY", "10.0.5.1")).unwrap(); + + let ip_addr = IpCidr::from(Ipv4Cidr::new(ip, prefix_len)); - let ip_addr = IpCidr::from(Ipv4Cidr::from_netmask(myip, mymask).unwrap()); info!("IP address: {ip_addr}"); - info!("Gateway: {mygw}"); + info!("Gateway: {gw}"); iface.update_ip_addrs(|ip_addrs| { ip_addrs.push(ip_addr).unwrap(); }); - iface.routes_mut().add_default_ipv4_route(mygw).unwrap(); + iface.routes_mut().add_default_ipv4_route(gw).unwrap(); #[cfg(feature = "dns")] { diff --git a/xtask/src/ci/qemu.rs b/xtask/src/ci/qemu.rs index 70201ac8de..34e625e4df 100644 --- a/xtask/src/ci/qemu.rs +++ b/xtask/src/ci/qemu.rs @@ -17,6 +17,7 @@ use crate::arch::Arch; use crate::ci; const DEFAULT_GUEST_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 5, 3)); +const DEFAULT_GUEST_PREFIX_LEN: u8 = 24; /// Run image on QEMU. #[derive(Args)] @@ -575,7 +576,10 @@ impl Qemu { args.extend(["-freq".to_owned(), frequency.to_string()]); } if self.tap { - args.extend(["-ip".to_owned(), DEFAULT_GUEST_IP.to_string()]); + args.extend([ + "-ip".to_owned(), + format!("{DEFAULT_GUEST_IP}/{DEFAULT_GUEST_PREFIX_LEN}"), + ]); } args } From f2f053503a7f0b2d80d40283d4d30fe3e86907ca Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Thu, 13 Aug 2026 16:49:41 +0200 Subject: [PATCH 2/4] feat(env): Parse ip= parameter This is heavily inspired by Linux' ip= parameter, see https://docs.kernel.org/admin-guide/nfs/nfsroot.html#kernel-command-line The facilities are currently unused and will be replacing HERMIT_IP and HERMIT_GATEWAY in a followup commit. --- src/env/ip_config.rs | 173 +++++++++++++++++++++++++++++++++++++++++++ src/env/mod.rs | 56 ++++++++++++-- 2 files changed, 223 insertions(+), 6 deletions(-) create mode 100644 src/env/ip_config.rs diff --git a/src/env/ip_config.rs b/src/env/ip_config.rs new file mode 100644 index 0000000000..9d772c303b --- /dev/null +++ b/src/env/ip_config.rs @@ -0,0 +1,173 @@ +use core::net::Ipv4Addr; +use core::{fmt, str}; + +use smoltcp::wire::{IpCidr, Ipv4Cidr}; + +/// IP configuration as passed via ip= to the kernel commandline. +/// +/// Fields are specified separated by colons (:), for example: +/// - ip=none or ip=off to skip configuring the default interface +/// - ip=dhcp to use DHCP for configuring the default interface +/// - ip=10.0.5.3/24:10.0.5.1 to configure a static IP and gateway on the default interface +/// +/// This is heavily inspired by the Linux kernel's parameter of the same name: +/// +#[derive(Clone, Copy, Debug, Default)] +pub struct IpConfig { + pub ip_and_gateway: IpAddrConfig, + // hostname is omitted + // device is omitted + // autoconf is omitted + #[cfg(feature = "dns")] + pub dns0: Option, + #[cfg(feature = "dns")] + pub dns1: Option, + // ntp0 is omitted +} + +impl TryFrom<&str> for IpConfig { + type Error = IpConfigParseError; + + fn try_from(value: &str) -> Result { + let mut ret = Self::default(); + let mut parts = value.split(':'); + + // The IP configuration is mandatory + ret.ip_and_gateway = IpAddrConfig::parse_from_parts(&mut parts)?; + + // Everything else is optional + let Some(_hostname) = parts.next() else { + return Ok(ret); + }; + + let Some(_device) = parts.next() else { + return Ok(ret); + }; + + let Some(_autoconf) = parts.next() else { + return Ok(ret); + }; + + let Some(dns0_ip_str) = parts.next() else { + return Ok(ret); + }; + #[cfg(feature = "dns")] + if !dns0_ip_str.is_empty() { + ret.dns0 = Some(dns0_ip_str.parse().map_err(|_| Self::Error::InvalidDns)?); + } + #[cfg(not(feature = "dns"))] + if !dns0_ip_str.is_empty() { + warn!("DNS 0 IP specified without enabling the dns feature, ignoring"); + } + + let Some(dns1_ip_str) = parts.next() else { + return Ok(ret); + }; + #[cfg(feature = "dns")] + if !dns1_ip_str.is_empty() { + ret.dns1 = Some(dns1_ip_str.parse().map_err(|_| Self::Error::InvalidDns)?); + } + #[cfg(not(feature = "dns"))] + if !dns1_ip_str.is_empty() { + warn!("DNS 1 IP specified without enabling the dns feature, ignoring"); + } + + let Some(_ntp0_ip) = parts.next() else { + return Ok(ret); + }; + + Ok(ret) + } +} + +#[derive(Debug)] +pub enum IpConfigParseError { + #[cfg(feature = "dns")] + InvalidDns, + InvalidGateway, + InvalidIp, + InvalidPrefixLen, + MissingIpOrMethod, + MissingPrefixLen, + #[cfg(not(feature = "dhcpv4"))] + DhcpNotEnabled, +} + +impl fmt::Display for IpConfigParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + #[cfg(feature = "dns")] + Self::InvalidDns => f.write_str("invalid DNS IP address"), + Self::InvalidGateway => f.write_str("invalid gateway IP address"), + Self::InvalidIp => f.write_str("invalid IP address"), + Self::InvalidPrefixLen => f.write_str("invalid prefix length"), + Self::MissingIpOrMethod => f.write_str("IP configuration is missing a method"), + Self::MissingPrefixLen => { + f.write_str("static IP configuration is missing a prefix length") + } + #[cfg(not(feature = "dhcpv4"))] + Self::DhcpNotEnabled => f.write_str("DHCP cannot be selected, disable via feature flags"), + } + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub enum IpAddrConfig { + #[cfg_attr(not(feature = "dhcpv4"), default)] + None, + #[cfg(feature = "dhcpv4")] + #[default] + Dhcp, + Static { + ip_and_netmask: IpCidr, + gateway: Option, + }, +} + +impl IpAddrConfig { + fn parse_from_parts(parts: &mut str::Split<'_, char>) -> Result { + let ip_or_type = parts.next().ok_or(IpConfigParseError::MissingIpOrMethod)?; + + match ip_or_type { + "none" | "off" => Ok(Self::None), + "dhcp" => { + #[cfg(feature = "dhcpv4")] + { + Ok(Self::Dhcp) + } + #[cfg(not(feature = "dhcpv4"))] + { + Err(IpConfigParseError::DhcpNotEnabled) + } + } + // Anything else must be an IP with a prefix length + ip_and_prefix => { + let mut ip_and_prefix_parts = ip_and_prefix.split('/'); + // We only support IPv4 for now + let ip = ip_and_prefix_parts + .next() + // split always has at least one item + .unwrap() + .parse() + .map_err(|_| IpConfigParseError::InvalidIp)?; + let prefix_len = ip_and_prefix_parts + .next() + .ok_or(IpConfigParseError::MissingPrefixLen)? + .parse() + .map_err(|_| IpConfigParseError::InvalidPrefixLen)?; + + // The gateway is optional since you can technically not specify one + let gateway = parts.next().map_or(Ok(None), |ip_str| { + ip_str + .parse() + .map_or(Err(IpConfigParseError::InvalidGateway), |ip| Ok(Some(ip))) + })?; + + Ok(Self::Static { + ip_and_netmask: IpCidr::from(Ipv4Cidr::new(ip, prefix_len)), + gateway, + }) + } + } + } +} diff --git a/src/env/mod.rs b/src/env/mod.rs index d95bd96f0c..ae3a0119bd 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,11 +1,12 @@ //! Inspection and manipulation of the kernel's environment. +#[cfg(feature = "net")] +mod ip_config; mod start_info; use alloc::borrow::{Cow, ToOwned}; use alloc::string::String; use alloc::vec::Vec; -use core::str; use ahash::RandomState; use hashbrown::HashMap; @@ -13,6 +14,8 @@ use hashbrown::hash_map::Iter; use hermit_sync::OnceCell; use shlex::Shlex; +#[cfg(feature = "net")] +pub use self::ip_config::*; pub use self::start_info::*; static CLI: OnceCell = OnceCell::new(); @@ -27,6 +30,8 @@ struct Cli { image_path: Option, #[cfg(not(target_arch = "riscv64"))] freq: Option, + #[cfg(feature = "net")] + default_interface_config: IpConfig, env_vars: HashMap, String, RandomState>, args: Vec, #[allow(dead_code)] @@ -52,6 +57,9 @@ impl Default for Cli { }) }; + #[cfg(feature = "net")] + let mut default_interface_config = None; + let mut args = Vec::new(); let mut mmio = Vec::new(); while let Some(word_owned) = words.next() { @@ -62,6 +70,28 @@ impl Default for Cli { continue; } + #[cfg_attr(not(feature = "net"), expect(unused_variables))] + if let Some(ip_config_str) = word.strip_prefix("ip=") { + #[cfg(feature = "net")] + match IpConfig::try_from(ip_config_str) { + Ok(config) => { + // This is the IP configuration for the default interface + // Once we support multiple interfaces, we need to support parsing multiple configurations + if default_interface_config.is_some() { + warn!("Duplicate ip= parameter passed, this is currently unsupported!"); + } + + default_interface_config = Some(config); + } + Err(e) => panic!("Could not parse configuration for default interface: {e}"), + } + + #[cfg(not(feature = "net"))] + warn!("ip= parameter passed with networking support disabled, ignoring"); + + continue; + } + match word { #[cfg(not(target_arch = "riscv64"))] "-freq" => { @@ -69,19 +99,25 @@ impl Default for Cli { freq = Some(s.parse().unwrap()); } "-ip" => { - let ip = expect_arg(words.next(), word); - env_vars.insert(Cow::Borrowed("HERMIT_IP"), ip); + // Ignore the argument value + drop(words.next()); + warn!( + "The -ip bootarg was removed in favor of the ip= parameter and has no effect anymore" + ); } "-mask" => { // Ignore the argument value drop(words.next()); warn!( - "The -mask bootarg was removed in favor of including the prefix length in the -ip parameter" + "The -mask bootarg was removed in favor of including the prefix length in the IP as part of the ip= parameter and has no effect anymore" ); } "-gateway" => { - let gateway = expect_arg(words.next(), word); - env_vars.insert(Cow::Borrowed("HERMIT_GATEWAY"), gateway); + // Ignore the argument value + drop(words.next()); + warn!( + "The -gateway bootarg was removed in favor of the ip= parameter and has no effect anymore" + ); } "-mount" => { let gateway = expect_arg(words.next(), word); @@ -108,6 +144,8 @@ impl Default for Cli { image_path, #[cfg(not(target_arch = "riscv64"))] freq, + #[cfg(feature = "net")] + default_interface_config: default_interface_config.unwrap_or_default(), env_vars, args, #[allow(dead_code)] @@ -144,6 +182,12 @@ pub fn early_var(key: &str) -> Option { } } +/// Returns the default interface IP configuration specified via ip=. +#[cfg(feature = "net")] +pub fn default_interface_config() -> IpConfig { + CLI.get().unwrap().default_interface_config +} + pub fn vars() -> Iter<'static, Cow<'static, str>, String> { CLI.get().unwrap().env_vars.iter() } From 8115beae721d411ad053e48b9747cc48f9c19385 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Thu, 13 Aug 2026 17:52:13 +0200 Subject: [PATCH 3/4] feat(network): Use parsed ip= parameter instead of environment variables --- .github/workflows/ci.yml | 4 +- src/executor/device.rs | 85 ++++++++++++++++++++-------------------- src/executor/network.rs | 8 +++- xtask/src/ci/qemu.rs | 16 ++++---- 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5ad6878f7..5cd3f57a84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -308,6 +308,4 @@ jobs: - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package poll --features hermit/dhcpv4,hermit/rtl8139 qemu ${{ matrix.qemu_flags }} --devices rtl8139 - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package mioudp --features hermit/udp,hermit/dhcpv4,hermit/virtio-net qemu ${{ matrix.qemu_flags }} --devices virtio-net-pci - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package mioudp --features hermit/udp,hermit/dhcpv4,hermit/rtl8139 qemu ${{ matrix.qemu_flags }} --devices rtl8139 - - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package loopback qemu ${{ matrix.qemu_flags }} - env: - HERMIT_IP: 127.0.0.1 + - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package loopback qemu ${{ matrix.qemu_flags }} -- -- ip=127.0.0.1/8 diff --git a/src/executor/device.rs b/src/executor/device.rs index 7950c0105f..64cd803e28 100644 --- a/src/executor/device.rs +++ b/src/executor/device.rs @@ -1,6 +1,6 @@ use alloc::boxed::Box; -use alloc::string::ToString; -use core::str::FromStr; +#[cfg(feature = "dns")] +use alloc::vec::Vec; use smoltcp::iface::{Config, Interface, SocketSet}; #[cfg(feature = "net-trace")] @@ -12,7 +12,7 @@ use smoltcp::phy::{PcapMode, PcapWriter}; use smoltcp::socket::dhcpv4; #[cfg(feature = "dns")] use smoltcp::socket::dns; -use smoltcp::wire::{EthernetAddress, HardwareAddress, IpCidr, Ipv4Address, Ipv4Cidr}; +use smoltcp::wire::{EthernetAddress, HardwareAddress}; use super::network::{NetworkInterface, NetworkState}; use crate::arch::kernel::systemtime; @@ -26,6 +26,7 @@ use crate::drivers::Driver; ))] use crate::drivers::net::NetworkDevice; use crate::drivers::net::NetworkDriver; +use crate::env::{IpAddrConfig, default_interface_config}; cfg_select! { any( @@ -102,55 +103,53 @@ impl<'a> NetworkInterface<'a> { #[cfg_attr(all(not(feature = "dhcpv4"), not(feature = "dns")), expect(unused_mut))] let mut sockets = SocketSet::new(vec![]); + #[cfg(feature = "dhcpv4")] + let mut dhcp_handle = None; #[cfg(feature = "dns")] let mut dns_handle = None; - #[cfg(feature = "dhcpv4")] - let dhcp_handle = { - if let Some(hermit_ip) = hermit_var!("HERMIT_IP") { - warn!("HERMIT_IP was set to {hermit_ip}, but Hermit was built with DHCPv4."); - warn!( - "HERMIT_IP will be overwritten if a DHCP configuration is acquired. If the provided configuration was not meant to be a fallback, disable the DHCP feature." - ); - } - sockets.add(dhcpv4::Socket::new()) - }; - - if hermit_var!("HERMIT_MASK").is_some() { + if hermit_var!("HERMIT_IP").is_some() + || hermit_var!("HERMIT_GATEWAY").is_some() + || hermit_var!("HERMIT_MASK").is_some() + { warn!( - "HERMIT_MASK was removed in favor of including the prefix length in HERMIT_IP and has no effect anymore" + "HERMIT_IP, HERMIT_GATEWAY and HERMIT_MASK were removed in favor of the ip= parameter and have no effect anymore" ); } - if !cfg!(feature = "dhcpv4") || hermit_var!("HERMIT_IP").is_some() { - let ip_and_prefix_len = hermit_var_or!("HERMIT_IP", "10.0.5.3/24").to_string(); - let mut parts = ip_and_prefix_len.split('/'); - let ip = Ipv4Address::from_str(parts.next().unwrap()).unwrap(); - let prefix_len = parts.next().unwrap().parse().unwrap(); - let gw = Ipv4Address::from_str(hermit_var_or!("HERMIT_GATEWAY", "10.0.5.1")).unwrap(); - - let ip_addr = IpCidr::from(Ipv4Cidr::new(ip, prefix_len)); - - info!("IP address: {ip_addr}"); - info!("Gateway: {gw}"); + let if_config = default_interface_config(); - iface.update_ip_addrs(|ip_addrs| { - ip_addrs.push(ip_addr).unwrap(); - }); - iface.routes_mut().add_default_ipv4_route(gw).unwrap(); + match if_config.ip_and_gateway { + IpAddrConfig::None => {} + #[cfg(feature = "dhcpv4")] + IpAddrConfig::Dhcp => { + dhcp_handle = Some(sockets.add(dhcpv4::Socket::new())); + } + IpAddrConfig::Static { + ip_and_netmask, + gateway, + } => { + info!("IP address: {ip_and_netmask}"); + + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.push(ip_and_netmask).unwrap(); + }); + + if let Some(gateway) = gateway { + info!("Gateway: {gateway}"); + iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); + } - #[cfg(feature = "dns")] - { - // Quad9 DNS server - let mydns1 = - Ipv4Address::from_str(hermit_var_or!("HERMIT_DNS1", "9.9.9.9")).unwrap(); - // Cloudflare DNS server - let mydns2 = - Ipv4Address::from_str(hermit_var_or!("HERMIT_DNS2", "1.1.1.1")).unwrap(); - let servers = &[mydns1.into(), mydns2.into()]; - let dns_socket = dns::Socket::new(servers, vec![]); - dns_handle = Some(sockets.add(dns_socket)); - }; + #[cfg(feature = "dns")] + { + let servers = &[if_config.dns0, if_config.dns1] + .into_iter() + .flat_map(|i| i.map(Into::into)) + .collect::>(); + let dns_socket = dns::Socket::new(servers, vec![]); + dns_handle = Some(sockets.add(dns_socket)); + }; + } } NetworkState::Initialized(Box::new(Self { diff --git a/src/executor/network.rs b/src/executor/network.rs index a233f427a2..885f63e5b8 100644 --- a/src/executor/network.rs +++ b/src/executor/network.rs @@ -98,7 +98,7 @@ pub(crate) struct NetworkInterface<'a> { pub(super) sockets: SocketSet<'a>, pub(super) device: MaybeTracerDevice, #[cfg(feature = "dhcpv4")] - pub(super) dhcp_handle: SocketHandle, + pub(super) dhcp_handle: Option, #[cfg(feature = "dns")] pub(super) dns_handle: Option, } @@ -149,7 +149,11 @@ async fn dhcpv4_run() { }; let nic = guard.as_nic_mut().unwrap(); - let dhcp_handle = nic.dhcp_handle; + let Some(dhcp_handle) = nic.dhcp_handle else { + // DHCP enabled at compile time but not configure at runtime + return Poll::Ready(()); + }; + let socket = nic.sockets.get_mut::>(dhcp_handle); socket.register_waker(cx.waker()); diff --git a/xtask/src/ci/qemu.rs b/xtask/src/ci/qemu.rs index 34e625e4df..3e5947621c 100644 --- a/xtask/src/ci/qemu.rs +++ b/xtask/src/ci/qemu.rs @@ -18,6 +18,9 @@ use crate::ci; const DEFAULT_GUEST_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 5, 3)); const DEFAULT_GUEST_PREFIX_LEN: u8 = 24; +const DEFAULT_GUEST_GATEWAY: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 5, 1)); +const DEFAULT_GUEST_DNS0: IpAddr = IpAddr::V4(Ipv4Addr::new(9, 9, 9, 9)); +const DEFAULT_GUEST_DNS1: IpAddr = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)); /// Run image on QEMU. #[derive(Args)] @@ -576,10 +579,9 @@ impl Qemu { args.extend(["-freq".to_owned(), frequency.to_string()]); } if self.tap { - args.extend([ - "-ip".to_owned(), - format!("{DEFAULT_GUEST_IP}/{DEFAULT_GUEST_PREFIX_LEN}"), - ]); + args.push(format!( + "ip={DEFAULT_GUEST_IP}/{DEFAULT_GUEST_PREFIX_LEN}:{DEFAULT_GUEST_GATEWAY}::::{DEFAULT_GUEST_DNS0}:{DEFAULT_GUEST_DNS1}" + )); } args } @@ -605,11 +607,7 @@ impl Qemu { fn guest_ip(&self) -> IpAddr { if self.tap { - if let Ok(ip) = env::var("HERMIT_IP") { - ip.parse().unwrap() - } else { - DEFAULT_GUEST_IP - } + DEFAULT_GUEST_IP } else { Ipv4Addr::LOCALHOST.into() } From 0c5af000586a1cad95d50fd8f493ff81582c60b9 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Thu, 17 Sep 2026 14:39:04 +0200 Subject: [PATCH 4/4] docs: Drop removed env vars and refer to ip= parameter --- src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e732f814e1..244e48d460 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,13 +51,14 @@ //! //! - **`HERMIT_MTU`** — Sets the *maximum transmission unit* (MTU). Defaults to `1500`. //! - **`HERMIT_MRG_RXBUF_SIZE`** — Sets the receive buffer size. Useful for testing receive buffer merging of virtio-net devices when the feature `VIRTIO_NET_F_MRG_RXBUF` is negotiated. Defaults to unset. -//! - **`HERMIT_IP`** — Sets the IPv4 address. Defaults to `10.0.5.3`. -//! - **`HERMIT_GATEWAY`** — Sets the gateway IPv4 address. Defaults to `10.0.5.1`. Is only used when DHCP is not successful. -//! - **`HERMIT_MASK`** — Sets the network mask. Defaults to `255.255.255.0`. Is only used when DHCP is not successful. -//! - **`HERMIT_DNS1`** — Sets the first DNS server. Defaults to `9.9.9.9`. Is only used when DHCP is not successful. -//! - **`HERMIT_DNS2`** — Sets the second DNS server. Defaults to `1.1.1.1`. Is only used when DHCP is not successful. //! - **`HERMIT_PCAP_PATH`** — Sets the packet capture file path. Defaults to `/root/`. See the `write-pcap-file` feature for details. //! +//! IP address, gateway and DNS are configured via the `ip=` command-line parameter for the kernel and do not have a default value: +//! +//! - `ip=none` or `ip=off` do not configure a network interface. +//! - `ip=dhcp` uses DHCPv4 for configuring the network interface. +//! - `ip=10.0.5.3/24:10.0.5.1::::1.1.1.1:1.0.0.1` would configure the static IP address `10.0.5.3`, set the gateway as `10.0.5.1` and configure two DNS servers. +//! //! ## Output environment variables //! //! - **`NO_COLOR`** — Prevents the addition of ANSI colors to the kernel output. Defaults to unset. For details, see [`NO_COLOR`].