meta-isp #295

Open
thubrecht wants to merge 2 commits from meta-isp into main
7 changed files with 524 additions and 380 deletions

View file

@ -2,7 +2,16 @@
# #
# SPDX-License-Identifier: EUPL-1.2 # SPDX-License-Identifier: EUPL-1.2
{ config, ... }: {
config,
lib,
meta,
...
}:
let
inherit (lib) filterAttrs mapAttrsToList;
in
{ {
imports = [ ./module.nix ]; imports = [ ./module.nix ];
@ -40,13 +49,14 @@
radius_required_groups = [ "radius_access@sso.dgnum.eu" ]; radius_required_groups = [ "radius_access@sso.dgnum.eu" ];
# A mapping between Kanidm groups and VLANS # A mapping between Kanidm groups and VLANS
radius_groups = map ( radius_groups = mapAttrsToList (
{ vlan, ... }: _:
{ id, ... }:
{ {
inherit vlan; vlan = id;
spn = "vlan_${toString vlan}@sso.dgnum.eu"; spn = "vlan_${builtins.toString id}@sso.dgnum.eu";
} }
) config.networking.vlans-info; ) (filterAttrs (_: { userOnly, ... }: userOnly) meta.isp.vlans);
}; };
authTokenFile = config.age.secrets."radius-auth_token_file".path; authTokenFile = config.age.secrets."radius-auth_token_file".path;

View file

@ -3,300 +3,177 @@
# SPDX-License-Identifier: EUPL-1.2 # SPDX-License-Identifier: EUPL-1.2
{ {
pkgs,
lib,
meta,
name,
config, config,
lib,
pkgs,
meta,
... ...
}: }:
let let
inherit (lib) mapAttrs' mkOption nameValuePair; inherit (lib)
inherit (lib.types) listOf attrs; concatMapStringsSep
filterAttrs
mapAttrs'
nameValuePair
;
uplink = { mkNetwork = name: { settings, ... }: nameValuePair "10-${name}" ({ inherit name; } // settings);
ip = "10.120.33.250";
prefix = 30;
router = "10.120.33.249";
};
mkNetwork =
name:
{
address ? [ ],
extraNetwork ? { },
...
}:
nameValuePair "10-${name}" ({ inherit name address; } // extraNetwork);
mkNetdev = mkNetdev =
name: name:
{ Id, ... }: { id, ... }:
nameValuePair "10-${name}" { nameValuePair "10-${name}" {
netdevConfig = { netdevConfig = {
Name = name; Name = name;
Kind = "vlan"; Kind = "vlan";
}; };
vlanConfig.Id = Id; vlanConfig.Id = id;
}; };
mkUserVlan = vlans = mapAttrs' (name: nameValuePair "vlan-${name}") meta.isp.vlans;
{ in
vlan,
netIP, {
servIP, systemd = {
lbailly marked this conversation as resolved Outdated

Si on retire l'option (ce qui est le but), on peut retirer ce config =

Si on retire l'option (ce qui est le but), on peut retirer ce `config =`
interfaceName, network = {
... config.routeTables."user" = 1000;
}: networks = {
{ "10-lo" = {
name = interfaceName; name = "lo";
value = { address = [
Id = vlan; "::1/128"
extraNetwork = { "127.0.0.1/8"
networkConfig = { "10.0.0.1/27"
LinkLocalAddressing = "no";
DHCPServer = "yes";
};
linkConfig = {
Promiscuous = true;
MTUBytes = 1500;
};
addresses = [
{
Address = "${servIP}/27";
AddPrefixRoute = false;
}
]; ];
routes = [ routes = [
{ {
Destination = "${netIP}/27"; Destination = "10.0.0.0/27";
Table = "user";
}
];
routingPolicyRules = [
{
To = "10.0.0.0/16";
Table = "user"; Table = "user";
} }
]; ];
}; };
}; "10-enp67s0f0np0" = {
}; name = "enp67s0f0np0";
linkConfig.Promiscuous = true;
networkConfig = {
Bridge = "br0";
userVlans = builtins.genList (id: rec { LinkLocalAddressing = false;
vlan = 4094 - id; LLDP = false;
prefix24nb = (id + 1) / 8; EmitLLDP = false;
prefix27nb = (id + 1 - prefix24nb * 8) * 32; IPv6AcceptRA = false;
netIP = "10.0.${toString prefix24nb}.${toString prefix27nb}"; IPv6SendRA = false;
servIP = "10.0.${toString prefix24nb}.${toString (prefix27nb + 1)}"; };
interfaceName = "vlan-user-${toString vlan}"; linkConfig.MTUBytes = 1504;
prefixLen = 27;
}) 850;
vlans = {
vlan-uplink-cri = {
Id = 223;
address = with uplink; [ "${ip}/${builtins.toString prefix}" ];
extraNetwork = {
routes = [
{
# Get the public ip from the metadata
PreferredSource = builtins.head meta.network.${name}.addresses.ipv4;
Gateway = uplink.router;
}
];
linkConfig.MTUBytes = 1500;
};
};
vlan-admin = {
Id = 3000;
address = [ "fd26:baf9:d250:8000::1/64" ];
};
vlan-admin-ap = {
Id = 3001;
address = [
"fd26:baf9:d250:8001::1/64"
# FIXME: ipv4 is temporary for APs in production
"10.0.253.1/24"
];
extraNetwork = {
networkConfig = {
IPv6SendRA = true;
DHCPServer = "yes";
}; };
ipv6Prefixes = [ "50-gretap1" = {
{ name = "gretap1";
AddressAutoconfiguration = false; networkConfig = {
OnLink = false; Bridge = "br0";
Prefix = "fd26:baf9:d250:8001::/64";
}
];
};
};
vlan-apro = { LinkLocalAddressing = false;
Id = 2000; LLDP = false;
address = [ "10.0.255.1/24" ]; EmitLLDP = false;
IPv6AcceptRA = false;
extraNetwork = { IPv6SendRA = false;
networkConfig.DHCPServer = "yes";
linkConfig.MTUBytes = 1500;
};
};
vlan-hypervisor = {
Id = 2001;
address = [ "10.0.254.1/24" ];
extraNetwork = {
networkConfig.DHCPServer = "yes";
linkConfig.MTUBytes = 1500;
};
};
} // builtins.listToAttrs (map mkUserVlan userVlans);
in
{
options.networking.vlans-info = mkOption {
type = listOf attrs;
description = ''
Information about vlans for log analysis.
'';
readOnly = true;
};
config = {
systemd = {
network = {
config.routeTables."user" = 1000;
networks = {
"10-lo" = {
name = "lo";
address = [
"::1/128"
"127.0.0.1/8"
"10.0.0.1/27"
];
routes = [
{
Destination = "10.0.0.0/27";
Table = "user";
}
];
routingPolicyRules = [
{
To = "10.0.0.0/16";
Table = "user";
}
];
}; };
"10-enp67s0f0np0" = { linkConfig.MTUBytes = 1504;
name = "enp67s0f0np0";
linkConfig.Promiscuous = true;
networkConfig = {
Bridge = "br0";
LinkLocalAddressing = false;
LLDP = false;
EmitLLDP = false;
IPv6AcceptRA = false;
IPv6SendRA = false;
};
linkConfig.MTUBytes = 1504;
};
"50-gretap1" = {
name = "gretap1";
networkConfig = {
Bridge = "br0";
LinkLocalAddressing = false;
LLDP = false;
EmitLLDP = false;
IPv6AcceptRA = false;
IPv6SendRA = false;
};
linkConfig.MTUBytes = 1504;
};
"50-br0" = {
name = "br0";
networkConfig = {
VLAN = builtins.attrNames vlans;
LinkLocalAddressing = false;
LLDP = false;
EmitLLDP = false;
IPv6AcceptRA = false;
IPv6SendRA = false;
};
linkConfig.MTUBytes = 1504;
};
"50-wg0" = {
name = "wg0";
address = [ "10.10.17.1/30" ];
networkConfig.Tunnel = "gretap1";
};
} // (mapAttrs' mkNetwork vlans);
netdevs = {
"50-gretap1" = {
netdevConfig = {
Name = "gretap1";
Kind = "gretap";
};
tunnelConfig = {
Local = "10.10.17.1";
Remote = "10.10.17.2";
};
};
"50-br0" = {
netdevConfig = {
Name = "br0";
Kind = "bridge";
};
bridgeConfig = {
VLANFiltering = false;
STP = false;
};
};
"50-wg0" = {
netdevConfig = {
Name = "wg0";
Kind = "wireguard";
};
wireguardConfig = {
ListenPort = 1194;
PrivateKeyFile = config.age.secrets."wg-key".path;
};
wireguardPeers = [
{
AllowedIPs = [
"10.10.17.0/30"
];
PublicKey = "g6S3gBx1Hf2iX41tokD+m8WfzJJTTcsKifOkn+Wcd00=";
}
];
};
} // mapAttrs' mkNetdev vlans;
};
services = {
ethtoolConfig = {
wantedBy = [ "systemd-networkd.service" ];
after = [ "sys-subsystem-net-devices-enp67s0f0np0.device" ];
bindsTo = [ "sys-subsystem-net-devices-enp67s0f0np0.device" ];
script = builtins.concatStringsSep "\n" (
builtins.map (name: "${lib.getExe pkgs.ethtool} -K enp67s0f0np0 ${name} off") [
"rxvlan"
"txvlan"
"rx-vlan-filter"
"rx-vlan-offload"
"tx-vlan-offload"
"tx-vlan-stag-hw-insert"
]
);
}; };
"50-br0" = {
name = "br0";
networkConfig = {
VLAN = builtins.attrNames vlans;
systemd-networkd.serviceConfig.LimitNOFILE = 4096; LinkLocalAddressing = false;
LLDP = false;
EmitLLDP = false;
IPv6AcceptRA = false;
IPv6SendRA = false;
};
linkConfig.MTUBytes = 1504;
};
"50-wg0" = {
name = "wg0";
address = [ "10.10.17.1/30" ];
networkConfig.Tunnel = "gretap1";
};
} // (mapAttrs' mkNetwork vlans);
net-checker = { netdevs = {
"50-gretap1" = {
netdevConfig = {
Name = "gretap1";
Kind = "gretap";
};
tunnelConfig = {
Local = "10.10.17.1";
Remote = "10.10.17.2";
};
};
"50-br0" = {
netdevConfig = {
Name = "br0";
Kind = "bridge";
};
bridgeConfig = {
VLANFiltering = false;
STP = false;
};
};
"50-wg0" = {
netdevConfig = {
Name = "wg0";
Kind = "wireguard";
};
wireguardConfig = {
ListenPort = 1194;
PrivateKeyFile = config.age.secrets."wg-key".path;
};
wireguardPeers = [
{
AllowedIPs = [
"10.10.17.0/30"
];
PublicKey = "g6S3gBx1Hf2iX41tokD+m8WfzJJTTcsKifOkn+Wcd00=";
}
];
};
} // (mapAttrs' mkNetdev vlans);
};
services = {
ethtoolConfig = {
wantedBy = [ "systemd-networkd.service" ];
after = [ "sys-subsystem-net-devices-enp67s0f0np0.device" ];
bindsTo = [ "sys-subsystem-net-devices-enp67s0f0np0.device" ];
script = builtins.concatStringsSep "\n" (
builtins.map (name: "${lib.getExe pkgs.ethtool} -K enp67s0f0np0 ${name} off") [
"rxvlan"
"txvlan"
"rx-vlan-filter"
"rx-vlan-offload"
"tx-vlan-offload"
"tx-vlan-stag-hw-insert"
]
);
};
systemd-networkd.serviceConfig.LimitNOFILE = 4096;
net-checker =
let
userVlans = builtins.attrNames (filterAttrs (_: { userOnly, ... }: userOnly) vlans);
networkctl = action: concatMapStringsSep "\n " (name: "networkctl ${action} ${name}") userVlans;
in
{
path = [ path = [
pkgs.iputils pkgs.iputils
pkgs.systemd pkgs.systemd
@ -304,108 +181,91 @@ in
script = '' script = ''
if ping -c 1 8.8.8.8 > /dev/null || ping -c 1 1.1.1.1 > /dev/null; then if ping -c 1 8.8.8.8 > /dev/null || ping -c 1 1.1.1.1 > /dev/null; then
echo network is up echo network is up
${lib.concatMapStringsSep "\n " ( ${networkctl "up"}
{ interfaceName, ... }: "networkctl up ${interfaceName}"
) userVlans}
else else
echo network is down echo network is down
${lib.concatMapStringsSep "\n " ( ${networkctl "down"}
{ interfaceName, ... }: "networkctl down ${interfaceName}"
) userVlans}
fi fi
''; '';
}; };
};
timers.net-checker = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "*-*-* *:*:42";
};
}; };
networking = { timers.net-checker = {
vlans-info = [ wantedBy = [ "timers.target" ];
{ timerConfig.OnCalendar = "*-*-* *:*:42";
vlan = 2001; };
netIP = "10.0.254.0"; };
prefixLen = 24;
}
{
vlan = 3001;
netIP = "10.0.253.0";
prefixLen = 24;
}
] ++ userVlans;
nftables = {
enable = true;
tables = {
nat = {
family = "ip";
content = ''
chain postrouting {
type nat hook postrouting priority 100;
ip saddr 10.0.0.0/16 ip daddr != 10.0.0.0/16 snat ip to 129.199.195.130-129.199.195.157
}
'';
};
filter = {
family = "inet";
content = ''
chain forward {
type filter hook forward priority filter; policy accept;
ct state vmap {
invalid: drop,
established: accept,
related: accept,
new: jump forward_decide,
untracked: jump forward_decide,
};
}
chain forward_decide {
# Block access to vpn
ip daddr {
10.10.17.0/30,
100.80.0.0/16,
} jump forward_reject;
# And administrative vlans networking = {
ip6 daddr { nftables = {
fd26:baf9:d250::/48, enable = true;
} jump forward_reject; tables = {
nat = {
family = "ip";
content = ''
chain postrouting {
type nat hook postrouting priority 100;
ip saddr 10.0.0.0/16 ip daddr != 10.0.0.0/16 snat ip to 129.199.195.130-129.199.195.157
}
'';
};
filter = {
family = "inet";
content = ''
chain forward {
type filter hook forward priority filter; policy accept;
ct state vmap {
invalid: drop,
established: accept,
related: accept,
new: jump forward_decide,
untracked: jump forward_decide,
};
}
chain forward_decide {
# Block access to vpn
ip daddr {
10.10.17.0/30,
100.80.0.0/16,
} jump forward_reject;
# These are being deployed, and so are not trusted # And administrative vlans
ip saddr 10.0.255.0/24 jump forward_reject; ip6 daddr {
fd26:baf9:d250::/48,
} jump forward_reject;
# We only forward for ISP clients and our stuff # These are being deployed, and so are not trusted
ip saddr != 10.0.0.0/16 jump forward_reject; ip saddr 10.0.255.0/24 jump forward_reject;
# Can talk to us # We only forward for ISP clients and our stuff
ip daddr 10.0.0.0/27 accept; ip saddr != 10.0.0.0/16 jump forward_reject;
# Not others nor CRI # Can talk to us
ip daddr 10.0.0.0/8 jump forward_reject; ip daddr 10.0.0.0/27 accept;
}
chain forward_reject { # Not others nor CRI
reject with icmpx type admin-prohibited; ip daddr 10.0.0.0/8 jump forward_reject;
} }
''; chain forward_reject {
}; reject with icmpx type admin-prohibited;
}
'';
}; };
}; };
firewall = {
allowedUDPPorts = [
67
1194
];
# FIXME: I dont't remember why it's here, and it doesn't seems right
# comes from https://git.dgnum.eu/DGNum/infrastructure/commit/411795c664374549e5e831722a80180b51fbf0d5
# checkReversePath = false;
};
}; };
firewall = {
age.secrets."wg-key".owner = "systemd-network"; allowedUDPPorts = [
users.users."systemd-network".extraGroups = [ "keys" ]; 67
1194
boot.kernel.sysctl."net.ipv4.ip_forward" = true; ];
# FIXME: I dont't remember why it's here, and it doesn't seems right
# comes from https://git.dgnum.eu/DGNum/infrastructure/commit/411795c664374549e5e831722a80180b51fbf0d5
# checkReversePath = false;
};
}; };
age.secrets."wg-key".owner = "systemd-network";
users.users."systemd-network".extraGroups = [ "keys" ];
boot.kernel.sysctl."net.ipv4.ip_forward" = true;
} }

View file

@ -6,8 +6,10 @@
config, config,
lib, lib,
pkgs, pkgs,
meta,
... ...
}: }:
{ {
services = { services = {
ulogd = { ulogd = {
@ -59,7 +61,7 @@
}; };
environment.defaultPackages = [ environment.defaultPackages = [
(pkgs.callPackage ./fill-vlan_prefixes.nix { (pkgs.callPackage ./fill-vlan_prefixes.nix {
inherit (config.networking) vlans-info; inherit (meta.isp) vlans;
postgresql = config.services.postgresql.package; postgresql = config.services.postgresql.package;
}) })
(pkgs.callPackage ./nat-request-daddr.nix { (pkgs.callPackage ./nat-request-daddr.nix {

View file

@ -6,34 +6,29 @@
lib, lib,
writeShellApplication, writeShellApplication,
writeText, writeText,
vlans-info, vlans,
postgresql, postgresql,
}: }:
let let
inherit (lib) concatMapStringsSep; inherit (lib) attrValues filter concatMapStringsSep;
sql-script = writeText "vlan-filling.sql" ''
DROP TABLE IF EXISTS vlan_prefixes;
CREATE TABLE vlan_prefixes (
vlan_id smallint PRIMARY KEY UNIQUE NOT NULL,
prefix inet NOT NULL
);
INSERT INTO vlan_prefixes VALUES
${concatMapStringsSep ",\n " (
{
vlan,
netIP,
prefixLen,
...
}:
"(${toString vlan}, inet '${netIP}/${toString prefixLen}')"
) vlans-info}
;
'';
in in
writeShellApplication { writeShellApplication {
name = "fill-vlan_prefixes"; name = "fill-vlan_prefixes";
runtimeInputs = [ postgresql ]; runtimeInputs = [ postgresql ];
text = '' text = ''
psql -d ulogd -U ulogd -f ${sql-script} psql -d ulogd -U ulogd -f ${writeText "vlan-filling.sql" ''
DROP TABLE IF EXISTS vlan_prefixes;
CREATE TABLE vlan_prefixes (
vlan_id smallint PRIMARY KEY UNIQUE NOT NULL,
prefix inet NOT NULL
);
INSERT INTO vlan_prefixes VALUES
${concatMapStringsSep ",\n " (
vlan: "(${builtins.toString vlan.id}, inet '${vlan.internal.cidr}')"
) (filter ({ internal, ... }: internal.cidr != null) (attrValues vlans))}
;
''}
''; '';
} }

View file

@ -11,6 +11,7 @@ lib:
(lib.evalModules { (lib.evalModules {
modules = lib.extra.mkImports ./. [ modules = lib.extra.mkImports ./. [
"isp"
"network" "network"
"nodes" "nodes"
"options" "options"

131
meta/isp/default.nix Normal file
View file

@ -0,0 +1,131 @@
# SPDX-FileCopyrightText: 2024 Tom Hubrecht <tom.hubrecht@dgnum.eu>
# SPDX-FileCopyrightText: 2025 Lubin Bailly <lubin.bailly@dgnum.eu>
#
# SPDX-License-Identifier: EUPL-1.2
{ config, lib, ... }:
let
inherit (lib) genList listToAttrs nameValuePair;
mkCIDR = address: prefix: "${address}/${builtins.toString prefix}";
in
{
imports = [ ./module.nix ];
isp = {
vlans =
{
uplink-cri = {
id = 223;
settings = {
address = [ (mkCIDR "10.120.33.250" 30) ];
routes = [
{
PreferredSource = builtins.head config.network.vault01.addresses.ipv4;
Gateway = "10.120.33.249";
}
];
linkConfig.MTUBytes = 1500;
};
};
admin = {
id = 3000;
settings = {
address = [ "fd26:baf9:d250:8000::1/64" ];
};
};
admin-ap = {
id = 3001;
settings = {
address = [
"fd26:baf9:d250:8001::1/64"
# FIXME: ipv4 is temporary for APs in production
"10.0.253.1/24"
];
networkConfig = {
IPv6SendRA = true;
DHCPServer = "yes";
};
ipv6Prefixes = [
{
AddressAutoconfiguration = false;
OnLink = false;
Prefix = "fd26:baf9:d250:8001::/64";
}
];
};
internal = {
network = "10.0.253.0";
prefix = 24;
};
};
apro = {
id = 2000;
settings = {
address = [ "10.0.255.1/24" ];
networkConfig.DHCPServer = "yes";
linkConfig.MTUBytes = 1500;
};
};
hypervisor = {
id = 2001;
settings = {
address = [ "10.0.254.1/24" ];
networkConfig.DHCPServer = "yes";
linkConfig.MTUBytes = 1500;
};
internal = {
network = "10.0.254.0";
prefix = 24;
};
};
}
// listToAttrs (
genList (
base:
let
id = (4096 - 2) - base;
range24 = (base + 1) / 8;
range27 = (base + 1 - range24 * 8) * 32;
in
nameValuePair "user-${builtins.toString id}" rec {
inherit id;
internal = {
network = "10.0.${builtins.toString range24}.${builtins.toString range27}";
address = "10.0.${builtins.toString range24}.${builtins.toString (range27 + 1)}";
prefix = 27;
};
settings = {
networkConfig = {
LinkLocalAddressing = "no";
DHCPServer = "yes";
};
linkConfig = {
Promiscuous = true;
MTUBytes = 1500;
};
addresses = [
{
Address = mkCIDR internal.address internal.prefix;
AddPrefixRoute = false;
}
];
routes = [
{
Destination = mkCIDR internal.network internal.prefix;
Table = "user";
}
];
};
userOnly = true;
}
) 850
);
};
}

145
meta/isp/module.nix Normal file
View file

@ -0,0 +1,145 @@
# SPDX-FileCopyrightText: 2024 Tom Hubrecht <tom.hubrecht@dgnum.eu>
#
# SPDX-License-Identifier: EUPL-1.2
{ lib, config, ... }:
let
inherit (lib)
attrValues
genAttrs
mkDefault
mkIf
mkMerge
mkOption
optional
;
inherit (lib.types)
attrs
attrsOf
bool
ints
listOf
nullOr
submodule
str
;
cfg = config.isp;
in
{
options.isp = {
vlans = mkOption {
type = attrsOf (
submodule (
{ config, ... }:
{
options = {
flags = mkOption {
type = listOf str;
default = optional config.userOnly "users";
defaultText = ''optional config.userOnly "users"'';
description = ''
Groups of VLANs this VLAN belong to.
'';

Je suis très peu convaincu de l'idée de mettre de la configuration systemd-networkd dans le meta, surtout pour des choses type MTU, adresses ou DHCP.
Notamment, il faudrait garantir que c'est utilisé à un unique endroit, ce qui me parait peu scallable, sans apporter beaucoup au meta, vu qu'on est sensé déjà avoir mis toute les informations pertinente dans les autres fields.

Je suis très peu convaincu de l'idée de mettre de la configuration systemd-networkd dans le meta, surtout pour des choses type MTU, adresses ou DHCP. Notamment, il faudrait garantir que c'est utilisé à un unique endroit, ce qui me parait peu scallable, sans apporter beaucoup au meta, vu qu'on est sensé déjà avoir mis toute les informations pertinente dans les autres fields.
};
id = mkOption {
type = ints.between 0 (4096 - 1);
description = ''
The VLAN id to use.
'';
};
settings = mkOption {
type = attrs;
default = { };
description = ''
Settings for the configuration of networkd.
'';
};
internal = {
network = mkOption {
type = nullOr str;
description = ''
The internal network address of this VLAN.
'';
};
prefix = mkOption {
type = nullOr (ints.between 0 32);
default = if config.userOnly then 27 else null;
description = ''
The prefix length of the network associated to this VLAN.
'';
};
address = mkOption {
type = nullOr str;
description = ''
The router address in the VLAN. It should be the first ipv4 in the network.
'';
};
cidr = mkOption {
type = nullOr str;
default =
with config.internal;
if (prefix != null && network != null) then "${network}/${builtins.toString prefix}" else null;
description = ''
The CIDR notation of the network associated to the VLAN.
'';
};
};
userOnly = mkOption {
type = bool;
default = false;
description = ''
Whether this VLAN is only used by a user in the context of the IPS.
I.e. this is not an administration VLAN.
'';
};
};
# The address is null by default when not on a user VLAN
config.internal = mkIf (!config.userOnly) {
address = mkDefault null;
network = mkDefault null;
};
}
)
);
default = { };
description = ''
The list of VLANs known to our ISP.
'';
};
vlans-groups = mkOption {
type = attrsOf (submodule {
options.id-list = mkOption {
type = listOf (ints.between 0 (4096 - 1));
description = ''
List of VLANs IDs inside this group.
'';
};
});
default = { };
description = ''
The list of groups of VLANs known to our ISP.
'';
};
};
config.isp.vlans-groups = mkMerge (
map (
{ flags, id, ... }:
genAttrs flags (_: {
id-list = [ id ];
})
) (attrValues cfg.vlans)
);
}