diff --git a/modules/nixos/hosts/masthead/conntrack/default.nix b/modules/nixos/hosts/masthead/conntrack/default.nix new file mode 100644 index 0000000..594f045 --- /dev/null +++ b/modules/nixos/hosts/masthead/conntrack/default.nix @@ -0,0 +1,71 @@ +{ + config, + lib, + pkgs, + namespace, + ... +}: + +with lib; +let + cfg = config.${namespace}.hosts.masthead; +in +{ + config = mkIf cfg.enable { + environment.etc."conntrackd/conntrackd.conf".text = '' + Sync { + Mode FTFW { + ResendQueueSize 131072 + CommitTimeout 180 + PurgeTimeout 5 + ACKWindowSize 300 + DisableExternalCache Off + } + + Multicast { + IPv4_address 225.0.0.50 + IPv4_interface vlan40 + Port 3780 + Group 3780 + } + } + + General { + HashSize 32768 + HashLimit 131072 + Syslog on + LockFile /var/lock/conntrack.lock + UNIX { + Path /var/run/conntrackd.ctl + Backlog 20 + } + NetlinkBufferSize 2097152 + NetlinkBufferSizeMaxGrowth 8388608 + Filter From Userspace { + Protocol Accept { + TCP + SCTP + DCCP + } + Address Ignore { + IPv4_address 127.0.0.1 + } + } + } + ''; + + systemd.services.conntrackd = { + description = "Connection tracking state synchronization daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + path = [ + pkgs.conntrack-tools + pkgs.iproute2 + ]; + serviceConfig = { + ExecStart = "${pkgs.conntrack-tools}/bin/conntrackd -C /etc/conntrackd/conntrackd.conf"; + Restart = "always"; + }; + }; + }; +} diff --git a/modules/nixos/hosts/masthead/default.nix b/modules/nixos/hosts/masthead/default.nix index e84b42a..4a3c18b 100644 --- a/modules/nixos/hosts/masthead/default.nix +++ b/modules/nixos/hosts/masthead/default.nix @@ -13,12 +13,26 @@ let cfg = config.${namespace}.hosts.masthead; in { + imports = [ + ./vrrp + ./multi-wan + ./conntrack + ]; + options.${namespace}.hosts.masthead = with types; { enable = mkBoolOpt false "Whether or not to enable the masthead router base config."; role = mkOpt (types.enum [ "primary" "backup" ]) "primary" "The role of the masthead router."; + wanMac = mkOpt types.str "00:00:00:00:00:00" "MAC address for WAN spoofing."; + wanVip = mkOpt types.str "203.0.113.100" "Virtual IP for WAN interface."; + lanVip = mkOpt types.str "172.16.1.1" "Virtual IP for LAN interface."; + vlan10Vip = mkOpt types.str "192.168.10.1" "Virtual IP for VLAN 10."; + vlan21Vip = mkOpt types.str "192.168.21.1" "Virtual IP for VLAN 21."; + vlan22Vip = mkOpt types.str "192.168.22.1" "Virtual IP for VLAN 22."; + vlan30Vip = mkOpt types.str "192.168.30.1" "Virtual IP for VLAN 30."; + healthCheckIp = mkOpt types.str "8.8.8.8" "IP address for Multi-WAN health check."; }; config = mkIf cfg.enable { @@ -29,6 +43,60 @@ in }; }; + networking.interfaces.lan0 = { + ipv4.addresses = [ + { + address = if cfg.role == "primary" then "172.16.1.2" else "172.16.1.3"; + prefixLength = 24; + } + ]; + }; + + networking.interfaces.vlan10 = { + ipv4.addresses = [ + { + address = if cfg.role == "primary" then "192.168.10.2" else "192.168.10.3"; + prefixLength = 24; + } + ]; + }; + + networking.interfaces.vlan21 = { + ipv4.addresses = [ + { + address = if cfg.role == "primary" then "192.168.21.2" else "192.168.21.3"; + prefixLength = 24; + } + ]; + }; + + networking.interfaces.vlan22 = { + ipv4.addresses = [ + { + address = if cfg.role == "primary" then "192.168.22.2" else "192.168.22.3"; + prefixLength = 24; + } + ]; + }; + + networking.interfaces.vlan30 = { + ipv4.addresses = [ + { + address = if cfg.role == "primary" then "192.168.30.2" else "192.168.30.3"; + prefixLength = 24; + } + ]; + }; + + networking.interfaces.vlan40 = { + ipv4.addresses = [ + { + address = if cfg.role == "primary" then "169.254.255.1" else "169.254.255.2"; + prefixLength = 24; + } + ]; + }; + networking.vlans = { vlan1 = { id = 1; @@ -50,6 +118,10 @@ in id = 30; interface = "lan0"; }; + vlan40 = { + id = 40; + interface = "lan0"; + }; }; # Configure DNS resolvers diff --git a/modules/nixos/hosts/masthead/multi-wan/default.nix b/modules/nixos/hosts/masthead/multi-wan/default.nix new file mode 100644 index 0000000..45556f1 --- /dev/null +++ b/modules/nixos/hosts/masthead/multi-wan/default.nix @@ -0,0 +1,53 @@ +{ + config, + lib, + pkgs, + namespace, + ... +}: + +with lib; +let + cfg = config.${namespace}.hosts.masthead; +in +{ + config = mkIf cfg.enable { + systemd.services.multi-wan-healthcheck = { + description = "Multi-WAN Health Check and Failover Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + path = [ + pkgs.iputils + pkgs.iproute2 + pkgs.gawk + ]; + + script = '' + set -euo pipefail + + while true; do + # Dynamically resolve gateway via wan0 + GATEWAY=$(ip -4 route show default dev wan0 2>/dev/null | awk '{print $3}' | head -n 1 || true) + + if [ -n "$GATEWAY" ]; then + if ping -I wan0 -c 1 -W 2 "${cfg.healthCheckIp}" > /dev/null 2>&1; then + # Ping succeeded. Ensure default route exists with appropriate metric + ip route replace default via "$GATEWAY" dev wan0 metric 10 + else + # Ping failed. Set metric to 1000 so it acts as disabled but gateway remains discoverable + ip route replace default via "$GATEWAY" dev wan0 metric 1000 || true + fi + fi + + sleep 10 + done + ''; + + serviceConfig = { + Restart = "always"; + RestartSec = "5s"; + }; + }; + }; +} diff --git a/modules/nixos/hosts/masthead/vrrp/default.nix b/modules/nixos/hosts/masthead/vrrp/default.nix new file mode 100644 index 0000000..d04b187 --- /dev/null +++ b/modules/nixos/hosts/masthead/vrrp/default.nix @@ -0,0 +1,83 @@ +{ + config, + lib, + pkgs, + namespace, + ... +}: + +with lib; +let + cfg = config.${namespace}.hosts.masthead; + priority = if cfg.role == "primary" then 255 else 100; +in +{ + config = mkIf cfg.enable { + networking.vrrp.enable = true; + + networking.vrrp.vrrpInstances = { + lan0 = { + virtualRouterId = 20; + priority = priority; + interface = "lan0"; + virtualIPs = [ + { + address = cfg.lanVip; + prefixLength = 24; + } + ]; + notifyMaster = "${pkgs.writeShellScript "notify-master-lan0" '' + ${pkgs.iproute2}/bin/ip link set dev wan0 address ${cfg.wanMac} + ${pkgs.iproute2}/bin/ip link set dev wan0 up + ''}"; + notifyBackup = "${pkgs.writeShellScript "notify-backup-lan0" '' + ${pkgs.iproute2}/bin/ip link set dev wan0 down + ''}"; + }; + vlan10 = { + virtualRouterId = 30; + priority = priority; + interface = "vlan10"; + virtualIPs = [ + { + address = cfg.vlan10Vip; + prefixLength = 24; + } + ]; + }; + vlan21 = { + virtualRouterId = 40; + priority = priority; + interface = "vlan21"; + virtualIPs = [ + { + address = cfg.vlan21Vip; + prefixLength = 24; + } + ]; + }; + vlan22 = { + virtualRouterId = 50; + priority = priority; + interface = "vlan22"; + virtualIPs = [ + { + address = cfg.vlan22Vip; + prefixLength = 24; + } + ]; + }; + vlan30 = { + virtualRouterId = 60; + priority = priority; + interface = "vlan30"; + virtualIPs = [ + { + address = cfg.vlan30Vip; + prefixLength = 24; + } + ]; + }; + }; + }; +}