# Home network configuration # (2.5GbE clients) # | | # +----------+ +----------+ # | | | | | | (1GbE clients) # | | | | | +|-|-|-|-|+ # +---+----+ +-+-+-+-+-+ |5 4 3 2 1| # |upstream| | 1 2 3 4 | |TL-SG105 | # +--------+ | shinobu | +---------+ # +---------+ # # It consists of shinobu as a router (this configuration), # connected to a TP-LINK TL-SG105E “smart managed” (i.e., it can do VLANs) 5-port switch. # The upstream comes from some plasic Huawei router/AP I don’t control. # # Because the switch only supports GbE, # the two clients I currently have with support for 2.5GbE are connected # directly to the two remaining network interfaces on shinobu. # Once I have more devices with support for 2.5GbE # or I find a good deal on a matching switch, # I will change this. # # Wireless is configured by providing the whole hostapd configuration file as a secret. # Once nixpkgs PR 222536 is merged, I will migrate to using the NixOS module. # Thanks to Intel’s wisdom, it’s not possible to use 5GHz in AP mode. { config, lib, pkgs, ... }: let domain = "home.sbruder.de"; noVpnFwMark = 10000; in { sops.secrets.wg-upstream-private-key = { owner = config.users.users.systemd-network.name; sopsFile = ../secrets.yaml; }; sops.secrets.hostapd-config = { sopsFile = ../secrets.yaml; }; boot.kernel.sysctl = { "net.ipv4.conf.all.forwarding" = true; }; networking = { # networkd handles this useDHCP = false; nftables = { enable = true; ruleset = '' define NAT_LAN_IFACES = { "br-lan" } define NAT_WAN_IFACES = { "wg-upstream" } define PHYSICAL_WAN = "enp1s0" define MASQUERADE_IFACES = { $NAT_WAN_IFACES, $PHYSICAL_WAN } define VUEKO_V4 = 168.119.176.53 define VUEKO_V6 = 2a01:4f8:c012:2f4:: define VUEKO_PORT = 51820 table inet filter { chain forward { type filter hook forward priority filter; policy drop; iifname $NAT_LAN_IFACES oifname $NAT_WAN_IFACES counter accept; iifname $NAT_WAN_IFACES oifname $NAT_LAN_IFACES ct state established,related counter accept; iifname $NAT_LAN_IFACES oifname $PHYSICAL_WAN ip daddr $VUEKO_V4 udp dport $VUEKO_PORT counter accept; iifname $PHYSICAL_WAN oifname $NAT_LAN_IFACES ip saddr $VUEKO_V4 udp sport $VUEKO_PORT ct state established,related counter accept; iifname $NAT_LAN_IFACES oifname $PHYSICAL_WAN ip6 daddr $VUEKO_V6 udp dport $VUEKO_PORT counter accept; iifname $PHYSICAL_WAN oifname $NAT_LAN_IFACES ip6 saddr $VUEKO_V6 udp sport $VUEKO_PORT ct state established,related counter accept; } } table inet nat { chain prerouting { type nat hook prerouting priority filter; policy accept; } chain postrouting { type nat hook postrouting priority filter; policy accept; oifname $MASQUERADE_IFACES masquerade; } } table inet mangle { chain output { type route hook output priority mangle; # Add fwmark noVpnMark to packets to vueko, so it will get routed correctly ip daddr $VUEKO_V4 udp dport $VUEKO_PORT mark set ${toString noVpnFwMark} counter; ip6 daddr $VUEKO_V6 udp dport $VUEKO_PORT mark set ${toString noVpnFwMark} counter; } } ''; }; }; systemd.network = { enable = true; # not all interfaces need to be up wait-online.extraArgs = [ "--any" ]; netdevs = { br-lan = { netdevConfig = { Name = "br-lan"; Kind = "bridge"; }; }; wg-upstream = { netdevConfig = { Kind = "wireguard"; Name = "wg-upstream"; }; wireguardConfig = { PrivateKeyFile = config.sops.secrets.wg-upstream-private-key.path; FirewallMark = 51820; }; wireguardPeers = lib.singleton { wireguardPeerConfig = { Endpoint = "193.32.248.71:51820"; PublicKey = "eprzkkkSbXCANngQDo305DIAvkKAnZaN71IpTNaOoTk="; AllowedIPs = [ "0.0.0.0/0" "::0/0" ]; PersistentKeepalive = 25; }; }; }; }; networks = { wan = { name = "enp1s0"; networkConfig = { # Upstream provides no IPv6 :( # If this is not set, it waits and fails systemd-networkd-wait-online LinkLocalAddressing = "no"; IPv6AcceptRA = "no"; }; DHCP = "ipv4"; dhcpV4Config = { UseDNS = "no"; }; }; lan1 = { name = "enp2s0"; bridge = [ "br-lan" ]; }; lan2 = { name = "enp3s0"; bridge = [ "br-lan" ]; }; lan3 = { name = "enp4s0"; bridge = [ "br-lan" ]; }; br-lan = { name = "br-lan"; domains = [ domain ]; address = [ "10.80.1.1/24" "fd00:80:1::1/64" ]; }; wg-upstream = { name = "wg-upstream"; address = [ "10.66.208.88/32" "fc00:bbbb:bbbb:bb01::3:d057/128" ]; dns = [ "10.64.0.1" ]; routingPolicyRules = [ { routingPolicyRuleConfig = { FirewallMark = 51820; InvertRule = "yes"; Table = 51820; Priority = 10; #SuppressPrefixLength = 0; # can’t be used here (forwarding does not work with it) }; } # FIXME: those two shouldn’t be necessary # It should automatically detect those routes existing and prioritise them { routingPolicyRuleConfig = { To = "10.80.0.0/24"; Priority = 9; }; } { routingPolicyRuleConfig = { To = "10.80.1.0/24"; Priority = 9; }; } { routingPolicyRuleConfig = { To = "168.119.176.53"; FirewallMark = noVpnFwMark; Priority = 9; }; } ]; routes = [ { routeConfig = { Gateway = "0.0.0.0"; # point-to-point connection Table = 51820; }; } { routeConfig = { Gateway = "::"; }; } ]; }; }; }; services.resolved.enable = false; services.dnsmasq = { enable = true; settings = { bogus-priv = true; # do not forward revese lookups of internal addresses domain-needed = true; # do not forward names without domain interface = "br-lan"; # only respond to queries from lan no-hosts = true; # do not resolve hosts from /etc/hosts no-resolv = true; # only use explicitly configured resolvers cache-size = 10000; inherit domain; # Allow resolving the router interface-name = [ "${config.networking.hostName}.${domain},br-lan" "${config.networking.hostName},br-lan" ]; # DHCPv4 dhcp-range = [ "10.80.1.20,10.80.1.150,12h" # DHCPv4 "fd00:80:1::,ra-stateless,ra-names" # SLAAC (for addresses) / DHCPv6 (for DNS) ]; dhcp-option = [ "option:router,10.80.1.1" "option6:dns-server,fd00:80:1::1" ]; # Despite its name, the switch does not have a “smart” configuration, # that would allow me to tell it not to get DHCP from wan, # but from lan instead. # So it has to use static configuration. host-record = "switchviech,switchviech.${domain},10.80.1.19"; server = [ "10.64.0.1" # mullvad DNS, should be fastest overall #"9.9.9.9" # dns.quad9.net #"2620:fe::fe" ]; }; }; systemd.services.dnsmasq.after = [ "systemd-networkd.service" ]; services.prometheus.exporters.dnsmasq = { enable = true; listenAddress = config.sbruder.wireguard.home.address; leasesPath = "/var/lib/dnsmasq/dnsmasq.leases"; }; networking.firewall.allowedUDPPorts = [ 53 67 ]; networking.firewall.allowedTCPPorts = [ 53 ]; # Wireless boot.kernelModules = [ "nl80211" ]; environment.systemPackages = with pkgs; [ iw wirelesstools ]; # The service is mostly taken from nixpkgs pr 222536. systemd.services.hostapd = { path = with pkgs; [ hostapd ]; after = [ "sys-subsystem-net-devices-wlp5s0.device" ]; bindsTo = [ "sys-subsystem-net-devices-wlp5s0.device" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${pkgs.hostapd}/bin/hostapd ${config.sops.secrets.hostapd-config.path}"; Restart = "always"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; RuntimeDirectory = "hostapd"; # Hardening LockPersonality = true; MemoryDenyWriteExecute = true; DevicePolicy = "closed"; DeviceAllow = "/dev/rfkill rw"; NoNewPrivileges = true; PrivateUsers = false; # hostapd requires true root access. PrivateTmp = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProcSubset = "pid"; ProtectSystem = "strict"; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ]; UMask = "0077"; }; }; }