Compare commits

..

11 Commits

Author SHA1 Message Date
Daniel Barlow e1ae986cf6 convert l2tp example to use gateway profile 2024-07-23 09:31:34 +01:00
Daniel Barlow bce0c7ffb6 rename services.dhcpc in l2tp example
it's only used to get the address of the l2tp server, not for
name lookups in general
2024-07-23 09:31:34 +01:00
Daniel Barlow 28ca1e68ab wwan module needs mdevd 2024-07-23 09:31:34 +01:00
Daniel Barlow acf33a100f think 2024-07-23 09:31:34 +01:00
Daniel Barlow 7f9cae9d5c generalise profile.gateway.wan so not just pppoe 2024-07-23 09:31:34 +01:00
Daniel Barlow 3012c91b47 executive decision: rotuer example should build on gl-ar750 2024-07-23 09:31:34 +01:00
Daniel Barlow 1edf20c08f fix whitespace 2024-07-23 09:31:34 +01:00
Daniel Barlow 7195cb10ce add structured config for common pppoe options 2024-07-23 09:31:34 +01:00
Daniel Barlow 135a445672 restore param removed by deadnix
dochain is called with `family` even if it never uses it
2024-07-16 20:41:21 +01:00
Daniel Barlow 3899daee56 create a module for round-robin 2024-07-15 22:37:37 +01:00
Daniel Barlow b17f623d03 need insmod when we habve kmodloader 2024-07-15 22:35:26 +01:00
12 changed files with 274 additions and 134 deletions

12
NEWS
View File

@ -103,3 +103,15 @@ a bit more useful :-)
}; };
}) })
]; ];
2024-07-16
* structured parameters are available for the pppoe service
* The "wan" configuration in modules/profiles/gateway.nix has changed:
instead of passing options that are used to create a pppoe interface,
callers should create a (pppoe or other) interface and pass that as
the value of profile.gateway.wan. For the pppoe case this is now only
very slightly more verbose, and it allows using the gateway profile
with other kinds of upstream.

View File

@ -5400,3 +5400,24 @@ generalising the failover example:
- usb stick may or may not need a modeswitch - usb stick may or may not need a modeswitch
- may need a different chat script - may need a different chat script
- usb ids - usb ids
Mon Jul 15 17:52:57 BST 2024
DONE 1) Should round-robin be a callService service or a function a la
longrun/oneshot, or even an overridable package?
DONE 2) maybe we should replace all liminix.callService with
config.system.callService
3) for consistency, can we make the networking "primitives" into
services? answer: no. the only thing left there is `ifup` which is a
function returning a string, not a derivation
Tue Jul 16 18:25:41 BST 2024
can we make the gateway profile able to use failover? perhaps if we
add username and password as options to the pppoe service, then call
gateway with the pppoe service instead of building it _in_ the profile,
we can have gateways with other-than-pppoe for the wan
(for a straight lte uplink, could pass the wwan interface as wan)

View File

@ -30,6 +30,11 @@
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) serviceFns; inherit (pkgs) serviceFns;
svc = config.system.service; svc = config.system.service;
wirelessConfig = {
country_code = "GB";
inherit (rsecrets) wpa_passphrase;
wmm_enabled = 1;
};
in rec { in rec {
boot = { boot = {
tftp = { tftp = {
@ -41,12 +46,14 @@ in rec {
imports = [ imports = [
../modules/wwan ../modules/wwan
../modules/network ../modules/network
../modules/vlan # ../modules/vlan
../modules/ssh ../modules/ssh
../modules/usb.nix ../modules/usb.nix
../modules/watchdog # ../modules/watchdog
../modules/mount # ../modules/mount
../modules/ppp ../modules/ppp
../modules/round-robin
../modules/profiles/gateway.nix
]; ];
hostname = "thing"; hostname = "thing";
@ -57,41 +64,49 @@ in rec {
authType = "chap"; authType = "chap";
}; };
services.wan = profile.gateway = {
let lan = {
controller = longrun rec { interfaces = with config.hardware.networkInterfaces;
name = "wan-switcher"; [
run = '' # EDIT: these are the interfaces exposed by the gl.inet gl-ar750:
in_outputs ${name} # if your device has more or differently named lan interfaces,
exec ${pkgs.s6-rc-round-robin}/bin/s6-rc-round-robin \ # specify them here
-p ${proxy.name} \ wlan wlan5
${lib.concatStringsSep " " lan
(builtins.map (f: f.name) [pppoe l2tp])}
'';
};
pppoe = (svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
ppp-options = [
"debug" "+ipv6" "noauth"
"name" rsecrets.l2tp.name
"password" rsecrets.l2tp.password
]; ];
}).overrideAttrs(o: { inherit controller; }); inherit (rsecrets.lan) prefix;
address = {
family = "inet"; address ="${rsecrets.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 = let
pppoe = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
debug = true;
username = rsecrets.l2tp.name;
password = rsecrets.l2tp.password;
};
l2tp = l2tp =
let let
check-address = oneshot rec { check-address = oneshot rec {
name = "check-lns-address"; name = "check-lns-address";
up = "grep -Fx ${ lns.address} $(output_path ${services.lns-address} addresses)"; up = "grep -Fx ${lns.address} $(output_path ${services.lns-address} addresses)";
dependencies = [ services.lns-address ]; dependencies = [ services.lns-address ];
}; };
route = svc.network.route.build { route = svc.network.route.build {
via = "$(output ${services.dhcpc} router)"; via = "$(output ${services.bootstrap-dhcpc} router)";
target = lns.address; target = lns.address;
dependencies = [services.dhcpc check-address]; dependencies = [services.bootstrap-dhcpc check-address];
}; };
in (svc.l2tp.build { in svc.l2tp.build {
lns = lns.address; lns = lns.address;
ppp-options = [ ppp-options = [
"debug" "+ipv6" "noauth" "debug" "+ipv6" "noauth"
@ -99,57 +114,51 @@ in rec {
"password" rsecrets.l2tp.password "password" rsecrets.l2tp.password
]; ];
dependencies = [config.services.lns-address route check-address]; dependencies = [config.services.lns-address route check-address];
}).overrideAttrs(o: { inherit controller; });
proxy = oneshot rec {
name = "wan-proxy";
inherit controller;
buildInputs = [ pppoe l2tp];
up = ''
echo start proxy ${name}
set -x
(in_outputs ${name}
cp -rv $(output_path ${controller} active)/* .
)
'';
}; };
in proxy; in svc.round-robin.build {
name = "wan";
services.sshd = svc.ssh.build { }; services = [ l2tp pppoe ];
services.resolvconf = oneshot rec {
dependencies = [ services.wan ];
name = "resolvconf";
up = ''
. ${serviceFns}
( in_outputs ${name}
for i in ns1 ns2 ; do
ns=$(output ${services.wan} $i)
echo "nameserver $ns" >> resolv.conf
done
)
'';
}; };
filesystem = dir { dhcp6.enable = true;
etc = dir { };
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
wireless.networks = {
"${rsecrets.ssid}" = {
interface = config.hardware.networkInterfaces.wlan;
hw_mode = "g";
channel = "6";
ieee80211n = 1;
} // wirelessConfig;
"${rsecrets.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.dhcpc = svc.network.dhcp.client.build { services.bootstrap-dhcpc = svc.network.dhcp.client.build {
interface = config.services.wwan; interface = config.services.wwan;
dependencies = [ config.services.hostname ]; dependencies = [ config.services.hostname ];
}; };
services.sshd = svc.ssh.build { };
services.lns-address = let services.lns-address = let
ns = "$(output_word ${services.dhcpc} dns 1)"; ns = "$(output_word ${services.bootstrap-dhcpc} dns 1)";
route-to-bootstrap-nameserver = svc.network.route.build { route-to-bootstrap-nameserver = svc.network.route.build {
via = "$(output ${services.dhcpc} router)"; via = "$(output ${services.bootstrap-dhcpc} router)";
target = ns; target = ns;
dependencies = [services.dhcpc]; dependencies = [services.bootstrap-dhcpc];
}; };
in oneshot rec { in oneshot rec {
name = "resolve-l2tp-server"; name = "resolve-l2tp-server";
dependencies = [ services.dhcpc route-to-bootstrap-nameserver ]; dependencies = [ services.bootstrap-dhcpc route-to-bootstrap-nameserver ];
up = '' up = ''
(in_outputs ${name} (in_outputs ${name}
DNSCACHEIP="${ns}" ${pkgs.s6-dns}/bin/s6-dnsip4 ${lns.hostname} \ DNSCACHEIP="${ns}" ${pkgs.s6-dns}/bin/s6-dnsip4 ${lns.hostname} \
@ -158,18 +167,13 @@ in rec {
''; '';
}; };
services.defaultroute4 = svc.network.route.build { # services.ntp = svc.ntp.build {
via = "$(output ${services.wan} peer-address)"; # pools = { "pool.ntp.org" = ["iburst"]; };
target = "default"; # makestep = { threshold = 1.0; limit = 3; };
dependencies = [services.wan]; # dependencies = with config.services; [ defaultroute4 defaultroute6 ];
}; # };
# defaultProfile.packages = [ pkgs.go-l2tp ]; users.root = rsecrets.root;
users.root = {
passwd = lib.mkForce secrets.root.passwd;
openssh.authorizedKeys.keys = secrets.root.keys;
};
programs.busybox.options = { programs.busybox.options = {
FEATURE_FANCY_TAIL = "y"; FEATURE_FANCY_TAIL = "y";

View File

@ -1,10 +1,8 @@
# This is not part of Liminix per se. This is my "scratchpad" # This is an example that uses the "gateway" profile to create a
# configuration for the device I'm testing with. # "typical home wireless router" configuration suitable for a Gl.inet
# # gl-ar750 router. It should be fairly simple to edit it for other
# Parts of it do do things that Liminix eventually needs to do, but # devices: mostly you will need to attend to the number of wlan and lan
# don't look in here for solutions - just for identifying the # interfaces
# problems.
{ config, pkgs, lib, modulesPath, ... } : { config, pkgs, lib, modulesPath, ... } :
let let
@ -30,21 +28,18 @@ in rec {
imports = [ imports = [
"${modulesPath}/profiles/gateway.nix" "${modulesPath}/profiles/gateway.nix"
"${modulesPath}/schnapps"
"${modulesPath}/outputs/btrfs.nix"
"${modulesPath}/outputs/extlinux.nix"
]; ];
hostname = "rotuer"; hostname = "rotuer";
rootfsType = "btrfs";
rootOptions = "subvol=@";
boot.loader.extlinux.enable = true;
profile.gateway = { profile.gateway = {
lan = { lan = {
interfaces = with config.hardware.networkInterfaces; interfaces = with config.hardware.networkInterfaces;
[ [
# EDIT: these are the interfaces exposed by the gl.inet gl-ar750:
# if your device has more or differently named lan interfaces,
# specify them here
wlan wlan5 wlan wlan5
lan0 lan1 lan2 lan3 lan4 lan
]; ];
inherit (secrets.lan) prefix; inherit (secrets.lan) prefix;
address = { address = {
@ -58,9 +53,17 @@ in rec {
}; };
}; };
wan = { wan = {
# wan interface depends on your upstream - could be dhcp, static
# ethernet, a pppoe, ppp over serial, a complicated bonded
# failover ... who knows what else?
interface = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan; interface = config.hardware.networkInterfaces.wan;
username = secrets.l2tp.name; username = secrets.l2tp.name;
password = secrets.l2tp.password; password = secrets.l2tp.password;
};
# once the wan has ipv4 connnectivity, should we run dhcp6
# client to potentially get an address range ("prefix
# delegation")
dhcp6.enable = true; dhcp6.enable = true;
}; };
firewall = { firewall = {
@ -68,6 +71,10 @@ in rec {
rules = secrets.firewallRules; rules = secrets.firewallRules;
}; };
wireless.networks = { wireless.networks = {
# EDIT: if you have more or fewer wireless radios, here is where
# you need to say so. hostapd tuning is hardware-specific and
# left as an exercise for the reader :-).
"${secrets.ssid}" = { "${secrets.ssid}" = {
interface = config.hardware.networkInterfaces.wlan; interface = config.hardware.networkInterfaces.wlan;
hw_mode = "g"; hw_mode = "g";

View File

@ -12,6 +12,8 @@
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs) liminix; inherit (pkgs) liminix;
mkStringOption =
description: mkOption { type = types.str; inherit description; };
in { in {
options = { options = {
system.service.pppoe = mkOption { system.service.pppoe = mkOption {
@ -27,9 +29,34 @@ in {
type = liminix.lib.types.service; type = liminix.lib.types.service;
description = "ethernet interface to run PPPoE over"; description = "ethernet interface to run PPPoE over";
}; };
username = mkStringOption "username";
password = mkStringOption "password";
lcpEcho = {
adaptive = mkOption {
description = "send LCP echo-request frames only if no traffic was received from the peer since the last echo-request was sent";
type = types.bool;
default = true;
};
interval = mkOption {
type = types.nullOr types.int;
default = 3;
description = "send an LCP echo-request frame to the peer every n seconds";
};
failure = mkOption {
type = types.nullOr types.int;
default = 3;
description = "terminate connection if n LCP echo-requests are sent without receiving a valid LCP echo-reply";
};
};
debug = mkOption {
description = "log the contents of all control packets sent or received";
default = false;
type = types.bool;
};
ppp-options = mkOption { ppp-options = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
description = "options supplied on ppp command line"; description = "options supplied on ppp command line";
default = [];
}; };
}; };
system.service.l2tp = config.system.callService ./l2tp.nix { system.service.l2tp = config.system.callService ./l2tp.nix {

View File

@ -6,11 +6,16 @@
, writeAshScript , writeAshScript
, serviceFns , serviceFns
} : } :
{ interface, ppp-options }: { interface,
ppp-options,
lcpEcho,
username,
password,
debug
}:
let let
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
lcp-echo-interval = 4; inherit (lib) optional optionals;
lcp-echo-failure = 3;
name = "${interface.name}.pppoe"; name = "${interface.name}.pppoe";
ip-up = writeAshScript "ip-up" {} '' ip-up = writeAshScript "ip-up" {} ''
. ${serviceFns} . ${serviceFns}
@ -33,25 +38,35 @@ let
) )
echo >/proc/self/fd/10 echo >/proc/self/fd/10
''; '';
ppp-options' = ppp-options ++ [ ppp-options' = ["+ipv6" "noauth"]
"ip-up-script" ip-up ++ optional debug "debug"
++ optionals (username != null) ["name" username]
++ optionals (password != null) ["password" password]
++ optional lcpEcho.adaptive "lcp-echo-adaptive"
++ optionals (lcpEcho.interval != null)
["lcp-echo-interval" (builtins.toString lcpEcho.interval)]
++ optionals (lcpEcho.failure != null)
["lcp-echo-failure" (builtins.toString lcpEcho.failure)]
++ ppp-options
++ ["ip-up-script" ip-up
"ipv6-up-script" ip6-up "ipv6-up-script" ip6-up
"ipparam" name "ipparam" name
"nodetach" "nodetach"
"usepeerdns" "usepeerdns"
"lcp-echo-interval" (builtins.toString lcp-echo-interval)
"lcp-echo-failure" (builtins.toString lcp-echo-failure)
"logfd" "2" "logfd" "2"
]; ];
timeoutOpt = if lcpEcho.interval != null then "-T ${builtins.toString (4 * lcpEcho.interval)}" else "";
in in
longrun { longrun {
inherit name; inherit name;
run = '' run = ''
. ${serviceFns} . ${serviceFns}
echo Starting pppoe, pppd pid is $$ echo Starting pppoe, pppd pid is $$
exec ${ppp}/bin/pppd pty "${pppoe}/bin/pppoe -T ${builtins.toString (4 * lcp-echo-interval)} -I $(output ${interface} ifname)" ${lib.concatStringsSep " " ppp-options'} exec ${ppp}/bin/pppd pty "${pppoe}/bin/pppoe ${timeoutOpt} -I $(output ${interface} ifname)" ${lib.concatStringsSep " " ppp-options'}
''; '';
notification-fd = 10; notification-fd = 10;
timeout-up = (10 + lcp-echo-failure * lcp-echo-interval) * 1000; timeout-up = if lcpEcho.failure != null
then (10 + lcpEcho.failure * lcpEcho.interval) * 1000
else 60 * 1000;
dependencies = [ interface ]; dependencies = [ interface ];
} }

View File

@ -52,8 +52,6 @@ in {
wan = { wan = {
interface = mkOption { type = liminix.lib.types.interface; }; interface = mkOption { type = liminix.lib.types.interface; };
username = mkOption { type = types.str; };
password = mkOption { type = types.str; };
dhcp6.enable = mkOption { type = types.bool; }; dhcp6.enable = mkOption { type = types.bool; };
}; };
@ -86,14 +84,7 @@ in {
members = cfg.lan.interfaces; members = cfg.lan.interfaces;
}; };
services.wan = svc.pppoe.build { services.wan = cfg.wan.interface;
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.packet_forwarding = svc.network.forward.build { };

View File

@ -0,0 +1,27 @@
## Round Robin
##
## Given a list of services, run each in turn until it exits, then
## runs the next.
{ lib, pkgs, config, ...}:
let
inherit (lib) mkOption types;
inherit (pkgs) liminix;
inherit (pkgs.liminix.services) longrun;
in {
options = {
system.service.round-robin = mkOption {
description = "run services one at a time and failover to next";
type = liminix.lib.types.serviceDefn;
};
};
config.system.service.round-robin = config.system.callService ./service.nix {
services = mkOption {
type = types.listOf liminix.lib.types.service;
};
name = mkOption {
type = types.str;
};
};
}

View File

@ -0,0 +1,32 @@
{
liminix, lib, s6-rc-round-robin
}:
{ services, name} :
let
inherit (liminix.services) oneshot longrun;
controlled-services = builtins.map
(s: s.overrideAttrs(o: { inherit controller; }))
services;
controller = let name' = "control-${name}"; in longrun {
name = name';
run = ''
in_outputs ${name'}
exec ${s6-rc-round-robin}/bin/s6-rc-round-robin \
-p ${proxy.name} \
${lib.concatStringsSep " "
(builtins.map (f: f.name) controlled-services)}
'';
};
proxy = oneshot rec {
inherit name;
inherit controller;
buildInputs = controlled-services;
up = ''
echo start proxy ${name}
set -x
(in_outputs ${name}
cp -rv $(output_path ${controller} active)/* .
)
'';
};
in proxy

View File

@ -9,6 +9,7 @@ let
in { in {
imports = [ imports = [
../service-trigger ../service-trigger
../mdevd.nix
]; ];
options = { options = {
@ -23,6 +24,9 @@ in {
USB_SERIAL = "y"; USB_SERIAL = "y";
USB_SERIAL_OPTION = "y"; USB_SERIAL_OPTION = "y";
}; };
programs.busybox.applets = [
"insmod" "rmmod"
];
# https://www.0xf8.org/2017/01/flashing-a-huawei-e3372h-4g-lte-stick-from-hilink-to-stick-mode/ # https://www.0xf8.org/2017/01/flashing-a-huawei-e3372h-4g-lte-stick-from-hilink-to-stick-mode/
system.service.wwan.huawei-e3372 = system.service.wwan.huawei-e3372 =

View File

@ -31,7 +31,7 @@ let
indent = text : indentLines 0 (splitString "\n" text); indent = text : indentLines 0 (splitString "\n" text);
dochain = { name, type, rules, dochain = { name, type, family, rules,
policy ? null, policy ? null,
priority ? "filter", priority ? "filter",
hook ? null } : '' hook ? null } : ''

View File

@ -13,7 +13,7 @@ stdenv.mkDerivation {
propagatedBuildInputs = [ s6-rc-up-tree ]; propagatedBuildInputs = [ s6-rc-up-tree ];
installPhase = '' installPhase = ''
mkdir -p $out/bin mkdir -p $out/bin
cp -p ${writeFennel "uevent-watch" { cp -p ${writeFennel "s6-rc-round-robin" {
packages = [fennel anoia linotify lualinux s6-rc-up-tree] ; packages = [fennel anoia linotify lualinux s6-rc-up-tree] ;
mainFunction = "run"; mainFunction = "run";
} ./robin.fnl} $out/bin/s6-rc-round-robin } ./robin.fnl} $out/bin/s6-rc-round-robin