diff --git a/common/ssh.nix b/common/ssh.nix index 8ace660..a2351f6 100644 --- a/common/ssh.nix +++ b/common/ssh.nix @@ -4,7 +4,7 @@ rec { "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0dcqL/FhHmv+a1iz3f9LJ48xubO7MZHy35rW9SZOYM" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHSkKiRUUmnErOKGx81nyge/9KqjkPh8BfDk0D3oP586" # nat "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeTK1iARlNIKP/DS8/ObBm9yUM/3L1Ub4XI5A2r9OzP" # ray - ] ++ higherTrustUserKeys; + ]; system = { ponyo = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMBBlTAIp38RhErU1wNNV5MBeb+WGH0mhF/dxh5RsAXN"; ponyo-unlock = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC9LQuuImgWlkjDhEEIbM1wOd+HqRv1RxvYZuLXPSdRi"; diff --git a/flake.lock b/flake.lock index bda597b..371354e 100644 --- a/flake.lock +++ b/flake.lock @@ -212,6 +212,18 @@ "type": "indirect" } }, + "nixpkgs-hostapd-pr": { + "flake": false, + "locked": { + "narHash": "sha256-XwZgYqlPmqNU2vWl/xgeg6X15U2b3ln2KOVPY2yPwlI=", + "type": "file", + "url": "https://github.com/NixOS/nixpkgs/pull/222536.patch" + }, + "original": { + "type": "file", + "url": "https://github.com/NixOS/nixpkgs/pull/222536.patch" + } + }, "radio": { "inputs": { "flake-utils": [ @@ -262,6 +274,7 @@ "flake-utils": "flake-utils", "nix-index-database": "nix-index-database", "nixpkgs": "nixpkgs", + "nixpkgs-hostapd-pr": "nixpkgs-hostapd-pr", "radio": "radio", "radio-web": "radio-web", "simple-nixos-mailserver": "simple-nixos-mailserver" diff --git a/flake.nix b/flake.nix index 842bd78..9313710 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,9 @@ # prebuilt nix-index database nix-index-database.url = "github:Mic92/nix-index-database"; nix-index-database.inputs.nixpkgs.follows = "nixpkgs"; + + nixpkgs-hostapd-pr.url = "https://github.com/NixOS/nixpkgs/pull/222536.patch"; + nixpkgs-hostapd-pr.flake = false; }; outputs = { self, nixpkgs, ... }@inputs: { @@ -72,7 +75,7 @@ name = "nixpkgs-patched"; src = nixpkgs; patches = [ - # inputs.nixpkgs-patch-howdy + inputs.nixpkgs-hostapd-pr ]; }; patchedNixpkgs = nixpkgs.lib.fix (self: (import "${patchedNixpkgsSrc}/flake.nix").outputs { self=nixpkgs; }); diff --git a/machines/router/configuration.nix b/machines/router/configuration.nix index e6aad9d..cf00621 100644 --- a/machines/router/configuration.nix +++ b/machines/router/configuration.nix @@ -3,13 +3,38 @@ { imports = [ ./hardware-configuration.nix + ./router.nix ]; + # https://dataswamp.org/~solene/2022-08-03-nixos-with-live-usb-router.html + # https://github.com/mdlayher/homelab/blob/391cfc0de06434e4dee0abe2bec7a2f0637345ac/nixos/routnerr-2/configuration.nix + # https://github.com/skogsbrus/os/blob/master/sys/router.nix + # http://trac.gateworks.com/wiki/wireless/wifi + networking.hostName = "router"; system.autoUpgrade.enable = true; services.tailscale.exitNode = true; - networking.useDHCP = lib.mkForce true; + router.enable = true; + router.privateSubnet = "192.168.3"; + + services.iperf3.enable = true; + + # networking.useDHCP = lib.mkForce true; + + # TODO + # networking.usePredictableInterfaceNames = true; + + powerManagement.cpuFreqGovernor = "ondemand"; + + + services.irqbalance.enable = true; + + # services.miniupnpd = { + # enable = true; + # externalInterface = "eth0"; + # internalIPs = [ "br0" ]; + # }; } \ No newline at end of file diff --git a/machines/router/firmware/mediatek/mt7916_eeprom.bin b/machines/router/firmware/mediatek/mt7916_eeprom.bin new file mode 100644 index 0000000..35b287c Binary files /dev/null and b/machines/router/firmware/mediatek/mt7916_eeprom.bin differ diff --git a/machines/router/firmware/mediatek/mt7916_rom_patch.bin b/machines/router/firmware/mediatek/mt7916_rom_patch.bin new file mode 100644 index 0000000..f06d5a2 Binary files /dev/null and b/machines/router/firmware/mediatek/mt7916_rom_patch.bin differ diff --git a/machines/router/firmware/mediatek/mt7916_wa.bin b/machines/router/firmware/mediatek/mt7916_wa.bin new file mode 100644 index 0000000..21caab9 Binary files /dev/null and b/machines/router/firmware/mediatek/mt7916_wa.bin differ diff --git a/machines/router/firmware/mediatek/mt7916_wm.bin b/machines/router/firmware/mediatek/mt7916_wm.bin new file mode 100644 index 0000000..b9267a5 Binary files /dev/null and b/machines/router/firmware/mediatek/mt7916_wm.bin differ diff --git a/machines/router/generate_hostapd_config.sh b/machines/router/generate_hostapd_config.sh new file mode 100755 index 0000000..3c8906f --- /dev/null +++ b/machines/router/generate_hostapd_config.sh @@ -0,0 +1,223 @@ +#!/bin/sh + +# TODO allow adding custom parameters to ht_capab, vht_capab +# TODO detect bad channel numbers (preferably not at runtime) +# TODO error if 160mhz is not supported +# TODO 'b' only goes up to 40mhz + +# gets the phy number using the input interface +# Ex: get_phy_number("wlan0") -> "1" +get_phy_number() { + local interface=$1 + phy=$(iw dev "$interface" info | awk '/phy/ {gsub(/#/,"");print $2}') + if [[ -z "$phy" ]]; then + echo "Error: interface not found" >&2 + exit 1 + fi + phy=phy$phy +} + +get_ht_cap_mask() { + ht_cap_mask=0 + + for cap in $(iw phy "$phy" info | grep 'Capabilities:' | cut -d: -f2); do + ht_cap_mask="$(($ht_cap_mask | $cap))" + done + + local cap_rx_stbc + cap_rx_stbc=$((($ht_cap_mask >> 8) & 3)) + ht_cap_mask="$(( ($ht_cap_mask & ~(0x300)) | ($cap_rx_stbc << 8) ))" +} + +get_vht_cap_mask() { + vht_cap_mask=0 + for cap in $(iw phy "$phy" info | awk -F "[()]" '/VHT Capabilities/ { print $2 }'); do + vht_cap_mask="$(($vht_cap_mask | $cap))" + done + + local cap_rx_stbc + cap_rx_stbc=$((($vht_cap_mask >> 8) & 7)) + vht_cap_mask="$(( ($vht_cap_mask & ~(0x700)) | ($cap_rx_stbc << 8) ))" +} + +mac80211_add_capabilities() { + local __var="$1"; shift + local __mask="$1"; shift + local __out= oifs + + oifs="$IFS" + IFS=: + for capab in "$@"; do + set -- $capab + [ "$(($4))" -gt 0 ] || continue + [ "$(($__mask & $2))" -eq "$((${3:-$2}))" ] || continue + __out="$__out[$1]" + done + IFS="$oifs" + + export -n -- "$__var=$__out" +} + +add_special_ht_capabilities() { + case "$hwmode" in + a) + case "$(( ($channel / 4) % 2 ))" in + 1) ht_capab="$ht_capab[HT40+]";; + 0) ht_capab="$ht_capab[HT40-]";; + esac + ;; + *) + if [ "$channel" -lt 7 ]; then + ht_capab="$ht_capab[HT40+]" + else + ht_capab="$ht_capab[HT40-]" + fi + ;; + esac +} + +add_special_vht_capabilities() { + local cap_ant + [ "$(($vht_cap_mask & 0x800))" -gt 0 ] && { + cap_ant="$(( ( ($vht_cap_mask >> 16) & 3 ) + 1 ))" + [ "$cap_ant" -gt 1 ] && vht_capab="$vht_capab[SOUNDING-DIMENSION-$cap_ant]" + } + + [ "$(($vht_cap_mask & 0x1000))" -gt 0 ] && { + cap_ant="$(( ( ($vht_cap_mask >> 13) & 3 ) + 1 ))" + [ "$cap_ant" -gt 1 ] && vht_capab="$vht_capab[BF-ANTENNA-$cap_ant]" + } + + if [ "$(($vht_cap_mask & 12))" -eq 4 ]; then + vht_capab="$vht_capab[VHT160]" + fi + + local vht_max_mpdu_hw=3895 + [ "$(($vht_cap_mask & 3))" -ge 1 ] && \ + vht_max_mpdu_hw=7991 + [ "$(($vht_cap_mask & 3))" -ge 2 ] && \ + vht_max_mpdu_hw=11454 + [ "$vht_max_mpdu_hw" != 3895 ] && \ + vht_capab="$vht_capab[MAX-MPDU-$vht_max_mpdu_hw]" + + # maximum A-MPDU length exponent + local vht_max_a_mpdu_len_exp_hw=0 + [ "$(($vht_cap_mask & 58720256))" -ge 8388608 ] && \ + vht_max_a_mpdu_len_exp_hw=1 + [ "$(($vht_cap_mask & 58720256))" -ge 16777216 ] && \ + vht_max_a_mpdu_len_exp_hw=2 + [ "$(($vht_cap_mask & 58720256))" -ge 25165824 ] && \ + vht_max_a_mpdu_len_exp_hw=3 + [ "$(($vht_cap_mask & 58720256))" -ge 33554432 ] && \ + vht_max_a_mpdu_len_exp_hw=4 + [ "$(($vht_cap_mask & 58720256))" -ge 41943040 ] && \ + vht_max_a_mpdu_len_exp_hw=5 + [ "$(($vht_cap_mask & 58720256))" -ge 50331648 ] && \ + vht_max_a_mpdu_len_exp_hw=6 + [ "$(($vht_cap_mask & 58720256))" -ge 58720256 ] && \ + vht_max_a_mpdu_len_exp_hw=7 + vht_capab="$vht_capab[MAX-A-MPDU-LEN-EXP$vht_max_a_mpdu_len_exp_hw]" + + local vht_link_adapt_hw=0 + [ "$(($vht_cap_mask & 201326592))" -ge 134217728 ] && \ + vht_link_adapt_hw=2 + [ "$(($vht_cap_mask & 201326592))" -ge 201326592 ] && \ + vht_link_adapt_hw=3 + [ "$vht_link_adapt_hw" != 0 ] && \ + vht_capab="$vht_capab[VHT-LINK-ADAPT-$vht_link_adapt_hw]" +} + +calculate_channel_offsets() { + vht_oper_chwidth=0 + vht_oper_centr_freq_seg0_idx= + + local idx="$channel" + case "$channelWidth" in + 40) + case "$(( ($channel / 4) % 2 ))" in + 1) idx=$(($channel + 2));; + 0) idx=$(($channel - 2));; + esac + vht_oper_centr_freq_seg0_idx=$idx + ;; + 80) + case "$(( ($channel / 4) % 4 ))" in + 1) idx=$(($channel + 6));; + 2) idx=$(($channel + 2));; + 3) idx=$(($channel - 2));; + 0) idx=$(($channel - 6));; + esac + vht_oper_chwidth=1 + vht_oper_centr_freq_seg0_idx=$idx + ;; + 160) + case "$channel" in + 36|40|44|48|52|56|60|64) idx=50;; + 100|104|108|112|116|120|124|128) idx=114;; + esac + vht_oper_chwidth=2 + vht_oper_centr_freq_seg0_idx=$idx + ;; + esac + + he_oper_chwidth=$vht_oper_chwidth + he_oper_centr_freq_seg0_idx=$vht_oper_centr_freq_seg0_idx +} + +interface=$1 +channel=$2 +hwmode=$3 +channelWidth=$4 + +get_phy_number $interface +get_ht_cap_mask +get_vht_cap_mask + +mac80211_add_capabilities vht_capab $vht_cap_mask \ + RXLDPC:0x10::1 \ + SHORT-GI-80:0x20::1 \ + SHORT-GI-160:0x40::1 \ + TX-STBC-2BY1:0x80::1 \ + SU-BEAMFORMER:0x800::1 \ + SU-BEAMFORMEE:0x1000::1 \ + MU-BEAMFORMER:0x80000::1 \ + MU-BEAMFORMEE:0x100000::1 \ + VHT-TXOP-PS:0x200000::1 \ + HTC-VHT:0x400000::1 \ + RX-ANTENNA-PATTERN:0x10000000::1 \ + TX-ANTENNA-PATTERN:0x20000000::1 \ + RX-STBC-1:0x700:0x100:1 \ + RX-STBC-12:0x700:0x200:1 \ + RX-STBC-123:0x700:0x300:1 \ + RX-STBC-1234:0x700:0x400:1 \ + +mac80211_add_capabilities ht_capab $ht_cap_mask \ + LDPC:0x1::1 \ + GF:0x10::1 \ + SHORT-GI-20:0x20::1 \ + SHORT-GI-40:0x40::1 \ + TX-STBC:0x80::1 \ + RX-STBC1:0x300::1 \ + MAX-AMSDU-7935:0x800::1 \ + + # TODO this is active when the driver doesn't support it? + # DSSS_CCK-40:0x1000::1 \ + + # TODO these are active when the driver doesn't support them? + # RX-STBC1:0x300:0x100:1 \ + # RX-STBC12:0x300:0x200:1 \ + # RX-STBC123:0x300:0x300:1 \ + +add_special_ht_capabilities +add_special_vht_capabilities + +echo ht_capab=$ht_capab +echo vht_capab=$vht_capab + +if [ "$channelWidth" != "20" ]; then + calculate_channel_offsets + echo he_oper_chwidth=$he_oper_chwidth + echo vht_oper_chwidth=$vht_oper_chwidth + echo he_oper_centr_freq_seg0_idx=$he_oper_centr_freq_seg0_idx + echo vht_oper_centr_freq_seg0_idx=$vht_oper_centr_freq_seg0_idx +fi \ No newline at end of file diff --git a/machines/router/hardware-configuration.nix b/machines/router/hardware-configuration.nix index 6e4c0cf..fed46a6 100644 --- a/machines/router/hardware-configuration.nix +++ b/machines/router/hardware-configuration.nix @@ -21,7 +21,6 @@ # firmware firmware.x86_64.enable = true; - hardware.enableAllFirmware = true; nixpkgs.config.allowUnfree = true; # boot diff --git a/machines/router/router.nix b/machines/router/router.nix new file mode 100644 index 0000000..eed66d1 --- /dev/null +++ b/machines/router/router.nix @@ -0,0 +1,176 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.router; + inherit (lib) mapAttrs' genAttrs nameValuePair mkOption types mkIf mkEnableOption; +in +{ + options.router = { + enable = mkEnableOption "router"; + + privateSubnet = mkOption { + type = types.str; + default = "192.168.1"; + description = "IP block (/24) to use for the private subnet"; + }; + }; + + config = mkIf cfg.enable { + networking.ip_forward = true; + + networking.interfaces.enp1s0.useDHCP = true; + + networking.nat = { + enable = true; + internalInterfaces = [ + "br0" + ]; + externalInterface = "enp1s0"; + }; + + networking.bridges = { + br0 = { + interfaces = [ + "enp2s0" + "wlp4s0" + "wlan1" + ]; + }; + }; + + networking.interfaces = { + br0 = { + useDHCP = false; + ipv4.addresses = [ + { + address = "${cfg.privateSubnet}.1"; + prefixLength = 24; + } + ]; + }; + }; + + networking.networkmanager.enable = false; + + services.dnsmasq = { + enable = true; + extraConfig = '' + # sensible behaviours + domain-needed + bogus-priv + no-resolv + + # upstream name servers + server=1.1.1.1 + server=8.8.8.8 + + # local domains + expand-hosts + domain=home + local=/home/ + + # Interfaces to use DNS on + interface=br0 + + # subnet IP blocks to use DHCP on + dhcp-range=${cfg.privateSubnet}.10,${cfg.privateSubnet}.254,24h + ''; + }; + + networking.firewall.enable = lib.mkForce false; # TODO + + services.hostapd = { + enable = true; + radios = { + # 2.4GHz + wlp4s0 = { + hwMode = "g"; + noScan = true; + channel = 6; + countryCode = "US"; + wifi4 = { + capabilities = ["LDPC" "GF" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1" "MAX-AMSDU-7935" "HT40+"]; + }; + wifi5 = { + operatingChannelWidth = "20or40"; + capabilities = ["MAX-A-MPDU-LEN-EXP0"]; + }; + wifi6 = { + enable = true; + singleUserBeamformer = true; + singleUserBeamformee = true; + multiUserBeamformer = true; + operatingChannelWidth = "20or40"; + }; + networks.wlp4s0 = { + ssid = "- Experimental 5G Tower by AT&T"; + authentication.saePasswordsFile = "/run/agenix/hostapd-pw-experimental-tower"; + }; + extraConfig = '' + he_oper_centr_freq_seg0_idx=8 + vht_oper_centr_freq_seg0_idx=8 + ''; + }; + + # 5GHz + wlan1 = { + hwMode = "a"; + noScan = true; + channel = 128; + countryCode = "US"; + wifi4 = { + capabilities = ["LDPC" "GF" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1" "MAX-AMSDU-7935" "HT40-"]; + }; + wifi5 = { + 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-3" "BF-ANTENNA-3" "VHT160" "MAX-MPDU-11454" "MAX-A-MPDU-LEN-EXP7"]; + }; + wifi6 = { + enable = true; + singleUserBeamformer = true; + singleUserBeamformee = true; + multiUserBeamformer = true; + operatingChannelWidth = "160"; + }; + networks.wlan1 = { + ssid = "- Experimental 5G Tower by AT&T"; + authentication.saePasswordsFile = "/run/agenix/hostapd-pw-experimental-tower"; + }; + extraConfig = '' + vht_oper_centr_freq_seg0_idx=114 + he_oper_centr_freq_seg0_idx=114 + ''; + }; + }; + }; + age.secrets.hostapd-pw-experimental-tower.file = ../../secrets/hostapd-pw-experimental-tower.age; + + hardware.firmware = [ + pkgs.mt7916-firmware + ]; + + nixpkgs.overlays = [ + (self: super: { + mt7916-firmware = pkgs.stdenvNoCC.mkDerivation { + pname = "mt7916-firmware"; + version = "custom-feb-02-23"; + src = ./firmware/mediatek; # from here https://github.com/openwrt/mt76/issues/720#issuecomment-1413537674 + dontBuild = true; + installPhase = '' + for i in \ + mt7916_eeprom.bin \ + mt7916_rom_patch.bin \ + mt7916_wa.bin \ + mt7916_wm.bin; + do + install -D -pm644 $i $out/lib/firmware/mediatek/$i + done + ''; + meta = with lib; { + license = licenses.unfreeRedistributableFirmware; + }; + }; + }) + ]; + }; +} \ No newline at end of file diff --git a/secrets/hostapd-pw-experimental-tower.age b/secrets/hostapd-pw-experimental-tower.age new file mode 100644 index 0000000..7129eba --- /dev/null +++ b/secrets/hostapd-pw-experimental-tower.age @@ -0,0 +1,23 @@ +age-encryption.org/v1 +-> ssh-ed25519 xoAm7w j3b4I8BlfjbyJ2Yxef57ihKgYNZKJ/LHj0fxJq0uYTg +i9SXMkfGdK+7y39e74KjTtYnEIDsnawR+AIklGrDPcc +-> ssh-ed25519 mbw8xA 1XW5+CDzbuAN4yoWUxugOLjMvgqDgfY8TXPlK5HKglo +9howcz96J/Tm7zdQwVQUkQRbAf6qVg95veCMJb34XPY +-> ssh-ed25519 2a2Yhw PP3vG9MJeH+LGmwtqtm+y29GDh7falcclqFz6aArOhk +FNAldZ1i4Sp4HgWfSR7pbc14aWb0bKpDHaJKtOpcLvI +-> ssh-ed25519 dMQYog P7CL7hkjSEaSSc0sKO+9dAKmYvFl/sx2J62I6fZxcDI +p8npETMIJm+5xV5UW2fPjKedzaKrWr2cYXWGQLuP3As +-> ssh-ed25519 6AT2/g fLO3IT6xKRZJ5s1Jz+tJmf9ze72069GWbSssUKUGVnw +lTsroAJY36etAROH/UDsJYSROFAuDJDl7w4eRHsid7s +-> ssh-ed25519 yHDAQw O6gMZ7W6074+gzHYsQmVfhGfFDaxxSpPyzCvHF8HPw4 +5bb5PiHTKOdYMeBfKw7ynV+Y7Tpr3QilNpch4uB+TuA +-> ssh-ed25519 VyYH/Q JpMmSBCi/Q911VpXzhHsHGTzqLjguOwc37FiiAQ2dVY +waX+j1Urj2GmYGX5uZkZ55sG051+OdYdHhJaXfogO8c +-> ssh-ed25519 hPp1nw EfWqRoxo08D2An+yEc1e1NCGwcyfoOzRfBJP2WCqiGc +hGjdDZdyEn2w5uaQEz6MLi5GTMEDkhzS/sT4zRwUI54 +-> #RLU8aJw-grease 389"AoX pIrn%*Qm ^W$ 3Y+%99; +AshAkiQFyywtltjNEkGxC0eHLFVYK1azVOySDOvcZEugtm9uGE46kM1Gz6kLVN59 +LlO9Do+6pdVd3LEb4sqRHQR4fh6DDF8n3VDnnO9Hzu5YQbJ1kEoxB6P1do/YFA +--- AiQzm6IGLwPgglj9y/75m63+5b5UwUn6DHw33LL4Njw +iΧW=֓#T4y/Yȯid^ =DRr@UYr +y|f \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 25811bb..bc862fa 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -5,6 +5,7 @@ let all = users ++ systems; in { + # TODO: Minimum necessary access to keys "email-pw.age".publicKeys = all; "iodine.age".publicKeys = all; "nextcloud-pw.age".publicKeys = all; @@ -14,4 +15,7 @@ in "smb-secrets.age".publicKeys = all; "spotifyd.age".publicKeys = all; "wolframalpha.age".publicKeys = all; + + # hostapd + "hostapd-pw-experimental-tower.age".publicKeys = all; }