{ config, pkgs, lib, ... }: let wifiIf = "wlp194s0"; in { # --------------------------------------------------------------------------- # 1. MAC randomization — per-SSID stable random, plus scan-time random. # Keeps captive-portal whitelists working (same MAC per SSID) but breaks # cross-network device tracking. # --------------------------------------------------------------------------- networking.networkmanager.wifi = { macAddress = "stable"; # per-SSID random, deterministic by SSID scanRandMacAddress = true; powersave = false; # don't drop to legacy rates on idle backend = "iwd"; # better than wpa_supplicant for modern Wi-Fi }; networking.networkmanager.ethernet.macAddress = "stable"; # --------------------------------------------------------------------------- # 2. Disable mDNS / LLMNR / NetBIOS — these broadcast hostname/services # onto the hostel LAN. avahi/Windows-style discovery is a leak. # --------------------------------------------------------------------------- services.avahi.enable = lib.mkForce false; services.resolved = { enable = true; llmnr = "false"; dnsovertls = "opportunistic"; settings.Resolve.MulticastDNS = "false"; }; # --------------------------------------------------------------------------- # 3. Reject IPv6 router advertisements on Wi-Fi (hostile RA = MITM). # Tunnels can still bring their own v6 if needed. # --------------------------------------------------------------------------- boot.kernel.sysctl = { "net.ipv6.conf.${wifiIf}.accept_ra" = 0; "net.ipv6.conf.${wifiIf}.autoconf" = 0; "net.ipv6.conf.all.accept_ra_rtr_pref" = 0; # Tighten reverse-path + martian logging on Wi-Fi specifically "net.ipv4.conf.${wifiIf}.rp_filter" = 1; "net.ipv4.conf.${wifiIf}.accept_source_route" = 0; }; # --------------------------------------------------------------------------- # 4. Stateful firewall: deny inbound on Wi-Fi, deny LAN-side probing back. # --------------------------------------------------------------------------- networking.firewall = { enable = true; allowPing = false; logRefusedConnections = false; # noisy on hostel LANs; flip on for debug # No open ports. WireGuard is outbound-initiated. ollama is 127.0.0.1. interfaces.${wifiIf} = { allowedTCPPorts = []; allowedUDPPorts = []; }; # Drop hostel LAN from talking back to ephemeral ports outside of # established/related flows (the default policy already handles this, # but be explicit for the Wi-Fi iface). extraInputRules = '' iifname "${wifiIf}" ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } ct state new drop iifname "${wifiIf}" ip6 saddr { fc00::/7, fe80::/10 } ct state new drop ''; }; # --------------------------------------------------------------------------- # 5. Killswitch — fail-closed when tunnels are down. Allow only: # - traffic via wg0, wg2, tun0 # - traffic to xray server IP (so the tunnel can establish) # - DHCP / ARP / ICMP-need (RFC requirement, link viability) # - captive-portal detection endpoints (so portals load) # # Toggle: `systemctl start untrusted-wifi.target` enables strict mode. # `systemctl stop untrusted-wifi.target` reverts to default. # --------------------------------------------------------------------------- systemd.targets.untrusted-wifi = { description = "Strict killswitch profile for untrusted Wi-Fi"; }; systemd.services.killswitch = { description = "nftables killswitch — only tunnel egress allowed"; bindsTo = [ "untrusted-wifi.target" ]; after = [ "untrusted-wifi.target" "nftables.service" ]; wantedBy = [ "untrusted-wifi.target" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "killswitch-up" '' XRAY_SERVER=$(cat ${config.sops.secrets."xray-endpoint".path}) ${pkgs.nftables}/bin/nft -f - <