{ config, pkgs, lib, ... }: let inherit (lib) mkOption types; mkVLAN = name: id: { netdevConfig = { Kind = "vlan"; Name = name; }; vlanConfig.Id = id; }; mkTunnel = kind: name: { local, remote, mtu ? 1480 }: { netdevConfig = { Kind = kind; Name = name; MTUBytes = toString mtu; }; tunnelConfig = { Local = local; Remote = remote; }; }; vip = config.router.wan.vip; rip = config.router.wan.rip; in { options.router = { wan = { vip = mkOption { type = types.str; description = "Highly-available virtual IP address of the router"; }; rip = mkOption { type = types.str; description = "Real IP address of the router"; }; }; }; config = { router.wan.vip = "129.199.146.230"; router.wan.rip = "129.199.146.231"; networking.firewall.allowedUDPPorts = [ 25351 ]; systemd.network.enable = true; networking.dhcpcd.enable = false; systemd.network.links."10-swp" = { matchConfig.MACAddress = "92:E3:9C:CE:EF:14"; linkConfig.Name = "swp"; }; systemd.network = { config.routeTables = { he = 100; mwan = 110; }; netdevs = { "05-admin-vpn" = { netdevConfig = { Kind = "wireguard"; Name = "wgadmin"; MTUBytes = "1420"; }; wireguardConfig = { PrivateKeyFile = "/etc/secrets/wireguard/wgadmin"; ListenPort = 25351; }; wireguardPeers = [ { wireguardPeerConfig = { PublicKey = "obsUPq4Y1XGbl3yPUytPKkVcSP+eECpaQX+bV+ocwXg="; AllowedIPs = [ "fd81:fb3a:50cc::100/128" ]; }; } ]; }; "10-tun-mwan" = mkTunnel "gre" "gre-mwan" { remote = "80.67.167.30"; local = vip; }; "10-tun-he" = mkTunnel "sit" "sit-he" { remote = "216.66.84.42"; local = vip; }; # VLANs # 401: uplink ENS # 3500: intranet club réseau, proxy ARP et proxy arp pvlan / 10.1.1.1/22 # 3510: mgmt club réseau (administration network) / fd81:fb3a:50cc::/64 # 3605: MWAN V6 DMZ / 2a0e:e701:1120:b00c::1/64 # 3606: MWAN V4 DMZ / 45.13.104.25/29 # 3607: Club Réseau v6 DMZ (en ASN propre) # 3608: DN42 DMZ # 3609: HE V6 DMZ / 2001:470:1f13:187::1/64 # 3610: Free V6 DMZ # 3620: HE.net IPv6 /48 -> DHCP-PD /60 # 3621: MWAN DMZ /48 PD delivery / 2a0e:e701:1120::1/48 # 3622: Router VRRP link / $to_be_determined. # "10-uplink-ens" = mkVLAN "uplink-ens" 401; dysfunctional? "10-intranet-krz" = mkVLAN "intranet-krz" 3500; "10-admin" = mkVLAN "admin" 3510; "10-mwan-v6" = mkVLAN "mwan-v6" 3605; "10-mwan-dual" = mkVLAN "mwan-dual" 3606; "10-krz-v6" = mkVLAN "krz-v6" 3607; "10-dn42-dmz" = mkVLAN "dn42-dmz" 3608; "10-he-dmz" = mkVLAN "he-dmz" 3609; "10-free-dmz" = mkVLAN "free-dmz" 3610; "10-he-pd" = mkVLAN "he-v6-pd" 3620; "10-mwan-pd" = mkVLAN "mwan-v6-pd" 3621; "10-vrrp-router" = mkVLAN "vrrp-router" 3622; }; networks = { "10-admin-vpn" = { matchConfig.Name = "wgadmin"; networkConfig = { Description = "VPN d'administration système de l'infrastructure"; Address = [ "fd81:fb3a:50cc::1/64" ]; # Give access to the rest of the network. IPForward = "ipv6"; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "routable"; }; "15-admin-vlan" = { matchConfig.Name = "admin"; networkConfig = { Description = "VLAN d'administration système de l'infrastructure"; Address = [ "fd81:fb3a:50cc:1::1/48" ]; # Give access to the rest of the network. IPForward = "ipv6"; IPv6ProxyNDP = true; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "routable"; }; "20-tun-mwan" = { matchConfig.Name = "gre-mwan"; networkConfig = { Description = "Tunnel de livraison GRE IPv4/IPv6 de MilkyWAN"; Address = [ "10.1.1.50/30" "2a0b:cbc0:1::216/126" ]; ConfigureWithoutCarrier = true; }; routes = [ { routeConfig = { Gateway = "10.1.1.49"; Table = "mwan"; Scope = "global"; # FIXME(raito): Has no effect? Upstream bug? Source = "45.13.104.25/29"; }; } { routeConfig = { Destination = "::/0"; Gateway = "2a0b:cbc0:1::215"; Table = "mwan"; Scope = "global"; Source = "2a0e:e701:1120::/48"; }; } ]; routingPolicyRules = [ { routingPolicyRuleConfig = { From = "2a0e:e701:1120::/48"; Table = "mwan"; }; } { routingPolicyRuleConfig = { From = "45.13.104.25/29"; Table = "mwan"; }; } { routingPolicyRuleConfig = { To = "45.13.104.25/29"; Table = "mwan"; }; } ]; }; "20-tun-he" = { matchConfig.Name = "sit-he"; networkConfig = { Description = "HE.NET IPv6 Tunnel (owned by gdd)"; Address = [ "2001:470:1f12:187::2/64" ]; ConfigureWithoutCarrier = true; }; routes = [ { routeConfig = { Destination = "::/0"; Table = "he"; Scope = "global"; Source = "2001:470:1f13::/48"; }; } ]; routingPolicyRules = [ { routingPolicyRuleConfig = { From = "2001:470:1f13::/48"; Table = "he"; }; } ]; }; "10-swp" = { matchConfig.Name = "swp"; networkConfig = { Description = "VLAN-aware switch port"; Address = [ "${rip}/24" ]; Gateway = "129.199.146.254"; LLDP = true; # Only to the switch we are connected to directly, e.g. the hypervisor or the switch. EmitLLDP = "nearest-bridge"; # For VRRP. KeepConfiguration = true; }; routingPolicyRules = [ { routingPolicyRuleConfig = { From = "45.13.104.25/29"; Type = "prohibit"; }; } ]; tunnel = [ "gre-mwan" "sit-he" ]; vlan = [ # "intranet-krz" - we don't want to keep this. "admin" # FIXME: "mwan-v6" - do we want to keep this? # We can achieve v6-only by enforcing MAC address isolation for IPv4. "mwan-dual" # FIXME: legacy-nat-zone. # FIXME: "krz-v6" - not ready yet. # FIXME: "dn42-dmz" - revive this if you want. "he-dmz" # FIXME: "free-dmz" - not ready yet, abandoned? # FIXME: "he-v6-pd" - require rework # FIXME: "mwan-v6-pd" - require rework ]; }; # TODO: SIIT/NAT64/DNS64 component to avoid IPv4 dependency. "20-mwan-dual" = { matchConfig.Name = "mwan-dual"; addresses = [ { addressConfig = { Address = "2a0e:e701:1120:b00c::1/64"; AddPrefixRoute = false; }; } { addressConfig = { Address = "45.13.104.25/29"; AddPrefixRoute = false; }; } ]; routes = [ { routeConfig = { Destination = "2a0e:e701:1120:b00c::/64"; Metric = 256; Table = "mwan"; }; } { routeConfig = { Destination = "45.13.104.25/29"; Metric = 256; Table = "mwan"; }; } ]; networkConfig = { Description = "MilkyWAN dual stack public interface"; DHCPServer = true; IPv6SendRA = true; IPForward = true; ConfigureWithoutCarrier = true; }; }; "20-he-dmz" = { matchConfig.Name = "he-dmz"; addresses = [ { addressConfig = { Address = "2001:470:1f13:187::1/64"; # This will add it in the wrong table. # TODO: add to systemd a `Table` option here. AddPrefixRoute = false; }; } ]; routes = [ { routeConfig = { Destination = "2001:470:1f13:187::/64"; Metric = 256; Table = "he"; }; } ]; networkConfig = { Description = "Hurricane Electrical's 187 /64 unfirewalled zone"; IPv6SendRA = true; ConfigureWithoutCarrier = true; }; }; }; }; # services.keepalived.enable = true; # services.keepalived.vrrpInstances.wan = { # interface = "swp"; # state = "MASTER"; # priority = 50; # virtualIps = [{ addr = "129.199.146.230"; }]; # virtualRouterId = 1; # }; # systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug"; environment.systemPackages = [ pkgs.tcpdump pkgs.wireguard-tools ]; # Zone based firewall # Flow accounting in PostgreSQL. services.postgresql = { enable = true; ensureUsers = []; }; # services.ulogd = { # enable = true; # settings = { # }; # }; }; }