commit 6649ebeccd95848062ca114ec0aee7714eb62c9f
parent 929226ed9eaf18fa87d6e3f9cd283b2680d59d58
Author: Daniel Barlow <dan@telent.net>
Date: Fri, 28 Feb 2025 00:43:20 +0000
firewall: use watch-outputs to track changes in zone->interface map
includes a horrible hack to work around (claimed (by me)) deficiencies
in the nftables parser
Diffstat:
7 files changed, 108 insertions(+), 48 deletions(-)
diff --git a/modules/firewall/service.nix b/modules/firewall/service.nix
@@ -5,6 +5,9 @@
nftables,
writeFennel,
anoia,
+ svc,
+ lua,
+ output-template,
lualinux,
linotify,
}:
@@ -18,14 +21,18 @@ let
inherit (lib.attrsets) mapAttrs' nameValuePair mapAttrsToList;
inherit (lib.strings) concatStringsSep;
inherit (lib.lists) flatten;
+ inherit (builtins) concatLists attrValues;
+ inherit (liminix) outputRef;
mkSet =
family: name:
nameValuePair "${name}-set-${family}" {
kind = "set";
inherit name family;
type = "ifname";
+ elements = map (s: "{{ output(${builtins.toJSON s}, \"ifname\", \"\") }}") zones.${name};
};
- sets = (mapAttrs' (n: _: mkSet "ip" n) zones) // (mapAttrs' (n: _: mkSet "ip6" n) zones);
+ sets = (mapAttrs' (n: _: mkSet "ip" n) zones) //
+ (mapAttrs' (n: _: mkSet "ip6" n) zones);
allRules = lib.recursiveUpdate extraRules (lib.recursiveUpdate (builtins.trace sets sets) rules);
script = firewallgen "firewall1.nft" allRules;
ifwatch = writeFennel "ifwatch" {
@@ -37,13 +44,26 @@ let
mainFunction = "run";
} ./ifwatch.fnl;
watchArg = z: intfs: map (i: "${z}:${i}/.outputs") intfs;
-in
-longrun {
name = "firewall";
- run = ''
- ${script}
- PATH=${nftables}/bin:$PATH
- ${ifwatch} ${concatStringsSep " " (flatten (mapAttrsToList watchArg zones))}
- '';
- finish = "${nftables}/bin/nft flush ruleset";
+ service = longrun {
+ inherit name;
+ run = ''
+ mkdir -p /run/${name}; in_outputs ${name}
+ # exec > /dev/console 2>&1
+ echo RESTARTING FIREWALL >/dev/console
+ PATH=${nftables}/bin:${lua}/bin:$PATH
+ ${output-template}/bin/output-template '{{' '}}' < ${script} | lua -e 'for x in io.lines() do if not string.match(x, "elements = {%s+}") then print(x) end; end' > /run/${name}/fw.nft
+ # cat /run/${name}/fw.nft > /dev/console
+ nft -f /run/${name}/fw.nft
+ while sleep 86400 ; do : ; done
+ '';
+ finish = "${nftables}/bin/nft flush ruleset";
+ };
+in
+svc.secrets.subscriber.build {
+ watch =
+ concatLists
+ (mapAttrsToList (_zone : services : map (s: outputRef s "ifname") services) zones);
+
+ inherit service;
}
diff --git a/modules/secrets/subscriber.nix b/modules/secrets/subscriber.nix
@@ -13,12 +13,11 @@
}:
let
inherit (liminix.services) oneshot longrun;
- inherit (builtins) length head toString;
- inherit (lib) unique optional optionals;
+ inherit (builtins) map length head toString;
+ inherit (lib) unique optional optionals concatStringsSep;
inherit (service) name;
watched-services = unique (map (f: f "service") watch);
- paths = unique (map (f: f "path") watch);
restart-flag =
{
@@ -35,17 +34,11 @@ let
}
.${action};
- watched-service =
- if length watched-services == 0 then
- null
- else if length watched-services == 1 then
- head watched-services
- else
- throw "cannot subscribe to more than one source service for secrets";
-
watcher =
let
name' = "restart-${name}";
+ refs = concatStringsSep " "
+ (map (s: "${s "service"}:${s "path"}") watch);
in
longrun {
name = name';
@@ -55,16 +48,16 @@ let
if test -e $dir/notification-fd; then flag="-U"; else flag="-u"; fi
${s6}/bin/s6-svwait $flag /run/service/${name} || exit
PATH=${s6-rc}/bin:${s6}/bin:$PATH
- ${watch-outputs}/bin/watch-outputs ${restart-flag} ${name} ${watched-service.name} ${lib.concatStringsSep " " paths}
+ ${watch-outputs}/bin/watch-outputs ${restart-flag} ${name} ${refs}
'';
};
in
service.overrideAttrs (o: {
- buildInputs = (lim.orEmpty o.buildInputs) ++ optional (watched-service != null) watcher;
+ buildInputs = (lim.orEmpty o.buildInputs) ++ optional (watch != []) watcher;
dependencies =
(lim.orEmpty o.dependencies)
- ++ optionals (watched-service != null) [
- watcher
- watched-service
- ];
+ # ++ optionals
+ # (watch != [])
+ # ([ watcher ] ++ watched-services);
+ ;
})
diff --git a/pkgs/firewallgen/default.nix b/pkgs/firewallgen/default.nix
@@ -61,7 +61,7 @@ let
''
set ${name} {
type ${type}
- ${if elements != [ ] then "elements = { ${concatStringsSep ", " elements} }" else ""}
+ ${if elements != [ ] then "elements = { ${concatStringsSep ", " (builtins.trace elements elements)} }" else ""}
}
'';
diff --git a/pkgs/output-template/output-template.fnl b/pkgs/output-template/output-template.fnl
@@ -25,9 +25,9 @@
myenv {
: string
:output
- (fn [service-path path]
+ (fn [service-path path default]
(let [s (assert (svc.open (.. service-path "/.outputs")))]
- (s:output path)))
+ (or (s:output path) default)))
:lua_quote #(string.format "%q" %1)
:json_quote (fn [x] (.. "\"" (json-escape x) "\""))
}]
diff --git a/pkgs/watch-outputs/default.nix b/pkgs/watch-outputs/default.nix
@@ -21,6 +21,7 @@ stdenv.mkDerivation {
nativeBuildInputs = [ fennelrepl ] ;
buildPhase = ''
+ fennelrepl --test ./watch-outputs.fnl
cp -p ${
writeFennel name {
packages = [
@@ -29,6 +30,7 @@ stdenv.mkDerivation {
linotify
fennel
];
+ macros = [ anoia.dev ];
mainFunction = "run";
} ./watch-outputs.fnl
} ${name}
diff --git a/pkgs/watch-outputs/watch-outputs.fnl b/pkgs/watch-outputs/watch-outputs.fnl
@@ -1,10 +1,18 @@
(local { : %% : system : assoc : split : table= : dig } (require :anoia))
(local svc (require :anoia.svc))
-(local { : kill } (require :lualinux))
+(local { : kill &as ll} (require :lualinux))
+(import-macros { : define-tests : expect : expect= } :anoia.assert)
-(fn split-paths [paths]
- (icollect [_ path (ipairs paths)]
- (split "/" path)))
+(local { : view } (require :fennel))
+
+(fn output-refs [outputs]
+ (let [result {}]
+ (each [_ v (ipairs outputs)]
+ (let [[service path] (split ":" v)
+ paths (or (. result service) [])]
+ (table.insert paths (split "/" path ))
+ (tset result service paths)))
+ result))
(fn parse-args [args]
(match args
@@ -17,37 +25,73 @@
["-s" signal service & rest] (assoc (parse-args rest)
:controlled-service service
:action [:signal signal])
- [watched-service & paths] { : watched-service
- :paths (split-paths paths)
- }))
+ outputs { :output-references (output-refs outputs) } ))
+
+(define-tests
+ (expect= (parse-args ["-r" "daemon"
+ "/nix/store/s1:out1"
+ "/nix/store/s2:out1" "/nix/store/s2:out2/ifname"])
+ {:action "restart"
+ :controlled-service "daemon"
+ :output-references
+ {"/nix/store/s1" [["out1"]]
+ "/nix/store/s2" [["out1"] ["out2" "ifname"]]}}
+ ))
+
(fn changed? [paths old-tree new-tree]
(accumulate [changed? false
_ path (ipairs paths)]
(or changed? (not (table= (dig old-tree path) (dig new-tree path))))))
+(define-tests
+ (expect (changed? [["ifname"]] {:ifname "true"} {:ifindex 2}))
+ (expect (changed? [["ifname"]] {:ifname "true"} {:ifname "false"}))
+ (expect (not (changed? [["ifname"]] {:ifname "true"} {:ifname "true"})))
+ (expect (not (changed? [["mtu"]] {:ifname "true"} {:ifname "false"})))
+ (expect (not (changed? [["mtu"]] {:ifname "true"} {:ifname "false"})))
+ )
+
+
+
(fn do-action [action service]
(case action
:restart (system (%% "s6-svc -r /run/service/%s" service))
:restart-all (system (%% "s6-rc -b -d %q; s6-rc-up-tree %q" service service))
[:signal n] (system (%% "s6-svc -s %d /run/service/%s" n service))))
+(local POLLIN 0x0001)
+(local POLLHUP 0x0010)
+
+(fn wait-for-change [services]
+ (let [pollfds (collect [s _p (ipairs services)]
+ (bor (lshift (s:fileno) 32)
+ (lshift (bor POLLIN POLLHUP) 16)))]
+ (ll.poll pollfds)))
+
+(fn open-services [output-references]
+ (collect [s p (pairs output-references)]
+ (values (assert (svc.open (.. s "/.outputs"))) p)))
+
(fn run []
- (let [{
+ (let [trees {}
+ {
+ : output-references
: controlled-service
: action
: watched-service
- : paths } (parse-args arg)
- dir (.. watched-service "/.outputs")
- service (assert (svc.open dir))]
- (print (%% "watching %q" watched-service))
- (accumulate [tree (service:output ".")
- v (service:events)]
- (let [new-tree (service:output ".")]
- (when (changed? paths tree new-tree)
- (print "watched path event:" action controlled-service)
- (do-action action controlled-service))
- new-tree))))
+ : paths } (parse-args arg)]
+ (while true
+ (let [services (open-services output-references)
+ trees (collect [s _ (pairs services)]
+ (values s (s:output ".")))]
+ (wait-for-change services)
+ (each [service paths (pairs services)]
+ (let [new-tree (service:output ".")]
+ (when (changed? paths (. trees service) new-tree)
+ (print "watched path event:" action controlled-service)
+ (do-action action controlled-service))))))))
+
{ : run }
diff --git a/tests/pppoe/getaddress.expect b/tests/pppoe/getaddress.expect
@@ -17,6 +17,7 @@ while { $FINISHED < 10 } {
}
set FINISHED [ expr $FINISHED + 1 ]
}
+expect "#READY#"
send "nft list set ip table-ip wan || touch /non/existent\n"
expect {
"ppp0" { puts "ppp0 found " }