Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker

feat: replace NextDHCP with Kea DHCP server integration and update related configurations

+299 -177
+2 -1
.gitignore
··· 1 1 target/ 2 - fire.toml 2 + fire.toml 3 + .vscode/
+1 -1
README.md
··· 17 17 18 18 ## Prerequisites 19 19 - [CoreDNS](https://coredns.io/) (for DNS resolution) 20 - - [NextDHCP](https://github.com/nextdhcp/nextdhcp) (for DHCP services) 20 + - [Kea DHCP](https://kea.readthedocs.io/en/latest/) (for DHCP services) 21 21 - [Mosquitto](https://mosquitto.org/) (MQTT Server) 22 22 23 23 ## Installation
+1 -1
crates/firecracker-prepare/src/lib.rs
··· 79 79 arch.bright_green() 80 80 ); 81 81 let kernel_file = downloader::download_kernel(arch)?; 82 - let debootstrap_dir = format!("{}/debootstrap", app_dir); 82 + let debootstrap_dir = format!("{}/debian-rootfs", app_dir); 83 83 84 84 let arch = match arch { 85 85 "x86_64" => "amd64",
+51
crates/firecracker-vm/src/apparmor/usr.sbin.kea-dhcp4
··· 1 + abi <abi/3.0>, 2 + 3 + include <tunables/global> 4 + 5 + profile kea-dhcp4 /usr/sbin/kea-dhcp4 { 6 + include <abstractions/base> 7 + include <abstractions/nameservice> 8 + 9 + # for MySQL access, localhost 10 + include <abstractions/mysql> 11 + include <abstractions/openssl> 12 + 13 + capability net_bind_service, 14 + capability net_raw, 15 + 16 + network inet dgram, 17 + network inet stream, 18 + network netlink raw, 19 + network packet raw, 20 + 21 + /etc/gss/mech.d/ r, 22 + /etc/gss/mech.d/* r, 23 + 24 + /etc/kea/ r, 25 + /etc/kea/** r, 26 + /usr/sbin/kea-dhcp4 mr, 27 + /usr/sbin/kea-lfc Px, 28 + 29 + owner /run/kea/kea-dhcp4.kea-dhcp4.pid rw, 30 + owner /run/lock/kea/logger_lockfile rwk, 31 + 32 + # Control sockets 33 + # Before LP: #1863100, these were in /tmp. For compatibility, let's keep both 34 + # locations 35 + owner /{tmp,run/kea}/kea4-ctrl-socket w, 36 + owner /{tmp,run/kea}/kea4-ctrl-socket.lock rwk, 37 + 38 + # this includes .completed, .output, .pid, .[0-9] 39 + owner /var/lib/kea/kea-leases4.csv* rw, 40 + 41 + owner /var/log/kea/kea-dhcp4.log rw, 42 + owner /var/log/kea/kea-dhcp4.log.[0-9]* rw, 43 + owner /var/log/kea/kea-dhcp4.log.lock rwk, 44 + 45 + # Site-specific additions and overrides. See local/README for details. 46 + #include <local/usr.sbin.kea-dhcp4> 47 + 48 + /usr/local/bin/kea-mqtt-hook.sh ux, 49 + /usr/bin/mosquitto_pub ixr, 50 + /usr/lib/** rm, 51 + }
+52 -75
crates/firecracker-vm/src/coredns.rs
··· 1 1 use std::{process, thread}; 2 2 3 3 use anyhow::{Context, Error}; 4 - use firecracker_state::repo; 5 4 6 5 use crate::{command::run_command, mqttc, types::VmOptions}; 7 6 ··· 10 9 11 10 pub fn setup_coredns(config: &VmOptions) -> Result<(), Error> { 12 11 let api_socket = config.api_socket.clone(); 12 + if !coredns_is_installed()? { 13 + println!("[✗] CoreDNS is not installed. Please install it first to /usr/sbin."); 14 + process::exit(1); 15 + } 16 + 17 + if !etcd_is_installed()? { 18 + println!("[+] Installing etcd..."); 19 + run_command( 20 + "apt-get", 21 + &["install", "-y", "etcd-server", "etcd-client"], 22 + true, 23 + )?; 24 + } 25 + 26 + run_command( 27 + "sh", 28 + &[ 29 + "-c", 30 + &format!( 31 + "echo '{}' > {}", 32 + include_str!("./coredns/Corefile"), 33 + COREDNS_CONFIG_PATH 34 + ), 35 + ], 36 + true, 37 + )?; 38 + 39 + run_command( 40 + "sh", 41 + &[ 42 + "-c", 43 + &format!( 44 + "echo '{}' > /etc/systemd/system/coredns.service", 45 + COREDNS_SERVICE_TEMPLATE 46 + ), 47 + ], 48 + true, 49 + )?; 50 + restart_coredns()?; 51 + 13 52 thread::spawn(move || { 14 53 let runtime = tokio::runtime::Runtime::new().unwrap(); 15 54 match runtime.block_on(async { 16 - println!("[+] Checking if CoreDNS is installed..."); 17 - if !coredns_is_installed()? { 18 - // TODO: install it automatically 19 - println!("[✗] CoreDNS is not installed. Please install it first to /usr/sbin."); 20 - process::exit(1); 21 - } 22 - 23 55 let message = mqttc::wait_for_mqtt_message("REQUEST").await?; 24 56 let ip_addr = message 25 57 .split_whitespace() ··· 36 68 std::fs::write(format!("/tmp/firecracker-{}.ip", name), ip_addr) 37 69 .with_context(|| "Failed to write IP address to file")?; 38 70 39 - let pool = firecracker_state::create_connection_pool().await?; 40 - let vms = repo::virtual_machine::all(&pool).await?; 41 - let mut hosts = vms 42 - .into_iter() 43 - .filter(|vm| vm.ip_address.is_some() && vm.name != name) 44 - .map(|vm| format!("{} {}.firecracker", vm.ip_address.unwrap(), vm.name)) 45 - .collect::<Vec<String>>(); 46 - 47 - hosts.extend(vec![format!("{} {}.firecracker", ip_addr, name)]); 48 - 49 - let hosts = hosts.join("\n "); 50 - 51 - let coredns_config: &str = &format!( 52 - r#" 53 - firecracker:53 {{ 54 - hosts {{ 55 - 172.16.0.1 br.firecracker 56 - {} 57 - fallthrough 58 - }} 59 - 60 - loadbalance 61 - }} 62 - 63 - ts.net:53 {{ 64 - # Forward non-internal queries (e.g., to Tailscale DNS) 65 - forward . 100.100.100.100 66 - # Log and errors for debugging 67 - log 68 - errors 69 - health 70 - }} 71 - 72 - .:53 {{ 73 - # Forward non-internal queries (e.g., to Google DNS) 74 - forward . 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 {{ 75 - max_fails 3 76 - expire 10s 77 - health_check 5s 78 - policy round_robin 79 - except ts.net 80 - }} 81 - # Log and errors for debugging 82 - log 83 - errors 84 - health 85 - }} 86 - "#, 87 - hosts 71 + println!( 72 + "[+] Assigning DNS entry: {}.firecracker -> {}", 73 + name, ip_addr 88 74 ); 89 75 76 + let etcd_key = format!("/skydns/firecracker/{}", name); 77 + let etcd_value = format!("{{\"host\":\"{}\"}}", ip_addr); 90 78 run_command( 91 - "sh", 92 - &[ 93 - "-c", 94 - &format!("echo '{}' > {}", coredns_config, COREDNS_CONFIG_PATH), 95 - ], 79 + "etcdctl", 80 + &["put", &etcd_key, &etcd_value], 96 81 true, 97 82 )?; 98 83 99 - run_command( 100 - "sh", 101 - &[ 102 - "-c", 103 - &format!( 104 - "echo '{}' > /etc/systemd/system/coredns.service", 105 - COREDNS_SERVICE_TEMPLATE 106 - ), 107 - ], 108 - true, 109 - )?; 110 - restart_coredns()?; 111 - 112 84 Ok::<(), Error>(()) 113 85 }) { 114 86 Ok(_) => {} ··· 136 108 let output = run_command("which", &["coredns"], false)?; 137 109 Ok(output.status.success()) 138 110 } 111 + 112 + pub fn etcd_is_installed() -> Result<bool, Error> { 113 + let output = run_command("ls", &["/usr/bin/etcd"], false)?; 114 + Ok(output.status.success()) 115 + }
+35
crates/firecracker-vm/src/coredns/Corefile
··· 1 + firecracker:53 { 2 + etcd firecracker { 3 + path /skydns 4 + endpoint http://127.0.0.1:2379 5 + fallthrough 6 + } 7 + 8 + cache 30 9 + loadbalance 10 + log 11 + } 12 + 13 + ts.net:53 { 14 + # Forward non-internal queries (e.g., to Tailscale DNS) 15 + forward . 100.100.100.100 16 + # Log and errors for debugging 17 + log 18 + errors 19 + health 20 + } 21 + 22 + .:53 { 23 + # Forward non-internal queries (e.g., to Google DNS) 24 + forward . 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 { 25 + max_fails 3 26 + expire 10s 27 + health_check 5s 28 + policy round_robin 29 + except ts.net 30 + } 31 + # Log and errors for debugging 32 + log 33 + errors 34 + health 35 + }
+128
crates/firecracker-vm/src/dhcpd.rs
··· 1 + use anyhow::Error; 2 + 3 + use crate::{command::run_command, constants::BRIDGE_DEV, types::VmOptions}; 4 + 5 + pub const DHCPD_CONFIG_PATH: &str = "/etc/kea/kea-dhcp4.conf"; 6 + 7 + pub fn setup_kea_dhcp(_config: &VmOptions) -> Result<(), Error> { 8 + println!("[+] Checking if isc-kea-dhcp-server is installed..."); 9 + if is_kea_dhcp_installed().is_err() { 10 + run_command( 11 + "apt-get", 12 + &[ 13 + "install", 14 + "-y", 15 + "kea-dhcp4-server", 16 + "kea-admin", 17 + "kea-common", 18 + "etcd-client", 19 + "etcd-server", 20 + ], 21 + true, 22 + )?; 23 + } 24 + 25 + const KEA_MQTT_HOOK_SH: &str = include_str!("./scripts/kea-mqtt-hook.sh"); 26 + println!("[+] Installing kea-mqtt-hook.sh script..."); 27 + std::fs::write("/tmp/kea-mqtt-hook.sh", KEA_MQTT_HOOK_SH)?; 28 + run_command("cp", &["/tmp/kea-mqtt-hook.sh", "/usr/local/bin"], true)?; 29 + run_command("chmod", &["a+x", "/usr/local/bin/kea-mqtt-hook.sh"], true)?; 30 + run_command("rm", &["/tmp/kea-mqtt-hook.sh"], false)?; 31 + 32 + println!("[+] Setting up AppArmor for kea-dhcp4..."); 33 + std::fs::write( 34 + "/tmp/usr.sbin.kea-dhcp4", 35 + include_str!("./apparmor/usr.sbin.kea-dhcp4"), 36 + )?; 37 + run_command("cp", &["/tmp/usr.sbin.kea-dhcp4", "/etc/apparmor.d/"], true)?; 38 + run_command("rm", &["/tmp/usr.sbin.kea-dhcp4"], false)?; 39 + run_command( 40 + "apparmor_parser", 41 + &["-r", "/etc/apparmor.d/usr.sbin.kea-dhcp4"], 42 + true, 43 + )?; 44 + 45 + let kea_dhcp_config: &str = &format!( 46 + r#" 47 + {{ 48 + "Dhcp4": {{ 49 + "valid-lifetime": 4000, 50 + "renew-timer": 1000, 51 + "rebind-timer": 2000, 52 + "interfaces-config": {{ 53 + "interfaces": [ "{}" ] 54 + }}, 55 + "lease-database": {{ 56 + "type": "memfile", 57 + "lfc-interval": 3600 58 + }}, 59 + "subnet4": [ 60 + {{ 61 + "subnet": "172.16.0.0/24", 62 + "pools": [ {{ "pool": "172.16.0.2 - 172.16.0.150" }} ], 63 + "option-data": [ 64 + {{ "name": "routers", "data": "172.16.0.1" }}, 65 + {{ "name": "domain-name-servers", "data": "172.16.0.1" }} 66 + ] 67 + }} 68 + ], 69 + "hooks-libraries": [ 70 + {{ 71 + "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_run_script.so", 72 + "parameters": {{ 73 + "name": "/usr/local/bin/kea-mqtt-hook.sh" 74 + }} 75 + }} 76 + ], 77 + "loggers": [ 78 + {{ 79 + "name": "kea-dhcp4", 80 + "severity": "DEBUG", 81 + "debuglevel": 99 82 + }} 83 + ] 84 + }} 85 + }} 86 + "#, 87 + BRIDGE_DEV 88 + ); 89 + 90 + run_command( 91 + "sh", 92 + &[ 93 + "-c", 94 + &format!("echo '{}' > {}", kea_dhcp_config, DHCPD_CONFIG_PATH), 95 + ], 96 + true, 97 + )?; 98 + 99 + restart_kea_dhcp()?; 100 + 101 + Ok(()) 102 + } 103 + 104 + pub fn restart_kea_dhcp() -> Result<(), Error> { 105 + println!("[+] Starting kea-dhcp4-server..."); 106 + 107 + let dummy_is_up = run_command("ip", &["link", "show", "dummy0"], false) 108 + .map(|output| output.status.success()) 109 + .unwrap_or(false); 110 + if !dummy_is_up { 111 + println!("[+] Creating dummy0 interface..."); 112 + run_command("ip", &["link", "add", "dummy0", "type", "dummy"], true)?; 113 + run_command("ip", &["link", "set", "dummy0", "up"], true)?; 114 + run_command("ip", &["link", "set", "dummy0", "master", BRIDGE_DEV], true)?; 115 + } 116 + 117 + run_command("systemctl", &["enable", "kea-dhcp4-server"], true)?; 118 + run_command("systemctl", &["daemon-reload"], true)?; 119 + run_command("systemctl", &["stop", "kea-dhcp4-server"], true)?; 120 + run_command("systemctl", &["start", "kea-dhcp4-server"], true)?; 121 + println!("[✓] kea-dhcp4-server started successfully."); 122 + Ok(()) 123 + } 124 + 125 + pub fn is_kea_dhcp_installed() -> Result<bool, Error> { 126 + let output = run_command("which", &["kea-dhcp4"], false)?; 127 + Ok(output.status.success()) 128 + }
+2 -2
crates/firecracker-vm/src/lib.rs
··· 10 10 mod config; 11 11 pub mod constants; 12 12 mod coredns; 13 + mod dhcpd; 13 14 mod firecracker; 14 15 mod guest; 15 16 pub mod mac; 16 17 mod mosquitto; 17 18 mod mqttc; 18 19 mod network; 19 - mod nextdhcp; 20 20 pub mod types; 21 21 22 22 pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option<String>) -> Result<()> { ··· 98 98 network::setup_network(options)?; 99 99 mosquitto::setup_mosquitto(options)?; 100 100 coredns::setup_coredns(options)?; 101 - nextdhcp::setup_nextdhcp(options)?; 101 + dhcpd::setup_kea_dhcp(options)?; 102 102 103 103 firecracker::configure(&logfile, &kernel, &rootfs, &arch, &options)?; 104 104
-79
crates/firecracker-vm/src/nextdhcp.rs
··· 1 - use std::process; 2 - 3 - use anyhow::Error; 4 - 5 - use crate::{command::run_command, types::VmOptions}; 6 - 7 - pub const NEXTDHCP_CONFIG_PATH: &str = "/etc/nextdhcp/Dhcpfile"; 8 - pub const NEXTDHCP_SERVICE_TEMPLATE: &str = include_str!("./systemd/nextdhcp.service"); 9 - 10 - pub fn setup_nextdhcp(_config: &VmOptions) -> Result<(), Error> { 11 - println!("[+] Checking if NextDHCP is installed..."); 12 - if !nextdhcp_is_installed()? { 13 - // TODO: install it automatically 14 - println!("[✗] NextDHCP is not installed. Please install it first to /usr/sbin."); 15 - process::exit(1); 16 - } 17 - 18 - let nextdhcp_config: &str = r#" 19 - 172.16.0.1/24 { 20 - lease 30m 21 - 22 - range 172.16.0.2 172.16.0.150 23 - 24 - mqtt { 25 - name default 26 - broker tcp://localhost:1883 27 - 28 - topic /dhcp/hwaddr/{hwaddr} 29 - payload "{msgtype} {hwaddr} {requestedip} {state}" 30 - qos 1 31 - } 32 - 33 - option { 34 - router 172.16.0.1 35 - nameserver 172.16.0.1 36 - } 37 - } 38 - "#; 39 - 40 - run_command( 41 - "sh", 42 - &[ 43 - "-c", 44 - &format!("echo '{}' > {}", nextdhcp_config, NEXTDHCP_CONFIG_PATH), 45 - ], 46 - true, 47 - )?; 48 - 49 - run_command( 50 - "sh", 51 - &[ 52 - "-c", 53 - &format!( 54 - "echo '{}' > /etc/systemd/system/nextdhcp.service", 55 - NEXTDHCP_SERVICE_TEMPLATE 56 - ), 57 - ], 58 - true, 59 - )?; 60 - restart_nextdhcp()?; 61 - 62 - Ok(()) 63 - } 64 - 65 - pub fn restart_nextdhcp() -> Result<(), Error> { 66 - println!("[+] Starting nextdhcp..."); 67 - 68 - run_command("systemctl", &["enable", "nextdhcp"], true)?; 69 - run_command("systemctl", &["daemon-reload"], true)?; 70 - run_command("systemctl", &["stop", "nextdhcp"], true)?; 71 - run_command("systemctl", &["start", "nextdhcp"], true)?; 72 - println!("[✓] Nextdhcp started successfully."); 73 - Ok(()) 74 - } 75 - 76 - pub fn nextdhcp_is_installed() -> Result<bool, Error> { 77 - let output = run_command("which", &["nextdhcp"], false)?; 78 - Ok(output.status.success()) 79 - }
+27
crates/firecracker-vm/src/scripts/kea-mqtt-hook.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + export BROKER="localhost" 5 + export PORT="1883" 6 + export TOPIC="/dhcp/hwaddr" 7 + 8 + echo "Starting Kea DHCP hook script..." 9 + echo $1 $QUERY4_HWADDR 10 + 11 + if [ "$1" = "leases4_committed" ]; then 12 + if [ -z "${QUERY4_HWADDR:-}" ]; then 13 + echo "QUERY4_HWADDR is not set. Exiting." 14 + exit 0 15 + fi 16 + 17 + echo ">> Lease committed event detected." >> /tmp/kea-lease-hook.log 18 + env >> /tmp/kea-lease-hook.log 19 + # Log to a file 20 + echo "$(date): New lease assigned - IP: $LEASES4_AT0_ADDRESS, MAC: $QUERY4_HWADDR" >> /tmp/kea-lease-hook.log 21 + 22 + echo "New lease assigned - IP: $LEASES4_AT0_ADDRESS, MAC: $QUERY4_HWADDR" 23 + 24 + mosquitto_pub -h "$BROKER" -p "$PORT" -t "$TOPIC" -m "REQUEST $QUERY4_HWADDR $LEASES4_AT0_ADDRESS binding" 25 + fi 26 + 27 + exit 0
-18
crates/firecracker-vm/src/systemd/nextdhcp.service
··· 1 - [Unit] 2 - Description=NextDHCP Service 3 - After=network.target systemd-tmpfiles-setup.service 4 - 5 - [Service] 6 - ExecStart=/usr/sbin/nextdhcp -conf /etc/nextdhcp/Dhcpfile 7 - Restart=on-failure 8 - RestartSec=5 9 - LimitNOFILE=65535 10 - LimitNPROC=512 11 - ProtectSystem=strict 12 - ReadWritePaths=/etc/nextdhcp 13 - NoNewPrivileges=true 14 - AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE 15 - WorkingDirectory=/etc/nextdhcp 16 - 17 - [Install] 18 - WantedBy=multi-user.target