router: more progress
This commit is contained in:
parent
818ba92987
commit
ec9c66555d
|
@ -84,6 +84,7 @@
|
||||||
modules = [
|
modules = [
|
||||||
./system/hardware/bpi_r3/emmc.nix
|
./system/hardware/bpi_r3/emmc.nix
|
||||||
./system/hosts/router
|
./system/hosts/router
|
||||||
|
./system/modules/router
|
||||||
{ networking.hostName = "router"; }
|
{ networking.hostName = "router"; }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -92,6 +93,7 @@
|
||||||
modules = [
|
modules = [
|
||||||
./system/hardware/bpi_r3/sd.nix
|
./system/hardware/bpi_r3/sd.nix
|
||||||
./system/hosts/router
|
./system/hosts/router
|
||||||
|
./system/modules/router
|
||||||
{ networking.hostName = "router"; }
|
{ networking.hostName = "router"; }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
});
|
});
|
||||||
extraPackages = with pkgs; [
|
extraPackages = with pkgs; [
|
||||||
# utils
|
# utils
|
||||||
gnused mktemp fzf coreutils-full findutils xdg-utils git gnupg whois curl
|
gnused mktemp fzf coreutils-full findutils xdg-utils gnupg whois curl
|
||||||
file mediainfo unzip gnutar man rclone sshfs trash-cli
|
file mediainfo unzip gnutar man rclone sshfs trash-cli
|
||||||
# for preview
|
# for preview
|
||||||
exa bat
|
exa bat
|
||||||
|
@ -94,6 +94,7 @@
|
||||||
credential.helper = "${pkgs.gitAndTools.gitFull}/bin/git-credential-libsecret";
|
credential.helper = "${pkgs.gitAndTools.gitFull}/bin/git-credential-libsecret";
|
||||||
init.defaultBranch = "master";
|
init.defaultBranch = "master";
|
||||||
};
|
};
|
||||||
|
lfs.enable = true;
|
||||||
};
|
};
|
||||||
bat = {
|
bat = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
@ -64,7 +64,6 @@ in {
|
||||||
};
|
};
|
||||||
zramSwap.enable = true;
|
zramSwap.enable = true;
|
||||||
swapDevices = [ ];
|
swapDevices = [ ];
|
||||||
services.tlp.enable = false;
|
|
||||||
impermanence = {
|
impermanence = {
|
||||||
enable = true;
|
enable = true;
|
||||||
path = /persist;
|
path = /persist;
|
||||||
|
|
|
@ -4,7 +4,48 @@
|
||||||
let
|
let
|
||||||
rootUuid = "44444444-4444-4444-8888-888888888888";
|
rootUuid = "44444444-4444-4444-8888-888888888888";
|
||||||
rootPart = "/dev/disk/by-uuid/${rootUuid}";
|
rootPart = "/dev/disk/by-uuid/${rootUuid}";
|
||||||
|
cfg = config.router-settings;
|
||||||
|
hapdConfig = {
|
||||||
|
inherit (cfg) country_code wpa_passphrase;
|
||||||
|
he_su_beamformer = true;
|
||||||
|
he_su_beamformee = true;
|
||||||
|
he_mu_beamformer = true;
|
||||||
|
he_bss_color = 128;
|
||||||
|
he_spr_sr_control = 3;
|
||||||
|
he_default_pe_duration = 4;
|
||||||
|
he_rts_threshold = 1023;
|
||||||
|
he_mu_edca_qos_info_param_count = 0;
|
||||||
|
he_mu_edca_qos_info_q_ack = 0;
|
||||||
|
he_mu_edca_qos_info_queue_request = 0;
|
||||||
|
he_mu_edca_qos_info_txop_request = 0;
|
||||||
|
he_mu_edca_ac_be_aifsn = 8;
|
||||||
|
he_mu_edca_ac_be_aci = 0;
|
||||||
|
he_mu_edca_ac_be_ecwmin = 9;
|
||||||
|
he_mu_edca_ac_be_ecwmax = 10;
|
||||||
|
he_mu_edca_ac_be_timer = 255;
|
||||||
|
he_mu_edca_ac_bk_aifsn = 15;
|
||||||
|
he_mu_edca_ac_bk_aci = 1;
|
||||||
|
he_mu_edca_ac_bk_ecwmin = 9;
|
||||||
|
he_mu_edca_ac_bk_ecwmax = 10;
|
||||||
|
he_mu_edca_ac_bk_timer = 255;
|
||||||
|
he_mu_edca_ac_vi_ecwmin = 5;
|
||||||
|
he_mu_edca_ac_vi_ecwmax = 7;
|
||||||
|
he_mu_edca_ac_vi_aifsn = 5;
|
||||||
|
he_mu_edca_ac_vi_aci = 2;
|
||||||
|
he_mu_edca_ac_vi_timer = 255;
|
||||||
|
he_mu_edca_ac_vo_aifsn = 5;
|
||||||
|
he_mu_edca_ac_vo_aci = 3;
|
||||||
|
he_mu_edca_ac_vo_ecwmin = 5;
|
||||||
|
he_mu_edca_ac_vo_ecwmax = 7;
|
||||||
|
he_mu_edca_ac_vo_timer = 255;
|
||||||
|
preamble = true;
|
||||||
|
vht_oper_chwidth = 1; # 80mhz ch width
|
||||||
|
vht_oper_centr_freq_seg0_idx = 42;
|
||||||
|
vht_capab = "[RXLDPC][SHORT-GI-80][SHORT-GI-160][TX-STBC-2BY1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN][RX-STBC-1][SOUNDING-DIMENSION-4][BF-ANTENNA-4][VHT160][MAX-MPDU-11454][MAX-A-MPDU-LEN-EXP7]";
|
||||||
|
country3 = "0x49"; # indoor
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
|
imports = [ ./options.nix ];
|
||||||
system.stateVersion = "22.11";
|
system.stateVersion = "22.11";
|
||||||
fileSystems = {
|
fileSystems = {
|
||||||
# mount root on tmpfs
|
# mount root on tmpfs
|
||||||
|
@ -27,6 +68,56 @@ in {
|
||||||
directories = [
|
directories = [
|
||||||
{ directory = /home/${config.common.mainUsername}; user = config.common.mainUsername; group = "users"; mode = "0700"; }
|
{ directory = /home/${config.common.mainUsername}; user = config.common.mainUsername; group = "users"; mode = "0700"; }
|
||||||
{ directory = /root; mode = "0700"; }
|
{ directory = /root; mode = "0700"; }
|
||||||
|
{ directory = /var/db/dhcpcd; user = "root"; group = "root"; mode = "0755"; }
|
||||||
|
{ directory = /var/lib/kea; user = "root"; group = "root"; mode = "0755"; }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
router.enable = true;
|
||||||
|
router.interfaces.wlan0 = {
|
||||||
|
bridge = "br0";
|
||||||
|
hostapd.enable = true;
|
||||||
|
hostapd.settings = {
|
||||||
|
inherit (cfg) ssid;
|
||||||
|
hw_mode = "g";
|
||||||
|
supported_rates = [ 60 90 120 180 240 360 480 540 ];
|
||||||
|
basic_rates = [ 60 120 240 ];
|
||||||
|
ht_capab = "[LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1][MAX-AMSDU-7935]";
|
||||||
|
} // hapdConfig;
|
||||||
|
};
|
||||||
|
router.interfaces.wlan1 = {
|
||||||
|
bridge = "br0";
|
||||||
|
hostapd.enable = true;
|
||||||
|
hostapd.settings = {
|
||||||
|
ssid = "${cfg.ssid} 5G";
|
||||||
|
ieee80211h = true;
|
||||||
|
hw_mode = "a";
|
||||||
|
tx_queue_data2_burst = 2;
|
||||||
|
ht_capab = "[HT40+][LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1][MAX-AMSDU-7935]";
|
||||||
|
} // hapdConfig;
|
||||||
|
};
|
||||||
|
router.interfaces.lan0 = {
|
||||||
|
matchUdevAttrs.address = "11:11:11:11:11:11";
|
||||||
|
macAddress = "11:22:33:44:55:66";
|
||||||
|
};
|
||||||
|
router.interfaces.wan0 = {
|
||||||
|
matchUdevAttrs.address = "22:11:11:11:11:11";
|
||||||
|
macAddress = "22:22:33:44:55:66";
|
||||||
|
dhcpcd.enable = true;
|
||||||
|
};
|
||||||
|
router.interfaces.br0 = {
|
||||||
|
ipv4.addresses = [ {
|
||||||
|
address = "10.0.0.1";
|
||||||
|
prefixLength = 24;
|
||||||
|
dns = [ "10.0.0.1" ];
|
||||||
|
} ];
|
||||||
|
ipv6.addresses = [ {
|
||||||
|
address = "fd00::1";
|
||||||
|
prefixLength = 64;
|
||||||
|
dns = [ "fd00::1" ];
|
||||||
|
} ];
|
||||||
|
ipv4.kea.enable = true;
|
||||||
|
ipv6.kea.enable = true;
|
||||||
|
ipv6.radvd.enable = true;
|
||||||
|
ipv6.corerad.enable = false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
16
system/hosts/router/options.nix
Normal file
16
system/hosts/router/options.nix
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{ lib
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
options.router-settings = {
|
||||||
|
country_code = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
ssid = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
wpa_passphrase = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -72,7 +72,7 @@ in {
|
||||||
{ directory = /var/lib/swtpm-localca; user = "root"; group = "root"; mode = "0750"; }
|
{ directory = /var/lib/swtpm-localca; user = "root"; group = "root"; mode = "0750"; }
|
||||||
]))) ++ (lib.optionals config.networking.wireless.iwd.enable [
|
]))) ++ (lib.optionals config.networking.wireless.iwd.enable [
|
||||||
{ directory = /var/lib/iwd; user = "root"; group = "root"; mode = "0700"; }
|
{ directory = /var/lib/iwd; user = "root"; group = "root"; mode = "0700"; }
|
||||||
]) ++ (lib.optionals (builtins.any (x: x.useDHCP) (builtins.attrValues config.networking.interfaces) || config.networking.useDHCP) [
|
]) ++ (lib.optionals (builtins.any (x: x.useDHCP != false) (builtins.attrValues config.networking.interfaces) && config.networking.useDHCP) [
|
||||||
{ directory = /var/db/dhcpcd; user = "root"; group = "root"; mode = "0755"; }
|
{ directory = /var/db/dhcpcd; user = "root"; group = "root"; mode = "0755"; }
|
||||||
]) ++ (lib.optionals config.services.gitea.enable [
|
]) ++ (lib.optionals config.services.gitea.enable [
|
||||||
{ directory = /var/lib/gitea; user = "gitea"; group = "gitea"; mode = "0755"; }
|
{ directory = /var/lib/gitea; user = "gitea"; group = "gitea"; mode = "0755"; }
|
||||||
|
|
11
system/modules/router/avahi.nix
Normal file
11
system/modules/router/avahi.nix
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
in {
|
||||||
|
services.avahi.enable = lib.mkDefault true;
|
||||||
|
services.avahi.publish.enable = lib.mkDefault true;
|
||||||
|
services.avahi.allowInterfaces = lib.mkDefault (builtins.attrNames cfg.interfaces);
|
||||||
|
}
|
60
system/modules/router/corerad.nix
Normal file
60
system/modules/router/corerad.nix
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, pkgs
|
||||||
|
, utils
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
systemd.services = lib.mapAttrs' (interface: icfg: let
|
||||||
|
cfg = icfg.ipv6.corerad;
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
settingsFormat = pkgs.formats.toml {};
|
||||||
|
configFile = if cfg.configFile != null then cfg.configFile else settingsFormat.generate "corerad-${escapedInterface}.toml" ({
|
||||||
|
interfaces = [
|
||||||
|
(rec {
|
||||||
|
name = interface;
|
||||||
|
monitor = false;
|
||||||
|
advertise = true;
|
||||||
|
managed = icfg.ipv6.kea.enable && builtins.any (x: lib.hasInfix ":" x.address) icfg.ipv6.addresses;
|
||||||
|
other_config = managed && cfg.interfaceSettings.managed or true;
|
||||||
|
prefix = map ({ address, prefixLength, coreradSettings, ... }: {
|
||||||
|
prefix = "${address}/${toString prefixLength}";
|
||||||
|
autonomous = !(other_config && cfg.interfaceSettings.other_config or true);
|
||||||
|
} // coreradSettings) icfg.ipv6.addresses;
|
||||||
|
route = builtins.concatLists (map ({ address, prefixLength, gateways, ... }: map (gateway: {
|
||||||
|
prefix = "${if builtins.isString gateway then gateway else gateway.address}/${toString (if gateway.prefixLength or null != null then gateway.prefixLength else prefixLength)}";
|
||||||
|
} // (gateway.coreradSettings or { })) gateways) icfg.ipv6.addresses);
|
||||||
|
rdnss = builtins.concatLists (map ({ dns, ... }: map (dns: {
|
||||||
|
servers = if builtins.isString dns then dns else dns.address;
|
||||||
|
} // (dns.coreradSettings or { })) dns) icfg.ipv6.addresses);
|
||||||
|
} // cfg.interfaceSettings)
|
||||||
|
];
|
||||||
|
} // cfg.settings);
|
||||||
|
package = pkgs.corerad;
|
||||||
|
in {
|
||||||
|
name = "corerad-${escapedInterface}";
|
||||||
|
value = lib.mkIf icfg.ipv6.corerad.enable {
|
||||||
|
description = "CoreRAD IPv6 NDP RA daemon (${interface})";
|
||||||
|
after = [ "network.target" "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
LimitNPROC = 512;
|
||||||
|
LimitNOFILE = 1048576;
|
||||||
|
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
|
||||||
|
AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW";
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
DynamicUser = true;
|
||||||
|
Type = "notify";
|
||||||
|
NotifyAccess = "main";
|
||||||
|
ExecStart = "${lib.getBin package}/bin/corerad -c=${configFile}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartKillSignal = "SIGHUP";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
};
|
||||||
|
}
|
369
system/modules/router/default.nix
Normal file
369
system/modules/router/default.nix
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, pkgs
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
/*./avahi.nix*/
|
||||||
|
./hostapd.nix
|
||||||
|
./kea.nix
|
||||||
|
./radvd.nix
|
||||||
|
./corerad.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options.router = {
|
||||||
|
enable = lib.mkEnableOption "router config";
|
||||||
|
interfaces = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
description = "All interfaces managed by the router";
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options.matchUdevAttrs = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
When a device with those attrs is detected by udev, the device is automatically renamed to this interface name.
|
||||||
|
|
||||||
|
See [kernel docs](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/Documentation/ABI/testing/sysfs-class-net?h=linux-6.3.y) for the list of attrs available.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression { address = "11:22:33:44:55:66"; };
|
||||||
|
type = lib.types.attrs;
|
||||||
|
};
|
||||||
|
options.bridge = lib.mkOption {
|
||||||
|
description = "Add this device to this bridge";
|
||||||
|
default = null;
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
};
|
||||||
|
options.macAddress = lib.mkOption {
|
||||||
|
description = "Change this device's mac address to this";
|
||||||
|
default = null;
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
};
|
||||||
|
options.hostapd = lib.mkOption {
|
||||||
|
description = "hostapd options";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkEnableOption "hostapd";
|
||||||
|
options.settings = lib.mkOption {
|
||||||
|
description = "hostapd config";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options.dhcpcd = lib.mkOption {
|
||||||
|
description = "dhcpcd options";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkEnableOption "dhcpcd";
|
||||||
|
options.extraConfig = lib.mkOption {
|
||||||
|
description = "dhcpcd text config";
|
||||||
|
default = "";
|
||||||
|
type = lib.types.lines;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options.ipv4 = lib.mkOption {
|
||||||
|
description = "IPv4 config";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.addresses = lib.mkOption {
|
||||||
|
description = "Device's IPv4 addresses";
|
||||||
|
default = [ ];
|
||||||
|
type = lib.types.listOf (lib.types.submodule {
|
||||||
|
options.address = lib.mkOption {
|
||||||
|
description = "IPv4 address";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
options.prefixLength = lib.mkOption {
|
||||||
|
description = "IPv4 prefix length";
|
||||||
|
type = lib.types.int;
|
||||||
|
};
|
||||||
|
options.gateways = lib.mkOption {
|
||||||
|
description = "IPv4 gateway addresses (optional)";
|
||||||
|
default = [ ];
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
};
|
||||||
|
options.dns = lib.mkOption {
|
||||||
|
description = "IPv4 DNS servers associated with this device";
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
options.keaSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.json {}).type;
|
||||||
|
example = lib.literalExpression {
|
||||||
|
pools = [ { pool = "192.168.1.15 - 192.168.1.200"; } ];
|
||||||
|
option-data = [ {
|
||||||
|
name = "domain-name-servers";
|
||||||
|
code = 6;
|
||||||
|
csv-format = true;
|
||||||
|
space = "dhcp4";
|
||||||
|
data = "8.8.8.8, 8.8.4.4";
|
||||||
|
} ];
|
||||||
|
};
|
||||||
|
description = "Kea IPv4 prefix-specific settings";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
options.kea = lib.mkOption {
|
||||||
|
description = "Kea options";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
description = "Enable Kea for IPv4";
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
options.extraArgs = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = "List of additional arguments to pass to the daemon.";
|
||||||
|
};
|
||||||
|
options.configFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = "Kea config file (takes precedence over settings)";
|
||||||
|
};
|
||||||
|
options.settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.json {}).type;
|
||||||
|
description = "Kea settings";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options.ipv6 = lib.mkOption {
|
||||||
|
description = "IPv6 config";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.addresses = lib.mkOption {
|
||||||
|
description = "Device's IPv6 addresses";
|
||||||
|
default = [ ];
|
||||||
|
type = lib.types.listOf (lib.types.submodule {
|
||||||
|
options.address = lib.mkOption {
|
||||||
|
description = "IPv6 address";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
options.prefixLength = lib.mkOption {
|
||||||
|
description = "IPv6 prefix length";
|
||||||
|
type = lib.types.int;
|
||||||
|
};
|
||||||
|
options.gateways = lib.mkOption {
|
||||||
|
description = "IPv6 gateways information (optional)";
|
||||||
|
default = [ ];
|
||||||
|
type = with lib.types; listOf (either str (submodule {
|
||||||
|
options.address = lib.mkOption {
|
||||||
|
description = "Gateway's IPv6 address";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
options.prefixLength = lib.mkOption {
|
||||||
|
description = "Gateway's IPv6 prefix length (defaults to interface address's prefix length)";
|
||||||
|
type = nullOr int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
options.radvdSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = attrsOf (oneOf [ bool str int ]);
|
||||||
|
example = lib.literalExpression {
|
||||||
|
AdvRoutePreference = "high";
|
||||||
|
};
|
||||||
|
description = "radvd prefix-specific route settings";
|
||||||
|
};
|
||||||
|
options.coreradSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.toml {}).type;
|
||||||
|
example = lib.literalExpression {
|
||||||
|
preference = "high";
|
||||||
|
};
|
||||||
|
description = "CoreRAD prefix-specific route settings";
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
options.dns = lib.mkOption {
|
||||||
|
description = "IPv6 DNS servers associated with this device";
|
||||||
|
type = with lib.types; listOf (either str (submodule {
|
||||||
|
options.address = lib.mkOption {
|
||||||
|
description = "DNS server's address";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
options.radvdSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = attrsOf (oneOf [ bool str int ]);
|
||||||
|
example = lib.literalExpression { FlushRDNSS = false; };
|
||||||
|
description = "radvd prefix-specific RDNSS settings";
|
||||||
|
};
|
||||||
|
options.coreradSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.toml {}).type;
|
||||||
|
example = lib.literalExpression { lifetime = "auto"; };
|
||||||
|
description = "CoreRAD prefix-specific RDNSS settings";
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
options.keaSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.json {}).type;
|
||||||
|
example = lib.literalExpression {
|
||||||
|
pools = [ {
|
||||||
|
pool = "192.168.1.15 - 192.168.1.200";
|
||||||
|
} ];
|
||||||
|
option-data = [ {
|
||||||
|
name = "dns-servers";
|
||||||
|
code = 23;
|
||||||
|
csv-format = true;
|
||||||
|
space = "dhcp6";
|
||||||
|
data = "aaaa::, bbbb::";
|
||||||
|
} ];
|
||||||
|
};
|
||||||
|
description = "Kea prefix-specific settings";
|
||||||
|
};
|
||||||
|
options.radvdSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = with lib.types; attrsOf (oneOf [ bool str int ]);
|
||||||
|
example = lib.literalExpression {
|
||||||
|
AdvOnLink = true;
|
||||||
|
AdvAutonomous = true;
|
||||||
|
Base6to4Interface = "ppp0";
|
||||||
|
};
|
||||||
|
description = "radvd prefix-specific settings";
|
||||||
|
};
|
||||||
|
options.coreradSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.toml {}).type;
|
||||||
|
example = lib.literalExpression {
|
||||||
|
on_link = true;
|
||||||
|
autonomous = true;
|
||||||
|
};
|
||||||
|
description = "CoreRAD prefix-specific settings";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
options.kea = lib.mkOption {
|
||||||
|
description = "Kea options";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkEnableOption "Kea for IPv6";
|
||||||
|
options.extraArgs = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = "List of additional arguments to pass to the daemon.";
|
||||||
|
};
|
||||||
|
options.configFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = "Kea config file (takes precedence over settings)";
|
||||||
|
};
|
||||||
|
options.settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.json {}).type;
|
||||||
|
description = "Kea settings";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options.radvd = lib.mkOption {
|
||||||
|
description = "radvd options";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
description = "Enable radvd";
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
options.interfaceSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = with lib.types; attrsOf (oneOf [ bool str int ]);
|
||||||
|
example = lib.literalExpression {
|
||||||
|
UnicastOnly = true;
|
||||||
|
};
|
||||||
|
description = "radvd interface-specific settings";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options.corerad = lib.mkOption {
|
||||||
|
description = "CoreRAD options";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkEnableOption "CoreRAD (don't forget to disable radvd)";
|
||||||
|
options.configFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = "CoreRAD config file (takes precedence over settings)";
|
||||||
|
};
|
||||||
|
options.interfaceSettings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.toml {}).type;
|
||||||
|
description = "CoreRAD interface-specific settings";
|
||||||
|
};
|
||||||
|
options.settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = (pkgs.formats.toml {}).type;
|
||||||
|
example = lib.literalExpression {
|
||||||
|
debug.address = "localhost:9430";
|
||||||
|
debug.prometheus = true;
|
||||||
|
};
|
||||||
|
description = "General CoreRAD settings";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
dig.dnsutils
|
||||||
|
ethtool
|
||||||
|
tcpdump
|
||||||
|
];
|
||||||
|
|
||||||
|
# performance tweaks
|
||||||
|
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
|
||||||
|
services.irqbalance.enable = lib.mkDefault true;
|
||||||
|
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_xanmod;
|
||||||
|
|
||||||
|
boot.kernel.sysctl = {
|
||||||
|
"net.netfilter.nf_log_all_netns" = true;
|
||||||
|
"net.ipv4.conf.all.forwarding" = true;
|
||||||
|
"net.ipv4.conf.default.forwarding" = true;
|
||||||
|
"net.ipv6.conf.all.forwarding" = config.networking.enableIPv6;
|
||||||
|
"net.ipv6.conf.default.forwarding" = config.networking.enableIPv6;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.enableIPv6 = lib.mkDefault true;
|
||||||
|
networking.usePredictableInterfaceNames = true;
|
||||||
|
networking.firewall.allowPing = lib.mkDefault true;
|
||||||
|
networking.firewall.rejectPackets = lib.mkDefault false; # drop rather than reject
|
||||||
|
services.udev.extraRules =
|
||||||
|
let
|
||||||
|
devs = lib.filterAttrs (k: v: (v.matchUdevAttrs or { }) != { }) cfg.interfaces;
|
||||||
|
in lib.mkIf (devs != { })
|
||||||
|
(builtins.concatStringsSep "\n" (lib.mapAttrsToList (k: v:
|
||||||
|
let
|
||||||
|
attrs = lib.mapAttrsToList (k: v: "ATTR{${k}}==${builtins.toJSON (toString v)}") v.matchUdevAttrs;
|
||||||
|
in ''
|
||||||
|
SUBSYSTEM=="net", ACTION=="add", ${builtins.concatStringsSep ", " attrs}, NAME="${k}"
|
||||||
|
'') devs));
|
||||||
|
networking.interfaces = builtins.mapAttrs (interface: icfg: {
|
||||||
|
ipv4.addresses = map ({ address, prefixLength, ... }: { inherit address prefixLength; }) icfg.ipv4.addresses;
|
||||||
|
ipv6.addresses = map ({ address, prefixLength, ... }: { inherit address prefixLength; }) icfg.ipv6.addresses;
|
||||||
|
} // lib.optionalAttrs (icfg.macAddress != null) {
|
||||||
|
inherit (icfg) macAddress;
|
||||||
|
}) cfg.interfaces;
|
||||||
|
networking.bridges =
|
||||||
|
builtins.zipAttrsWith
|
||||||
|
(k: vs: { interfaces = vs; })
|
||||||
|
(lib.mapAttrsToList
|
||||||
|
(interface: icfg:
|
||||||
|
if icfg.bridge != null && !icfg.hostapd.enable then {
|
||||||
|
${icfg.bridge} = interface;
|
||||||
|
} else {})
|
||||||
|
cfg.interfaces);
|
||||||
|
networking.useDHCP = lib.mkIf (builtins.any (x: x.dhcpcd.enable) (builtins.attrValues cfg.interfaces)) false;
|
||||||
|
};
|
||||||
|
}
|
69
system/modules/router/dhcpcd.nix
Normal file
69
system/modules/router/dhcpcd.nix
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, pkgs
|
||||||
|
, utils
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
exitHook = pkgs.writeText "dhcpcd.exit-hook" ''
|
||||||
|
if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
|
||||||
|
# Restart ntpd. We need to restart it to make sure that it
|
||||||
|
# will actually do something: if ntpd cannot resolve the
|
||||||
|
# server hostnames in its config file, then it will never do
|
||||||
|
# anything ever again ("couldn't resolve ..., giving up on
|
||||||
|
# it"), so we silently lose time synchronisation. This also
|
||||||
|
# applies to openntpd.
|
||||||
|
/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
config = lib.mkIf (cfg.enable && builtins.any (x: x.dhcpcd.enable) (builtins.attrValues cfg.interfaces)) {
|
||||||
|
users.users.dhcpcd = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "dhcpcd";
|
||||||
|
};
|
||||||
|
users.groups.dhcpcd = {};
|
||||||
|
environment.systemPackages = [ pkgs.dhcpcd ];
|
||||||
|
environment.etc."dhcpcd.exit-hook".source = exitHook;
|
||||||
|
powerManagement.resumeCommands = builtins.concatStringsSep "\n" (lib.mapAttrsToList (interface: icfg: ''
|
||||||
|
# Tell dhcpcd to rebind its interfaces if it's running.
|
||||||
|
/run/current-system/systemd/bin/systemctl reload "dhcpcd-${utils.escapeSystemdPath interface}.service"
|
||||||
|
''));
|
||||||
|
systemd.services = lib.mapAttrs' (interface: icfg: let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
dhcpcdConf = pkgs.writeText "dhcpcd.conf" ''
|
||||||
|
hostname
|
||||||
|
option domain_name_servers, domain_name, domain_search, host_name
|
||||||
|
option classless_static_routes, ntp_servers, interface_mtu
|
||||||
|
nohook lookup-hostname
|
||||||
|
denyinterfaces ve-* vb-* lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
|
||||||
|
allowinterfaces ${interface}
|
||||||
|
waitip
|
||||||
|
${icfg.dhcpcd.extraConfig}
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
name = "dhcpcd-${escapedInterface}";
|
||||||
|
value = lib.mkIf icfg.dhcpcd.enable {
|
||||||
|
description = "DHCP Client";
|
||||||
|
wantedBy = [ "multi-user.target" "network-online.target" ];
|
||||||
|
wants = [ "network.target" ];
|
||||||
|
before = [ "network-online.target" ];
|
||||||
|
after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
restartTriggers = [ exitHook ];
|
||||||
|
stopIfChanged = false;
|
||||||
|
path = [ pkgs.dhcpcd pkgs.nettools config.networking.resolvconf.package ];
|
||||||
|
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "forking";
|
||||||
|
PIDFile = "/run/dhcpcd/${interface}.pid";
|
||||||
|
RuntimeDirectory = "dhcpcd";
|
||||||
|
ExecStart = "@${pkgs.dhcpcd}/sbin/dhcpcd dhcpcd --quiet --config ${dhcpcdConf} ${lib.escapeShellArg interface}";
|
||||||
|
ExecReload = "${pkgs.dhcpcd}/sbin/dhcpcd --rebind";
|
||||||
|
Restart = "always";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
};
|
||||||
|
}
|
84
system/modules/router/hostapd.nix
Normal file
84
system/modules/router/hostapd.nix
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, pkgs
|
||||||
|
, utils
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
in {
|
||||||
|
config = lib.mkIf (cfg.enable && builtins.any (x: x.hostapd.enable) (builtins.attrValues cfg.interfaces)) {
|
||||||
|
environment.systemPackages = with pkgs; [ hostapd wirelesstools ];
|
||||||
|
services.udev.packages = with pkgs; [ crda ];
|
||||||
|
hardware.wirelessRegulatoryDatabase = true;
|
||||||
|
systemd.services = lib.mapAttrs' (interface: icfg: let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
compileValue = k: v:
|
||||||
|
if builtins.isBool v then (if v then "1" else "0")
|
||||||
|
else if builtins.isList v then builtins.concatStringsSep " " (map (compileValue k) v)
|
||||||
|
else if k == "ssid2" then "P${builtins.toJSON (toString v)}"
|
||||||
|
else toString v;
|
||||||
|
compileSettings = x:
|
||||||
|
let
|
||||||
|
y = builtins.removeAttrs x [ "ssid" ];
|
||||||
|
z = if y?ssid2 then y else y // { ssid2 = x.ssid; };
|
||||||
|
in
|
||||||
|
if !x?ssid && !x?ssid2 then
|
||||||
|
throw "Must specify ssid for hostapd"
|
||||||
|
else if x.wpa_key_mgmt == defaultSettings.wpa_key_mgmt && !x?wpa_passphrase && !x?sae_password then
|
||||||
|
throw "Either change authentication methods or specify wpa_passphrase for hostapd"
|
||||||
|
else builtins.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${compileValue k v}") z);
|
||||||
|
forceSettings = {
|
||||||
|
inherit interface;
|
||||||
|
};
|
||||||
|
defaultSettings = {
|
||||||
|
driver = "nl80211";
|
||||||
|
logger_syslog = -1;
|
||||||
|
logger_syslog_level = 2;
|
||||||
|
logger_stdout = -1;
|
||||||
|
logger_stdout_level = 2;
|
||||||
|
# not sure if enabling it when it isn't supported is gonna break anything?
|
||||||
|
ieee80211n = true; # wifi 4
|
||||||
|
ieee80211ac = true; # wifi 5
|
||||||
|
ieee80211ax = true; # wifi 6
|
||||||
|
ieee80211be = true; # wifi 7
|
||||||
|
ctrl_interface = "/run/hostapd";
|
||||||
|
disassoc_low_ack = true;
|
||||||
|
wmm_enabled = true;
|
||||||
|
uapsd_advertisement_enabled = true;
|
||||||
|
utf8_ssid = true;
|
||||||
|
sae_require_mfp = true;
|
||||||
|
ieee80211w = 1; # optional mfp
|
||||||
|
sae_pwe = 2;
|
||||||
|
auth_algs = 1;
|
||||||
|
wpa = 2;
|
||||||
|
wpa_pairwise = [ "CCMP" ];
|
||||||
|
wpa_key_mgmt = [ "WPA-PSK" "WPA-PSK-SHA256" "SAE" ];
|
||||||
|
okc = true;
|
||||||
|
group_mgmt_cipher = "AES-128-CMAC";
|
||||||
|
qos_map_set = "0,0,2,16,1,1,255,255,18,22,24,38,40,40,44,46,48,56"; # from openwrt
|
||||||
|
# ap_isolate = true; # to isolate clients
|
||||||
|
} // lib.optionalAttrs (icfg.hostapd.settings?country_code) {
|
||||||
|
ieee80211d = true;
|
||||||
|
} // lib.optionalAttrs (icfg.bridge != null) {
|
||||||
|
inherit (icfg) bridge;
|
||||||
|
};
|
||||||
|
settings = defaultSettings // icfg.hostapd.settings // forceSettings;
|
||||||
|
configFile = pkgs.writeText "hostapd.conf" (compileSettings settings);
|
||||||
|
in {
|
||||||
|
name = "hostapd-${escapedInterface}";
|
||||||
|
value = lib.mkIf icfg.hostapd.enable {
|
||||||
|
description = "hostapd wireless AP (${interface})";
|
||||||
|
path = [ pkgs.hostapd ];
|
||||||
|
after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
requiredBy = [ "network-link-${escapedInterface}.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
|
||||||
|
Restart = "always";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
};
|
||||||
|
}
|
258
system/modules/router/kea.nix
Normal file
258
system/modules/router/kea.nix
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, pkgs
|
||||||
|
, utils
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
# add x to last component of an ipv4
|
||||||
|
addToLastComp4 = x: split:
|
||||||
|
let
|
||||||
|
n0 = lib.last split;
|
||||||
|
nx = n0 + x;
|
||||||
|
n = if nx >= 255 then 254 else if nx < 2 then 2 else nx;
|
||||||
|
in
|
||||||
|
if x > 0 && n0 >= 255 then null
|
||||||
|
else if x < 0 && n0 < 2 then null
|
||||||
|
else lib.init split ++ [ n ];
|
||||||
|
# add x to last component of an ipv6
|
||||||
|
addToLastComp6 = x: split:
|
||||||
|
let
|
||||||
|
n0 = lib.last split;
|
||||||
|
nx = n0 + x;
|
||||||
|
n = if nx >= 65535 then 65534 else if nx <= 2 then 2 else nx;
|
||||||
|
in
|
||||||
|
if x > 0 && n0 >= 65535 then null
|
||||||
|
else if x < 0 && n0 < 2 then null
|
||||||
|
else lib.init split ++ [ n ];
|
||||||
|
# generate an integer of `total` bits with `set` most significant bits set
|
||||||
|
genMask = total: set:
|
||||||
|
parseBin (builtins.concatStringsSep "" (builtins.genList (i: if i < set then "1" else "0") total));
|
||||||
|
# generate subnet mask for ipv4
|
||||||
|
genMask4 = len:
|
||||||
|
builtins.genList (i: let
|
||||||
|
len' = len - i * 8;
|
||||||
|
in
|
||||||
|
if len' <= 0 then 0
|
||||||
|
else if len' >= 8 then 255
|
||||||
|
else genMask 8 len') 4;
|
||||||
|
# generate subnet mask for ipv6
|
||||||
|
genMask6 = len:
|
||||||
|
builtins.genList (i: let
|
||||||
|
len' = len - i * 16;
|
||||||
|
in
|
||||||
|
if len' <= 0 then 0
|
||||||
|
else if len' >= 16 then 65535
|
||||||
|
else genMask 16 len') 8;
|
||||||
|
# invert a mask
|
||||||
|
invMask4 = map (builtins.bitXor 255);
|
||||||
|
invMask6 = map (builtins.bitXor 65535);
|
||||||
|
orMask = lib.zipListsWith builtins.bitOr;
|
||||||
|
andMask = lib.zipListsWith builtins.bitAnd;
|
||||||
|
# parses hexadecimal number
|
||||||
|
parseHex = x: (builtins.fromTOML "x=0x${x}").x;
|
||||||
|
# parses binary number
|
||||||
|
parseBin = x: (builtins.fromTOML "x=0b${x}").x;
|
||||||
|
# finds the longest zero-only sequence
|
||||||
|
# returns an attrset with maxS (start of the sequence) and max (sequence length)
|
||||||
|
longestZeroSeq =
|
||||||
|
builtins.foldl' ({ cur, max, curS, maxS, i }: elem: let self = {
|
||||||
|
i = i + 1;
|
||||||
|
cur = if elem == 0 then cur + 1 else 0;
|
||||||
|
max = if max >= self.cur then max else self.cur;
|
||||||
|
curS = if self.cur > 0 && cur > 0 then curS else if self.cur > 0 then i else -1;
|
||||||
|
maxS = if max >= self.cur then maxS else self.curS;
|
||||||
|
}; in self) { cur = 0; max = 0; curS = -1; maxS = -1; i = 0; };
|
||||||
|
# parses an IPv4 address
|
||||||
|
parseIp4 = x: map builtins.fromJSON (lib.splitString "." x);
|
||||||
|
# serializes an IPv4 address
|
||||||
|
compIp4 = x: builtins.concatStringsSep "." (map toString x);
|
||||||
|
# parses an IPv6 address
|
||||||
|
parseIp6 = x:
|
||||||
|
let
|
||||||
|
nzparts = map (x: if x == "" then [] else map parseHex (lib.splitString ":" x)) (lib.splitString "::" x);
|
||||||
|
in
|
||||||
|
if builtins.length nzparts == 1 then builtins.head nzparts
|
||||||
|
else let a = builtins.head nzparts; b = builtins.elemAt nzparts 1; in
|
||||||
|
a ++ (builtins.genList (_: 0) (8 - builtins.length a - builtins.length b)) ++ b;
|
||||||
|
# serializes an IPv6 address
|
||||||
|
compIp6 = x:
|
||||||
|
let
|
||||||
|
long = longestZeroSeq x;
|
||||||
|
joined = builtins.concatStringsSep ":" (builtins.foldl' ({ i, res }: x: {
|
||||||
|
i = i + 1;
|
||||||
|
res = res ++ (if i >= long.maxS && i < long.maxS + long.max then [ "" ] else [ (lib.toLower (lib.toHexString x)) ]);
|
||||||
|
}) { i = 0; res = [ ]; } x).res;
|
||||||
|
fix = builtins.replaceStrings [":::"] ["::"];
|
||||||
|
in
|
||||||
|
fix (fix (fix (fix (fix joined))));
|
||||||
|
format = pkgs.formats.json {};
|
||||||
|
package = pkgs.kea;
|
||||||
|
commonServiceConfig = {
|
||||||
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||||
|
DynamicUser = true;
|
||||||
|
User = "kea";
|
||||||
|
ConfigurationDirectory = "kea";
|
||||||
|
RuntimeDirectory = "kea";
|
||||||
|
StateDirectory = "kea";
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||||
|
(let
|
||||||
|
configs = builtins.mapAttrs (interface: icfg:
|
||||||
|
let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
cfg4 = icfg.ipv4.kea;
|
||||||
|
in if cfg4.configFile != null then cfg4.configFile else (format.generate "kea-dhcp4-${escapedInterface}.conf" {
|
||||||
|
Dhcp4 = {
|
||||||
|
valid-lifetime = 4000;
|
||||||
|
interfaces-config.interfaces = [ interface ];
|
||||||
|
lease-database = {
|
||||||
|
type = "memfile";
|
||||||
|
persist = true;
|
||||||
|
name = "/var/lib/kea/dhcp4-${escapedInterface}.leases";
|
||||||
|
};
|
||||||
|
subnet4 = map ({ address, prefixLength, gateways, dns, keaSettings, ... }:
|
||||||
|
let
|
||||||
|
subnetMask = genMask4 prefixLength;
|
||||||
|
parsed = parseIp4 address;
|
||||||
|
minIp = andMask subnetMask parsed;
|
||||||
|
maxIp = orMask (invMask4 subnetMask) parsed;
|
||||||
|
in {
|
||||||
|
subnet = "${address}/${toString prefixLength}";
|
||||||
|
option-data =
|
||||||
|
lib.optional (dns != [ ]) {
|
||||||
|
name = "domain-name-servers";
|
||||||
|
code = 6;
|
||||||
|
csv-format = true;
|
||||||
|
space = "dhcp4";
|
||||||
|
data = builtins.concatStringsSep ", " dns;
|
||||||
|
}
|
||||||
|
++ lib.optional (gateways != [ ]) {
|
||||||
|
name = "routers";
|
||||||
|
code = 3;
|
||||||
|
csv-format = true;
|
||||||
|
space = "dhcp4";
|
||||||
|
data = builtins.concatStringsSep ", " gateways;
|
||||||
|
};
|
||||||
|
pools = let
|
||||||
|
a = addToLastComp4 16 minIp;
|
||||||
|
b = addToLastComp4 (-16) parsed;
|
||||||
|
c = addToLastComp4 16 parsed;
|
||||||
|
d = addToLastComp4 (-16) maxIp;
|
||||||
|
in
|
||||||
|
lib.optional (a != null && b != null && a <= b) { pool = "${compIp4 a}-${compIp4 b}"; }
|
||||||
|
++ lib.optional (c != null && d != null && c <= d) { pool = "${compIp4 c}-${compIp4 d}"; };
|
||||||
|
} // keaSettings) icfg.ipv4.addresses;
|
||||||
|
} // cfg4.settings;
|
||||||
|
})) cfg.interfaces;
|
||||||
|
in {
|
||||||
|
environment.etc = lib.mapAttrs' (interface: icfg: {
|
||||||
|
name = "kea/dhcp4-server-${utils.escapeSystemdPath interface}.conf";
|
||||||
|
value = lib.mkIf (icfg.ipv4.kea.enable && icfg.ipv4.addresses != [ ]) {
|
||||||
|
source = configs.${interface};
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
systemd.services = lib.mapAttrs' (interface: icfg: let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
in {
|
||||||
|
name = "kea-dhcp4-server-${escapedInterface}";
|
||||||
|
value = lib.mkIf (icfg.ipv4.kea.enable && icfg.ipv4.addresses != [ ]) {
|
||||||
|
description = "Kea DHCP4 Server (${interface})";
|
||||||
|
documentation = [ "man:kea-dhcp4(8)" "https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html" ];
|
||||||
|
after = [ "network-online.target" "time-sync.target" "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = { KEA_PIDFILE_DIR = "/run/kea"; KEA_LOCKFILE_DIR = "/run/kea"; };
|
||||||
|
restartTriggers = [ configs.${interface} ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${package}/bin/kea-dhcp4 -c "
|
||||||
|
+ lib.escapeShellArgs ([ "/etc/kea/dhcp4-server-${escapedInterface}.conf" ]);
|
||||||
|
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ];
|
||||||
|
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ];
|
||||||
|
} // commonServiceConfig;
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
})
|
||||||
|
(let
|
||||||
|
configs = builtins.mapAttrs (interface: icfg:
|
||||||
|
let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
cfg6 = icfg.ipv6.kea;
|
||||||
|
in if cfg6.configFile != null then cfg6.configFile else (format.generate "kea-dhcp6-${escapedInterface}.conf" {
|
||||||
|
Dhcp6 = {
|
||||||
|
valid-lifetime = 4000;
|
||||||
|
preferred-lifetime = 3000;
|
||||||
|
interfaces-config.interfaces = [ interface ];
|
||||||
|
lease-database = {
|
||||||
|
type = "memfile";
|
||||||
|
persist = true;
|
||||||
|
name = "/var/lib/kea/dhcp6-${escapedInterface}.leases";
|
||||||
|
};
|
||||||
|
subnet6 = map ({ address, prefixLength, dns, keaSettings, ... }:
|
||||||
|
let
|
||||||
|
subnetMask = genMask6 prefixLength;
|
||||||
|
parsed = parseIp6 address;
|
||||||
|
minIp = andMask subnetMask parsed;
|
||||||
|
maxIp = orMask (invMask6 subnetMask) parsed;
|
||||||
|
in {
|
||||||
|
subnet = "${address}/${toString prefixLength}";
|
||||||
|
pools = let
|
||||||
|
a = addToLastComp6 16 minIp;
|
||||||
|
b = addToLastComp6 (-16) parsed;
|
||||||
|
c = addToLastComp6 16 parsed;
|
||||||
|
d = addToLastComp6 (-16) maxIp;
|
||||||
|
option-data =
|
||||||
|
lib.optional (dns != [ ]) {
|
||||||
|
name = "dns-servers";
|
||||||
|
code = 23;
|
||||||
|
csv-format = true;
|
||||||
|
space = "dhcp6";
|
||||||
|
data = builtins.concatStringsSep ", " (map (x: if builtins.isString x then x else x.address) dns);
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.optional (a != null && b != null && a <= b) {
|
||||||
|
pool = "${compIp6 a}-${compIp6 b}";
|
||||||
|
inherit option-data;
|
||||||
|
}
|
||||||
|
++ lib.optional (c != null && d != null && c <= d) {
|
||||||
|
pool = "${compIp6 c}-${compIp6 d}";
|
||||||
|
inherit option-data;
|
||||||
|
};
|
||||||
|
} // keaSettings) icfg.ipv6.addresses;
|
||||||
|
} // cfg6.settings;
|
||||||
|
})) cfg.interfaces;
|
||||||
|
in {
|
||||||
|
environment.etc = lib.mapAttrs' (interface: icfg: {
|
||||||
|
name = "kea/dhcp6-server-${utils.escapeSystemdPath interface}.conf";
|
||||||
|
value = lib.mkIf (icfg.ipv6.kea.enable && icfg.ipv6.addresses != [ ]) {
|
||||||
|
source = configs.${interface};
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
systemd.services = lib.mapAttrs' (interface: icfg: let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
in {
|
||||||
|
name = "kea-dhcp6-server-${escapedInterface}";
|
||||||
|
value = lib.mkIf (icfg.ipv6.kea.enable && icfg.ipv6.addresses != [ ]) {
|
||||||
|
description = "Kea DHCP6 Server (${interface})";
|
||||||
|
documentation = [ "man:kea-dhcp6(8)" "https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html" ];
|
||||||
|
after = [ "network-online.target" "time-sync.target" "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = { KEA_PIDFILE_DIR = "/run/kea"; KEA_LOCKFILE_DIR = "/run/kea"; };
|
||||||
|
restartTriggers = [ configs.${interface} ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${package}/bin/kea-dhcp6 -c "
|
||||||
|
+ lib.escapeShellArgs ([ "/etc/kea/dhcp6-server-${escapedInterface}.conf" ]);
|
||||||
|
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ];
|
||||||
|
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ];
|
||||||
|
} // commonServiceConfig;
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
81
system/modules/router/multiservice.nix
Normal file
81
system/modules/router/multiservice.nix
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
{ lib
|
||||||
|
, pkgs
|
||||||
|
, config
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
baseSystem = modules: lib.nixosSystem {
|
||||||
|
inherit (pkgs) system;
|
||||||
|
modules = [
|
||||||
|
({ lib, ... }: {
|
||||||
|
networking = {
|
||||||
|
firewall.enable = false;
|
||||||
|
useDHCP = false;
|
||||||
|
};
|
||||||
|
system = {
|
||||||
|
inherit (config.system) stateVersion;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
] ++ modules;
|
||||||
|
};
|
||||||
|
baseServices = builtins.concatLists (map builtins.attrNames (baseSystem [ ]).options.systemd.services.definitions);
|
||||||
|
baseEtc = builtins.concatLists (map builtins.attrNames (baseSystem [ ]).options.environment.etc.definitions);
|
||||||
|
cfg = config.multiservice;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.multiservice = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
etc = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkEnableOption {
|
||||||
|
description = "Copy etc files";
|
||||||
|
};
|
||||||
|
options.fixup = lib.mkOption {
|
||||||
|
default = lib.id;
|
||||||
|
type = lib.types.function;
|
||||||
|
description = lib.mdDoc "Function applied to each etc files (must return an attrset with `name` and `value`)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options.enable = lib.mkEnableOption {
|
||||||
|
description = "Copy services";
|
||||||
|
};
|
||||||
|
options.fixup = lib.mkOption {
|
||||||
|
default = lib.id;
|
||||||
|
type = lib.types.function;
|
||||||
|
description = "Function applied to each systemd service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = lib.mkOption {
|
||||||
|
description = "nixpkgs instance's config";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
config = lib.mkIf (cfg != { }) (lib.mkMerge (lib.mapAttrsToList (instName: instCfg:
|
||||||
|
let
|
||||||
|
result = baseSystem [ ({ ... }: instCfg.config) ];
|
||||||
|
in {
|
||||||
|
systemd.services = lib.mkIf instCfg.services.enable (lib.mkMerge (map
|
||||||
|
(services: lib.mapAttrs' (name: value: {
|
||||||
|
name = name + "-" + instName;
|
||||||
|
value = instCfg.services.fixup name value;
|
||||||
|
}) (builtins.removeAttrs services baseServices))
|
||||||
|
result.options.systemd.services.definitions));
|
||||||
|
environment.etc = lib.mkIf instCfg.etc.enable (lib.mkMerge
|
||||||
|
(map
|
||||||
|
(etc:
|
||||||
|
lib.mapAttrs'
|
||||||
|
(name: value: instCfg.etc.fixup { inherit name value; })
|
||||||
|
(builtins.removeAttrs etc baseEtc))
|
||||||
|
result.options.environment.etc.definitions));
|
||||||
|
}) cfg));
|
||||||
|
}
|
68
system/modules/router/radvd.nix
Normal file
68
system/modules/router/radvd.nix
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{ lib
|
||||||
|
, config
|
||||||
|
, pkgs
|
||||||
|
, utils
|
||||||
|
, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.router;
|
||||||
|
in {
|
||||||
|
config = lib.mkIf (cfg.enable && builtins.any (x: x.ipv6.radvd.enable) (builtins.attrValues cfg.interfaces)) {
|
||||||
|
users.users.radvd = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "radvd";
|
||||||
|
description = "Router Advertisement Daemon User";
|
||||||
|
};
|
||||||
|
users.groups.radvd = { };
|
||||||
|
|
||||||
|
systemd.services = lib.mapAttrs' (interface: icfg: let
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
ifaceOpts = rec {
|
||||||
|
AdvSendAdvert = true;
|
||||||
|
AdvManagedFlag = icfg.ipv6.kea.enable && icfg.ipv6.addresses != [ ];
|
||||||
|
AdvOtherConfigFlag = AdvManagedFlag && icfg.ipv6.radvd.interfaceSettings.AdvManagedFlag or true;
|
||||||
|
} // icfg.ipv6.radvd.interfaceSettings;
|
||||||
|
prefixOpts = {
|
||||||
|
# if dhcp6 is enabled: don't autoconfigure addresses, ask dhcp
|
||||||
|
AdvAutonomous = !ifaceOpts.AdvManagedFlag;
|
||||||
|
};
|
||||||
|
compileOpt = x:
|
||||||
|
if x == true then "on"
|
||||||
|
else if x == false then "off"
|
||||||
|
else toString x;
|
||||||
|
compileOpts = lib.mapAttrsToList (k: v: "${k} ${compileOpt v};");
|
||||||
|
indent = map (x: " " + x);
|
||||||
|
confFile = pkgs.writeText "radvd-${escapedInterface}.conf" (
|
||||||
|
builtins.concatStringsSep "\n" (
|
||||||
|
[ "interface ${interface} {" ]
|
||||||
|
++ indent (
|
||||||
|
compileOpts ifaceOpts
|
||||||
|
++ builtins.concatLists (map ({ address, gateways, prefixLength, dns, radvdSettings, ... }:
|
||||||
|
[ "prefix ${address}/${toString prefixLength} {" ]
|
||||||
|
++ indent (compileOpts (prefixOpts // radvdSettings))
|
||||||
|
++ [ "};" ]
|
||||||
|
++ (builtins.concatLists (map (gateway:
|
||||||
|
[ "route ${if builtins.isString gateway then gateway else gateway.address}/${toString (if gateway.prefixLength or null != null then gateway.prefixLength else prefixLength)} {" ]
|
||||||
|
++ indent (compileOpts (gateway.radvdSettings or { }))
|
||||||
|
++ [ "};" ]) gateways))
|
||||||
|
++ (builtins.concatLists (map (dns:
|
||||||
|
[ "RDNSS ${if builtins.isString dns then dns else dns.address} {" ]
|
||||||
|
++ indent (compileOpts (dns.radvdSettings or { }))
|
||||||
|
++ [ "};" ]) dns))) icfg.ipv6.addresses)
|
||||||
|
) ++ [ "};" ]));
|
||||||
|
package = pkgs.radvd;
|
||||||
|
in {
|
||||||
|
name = "radvd-${escapedInterface}";
|
||||||
|
value = lib.mkIf icfg.ipv6.radvd.enable {
|
||||||
|
description = "IPv6 Router Advertisement Daemon (${interface})";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "@${package}/bin/radvd radvd -n -u radvd -C ${confFile}";
|
||||||
|
Restart = "always";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) cfg.interfaces;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue