From e7987c95208fef03481f8ebe23782d4c6b9c920f Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Tue, 4 Oct 2022 23:08:43 +0100 Subject: [PATCH] add build-host tftp server --- README.md | 16 +++++++++++++ default.nix | 1 + overlay.nix | 2 ++ pkgs/tufted/default.nix | 44 +++++++++++++++++++++++++++++++++++ pkgs/tufted/tufted.fnl | 51 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 pkgs/tufted/default.nix create mode 100644 pkgs/tufted/tufted.fnl diff --git a/README.md b/README.md index c20e240..895e717 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,22 @@ Assuming you have nixpkgs checked out in a peer directory of this one, Some of the tests require the emulated upstream connection to be running. +## Hardware + +How you get the thing onto hardware will vary according to the device, +but is likely to involve U-Boot and TFTP. + +There is a rudimentary TFTP server bundled with the system which runs +from the command line, has an allowlist for client connections, and +follows symlinks, so you can have your device download images direct +from the `./result` directory without exposing `/nix/store/` to the +internet or mucking about copying files to `/tftproot`. If the +permitted device is to be given the IP address 192.168.8.251 you might +do something like this: + + $ NIX_PATH=nixpkgs=../nixpkgs:$NIX_PATH NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix-build -I liminix-config=./tests/smoke/configuration.nix --arg device "import ./devices/gl-ar750.nix" -A outputs.tftpd -o tftpd + $ ./tftpd/bin/tufted -a 192.168.8.251 result + ## Troubleshooting diff --git a/default.nix b/default.nix index e40d39f..0c852e4 100644 --- a/default.nix +++ b/default.nix @@ -39,6 +39,7 @@ let # this exists so that you can run "nix-store -q --tree" on it and find # out what's in the image, which is nice if it's unexpectedly huge manifest = writeText "manifest.json" (builtins.toJSON config.filesystem.contents); + tftpd = nixpkgs.pkgs.buildPackages.tufted; }; in { outputs = outputs // { default = outputs.${device.outputs.default}; }; diff --git a/overlay.nix b/overlay.nix index 693816e..65bc581 100644 --- a/overlay.nix +++ b/overlay.nix @@ -16,6 +16,8 @@ final: prev: { dbusSupport = false; }; + tufted = final.callPackage ./pkgs/tufted {}; + pppoe = final.callPackage ./pkgs/pppoe {}; ppp = (prev.ppp.override { diff --git a/pkgs/tufted/default.nix b/pkgs/tufted/default.nix new file mode 100644 index 0000000..07d1c98 --- /dev/null +++ b/pkgs/tufted/default.nix @@ -0,0 +1,44 @@ +{ + lua5_3 +, stdenv +, fennel +, fetchFromGitHub +, makeWrapper +} : +let + tufty-lua = lua.pkgs.buildLuaPackage { + pname = "tufty"; + version = "1"; + src = fetchFromGitHub { + owner = "telent"; + repo = "tufty"; + sha256 = "sha256-m5UEfcCNdG0Ku380cPhu1inNQmSfQJ5NcRIxLohUOh8="; + rev = "75c6d38713a82f4197f91dcb182a2e34f255bf7c"; + }; + buildPhase = ":"; + installPhase = '' + mkdir -p "$out/share/lua/${lua5_3.luaversion}" + cp src/*.lua "$out/share/lua/${lua5_3.luaversion}/" + ''; + }; + lua = lua5_3.withPackages (ps: with ps; [ + tufty-lua luasocket luaposix + ]); + fennel_ = (fennel.override { inherit lua; }); +in stdenv.mkDerivation { + pname = "tufted"; + version = "1"; + phases = [ "unpackPhase" "installPhase" ]; + buildInputs = [ lua fennel_ ]; + nativeBuildInputs = [ makeWrapper ]; + src = ./.; + installPhase = '' + mkdir -p $out/lib + cp tufted.fnl $out/lib + makeWrapper ${fennel_}/bin/fennel \ + $out/bin/tufted \ + --add-flags "--add-fennel-path $out/lib/?.fnl" \ + --add-flags "--add-package-path $out/lib/?.lua" \ + --add-flags "$out/lib/tufted.fnl" + ''; +} diff --git a/pkgs/tufted/tufted.fnl b/pkgs/tufted/tufted.fnl new file mode 100644 index 0000000..417794c --- /dev/null +++ b/pkgs/tufted/tufted.fnl @@ -0,0 +1,51 @@ +(local tftp (require :tftp)) +(local { : realpath} (require :posix.stdlib)) +(local { : view } (require :fennel)) + +(local options + (match arg + ["-a" ip-address dir] + { :allow ip-address :base-directory (realpath dir)} + + [dir] + { :allow nil :base-directory (realpath dir)} + + [] + (error "missing command line parameters") + )) + +(print (.. "TFTP serving from " options.base-directory)) + +(fn merge-pathname [directory filename] + (if (directory:match "/$") + (.. directory filename) + (.. directory "/" filename))) + +(-> + (tftp:listen + (fn [file host port] + (if (or (not options.allow) (= host options.allow)) + (let [pathname (merge-pathname options.base-directory file) + f (io.open pathname "rb") + size (f:seek "end")] + (f:seek "set" 0) + (var eof? false) + (values + (fn handler [reqlen] + (let [bytes (f:read reqlen)] + (if eof? + false + bytes + (values true bytes) + (do + ;; if the file length is divisible by the block + ;; length, need to send an empty block at eof + (set eof? true) + (values true ""))))) + size)) + (error "host not allowed"))) + nil + ["*"] + 69) + +(os.exit))