# SPDX-FileCopyrightText: 2024 Lubin Bailly # # SPDX-License-Identifier: EUPL-1.2 { pkgs, lib, meta, config, name, ... }: let inherit (lib) catAttrs concatStringsSep getExe mapAttrs' nameValuePair ; uplink = { ip = "10.120.33.250"; prefix = 30; router = "10.120.33.249"; }; mkNetwork = name: { address ? [ ], extraNetwork ? { }, ... }: nameValuePair "10-${name}" ({ inherit name address; } // extraNetwork); mkNetdev = name: { Id, ... }: nameValuePair "10-${name}" { netdevConfig = { Name = name; Kind = "vlan"; }; vlanConfig.Id = Id; }; mkUserVlan = { vlan, interfaceName, ... }: { name = interfaceName; value = { Id = vlan; extraNetwork = { networkConfig.LinkLocalAddressing = "no"; linkConfig = { Promiscuous = true; MTUBytes = 1500; }; addresses = [ { Address = "10.0.0.1/16"; AddPrefixRoute = false; } ]; }; }; }; userVlans = builtins.genList (id: rec { vlan = 4094 - id; interfaceName = "vlan-user-${toString vlan}"; # TODO: remove when logs are migrated prefix24nb = (id + 1) / 8; prefix27nb = (id + 1 - prefix24nb * 8) * 32; netIP = "10.0.${toString prefix24nb}.${toString prefix27nb}"; servIP = "10.0.${toString prefix24nb}.${toString (prefix27nb + 1)}"; 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" "192.168.129.1/24" ]; }; 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 = [ { AddressAutoconfiguration = false; OnLink = false; Prefix = "fd26:baf9:d250:8001::/64"; } ]; }; }; vlan-apro = { Id = 2000; address = [ "10.0.255.1/24" ]; extraNetwork = { 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); sql_files = { kea_log-init = pkgs.writeText "kea_log.sql" '' CREATE SEQUENCE IF NOT EXISTS kea_log_lease_id_seq; CREATE TABLE IF NOT EXISTS kea_log ( _lease_id bigint PRIMARY KEY UNIQUE NOT NULL DEFAULT nextval('kea_log_lease_id_seq'), ip_addr inet NOT NULL, vlan_id int NOT NULL, lease_start_sec bigint NOT NULL, lease_end_sec bigint NOT NULL ); ''; link-del = pkgs.writeText "link-del.sql" '' WITH lease_entry_id AS ( SELECT max(_lease_id) AS curr_id FROM kea_log WHERE ip_addr = inet 'LEASE4_ADDRESS' AND lease_end_sec > TIMESTAMP ) UPDATE kea_log SET lease_end_sec = TIMESTAMP FROM lease_entry_id WHERE _lease_id = curr_id ; ''; link-new = pkgs.writeText "link-new.sql" '' INSERT INTO kea_log (ip_addr, vlan_id, lease_start_sec, lease_end_sec) VALUES (inet 'LEASE4_ADDRESS', VLAN_ID, TIMESTAMP, TIMESTAMP+7200 ); ''; link-renew = pkgs.writeText "link-renew.sql" '' WITH lease_entry_id AS ( SELECT max(_lease_id) AS curr_id FROM kea_log WHERE ip_addr = inet 'LEASE4_ADDRESS' AND lease_end_sec > TIMESTAMP ) UPDATE kea_log SET lease_end_sec = TIMESTAMP+7200 FROM lease_entry_id WHERE _lease_id = curr_id ; ''; }; in { nixpkgs.overlays = [ (_: super: { kea = super.kea.overrideAttrs (o: { patches = o.patches ++ [ ./0001-fix-multiple-interface-with-same-IP.patch ]; }); }) ]; 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" = { name = "enp67s0f0np0"; linkConfig.Promiscuous = true; networkConfig = { VLAN = builtins.attrNames vlans; LinkLocalAddressing = false; LLDP = false; EmitLLDP = false; IPv6AcceptRA = false; IPv6SendRA = false; }; linkConfig.MTUBytes = 1504; }; } // (mapAttrs' mkNetwork vlans); netdevs = 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; kea-dhcp4-server = { serviceConfig = { AmbientCapabilities = [ "CAP_NET_ADMIN" ]; CapabilityBoundingSet = [ "CAP_NET_ADMIN" ]; LimitNOFILE = 4096; }; requires = [ "postgresql.service" ]; after = [ "postgresql.service" ]; path = [ pkgs.kea config.services.postgresql.package ]; preStart = lib.mkAfter '' pushd $STATE_DIRECTORY if ! test -e .db.initialized; then psql -d ulogd -U ulogd -f ${sql_files.kea_log-init} kea-admin db-init pgsql -n kea -u kea -h /run/postgresql touch .db.initialized else kea-admin db-upgrade pgsql -n kea -u kea -h /run/postgresql fi popd ''; }; }; }; networking = { 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 ip6 daddr { fd26:baf9:d250::/48, } jump forward_reject; ip daddr { 192.168.129.0/24, } jump forward_reject; # These are being deployed, and so are not trusted ip saddr 10.0.255.0/24 jump forward_reject; # We only forward for ISP clients and our stuff ip saddr != 10.0.0.0/16 jump forward_reject; # Can talk to us ip daddr 10.0.0.0/27 accept; # Not others nor CRI ip daddr 10.0.0.0/8 jump forward_reject; } chain forward_reject { reject with icmpx type admin-prohibited; } ''; }; }; }; firewall.allowedUDPPorts = [ 67 ]; }; services = { kea.dhcp4 = { enable = true; settings = { interfaces-config = { interfaces = catAttrs "interfaceName" userVlans; dhcp-socket-type = "raw"; outbound-interface = "same-as-inbound"; service-sockets-require-all = true; }; lease-database = { name = "kea"; user = "kea"; host = "/run/postgresql"; persist = true; type = "pgsql"; }; valid-lifetime = 7200; rebind-timer = 3600; renew-timer = 1800; expired-leases-processing = { reclaim-timer-wait-time = 3; max-reclaim-leases = 0; max-reclaim-time = 50; unwarned-reclaim-cycles = 10; hold-reclaimed-time = 3600 * 24 * 2; flush-reclaimed-timer-wait-time = 60; }; subnet4 = [ { id = 1; pools = [ { pool = "10.0.0.32 - 10.0.252.255"; } ]; subnet = "10.0.0.0/16"; } ]; hooks-libraries = [ { library = "${pkgs.kea}/lib/kea/hooks/libdhcp_run_script.so"; parameters = { name = getExe ( pkgs.writeShellApplication { name = "hook.sh"; runtimeInputs = [ pkgs.iproute2 pkgs.coreutils pkgs.gnused config.services.postgresql.package ]; text = '' TIMESTAMP="$(date +%s)" case "$1" in "lease4_expire") ip r del table user "$LEASE4_ADDRESS" sed "s/TIMESTAMP/$TIMESTAMP/; s/LEASE4_ADDRESS/$LEASE4_ADDRESS/" \ ${sql_files.link-del} \ | psql -d ulogd -U ulogd ;; "leases4_committed") for i in $(seq 0 $((DELETED_LEASES4_SIZE-1))); do LEASE4_ADDRESS=$(eval "echo \$DELETED_LEASES4_AT''${i}_ADDRESS") ip r del table user dev "$QUERY4_IFACE_NAME" "$LEASE4_ADDRESS" sed "s/TIMESTAMP/$TIMESTAMP/; s/LEASE4_ADDRESS/$LEASE4_ADDRESS/" \ ${sql_files.link-del} \ | psql -d ulogd -U ulogd done for i in $(seq 0 $((LEASES4_SIZE-1))); do LEASE4_ADDRESS=$(eval "echo \$LEASES4_AT''${i}_ADDRESS") if [ -z "$(ip r show table user "$LEASE4_ADDRESS" dev "$QUERY4_IFACE_NAME")" ]; then ip r add table user dev "$QUERY4_IFACE_NAME" "$LEASE4_ADDRESS" sed "s/TIMESTAMP/$TIMESTAMP/; s/LEASE4_ADDRESS/$LEASE4_ADDRESS/; s/VLAN_ID/$(cut -d- -f3 <<< "$QUERY4_IFACE_NAME")/" \ ${sql_files.link-new} \ | psql -d ulogd -U ulogd else sed "s/TIMESTAMP/$TIMESTAMP/; s/LEASE4_ADDRESS/$LEASE4_ADDRESS/" \ ${sql_files.link-renew} \ | psql -d ulogd -U ulogd fi done ;; esac ''; } ); sync = false; }; } ]; }; }; postgresql = { enable = true; identMap = '' ulogd-map kea ulogd ''; settings.timezone = "CET"; ensureUsers = [ { name = "kea"; ensureDBOwnership = true; } ]; ensureDatabases = [ "kea" ]; }; }; environment.systemPackages = [ (pkgs.writeShellApplication { name = "netuserctl"; runtimeInputs = [ pkgs.systemd ]; text = concatStringsSep "\n" ( map ({ interfaceName, ... }: ''networkctl "$1" ${interfaceName}'') userVlans ); }) (pkgs.callPackage ./migrate-vlan-logging.nix { postgresql = config.services.postgresql.package; vlans-info = userVlans; inherit (sql_files) kea_log-init; }) ]; boot.kernel.sysctl."net.ipv4.ip_forward" = true; }