forked from dan/liminix
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67080627a1 | |||
| 269c9cd916 | |||
| 95ebddb661 | |||
| a66510c9e6 | |||
| 75aa01513c | |||
| d3fd6f5c0a | |||
| 90302f0944 | |||
| 00bf3446f1 | |||
| 0d8abbc314 | |||
| 4c3883a5e7 | |||
| 2ecaa3bef8 | |||
| 5dd595a209 | |||
| 0b61c0b318 | |||
| 78efd6dfcf | |||
| 77421a5d3b | |||
| 74f4577521 | |||
| 3ec5245fa4 | |||
| c223e5cea2 | |||
| 921be2f3c8 | |||
| da64f64ee9 | |||
| c46e228c60 | |||
| 0386452ddc | |||
| 4c549ac625 | |||
| 4d0061e90a | |||
| af966056c7 | |||
| e5963ae3f7 | |||
| f164f19d95 | |||
| dd4ab41f6a | |||
| 5d5dff6729 | |||
| 570d29c368 | |||
| 725af00dc9 | |||
| e1b932ec27 | |||
| 7173b6fb1c | |||
| ed9548f21d | |||
| 0787807a7f | |||
| 38ed91f641 | |||
| ffe9603c39 | |||
| cbd3dfefc5 | |||
| 018c1868b5 | |||
| 5184ff63f7 | |||
| 35909c9a23 | |||
| 4383462199 | |||
| 9730cdd63b | |||
| 095853214b | |||
| 9d6e50cbbc | |||
| 94dbc56595 | |||
| 2cd7f932eb | |||
| 27c7735f02 | |||
| 29c9de248d | |||
| 3ca0d87c27 |
+205
@@ -4137,3 +4137,208 @@ p-clock-init,mfp,allows-mesh-bcast crc32 62f7565f
|
||||
[ 16.762697] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
|
||||
[ 23.030622] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
|
||||
[
|
||||
|
||||
|
||||
Tue Feb 27 23:16:27 GMT 2024
|
||||
|
||||
We made it a full week with rotuer running internet chez nous and no
|
||||
need for an intervention, so I am happy to call it "production". There are
|
||||
still things that need fixing but they're mostly within scope for
|
||||
a services refresh
|
||||
|
||||
I have embarked on "profiles" by creating a wap.nix
|
||||
|
||||
I think we could have a service module for resolvconf
|
||||
|
||||
It would be good to build a wap.nix example for the belkin and we
|
||||
could start looking at ubifs
|
||||
|
||||
I've lost a chunk of notes about using events to drive desired service
|
||||
state. There is probably only going to be one udev listener, so
|
||||
what if we have udev as a config key thusly
|
||||
|
||||
udev.rules = [
|
||||
{
|
||||
match = {
|
||||
SUBSYSTEM = "rpmsg";
|
||||
ATTR.name = "DATA5_CNTL";
|
||||
};
|
||||
|
||||
service = longrun {
|
||||
name = "lte-modem";
|
||||
run = "blah blah blah";
|
||||
};
|
||||
}
|
||||
|
||||
# this one would be provided by the bridge module instead of
|
||||
# adding bridge member services to the default target
|
||||
|
||||
{
|
||||
match = {
|
||||
SUBSYSTEM="net";
|
||||
ID_PATH="pci-0000:04:00.0";
|
||||
ATTR.operstate = "up";
|
||||
};
|
||||
|
||||
service = oneshot {
|
||||
up = "ip link set dev $dev master $(output ${primary} ifname)";
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
This works for udev/sysfs, but we want a similar architecture(sic) for
|
||||
user-generated target state so we could have services that run on e.g.
|
||||
"is the ppp0 service healthy" or not. Probably there isn't a top-level
|
||||
config key for each service though
|
||||
|
||||
services.wan = svc.ppoe.build { .... };
|
||||
services.lte = watcher.build {
|
||||
watching = services.wan;
|
||||
match = {
|
||||
# an expression matching the outputs of the service
|
||||
# to be watched
|
||||
health = "failing";
|
||||
};
|
||||
service = oneshot {
|
||||
run = "start_lte_blah";
|
||||
};
|
||||
}
|
||||
|
||||
thing is, we could use this syntax also for sysfs watches, but not vice versa
|
||||
|
||||
... but it's not quite the same because here we're doing static matches
|
||||
on contents of files, whereas the udev one is a query expression on the
|
||||
sysfs database. we might need that flexibiity to implement "mount the
|
||||
backup drive no matter _which_ damn sda_n_ device it appears as". I don't
|
||||
know if there's the same need for service outputs - postulate the
|
||||
existence of a collection of services which are all similar enough that
|
||||
some other service can watch them all and do $something when one of
|
||||
the changes state. Or a single service with very complicated outputs.
|
||||
For example, something could watch the snmp database and update service
|
||||
status depending on what it finds. Or something something mqtt...
|
||||
|
||||
we find that the "match" needs to be interpreted differently according
|
||||
to the thing being watched. perhaps the service being watched needs to
|
||||
provide a "watch me" interface somehow which accepts match criteria and
|
||||
outputs a true/false. Something else then needs to
|
||||
|
||||
|
||||
services.addmember = services.udev.watch {
|
||||
match = {
|
||||
SUBSYSTEM = "net";
|
||||
ID_PATH = "pci-0000:04:00.0";
|
||||
ATTR.operstate = "up";
|
||||
};
|
||||
|
||||
service = oneshot {
|
||||
up = "ip link set dev $dev master $(output ${primary} ifname)";
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
};
|
||||
}
|
||||
|
||||
Sat Mar 2 15:37:29 GMT 2024
|
||||
|
||||
Simply put, what I think it boils down to is that we want a service
|
||||
which acts as an actuator or control switch for another service,
|
||||
and will start/stop that controlled service according to some
|
||||
criteria.
|
||||
|
||||
services.addmember = svc.network.ifwatch.build {
|
||||
interface = config.hardware.networkInterfaces.lan1;
|
||||
|
||||
# this should be part of the definition not the params
|
||||
service = oneshot {
|
||||
name = "member-${bridge}-${interface}";
|
||||
up = "ip link set dev $dev master $(output ${primary} ifname)";
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
};
|
||||
}
|
||||
|
||||
we could start by writing this. we need to adapt ifwait
|
||||
|
||||
Sun Mar 3 17:09:21 GMT 2024
|
||||
|
||||
this is annoyingly hard to test. the tests we'd like to write are
|
||||
|
||||
1) when it gets events that don't match the requirement, nothing happens
|
||||
2) when it gets an event that should start the service, the
|
||||
service starts
|
||||
3) when stop should stop
|
||||
4) when start and already started, nothing happens
|
||||
5) when stop and already stopped, nothing happens
|
||||
|
||||
what do we do if service fails to start? s6-rc will eventually reset it
|
||||
to "down", I think: do we need to take action?
|
||||
|
||||
Mon Mar 4 20:46:55 GMT 2024
|
||||
|
||||
# relevant but not correct for this model: https://www.forked.net/forums/viewtopic.php?f=13&t=3490
|
||||
|
||||
# power on port 5
|
||||
snmpset -v 1 -c private 192.168.5.14 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 integer 1
|
||||
|
||||
# power off port 5
|
||||
snmpset -v 1 -c private 192.168.5.14 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 integer 2
|
||||
|
||||
# toggle off/on port 5
|
||||
snmpset -v 1 -c private 192.168.5.14 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 integer 3
|
||||
|
||||
Wed Mar 6 18:24:29 GMT 2024
|
||||
|
||||
What happens when we attempt to start the service but it fails? We
|
||||
assume the start was successful so we won't try and restart it again
|
||||
next time we get an event that should cause it to start.
|
||||
|
||||
Thu Mar 7 11:48:26 GMT 2024
|
||||
|
||||
what next?
|
||||
|
||||
- fennel script needs to know where s6-rc is
|
||||
- some nix syntax
|
||||
- update bridge module members.nix to use the new thing
|
||||
|
||||
I can't find a ci derivation that uses the bridge.
|
||||
|
||||
Mon Mar 11 20:31:45 GMT 2024
|
||||
|
||||
Create a qemu config where wan and lan devices are bridged into a
|
||||
single bridge
|
||||
|
||||
start qemu paused
|
||||
Use qemu monitor commands to no-carrier the network devices
|
||||
set_link virtio-net-pci.1 off
|
||||
set_link virtio-net-pci.0 off
|
||||
|
||||
Boot the system
|
||||
|
||||
See if both devices are bridge members
|
||||
|
||||
See if reboot is possible
|
||||
|
||||
Use qemu monitor commands to enable the network devices
|
||||
set_link virtio-net-pci.1 on
|
||||
set_link virtio-net-pci.0 on
|
||||
|
||||
See if both devices are bridge members
|
||||
|
||||
disable again,check if back to starting position
|
||||
|
||||
|
||||
Wed Mar 13 00:00:16 GMT 2024
|
||||
|
||||
aside: "trigger" is the least bad word I've thought of so far for
|
||||
these services that stop/start other services
|
||||
|
||||
telent: yeah, in general 'ps afuxww' (or s6-ps -H :)) is the way to solve this, look for hung s6-rc processes and in particular their s6-svlisten1 children, where the command line will show what service is still waiting for readiness
|
||||
|
||||
Sat Mar 16 23:28:10 GMT 2024
|
||||
|
||||
We have the gl-ar750 booting with the bridge members added by trigger, and
|
||||
it seems to work.
|
||||
|
||||
One thing worth flagging is that we have to run s6-rc with the -b
|
||||
(block if locked) flag in the trigger otherwise the triggered service
|
||||
just doesn't come up if there's something else starting at the same
|
||||
time. There are warnings in the s6-rc docs about doing this, though.
|
||||
We need to think hard about whether we can end up calling s6-rc recursively
|
||||
|
||||
@@ -9,8 +9,12 @@ let
|
||||
borderVmConf = ./bordervm.conf-example.nix;
|
||||
inherit (pkgs.lib.attrsets) genAttrs;
|
||||
devices = [
|
||||
"gl-ar750" "gl-mt300n-v2" "gl-mt300a"
|
||||
"qemu" "qemu-aarch64" "qemu-armv7l"
|
||||
"gl-ar750"
|
||||
"gl-mt300a"
|
||||
"gl-mt300n-v2"
|
||||
"qemu"
|
||||
"qemu-aarch64"
|
||||
"qemu-armv7l"
|
||||
"tp-archer-ax23"
|
||||
"zyxel-nwa50ax"
|
||||
];
|
||||
|
||||
@@ -20,6 +20,9 @@ let
|
||||
});
|
||||
|
||||
eval = pkgs.lib.evalModules {
|
||||
specialArgs = {
|
||||
modulesPath = builtins.toString ./modules;
|
||||
};
|
||||
modules = [
|
||||
{ _module.args = { inherit pkgs; inherit (pkgs) lim; }; }
|
||||
./modules/hardware.nix
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
MTK_INFRACFG = "y";
|
||||
|
||||
MTK_PMIC_WRAP = "y";
|
||||
MTK_EFUSE="y";
|
||||
NVMEM_MTK_EFUSE="y";
|
||||
# MTK_HSDMA="y";
|
||||
MTK_SCPSYS="y";
|
||||
MTK_SCPSYS_PM_DOMAINS="y";
|
||||
@@ -92,7 +92,6 @@
|
||||
|
||||
MEDIATEK_GE_PHY = "y";
|
||||
# MEDIATEK_MT6577_AUXADC = "y";
|
||||
# MEDIATEK_WATCHDOG = "y";
|
||||
NET_MEDIATEK_SOC = "y";
|
||||
NET_MEDIATEK_SOC_WED = "y";
|
||||
NET_MEDIATEK_STAR_EMAC = "y"; # this enables REGMAP_MMIO
|
||||
|
||||
+16
-99
@@ -8,12 +8,10 @@
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}: let
|
||||
secrets = import ./extneder-secrets.nix;
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs) dropbear ifwait serviceFns;
|
||||
svc = config.system.service;
|
||||
in rec {
|
||||
boot = {
|
||||
@@ -24,113 +22,32 @@ in rec {
|
||||
};
|
||||
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/vlan
|
||||
../modules/network
|
||||
../modules/hostapd
|
||||
../modules/bridge
|
||||
../modules/ssh
|
||||
"${modulesPath}/profiles/wap.nix"
|
||||
"${modulesPath}/vlan"
|
||||
"${modulesPath}/ssh"
|
||||
];
|
||||
|
||||
hostname = "extneder";
|
||||
|
||||
kernel = {
|
||||
config = {
|
||||
|
||||
NETFILTER_XT_MATCH_CONNTRACK = "y";
|
||||
|
||||
IP6_NF_IPTABLES = "y"; # do we still need these
|
||||
IP_NF_IPTABLES = "y"; # if using nftables directly
|
||||
|
||||
# these are copied from rotuer and need review.
|
||||
# we're not running a firewall, so why do we need
|
||||
# nftables config?
|
||||
IP_NF_NAT = "y";
|
||||
IP_NF_TARGET_MASQUERADE = "y";
|
||||
NETFILTER = "y";
|
||||
NETFILTER_ADVANCED = "y";
|
||||
NETFILTER_XTABLES = "y";
|
||||
|
||||
NFT_COMPAT = "y";
|
||||
NFT_CT = "y";
|
||||
NFT_LOG = "y";
|
||||
NFT_MASQ = "y";
|
||||
NFT_NAT = "y";
|
||||
NFT_REJECT = "y";
|
||||
NFT_REJECT_INET = "y";
|
||||
|
||||
NF_CONNTRACK = "y";
|
||||
NF_NAT = "y";
|
||||
NF_NAT_MASQUERADE = "y";
|
||||
NF_TABLES = "y";
|
||||
NF_TABLES_INET = "y";
|
||||
NF_TABLES_IPV4 = "y";
|
||||
NF_TABLES_IPV6 = "y";
|
||||
};
|
||||
};
|
||||
|
||||
services.hostap = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
params = {
|
||||
country_code = "GB";
|
||||
hw_mode = "g";
|
||||
wmm_enabled = 1;
|
||||
ieee80211n = 1;
|
||||
inherit (secrets) ssid channel wpa_passphrase;
|
||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||
wpa_key_mgmt = "WPA-PSK";
|
||||
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
|
||||
rsn_pairwise = "CCMP"; # auth for wpa2
|
||||
};
|
||||
};
|
||||
|
||||
services.int = svc.bridge.primary.build {
|
||||
ifname = "int";
|
||||
};
|
||||
|
||||
services.dhcpc = svc.network.dhcp.client.build {
|
||||
interface = services.int;
|
||||
dependencies = [ config.services.hostname ];
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces; [
|
||||
profile.wap = {
|
||||
interfaces = with config.hardware.networkInterfaces; [
|
||||
lan
|
||||
wlan
|
||||
];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build {};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.dhcpc ];
|
||||
name = "resolvconf";
|
||||
# CHECK: https://udhcp.busybox.net/README.udhcpc says
|
||||
# 'A list of DNS server' but doesn't say what separates the
|
||||
# list members. Assuming it's a space or other IFS character
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
for i in $(output ${services.dhcpc} dns); do
|
||||
echo "nameserver $i" > resolv.conf
|
||||
done
|
||||
)
|
||||
'';
|
||||
};
|
||||
filesystem = dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||
wireless = {
|
||||
networks.${secrets.ssid} = {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
inherit (secrets) channel wpa_passphrase;
|
||||
country_code = "GB";
|
||||
hw_mode = "g";
|
||||
wmm_enabled = 1;
|
||||
ieee80211n = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpc} router)";
|
||||
target = "default";
|
||||
dependencies = [services.dhcpc];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build {};
|
||||
users.root.passwd = lib.mkForce secrets.root.passwd;
|
||||
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
|
||||
}
|
||||
|
||||
+54
-149
@@ -6,23 +6,18 @@
|
||||
# problems.
|
||||
|
||||
|
||||
{ config, pkgs, lib, ... } :
|
||||
{ config, pkgs, lib, modulesPath, ... } :
|
||||
let
|
||||
secrets = {
|
||||
domainName = "fake.liminix.org";
|
||||
firewallRules = {};
|
||||
} // (import ./rotuer-secrets.nix);
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle;
|
||||
inherit (pkgs.liminix.services) oneshot bundle;
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
wirelessConfig = {
|
||||
country_code = "GB";
|
||||
inherit (secrets) wpa_passphrase;
|
||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||
wpa_key_mgmt = "WPA-PSK";
|
||||
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
|
||||
rsn_pairwise = "CCMP"; # auth for wpa2
|
||||
wmm_enabled = 1;
|
||||
};
|
||||
|
||||
@@ -36,65 +31,64 @@ in rec {
|
||||
};
|
||||
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/network
|
||||
../modules/ppp
|
||||
../modules/dnsmasq
|
||||
../modules/dhcp6c
|
||||
../modules/firewall
|
||||
../modules/hostapd
|
||||
../modules/bridge
|
||||
../modules/ntp
|
||||
../modules/schnapps
|
||||
../modules/ssh
|
||||
../modules/outputs/btrfs.nix
|
||||
../modules/outputs/extlinux.nix
|
||||
"${modulesPath}/profiles/gateway.nix"
|
||||
"${modulesPath}/schnapps"
|
||||
"${modulesPath}/outputs/btrfs.nix"
|
||||
"${modulesPath}/outputs/extlinux.nix"
|
||||
];
|
||||
hostname = "rotuer";
|
||||
rootfsType = "btrfs";
|
||||
rootOptions = "subvol=@";
|
||||
boot.loader.extlinux.enable = true;
|
||||
|
||||
services.hostap = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
params = {
|
||||
ssid = secrets.ssid;
|
||||
hw_mode="g";
|
||||
channel = "2";
|
||||
ieee80211n = 1;
|
||||
} // wirelessConfig;
|
||||
};
|
||||
|
||||
services.hostap5 = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan5;
|
||||
params = rec {
|
||||
ssid = "${secrets.ssid}5";
|
||||
hw_mode="a";
|
||||
channel = 36;
|
||||
ht_capab = "[HT40+]";
|
||||
vht_oper_chwidth = 1;
|
||||
vht_oper_centr_freq_seg0_idx = channel + 6;
|
||||
ieee80211n = 1;
|
||||
ieee80211ac = 1;
|
||||
} // wirelessConfig;
|
||||
};
|
||||
|
||||
services.int = svc.network.address.build {
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
family = "inet"; address ="${secrets.lan.prefix}.1"; prefixLength = 24;
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces;
|
||||
[ wlan
|
||||
wlan5
|
||||
lan0
|
||||
lan1
|
||||
lan2
|
||||
lan3
|
||||
lan4
|
||||
];
|
||||
profile.gateway = {
|
||||
lan = {
|
||||
interfaces = with config.hardware.networkInterfaces;
|
||||
[
|
||||
wlan wlan5
|
||||
lan0 lan1 lan2 lan3 lan4
|
||||
];
|
||||
inherit (secrets.lan) prefix;
|
||||
address = {
|
||||
family = "inet"; address ="${secrets.lan.prefix}.1"; prefixLength = 24;
|
||||
};
|
||||
dhcp = {
|
||||
start = 10;
|
||||
end = 240;
|
||||
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
|
||||
localDomain = "lan";
|
||||
};
|
||||
};
|
||||
wan = {
|
||||
interface = config.hardware.networkInterfaces.wan;
|
||||
username = secrets.l2tp.name;
|
||||
password = secrets.l2tp.password;
|
||||
dhcp6.enable = true;
|
||||
};
|
||||
firewall = {
|
||||
enable = true;
|
||||
rules =
|
||||
let defaults = import ./demo-firewall.nix;
|
||||
in lib.recursiveUpdate defaults secrets.firewallRules;
|
||||
};
|
||||
wireless.networks = {
|
||||
"${secrets.ssid}" = {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
hw_mode="g";
|
||||
channel = "2";
|
||||
ieee80211n = 1;
|
||||
} // wirelessConfig;
|
||||
"${secrets.ssid}5" = rec {
|
||||
interface = config.hardware.networkInterfaces.wlan5;
|
||||
hw_mode="a";
|
||||
channel = 36;
|
||||
ht_capab = "[HT40+]";
|
||||
vht_oper_chwidth = 1;
|
||||
vht_oper_centr_freq_seg0_idx = channel + 6;
|
||||
ieee80211n = 1;
|
||||
ieee80211ac = 1;
|
||||
} // wirelessConfig;
|
||||
};
|
||||
};
|
||||
|
||||
services.ntp = svc.ntp.build {
|
||||
@@ -106,95 +100,6 @@ in rec {
|
||||
|
||||
users.root = secrets.root;
|
||||
|
||||
services.dns =
|
||||
let interface = services.int;
|
||||
in svc.dnsmasq.build {
|
||||
resolvconf = services.resolvconf;
|
||||
inherit interface;
|
||||
ranges = [
|
||||
"${secrets.lan.prefix}.10,${secrets.lan.prefix}.240"
|
||||
# ra-stateless: sends router advertisements with the O and A
|
||||
# bits set, and provides a stateless DHCP service. The client
|
||||
# will use a SLAAC address, and use DHCP for other
|
||||
# configuration information.
|
||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
||||
];
|
||||
|
||||
# You can add static addresses for the DHCP server here. I'm
|
||||
# not putting my actual MAC addresses in a public git repo ...
|
||||
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
|
||||
upstreams = [ "/${secrets.domainName}/" ];
|
||||
domain = secrets.domainName;
|
||||
};
|
||||
|
||||
services.wan = svc.pppoe.build {
|
||||
interface = config.hardware.networkInterfaces.wan;
|
||||
ppp-options = [
|
||||
"debug" "+ipv6" "noauth"
|
||||
"name" secrets.l2tp.name
|
||||
"password" secrets.l2tp.password
|
||||
];
|
||||
};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.wan ];
|
||||
name = "resolvconf";
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
|
||||
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
|
||||
chmod 0444 resolv.conf
|
||||
)
|
||||
'';
|
||||
};
|
||||
|
||||
filesystem =
|
||||
let inherit (pkgs.pseudofile) dir symlink;
|
||||
in dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||
};
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.wan ];
|
||||
};
|
||||
|
||||
services.defaultroute6 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} ipv6-peer-address)";
|
||||
target = "default";
|
||||
interface = services.wan;
|
||||
};
|
||||
|
||||
services.firewall = svc.firewall.build {
|
||||
ruleset =
|
||||
let defaults = import ./demo-firewall.nix;
|
||||
in lib.recursiveUpdate defaults secrets.firewallRules;
|
||||
};
|
||||
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
||||
services.dhcp6c =
|
||||
let client = svc.dhcp6c.client.build {
|
||||
interface = services.wan;
|
||||
};
|
||||
in bundle {
|
||||
name = "dhcp6c";
|
||||
contents = [
|
||||
(svc.dhcp6c.prefix.build {
|
||||
inherit client;
|
||||
interface = services.int;
|
||||
})
|
||||
(svc.dhcp6c.address.build {
|
||||
inherit client;
|
||||
interface = services.wan;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
min-collect-garbage
|
||||
nftables
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
./ssh
|
||||
./outputs/tftpboot.nix
|
||||
./outputs/ubifs.nix
|
||||
./ubifs.nix
|
||||
./ubinize.nix
|
||||
./users.nix
|
||||
./vlan
|
||||
|
||||
@@ -29,6 +29,10 @@ in {
|
||||
services = mkOption {
|
||||
type = types.attrsOf type_service;
|
||||
};
|
||||
system.callService = mkOption {
|
||||
type = types.functionTo (types.functionTo types.anything);
|
||||
};
|
||||
|
||||
filesystem = mkOption {
|
||||
type = types.anything;
|
||||
description = ''
|
||||
@@ -111,6 +115,31 @@ in {
|
||||
"fw_devlink=off"
|
||||
] ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}";
|
||||
|
||||
system.callService = path : parameters :
|
||||
let
|
||||
typeChecked = caller: type: value:
|
||||
let
|
||||
inherit (lib) types mergeDefinitions;
|
||||
defs = [{ file = caller; inherit value; }];
|
||||
type' = types.submodule { options = type; };
|
||||
in (mergeDefinitions [] type' defs).mergedValue;
|
||||
cp = lib.callPackageWith(pkgs // { svc = config.system.service; });
|
||||
pkg = cp path {};
|
||||
checkTypes = t : p : typeChecked (builtins.toString path) t p;
|
||||
in {
|
||||
inherit parameters;
|
||||
build = { dependencies ? [], ... } @ args :
|
||||
let
|
||||
s = pkg (checkTypes parameters
|
||||
(builtins.removeAttrs args ["dependencies"]));
|
||||
in s.overrideAttrs (o: {
|
||||
dependencies = (builtins.map (d: d.name) dependencies) ++ o.dependencies;
|
||||
buildInputs = dependencies ++ o.buildInputs;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
users.root = {
|
||||
uid = 0; gid= 0; gecos = "Root of all evaluation";
|
||||
dir = "/home/root/";
|
||||
|
||||
@@ -14,6 +14,8 @@ let
|
||||
inherit (pkgs) liminix;
|
||||
in
|
||||
{
|
||||
imports = [ ../ifwait ];
|
||||
|
||||
options = {
|
||||
system.service.bridge = {
|
||||
primary = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
@@ -27,7 +29,7 @@ in
|
||||
description = "bridge interface name to create";
|
||||
};
|
||||
};
|
||||
members = liminix.callService ./members.nix {
|
||||
members = config.system.callService ./members.nix {
|
||||
primary = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
description = "primary bridge interface";
|
||||
@@ -47,5 +49,5 @@ in
|
||||
# a better way to test for the existence of vlan config:
|
||||
# maybe the module should set an `enabled` attribute?
|
||||
BRIDGE_VLAN_FILTERING = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
liminix
|
||||
, ifwait
|
||||
, lib
|
||||
, svc
|
||||
}:
|
||||
{ members, primary } :
|
||||
|
||||
@@ -10,14 +11,20 @@ let
|
||||
inherit (liminix.services) bundle oneshot;
|
||||
inherit (lib) mkOption types;
|
||||
addif = member :
|
||||
oneshot {
|
||||
name = "${primary.name}.member.${member.name}";
|
||||
up = ''
|
||||
dev=$(output ${member} ifname)
|
||||
${ifwait}/bin/ifwait $dev running && ip link set dev $dev master $(output ${primary} ifname)
|
||||
'';
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
# 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 = ''
|
||||
ip link set dev $(output ${member} ifname) master $(output ${primary} ifname)
|
||||
'';
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
};
|
||||
};
|
||||
in bundle {
|
||||
name = "${primary.name}.members";
|
||||
|
||||
@@ -67,7 +67,7 @@ in {
|
||||
};
|
||||
loadAddress = mkOption { type = types.ints.unsigned; default = null; };
|
||||
entryPoint = mkOption { type = types.ints.unsigned; };
|
||||
alignment = mkOption { type = types.ints.unsigned; default = null; description = "Alignment passed to `mkimage` for FIT"; };
|
||||
alignment = mkOption { type = types.nullOr types.ints.unsigned; default = null; description = "Alignment passed to `mkimage` for FIT"; };
|
||||
radios = mkOption {
|
||||
description = ''
|
||||
Kernel modules (from mac80211 package) required for the
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs) liminix;
|
||||
inherit (lib) mkOption types;
|
||||
in {
|
||||
options.system.service.ifwait =
|
||||
mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
|
||||
config.system.service.ifwait = config.system.callService ./ifwait.nix {
|
||||
state = mkOption { type = types.str; };
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
};
|
||||
service = mkOption {
|
||||
type = liminix.lib.types.service;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{ ifwait, liminix } :
|
||||
{
|
||||
state
|
||||
, interface
|
||||
, service
|
||||
}:
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
in longrun {
|
||||
name = "ifwait.${interface.name}";
|
||||
buildInputs = [ service ];
|
||||
run = ''
|
||||
${ifwait}/bin/ifwait -s ${service.name} $(output ${interface} ifname) ${state}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
svc = config.system.service;
|
||||
cfg = config.profile.gateway;
|
||||
inherit (lib) mkOption mkEnableOption mkIf mdDoc types optional optionals;
|
||||
inherit (pkgs) liminix serviceFns;
|
||||
inherit (liminix.services) bundle oneshot;
|
||||
hostaps =
|
||||
let
|
||||
defaults = {
|
||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||
wpa_key_mgmt = "WPA-PSK";
|
||||
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
|
||||
rsn_pairwise = "CCMP"; # auth for wpa2
|
||||
};
|
||||
in lib.mapAttrs'
|
||||
(name : value :
|
||||
let
|
||||
attrs = defaults // { ssid = name; } // value;
|
||||
in lib.nameValuePair
|
||||
"hostap-${name}"
|
||||
(svc.hostapd.build {
|
||||
interface = attrs.interface;
|
||||
params = lib.filterAttrs (k: v: k != "interface") attrs;
|
||||
}))
|
||||
cfg.wireless.networks;
|
||||
in {
|
||||
|
||||
options.profile.gateway = {
|
||||
lan = {
|
||||
interfaces = mkOption {
|
||||
type = types.listOf liminix.lib.types.interface;
|
||||
default = [];
|
||||
};
|
||||
address = mkOption {
|
||||
type = types.attrs;
|
||||
};
|
||||
prefix = mkOption { type = types.str; };
|
||||
dhcp = {
|
||||
start = mkOption { type = types.int; };
|
||||
end = mkOption { type = types.int; };
|
||||
hosts = mkOption { type = types.attrs; };
|
||||
localDomain = mkOption { type = types.str; };
|
||||
};
|
||||
};
|
||||
|
||||
firewall = {
|
||||
enable = mkEnableOption "firewall";
|
||||
rules = mkOption { type = types.attrsOf types.attrs; };
|
||||
};
|
||||
|
||||
wan = {
|
||||
interface = mkOption { type = liminix.lib.types.interface; };
|
||||
username = mkOption { type = types.str; };
|
||||
password = mkOption { type = types.str; };
|
||||
dhcp6.enable = mkOption { type = types.bool; };
|
||||
};
|
||||
|
||||
wireless = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
../wlan.nix
|
||||
../network
|
||||
../ppp
|
||||
../dnsmasq
|
||||
../dhcp6c
|
||||
../firewall
|
||||
../hostapd
|
||||
../bridge
|
||||
../ntp
|
||||
../ssh
|
||||
{ config.services = hostaps; }
|
||||
];
|
||||
|
||||
config = {
|
||||
services.int = svc.network.address.build ({
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
} // cfg.lan.address);
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = config.services.int;
|
||||
members = cfg.lan.interfaces;
|
||||
};
|
||||
|
||||
services.wan = svc.pppoe.build {
|
||||
inherit (cfg.wan) interface;
|
||||
ppp-options = [
|
||||
"debug" "+ipv6" "noauth"
|
||||
"name" cfg.wan.username
|
||||
"password" cfg.wan.password
|
||||
];
|
||||
};
|
||||
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
||||
services.dhcp6c =
|
||||
let
|
||||
client = svc.dhcp6c.client.build {
|
||||
interface = config.services.wan;
|
||||
};
|
||||
bundl = bundle {
|
||||
name = "dhcp6c";
|
||||
contents = [
|
||||
(svc.dhcp6c.prefix.build {
|
||||
inherit client;
|
||||
interface = config.services.int;
|
||||
})
|
||||
(svc.dhcp6c.address.build {
|
||||
inherit client;
|
||||
interface = config.services.wan;
|
||||
})
|
||||
];
|
||||
};
|
||||
in mkIf cfg.wan.dhcp6.enable bundl;
|
||||
|
||||
services.dns =
|
||||
let interface = config.services.int;
|
||||
dcfg = cfg.lan.dhcp;
|
||||
in svc.dnsmasq.build {
|
||||
resolvconf = config.services.resolvconf;
|
||||
inherit interface;
|
||||
ranges = [
|
||||
"${cfg.lan.prefix}.${toString dcfg.start},${cfg.lan.prefix}.${toString dcfg.end}"
|
||||
# ra-stateless: sends router advertisements with the O and A
|
||||
# bits set, and provides a stateless DHCP service. The client
|
||||
# will use a SLAAC address, and use DHCP for other
|
||||
# configuration information.
|
||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
||||
];
|
||||
|
||||
hosts = dcfg.hosts;
|
||||
upstreams = [ "/${dcfg.localDomain}/" ];
|
||||
domain = dcfg.localDomain;
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${config.services.wan} address)";
|
||||
target = "default";
|
||||
dependencies = [ config.services.wan ];
|
||||
};
|
||||
|
||||
services.defaultroute6 = svc.network.route.build {
|
||||
via = "$(output ${config.services.wan} ipv6-peer-address)";
|
||||
target = "default";
|
||||
interface = config.services.wan;
|
||||
};
|
||||
|
||||
services.firewall = mkIf cfg.firewall.enable
|
||||
(svc.firewall.build {
|
||||
ruleset = cfg.firewall.rules;
|
||||
});
|
||||
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ config.services.wan ];
|
||||
name = "resolvconf";
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
echo "nameserver $(output ${config.services.wan} ns1)" > resolv.conf
|
||||
echo "nameserver $(output ${config.services.wan} ns2)" >> resolv.conf
|
||||
chmod 0444 resolv.conf
|
||||
)
|
||||
'';
|
||||
};
|
||||
|
||||
filesystem =
|
||||
let inherit (pkgs.pseudofile) dir symlink;
|
||||
in dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${config.services.resolvconf}/.outputs/resolv.conf";
|
||||
};
|
||||
};
|
||||
};
|
||||
# services.defaultroute4 = svc.network.route.build {
|
||||
# via = "$(output ${services.wan} address)";
|
||||
# target = "default";
|
||||
# dependencies = [ services.wan ];
|
||||
# };
|
||||
|
||||
# services.defaultroute6 = svc.network.route.build {
|
||||
# via = "$(output ${services.wan} ipv6-peer-address)";
|
||||
# target = "default";
|
||||
# interface = services.wan;
|
||||
# };
|
||||
|
||||
# services.firewall = svc.firewall.build {
|
||||
# ruleset =
|
||||
# let defaults = import ./demo-firewall.nix;
|
||||
# in lib.recursiveUpdate defaults secrets.firewallRules;
|
||||
# };
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (pkgs) liminix;
|
||||
inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
|
||||
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
cfg = config.profile.wap;
|
||||
|
||||
hostaps =
|
||||
let
|
||||
defaults = {
|
||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||
wpa_key_mgmt = "WPA-PSK";
|
||||
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
|
||||
rsn_pairwise = "CCMP"; # auth for wpa2
|
||||
};
|
||||
in lib.mapAttrs'
|
||||
(name : value :
|
||||
let
|
||||
attrs = defaults // { ssid = name; } // value;
|
||||
in lib.nameValuePair
|
||||
"hostap-${name}"
|
||||
(svc.hostapd.build {
|
||||
interface = attrs.interface;
|
||||
params = lib.filterAttrs (k: v: k != "interface") attrs;
|
||||
}))
|
||||
cfg.wireless.networks;
|
||||
|
||||
in {
|
||||
imports = [
|
||||
../wlan.nix
|
||||
../network
|
||||
../hostapd
|
||||
../bridge
|
||||
{ config.services = hostaps; }
|
||||
];
|
||||
|
||||
options.profile.wap = {
|
||||
interfaces = mkOption {
|
||||
type = types.listOf liminix.lib.types.interface;
|
||||
default = [];
|
||||
};
|
||||
wireless = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
};
|
||||
};
|
||||
config = {
|
||||
|
||||
services.int = svc.bridge.primary.build {
|
||||
ifname = "int";
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = config.services.int;
|
||||
members = cfg.interfaces;
|
||||
};
|
||||
|
||||
services.dhcpc = svc.network.dhcp.client.build {
|
||||
interface = config.services.int;
|
||||
dependencies = [ config.services.hostname ];
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${config.services.dhcpc} router)";
|
||||
target = "default";
|
||||
dependencies = [config.services.dhcpc];
|
||||
};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ config.services.dhcpc ];
|
||||
name = "resolvconf";
|
||||
# CHECK: https://udhcp.busybox.net/README.udhcpc says
|
||||
# 'A list of DNS server' but doesn't say what separates the
|
||||
# list members. Assuming it's a space or other IFS character
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
for i in $(output ${config.services.dhcpc} dns); do
|
||||
echo "nameserver $i" > resolv.conf
|
||||
done
|
||||
)
|
||||
'';
|
||||
};
|
||||
filesystem = dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${config.services.resolvconf}/.outputs/resolv.conf";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+16
@@ -77,6 +77,22 @@ extraPkgs // {
|
||||
|
||||
};
|
||||
|
||||
# luarocks wants a cross-compiled cmake (which seems like a bug,
|
||||
# we're never going to run luarocks on the device, but ...)
|
||||
# but https://github.com/NixOS/nixpkgs/issues/284734
|
||||
# so we do surgery on the cmake derivation until that's fixed
|
||||
|
||||
cmake = prev.cmake.overrideAttrs(o:
|
||||
# don't override the build cmake or we'll have to rebuild
|
||||
# half the known universe to no useful benefit
|
||||
if final.stdenv.buildPlatform != final.stdenv.hostPlatform
|
||||
then {
|
||||
preConfigure =
|
||||
builtins.replaceStrings
|
||||
["$configureFlags"] ["$configureFlags $cmakeFlags"] o.preConfigure;
|
||||
}
|
||||
else {}
|
||||
);
|
||||
|
||||
dnsmasq =
|
||||
let d = prev.dnsmasq.overrideAttrs(o: {
|
||||
|
||||
+5
-1
@@ -1,3 +1,7 @@
|
||||
(fn assoc [tbl k v]
|
||||
(tset tbl k v)
|
||||
tbl)
|
||||
|
||||
(fn merge [table1 table2]
|
||||
(collect [k v (pairs table2) &into table1]
|
||||
k v))
|
||||
@@ -62,4 +66,4 @@
|
||||
(s:sub 1 (- (# s) pad))))
|
||||
|
||||
|
||||
{ : merge : split : file-exists? : system : hash : base64url : dup }
|
||||
{ : assoc : merge : split : file-exists? : system : hash : base64url : dup }
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
(local netlink (require :netlink))
|
||||
|
||||
; (local { : view } (require :fennel))
|
||||
|
||||
(fn events [groups]
|
||||
(let [sock (netlink.socket)]
|
||||
(coroutine.wrap
|
||||
(fn []
|
||||
(each [_ e (ipairs (sock:query groups))]
|
||||
(coroutine.yield e))
|
||||
(while (sock:poll)
|
||||
(each [_ e (ipairs (sock:event))]
|
||||
(coroutine.yield e)))))))
|
||||
|
||||
{ : events }
|
||||
@@ -0,0 +1,7 @@
|
||||
(local nl (require :anoia.nl))
|
||||
(local { : view } (require :fennel))
|
||||
|
||||
(let [events (nl.events {:link true})]
|
||||
(each [ev events]
|
||||
(print "got one ")
|
||||
(print (view ev))))
|
||||
@@ -8,12 +8,14 @@
|
||||
, writeScriptBin
|
||||
, linotify
|
||||
, anoia
|
||||
, netlink-lua
|
||||
, fennel
|
||||
}:
|
||||
let packages = [
|
||||
linotify
|
||||
anoia
|
||||
fennel
|
||||
netlink-lua
|
||||
lua.pkgs.luafilesystem
|
||||
];
|
||||
join = ps: builtins.concatStringsSep ";" ps;
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
, netlink-lua
|
||||
, writeFennelScript
|
||||
, runCommand
|
||||
, anoia
|
||||
}:
|
||||
runCommand "ifwait" {} ''
|
||||
mkdir -p $out/bin
|
||||
cp -p ${writeFennelScript "ifwait" [netlink-lua] ./ifwait.fnl} $out/bin/ifwait
|
||||
cp -p ${writeFennelScript "ifwait" [anoia netlink-lua] ./ifwait.fnl} $out/bin/ifwait
|
||||
''
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
{:event "newlink"
|
||||
:hwaddr "00:00:00:00:00:00"
|
||||
:index 1
|
||||
:mtu 65536
|
||||
:name "lo"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "50:3e:aa:08:df:52"
|
||||
:index 2
|
||||
:mtu 1500
|
||||
:name "enp1s0"
|
||||
:running "no"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "1c:1b:0d:9c:39:2d"
|
||||
:index 3
|
||||
:mtu 1500
|
||||
:name "enp0s31f6"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "da:4d:53:c3:54:43"
|
||||
:index 4
|
||||
:mtu 1500
|
||||
:name "vbridge0"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "00:28:f8:69:fa:14"
|
||||
:index 6
|
||||
:mtu 1500
|
||||
:name "wlp4s0"
|
||||
:running "no"
|
||||
:stamp 857161382
|
||||
:up "no"}
|
||||
{:event "newlink"
|
||||
:hwaddr "02:42:b1:e6:e5:bd"
|
||||
:index 7
|
||||
:mtu 1500
|
||||
:name "br-7ddfef4820c5"
|
||||
:running "no"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "02:42:8d:d4:36:34"
|
||||
:index 8
|
||||
:mtu 1500
|
||||
:name "br-95da8b40a7cc"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "02:42:bc:cf:a8:5e"
|
||||
:index 9
|
||||
:mtu 1500
|
||||
:name "docker0"
|
||||
:running "no"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "b6:66:50:69:33:a6"
|
||||
:index 11
|
||||
:mtu 1500
|
||||
:name "veth2ff6ec3"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "e6:94:c8:48:f3:97"
|
||||
:index 13
|
||||
:mtu 1500
|
||||
:name "veth0913974"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "9a:87:d8:f2:c6:96"
|
||||
:index 15
|
||||
:mtu 1500
|
||||
:name "veth0e74156"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "5e:d2:92:b9:5f:6d"
|
||||
:index 17
|
||||
:mtu 1500
|
||||
:name "veth89a36b3"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "ca:88:3f:09:bc:51"
|
||||
:index 19
|
||||
:mtu 1500
|
||||
:name "veth73c1e0b"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "b6:7d:5c:38:89:1d"
|
||||
:index 21
|
||||
:mtu 1500
|
||||
:name "dummy0"
|
||||
:running "no"
|
||||
:stamp 857161382
|
||||
:up "no"}
|
||||
{:event "newlink"
|
||||
:hwaddr "52:f0:46:da:0c:0c"
|
||||
:index 22
|
||||
:mtu 1500
|
||||
:name "dummy1"
|
||||
:running "yes"
|
||||
:stamp 857161382
|
||||
:up "yes"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "00:22:61:3d:f7:54"
|
||||
:index 4
|
||||
:ip "192.168.8.140"
|
||||
:probes 1
|
||||
:stamp 857165355
|
||||
:state "stale"}
|
||||
{:event "delneigh"
|
||||
:hwaddr "5c:60:ba:58:34:93"
|
||||
:index 3
|
||||
:stamp 857166891
|
||||
:state "stale"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "80:64:6f:9e:15:02"
|
||||
:index 4
|
||||
:ip "192.168.8.161"
|
||||
:probes 1
|
||||
:stamp 857172523
|
||||
:state "stale"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "e4:95:6e:42:c2:6c"
|
||||
:index 3
|
||||
:stamp 857174763
|
||||
:state "reachable"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "e4:b3:18:76:1b:23"
|
||||
:index 4
|
||||
:ip "2001:8b0:de3a:40de:4708:c700:4de2:9264"
|
||||
:probes 1
|
||||
:stamp 857175595
|
||||
:state "stale"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "80:64:6f:9e:10:c6"
|
||||
:index 4
|
||||
:ip "192.168.8.53"
|
||||
:probes 1
|
||||
:stamp 857176619
|
||||
:state "stale"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "80:64:6f:9e:15:02"
|
||||
:index 4
|
||||
:ip "192.168.8.161"
|
||||
:probes 1
|
||||
:stamp 857177643
|
||||
:state "probe"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "80:64:6f:9e:15:02"
|
||||
:index 4
|
||||
:ip "192.168.8.161"
|
||||
:probes 1
|
||||
:stamp 857177644
|
||||
:state "reachable"}
|
||||
{:event "newlink"
|
||||
:hwaddr "b6:7d:5c:38:89:1d"
|
||||
:index 21
|
||||
:mtu 1500
|
||||
:name "dummy0"
|
||||
:running "yes"
|
||||
:stamp 857178258
|
||||
:up "yes"}
|
||||
{:event "newlink"
|
||||
:hwaddr "b6:7d:5c:38:89:1d"
|
||||
:index 21
|
||||
:mtu 1500
|
||||
:name "dummy0"
|
||||
:running "no"
|
||||
:stamp 857181661
|
||||
:up "no"}
|
||||
{:event "newneigh"
|
||||
:hwaddr "80:64:6f:9e:10:c6"
|
||||
:index 4
|
||||
:ip "192.168.8.53"
|
||||
:probes 1
|
||||
:stamp 857182251
|
||||
:state "probe"}
|
||||
+43
-31
@@ -1,52 +1,64 @@
|
||||
(local netlink (require :netlink))
|
||||
(local sock (netlink.socket))
|
||||
(local nl (require :anoia.nl))
|
||||
(local { : assoc : system } (require :anoia))
|
||||
|
||||
; (local { : view} (require :fennel))
|
||||
|
||||
(fn assoc [tbl k v]
|
||||
(tset tbl k v)
|
||||
tbl)
|
||||
|
||||
(fn parse-args [args]
|
||||
(match args
|
||||
["-v" & rest] (assoc (parse-args rest) :verbose true)
|
||||
["-t" timeout & rest] (assoc (parse-args rest) :timeout (tonumber timeout))
|
||||
["-s" service & rest] (assoc (parse-args rest) :service service)
|
||||
[linkname "up"] {:link linkname :expecting "up"}
|
||||
[linkname "running"] {:link linkname :expecting "running"}
|
||||
[linkname "present"] {:link linkname :expecting "present"}
|
||||
[linkname nil] {:link linkname :expecting "present"}
|
||||
_ nil))
|
||||
|
||||
(local parameters
|
||||
(or
|
||||
(parse-args arg)
|
||||
(assert false (.. "Usage: " (. arg 0) " [-v] ifname [present|up|running]"))))
|
||||
(fn event-matches? [params v]
|
||||
(let [got
|
||||
(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}
|
||||
{:present true :up true :running true}
|
||||
|
||||
(fn run-events [evs]
|
||||
(each [_ v (ipairs evs)]
|
||||
(let [got
|
||||
(match v
|
||||
;; - up: Reflects the administrative state of the interface (IFF_UP)
|
||||
;; - running: Reflects the operational state (IFF_RUNNING).
|
||||
{:event "newlink" :name parameters.link :up :yes :running :yes}
|
||||
{:present true :up true :running true}
|
||||
{:event "newlink" :name params.link :up :yes}
|
||||
{:present :true :up true}
|
||||
|
||||
{:event "newlink" :name parameters.link :up :yes}
|
||||
{:present :true :up true}
|
||||
{:event "newlink" :name params.link}
|
||||
{:present true }
|
||||
|
||||
{:event "newlink" :name parameters.link}
|
||||
{:present true }
|
||||
_
|
||||
{})]
|
||||
(not (not (. got params.expecting)))))
|
||||
|
||||
_
|
||||
{})]
|
||||
(when (. got parameters.expecting)
|
||||
(os.exit 0)))))
|
||||
(var up :unknown)
|
||||
(fn toggle-service [service wanted?]
|
||||
(when (not (= up wanted?))
|
||||
(set up
|
||||
(if wanted?
|
||||
(pcall system (.. "s6-rc -b -u change " service))
|
||||
(not (pcall system (.. "s6-rc -b -d change " service)))))
|
||||
))
|
||||
|
||||
(fn run [args event-fn]
|
||||
(set up :unknown)
|
||||
(let [parameters
|
||||
(assert (parse-args args)
|
||||
(.. "Usage: ifwait [-v] ifname [present|up|running]"))]
|
||||
(when parameters.verbose
|
||||
(print (.. "ifwait: waiting for "
|
||||
parameters.link " to be " parameters.expecting)))
|
||||
|
||||
(when parameters.verbose
|
||||
(print (.. (. arg 0) ": waiting for "
|
||||
parameters.link " to be " parameters.expecting)))
|
||||
(if parameters.service
|
||||
(each [e (event-fn)]
|
||||
(if (= e.name parameters.link)
|
||||
(toggle-service parameters.service (event-matches? parameters e))))
|
||||
(each [e (event-fn)
|
||||
&until (event-matches? parameters e)]
|
||||
true))))
|
||||
|
||||
(run-events (sock:query {:link true}))
|
||||
(when (not (= (. arg 0) "test"))
|
||||
(run arg #(nl.events {:link true})))
|
||||
|
||||
(while (sock:poll) (run-events (sock:event)))
|
||||
{ : run }
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
(local { : view &as fennel } (require :fennel))
|
||||
(local anoia (require :anoia))
|
||||
|
||||
(var fake-system (fn [s] (print "executing " s)))
|
||||
(tset anoia :system #(fake-system $1))
|
||||
|
||||
(macro expect= [actual expected]
|
||||
`(let [ve# (view ,expected)
|
||||
va# (view ,actual)]
|
||||
(when (not (= ve# va#))
|
||||
(assert false
|
||||
(.. "\nexpected " ve# "\ngot " va#)
|
||||
))))
|
||||
|
||||
(fn event-generator [events]
|
||||
(coroutine.wrap
|
||||
(fn []
|
||||
(each [_ e (ipairs events)] (coroutine.yield e)))))
|
||||
|
||||
(fn file-events [path]
|
||||
(let [data (with-open [e (io.open path "r")] (e:read "*a"))
|
||||
parse (fennel.parser data)]
|
||||
(icollect [_ ast parse]
|
||||
ast)))
|
||||
|
||||
(set _G.arg (doto [] (tset 0 "test")))
|
||||
(local ifwait (require :ifwait))
|
||||
|
||||
(let [gen (event-generator (file-events "events-fixture"))]
|
||||
(ifwait.run ["dummy0" "up"] #gen)
|
||||
(match (pcall gen)
|
||||
(true _) true
|
||||
(false msg) (error "didn't detect dummy0 up event")))
|
||||
|
||||
(var upsies [])
|
||||
(set fake-system
|
||||
(fn [s]
|
||||
(if (s:match "-u change addmember")
|
||||
(table.insert upsies :u)
|
||||
(s:match "-d change addmember")
|
||||
(table.insert upsies :d))))
|
||||
|
||||
(fn newlink [name up running]
|
||||
{:event "newlink"
|
||||
:hwaddr "b6:7d:5c:38:89:1d"
|
||||
:index (string.unpack ">i2" name)
|
||||
:mtu 1500
|
||||
: name
|
||||
: running
|
||||
:stamp 857161382
|
||||
: up })
|
||||
|
||||
"when it gets events that don't match the interface, nothing happens"
|
||||
|
||||
(let [gen (-> [(newlink "eth1" "no" "no")] event-generator)]
|
||||
(set upsies [])
|
||||
(ifwait.run [ "-s" "addmember" "dummy0" "up"] #gen)
|
||||
(expect= upsies []))
|
||||
|
||||
"when it gets an event that should start the service, the service starts"
|
||||
|
||||
(let [gen (->
|
||||
[(newlink "dummy0" "no" "no")
|
||||
(newlink "dummy0" "yes" "no")
|
||||
(newlink "eth1" "no" "no")]
|
||||
event-generator)]
|
||||
(set upsies [])
|
||||
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
|
||||
(expect= upsies [:d :u]))
|
||||
|
||||
"when it gets an event that should stop the service, the service stops"
|
||||
|
||||
(let [gen (->
|
||||
[(newlink "dummy0" "no" "no")
|
||||
(newlink "dummy0" "yes" "no")
|
||||
(newlink "dummy0" "no" "no")
|
||||
]
|
||||
event-generator)]
|
||||
(set upsies [])
|
||||
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
|
||||
(expect= upsies [:d :u :d]))
|
||||
|
||||
"it does not call s6-rc again if the service is already in required state"
|
||||
|
||||
(let [gen (->
|
||||
[(newlink "dummy0" "no" "no")
|
||||
(newlink "dummy0" "yes" "no")
|
||||
(newlink "dummy0" "yes" "yes")
|
||||
(newlink "dummy0" "yes" "yes")
|
||||
(newlink "dummy0" "yes" "no")
|
||||
(newlink "dummy0" "no" "no")
|
||||
]
|
||||
event-generator)]
|
||||
(set upsies [])
|
||||
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
|
||||
(expect= upsies [:d :u :d]))
|
||||
|
||||
"it handles an error return from s6-rc"
|
||||
|
||||
(set fake-system
|
||||
(fn [s]
|
||||
(if (s:match "-u change addmember")
|
||||
(table.insert upsies :u)
|
||||
(s:match "-d change addmember")
|
||||
(table.insert upsies :d))
|
||||
(error "false")
|
||||
))
|
||||
|
||||
(let [gen (->
|
||||
[(newlink "dummy0" "yes" "no")
|
||||
(newlink "dummy0" "yes" "yes")
|
||||
(newlink "dummy0" "yes" "yes")
|
||||
(newlink "dummy0" "yes" "no")
|
||||
(newlink "dummy0" "no" "no")
|
||||
]
|
||||
event-generator)]
|
||||
(set upsies [])
|
||||
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
|
||||
(expect= upsies [:u :u :u :u]))
|
||||
@@ -11,7 +11,7 @@ test -n "$contents" && for d in $contents; do
|
||||
touch $out/${name}/contents.d/$d
|
||||
done
|
||||
|
||||
for i in run notification-fd up down consumer-for producer-for pipeline-name ; do
|
||||
for i in timeout-up timeout-down run notification-fd up down consumer-for producer-for pipeline-name ; do
|
||||
test -n "$(printenv $i)" && (echo "$(printenv $i)" > $out/${name}/$i)
|
||||
done
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ let
|
||||
, producer-for ? null
|
||||
, consumer-for ? null
|
||||
, pipeline-name ? null
|
||||
, timeout-up ? 30000 # milliseconds
|
||||
, timeout-down ? 0
|
||||
, dependencies ? []
|
||||
, contents ? []
|
||||
, buildInputs ? []
|
||||
@@ -39,7 +41,7 @@ let
|
||||
# we use stdenvNoCC to avoid generating derivations with names
|
||||
# like foo.service-mips-linux-musl
|
||||
inherit name serviceType up down run notification-fd
|
||||
producer-for consumer-for pipeline-name;
|
||||
producer-for consumer-for pipeline-name timeout-up timeout-down;
|
||||
buildInputs = buildInputs ++ dependencies ++ contents;
|
||||
dependencies = builtins.map (d: d.name) dependencies;
|
||||
contents = builtins.map (d: d.name) contents;
|
||||
@@ -52,6 +54,7 @@ let
|
||||
, outputs ? []
|
||||
, notification-fd ? null
|
||||
, dependencies ? []
|
||||
, buildInputs ? []
|
||||
, ...
|
||||
} @ args:
|
||||
let logger = service {
|
||||
@@ -63,7 +66,7 @@ let
|
||||
pipeline-name = "${name}-pipeline";
|
||||
};
|
||||
in service (args // {
|
||||
buildInputs = [ logger ];
|
||||
buildInputs = buildInputs ++ [ logger ];
|
||||
serviceType = "longrun";
|
||||
run = serviceScript "${run}\n${cleanupScript name}";
|
||||
producer-for = "${name}-log";
|
||||
|
||||
@@ -14,6 +14,7 @@ let
|
||||
cp -av ${src}/target/linux/generic/files/* .
|
||||
chmod -R u+w .
|
||||
cp -av ${src}/target/linux/${family}/files/* .
|
||||
chmod -R u+w .
|
||||
test -d ${src}/target/linux/${family}/files-5.15/ && cp -av ${src}/target/linux/${family}/files-5.15/* .
|
||||
chmod -R u+w .
|
||||
patches() {
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
min-copy-closure = import ./min-copy-closure/test.nix;
|
||||
fennel = import ./fennel/test.nix;
|
||||
tftpboot = import ./tftpboot/test.nix;
|
||||
updown = import ./updown/test.nix;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs.liminix.services) bundle oneshot longrun;
|
||||
inherit (pkgs) serviceFns;
|
||||
# EDIT: you can pick your preferred RFC1918 address space
|
||||
# for NATted connections, if you don't like this one.
|
||||
ipv4LocalNet = "10.8.0";
|
||||
svc = config.system.service;
|
||||
|
||||
in rec {
|
||||
imports = [
|
||||
../../modules/bridge
|
||||
../../modules/dhcp6c
|
||||
../../modules/dnsmasq
|
||||
../../modules/firewall
|
||||
../../modules/hostapd
|
||||
../../modules/network
|
||||
../../modules/ssh
|
||||
../../modules/vlan
|
||||
../../modules/wlan.nix
|
||||
];
|
||||
rootfsType = "jffs2";
|
||||
hostname = "updown";
|
||||
|
||||
services.int = svc.network.address.build {
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
family = "inet"; address = "${ipv4LocalNet}.1"; prefixLength = 16;
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces;
|
||||
[ lan ];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
# users.root = {
|
||||
# # EDIT: choose a root password and then use
|
||||
# # "mkpasswd -m sha512crypt" to determine the hash.
|
||||
# # It should start wirh $6$.
|
||||
# passwd = "$6$6HG7WALLQQY1LQDE$428cnouMJ7wVmyK9.dF1uWs7t0z9ztgp3MHvN5bbeo0M4Kqg/u2ThjoSHIjCEJQlnVpDOaEKcOjXAlIClHWN21";
|
||||
# openssh.authorizedKeys.keys = [
|
||||
# # EDIT: you can add your ssh pubkey here
|
||||
# # "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
|
||||
# ];
|
||||
# };
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
min-collect-garbage
|
||||
# strace
|
||||
# ethtool
|
||||
tcpdump
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
set timeout 10
|
||||
|
||||
spawn socat unix-connect:vm/monitor -
|
||||
set monitor_id $spawn_id
|
||||
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.1 off\n"
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.0 off\n"
|
||||
expect "(qemu)"
|
||||
send "c\r\n"
|
||||
spawn socat unix-connect:vm/console -
|
||||
set console_id $spawn_id
|
||||
|
||||
expect "BusyBox"
|
||||
expect "#" { send "PS1=RE\\ADY_\\ ; stty -echo \r" }
|
||||
expect "READY_" { send "s6-rc -b -a list\r" } ; # -b waits for s6-rc lock
|
||||
expect "READY_" { send "ls /sys/class/net/lan/master\r" }
|
||||
expect {
|
||||
"No such file or directory" { }
|
||||
timeout { exit 1 }
|
||||
}
|
||||
|
||||
expect "READY_" { send "cat /sys/class/net/lan/operstate\r" }
|
||||
expect {
|
||||
"down" { }
|
||||
"up" { exit 1 }
|
||||
}
|
||||
expect "READY_" { send "s6-rc -a -u change\r" }
|
||||
expect {
|
||||
"unable to take locks" { exit 1 }
|
||||
"READY_" { send "\r" }
|
||||
}
|
||||
|
||||
set spawn_id $monitor_id
|
||||
send "\r"
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.1 on\n"
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.0 on\n"
|
||||
expect "(qemu)"
|
||||
set spawn_id $console_id
|
||||
|
||||
expect "entered forwarding state"
|
||||
send "\r"
|
||||
expect "READY_" { send "cat /sys/class/net/lan/operstate\r" }
|
||||
expect {
|
||||
"down" { exit 1 }
|
||||
"up" { }
|
||||
}
|
||||
|
||||
expect "READY_" { send "cat /sys/class/net/lan/master/uevent\r" }
|
||||
expect {
|
||||
"INTERFACE=int" { }
|
||||
timeout { exit 1 }
|
||||
}
|
||||
|
||||
expect "READY_" { send "s6-rc listall int.link.a.10.8.0.1.member.lan.link ; hostname\r" }
|
||||
|
||||
expect {
|
||||
"updown" {}
|
||||
timeout { exit 1 }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
liminix
|
||||
, nixpkgs
|
||||
}:
|
||||
let img = (import liminix {
|
||||
device = import "${liminix}/devices/qemu/";
|
||||
liminix-config = ./configuration.nix;
|
||||
}).outputs.vmroot;
|
||||
pkgs = import <nixpkgs> { overlays = [(import ../../overlay.nix)]; };
|
||||
in pkgs.runCommand "check" {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
expect
|
||||
socat
|
||||
] ;
|
||||
} ''
|
||||
mkdir vm
|
||||
${img}/run.sh --flag -S --background ./vm
|
||||
expect ${./script.expect} | tee $out
|
||||
''
|
||||
Reference in New Issue
Block a user