1
0

Compare commits

...

21 Commits

Author SHA1 Message Date
9ab77a7d7e remove unused function 2025-03-09 20:44:35 +00:00
c6918fec00 firewall: use extraText for zone set contents
* the lua necessary is quite wordy, but it's less of a hack than
post-processing the rules file with pseudo-sed to get rid of `elements
= { }` lines

* also switch from stop/starting the firewall service to using a
signal, so that we don't go briefly offline every time a new interface
appears
2025-03-09 20:42:02 +00:00
d4e46dbe28 secrets/subscriber don't depend on the services we're watching
this means a watched service can stop and start without killing
the subscriber, and that we can watch for services that don't
yet exist
2025-03-09 20:35:40 +00:00
d1f87a56e0 secrets/subscriber: use correct numbers for signals to s6-svc 2025-03-09 20:34:29 +00:00
8c39b47cae output-template: allow splicing statements instead of expression
if the text inside the delimiters begins with ; (a semicolon) then
the rest of it is expected to be one or more Lua statements. It needs
to say `return "foo"` to interpolate anything, as there is no
implicit return of the value of the last statement
2025-03-05 22:38:48 +00:00
2c7a16d792 firewallgen: add extraText param to set
anything in here is added verbatim to the set definition
2025-03-05 22:36:35 +00:00
d6b06abb63 delet second copy of output-template 2025-03-02 21:34:02 +00:00
6b32aa569e think 2025-03-02 21:21:45 +00:00
234d1bd87e basic unit tests for output-template 2025-03-02 21:14:46 +00:00
c38f180fb7 output-template expose table module 2025-03-02 21:14:16 +00:00
9a8b22997c output-template: pass the tests 2025-03-02 21:09:32 +00:00
c32d09bd83 output-template: run the tests 2025-03-02 21:09:11 +00:00
6649ebeccd 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
2025-02-28 00:43:20 +00:00
929226ed9e delete commented code 2025-02-27 20:55:30 +00:00
a98f026210 think 2025-02-27 20:54:44 +00:00
f4dc001b71 check firewall zones in pppoe test 2025-02-25 23:32:05 +00:00
024c018262 run the output-template test 2025-02-22 00:10:19 +00:00
e1293e3778 think 2025-02-21 23:22:39 +00:00
0c406058e9 remove acceotance of udp sport 5 on wan
this was added for replies to dns queries but isn't needed for
that purpose as connection tracking does that anyway
2025-02-12 21:54:01 +00:00
19d441333c remove duplicate rule 2025-02-10 23:50:07 +00:00
a726c09ae4 improve explanaton of reverse path filtering rule
thanks RoS for the references :-)
2025-02-10 23:48:29 +00:00
25 changed files with 393 additions and 150 deletions

View File

@ -7020,3 +7020,179 @@ Sun Feb 9 21:33:57 GMT 2025
nft update set @lan
echo 'flush set table-ip lan; add element table-ip lan { eth0,lo }' | nft -f -
Tue Feb 11 18:30:09 GMT 2025
outstanding for 1.0:
1) security audit fedback
a) ask ROS if I can ship their report, with a response doc
showing the commits that address each finding/non-finding
b) firewall rules: icmp rate limit, DNS, doc for icmpv6 packet dropping
c) look over env var inputs and parse them properly instead of
string glommeration
2) docs:
- for each device, add "finishedness" status and link to build status
- generally read them over and spruce up
- porting guide
3) some kconfig magic to generate minimal kconfig files so that
device modules don't end up as copy-pastes of the openwrt defconfig
---
apparently 5% of available bandwidth is a reasonable rate limit for
icmp
% nft add rule filter input limit rate over 10 mbytes/second drop
but nftables has no way to get interface bandwidth and indeed nor does
the device generally: the 1000Mb/s ethernet interface might be
connected to a 70Mb/s pppoe upstream and how would it know? So the
site operator needs to say somewhere what the upstream bandwidth is.
Sun Feb 16 22:16:29 GMT 2025
we probably didn't need to write that service, we could have used the
thing that makes templated config files _and_ if we somehow contrive
to write the interface bandwidth as an interface output we could get
that the same way
if only I could remember how it worked :-)
----
* watch-output watches only _one_ service and is called with a list of
outputs inside that service, so not exactly what we need. we can
extend it easily enough to watch multiple services using poll() if we
can figure out the syntax we want. Luckily all the places that call it
go through modules/secrets/subscriber.nix so it's easy enough to change
existing uses
we could do
watch-outputs -r foo /nix/store/blah/.outputs/ifname /nix/store/eee/.outputs/ifname ...
or
watch-outputs -r foo /nix/store/blah:ifname /nix/store/eee:ifname /nix/store/eee:bandwidth
or
watch-outputs -r foo /nix/store/blah:ifname /nix/store/eee:ifname:bandwidth
which I quite like insofar as it's shorter but has no other real merit
then we need to decide how to represent an output reference in a firewall rule.
Since each rule is basically text already, might just put the handlebars straight in
let qq = builtins.toJSON ;
in "icmp6 limit rate over {{ tonumber(output(${qq (intf "service")}, ${qq (intf "bandwidth")})) / 20 }} bytes/second drop"
probably we should do a separate rule for each interface in the wan zone
Sun Feb 23 00:34:34 GMT 2025
looks like we have no tests for anything involving watched services or subscribers,
or if we do I can't see what
Thu Feb 27 20:47:03 GMT 2025
- use output-template to write firewall rule file
- wrap firewall in svc.secrets.subscriber.build (c.f. e745991) with zones as
watched services
- put the handlebars in the firewall config
we have uncommitted changes to watch-outputs that I'm relunctant to
commit until I have some way to see if they're working. the pppoe test
will check both firewall zones so _should_ start to fail with the
current watch-outputs (because only one service) and then pass when we
put the new one in
Fri Feb 28 01:00:03 GMT 2025
Well, it works at least well enough to pass the test. There is an awful hack
though, because nftables doesn't accept "elements = { }" as valid syntax
for a set with no elements, so we post-process the file to wipe those lines
I wonder if we could instead create the set empty and then use the "other"
nftables format to generate commands that add the elements. If it's
all in the same file (or included files) it will continue to be atomic
Other options
- is the nftables json format any better? we will have to rebuild it
with json support, may be bugger
- write lua bindings to libnftables
Fri Feb 28 23:31:06 GMT 2025
adding json would add 76 + 88k to the image, but I think it would also
mean we have to rewrite all the default rules in json format
with json
[dan@loaclhost:~/src/liminix]$ du result/
20 result/share/doc/nftables/examples
24 result/share/doc/nftables
28 result/share/doc
12 result/share/man/man3
16 result/share/man/man5
44 result/share/man/man8
76 result/share/man
60 result/share/nftables
168 result/share
36 result/etc/nftables/osf
40 result/etc/nftables
44 result/etc
76 result/bin
8 result/include/nftables
12 result/include
8 result/lib/pkgconfig
1172 result/lib
1476 result/
[dan@loaclhost:~/src/liminix]$ du /nix/store/l0zsvldsskiv52b4c9b21ziq5z1qr7vn-jansson-mips-unknown-linux-musl-2.14/
84 /nix/store/l0zsvldsskiv52b4c9b21ziq5z1qr7vn-jansson-mips-unknown-linux-musl-2.14/lib
88 /nix/store/l0zsvldsskiv52b4c9b21ziq5z1qr7vn-jansson-mips-unknown-linux-musl-2.14/
without:
[dan@loaclhost:~/src/liminix]$ du result/
20 result/share/doc/nftables/examples
24 result/share/doc/nftables
28 result/share/doc
12 result/share/man/man3
16 result/share/man/man5
44 result/share/man/man8
76 result/share/man
60 result/share/nftables
168 result/share
36 result/etc/nftables/osf
40 result/etc/nftables
44 result/etc
76 result/bin
8 result/include/nftables
12 result/include
8 result/lib/pkgconfig
1096 result/lib
1400 result/
Sat Mar 1 23:43:17 GMT 2025
I don't think json is going to help because either we'd have to do
elements = map (f: "{{ lookup(f, \"ifname\") }}") zones.${zone}
and there would be null elements in the places for the interfaces that don't exist
yet, or we'd have to write actual json syntax at runtime, at which point why don't
we write the trad nftables syntax instead?
let's write a firewall .nftables file consisting of the zone set
elements plus an "include" directive for the rest of the firewall. NOTE THAT
we may still need to template the rest of the firewall if we want to have
other variables (rate limits) in it, because the rules for that need to be
inserted ahead of the rules for accepting icmp, and there's no way to
do that without

View File

@ -18,10 +18,16 @@ in
(drop "ip6 saddr 2001:db8::/32") # documentation addresses
(drop "ip6 daddr 2001:db8::/32")
# I think this means "check FIB for (saddr, iif) to see if we
# could route a packet to that address using that interface",
# and if we can't then it was an inapproppriate source address
# for packets received _from_ said interface
# Reverse path filtering: drop packet if it's not coming from
# the same interface that we'd use to send a reply. Works by
# doing a lookup in the FIB to find how we'd route a packet _to_
# saddr through iif, and then checking the output interface
# returned by the lookup. if oif is 0, that means no route was
# found for that address with that interface, so the packet can
# be dropped
#
# https://wiki.nftables.org/wiki-nftables/index.php/Matching_routing_information#fib
# https://thr3ads.net/netfilter-buglog/2018/01/2843000-Bug-1220-New-Reverse-path-filtering-using-fib-needs-better-documentation
(drop "fib saddr . iif oif eq 0")
(drop "icmpv6 type router-renumbering")
@ -49,8 +55,6 @@ in
(drop "ip6 daddr ::FFFF:0:0/96")
(drop "ip6 saddr fe80::/10") # link-local unicast
(drop "ip6 daddr fe80::/10")
(drop "ip6 saddr fc00::/7") # unique-local addresses
(drop "ip6 daddr fc00::/7")
(drop "ip6 saddr 2001:10::/28") # ORCHID [RFC4843].
(drop "ip6 daddr 2001:10::/28")
@ -196,9 +200,7 @@ in
type = "filter";
family = "ip";
rules = [
(accept "udp sport 53")
];
rules = [];
};
input-ip4 = {

View File

@ -5,6 +5,9 @@
nftables,
writeFennel,
anoia,
svc,
lua,
output-template,
lualinux,
linotify,
}:
@ -18,32 +21,61 @@ let
inherit (lib.attrsets) mapAttrs' nameValuePair mapAttrsToList;
inherit (lib.strings) concatStringsSep;
inherit (lib.lists) flatten;
inherit (builtins) concatLists toJSON attrValues;
inherit (liminix) outputRef;
mkSet =
family: name:
nameValuePair "${name}-set-${family}" {
kind = "set";
inherit name family;
type = "ifname";
extraText = ''
{{;
local services = { ${concatStringsSep ", " (map toJSON zones.${name})} }
local ifnames = {}
for _, v in ipairs(services) do
local o = output(v, "ifname")
if o then table.insert(ifnames, o) end
end
if (#ifnames > 0) then
return "elements = { " .. table.concat(ifnames, ", ") .. " }\n"
else
return ""
end
}}
'';
};
sets = (mapAttrs' (n: _: mkSet "ip" n) zones) // (mapAttrs' (n: _: mkSet "ip6" n) zones);
allRules = lib.recursiveUpdate extraRules (lib.recursiveUpdate (builtins.trace sets sets) rules);
sets = (mapAttrs' (n: _: mkSet "ip" n) zones) //
(mapAttrs' (n: _: mkSet "ip6" n) zones);
allRules = lib.recursiveUpdate extraRules (lib.recursiveUpdate sets rules);
script = firewallgen "firewall1.nft" allRules;
ifwatch = writeFennel "ifwatch" {
packages = [
anoia
lualinux
linotify
];
mainFunction = "run";
} ./ifwatch.fnl;
watchArg = z: intfs: map (i: "${z}:${i}/.outputs") intfs;
in
longrun {
name = "firewall";
service = longrun {
inherit name;
run = ''
${script}
PATH=${nftables}/bin:$PATH
${ifwatch} ${concatStringsSep " " (flatten (mapAttrsToList watchArg zones))}
PATH=${nftables}/bin:${lua}/bin:$PATH
reload() {
echo reloading firewall
${output-template}/bin/output-template '{{' '}}' < ${script} > /run/${name}/fw.nft;
nft -f /run/${name}/fw.nft ;
}
trap reload SIGUSR1
mkdir -p /run/${name}; in_outputs ${name}
reload
while :; do
# signals sent to ash won't interrupt sleep, but will interrupt wait
sleep 86400 & wait
done
'';
finish = "${nftables}/bin/nft flush ruleset";
};
in
svc.secrets.subscriber.build {
action = "usr1";
watch =
concatLists
(mapAttrsToList (_zone : services : map (s: outputRef s "ifname") services) zones);
inherit service;
}

View File

@ -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 =
{
@ -29,23 +28,17 @@ let
"quit" = "-s 3";
"kill" = "-s 9";
"term" = "-s 15";
"winch" = "-s 28";
"usr1" = "-s 10";
"usr2" = "-s 12";
"winch" = "-s 20";
"usr1" = "-s usr1";
"usr2" = "-s usr2";
}
.${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,13 @@ 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
];
++ optional (watch != []) watcher;
})

View File

@ -10,6 +10,7 @@ let
splitString
hasInfix
substring
optionalString
;
inherit (lib.lists) groupBy;
inherit (lib.attrsets) mapAttrsToList;
@ -56,12 +57,14 @@ let
name,
type,
elements ? [ ],
extraText ? null,
...
}:
''
set ${name} {
type ${type}
${if elements != [ ] then "elements = { ${concatStringsSep ", " elements} }" else ""}
${if elements != [ ] then "elements = { ${concatStringsSep ", " (builtins.trace elements elements)} }" else ""}
${optionalString (extraText != null) extraText}
}
'';

View File

@ -1,3 +0,0 @@
check:
./output-template '{{' '}}' < example.ini > output
diff -u output example.ini.expected

View File

@ -2,6 +2,7 @@
fetchurl,
writeFennel,
fennel,
fennelrepl,
runCommand,
lua,
anoia,
@ -17,9 +18,9 @@ stdenv.mkDerivation {
src = ./.;
buildInputs = [ lua ];
doCheck = true;
nativeBuildInputs = [ fennelrepl ] ;
buildPhase = ''
fennelrepl --test ./output-template.fnl
cp -p ${
writeFennel name {
packages = [
@ -27,11 +28,11 @@ stdenv.mkDerivation {
lualinux
linotify
];
macros = [ anoia.dev ];
mainFunction = "run";
} ./output-template.fnl
} ${name}
'';
checkPhase = "make check";
installPhase = ''
install -D ${name} $out/bin/${name}
'';

View File

@ -24,21 +24,43 @@
(let [delim (.. opening "(.-)" closing)
myenv {
: string
: table
: ipairs
:output
(fn [service-path path]
(fn [service-path path default]
(let [s (assert (svc.open (.. service-path "/.outputs")))]
(s:output path)))
:lua_quote #(string.format "%q" %1)
(or (s:output path) default)))
:lua_quote #(string.format "%q" $1)
:json_quote (fn [x] (.. "\"" (json-escape x) "\""))
}]
(string.gsub text delim
(fn [x]
(assert ((load (.. "return " x) x :t myenv))
(string.format "missing value for %q" x))))))
(let [chunk (if (= (x:sub 1 1) ";")
(x:sub 2)
(.. "return " x))]
(assert ((load chunk x :t myenv))
(string.format "missing value for %q" x)))))))
(fn run []
(let [[opening closing] arg
out (substitute (: (io.input) :read "*a") opening closing)]
(io.write out)))
(import-macros { : define-tests : expect : expect= } :anoia.assert)
(define-tests
(expect= (pick-values 1 (substitute "var={{ 2 + 3 }}" "{{" "}}")) "var=5")
(expect= (pick-values 1 (substitute "{{ json_quote(\"o'reilly\") }}" "{{" "}}"))
"\"o'reilly\"")
(expect= (pick-values 1 (substitute "{{; local a=9; return a }}" "{{" "}}")) "9")
;; "globals" set in one interpolation are available in subsequent ones
(expect= (pick-values 1 (substitute "{{; a=42; return a }} {{ a and 999 or 0 }}" "{{" "}}")) "42 999")
(fn slurp [name]
(with-open [f (assert (io.open name))] (f:read "*a")))
(expect=
(pick-values 1 (substitute (slurp "example.ini") "{{" "}}"))
(slurp "example.ini.expected")))
{ : run }

View File

@ -1,3 +0,0 @@
check:
./output-template '{{' '}}' < example.ini > output
diff -u output example.ini.expected

View File

@ -2,6 +2,7 @@
fetchurl,
writeFennel,
fennel,
fennelrepl,
runCommand,
lua,
anoia,
@ -17,9 +18,10 @@ stdenv.mkDerivation {
src = ./.;
buildInputs = [ lua ];
# doCheck = true;
nativeBuildInputs = [ fennelrepl ] ;
buildPhase = ''
fennelrepl --test ./watch-outputs.fnl
cp -p ${
writeFennel name {
packages = [
@ -28,11 +30,12 @@ stdenv.mkDerivation {
linotify
fennel
];
macros = [ anoia.dev ];
mainFunction = "run";
} ./watch-outputs.fnl
} ${name}
'';
# checkPhase = "make check";
installPhase = ''
install -D ${name} $out/bin/${name}
'';

View File

@ -1 +0,0 @@
eth1

View File

@ -1,3 +0,0 @@
wpa_passphrase={{ output("./example-service","colours/black") }}
think = {{ string.format("%q", output("./example-service","colours/blue")) }}
argonaut = {{ json_quote "hello\ngoodbye\tnext\027" }}

View File

@ -1,3 +0,0 @@
wpa_passphrase=000000
think = "0000ff"
argonaut = "hello\ngoodbye\tnext\u001B"

View File

@ -1,44 +0,0 @@
(local svc (require :anoia.svc))
(fn json-escape [s]
;; All Unicode characters may be placed within the quotation marks,
;; except for the characters that MUST be escaped:
;; quotation mark, reverse solidus, and the control characters (U+0000
;; through U+001F). (RFC 8259)
(-> s
(string.gsub
"[\"\b\f\n\r\t]" {
"\b" "\\b"
"\"" "\\\""
"\f" "\\f"
"\n" "\\n"
"\r" "\\r"
"\t" "\\t"
})
(string.gsub
"([\x00-\x1b])"
(fn [x] (string.format "\\u%04X" (string.byte x))))))
(fn substitute [text opening closing]
(let [delim (.. opening "(.-)" closing)
myenv {
: string
:output
(fn [service-path path]
(let [s (assert (svc.open (.. service-path "/.outputs")))]
(s:output path)))
:lua_quote #(string.format "%q" %1)
:json_quote (fn [x] (.. "\"" (json-escape x) "\""))
}]
(string.gsub text delim
(fn [x]
(assert ((load (.. "return " x) x :t myenv))
(string.format "missing value for %q" x))))))
(fn run []
(let [[opening closing] arg
out (substitute (: (io.input) :read "*a") opening closing)]
(io.write out)))
{ : run }

View File

@ -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,72 @@
["-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))))
[:signal n] (system (%% "s6-svc -s %q /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 (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)]
: 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 tree new-tree)
(print "watched path event:" action controlled-service)
(do-action action controlled-service))
new-tree))))
(when (changed? paths (. trees service) new-tree)
(do-action action controlled-service))))))))
{ : run }

View File

@ -15,6 +15,7 @@ rec {
../../modules/ppp
../../modules/dnsmasq
../../modules/network
../../modules/firewall
];
services.pppoe = svc.pppoe.build {
@ -23,6 +24,13 @@ rec {
password = "NotReallyTheSecret";
};
services.firewall = svc.firewall.build {
zones = {
wan = [ services.pppoe ];
lan = [ services.lan4 ];
};
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.pppoe} address)";
target = "default";
@ -39,5 +47,5 @@ rec {
domain = "fake.liminix.org";
};
defaultProfile.packages = [ pkgs.hello ];
defaultProfile.packages = with pkgs; [ nftables hello ];
}

View File

@ -1,18 +1,46 @@
set timeout 60
spawn socat unix-connect:vm/console -
expect "s6-linux-init"
send "\r\n"
expect "#"
send "PS1=\$(echo 'I1JFQURZIyA=' | base64 -d); stty -echo\n"
expect "#READY#"
set FINISHED 0
set EXIT "1"
while { $FINISHED < 10 } {
send "ip address show dev ppp0 | grep ppp0\r\n"
while { $FINISHED < 20 } {
send "ip address show dev ppp0 | grep ppp0\n"
expect {
"192.168.100.1" { set FINISHED 20; set EXIT 0; }
"can't find device" { send_user "waiting ..." ; send "\r\n"; sleep 3 }
"DOWN" { send_user "waiting ..." ; send "\r\n"; sleep 3 }
"192.168.100.1" { set FINISHED 200; set EXIT 0; }
"can't find device" { send_user "waiting ..." ; sleep 3 }
"DOWN" { send_user "waiting ..." ; sleep 3 }
}
set FINISHED [ expr $FINISHED + 1 ]
}
expect "#READY#"
send "s6-svwait -U /run/service/wan.link.pppoe\n"
expect "#READY#"
set timeout 30
send "nft list set ip table-ip lan || touch /non/existent\n"
expect {
"lan" { puts "lan found" }
"{ }" { puts "missing ifname"; exit 1 }
"No such file or directory" { exit 1 }
}
expect "#READY#"
# if the test fails for no apparent reason, it is quite likely because
# the firewall hasn't had time to reload after the new interface
# appears and you just have to make this sleep longer. Ew, yes
send "sleep 10; nft list set ip table-ip wan || touch /non/existent\n"
expect {
"ppp0" { puts "ppp0 found " }
"{ }" { puts "missing ifname"; exit 1 }
"No such file or directory" { exit 1 }
timeout { exit 1 }
}
expect "#READY#"
exit $EXIT