Compare commits

...

11 Commits

Author SHA1 Message Date
Daniel Barlow b326b685de add o+x permission on service-state directories
this is needed for resolvconf, which writes resolv.conf as
an output and wants to make it world-readable
2023-08-28 20:53:45 +01:00
Daniel Barlow d7209f33c8 TODO comments 2023-08-28 18:24:14 +01:00
Daniel Barlow 999a11f89c pppoe serviceFns 2023-08-28 18:23:48 +01:00
Daniel Barlow 27c9bd9707 rotuer: create resolv.conf 2023-08-28 18:23:32 +01:00
Daniel Barlow 540d2fcf87 default value for services.default
as a default default target, start all the services
2023-08-28 18:22:36 +01:00
Daniel Barlow d1fea06959 update examples so they build again 2023-08-28 16:08:46 +01:00
Daniel Barlow 5e37f2b99a add service fir dhcp v4 client 2023-08-28 15:10:53 +01:00
Daniel Barlow d83f8716ea convert network link/address to module-based-service
... and make bridge use it.

We also had to convert bridge back into a pair of services.
Downstreams want to depend on the bridge it self being configured
even if not necessarily all the members are up. e.g. don't want
to break ssh on lan if there's a misconfigured wlan device
2023-08-28 09:01:07 +01:00
Daniel Barlow 83c451dd8f extract common "interface up" code to a string
so that bridge service can use it
2023-08-28 09:01:07 +01:00
Daniel Barlow fbec31be79 more thoughts 2023-08-28 09:01:07 +01:00
Daniel Barlow 6ae062f5e4 remove interface.device
build-time uses can mostly be replaced with interface.name

for runtime uses, switch to $(output ${interface} name)
2023-08-28 09:01:07 +01:00
24 changed files with 397 additions and 138 deletions

View File

@ -2039,4 +2039,75 @@ to finish service/modules milestone
- anything else in rotuer.nix that we should servicify
- services for liminix.networking
- a nice way to specify service dependencies
- do another video
[done] - do another video
Mon Aug 21 20:02:55 BST 2023
a nice way to do dependencies would be somethng like
services.thething =
let s = svc.thing { .... };
in addDependencies s (with config.services; [otherthing yetanother]);
except that addDependencies is a really klunky name. dependsOn is very
slightly better? or maybe it could be a function of the derivation?
services.thething =
svc.thing { .... }.depends (with config.services; [otherthing yetanother]);
---
what does it mean to be dependent on an interface? that's it up? running?
has an address? has a collection of addresses?
services.defaultroute4 = route {
name = "defaultroute4";
via = "$(output ${services.wan} address)";
target = "default";
dependencies = [ services.wan ];
};
- this route requires the interface to have an address (if wan is an
interface, anyway ...)
- but otoh a dhcp client doesn't want to wait for an address, because
it is assigning the address.
should an address provider have "interface name" as an output?
is there a set of outputs that every address provider should have -
whether static, dhcp, pppoe?
maybe we're in decision paralysis and should just move forward with
what we know
Wed Aug 23 18:56:08 BST 2023
We may want to change the hardware device files to specify network
interface names not services. Otherwise hardware devices (boards)
depend on module-based-services, which is a bit weird.
Thu Aug 24 18:54:03 BST 2023
- we want network and bridge to be separate modules, because bridge
introduces extra kernel config
- bridge/service wants to create a network device ("ip link"),
using quite similar code as network/link.nix
- but bridge/service is a derivation: it has sight of pkgs but not
config
https://www.skarnet.org/software/s6-rc/faq.html
Fri Aug 25 23:37:57 BST 2023
where we left off: bridge is a bundle, and bundles can't have outputs,
so how do we set the ifname of the bridge?
- ifname of the primary is set
- actually, most things that depend on the bridge really just depend
on the primary anyway (it's OK if 1 <= n < #members are down)
- but *something* should depeond on all the members
turns out maybe we needed two services after all?

View File

@ -80,6 +80,8 @@
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) interface;
in {
imports = [ ../../modules/network];
programs.busybox.options = {
FEATURE_DD_IBS_OBS = "y"; # ath10k_cal_data needs skip_bytes,fullblock
};
@ -100,19 +102,20 @@
];
};
networkInterfaces = {
lan = interface { device = "eth0"; };
wan = interface { device = "eth1"; };
wlan_24 = interface {
device = "wlan0";
dependencies = [ mac80211 ];
networkInterfaces =
let inherit (config.system.service.network) link;
in {
lan = link.build { ifname = "eth0"; };
wan = link.build { ifname = "eth1"; };
wlan_24 = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
wlan_5 = link.build {
ifname = "wlan1";
dependencies = [ mac80211 ath10k_cal_data ];
};
};
wlan_5 = interface {
device = "wlan1";
dependencies = [ mac80211 ath10k_cal_data ];
};
};
};
filesystem = dir {
lib = dir {

View File

@ -13,14 +13,12 @@
secrets = import ./extneder-secrets.nix;
inherit
(pkgs.liminix.networking)
address
udhcpc
interface
route
;
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) writeText dropbear ifwait serviceFns;
svc = config.system.service;
in rec {
boot = {
tftp = {
@ -32,6 +30,7 @@ in rec {
imports = [
../modules/standard.nix
../modules/wlan.nix
../modules/network
];
hostname = "arhcive";
@ -62,10 +61,11 @@ in rec {
};
services.dhcpc =
let iface = config.hardware.networkInterfaces.lan;
in (udhcpc iface {
let iface = config.hardware.networkInterfaces.lan;
in svc.network.dhcp.client.build {
interface = iface;
dependencies = [ config.services.hostname ];
}) // { inherit (iface) device; };
};
services.sshd = longrun {
name = "sshd";

View File

@ -14,15 +14,13 @@
inherit
(pkgs.liminix.networking)
address
udhcpc
hostapd
interface
route
;
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) dropbear ifwait serviceFns
;
inherit (pkgs) dropbear ifwait serviceFns;
svc = config.system.service;
in rec {
boot = {
tftp = {
@ -33,6 +31,9 @@ in rec {
imports = [
../modules/wlan.nix
../modules/network
../modules/hostapd
../modules/bridge
../modules/standard.nix
];
@ -71,7 +72,8 @@ in rec {
};
};
services.hostap = hostapd (config.hardware.networkInterfaces.wlan) {
services.hostap = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan;
params = {
country_code = "GB";
hw_mode = "g";
@ -91,28 +93,18 @@ in rec {
device = "int";
};
services.dhcpc = (udhcpc services.int {
services.dhcpc = svc.network.dhcp.client.build {
interface = services.int;
dependencies = [ config.services.hostname ];
}) // { device = "int"; };
};
services.bridge = let
services.bridge = svc.bridge.members.build {
primary = services.int;
addif = dev:
oneshot {
name = "add-${dev.device}-to-bridge";
up = "${ifwait}/bin/ifwait -v ${dev.device} running && ip link set dev ${dev.device} master ${primary.device}";
down = "ip link set dev ${dev} nomaster";
dependencies = [primary dev];
};
in
bundle {
name = "bridge-members";
contents = with config.hardware.networkInterfaces;
map addif [
lan
wlan
];
};
members = with config.hardware.networkInterfaces; [
lan
wlan
];
};
services.sshd = longrun {
name = "sshd";

View File

@ -44,6 +44,7 @@ in rec {
imports = [
../modules/wlan.nix
../modules/standard.nix
../modules/network
../modules/ppp
../modules/dnsmasq
../modules/firewall
@ -78,16 +79,16 @@ in rec {
} // wirelessConfig;
};
services.int =
let iface = svc.bridge.build {
ifname = "int";
members = with config.hardware.networkInterfaces; [
wlan_24 lan wlan_5
];
};
in address iface {
family = "inet4"; address ="10.8.0.1"; prefixLength = 16;
};
services.int = svc.network.address.build {
interface = svc.bridge.primary.build { ifname = "int"; };# services.int;
family = "inet"; address ="10.8.0.1"; prefixLength = 16;
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces;
[ wlan_24 wlan_5 lan ];
};
services.ntp = svc.ntp.build {
pools = { "pool.ntp.org" = ["iburst"]; };
@ -105,7 +106,7 @@ in rec {
inherit interface;
ranges = [
"10.8.0.10,10.8.0.240"
"::,constructor:${interface.device},ra-stateless"
"::,constructor:$(output ${interface} ifname),ra-stateless"
];
domain = "fake.liminix.org";
};
@ -127,12 +128,21 @@ in rec {
( in_outputs ${name}
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
chmod 0444 resolv.conf
)
'';
down = ''
rm -rf /run/service-state/${name}/
'';
};
filesystem =
let inherit (pkgs.pseudofile) dir symlink;
in dir {
etc = dir {
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
};
};
services.defaultroute4 = route {
name = "defaultroute4";
@ -188,7 +198,7 @@ in rec {
let script = pkgs.callPackage ./acquire-delegated-prefix.nix { };
in longrun {
name = "acquire-lan-prefix";
run = "${script} /run/service-state/dhcp6c.wan ${services.int.device}";
run = "${script} /run/service-state/dhcp6c.wan $(output ${services.int} ifname)";
dependencies = [ services.dhcp6 ];
};
@ -200,27 +210,6 @@ in rec {
dependencies = [ services.dhcp6 ];
};
services.default = target {
name = "default";
contents = with config.services; [
config.hardware.networkInterfaces.lo
config.hardware.networkInterfaces.lan
int
hostap
hostap5
ntp
defaultroute4
defaultroute6
packet_forwarding
dns
resolvconf
sshd
config.services.hostname
dhcp6
acquire-lan-prefix
acquire-wan-address
];
};
defaultProfile.packages = with pkgs; [
min-collect-garbage
];

View File

@ -15,21 +15,29 @@ let
in
{
options = {
system.service.bridge = mkOption {
type = liminix.lib.types.serviceDefn;
system.service.bridge = {
primary = mkOption { type = liminix.lib.types.serviceDefn; };
members = mkOption { type = liminix.lib.types.serviceDefn; };
};
};
config.system.service = {
bridge = liminix.callService ./service.nix {
members = mkOption {
type = types.listOf liminix.lib.types.service;
description = "interfaces to add to the bridge";
};
config.system.service.bridge = {
primary = liminix.callService ./primary.nix {
ifname = mkOption {
type = types.str;
description = "bridge interface name to create";
};
};
members = liminix.callService ./members.nix {
primary = mkOption {
type = liminix.lib.types.interface;
description = "primary bridge interface";
};
members = mkOption {
type = types.listOf liminix.lib.types.interface;
description = "interfaces to add to the bridge";
};
};
};
config.kernel.config.BRIDGE = "y";
}

View File

@ -0,0 +1,25 @@
{
liminix
, ifwait
, lib
}:
{ members, primary } :
let
inherit (liminix.networking) interface;
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";
dependencies = [ primary member ];
};
in bundle {
name = "${primary.name}.members";
contents = map addif members;
}

View File

@ -0,0 +1,18 @@
{
liminix
, ifwait
, lib
}:
{ ifname } :
let
inherit (liminix.networking) interface;
inherit (liminix.services) bundle oneshot;
inherit (lib) mkOption types;
in oneshot rec {
name = "${ifname}.link";
up = ''
ip link add name ${ifname} type bridge
${liminix.networking.ifup name ifname}
'';
down = "ip link set down dev ${ifname}";
}

View File

@ -1,26 +0,0 @@
{
liminix
, ifwait
, lib
}:
{ members, ifname } :
let
inherit (liminix.networking) interface;
inherit (liminix.services) bundle oneshot;
inherit (lib) mkOption types;
primary = interface {
device = ifname;
type = "bridge";
};
addif = member :
oneshot {
name = "add-${member.device}-to-br-${primary.device}";
up = "${ifwait}/bin/ifwait ${member.device} running && ip link set dev ${member.device} master ${primary.device}";
down = "ip link set dev ${member.device} nomaster";
dependencies = [ primary member ];
};
in (bundle {
name = "bridge-${primary.device}-members";
contents = [ primary ] ++ map addif members;
}) // { device = primary.device; }

View File

@ -14,7 +14,7 @@
, resolvconf
}:
let
name = "${interface.device}.dnsmasq";
name = "${interface.name}.dnsmasq";
inherit (liminix.services) longrun;
inherit (lib) concatStringsSep;
in
@ -27,7 +27,7 @@ longrun {
--user=${user} \
--domain=${domain} \
--group=${group} \
--interface=${interface.device} \
--interface=$(output ${interface} ifname) \
${lib.concatStringsSep " " (builtins.map (r: "--dhcp-range=${r}") ranges)} \
${lib.concatStringsSep " " (builtins.map (r: "--server=${r}") upstreams)} \
--keep-in-foreground \

View File

@ -54,6 +54,7 @@ in {
'';
flashimage =
let o = config.system.outputs; in
# could use trivial-builders.linkFarmFromDrvs here?
pkgs.runCommand "flashimage" {} ''
mkdir $out
cd $out

View File

@ -17,7 +17,7 @@ let
# we'll add them as top-level attributes and rename params to
# extraParams
name = "${interface.device}.hostapd";
name = "${interface.name}.hostapd";
defaults = {
driver = "nl80211";
logger_syslog = "-1";
@ -35,5 +35,5 @@ let
in longrun {
inherit name;
dependencies = [ interface ];
run = "${hostapd}/bin/hostapd -i ${interface.device} -P /run/${name}.pid -S ${conf}";
run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}";
}

View File

@ -0,0 +1,29 @@
{
liminix
, ifwait
, serviceFns
, lib
}:
{interface, family, address, prefixLength} :
let
inherit (liminix.services) oneshot;
# rather depending on the assumption that nobody will
# ever add two addresses which are the same but with different
# prefixes, or the same but different protocols
name = "${interface.name}.a.${address}";
up = ''
. ${serviceFns}
dev=$(output ${interface} ifname)
ip address add ${address}/${toString prefixLength} dev $dev
(in_outputs ${name}
echo ${address} > address
echo ${toString prefixLength} > prefix-length
echo ${family} > family
echo $dev > ifname
)
'';
in oneshot {
inherit name up;
down = "true"; # this has been broken for ~ ages
dependencies = [ interface ];
}

View File

@ -0,0 +1,68 @@
## Network
## =======
##
## Basic network services for creating hardware ethernet devices
## and adding addresses
{ lib, pkgs, config, ...}:
let
inherit (lib) mkOption types;
inherit (pkgs) liminix;
in {
options = {
system.service.network = {
link = mkOption {
description = "hardware network interface";
type = liminix.lib.types.serviceDefn;
};
address = mkOption {
description = "network interface address";
type = liminix.lib.types.serviceDefn;
};
dhcp = {
client = mkOption {
# this needs to move to its own service as it has
# busybox config
description = "DHCP v4 client";
type = liminix.lib.types.serviceDefn;
};
};
};
};
config = {
system.service.network = {
link = liminix.callService ./link.nix {
ifname = mkOption {
type = types.str;
example = "eth0";
};
# other "ip link add" options could go here as well
mtu = mkOption {
type = types.nullOr types.int;
example = 1480;
};
};
address = liminix.callService ./address.nix {
interface = mkOption {
type = liminix.lib.types.service;
};
family = mkOption {
type = types.enum [ "inet" "inet6" ];
};
address = mkOption {
type = types.str;
};
prefixLength = mkOption {
type = types.ints.between 0 128;
};
};
dhcp.client = liminix.callService ./dhcpc.nix {
interface = mkOption {
type = liminix.lib.types.service;
};
};
};
};
}

46
modules/network/dhcpc.nix Normal file
View File

@ -0,0 +1,46 @@
{
liminix
, writeAshScript
, serviceFns
, lib
} :
{ interface }:
let
inherit (liminix.services) longrun;
name = "${interface.name}.dhcpc";
script = writeAshScript "dhcp-notify" { } ''
. ${serviceFns}
exec 2>&1
action=$1
set_address() {
ip address replace $ip/$mask dev $interface
(in_outputs ${name}
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
printenv $i > $i
done)
}
case $action in
deconfig)
ip address flush $interface
ip link set up dev $interface
;;
bound)
# this doesn't actually replace, it adds a new address.
set_address
echo >/proc/self/fd/10
;;
renew)
set_address
;;
nak)
echo "received NAK on $interface"
;;
esac
'';
in longrun {
inherit name;
run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}";
notification-fd = 10;
dependencies = [ interface ];
}

16
modules/network/link.nix Normal file
View File

@ -0,0 +1,16 @@
{
liminix
, ifwait
, serviceFns
, lib
}:
{ifname, mtu} :
let
inherit (liminix.services) longrun oneshot;
inherit (lib) concatStringsSep;
name = "${ifname}.link";
up = liminix.networking.ifup name ifname;
in oneshot {
inherit name up;
down = "ip link set down dev ${ifname}";
}

View File

@ -22,7 +22,7 @@ let
++
(mapAttrsToList (name: opts: "peer ${name} ${concatStringsSep "" opts}")
p.peers)
++ [ "user ${p.user}" ]
++ lib.optional (p.user != null) "user ${p.user}"
++ (lib.optional (p.makestep != null) "makestep ${toString p.makestep.threshold} ${toString p.makestep.limit}")
++ (map (n: "allow ${n}") p.allow)
++ (lib.optional (p.bindaddress != null) "bindaddress ${p.bindaddress}")

View File

@ -75,6 +75,7 @@ in
inherit kernel;
inherit dtb;
};
# could use trivial-builders.linkFarmFromDrvs here?
vmroot = pkgs.runCommand "qemu" {} ''
mkdir $out
cd $out

View File

@ -9,7 +9,7 @@
{ interface, ppp-options }:
let
inherit (liminix.services) longrun;
name = "${interface.device}.pppoe";
name = "${interface.name}.pppoe";
ip-up = writeAshScript "ip-up" {} ''
. ${serviceFns}
(in_outputs ${name}
@ -42,7 +42,10 @@ let
in
longrun {
inherit name;
run = "${ppp}/bin/pppd pty '${pppoe}/bin/pppoe -I ${interface.device}' ${lib.concatStringsSep " " ppp-options'}" ;
run = ''
. ${serviceFns}
${ppp}/bin/pppd pty "${pppoe}/bin/pppoe -I $(output ${interface} ifname)" ${lib.concatStringsSep " " ppp-options'}
'';
notification-fd = 10;
dependencies = [ interface ];
}

View File

@ -7,10 +7,21 @@ let
s6-linux-init
stdenvNoCC;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.services) bundle;
s6-rc-db = pkgs.s6-rc-database.override {
services = builtins.attrValues config.services;
};
s6-rc-db =
let
defaultDefaultTarget = bundle {
name = "default";
contents = builtins.attrValues config.services;
};
servicesAttrs = {
default = defaultDefaultTarget;
} // config.services;
in
pkgs.s6-rc-database.override {
services = builtins.attrValues servicesAttrs;
};
s6-init-scripts = stdenvNoCC.mkDerivation {
name = "s6-scripts";
src = ./scripts;

View File

@ -21,7 +21,7 @@ mount -t sysfs none /sys
mkdir /dev/pts
mount -t devpts none /dev/pts
mkdir -m 0750 /run/service-state
mkdir -m 0751 /run/service-state
chgrp system /run/service-state
### If your services are managed by s6-rc:

View File

@ -3,10 +3,20 @@
, liminix
, ifwait
, lib
, serviceFns
}:
let
inherit (liminix.services) oneshot longrun;
inherit (lib) concatStringsSep optional;
ifup = name : ifname : ''
. ${serviceFns}
${ifwait}/bin/ifwait -v ${ifname} present
ip link set up dev ${ifname}
(in_outputs ${name}
echo ${ifname} > ifname
)
'';
in {
interface = { type ? "hardware", device, link ? null, primary ? null, id ? null, dependencies ? [] } @ args:
let name = "${device}.link";
@ -16,8 +26,7 @@ in {
"ip link add name ${device} type bridge"
++ optional (type == "vlan")
"ip link add link ${link} name ${device} type vlan id ${id}"
++ ["${ifwait}/bin/ifwait -v ${device} present"]
++ ["ip link set up dev ${device}"]
++ [(ifup name device)]
++ optional (primary != null)
"ip link set dev ${device} master ${primary.device}";
in oneshot {
@ -25,18 +34,15 @@ in {
up = lib.concatStringsSep "\n" ups;
down = "ip link set down dev ${device}";
dependencies = dependencies ++ lib.optional (primary != null) primary;
} // {
inherit device;
};
inherit ifup;
address = interface: { family, dependencies ? [], prefixLength, address } @ args:
let inherit (builtins) toString;
in oneshot {
dependencies = [ interface ] ++ dependencies;
name = "${interface.device}.addr.${address}";
up = "ip address add ${address}/${toString prefixLength} dev ${interface.device} ";
down = "ip address del ${address}/${toString prefixLength} dev ${interface.device} ";
} // {
inherit (interface) device;
name = "${interface.name}.addr.${address}";
up = "ip address add ${address}/${toString prefixLength} dev $(output ${interface} ifname)";
down = "ip address del ${address}/${toString prefixLength} dev $(output ${interface} ifname)";
};
route = { name, target, via, dependencies, dev ? null }:
let with_dev = if dev != null then "dev ${dev}" else "";

View File

@ -4,7 +4,7 @@ writeText "service-fns.sh" ''
output_path() { echo $(realpath $1/.outputs)/$2; }
mkoutputs() {
d=/run/service-state/$1
mkdir -m 2750 -p $d && chown root:system $d
mkdir -m 2751 -p $d && chown root:system $d
echo $d
}
in_outputs() {

View File

@ -1,23 +1,21 @@
{ config, pkgs, ... } :
let
inherit (pkgs.liminix.networking) interface address udhcpc odhcpc route;
inherit (pkgs.liminix.networking) interface address route;
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs) writeText;
svc = config.system.service;
in rec {
imports = [
./modules/tftpboot.nix
./modules/wlan.nix
./modules/network
./modules/ntp
];
services.loopback = config.hardware.networkInterfaces.lo;
services.dhcpv4 =
let iface = interface { type = "hardware"; device = "eth1"; };
in udhcpc iface {};
services.dhcpv6 =
let iface = interface { type = "hardware"; device = "eth1"; };
in odhcpc iface { uid = "e7"; };
let iface = svc.network.link.build { ifname = "eth1"; };
in svc.network.dhcp.client.build { interface = iface; };
services.defaultroute4 = route {
name = "defautlrote";