commit e7987c95208fef03481f8ebe23782d4c6b9c920f
parent f9626d00f457db6bff1db33606a97bd08e04f2f0
Author: Daniel Barlow <dan@telent.net>
Date: Tue, 4 Oct 2022 23:08:43 +0100
add build-host tftp server
Diffstat:
5 files changed, 114 insertions(+), 0 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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))