# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0

{ depot, lib, pkgs, config, ... }:
let
  inherit (depot.ops) secrets;
  inherit (lib) mkMerge mkForce;
in {
  imports = [
    # We include this just so it sets some sysctls and firewall settings.
    ../lib/bgp.nix

    ../lib/erbium.nix
  ];

  config = mkMerge [ {
    boot.initrd.availableKernelModules = [
      "sd_mod"
      "ahci"
      "usb_storage"
      "usbhid"
    ];
    boot.kernelParams = [ "mitigations=off" ];

    fileSystems = {
      "/" = {
        device = "/dev/disk/by-uuid/fc964ef6-e3d0-4472-bc0e-f96f977ebf11";
        fsType = "ext4";
      };
      "/boot" = {
        device = "/dev/disk/by-uuid/AB36-5BE4";
        fsType = "vfat";
      };
    };

    nix.settings.max-jobs = lib.mkDefault 4;

    # Use the systemd-boot EFI boot loader.
    boot.loader.systemd-boot.enable = true;
    boot.loader.efi.canTouchEfiVariables = true;

    # Networking!
    networking = {
      # Routing tables:
      # bgp (150) -- contains default routes over WG tunnels
      # ee (201) -- table contains a default route via EE
      # vm (202) -- table contains a default route via VM
      # gnet (203) -- table contains a default route via gnetwork
      # main (254) -- basically empty

      hostName = "swann"; # Define your hostname.
      domain = "int.as205479.net";
      nameservers = ["8.8.8.8" "8.8.4.4"];
      useNetworkd = true;
      interfaces = {
        lo = {
          ipv4.addresses = [
            { address = "127.0.0.1"; prefixLength = 8; }
            { address = "92.118.30.254"; prefixLength = 32; }
            { address = "92.118.30.253"; prefixLength = 32; }
          ];
        };
        en-gnet = {
          useDHCP = true;
          ipv4.addresses = [
            { address = "192.168.201.2"; prefixLength = 24; }
          ];
          # Additional options configured in networkd.
        };
        en-ee = {
          useDHCP = true;
          ipv4.addresses = [
            { address = "10.42.0.2"; prefixLength = 24; }
          ];
          # Additional options configured in networkd.
        };
        br-internal = {
          ipv4.addresses = [
            { address = "192.168.1.1"; prefixLength = 23; }
            { address = "92.118.30.17"; prefixLength = 28; }
          ];
          ipv6.addresses = [
            { address = "2a09:a443::1"; prefixLength = 64; }
            { address = "2a09:a443:1::1"; prefixLength = 48; }
          ];
        };
        vl-eduroam = {
          ipv4.addresses = [
            { address = "192.168.10.1"; prefixLength = 24; }
          ];
          ipv6.addresses = [
            { address = "2a09:a443:2::1"; prefixLength = 64; }
            { address = "2a09:a443:3::1"; prefixLength = 48; }
          ];
        };
      };
    };
    systemd.network = let
      hexToInt = h: (builtins.fromTOML "h = ${h}").h;
      physicalNetwork = rtID: wireguardFwmark: extraRules: {
        dhcpV4Config.RouteTable = rtID;
        ipv6AcceptRAConfig.RouteTable = rtID;
        routingPolicyRules = [{
          Family = "both";
          FirewallMark = hexToInt wireguardFwmark;
          Priority = 10000;
          Table = rtID;
        }] ++ extraRules;
      };
      wireguardNetwork = { linkName, relativePriority, rtID, v4Linknet, v6Linknet }: {
        matchConfig.Name = linkName;

        routes = let
          replaceV4Octet = v4: fn: let
            pieces = builtins.match ''(([0-9]+\.){3})([0-9]+)'' v4;
          in
            "${builtins.elemAt pieces 0}${toString (fn (lib.toInt (builtins.elemAt pieces 2)))}";
          replaceV6Octet = v6: fn: let
            pieces = builtins.match ''^(([0-9a-f]+:+)+)([0-9a-f]*)$'' v6;
          in
            "${builtins.elemAt pieces 0}${lib.toHexString (fn (hexToInt "0x${builtins.elemAt pieces 2}"))}";
        in [
          {
            Destination = "${v4Linknet}/31";
            Table = rtID;
          }
          {
            Gateway = replaceV4Octet v4Linknet (n: n + 1);
            Table = rtID;
          }

          {
            Destination = "${replaceV6Octet v6Linknet (n: 0)}/112";
            Table = rtID;
          }
          {
            Gateway = replaceV6Octet v6Linknet (n: n + 1);
            Table = rtID;
          }
        ];

        networkConfig = {
          Address = [
            "${v4Linknet}/31"
            "${v6Linknet}/112"
          ];
        };

        routingPolicyRules = [
          (tailscaleRule (relativePriority + 5000) rtID)

          # Allow picking destination by source IP.
          {
            Family = "ipv4";
            From = v4Linknet;
            Priority = 10010;
            Table = rtID;
          }
          {
            Family = "ipv6";
            From = v6Linknet;
            Priority = 10010;
            Table = rtID;
          }
        ];
      };
      tailscaleRule = priority: table: {
        # Route Tailscale (fwmark 0x80000) via Wireguard first.
        Family = "both";
        FirewallMark = hexToInt "0x80000";  # Should be "0x80000/0xff0000"
        Priority = priority;
        Table = table;
      };
    in let
      routeTables = {
        bgp = 150;
        wg-ee = 152;
        wg-gnet = 153;
        wg-cf-ee = 154;
        wg-cf-gnet = 155;
        wg-eta = 160;
        ee = 201;
        gnet = 203;
      };
    in {
      enable = true;
      config.routeTables = routeTables;
      networks."50-wg-rexxar-ee" = wireguardNetwork {
        linkName = "wg-rexxar-ee";
        relativePriority = 3;
        rtID = routeTables.wg-ee;
        v4Linknet = "92.118.30.2";
        v6Linknet = "2a09:a442::2:1";
      };
      networks."50-wg-rexxar-gnet" = wireguardNetwork {
        linkName = "wg-rexxar-gnet";
        relativePriority = 1;
        rtID = routeTables.wg-gnet;
        v4Linknet = "92.118.30.4";
        v6Linknet = "2a09:a442::3:1";
      };
      networks."50-wg-cofractal-ee" = wireguardNetwork {
        linkName = "wg-cofractal-ee";
        relativePriority = 4;
        rtID = routeTables.wg-cf-ee;
        v4Linknet = "92.118.30.0";
        v6Linknet = "2a09:a442::1:1";
      };
      networks."50-wg-cofractal-gn" = wireguardNetwork {
        linkName = "wg-cofractal-gn";
        relativePriority = 2;
        rtID = routeTables.wg-cf-gnet;
        v4Linknet = "92.118.30.6";
        v6Linknet = "2a09:a442::4:1";
      };
      networks."50-wg-eta" = wireguardNetwork {
        linkName = "wg-eta";
        relativePriority = 10;
        rtID = routeTables.wg-eta;
        v4Linknet = "169.254.2.1";
        v6Linknet = "fe80:1234::b";
      };
      networks."40-lo" = {
        routingPolicyRules = let
          viaMain = priority: to: {
            To = to;
            Table = "main";
            Priority = priority;
          };
          blackhole = fwmark: {
            Family = "both";
            FirewallMark = hexToInt fwmark;
            Priority = 10001;
            Type = "unreachable";
          };
        in [
          (tailscaleRule 5000 150)

          # Blackhole connections that should be routed over individual interfaces.
          (blackhole "0xdead")
          (blackhole "0xbeef")
          (blackhole "0xcafe")

          # RFC 1918 via main table.
          (viaMain 10020 "192.168.0.0/16")
          (viaMain 10021 "10.0.0.0/8")
          (viaMain 10022 "172.16.0.0/12")
          # and the linknets.
          (viaMain 10023 "92.118.30.0/24")
          (viaMain 10024 "2a09:a442::1:0/112")
          (viaMain 10025 "2a09:a442::2:0/112")
          (viaMain 10026 "2a09:a442::3:0/112")

          {
            # Catch-all "go via WG"
            Family = "both";
            Priority = 10080;
            Table = routeTables.bgp;
          }
        ];
      };
      networks."40-en-ee" = (physicalNetwork routeTables.ee "0xdead" [{
        # add-on.ee.co.uk goes via EE.
        To = "82.192.97.153/32";
        Table = routeTables.ee;
        Priority = 10031;
      } {
        # as does anything from 10.42.0.0/24.
        From = "10.42.0.0/24";
        Table = routeTables.ee;
        Priority = 10031;
      }]) // {
        linkConfig.RequiredForOnline = "no";
        routes = [{
          Gateway = "10.42.0.1";
          Destination = "0.0.0.0/0";
          Table = routeTables.ee;
        } {
          Destination = "10.42.0.0/24";
          Table = routeTables.ee;
        }];
      };
      networks."40-en-gnet" = (physicalNetwork routeTables.gnet "0xcafe" [{
        # Catch-all mop-up rule at the end.
        Family = "both";
        Priority = 32768;
        Table = routeTables.gnet;
      }]);
      networks."40-br-internal" = {
        networkConfig.VLAN = [ "vl-eduroam" ];
      };
      networks."40-en-int-eth" = {
        matchConfig.Name = "en-int-eth";
        networkConfig.Bridge = "br-internal";
      };
      networks."40-en-int-sfp" = {
        matchConfig.Name = "en-int-sfp";
        networkConfig.Bridge = "br-internal";
      };

      netdevs = let
        wireguard = { name, listenPort, privateKey, endpoint, publicKey, fwmark }: {
          netdevConfig = {
            Name = name;
            Kind = "wireguard";
            Description = "WireGuard tunnel ${name}";
          };
          wireguardConfig = {
            ListenPort = listenPort;
            PrivateKeyFile = privateKey;
            FirewallMark = hexToInt fwmark;
            RouteTable = "off";
          };
          wireguardPeers = [{
            Endpoint = endpoint;
            PublicKey = publicKey;
            AllowedIPs = [
              "0.0.0.0/0"
              "::/0"
            ];
          }];
        };
        rexxarWireguard = args: wireguard (args // {
          privateKey = config.my.vault.secrets.wg-rexxar-private.path;
          publicKey = "UnmWp6OI283EJfJ9PabZePq/IDsfk1ku/vilRy9TIg0=";
        });
        cofractalams01Wireguard = args: wireguard (args // {
          privateKey = config.my.vault.secrets.wg-cofractal-ams01-private.path;
          publicKey = "go9lHRpTrtPx8oGzsCs3bG1Z9bQBYsCk7l3T+1KBi1Y=";
        });
      in {
        "40-wg-rexxar-ee" = rexxarWireguard {
          name = "wg-rexxar-ee";
          listenPort = 51821;
          #endpoint = "92.118.28.252:51821";
          endpoint = "[2a09:a442:1000::]:51821";
          fwmark = "0xdead";
        };
        "40-wg-rexxar-gnet" = rexxarWireguard {
          name = "wg-rexxar-gnet";
          listenPort = 51822;
          endpoint = "92.118.30.251:51822";
          fwmark = "0xcafe";
        };
        "40-wg-cofractal-ee" = cofractalams01Wireguard {
          name = "wg-cofractal-ee";
          listenPort = 51823;
          #endpoint = "92.118.28.252:51821";
          endpoint = "[2a09:a446:1337:ffff::10]:51821";
          fwmark = "0xdead";
        };
        "40-wg-cofractal-gn" = cofractalams01Wireguard {
          name = "wg-cofractal-gn";
          listenPort = 51824;
          endpoint = "199.19.152.160:51822";
          fwmark = "0xcafe";
        };
        "50-wg-eta" = {
          netdevConfig = {
            Name = "wg-eta";
            Kind = "wireguard";
            Description = "WireGuard tunnel wg-eta";
          };
          wireguardConfig = {
            ListenPort = 51830;
            PrivateKeyFile = config.my.vault.secrets.wg-eta-private.path;
            RouteTable = "off";
            FirewallMark = hexToInt "0xcafe";  # over gnet
          };
          wireguardPeers = [{
            Endpoint = "shenfield-mythic.i.eta.st:51825";
            #PublicKey = config.my.vault.secrets.wg-eta-public.path;
            PublicKey = "JDelaz8FQBtJBRVd9CMYikO/25gKipYgfyXtjL6jgS8=";
            AllowedIPs = [
              "0.0.0.0/0"
              "::/0"
            ];
          }];
        };
        "20-br-internal" = {
          netdevConfig = {
            Name = "br-internal";
            Kind = "bridge";
            Description = "Bridge br-internal";
          };
          extraConfig = ''
            [Bridge]
            VLANFiltering=false
            MulticastQuerier=true
            MulticastSnooping=false
            STP=true
            VLANProtocol=802.1q
            MulticastIGMPVersion=3
          '';
        };
        "25-vl-eduroam" = {
          netdevConfig = {
            Name = "vl-eduroam";
            Kind = "vlan";
            Description = "Eduroam VLAN on br-internal";
          };
          vlanConfig = {
            Id = 100;
          };
        };
      };
    };
    my.vault.secrets = let
      wireguardSecret = key: {
        group = "systemd-network";
        template = ''
          {{- with secret "kv/apps/wireguard/swann" -}}
          {{- .Data.data.${key} -}}
          {{- end -}}
        '';
      };
    in {
      wg-eta-private = wireguardSecret "privateKeyToEta";
      wg-rexxar-private = wireguardSecret "privateKeyToRexxar";
      wg-cofractal-ams01-private = wireguardSecret "privateKeyToCofractalAms01";
    };
    services.mstpd.enable = true;
    my.ip.tailscale = "100.102.224.95";
    my.ip.tailscale6 = "fd7a:115c:a1e0:ab12:4843:cd96:6266:e05f";
    services.udev.extraRules = ''
      ATTR{address}=="e4:3a:6e:16:07:63", DRIVERS=="?*", NAME="en-ee"
      ATTR{address}=="e4:3a:6e:16:07:64", DRIVERS=="?*", NAME="en-gnet"
      ATTR{address}=="e4:3a:6e:16:07:67", DRIVERS=="?*", NAME="en-int-eth"
      ATTR{address}=="e4:3a:6e:16:08:bc", DRIVERS=="?*", NAME="en-int-sfp"
    '';
    boot.kernel.sysctl = {
      "net.ipv4.ip_forward" = "1";
      "net.ipv6.conf.default.forwarding" = "1";
      "net.ipv6.conf.all.forwarding" = "1";
      "net.ipv6.conf.en-ee.accept_ra" = "2";
      "net.ipv6.conf.en-gnet.accept_ra" = "2";
    };
    networking.nat = {
      enable = true;
      internalInterfaces = ["br-internal"];
      externalInterface = "en-gnet";
      extraCommands = ''
        # Send PS5 RTMP to totoro instead.
        # See DHCP static lease.
        iptables -w -t nat -A nixos-nat-pre --src 92.118.30.18 -p tcp --dport 1935 -j DNAT --to-destination 192.168.1.40

        # NAT packets going over EE plain.
        iptables -w -t nat -A nixos-nat-post -m mark --mark 1 -o en-ee -j MASQUERADE

        # NAT packets going over GNetwork plain.
        iptables -w -t nat -A nixos-nat-post -m mark --mark 1 -o en-gnet -j MASQUERADE

        # SNAT packets we're sending over tunnels.
        iptables -w -t nat -A nixos-nat-post -m mark --mark 1 -o wg-rexxar-ee -j SNAT --to-source 92.118.30.254
        iptables -w -t nat -A nixos-nat-post -m mark --mark 1 -o wg-rexxar-gnet -j SNAT --to-source 92.118.30.254
        iptables -w -t nat -A nixos-nat-post -m mark --mark 1 -o wg-cofractal-ee -j SNAT --to-source 92.118.30.254
        iptables -w -t nat -A nixos-nat-post -m mark --mark 1 -o wg-cofractal-gn -j SNAT --to-source 92.118.30.254

        # eduroam
        # > mark incoming eduroam packets
        iptables -w -t nat -A nixos-nat-pre -i vl-eduroam -j MARK --set-mark 2
        # > NAT packets going out directly.
        iptables -w -t nat -A nixos-nat-post -m mark --mark 2 -o en-ee -j MASQUERADE
        iptables -w -t nat -A nixos-nat-post -m mark --mark 2 -o en-gnet -j MASQUERADE
        # > NAT packets going over tunnels.
        iptables -w -t nat -A nixos-nat-post -m mark --mark 2 -o wg-rexxar-ee -j SNAT --to-source 92.118.30.253
        iptables -w -t nat -A nixos-nat-post -m mark --mark 2 -o wg-rexxar-gnet -j SNAT --to-source 92.118.30.253
        iptables -w -t nat -A nixos-nat-post -m mark --mark 2 -o wg-cofractal-ee -j SNAT --to-source 92.118.30.253
        iptables -w -t nat -A nixos-nat-post -m mark --mark 2 -o wg-cofractal-gn -j SNAT --to-source 92.118.30.253
      '';
    };
    services.erbium = {
      enable = false;  # TODO: true;
      package = depot.nix.pkgs.erbium;
      settings = {
        addresses = [
          # internal
          "192.168.1.0/24" "92.118.30.16/28" "2a09:a443::/64" "2a09:a443:1::/48"

          # eduroam
          "192.168.10.0/24" "2a09:a443:2::/64" "2a09:a443:3::/48"
        ];

        dns-servers = [ "$self4" "$self6" ];

        api-listeners = [ "[::1]:9968" ];
        dns-listeners = [ "[::1]:11153" ];  # if we don't specify something then erbium crashes

        router-advertisements = let
          baseline = {
            mtu = 1420;
            lifetime = "1h";
            reachable = "20m";
          };
          baselinePrefix = {
            on-link = true;
            autonomous = true;
            valid = "30d";
            preferred = "7d";
          };
        in {
          br-internal = baseline // {
            dns-servers.addresses = [ "2a09:a443::1" ];
            dns-search.domains = [ "house.as205479.net" ];

            prefixes = [(baselinePrefix // {
              prefix = "2a09:a443::/64";
            }) (baselinePrefix // {
              prefix = "2a09:a443:1::/48";
              autonomous = false;
            })];
          };
          vl-eduroam = baseline // {
            dns-servers.addresses = [ "2a09:a443:2::1" ];
            dns-search.domains = [ "eduroam.as205479.net" ];

            prefixes = [(baselinePrefix // {
              prefix = "2a09:a443:2::/64";
            }) (baselinePrefix // {
              prefix = "2a09:a443:3::/48";
              autonomous = false;
            })];
          };
        };

        dhcp-policies = [
          # public internal
          {
            apply-subnet = "92.118.30.16/28";
            apply-domain-name = "house-ext.as205479.net";
            apply-dns-servers = [ "92.118.30.17" ];
            apply-routers = [ "92.118.30.17" ];
            apply-mtu = 1420;
            policies = [{
              match-hardware-address = "bc:33:29:26:01:5c";
              apply-host-name = "ps5";
              apply-address = "92.118.30.18";
            }];
          }

          # private internal
          {
            match-subnet = "192.168.1.0/24";
            apply-range.start = "192.168.1.100";
            apply-range.end = "192.168.1.200";
            apply-domain-name = "house.as205479.net";
            apply-dns-servers = [ "192.168.1.1" ];
            apply-routers = [ "192.168.1.1" ];
            apply-mtu = 1420;
            policies = [{
              match-hardware-address = "40:8d:5c:1f:e8:68";
              apply-host-name = "totoro";
              apply-address = "192.168.1.40";
            } {
              match-hardware-address = "52:54:00:cf:cd:94";
              apply-host-name = "totoro-pfsense";
              apply-address = "192.168.1.41";
            } {
              match-hardware-address = "00:0d:5d:1b:14:ba";
              apply-host-name = "kvm";
              apply-address = "192.168.1.50";
            } {
              match-hardware-address = "9c:93:4e:ad:1f:7b";
              apply-host-name = "printer-xerox";
              apply-address = "192.168.1.51";
            } {
              match-hardware-address = "84:39:be:77:65:52";
              apply-host-name = "qvmpc6552";
              apply-address = "192.168.1.60";
            }];
          }


          # eduroam
          {
            match-subnet = "192.168.10.0/24";
            apply-range.start = "192.168.10.10";
            apply-range.end = "192.168.10.200";
            apply-domain-name = "eduroam.as205479.net";
            apply-dns-servers = [ "192.168.10.1" ];
            apply-routers = [ "192.168.10.1" ];
            apply-mtu = 1420;
          }
        ];
      };
    };

    networking.firewall = {
      interfaces.br-internal = {
        allowedTCPPorts = [
          8080 6789  # Unifi
          53  # DNS
        ];
        allowedUDPPorts = [
          3478 10001  # Unifi
          53  # DNS
          69  # TFTP
          80  # HTTP
        ];
      };
      interfaces.vl-eduroam = {
        allowedTCPPorts = [
          53  # DNS
        ];
        allowedUDPPorts = [
          53  # DNS
        ];
      };
      interfaces.en-ee = {
        allowedUDPPorts = [
          51821
        ];
      };
      interfaces.en-gnet = {
        allowedUDPPorts = [
          51822
        ];
      };
      interfaces.wg-rexxar-ee = {
        allowedUDPPorts = [
          3784  # BFD
        ];
      };
      interfaces.wg-rexxar-gnet = {
        allowedUDPPorts = [
          3784  # BFD
        ];
      };
      interfaces.wg-cofractal-ee = {
        allowedUDPPorts = [
          3784  # BFD
        ];
      };
      interfaces.wg-cofractal-gn = {
        allowedUDPPorts = [
          3784  # BFD
        ];
      };
      interfaces.wg-eta = {
        allowedTCPPorts = [
          179  # BGP
        ];
      };
      extraCommands = ''
        ip46tables -F FORWARD

        ip46tables -N ts-forward || true
        ip46tables -A FORWARD -j ts-forward

        iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
        ip6tables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360

        ip46tables -A FORWARD -i vl-eduroam -o wg-rexxar-ee -j ACCEPT
        ip46tables -A FORWARD -i vl-eduroam -o wg-rexxar-gnet -j ACCEPT
        ip46tables -A FORWARD -i vl-eduroam -o wg-cofractal-ee -j ACCEPT
        ip46tables -A FORWARD -i vl-eduroam -o wg-cofractal-gn -j ACCEPT
        ip46tables -A FORWARD -i vl-eduroam -o wg-eta -j ACCEPT
        ip46tables -A FORWARD -i vl-eduroam -m state --state NEW,RELATED -j REJECT
      '';
    };

    environment.systemPackages = with pkgs; [
      ethtool
      conntrack-tools
      minicom
      screen
      (writeShellApplication {
        name = "bridge-stp";
        runtimeInputs = [ mstpd ];
        text = ''
          BRIDGES=("br-internal")
          for BRIDGE in "''${BRIDGES[@]}"; do
            if [[ "$BRIDGE" = "$1" ]]; then
              if [[ "$2" = "start" ]]; then
                mstpctl addbridge "$BRIDGE"
                exit 0
              elif [[ "$2" = "stop" ]]; then
               mstpctl delbridge "$BRIDGE"
               exit 0
              fi
              exit 1
            fi
          done
          exit 1
        '';
      })
    ];

    services.coredns = {
      enable = true;
      config = ''
        .:53 {
          bind 192.168.1.1 92.118.30.17 192.168.10.1 127.0.0.253 2a09:a443::1 2a09:a443:1::1 2a09:a443:2::1 2a09:a443:3::1
          acl {
            allow net 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.0/8 100.64.0.0/10 2a09:a443::/32 92.118.30.0/24
            block
          }
          hosts /dev/null {
            #216.239.38.120 stadia.google.com stadia.com
            fallthrough
          }
          loadbalance
          forward . tls://8.8.8.8 tls://8.8.4.4 {
            tls_servername dns.google
          }
          cache {
            success 4096
            denial 1024
            prefetch 512
          }
          prometheus :9153
          errors
          log
        }
      '';
    };
    systemd.services.coredns = {
      wants = [ "systemd-networkd-wait-online.service" ];
      after = [ "systemd-networkd-wait-online.service" ];
    };
    my.prometheus.additionalExporterPorts.coredns = 9153;
    networking.resolvconf.extraConfig = ''
      name_servers='127.0.0.253'
    '';

    services.prometheus.exporters.smokeping = {
      enable = true;
      hosts = [
        "8.8.8.8"  # Google Public DNS
        "2001:4860:4860::8888"
        "youtube.com" "ads.google.com" "google.com"

        "1.1.1.1"  # Cloudflare DNS
        "2606:4700:4700::1111"

        "twitter.com"

        "store.steampowered.com"
        "api.steampowered.com"

        # "euw1.api.riotgames.com"  # League of Legends EUW

        "eu.battle.net"
        "185.60.112.157" "185.60.112.158"  # Diablo 3/HotS/Hearthstone
        "185.60.114.159"  # Overwatch

        # BFOB TS
        "2a01:a500:85:3::2"
        "37.9.61.53"

        # lukegb01.ring.nlnog.net
        "2a09:a441::13"
        "92.118.28.13"
      ];
    };

    services.bird2 = {
      enable = true;
      config = ''
        router id 92.118.30.254;

        protocol kernel {
          kernel table 150;
          metric 0;
          ipv4 {
            import none;
            export all;
          };
        };
        protocol kernel {
          kernel table 150;
          metric 0;
          ipv6 {
            import none;
            export all;
          };
        };
        protocol device {};

        protocol static export4 {
          ipv4 {
            import filter {
              if (ro, 205479, 1000) ~ bgp_ext_community then {
                bgp_ext_community.add((ro, 205479, 2000));  #
                bgp_ext_community.add((ro, 205479, 2001));  #
                bgp_ext_community.add((ro, 205479, 2002));  #
                bgp_ext_community.add((ro, 205479, 2003));  #
                bgp_ext_community.add((ro, 205479, 3000));  # clouvider from clouvider-lon01
                bgp_ext_community.add((ro, 205479, 4000));  # frantech from frantech-nyc01/veloxserv from etheroute-lon01
                bgp_ext_community.add((ro, 205479, 4001));  # veloxserv from rexxar
                bgp_ext_community.add((ro, 205479, 4002));  # mercury from etheroute-lon01
                bgp_ext_community.add((ro, 205479, 5000));  # linx route collector from rexxar
                bgp_ext_community.add((ro, 205479, 5001));  # linx route server from rexxar
                bgp_ext_community.add((ro, 205479, 5002));  # facebook from rexxar
                bgp_ext_community.add((ro, 205479, 5003));  # openpeering from rexxar
                bgp_ext_community.add((ro, 205479, 5004));  # freetransitnet from rexxar
                bgp_ext_community.add((ro, 205479, 5005));  # he from rexxar
                bgp_ext_community.add((ro, 205479, 5006));  # clouvider from rexxar
                bgp_ext_community.add((ro, 205479, 5007));  # google from rexxar
                bgp_ext_community.add((ro, 205479, 5008));  # cloudflare from rexxar
                bgp_ext_community.add((ro, 205479, 5009));  # fastly from rexxar
                bgp_ext_community.add((ro, 205479, 5010));  # ovh from rexxar
                bgp_ext_community.add((ro, 205479, 6000));  # EMF: EMF-IX Route Server
                bgp_ext_community.add((ro, 205479, 7000));  # 1299 from rexxar
              }
              accept;
            };
          };
          route 0.0.0.0/0 via 92.118.30.3 bfd {
            # EE, rexxar
            preference = 10;
          };
          route 0.0.0.0/0 via 92.118.30.5 bfd {
            # GNetwork, rexxar
            preference = 200;
          };
          route 0.0.0.0/0 via 92.118.30.1 bfd {
            # EE, cofractal-ams01
            preference = 10;
          };
          route 0.0.0.0/0 via 92.118.30.7 bfd {
            # GNetwork, cofractal-ams01
            preference = 200;
          };

          route 92.118.30.0/24 unreachable {
            bgp_ext_community.add((ro, 205479, 10));  # export this - internal
            bgp_ext_community.add((ro, 205479, 1000));  # export this
          };
          route 92.118.30.16/28 via "br-internal";
          route 92.118.30.254/32 via "lo";
          route 92.118.30.253/32 via "lo";
          route 92.118.30.0/31 via "wg-cofractal-ee";
          route 92.118.30.2/31 via "wg-rexxar-ee";
          route 92.118.30.4/31 via "wg-rexxar-gnet";
          route 92.118.30.6/31 via "wg-cofractal-gn";
        };
        protocol static export6 {
          ipv6 {
            import filter {
              if ((ro, 205479, 1000) ~ bgp_ext_community) then {
                bgp_ext_community.add((ro, 205479, 2000));  #
                bgp_ext_community.add((ro, 205479, 2001));  #
                bgp_ext_community.add((ro, 205479, 2002));  #
                bgp_ext_community.add((ro, 205479, 2003));  #
                bgp_ext_community.add((ro, 205479, 3000));  # clouvider from clouvider-lon01
                bgp_ext_community.add((ro, 205479, 4000));  # frantech from frantech-nyc01/veloxserv from etheroute-lon01
                bgp_ext_community.add((ro, 205479, 4001));  # veloxserv from rexxar
                bgp_ext_community.add((ro, 205479, 4002));  # mercury from etheroute-lon01
                bgp_ext_community.add((ro, 205479, 5000));  # linx route collector from rexxar
                bgp_ext_community.add((ro, 205479, 5001));  # linx route server from rexxar
                bgp_ext_community.add((ro, 205479, 5002));  # facebook from rexxar
                bgp_ext_community.add((ro, 205479, 5003));  # openpeering from rexxar
                bgp_ext_community.add((ro, 205479, 5004));  # freetransitnet from rexxar
                bgp_ext_community.add((ro, 205479, 5005));  # he from rexxar
                bgp_ext_community.add((ro, 205479, 5006));  # clouvider from rexxar
                bgp_ext_community.add((ro, 205479, 5007));  # google from rexxar
                bgp_ext_community.add((ro, 205479, 5008));  # cloudflare from rexxar
                bgp_ext_community.add((ro, 205479, 5009));  # fastly from rexxar
                bgp_ext_community.add((ro, 205479, 5010));  # ovh from rexxar
                bgp_ext_community.add((ro, 205479, 6000));  # EMF: EMF-IX Route Server
                bgp_ext_community.add((ro, 205479, 7000));  # 1299 from rexxar
              }
              accept;
            };
          };
          route ::/0 via 2a09:a442::1:2 bfd {
            # EE, cofractal-ams01
            preference = 10;
            krt_prefsrc = 2a09:a443::1;
          };
          route ::/0 via 2a09:a442::2:2 bfd {
            # EE, rexxar
            preference = 10;
            krt_prefsrc = 2a09:a443::1;
          };
          route ::/0 via 2a09:a442::3:2 bfd {
            # GNetwork, rexxar
            preference = 200;
            krt_prefsrc = 2a09:a443::1;
          };
          route ::/0 via 2a09:a442::4:2 bfd {
            # GNetwork, cofractal-ams01
            preference = 200;
            krt_prefsrc = 2a09:a443::1;
          };

          # Covering route...
          route 2a09:a443::/64 via "br-internal";
          route 2a09:a443:1::/48 via "br-internal";
          route 2a09:a443:2::/64 via "vl-eduroam";
          route 2a09:a443:3::/48 via "vl-eduroam";
          route 2a09:a443::/32 unreachable {
            bgp_ext_community.add((ro, 205479, 10));  # export this - internal
            bgp_ext_community.add((ro, 205479, 1000));  # export this
          };
        };

        protocol bfd {
          interface "*" {
            min rx interval 10ms;
            min tx interval 50ms;
            idle tx interval 1s;
            multiplier 20;
          };
          neighbor 92.118.30.1;
          neighbor 2a09:a442::1:2;
          neighbor 92.118.30.3;
          neighbor 2a09:a442::2:2;
          neighbor 92.118.30.5;
          neighbor 2a09:a442::3:2;
          neighbor 92.118.30.7;
          neighbor 2a09:a442::4:2;
        };

        protocol bgp rexxar_ee4 {
          local 92.118.30.2 as 205479;
          neighbor 92.118.30.3 as 205479;
          interface "wg-rexxar-ee";
          direct;
          bfd on;

          ipv4 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp rexxar_ee6 {
          local 2a09:a442::2:1 as 205479;
          neighbor 2a09:a442::2:2 as 205479;
          interface "wg-rexxar-ee";
          direct;
          bfd on;

          ipv6 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp rexxar_gnet4 {
          local 92.118.30.4 as 205479;
          neighbor 92.118.30.5 as 205479;
          interface "wg-rexxar-gnet";
          direct;
          bfd on;

          ipv4 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp rexxar_gnet6 {
          local 2a09:a442::3:1 as 205479;
          neighbor 2a09:a442::3:2 as 205479;
          interface "wg-rexxar-gnet";
          direct;
          bfd on;

          ipv6 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp cofractal_ee4 {
          local 92.118.30.0 as 205479;
          neighbor 92.118.30.1 as 205479;
          interface "wg-cofractal-ee";
          direct;
          bfd on;

          ipv4 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp cofractal_ee6 {
          local 2a09:a442::1:1 as 205479;
          neighbor 2a09:a442::1:2 as 205479;
          interface "wg-cofractal-ee";
          direct;
          bfd on;

          ipv6 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp cofractal_gnet4 {
          local 92.118.30.6 as 205479;
          neighbor 92.118.30.7 as 205479;
          interface "wg-cofractal-gn";
          direct;
          bfd on;

          ipv4 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };
        protocol bgp cofractal_gnet6 {
          local 2a09:a442::4:1 as 205479;
          neighbor 2a09:a442::4:2 as 205479;
          interface "wg-cofractal-gn";
          direct;
          bfd on;

          ipv6 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 10) ~ bgp_ext_community) then reject;
              accept;
            };
            import filter {
              accept;
            };
            next hop self;
          };
        };

        protocol bgp eta4 {
          local 169.254.2.1 as 205479;
          neighbor 169.254.2.0 as 213185;
          interface "wg-eta";

          ipv4 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 1000) ~ bgp_ext_community) then reject;

              bgp_ext_community.delete([(ro, 205479, *)]);
              accept;
            };
            import filter {
              if ! (net ~ [
                44.31.189.0/24
              ]) then reject;
              accept;
            };
            next hop self;
          };
        };
        protocol bgp eta6 {
          local fe80:1234::b as 205479;
          neighbor fe80:1234::a as 213185;
          interface "wg-eta";

          ipv6 {
            export filter {
              if source != RTS_STATIC then reject;
              if ! ((ro, 205479, 1000) ~ bgp_ext_community) then reject;

              bgp_ext_community.delete([(ro, 205479, *)]);
              accept;
            };
            import filter {
              if ! (net ~ [
                2a0d:1a40:7553::/48{48,64}
              ]) then reject;
              accept;
            };
            next hop self;
          };
        };
      '';
    };

    systemd.services.prometheus-bird-exporter.serviceConfig.ExecStart = lib.mkForce ''
      ${depot.pkgs.prometheus-bird-exporter-lfty}/bin/bird_exporter \
        -web.listen-address 0.0.0.0:9324 \
        -bird.socket /var/run/bird/bird.ctl \
        -bird.v2=true \
        -format.new=true
    '';

    systemd.services.ee-scrape-data = let
      scriptFile = ./ee-scrape-data.py;
      python = pkgs.python3.withPackages (pm: with pm; [
        requests
        beautifulsoup4
        html5lib
      ]);
    in {
      enable = true;
      serviceConfig = {
        Type = "oneshot";
        ExecStart = "${python}/bin/python ${scriptFile} /run/prometheus-textfile-exports/ee-scrape-data.prom";
      };
    };
    systemd.timers.ee-scrape-data = {
      enable = true;
      wantedBy = [ "multi-user.target" ];
      timerConfig = {
        OnBootSec = "2m";
        OnUnitInactiveSec = "1m";
        RandomizedDelaySec = "20";
      };
    };

    system.stateVersion = "21.03";
  } {
    # Minimize writes to storage.
    boot.tmp.useTmpfs = true;
    services.journald.extraConfig = ''
      Storage=volatile
    '';
    systemd.services.tailscaled.environment.TS_LOGS_DIR = "/var/run/tailscale";
  } (lib.mkIf (!config.services.erbium.enable) {
    services.radvd = {
      enable = true;
      config = ''
        interface br-internal {
          AdvSendAdvert on;
          AdvLinkMTU 1420;  # Wireguard
          AdvManagedFlag on;

          RDNSS 2a09:a443::1 {};
          DNSSL house.as205479.net {};

          prefix 2a09:a443::/64 {
            AdvOnLink on;
            AdvAutonomous on;
          };
          prefix 2a09:a443:1::/48 {
            AdvOnLink on;
            AdvAutonomous off;
          };
        };
        interface vl-eduroam {
          AdvSendAdvert on;
          AdvLinkMTU 1420;  # Wireguard
          AdvManagedFlag on;

          RDNSS 2a09:a443:2::1 {};
          DNSSL eduroam.as205479.net {};

          prefix 2a09:a443:2::/64 {
            AdvOnLink on;
            AdvAutonomous on;
          };
          prefix 2a09:a443:3::/48 {
            AdvOnLink on;
            AdvAutonomous off;
          };
        };
      '';
    };

    services.atftpd = {
      enable = true;
    };
    services.nginx = {
      enable = true;
      virtualHosts."192.168.1.1".root = "/srv/tftp";
    };

    services.kea = {
      dhcp4.enable = true;
      dhcp4.settings = {
        interfaces-config.interfaces = ["br-internal" "vl-eduroam"];
        lease-database = {
          type = "memfile";
          persist = true;
          name = "/var/lib/kea/dhcp4.leases";
        };
        reservations-global = true;
        reservations = [{
          hw-address = "40:8d:5c:1f:e8:68";
          ip-address = "192.168.1.40";
          hostname = "totoro";
        } {
          hw-address = "52:54:00:cf:cd:94";
          ip-address = "192.168.1.41";
          hostname = "totoro-pfsense";
        } {
          hw-address = "00:0d:5d:1b:14:ba";
          ip-address = "192.168.1.50";
          hostname = "kvm";
        } {
          hw-address = "9c:93:4e:ad:1f:7b";
          ip-address = "192.168.1.51";
          hostname = "printer-xerox";
        } {
          hw-address = "e8:ed:f3:6a:b9:24";
          ip-address = "192.168.1.52";
          hostname = "phone-lukegb-desk";
          option-data = [{
            name = "tftp-server-name";
            data = "192.168.1.1";
          }];
        } {
          hw-address = "bc:33:29:26:01:5c";
          ip-address = "92.118.30.18";
          hostname = "ps5";
        } {
          hw-address = "84:39:be:77:65:52";
          ip-address = "192.168.1.60";
          hostname = "qvmpc6552";
        }];
        subnet4 = [{
          id = 1;
          subnet = "192.168.1.0/24";
          pools = [{
            pool = "192.168.1.100 - 192.168.1.200";
          }];
          option-data = [{
            name = "routers";
            data = "192.168.1.1";
          } {
            name = "domain-name-servers";
            data = "192.168.1.1";
          } {
            name = "domain-name";
            data = "house.as205479.net";
          } {
            name = "interface-mtu";
            data = "1420";
          }];
        } {
          id = 2;
          subnet = "92.118.30.16/28";
          option-data = [{
            name = "routers";
            data = "92.118.30.17";
          } {
            name = "domain-name-servers";
            data = "92.118.30.17";
          } {
            name = "domain-name";
            data = "house-ext.as205479.net";
          } {
            name = "interface-mtu";
            data = "1420";
          }];
        } {
          id = 10;
          subnet = "192.168.10.0/24";
          pools = [{
            pool = "192.168.10.100 - 192.168.10.200";
          }];
          option-data = [{
            name = "routers";
            data = "192.168.10.1";
          } {
            name = "domain-name-servers";
            data = "192.168.10.1";
          } {
            name = "domain-name";
            data = "eduroam.as205479.net";
          } {
            name = "interface-mtu";
            data = "1420";
          }];
        }];
      };

      dhcp6.enable = true;
      dhcp6.settings = {
        interfaces-config.interfaces = ["br-internal" "vl-eduroam"];
        lease-database = {
          type = "memfile";
          persist = true;
          name = "/var/lib/kea/dhcp6.leases";
        };
        subnet6 = [{
          id = 1;
          subnet = "2a09:a443:1::/48";
          pd-pools = [{
            prefix = "2a09:a443:1:8000::";
            prefix-len = 49;
            delegated-len = 64;
          }];
          option-data = [{
            name = "dns-servers";
            data = "2a09:a443:1::1";
          } {
            name = "domain-search";
            data = "house.as205479.net";
          }];
        } {
          id = 3;
          subnet = "2a09:a443:3::/48";
          pd-pools = [{
            prefix = "2a09:a443:3:8000::";
            prefix-len = 49;
            delegated-len = 64;
          }];
          option-data = [{
            name = "dns-servers";
            data = "2a09:a443:3::1";
          } {
            name = "domain-search";
            data = "eduroam.as205479.net";
          }];
        }];
      };
    };
  })];
}