From 5b5b527f92e4fc904730068b02326ea5b2e50937 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 17:58:40 +0200 Subject: [PATCH 01/17] tftp: introduce the FIT enclosing boot.scr This simplify TFTP. Signed-off-by: Raito Bezarius --- modules/outputs/tftpboot-fit.its | 14 +++++++++++++ modules/outputs/tftpboot.nix | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 modules/outputs/tftpboot-fit.its diff --git a/modules/outputs/tftpboot-fit.its b/modules/outputs/tftpboot-fit.its new file mode 100644 index 0000000..fcb58ab --- /dev/null +++ b/modules/outputs/tftpboot-fit.its @@ -0,0 +1,14 @@ +/dts-v1/; + +/ { + description = "Liminix TFTP bootscript"; + #address-cells = <1>; + + images { + bootscript { + description = "Bootscript"; + data = /incbin/("boot.scr"); + type = "script"; + compression = "none"; + }; +}; diff --git a/modules/outputs/tftpboot.nix b/modules/outputs/tftpboot.nix index f8f0572..94d5ef4 100644 --- a/modules/outputs/tftpboot.nix +++ b/modules/outputs/tftpboot.nix @@ -51,11 +51,47 @@ in { It uses the Linux `phram `_ driver to emulate a flash device using a segment of physical RAM. ''; }; + + tftpboot-fit = mkOption { + type = types.package; + description = '' + tftpboot-fit + ************ + + This output is a variant that encloses the `boot.scr` in a FIT + if that's simpler to transfer for you. + ''; + }; }; config = { boot.ramdisk.enable = true; system.outputs = rec { + tftpboot-fit = + let + tftpboot-fit = pkgs.writeText "tftpboot.its" '' + /dts-v1/; + + / { + description = "Liminix TFTP bootscript"; + #address-cells = <1>; + + images { + bootscript { + description = "Bootscript"; + data = /incbin/("${tftpboot}/boot.scr"); + type = "script"; + compression = "none"; + }; + }; + }; + ''; + in + pkgs.runCommand "tftpboot-fit" { nativeBuildInputs = with pkgs.pkgsBuildBuild; [ ubootTools ]; } '' + mkdir -p $out/ + cp -rf ${tftpboot}/* $out/ + mkimage -f ${tftpboot-fit} $out/script.ub + ''; tftpboot = let inherit (pkgs.lib.trivial) toHexString; -- 2.47.1 From 4689bb9bb1b608adbb42f1a8dca38115226d584e Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 17:53:08 +0200 Subject: [PATCH 02/17] tftp: introduce an alternative command line for TFTP Normal command line and TFTP command line can be sometimes very different. e.g. We don't want to load UBI filesystems for a TFTP boot as it may interfere with our root device loading. Signed-off-by: Raito Bezarius --- modules/arch/mips.nix | 3 +++ modules/base.nix | 6 ++++++ modules/outputs/tftpboot.nix | 10 +++++++++- modules/outputs/ubivolume.nix | 7 +++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/arch/mips.nix b/modules/arch/mips.nix index 155c147..359bb71 100644 --- a/modules/arch/mips.nix +++ b/modules/arch/mips.nix @@ -14,5 +14,8 @@ boot.commandLine = [ "console=ttyS0,115200" # true of all mips we've yet encountered ]; + boot.tftp.commandLine = [ + "console=ttyS0,115200" # true of all mips we've yet encountered + ]; }; } diff --git a/modules/base.nix b/modules/base.nix index 1c8b67c..4c179ec 100644 --- a/modules/base.nix +++ b/modules/base.nix @@ -111,6 +111,12 @@ in { ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}" ++ lib.optional (config.hardware.alternativeRootDevice != null) "altroot=${config.hardware.alternativeRootDevice}"; + boot.tftp.commandLine = [ + "panic=10 oops=panic init=/bin/init loglevel=8" + "fw_devlink=off" + "rootfstype=${config.rootfsType}" + ]; + system.callService = path : parameters : let typeChecked = caller: type: value: diff --git a/modules/outputs/tftpboot.nix b/modules/outputs/tftpboot.nix index 94d5ef4..6d5bad9 100644 --- a/modules/outputs/tftpboot.nix +++ b/modules/outputs/tftpboot.nix @@ -28,6 +28,14 @@ in { type = types.bool; default = false; }; + commandLine = mkOption { + type = types.listOf types.str; + default = config.boot.commandLine; + description = '' + TFTP-specific command line. + Defaults to the classical one if unset. + ''; + }; }; options.system.outputs = { tftpboot = mkOption { @@ -105,7 +113,7 @@ in { zimage = "bootz"; }; in choices.${cfg.kernelFormat}; - cmdline = concatStringsSep " " config.boot.commandLine; + cmdline = concatStringsSep " " config.boot.tftp.commandLine; objcopy = "${pkgs.stdenv.cc.bintools.targetPrefix}objcopy"; stripAndZip = '' ${objcopy} -O binary -R .reginfo -R .notes -R .note -R .comment -R .mdebug -R .note.gnu.build-id -S vmlinux.elf vmlinux.bin diff --git a/modules/outputs/ubivolume.nix b/modules/outputs/ubivolume.nix index 45aa889..1b9decc 100644 --- a/modules/outputs/ubivolume.nix +++ b/modules/outputs/ubivolume.nix @@ -29,6 +29,13 @@ in }; boot.initramfs.enable = true; + # In TFTP, the device named "rootfs" is the UBI device. + # We tell the kernel to load it. + # This avoids interference from the other UBI volumes. + boot.tftp.commandLine = [ + "ubi.mtd=rootfs" + ]; + system.outputs.rootfs = let inherit (pkgs.pkgsBuildBuild) runCommand; -- 2.47.1 From 7fb23079c298c711d9ec5bc3ffd9399c83285f0d Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 18:24:44 +0200 Subject: [PATCH 03/17] chore(zyxel/nwa50ax): write flash erase block size as kb size Signed-off-by: Raito Bezarius --- devices/zyxel-nwa50ax/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices/zyxel-nwa50ax/default.nix b/devices/zyxel-nwa50ax/default.nix index e58430f..73f9214 100644 --- a/devices/zyxel-nwa50ax/default.nix +++ b/devices/zyxel-nwa50ax/default.nix @@ -170,7 +170,7 @@ maxLEBcount = "256"; }; - flash.eraseBlockSize = 65536; + flash.eraseBlockSize = 64 * 1024; # This is a FIT containing a kernel padded and # a UBI volume rootfs. -- 2.47.1 From 0550e0a5b1e4a236521d748f8f866d1be89d164f Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 18:32:46 +0200 Subject: [PATCH 04/17] fix(zyxel/nwa50ax): make `altroot` useful Let's use `ubi1` if it exist, as it should be the second device containing a rootfs. Signed-off-by: Raito Bezarius --- devices/zyxel-nwa50ax/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devices/zyxel-nwa50ax/default.nix b/devices/zyxel-nwa50ax/default.nix index 73f9214..e53e28f 100644 --- a/devices/zyxel-nwa50ax/default.nix +++ b/devices/zyxel-nwa50ax/default.nix @@ -181,8 +181,8 @@ # Aligned on 2kb. alignment = 2048; - rootDevice = "ubi:rootfs"; - alternativeRootDevice = "ubi:rootfs"; + rootDevice = "ubi0:rootfs"; + alternativeRootDevice = "ubi1:rootfs"; # Auto-attach MTD devices: ubi_a then ubi_b. ubi.mtds = [ "ubi_a" "ubi_b" ]; -- 2.47.1 From 4be1d9c95cfef44f16384bdc981d823a2c49f0ab Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 18:38:01 +0200 Subject: [PATCH 05/17] fix(zyxel/nwa50ax): ensure the DTB is in the FIT Signed-off-by: Raito Bezarius --- devices/zyxel-nwa50ax/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/devices/zyxel-nwa50ax/default.nix b/devices/zyxel-nwa50ax/default.nix index e53e28f..393dcac 100644 --- a/devices/zyxel-nwa50ax/default.nix +++ b/devices/zyxel-nwa50ax/default.nix @@ -224,6 +224,7 @@ tftp = { # 5MB is nice. freeSpaceBytes = 5 * 1024 * 1024; + appendDTB = true; loadAddress = lim.parseInt "0x2000000"; }; }; -- 2.47.1 From e25df0b6e28b4623b625c1bebd70b01044ebdf53 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 18:55:58 +0200 Subject: [PATCH 06/17] fix(zyxel/nwa50ax): ubi cannot run on phram Discovered the hard way. Signed-off-by: Raito Bezarius --- modules/outputs/jffs2.nix | 18 ++++++------------ modules/outputs/tftpboot.nix | 23 ++++++++++++++++++----- modules/outputs/ubivolume.nix | 7 ------- pkgs/default.nix | 1 + pkgs/liminix-tools/builders/jffs2.nix | 17 +++++++++++++++++ 5 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 pkgs/liminix-tools/builders/jffs2.nix diff --git a/modules/outputs/jffs2.nix b/modules/outputs/jffs2.nix index 02c7c10..0a69a1f 100644 --- a/modules/outputs/jffs2.nix +++ b/modules/outputs/jffs2.nix @@ -5,7 +5,8 @@ , ... }: let - inherit (lib) mkIf mkOption types; + inherit (pkgs) liminix; + inherit (lib) mkIf; o = config.system.outputs; in { @@ -24,17 +25,10 @@ in }; boot.initramfs.enable = true; system.outputs = { - rootfs = - let - inherit (pkgs.pkgsBuildBuild) runCommand mtdutils; - endian = if pkgs.stdenv.isBigEndian - then "--big-endian" else "--little-endian"; - in runCommand "make-jffs2" { - depsBuildBuild = [ mtdutils ]; - } '' - tree=${o.bootablerootdir} - (cd $tree && mkfs.jffs2 --compression-mode=size ${endian} -e ${toString config.hardware.flash.eraseBlockSize} --enable-compressor=lzo --pad --root . --output $out --squash --faketime ) - ''; + rootfs = liminix.builders.jffs2 { + bootableRootDirectory = o.bootablerootdir; + inherit (config.hardware.flash) eraseBlockSize; + }; }; }; } diff --git a/modules/outputs/tftpboot.nix b/modules/outputs/tftpboot.nix index 6d5bad9..ca51d10 100644 --- a/modules/outputs/tftpboot.nix +++ b/modules/outputs/tftpboot.nix @@ -5,10 +5,18 @@ , ... }: let - inherit (lib) mkOption types concatStringsSep; + inherit (lib) mkOption mkIf types concatStringsSep; + inherit (pkgs) liminix; cfg = config.boot.tftp; hw = config.hardware; arch = pkgs.stdenv.hostPlatform.linuxArch; + + # UBI cannot run on the top of phram. + needsSquashfs = config.rootfsType == "ubifs"; + rootfstype = if needsSquashfs then "squashfs" else config.rootfsType; + rootfs = if needsSquashfs then + liminix.builders.squashfs config.filesystem.contents + else config.system.outputs.rootfs; in { imports = [ ../ramdisk.nix ]; options.boot.tftp = { @@ -74,6 +82,11 @@ in { config = { boot.ramdisk.enable = true; + kernel.config = mkIf needsSquashfs { + SQUASHFS = "y"; + SQUASHFS_XZ = "y"; + }; + system.outputs = rec { tftpboot-fit = let @@ -128,7 +141,7 @@ in { hex() { printf "0x%x" $1; } rootfsStart=${toString cfg.loadAddress} - rootfsSize=$(binsize64k ${o.rootfs} ) + rootfsSize=$(binsize64k ${rootfs} ) rootfsSize=$(($rootfsSize + ${toString cfg.freeSpaceBytes} )) ln -s ${o.manifest} manifest @@ -142,13 +155,13 @@ in { dtbStart=$(($rootfsStart + $rootfsSize)) ${if cfg.compressRoot then '' - lzma -z9cv ${o.rootfs} > rootfs.lz + lzma -z9cv ${rootfs} > rootfs.lz rootfsLzStart=$dtbStart rootfsLzSize=$(binsize rootfs.lz) dtbStart=$(($dtbStart + $rootfsLzSize)) '' else '' - ln -s ${o.rootfs} rootfs + ln -s ${rootfs} rootfs '' } @@ -165,7 +178,7 @@ in { fdtput -p -t s dtb /reserved-memory/$node compatible phram fdtput -p -t lx dtb /reserved-memory/$node reg $ac_prefix $(hex $rootfsStart) $sz_prefix $(hex $rootfsSize) - cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} root=/dev/mtdblock0"; + cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} rootfstype=${rootfstype} root=/dev/mtdblock0"; fdtput -t s dtb /chosen ${config.boot.commandLineDtbNode} "$cmd" dtbSize=$(binsize ./dtb ) diff --git a/modules/outputs/ubivolume.nix b/modules/outputs/ubivolume.nix index 1b9decc..45aa889 100644 --- a/modules/outputs/ubivolume.nix +++ b/modules/outputs/ubivolume.nix @@ -29,13 +29,6 @@ in }; boot.initramfs.enable = true; - # In TFTP, the device named "rootfs" is the UBI device. - # We tell the kernel to load it. - # This avoids interference from the other UBI volumes. - boot.tftp.commandLine = [ - "ubi.mtd=rootfs" - ]; - system.outputs.rootfs = let inherit (pkgs.pkgsBuildBuild) runCommand; diff --git a/pkgs/default.nix b/pkgs/default.nix index b63ab3a..7bb2470 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -13,6 +13,7 @@ in { liminix = { builders = { squashfs = callPackage ./liminix-tools/builders/squashfs.nix {}; + jffs2 = callPackage ./liminix-tools/builders/jffs2.nix {}; dtb = callPackage ./kernel/dtb.nix {}; uimage = callPackage ./kernel/uimage.nix {}; kernel = callPackage ./kernel {}; diff --git a/pkgs/liminix-tools/builders/jffs2.nix b/pkgs/liminix-tools/builders/jffs2.nix new file mode 100644 index 0000000..62378b4 --- /dev/null +++ b/pkgs/liminix-tools/builders/jffs2.nix @@ -0,0 +1,17 @@ +{ + stdenv +, busybox +, buildPackages +, callPackage +, pseudofile +, runCommand +, writeText +} : { eraseBlockSize, bootableRootDirectory }: +let + endian = if stdenv.isBigEndian then "--big-endian" else "--little-endian"; +in runCommand "frob-jffs2" { + depsBuildBuild = [ buildPackages.mtdutils ]; + } '' + tree=${bootableRootDirectory} + (cd $tree && mkfs.jffs2 --compression-mode=size ${endian} -e ${toString eraseBlockSize} --enable-compressor=lzo --pad --root . --output $out --squash --faketime) + '' -- 2.47.1 From 3c33c0eaf4b1553806f2a9f806dbfd16dd9e9113 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 20:01:34 +0200 Subject: [PATCH 07/17] fix(preinit): scan multiple times the cmdline and rename altroot in rootalt The way the parsing works is examining one character at a time. First, if we had `rootfstype=... root=...`, the parsing would jump and ignore `root=...`, which sucks. To fix this, we scan multiple times a copy of the cmdline. Now, we have a new problem: `root=... altroot=...` lead to opts.device being equal to the altroot as we are looking one char at a time, so we will arrive at a moment looking at `root=...` for `altroot=...`. To avoid this, we rename `altroot` in `rootalt`, cheap, I know. Signed-off-by: Raito Bezarius --- modules/base.nix | 2 +- pkgs/preinit/parseopts.c | 36 ++++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/modules/base.nix b/modules/base.nix index 4c179ec..d2182c6 100644 --- a/modules/base.nix +++ b/modules/base.nix @@ -109,7 +109,7 @@ in { ] ++ (map (mtd: "ubi.mtd=${mtd}") config.hardware.ubi.mtds) ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}" - ++ lib.optional (config.hardware.alternativeRootDevice != null) "altroot=${config.hardware.alternativeRootDevice}"; + ++ lib.optional (config.hardware.alternativeRootDevice != null) "rootalt=${config.hardware.alternativeRootDevice}"; boot.tftp.commandLine = [ "panic=10 oops=panic init=/bin/init loglevel=8" diff --git a/pkgs/preinit/parseopts.c b/pkgs/preinit/parseopts.c index 1803341..243f3b0 100644 --- a/pkgs/preinit/parseopts.c +++ b/pkgs/preinit/parseopts.c @@ -65,13 +65,18 @@ static char * eat_param(char *p, char *param_name, char **out) return p; } +#define SCAN_CMDLINE(cmdline, identifier, field) do { \ + for (char* p = strdup(cmdline); *p; p++) { \ + p = eat_param(p, identifier, &(opts->field)); \ + } \ +} while(0) + + void parseopts(char * cmdline, struct root_opts *opts) { - for(char *p = cmdline; *p; p++) { - p = eat_param(p, "root=", &(opts->device)); - p = eat_param(p, "rootfstype=", &(opts->fstype)); - p = eat_param(p, "rootflags=", &(opts->mount_opts)); - p = eat_param(p, "altroot=", &(opts->altdevice)); - }; + SCAN_CMDLINE(cmdline, "root=", device); + SCAN_CMDLINE(cmdline, "rootfstype=", fstype); + SCAN_CMDLINE(cmdline, "rootflags=", mount_opts); + SCAN_CMDLINE(cmdline, "rootalt=", altdevice); } #ifdef TESTS @@ -85,6 +90,8 @@ void parseopts(char * cmdline, struct root_opts *opts) { #define S(x) #x #define expect_equal(actual, expected) \ if(!actual || strcmp(actual, expected)) die("%d: expected \"%s\", got \"%s\"", __LINE__, expected, actual); +#define expect_null(actual) \ + if (actual) die("%d: expected null, got \"%s\"", __LINE__, actual); int main() @@ -92,6 +99,7 @@ int main() struct root_opts opts = { .device = "/dev/hda1", .fstype = "xiafs", + .altdevice = NULL, .mount_opts = NULL }; char *buf; @@ -103,14 +111,22 @@ int main() expect_equal(opts.fstype, "ubifs"); expect_equal(opts.mount_opts, "subvol=1"); - // finds altroot= options - buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 altroot=/dev/mtdblock6 foo"); + // finds rootalt= options + buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 rootalt=/dev/mtdblock6 foo"); memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); expect_equal(opts.device, "/dev/mtdblock0"); expect_equal(opts.altdevice, "/dev/mtdblock6"); expect_equal(opts.fstype, "ubifs"); expect_equal(opts.mount_opts, "subvol=1"); + // Ensure that `altdevice` is NULL. + buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 fw_devlink=off rootfstype=ubifs mtdparts=phram0:19791872(rootfs) phram.phram=phram0,33554432,19791872,65536 rootfstype=jffs2 root=/dev/mtdblock0"); + memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); + expect_equal(opts.device, "/dev/mtdblock0"); + expect_null(opts.altdevice); + expect_equal(opts.fstype, "jffs2"); + expect_null(opts.mount_opts); + // in case of duplicates, chooses the latter // also: works if the option is end of string buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0"); @@ -146,12 +162,12 @@ int main() if(opts.altdevice) die("expected null altdevice, got \"%s\"", opts.altdevice); // provides empty strings for empty options - buf = strdup("liminix rootfstype= fw_devlink=off root= altroot= /dev/hda1"); + buf = strdup("liminix rootfstype= fw_devlink=off root= rootalt= /dev/hda1"); memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); if(strlen(opts.fstype)) die("expected empty rootfstype, got \"%s\"", opts.fstype); if(strlen(opts.device)) die("expected empty root, got \"%s\"", opts.device); - if(strlen(opts.altdevice)) die("expected empty altroot, got \"%s\"", opts.altdevice); + if(strlen(opts.altdevice)) die("expected empty rootalt, got \"%s\"", opts.altdevice); expect_equal("01", pr_u32(1)); expect_equal("ab", pr_u32(0xab)); -- 2.47.1 From 499e30cdd78c366a000a3b598c203c7ce9270237 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Aug 2024 21:40:09 +0200 Subject: [PATCH 08/17] feat(hostapd): make the package configurable to enable RADIUS The default hostapd disable too many things, we need a bit more for RADIUS. Signed-off-by: Raito Bezarius --- modules/hostapd/default.nix | 4 ++++ modules/hostapd/service.nix | 5 ++--- overlay.nix | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/modules/hostapd/default.nix b/modules/hostapd/default.nix index 2bcf4d6..4338e74 100644 --- a/modules/hostapd/default.nix +++ b/modules/hostapd/default.nix @@ -26,6 +26,10 @@ in { interface = mkOption { type = liminix.lib.types.service; }; + package = mkOption { + type = types.package; + default = pkgs.hostapd; + }; params = mkOption { type = types.attrs; }; diff --git a/modules/hostapd/service.nix b/modules/hostapd/service.nix index 2bd5453..68e9754 100644 --- a/modules/hostapd/service.nix +++ b/modules/hostapd/service.nix @@ -1,10 +1,9 @@ { liminix -, hostapd , writeText , lib }: -{ interface, params} : +{ package, interface, params } : let inherit (liminix.services) longrun; inherit (lib) concatStringsSep mapAttrsToList; @@ -35,5 +34,5 @@ let in longrun { inherit name; dependencies = [ interface ]; - run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; + run = "${package}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; } diff --git a/overlay.nix b/overlay.nix index 09acaad..374c79a 100644 --- a/overlay.nix +++ b/overlay.nix @@ -143,6 +143,42 @@ extraPkgs // { }); in h.override { openssl = null; sqlite = null; }; + hostapd-radius = + let + config = [ + "CONFIG_DRIVER_NL80211=y" + "CONFIG_DRIVER_WIRED=y" + "CONFIG_EAP=y" + "CONFIG_EAP_PEAP=y" + "CONFIG_RADIUS_SERVER=y" + "CONFIG_FULL_DYNAMIC_VLAN=y" + "CONFIG_IAPP=y" + "CONFIG_IEEE80211AC=y" + "CONFIG_IEEE80211AX=y" + "CONFIG_IEEE80211N=y" + "CONFIG_IEEE80211W=y" + "CONFIG_INTERNAL_LIBTOMMATH=y" + "CONFIG_INTERNAL_LIBTOMMATH_FAST=y" + "CONFIG_IPV6=y" + "CONFIG_LIBNL32=y" + "CONFIG_PKCS12=y" + "CONFIG_RSN_PREAUTH=y" + # Required to read the key material for RADIUS. + "CONFIG_TLS=openssl" + ]; + h = prev.hostapd.overrideAttrs(o: { + extraConfig = ""; + configurePhase = '' + cat > hostapd/defconfig < Date: Sat, 31 Aug 2024 20:28:23 +0200 Subject: [PATCH 09/17] feat(mtd-utils): save more space Signed-off-by: Raito Bezarius --- overlay.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/overlay.nix b/overlay.nix index 374c79a..2b46089 100644 --- a/overlay.nix +++ b/overlay.nix @@ -204,6 +204,11 @@ extraPkgs // { patches = (if o ? patches then o.patches else []) ++ [ ./pkgs/mtdutils/0001-mkfs.jffs2-add-graft-option.patch ]; + + postInstall = '' + # Testing programs which we don't need. We save a lot of space! + rm -rf $out/libexec + ''; }); nftables = prev.nftables.overrideAttrs(o: { -- 2.47.1 From 0af8c7659ebe4af1a15a2b8cc61875f68434454e Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 31 Aug 2024 21:23:17 +0200 Subject: [PATCH 10/17] feat(pki): init TLS PKI module Signed-off-by: Raito Bezarius --- modules/pki/default.nix | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 modules/pki/default.nix diff --git a/modules/pki/default.nix b/modules/pki/default.nix new file mode 100644 index 0000000..b328f4d --- /dev/null +++ b/modules/pki/default.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, ... }: +# Inspired from nixpkgs/NixOS. + +with lib; + +let + inherit (pkgs.pseudofile) dir symlink; + cfg = config.security.pki; + + cacertPackage = pkgs.cacert.override { + blacklist = [ ]; + extraCertificateFiles = cfg.certificateFiles; + extraCertificateStrings = cfg.certificates; + }; + caBundleName = "ca-bundle.crt"; + caBundle = "${cacertPackage}/etc/ssl/certs/${caBundleName}"; + +in + +{ + + options = { + security.pki.installCACerts = mkEnableOption "installing CA certificates to the system" // { + default = false; + }; + + security.pki.certificateFiles = mkOption { + type = types.listOf types.path; + default = []; + example = literalExpression ''[ "''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]''; + description = '' + A list of files containing trusted root certificates in PEM + format. These are concatenated to form + {file}`/etc/ssl/certs/ca-certificates.crt`, which is + used by many programs that use OpenSSL, such as + {command}`curl` and {command}`git`. + ''; + }; + + security.pki.certificates = mkOption { + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ ''' + NixOS.org + ========= + -----BEGIN CERTIFICATE----- + MIIGUDCCBTigAwIBAgIDD8KWMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ + TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 + ... + -----END CERTIFICATE----- + ''' + ] + ''; + description = '' + A list of trusted root certificates in PEM format. + ''; + }; + }; + + config = mkIf cfg.installCACerts { + # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility. + filesystem = dir { + etc = dir { + ssl = dir { + certs = dir { + "ca-certificates.crt" = symlink caBundle; + "ca-bundle.crt" = symlink caBundle; + }; + }; + + # CentOS/Fedora compatibility. + pki = dir { + certs = dir { + "ca-bundle.crt" = symlink caBundle; + }; + }; + }; + }; + }; + +} + -- 2.47.1 From 048d16644f9bad4ff8d34f78907882647b82d632 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 31 Aug 2024 20:44:22 +0200 Subject: [PATCH 11/17] feat(jitterentropy): introduce a jitterentropy module Signed-off-by: Raito Bezarius --- modules/all-modules.nix | 1 + modules/jitter-rng/default.nix | 21 +++++++++++++++++++++ modules/jitter-rng/jitter-rng.nix | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 modules/jitter-rng/default.nix create mode 100644 modules/jitter-rng/jitter-rng.nix diff --git a/modules/all-modules.nix b/modules/all-modules.nix index abce5db..98352c1 100644 --- a/modules/all-modules.nix +++ b/modules/all-modules.nix @@ -8,6 +8,7 @@ ./bridge ./busybox.nix ./dhcp6c + ./jitter-rng ./dnsmasq ./firewall ./hardware.nix diff --git a/modules/jitter-rng/default.nix b/modules/jitter-rng/default.nix new file mode 100644 index 0000000..aa6f9e5 --- /dev/null +++ b/modules/jitter-rng/default.nix @@ -0,0 +1,21 @@ +## CPU Jitter RNG +## ============== +## +## CPU Jitter RNG is a random number generator # providing non-physical true +## random generation # that works equally for kernel and user-land. It relies +## on the availability of a high-resolution timer. +{ lib, pkgs, ... }: +let + inherit (lib) mkOption types; + inherit (pkgs) liminix; +in { + options.system.service.jitter-rng = mkOption { + type = liminix.lib.types.serviceDefn; + }; + + config = { + system.service.jitter-rng = pkgs.liminix.callService ./jitter-rng.nix { + }; + }; +} + diff --git a/modules/jitter-rng/jitter-rng.nix b/modules/jitter-rng/jitter-rng.nix new file mode 100644 index 0000000..1072be1 --- /dev/null +++ b/modules/jitter-rng/jitter-rng.nix @@ -0,0 +1,18 @@ +{ + liminix +, lib +, jitterentropy-rngd +}: +{ }: +let + inherit (liminix.services) longrun; + name = "jitterentropy-rngd"; +in +longrun { + # Does it need to be unique? + inherit name; + run = '' + mkdir -p /run/jitterentropy-rngd + ${jitterentropy-rngd}/bin/jitterentropy-rngd -v -p /run/jitterentropy-rngd/${name}.pid + ''; +} -- 2.47.1 From 3983b010354e175fbcf3601063f3090c19dd3779 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Sat, 15 Jun 2024 15:34:49 +0100 Subject: [PATCH 12/17] dhcpc handle case when env vars are missing the notify-script should continue and signal readiness even if one or more of the outputs it writes are mssing in the environment --- modules/network/dhcpc.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/network/dhcpc.nix b/modules/network/dhcpc.nix index ed665b3..cd652b2 100644 --- a/modules/network/dhcpc.nix +++ b/modules/network/dhcpc.nix @@ -17,7 +17,7 @@ let ip address replace $ip/$mask dev $interface (in_outputs ${name} for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do - printenv $i > $i + (printenv $i || true) > $i done) } case $action in @@ -40,7 +40,7 @@ let ''; in longrun { inherit name; - run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; + run = "exec /bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; notification-fd = 10; dependencies = [ interface ]; } -- 2.47.1 From 50d1bd1223e252a9e1a0e6c844cec00fceda5c27 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sun, 1 Sep 2024 01:32:45 +0200 Subject: [PATCH 13/17] fix(bridge/members): log attach/detach Signed-off-by: Raito Bezarius --- modules/bridge/members.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/bridge/members.nix b/modules/bridge/members.nix index a278730..114bc2e 100644 --- a/modules/bridge/members.nix +++ b/modules/bridge/members.nix @@ -21,9 +21,13 @@ let service = oneshot { name = "${primary.name}.member.${member.name}"; up = '' + echo "attaching $(output ${member} ifname) to $(output ${primary} ifname) bridge" ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) ''; - down = "ip link set dev $(output ${member} ifname) nomaster"; + down = '' + echo "detaching $(output ${member} ifname) from any bridge" + ip link set dev $(output ${member} ifname) nomaster + ''; }; }; in bundle { -- 2.47.1 From 6f04012235f2188602876a4d1b4e1fa757e0f0c6 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sun, 1 Sep 2024 02:05:33 +0200 Subject: [PATCH 14/17] fix(ifwait): return :present if newlink is up/yes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Urgh… Signed-off-by: Raito Bezarius --- pkgs/ifwait/ifwait.fnl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/ifwait/ifwait.fnl b/pkgs/ifwait/ifwait.fnl index cc5e76c..52122bd 100644 --- a/pkgs/ifwait/ifwait.fnl +++ b/pkgs/ifwait/ifwait.fnl @@ -23,7 +23,7 @@ {:present true :up true :running true} {:event "newlink" :name params.link :up :yes} - {:present :true :up true} + {:present true :up true} {:event "newlink" :name params.link} {:present true } -- 2.47.1 From dec3c904f6e5b72ff7ef693023adae9b0846c3d7 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sun, 1 Sep 2024 02:13:36 +0200 Subject: [PATCH 15/17] fix(ifwait): match over strings and not symbols Are they the same in Fennel? Signed-off-by: Raito Bezarius --- pkgs/ifwait/ifwait.fnl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/ifwait/ifwait.fnl b/pkgs/ifwait/ifwait.fnl index 52122bd..f835ebb 100644 --- a/pkgs/ifwait/ifwait.fnl +++ b/pkgs/ifwait/ifwait.fnl @@ -19,10 +19,10 @@ (match v ;; - up: Reflects the administrative state of the interface (IFF_UP) ;; - running: Reflects the operational state (IFF_RUNNING). - {:event "newlink" :name params.link :up :yes :running :yes} + {:event "newlink" :name params.link :up "yes" :running "yes"} {:present true :up true :running true} - {:event "newlink" :name params.link :up :yes} + {:event "newlink" :name params.link :up "yes"} {:present true :up true} {:event "newlink" :name params.link} -- 2.47.1 From 60726e3cd65f415555d11b8fe6c76226dc53b227 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sun, 1 Sep 2024 14:56:08 +0200 Subject: [PATCH 16/17] fix(bridge): pick up MAC from another interface This avoids the OPERSTATE unknown when the bridge is brought up but the members are not ready yet. This will make OPERSTATE to down, enabling us to wait until we have brought up completely all the members. Signed-off-by: Raito Bezarius --- modules/bridge/default.nix | 6 ++++++ modules/bridge/primary.nix | 11 ++++++++--- pkgs/liminix-tools/networking/default.nix | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/bridge/default.nix b/modules/bridge/default.nix index 32df4a5..fd779f8 100644 --- a/modules/bridge/default.nix +++ b/modules/bridge/default.nix @@ -28,6 +28,12 @@ in type = types.str; description = "bridge interface name to create"; }; + + macAddressFromInterface = mkOption { + type = types.nullOr liminix.lib.types.service; + default = null; + description = "reuse mac address from an existing interface service"; + }; }; members = config.system.callService ./members.nix { primary = mkOption { diff --git a/modules/bridge/primary.nix b/modules/bridge/primary.nix index c25e5fe..aa7822c 100644 --- a/modules/bridge/primary.nix +++ b/modules/bridge/primary.nix @@ -3,15 +3,20 @@ , ifwait , lib }: -{ ifname } : +{ ifname, macAddressFromInterface ? null } : let inherit (liminix.services) bundle oneshot; - inherit (lib) mkOption types; + inherit (lib) mkOption types optional; in oneshot rec { name = "${ifname}.link"; up = '' - ip link add name ${ifname} type bridge + ${if macAddressFromInterface == null then + "ip link add name ${ifname} type bridge" + else + "ip link add name ${ifname} address $(output ${macAddressFromInterface} ether) type bridge"} ${liminix.networking.ifup name ifname} ''; down = "ip link set down dev ${ifname}"; + + dependencies = optional (macAddressFromInterface != null) macAddressFromInterface; } diff --git a/pkgs/liminix-tools/networking/default.nix b/pkgs/liminix-tools/networking/default.nix index aed7c4e..b51b367 100644 --- a/pkgs/liminix-tools/networking/default.nix +++ b/pkgs/liminix-tools/networking/default.nix @@ -9,6 +9,7 @@ ip link set up dev ${ifname} (in_outputs ${name} echo ${ifname} > ifname + cat /sys/class/net/${ifname}/address > ether ) ''; } -- 2.47.1 From 998422e8284d755781c7429d681e8e71319bdcf8 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sun, 1 Sep 2024 15:42:26 +0200 Subject: [PATCH 17/17] fix(bridge): reorder initialization for bridge dependents Consider the scenario where you run DHCPv4 on the primary bridge interface. You have no real interface to "wait upon", so it's OK. Nonetheless, anything depending on successful completion of DHCPv4, e.g. adding a default route, will block `s6-rc -v2 up change default`. The way new interfaces are attached to the bridge is via `s6-rc -b -u change $attach-oneshot-service`, this introduce in turn a deadlock. At some point, DHCPv4 will timeout, unblocking the deadlock and attaching the members to the primary bridge interface, making it ready to send L2 broadcast packets for DHCP, unblocking DHCP in turn again. This is not satisfying because we really want to have a no-hiccups bring-up. To fix this, we proceed to multiple changes: - we remove `svc.ifwait.build` out of band `s6-rc -b -u $oneshot-attach` call, which is, by design, wrong here. - users can now depend on the members service to know when a bridge is fully operational (we could make it more granular and let them depend on the LAN member joining rather than WLAN, etc.) - users can also depend on the primary service being brought up rather than just being present, this is useful if you need to bring it up when it has AT LEAST one member to get link local address or MAC addresses (fixing DHCPv6 bring up as well because `ff02::1` is used there). One thing is not addressed yet, if you are running a WLAN service using RADIUS attached to the bridge, at bring up time, it will try to reach out the external RADIUS server and *fail*. To solve this, granular dependency on the DHCPv4 once LAN is joined. Then the hostapd can wait on defaultroute4 completion so that connectivity is available to reach RADIUS server. It can join the bridge later on without any hiccup as well. This is left as a TODO as hostapd can survive RADIUS authentication failure and retry later. Signed-off-by: Raito Bezarius --- modules/bridge/default.nix | 14 ++++++++++++++ modules/bridge/members.nix | 35 +++++++++++++++-------------------- modules/bridge/primary.nix | 8 ++++++-- modules/bridge/ready.nix | 18 ++++++++++++++++++ 4 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 modules/bridge/ready.nix diff --git a/modules/bridge/default.nix b/modules/bridge/default.nix index fd779f8..4c2911a 100644 --- a/modules/bridge/default.nix +++ b/modules/bridge/default.nix @@ -20,6 +20,7 @@ in system.service.bridge = { primary = mkOption { type = liminix.lib.types.serviceDefn; }; members = mkOption { type = liminix.lib.types.serviceDefn; }; + ready = mkOption { type = liminix.lib.types.serviceDefn; }; }; }; config.system.service.bridge = { @@ -46,6 +47,19 @@ in description = "interfaces to add to the bridge"; }; }; + + # TODO: generalize it outside + ready = config.system.callService ./ready.nix { + primary = mkOption { + type = liminix.lib.types.service; + description = "primary bridge interface"; + }; + + members = mkOption { + type = liminix.lib.types.service; + description = "members service"; + }; + }; }; config.kernel.config = { BRIDGE = "y"; diff --git a/modules/bridge/members.nix b/modules/bridge/members.nix index 114bc2e..b4f00be 100644 --- a/modules/bridge/members.nix +++ b/modules/bridge/members.nix @@ -10,26 +10,21 @@ let inherit (liminix.networking) interface; inherit (liminix.services) bundle oneshot; inherit (lib) mkOption types; - addif = member : - # how do we get sight of services from here? maybe we need to - # implement ifwait as a regualr derivation instead of a - # servicedefinition - svc.ifwait.build { - state = "running"; - interface = member; - dependencies = [ primary member ]; - service = oneshot { - name = "${primary.name}.member.${member.name}"; - up = '' - echo "attaching $(output ${member} ifname) to $(output ${primary} ifname) bridge" - ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) - ''; - down = '' - echo "detaching $(output ${member} ifname) from any bridge" - ip link set dev $(output ${member} ifname) nomaster - ''; - }; - }; + addif = member : oneshot { + name = "${primary.name}.member.${member.name}"; + up = '' + echo "waiting for bridge $(output ${primary} ifname) to be ready" + ${ifwait}/bin/ifwait -v $(output ${primary} ifname) running + echo "attaching $(output ${member} ifname) to $(output ${primary} ifname) bridge" + ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) + ''; + down = '' + echo "detaching $(output ${member} ifname) from any bridge" + ip link set dev $(output ${member} ifname) nomaster + ''; + + dependencies = [ primary member ]; + }; in bundle { name = "${primary.name}.members"; contents = map addif members; diff --git a/modules/bridge/primary.nix b/modules/bridge/primary.nix index aa7822c..f5e1219 100644 --- a/modules/bridge/primary.nix +++ b/modules/bridge/primary.nix @@ -14,9 +14,13 @@ in oneshot rec { "ip link add name ${ifname} type bridge" else "ip link add name ${ifname} address $(output ${macAddressFromInterface} ether) type bridge"} - ${liminix.networking.ifup name ifname} + + (in_outputs ${name} + echo ${ifname} > ifname + cat /sys/class/net/${ifname}/address > ether + ) ''; - down = "ip link set down dev ${ifname}"; + down = "ip link delete ${ifname}"; dependencies = optional (macAddressFromInterface != null) macAddressFromInterface; } diff --git a/modules/bridge/ready.nix b/modules/bridge/ready.nix new file mode 100644 index 0000000..f8c40b3 --- /dev/null +++ b/modules/bridge/ready.nix @@ -0,0 +1,18 @@ +{ + liminix +, ifwait +, lib +}: +{ primary, members } : +let + inherit (liminix.services) oneshot; +in oneshot { + name = "${primary.name}.oper"; + up = '' + ip link set up dev $(output ${primary} ifname) + ${ifwait}/bin/ifwait -v $(output ${primary} ifname) running + ''; + down = "ip link set down dev $(output ${primary} ifname)"; + + dependencies = [ members ]; +} -- 2.47.1