# Home network configuration # # +----------+ +------+ # | | | | ( clients ) # | | | +|-|-|-|-|+ # +---+----+ +-+-+-+ |5 4 3 2 1| # |upstream| |fuuko| |TL-SG105 | # +--------+ +-----+ +---------+ # # It consists of fuuko 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. # # fuuko has two physical network interfaces, # because remote unlocking (which requires network in initrd) is hard with VLANs. # # 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"; in { sops.secrets.wg-mullvad-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; # networkd didn’t work that well for this nat = { enable = true; enableIPv6 = true; externalInterface = "wg-mullvad"; internalInterfaces = [ "br-lan" ]; internalIPv6s = [ "fd00:80:1::/64" ]; }; }; systemd.network = { enable = true; netdevs = { br-lan = { netdevConfig = { Name = "br-lan"; Kind = "bridge"; }; }; wg-mullvad = { netdevConfig = { Kind = "wireguard"; Name = "wg-mullvad"; }; wireguardConfig = { PrivateKeyFile = config.sops.secrets.wg-mullvad-private-key.path; FirewallMark = 51820; }; wireguardPeers = lib.singleton { wireguardPeerConfig = { Endpoint = "193.32.127.70:51820"; PublicKey = "dV/aHhwG0fmp0XuvSvrdWjCtdyhPDDFiE/nuv/1xnRM="; AllowedIPs = [ "0.0.0.0/0" "::0/0" ]; PersistentKeepalive = 25; }; }; }; }; networks = { wan = { name = "enp8s0"; 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"; }; }; lan = { name = "enp9s0"; bridge = [ "br-lan" ]; }; br-lan = { name = "br-lan"; domains = [ domain ]; address = [ "10.80.1.1/24" "fd00:80:1::1/64" ]; }; wg-mullvad = { name = "wg-mullvad"; 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; }; } ]; routes = [ { routeConfig = { Gateway = "0.0.0.0"; # point-to-point connection Table = 51820; }; } { routeConfig = { Gateway = "::"; }; } ]; }; }; }; services.resolved.enable = false; services.dnsmasq = { enable = true; extraConfig = '' bogus-priv # do not forward revese lookups of internal addresses domain-needed # do not forward names without domain interface=br-lan # only respond to queries from lan no-hosts # do not resolve hosts from /etc/hosts no-resolv # only use explicitly configured resolvers cache-size=10000 domain=${domain} # Allow resolving the router interface-name=${config.networking.hostName}.${domain},br-lan interface-name=${config.networking.hostName},br-lan # DHCPv4 dhcp-range=10.80.1.20,10.80.1.150,12h dhcp-option=option:router,10.80.1.1 # SLAAC (for addresses) / DHCPv6 (for DNS) dhcp-range=fd00:80:1::,ra-stateless,ra-names dhcp-option=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 ''; servers = [ "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-wlp7s0.device" ]; bindsTo = [ "sys-subsystem-net-devices-wlp7s0.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"; }; }; }