{ repoFlake, pkgs, lib, config, nodeFlake, nodeName, localDomainName, system, ... }: let inherit (nodeFlake.inputs) bpir3 nixos-nftables-firewall ; vlanRangeStart = builtins.head vlanRange; vlanRangeEnd = builtins.elemAt vlanRange ((builtins.length vlanRange) - 1); vlanRange = builtins.map (vlanid: (lib.strings.toInt vlanid)) (builtins.attrNames vlans); vlanRangeWith0 = [0] ++ vlanRange; mkVlanIpv4HostAddr = { vlanid, host, thirdIpv4SegmentMin ? 20, cidr ? true, }: let # reserve the first subnet for vlanid == 0 # number the other subnets continously from there offset = if vlanid == 0 then thirdIpv4SegmentMin else thirdIpv4SegmentMin + 1 - vlanRangeStart; in builtins.concatStringsSep "." ["192" "168" (toString (vlanid + offset)) "${toString host}${lib.strings.optionalString cidr "/24"}"]; defaultVlan = { name = "${localDomainName}"; packet_priority = 0; }; vlans = { "10".name = "mgmt"; "10".packet_priority = 0; "11".name = "dmz"; "11".packet_priority = -5; "12".name = "iot"; "12".packet_priority = -5; "13".name = "office"; "13".packet_priority = -10; "14".name = "guests"; "14".packet_priority = 10; "15".name = "iot2"; "15".packet_priority = -10; }; vlansByName = lib.attrsets.mapAttrs' ( vlanid': attrs: lib.attrsets.nameValuePair attrs.name (attrs // { id = lib.strings.toInt vlanid'; id' = vlanid'; }) ) vlans; getVlanDomain = {vlanid}: if vlanid == 0 then defaultVlan.name else vlans."${toString vlanid}".name + "." + defaultVlan.name; bridgeInterfaceName = "br-lan"; mkInterfaceName = {vlanid}: if vlanid == 0 then bridgeInterfaceName else "${bridgeInterfaceName}.${toString vlanid}"; exposedHost = "sj-srv1.dmz.internal"; in { imports = [ repoFlake.inputs.sops-nix.nixosModules.sops ../../profiles/common/user.nix "${bpir3}/lib/sd-image-mt7986.nix" nixos-nftables-firewall.nixosModules.default { nix.nixPath = [ "nixpkgs=${pkgs.path}" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.max-jobs = lib.mkDefault "auto"; nix.settings.cores = lib.mkDefault 0; } { services.openssh.enable = true; services.openssh.settings.PermitRootLogin = "yes"; users.commonUsers = { enable = true; enableNonRoot = false; rootPasswordFile = config.sops.secrets.passwords-root.path; }; sops.defaultSopsFile = ../../../../secrets/${nodeName}/secrets.yaml; sops.defaultSopsFormat = "yaml"; sops.secrets.passwords-root.neededForUsers = true; sops.secrets.wlan0_saePasswordsFile = {}; sops.secrets.wlan0_wpaPskFile = {}; } ]; # sops.secrets.ssh_host_ed25519_key = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_ed25519_key"; # mode = "0600"; # }; # sops.secrets.ssh_host_ed25519_key_pub = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_ed25519_key.pub"; # mode = "0600"; # }; # sops.secrets.ssh_host_rsa_key = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_rsa_key"; # mode = "0600"; # }; # sops.secrets.ssh_host_rsa_key_pub = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_rsa_key.pub"; # mode = "0644"; # }; boot = { kernel = { sysctl = { "net.ipv4.conf.all.forwarding" = true; "net.ipv6.conf.all.forwarding" = true; }; }; }; networking = { hostName = nodeName; useNetworkd = true; useDHCP = false; # these will be configured via nftables nat.enable = lib.mkForce false; firewall.enable = lib.mkForce false; # Use the nftables firewall instead of the base nixos scripted rules. # This flake provides a similar utility to the base nixos scripting. # https://github.com/thelegy/nixos-nftables-firewall/tree/main # TODO: configure packet_priority for VLANs (see https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority, https://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation#packet_priority) nftables = { enable = true; stopRuleset = ""; chains = { prerouting = { "exposeHost" = { after = ["hook"]; rules = let wanInterfaces = builtins.concatStringsSep ", " config.networking.nftables.firewall.zones.wan.interfaces; in # ***TODO***: if this hostname doesn't resolve it'll break the whole ruleset [ "iifname { ${wanInterfaces} } tcp dport 220 redirect to 22" "iifname { ${wanInterfaces} } dnat ip to ${exposedHost}" ]; }; }; }; firewall = { enable = true; zones = { lan.interfaces = [(mkInterfaceName {vlanid = 0;})]; vlan.interfaces = builtins.map (vlanid: (mkInterfaceName {inherit vlanid;})) vlanRange; # lan.ipv4Addresses = ["192.168.0.0/16"]; wan.interfaces = ["wan" "lan0"]; vpn.interfaces = ["wg0" "wg1"]; } // # generate a zone for each vlan lib.attrsets.mapAttrs (key: value: { interfaces = [(mkInterfaceName {vlanid = value.id;})]; }) vlansByName; rules = let ipv6IcmpTypes = [ "destination-unreachable" "echo-reply" "echo-request" "packet-too-big" "parameter-problem" "time-exceeded" # Without the nd-* ones ipv6 will not work. "nd-neighbor-solicit" "nd-router-advert" "nd-neighbor-advert" ]; ipv4IcmpTypes = [ "destination-unreachable" "echo-reply" "echo-request" "source-quench" "time-exceeded" "router-advertisement" ]; allowIcmpLines = [ "ip protocol icmp icmp type { ${builtins.concatStringsSep ", " ipv4IcmpTypes} } accept" "ip6 nexthdr icmpv6 icmpv6 type { ${builtins.concatStringsSep ", " ipv6IcmpTypes} } accept" ]; in { fw = { from = ["fw"]; verdict = "accept"; }; office-to-dmz = { from = ["office"]; to = ["dmz"]; verdict = "accept"; }; lan-to-fw = { from = ["lan"]; to = ["fw" "lan"]; verdict = "accept"; }; lan-to-wan = { from = ["lan"]; to = ["wan"]; verdict = "accept"; }; vlan-to-wan = { from = ["vlan"]; to = ["wan"]; verdict = "accept"; }; vlan-to-fw = { allowedUDPPortRanges = [ { from = 67; to = 68; } { from = 53; to = 53; } ]; allowedTCPPortRanges = [ { from = 22; to = 22; } { from = 53; to = 53; } { from = 5201; to = 5201; } ]; from = ["vlan"]; to = ["fw"]; extraLines = allowIcmpLines ++ [ "drop" ]; }; to-wan-nat = { from = ["lan" "vlan"]; to = ["wan"]; masquerade = true; verdict = "accept"; }; wan-to-dmz = { from = ["wan"]; to = ["dmz"]; verdict = "accept"; }; wan-to-fw = { from = ["wan"]; to = ["fw"]; allowedTCPPortRanges = [ { from = 22; to = 22; } ]; extraLines = allowIcmpLines ++ [ "drop" ]; }; to-vpn-nat = { from = ["lan" "vlan"]; to = ["vpn"]; masquerade = false; verdict = "accept"; }; }; }; }; }; sops.secrets.wg0-privatekey = { mode = "440"; group = "systemd-network"; }; sops.secrets.wg1-privatekey = { mode = "440"; group = "systemd-network"; }; sops.secrets.wg0-peer0-psk = { mode = "440"; group = "systemd-network"; }; sops.secrets.wg1-peer0-psk = { mode = "440"; group = "systemd-network"; }; systemd.network = { wait-online.anyInterface = true; netdevs = let router0-nmfk_wg0Endpoint = "${repoFlake.colmena.router0-nfmnk.deployment.targetHost}:${ builtins.toString repoFlake .nixosConfigurations .router0-nfmnk .config .systemd .network .netdevs .wg0 .wireguardConfig .ListenPort }"; router0-nmfk_wg1Endpoint = "${repoFlake.colmena.router0-nfmnk.deployment.targetHost}:${ builtins.toString repoFlake .nixosConfigurations .router0-nfmnk .config .systemd .network .netdevs .wg1 .wireguardConfig .ListenPort }"; in { # Create the bridge interface "20-${bridgeInterfaceName}" = { netdevConfig = { Kind = "bridge"; Name = bridgeInterfaceName; }; extraConfig = '' [Bridge] STP=yes VLANFiltering=yes VLANProtocol=802.1q DefaultPVID=0 ''; }; wg0 = { enable = true; netdevConfig = { Name = "wg0"; Kind = "wireguard"; }; wireguardConfig = { PrivateKeyFile = builtins.toString config.sops.secrets.wg0-privatekey.path; FirewallMark = 100; }; wireguardPeers = [ { wireguardPeerConfig = { AllowedIPs = [ # this allows all traffic to be routed through this interface "0.0.0.0/0" # # alternatively, specific destinations could be allowed # # remote peer wg addr # "10.0.0.0/32" # "1.1.1.1/32" # # ifconfig.co. # "172.67.168.106" # "104.21.54.91" ]; PersistentKeepalive = 15; PresharedKeyFile = builtins.toString config.sops.secrets.wg0-peer0-psk.path; PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM="; Endpoint = router0-nmfk_wg0Endpoint; }; } ]; }; wg1 = { enable = true; netdevConfig = { Name = "wg1"; Kind = "wireguard"; }; wireguardConfig = { PrivateKeyFile = builtins.toString config.sops.secrets.wg1-privatekey.path; FirewallMark = 101; }; wireguardPeers = [ { wireguardPeerConfig = { AllowedIPs = [ # this allows all traffic to be routed through this interface "0.0.0.0/0" ]; PersistentKeepalive = 15; PresharedKeyFile = builtins.toString config.sops.secrets.wg1-peer0-psk.path; PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM="; Endpoint = router0-nmfk_wg1Endpoint; }; } ]; }; } # generate the vlan devices. these will be tagged on the main bridge // builtins.foldl' (acc: cur: acc // cur) {} ( builtins.map ({ vlanid, vlanid', }: { "20-${mkInterfaceName {inherit vlanid;}}" = { netdevConfig = { Kind = "vlan"; Name = "${mkInterfaceName {inherit vlanid;}}"; }; vlanConfig.Id = vlanid; }; }) ( builtins.map (vlanid: { inherit vlanid; vlanid' = builtins.toString vlanid; }) vlanRange ) ); networks = { # places options here that should always exist "lo" = { matchConfig.Name = "lo"; # these are roughly equivalent to: # ip rule add fwmark 100 priority 0 table 100 # ip rule add fwmark 100 priority 1 prohibit # ip rule add fwmark 101 priority 0 table 101 # ip rule add fwmark 101 priority 1 prohibit routingPolicyRules = [ { routingPolicyRuleConfig = { FirewallMark = 101; Priority = 30000; Table = 101; }; } { routingPolicyRuleConfig = { FirewallMark = 101; Priority = 30001; Table = 101; Type = "prohibit"; }; } { routingPolicyRuleConfig = { FirewallMark = 100; Priority = 30000; Table = 100; }; } { routingPolicyRuleConfig = { FirewallMark = 100; Priority = 30001; Table = 100; Type = "prohibit"; }; } ]; }; # use lan0 as secondary WAN interface "10-lan0-wan" = { matchConfig.Name = "lan0"; networkConfig = { # start a DHCP Client for IPv4 Addressing/Routing DHCP = "ipv4"; # accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC) IPv6AcceptRA = true; DNSOverTLS = true; DNSSEC = true; IPv6PrivacyExtensions = false; IPForward = true; }; linkConfig.RequiredForOnline = "no"; # similar to # ip route add default via 172.16.0.1 table 101 routes = [ { routeConfig = { Gateway = "_dhcp4"; Table = 101; }; } ]; }; "10-wan" = { matchConfig.Name = "wan"; networkConfig = { # start a DHCP Client for IPv4 Addressing/Routing DHCP = "ipv4"; # accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC) IPv6AcceptRA = true; DNSOverTLS = true; DNSSEC = true; IPv6PrivacyExtensions = false; IPForward = true; }; # make routing on this interface a dependency for network-online.target # linkConfig.RequiredForOnline = "routable"; linkConfig.RequiredForOnline = "no"; # similar to # ip route add default via 192.168.0.1 table 100 routes = [ { routeConfig = { Gateway = "_dhcp4"; Table = 100; }; } ]; }; # Connect the bridge ports to the bridge "30-lan1" = { matchConfig.Name = "lan1"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "enslaved"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlansByName.dmz.id; PVID = vlansByName.dmz.id; EgressUntagged = vlansByName.dmz.id; }; } ]; }; "30-lan2" = { matchConfig.Name = "lan2"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "enslaved"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlansByName.office.id; PVID = vlansByName.office.id; EgressUntagged = vlansByName.office.id; }; } ]; }; "30-lan3" = { matchConfig.Name = "lan3"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "enslaved"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}"; }; } ]; }; # Configure the bridge for its desired function "40-${bridgeInterfaceName}" = { matchConfig.Name = bridgeInterfaceName; bridgeConfig = {}; address = [ (mkVlanIpv4HostAddr { vlanid = 0; host = 1; }) ]; networkConfig = { ConfigureWithoutCarrier = true; }; # Don't wait for it as it also would wait for wlan and DFS which takes around 5 min linkConfig.RequiredForOnline = "no"; linkConfig.ActivationPolicy = "always-up"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}"; }; } ]; vlan = ( builtins.map (vlanid: (mkInterfaceName {inherit vlanid;})) vlanRange ); }; "50-wg0" = { enable = true; matchConfig.Name = "wg0"; address = [ "10.0.0.1/31" ]; routes = [ { routeConfig = { Destination = "185.143.101.42/32"; MultiPathRoute = "10.0.0.0 1"; }; } ]; }; "50-wg1" = { enable = true; matchConfig.Name = "wg1"; address = [ "10.0.0.3/31" ]; routes = [ { routeConfig = { Destination = "185.143.101.42/32"; MultiPathRoute = "10.0.0.2 1"; }; } ]; }; } # configuration for the hostapd dynamic interfaces # * netdev type vlan # * host address for vlan # * vlan config for wlan interface // builtins.foldl' (acc: cur: acc // cur) {} (builtins.map ({ vlanid, vlanid', }: { # configure the tagged vlan device with an address and vlan filtering. # dnsmasq is configured to serve the respective /24 range on each tagged device. # this device only receives traffic for the given vlanid and sends tagged traffic to the bridge. "41-${mkInterfaceName {inherit vlanid;}}" = { matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}"; address = [ (mkVlanIpv4HostAddr { inherit vlanid; host = 1; }) ]; networkConfig = { ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "no"; linkConfig.ActivationPolicy = "always-up"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlanid; }; } ]; }; # configure the wlan interface as a bridge member that # * only gets traffic for vid 15 # * untags traffic after receiving it # * tags traffic that comes out of it "41-wlan0.${vlanid'}" = { matchConfig.Name = "wlan0.${vlanid'}"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "no"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlanid; PVID = vlanid; EgressUntagged = vlanid; }; } ]; }; "50-${mkInterfaceName {inherit vlanid;}}" = { matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}"; address = [ (mkVlanIpv4HostAddr { inherit vlanid; host = 1; }) ]; networkConfig = { ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "no"; }; }) ( builtins.map (vlanid: { inherit vlanid; vlanid' = builtins.toString vlanid; }) vlanRange )); }; # wireless access point services.hostapd = { enable = true; package = nodeFlake.packages.${system}.hostapd_patched; radios = let # generated with https://miniwebtool.com/mac-address-generator/ mkBssid = i: "34:56:ce:0f:ed:4${toString i}"; in { wlan0 = { band = "2g"; countryCode = "CH"; channel = 0; # ACS # use 'iw phy#1 info' to determine your VHT capabilities wifi4 = { enable = true; capabilities = ["HT40+" "LDPC" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1" "MAX-AMSDU-7935"]; }; networks = { wlan0 = let iface = "wlan0"; in { ssid = "mlsia"; bssid = mkBssid 0; # authentication.mode = "wpa3-sae"; authentication.mode = "wpa3-sae-transition"; authentication.wpaPskFile = config.sops.secrets."${iface}_wpaPskFile".path; authentication.saePasswordsFile = config.sops.secrets."${iface}_saePasswordsFile".path; # see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf for reference settings = { # bridge = bridgeInterfaceName; # wpa_psk_file = config.sops.secrets.wlan0_wpaPskFile.path; # not yet supported on hostapd 2.10 # sae_password_file = config.sops.secrets.wlan0_saePasswordsFile.path; # enables debug logging logger_stdout_level = lib.mkForce 0; logger_stdout = -1; # logger_syslog_level= lib.mkForce 0; # resources on vlan tagging # https://wireless.wiki.kernel.org/en/users/Documentation/hostapd#dynamic_vlan_tagging # https://forum.openwrt.org/t/individual-per-passphrase-wifi-vlans-using-wpa-psk-file-no-radius-required/161696/4 dynamic_vlan = 1; # this option currently requires a patch to hostapd vlan_no_bridge = 1; /* not used due to the above vlan_no_bridge setting vlan_tagged_interface = bridgeInterfaceName; vlan_naming = 1; vlan_bridge = "br-${iface}."; */ vlan_file = let generated = builtins.map ( vlanid: "${builtins.toString vlanid} ${iface}.${builtins.toString vlanid}" ) vlanRange; wildcard = [ # Optional wildcard entry matching all VLAN IDs. The first # in the interface # name will be replaced with the VLAN ID. The network interfaces are created # (and removed) dynamically based on the use. # see https://w1.fi/cgit/hostap/tree/hostapd/hostapd.vlan "* ${iface}.#" ]; file = pkgs.writeText "hostapd.vlan" (builtins.concatStringsSep "\n" (generated ++ wildcard)); filePath = toString file; in filePath; wpa_key_mgmt = lib.mkForce (builtins.concatStringsSep " " [ "WPA-PSK" # TODO: the printer can't connect when this is on # "WPA-PSK-SHA256" # unfortunately SAE doesn't support VLAN passwords in the way i'd like to use them # "SAE" ]); # wpa_psk_radius = 0; wpa_pairwise = "CCMP"; wmm_enabled = 1; # IEEE 802.11i (authentication) related configuration # Encrypt management frames to protect against deauthentication and similar attacks ieee80211w = 1; sae_require_mfp = 1; sae_groups = "19 20 21"; # [ENABLE-TLSv1.3] = enable TLSv1.3 (experimental - disabled by default) tls_flags = "[ENABLE-TLSv1.3]"; ieee8021x = 0; eap_server = 0; }; }; # wlan0-1 = { # ssid = "mlsia-testing"; # authentication = { # mode = "wpa3-sae-transition"; # }; # bssid = mkBssid 1; # settings = { # bridge = bridgeInterfaceName; # }; # }; # wlan0-1 = { # ssid = "justtestingwifi-wpa3"; # authentication = { # mode = "wpa3-sae"; # saePasswordsFile = config.sops.secrets.wlan0_1_saePasswordFile.path; # }; # bssid = mkBssid 1; # settings = { # bridge = bridgeInterfaceName; # }; # }; # Uncomment when needed otherwise remove # wlan0-1 = { # ssid = "koteczkowo3"; # authentication = { # mode = "none"; # this is overriden by settings # }; # managementFrameProtection = "optional"; # bssid = "e6:02:43:07:00:00"; # settings = { # bridge = bridgeInterfaceName; # wpa = lib.mkForce 2; # wpa_key_mgmt = "WPA-PSK"; # wpa_pairwise = "CCMP"; # wpa_psk_file = config.sops.secrets.legacyWifiPassword.path; # }; # }; }; }; # wlan1 = { # band = "5g"; # # channels with 160 MHz width in Poland: 36, 52, 100 i 116 # channel = 0; # ACS # countryCode = "PL"; # # use 'iw phy#1 info' to determine your VHT capabilities # wifi4 = { # enable = true; # capabilities = ["HT40+" "LDPC" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1" "MAX-AMSDU-7935"]; # }; # wifi5 = { # enable = true; # operatingChannelWidth = "160"; # capabilities = ["RXLDPC" "SHORT-GI-80" "SHORT-GI-160" "TX-STBC-2BY1" "SU-BEAMFORMER" "SU-BEAMFORMEE" "MU-BEAMFORMER" "MU-BEAMFORMEE" "RX-ANTENNA-PATTERN" "TX-ANTENNA-PATTERN" "RX-STBC-1" "SOUNDING-DIMENSION-4" "BF-ANTENNA-4" "VHT160" "MAX-MPDU-11454" "MAX-A-MPDU-LEN-EXP7"]; # }; # wifi6 = { # enable = true; # singleUserBeamformer = true; # singleUserBeamformee = true; # multiUserBeamformer = true; # operatingChannelWidth = "160"; # }; # settings = { # # these two are mandatory for wifi 5 & 6 to work # vht_oper_centr_freq_seg0_idx = 50; # he_oper_centr_freq_seg0_idx = 50; # # The "tx_queue_data2_burst" parameter in Linux refers to the burst size for # # transmitting data packets from the second data queue of a network interface. # # It determines the number of packets that can be sent in a burst. # # Adjusting this parameter can impact network throughput and latency. # tx_queue_data2_burst = 2; # # The "he_bss_color" parameter in Wi-Fi 6 (802.11ax) refers to the BSS Color field in the HE (High Efficiency) MAC header. # # BSS Color is a mechanism introduced in Wi-Fi 6 to mitigate interference and improve network efficiency in dense deployment scenarios. # # It allows multiple overlapping Basic Service Sets (BSS) to differentiate and coexist in the same area without causing excessive interference. # he_bss_color = 63; # was set to 128 by openwrt but range of possible values in 2.10 is 1-63 # # Magic values that were set by openwrt but I didn't bother inspecting every single one # he_spr_sr_control = 3; # he_default_pe_duration = 4; # he_rts_threshold = 1023; # he_mu_edca_qos_info_param_count = 0; # he_mu_edca_qos_info_q_ack = 0; # he_mu_edca_qos_info_queue_request = 0; # he_mu_edca_qos_info_txop_request = 0; # # he_mu_edca_ac_be_aci=0; missing in 2.10 # he_mu_edca_ac_be_aifsn = 8; # he_mu_edca_ac_be_ecwmin = 9; # he_mu_edca_ac_be_ecwmax = 10; # he_mu_edca_ac_be_timer = 255; # he_mu_edca_ac_bk_aifsn = 15; # he_mu_edca_ac_bk_aci = 1; # he_mu_edca_ac_bk_ecwmin = 9; # he_mu_edca_ac_bk_ecwmax = 10; # he_mu_edca_ac_bk_timer = 255; # he_mu_edca_ac_vi_ecwmin = 5; # he_mu_edca_ac_vi_ecwmax = 7; # he_mu_edca_ac_vi_aifsn = 5; # he_mu_edca_ac_vi_aci = 2; # he_mu_edca_ac_vi_timer = 255; # he_mu_edca_ac_vo_aifsn = 5; # he_mu_edca_ac_vo_aci = 3; # he_mu_edca_ac_vo_ecwmin = 5; # he_mu_edca_ac_vo_ecwmax = 7; # he_mu_edca_ac_vo_timer = 255; # }; # networks = { # wlan1 = { # ssid = "koteczkowo5"; # authentication = { # mode = "wpa3-sae"; # saePasswordsFile = config.sops.secrets.wifiPassword.path; # Use saePasswordsFile if possible. # }; # bssid = "36:b9:02:21:08:a2"; # settings = { # bridge = bridgeInterfaceName; # }; # }; # }; # }; }; }; services.resolved.enable = false; services.dnsmasq = { enable = true; settings = { domain-needed = true; bogus-priv = true; no-resolv = true; localise-queries = true; proxy-dnssec = true; conntrack = true; # enable for debugging # log-debug = true; # log-queries = true; # disable negative caching no-negcache = true; local-ttl = 0; dhcp-ttl = 0; dhcp-range = let mkDhcpRange = { tag, vlanid, }: builtins.concatStringsSep "," [ tag (mkVlanIpv4HostAddr { inherit vlanid; host = 100; cidr = false; }) (mkVlanIpv4HostAddr { inherit vlanid; host = 199; cidr = false; }) "12h" ]; in builtins.map ( vlanid: mkDhcpRange { tag = mkInterfaceName {inherit vlanid;}; inherit vlanid; } ) vlanRangeWith0; # interface = bridgeInterfaceName; # bind-interfaces = true; # dhcp-host = "192.168.10.1"; # local domains # local = "/${getVlanDomain {vlanid = 0;}/"; # domain = getVlanDomain {vlanid = 0;}; expand-hosts = true; # don't use /etc/hosts as this would advertise ${nodeName} as localhost no-hosts = true; # address = "/${nodeName}.lan/${fwLanHostAddr}"; server = [ # upstream DNS servers # https://dnsforge.de/ "176.9.93.198" "176.9.1.117" "2a01:4f8:151:34aa::198" "2a01:4f8:141:316d::117" # cloudflare and google # "9.9.9.9" "8.8.8.8" "1.1.1.1" ]; domain = [ "/${getVlanDomain {vlanid = 0;}}/,local" ] ++ builtins.map ( vlanid: "${getVlanDomain {inherit vlanid;}},${mkVlanIpv4HostAddr { inherit vlanid; host = 0; cidr = true; }},local" ) vlanRangeWith0; # TODO: compare this to using `interface-name` dynamic-host = [ ] ++ builtins.map ( vlanid: builtins.concatStringsSep "," [ # "${getVlanDomain{inherit vlanid;}}" "0.0.0.1" (mkInterfaceName {inherit vlanid;}) "${nodeName}.${getVlanDomain {inherit vlanid;}}" "0.0.0.1" (mkInterfaceName {inherit vlanid;}) ] ) vlanRangeWith0; dhcp-option-force = builtins.map (vlanid: "${mkInterfaceName {inherit vlanid;}},option:domain-search,${getVlanDomain {inherit vlanid;}}") vlanRangeWith0; # auth-server = [ # (builtins.concatStringsSep "," [ # "www.stefanjunker.de" # # (mkInterfaceName { vlanid = vlansByName.dmz.id; }) # # (mkInterfaceName { vlanid = vlansByName.office.id; }) # ]) # ]; cname = [ "mailserver.svc.stefanjunker.de,${exposedHost}" "www.stefanjunker.de,${exposedHost}" "hedgedoc.www.stefanjunker.de,${exposedHost}" "jitsi.www.stefanjunker.de,${exposedHost}" "lldap.www.stefanjunker.de,${exposedHost}" ]; }; }; # The service irqbalance is useful as it assigns certain IRQ calls to specific CPUs instead of letting the first CPU core to handle everything. This is supposed to increase performance by hitting CPU cache more often. # disable for now as i think it causes wifi issues services.irqbalance.enable = false; system.stateVersion = "23.05"; boot.kernelPackages = pkgs.linuxPackages_bpir3_6_6; # We exclude a number of modules included in the default list. A non-insignificant amount do # not apply to embedded hardware like this, so simply skip the defaults. # # Custom kernel is required as a lot of MTK components misbehave when built as modules. # They fail to load properly, leaving the system without working ethernet, they'll oops on # remove. MTK-DSA parts and PCIe were observed to do this. boot.initrd.includeDefaultModules = false; boot.initrd.kernelModules = ["rfkill" "cfg80211" "mt7915e"]; boot.initrd.availableKernelModules = ["nvme"]; boot.kernelParams = ["console=ttyS0,115200"]; hardware.enableRedistributableFirmware = true; # Wireless hardware exists, regulatory database is essential. hardware.wirelessRegulatoryDatabase = true; # Extlinux compatible with custom uboot patches in this repo, which also provide unique # MAC addresses instead of the non-unique one that gets used by a lot of MTK devices... boot.loader.grub.enable = false; boot.loader.generic-extlinux-compatible.enable = true; # Known to work with u-boot; bz2, lzma, and lz4 should be safe too, need to test. boot.initrd.compressor = "gzip"; hardware.deviceTree.filter = "mt7986a-bananapi-bpi-r3.dtb"; hardware.deviceTree.overlays = [ { name = "bpir3-sd-enable"; dtsFile = "${bpir3}/bpir3-dts/mt7986a-bananapi-bpi-r3-sd.dts"; } { name = "bpir3-nand-enable"; dtsFile = "${bpir3}/bpir3-dts/mt7986a-bananapi-bpi-r3-nand.dts"; } { name = "bpi-r3 wifi training data"; dtsFile = "${bpir3}/bpir3-dts/mt7986a-bananapi-bpi-r3-wirless.dts"; } { name = "reset button disable"; dtsFile = "${bpir3}/bpir3-dts/mt7986a-bananapi-bpi-r3-pcie-button.dts"; } { name = "mt7986a efuses"; dtsFile = "${bpir3}/bpir3-dts/mt7986a-efuse-device-tree-node.dts"; } ]; boot.initrd.preDeviceCommands = '' if [ ! -d /sys/bus/pci/devices/0000:01:00.0 ]; then if [ -d /sys/bus/pci/devices/0000:00:00.0 ]; then # Remove PCI bridge, then rescan. NVMe init crashes if PCI bridge not removed first echo 1 > /sys/bus/pci/devices/0000:00:00.0/remove # Rescan brings PCI root back and brings the NVMe device in. echo 1 > /sys/bus/pci/rescan else info "PCIe bridge missing" fi fi ''; environment.systemPackages = [ pkgs.ethtool pkgs.neovim pkgs.wireguard-tools pkgs.tshark pkgs.tmux (pkgs.writeShellScriptBin "dbg-ip" '' echo links: ip -br -c l echo echo addresses: ip -br -c a echo echo vlans: bridge -c vlan '') (pkgs.writeShellScriptBin "dbg-dnsmasq" '' # get the rendered in-use config pgrep -a dnsmasq | grep -Eo '[^ ]*conf' | xargs cat | grep -Eo '[^=]*conf' | xargs cat '') ]; }