Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,20 @@
/.idea
/.vscode
/.zed
/result
/result-*

# Added by Spec Kitty CLI (auto-managed)
.claude/
.codex/
.opencode/
.windsurf/
.gemini/
.cursor/
.qwen/
.kilocode/
.augment/
.roo/
.amazonq/
.github/copilot/
.kittify/.dashboard
33 changes: 33 additions & 0 deletions feos/utils/src/network/dhcpv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,37 @@ fn send_router_solicitation(interface: &NetworkInterface, tx: &mut dyn datalink:
}
}

/// Default timeout for waiting for a Router Advertisement (in seconds).
/// This covers the 5s pre-RS sleep + time waiting for the RA response.
const RA_WAIT_TIMEOUT_SECS: u64 = 20;

pub fn is_dhcpv6_needed(interface_name: String, ignore_ra_flag: bool) -> Option<Ipv6Addr> {
// Run the blocking RA listener in a separate thread with a channel-based
// timeout. The rx.next() call on pnet's AF_PACKET socket blocks indefinitely
// even with read_timeout set, so we use a bounded channel recv_timeout to
// enforce the deadline regardless of the socket behavior.
let (tx, rx) = std::sync::mpsc::channel();
let iface_name = interface_name.clone();

std::thread::spawn(move || {
let result = is_dhcpv6_needed_inner(iface_name, ignore_ra_flag);
let _ = tx.send(result);
});

let timeout = Duration::from_secs(RA_WAIT_TIMEOUT_SECS);
match rx.recv_timeout(timeout) {
Ok(result) => result,
Err(_) => {
warn!(
"No Router Advertisement received within {RA_WAIT_TIMEOUT_SECS}s. \
Skipping DHCPv6 network configuration."
);
None
}
}
}

fn is_dhcpv6_needed_inner(interface_name: String, ignore_ra_flag: bool) -> Option<Ipv6Addr> {
let mut sender_ipv6_address: Option<Ipv6Addr> = None;
let interfaces = datalink::interfaces();
let interface = interfaces
Expand All @@ -187,6 +217,9 @@ pub fn is_dhcpv6_needed(interface_name: String, ignore_ra_flag: bool) -> Option<
if let Some(ra_packet) = RouterAdvertPacket::new(ipv6_packet.payload())
{
if (ra_packet.get_flags() & 0xC0) == 0xC0 || ignore_ra_flag {
info!(
"Received Router Advertisement from {sender_ipv6_address:?}"
);
break;
}
}
Expand Down
15 changes: 12 additions & 3 deletions feos/utils/src/network/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,18 @@ pub async fn configure_network_devices() -> Result<Option<(Ipv6Addr, u8, Vec<Ipv

if let Some(ipv6_gateway) = is_dhcpv6_needed(interface_name.clone(), ignore_ra_flag) {
sleep(Duration::from_secs(4)).await;
match run_dhcpv6_client(interface_name.clone()).await {
Ok(result) => {
let dhcpv6_timeout = Duration::from_secs(30);
let dhcpv6_result =
tokio::time::timeout(dhcpv6_timeout, run_dhcpv6_client(interface_name.clone())).await;
match dhcpv6_result {
Err(_elapsed) => {
warn!(
"DHCPv6 client timed out after {}s. Continuing without IPv6 address.",
dhcpv6_timeout.as_secs()
);
}
Ok(Err(e)) => warn!("Error running DHCPv6 client: {e}"),
Ok(Ok(result)) => {
send_neigh_solicitation(interface_name.clone(), &ipv6_gateway, &result.address);
if let Some(prefix_info) = result.prefix {
let delegated_prefix = prefix_info.prefix;
Expand Down Expand Up @@ -224,7 +234,6 @@ pub async fn configure_network_devices() -> Result<Option<(Ipv6Addr, u8, Vec<Ipv
warn!("Failed to set IPv6 gateway: {e}");
}
}
Err(e) => warn!("Error running DHCPv6 client: {e}"),
}
}

Expand Down
226 changes: 226 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
{
description = "FeOS - A minimal Linux init system for hypervisors and container hosts";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

crane.url = "github:ipetkov/crane";

rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};

flake-utils.url = "github:numtide/flake-utils";
};

outputs =
{
self,
nixpkgs,
crane,
rust-overlay,
flake-utils,
...
}:
let
# FeOS only targets x86_64-linux (musl static binary)
supportedSystems = [ "x86_64-linux" ];

# Version metadata
version = "0.5.0";
kernelVersion = "6.12.63";
in
flake-utils.lib.eachSystem supportedSystems (
system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};

# Rust toolchain with musl target for static linking
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
targets = [ "x86_64-unknown-linux-musl" ];
extensions = [
"rust-src"
"clippy"
"rustfmt"
];
};

# Crane lib configured with our custom toolchain
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;

# --- Package derivations ---

feosPackage = pkgs.callPackage ./nix/feos.nix {
inherit craneLib version;
inherit (pkgs) pkgsCross;
};

feosCliPackage = pkgs.callPackage ./nix/feos.nix {
inherit craneLib version;
inherit (pkgs) pkgsCross;
buildCli = true;
};

feosKernel = pkgs.callPackage ./nix/kernel.nix {
inherit kernelVersion;
kernelConfig = ./hack/kernel/config/feos-linux-${kernelVersion}.config;
};

# Pre-built hypervisor firmware (cross-compiled to x86_64-none,
# not directly buildable as a regular x86_64-linux package)
hypervisorFirmware = pkgs.fetchurl {
url = "https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.4.2/hypervisor-fw";
hash = "sha256-WMFGE7xmBnI/GBJNAPujRk+vMx1ssGp//lbeYtgHEkA=";
};

feosInitramfs = pkgs.callPackage ./nix/initramfs.nix {
feos = feosPackage;
kernel = feosKernel;
cloud-hypervisor = pkgs.cloud-hypervisor;
youki = pkgs.youki;
hypervisor-firmware = hypervisorFirmware;
};

feosUki = pkgs.callPackage ./nix/uki.nix {
kernel = feosKernel;
initramfs = feosInitramfs;
osRelease = ./hack/uki/os-release.txt;
cmdline = ./hack/kernel/cmdline.txt;
};

feosVm = pkgs.callPackage ./nix/vm.nix {
kernel = feosKernel;
initramfs = feosInitramfs;
};

in
{
packages = {
default = feosPackage;
feos = feosPackage;
feos-cli = feosCliPackage;
feos-kernel = feosKernel;
feos-initramfs = feosInitramfs;
feos-uki = feosUki;

# Convenience: build everything
all = pkgs.symlinkJoin {
name = "feos-all";
paths = [
feosPackage
feosCliPackage
feosKernel
feosInitramfs
feosUki
];
};
};

apps = {
default = flake-utils.lib.mkApp {
drv = feosVm;
name = "feos-vm";
};
vm = flake-utils.lib.mkApp {
drv = feosVm;
name = "feos-vm";
};
feos-cli = flake-utils.lib.mkApp {
drv = feosCliPackage;
name = "feos-cli";
};
};

# --- Checks (run via `nix flake check`) ---
checks = {
# Verify the main packages build
feos = feosPackage;
feos-cli = feosCliPackage;
feos-kernel = feosKernel;
feos-initramfs = feosInitramfs;
feos-uki = feosUki;

# Cargo fmt check
feos-fmt = craneLib.cargoFmt {
src = craneLib.path ./.;
};

# Cargo clippy
feos-clippy = craneLib.cargoClippy (
feosPackage.passthru.commonArgs
// {
inherit (feosPackage.passthru) cargoArtifacts src;
pname = "feos-clippy";
cargoClippyExtraArgs = "--all-targets -- -D warnings";
doCheck = false;
}
);
};

# Formatter (run via `nix fmt`)
formatter = pkgs.nixfmt;

devShells.default = pkgs.mkShell {
inputsFrom = [ ];

nativeBuildInputs = [
rustToolchain
pkgs.protobuf
pkgs.pkg-config
pkgs.perl
pkgs.openssl
pkgs.sqlite

# Development tools
pkgs.cargo-watch
pkgs.cargo-edit

# VM / testing
pkgs.qemu
pkgs.passt

# gRPC testing
pkgs.grpcurl

# Nix tools
pkgs.nixpkgs-fmt
];

# For openssl-sys vendored build
OPENSSL_NO_VENDOR = "0";
PROTOC = "${pkgs.protobuf}/bin/protoc";

shellHook = ''
echo "FeOS development shell"
echo " cargo build --target=x86_64-unknown-linux-musl --all"
echo " nix build .#feos -- build FeOS binary"
echo " nix build .#feos-kernel -- build custom kernel"
echo " nix build .#feos-initramfs -- build initramfs"
echo " nix build .#feos-uki -- build Unified Kernel Image"
echo " nix run .#vm -- launch test VM"
'';
};
}
)
// {
# System-independent outputs

nixosModules = {
default = self.nixosModules.feos;
feos = import ./nix/module.nix self;
};

# Overlay for use in other flakes
overlays.default = final: prev: {
feos = self.packages.${final.system}.feos;
feos-cli = self.packages.${final.system}.feos-cli;
feos-kernel = self.packages.${final.system}.feos-kernel;
feos-initramfs = self.packages.${final.system}.feos-initramfs;
feos-uki = self.packages.${final.system}.feos-uki;
};
};
}
Loading