diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix index a3308a1cd396..e67b819afd6e 100644 --- a/nixos/modules/services/networking/wireguard.nix +++ b/nixos/modules/services/networking/wireguard.nix @@ -15,6 +15,15 @@ let options = { + type = mkOption { + example = "amneziawg"; + default = "wireguard"; + type = types.enum ["wireguard" "amneziawg"]; + description = '' + The type of the interface. Currently only "wireguard" and "amneziawg" are supported. + ''; + }; + ips = mkOption { example = [ "192.168.2.1/24" ]; default = []; @@ -204,6 +213,22 @@ let ::: ''; }; + + extraOptions = mkOption { + type = with types; attrsOf (oneOf [ str int ]); + default = { }; + example = { + Jc = 5; + Jmin = 10; + Jmax = 42; + S1 = 60; + S2 = 90; + H4 = 12345; + }; + description = '' + Extra options to append to the interface section. Can be used to define AmneziaWG-specific options. + ''; + }; }; }; @@ -342,6 +367,16 @@ let }; + wgBins = { + wireguard = "wg"; + amneziawg = "awg"; + }; + + wgPackages = { + wireguard = pkgs.wireguard-tools; + amneziawg = pkgs.amneziawg-tools; + }; + generateKeyServiceUnit = name: values: assert values.generatePrivateKeyFile; nameValuePair "wireguard-${name}-key" @@ -350,7 +385,7 @@ let wantedBy = [ "wireguard-${name}.service" ]; requiredBy = [ "wireguard-${name}.service" ]; before = [ "wireguard-${name}.service" ]; - path = with pkgs; [ wireguard-tools ]; + path = [ wgPackages.${values.type} ]; serviceConfig = { Type = "oneshot"; @@ -366,7 +401,7 @@ let if [ ! -f "${values.privateKeyFile}" ]; then # Write private key file with atomically-correct permissions. - (set -e; umask 077; wg genkey > "${values.privateKeyFile}") + (set -e; umask 077; ${wgBins.${values.type}} genkey > "${values.privateKeyFile}") fi ''; }; @@ -391,7 +426,7 @@ let src = interfaceCfg.socketNamespace; dst = interfaceCfg.interfaceNamespace; ip = nsWrap "ip" src dst; - wg = nsWrap "wg" src dst; + wg = nsWrap wgBins.${interfaceCfg.type} src dst; dynamicEndpointRefreshSeconds = dynamicRefreshSeconds interfaceCfg peer; dynamicRefreshEnabled = dynamicEndpointRefreshSeconds != 0; # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds` @@ -408,7 +443,7 @@ let wantedBy = [ "wireguard-${interfaceName}.service" ]; environment.DEVICE = interfaceName; environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity"; - path = with pkgs; [ iproute2 wireguard-tools ]; + path = with pkgs; [ iproute2 wgPackages.${interfaceCfg.type} ]; serviceConfig = if !dynamicRefreshEnabled @@ -497,7 +532,7 @@ let dst = values.interfaceNamespace; ipPreMove = nsWrap "ip" src null; ipPostMove = nsWrap "ip" src dst; - wg = nsWrap "wg" src dst; + wg = nsWrap wgBins.${values.type} src dst; ns = if dst == "init" then "1" else dst; in @@ -508,7 +543,7 @@ let wants = [ "network.target" ]; before = [ "network.target" ]; environment.DEVICE = name; - path = with pkgs; [ kmod iproute2 wireguard-tools ]; + path = with pkgs; [ kmod iproute2 wgPackages.${values.type} ]; serviceConfig = { Type = "oneshot"; @@ -516,10 +551,10 @@ let }; script = concatStringsSep "\n" ( - optional (!config.boot.isContainer) "modprobe wireguard || true" + optional (!config.boot.isContainer) "modprobe ${values.type} || true" ++ [ values.preSetup - ''${ipPreMove} link add dev "${name}" type wireguard'' + ''${ipPreMove} link add dev "${name}" type ${values.type}'' ] ++ optional (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"'' ++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}'' @@ -531,6 +566,7 @@ let [ ''${wg} set "${name}" private-key "${privKey}"'' ] ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"'' ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"'' + ++ mapAttrsToList (k: v: ''${toLower k} "${toString v}"'') values.extraOptions )) ''${ipPostMove} link set up dev "${name}"'' values.postSetup @@ -550,6 +586,9 @@ let ns = last nsList; in if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd; + + usingWg = any (x: x.type == "wireguard") (attrValues cfg.interfaces); + usingAwg = any (x: x.type == "amneziawg") (attrValues cfg.interfaces); in { @@ -624,9 +663,11 @@ in message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used."; }) all_peers; - boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard; - boot.kernelModules = [ "wireguard" ]; - environment.systemPackages = [ pkgs.wireguard-tools ]; + boot.extraModulePackages = + optional (usingWg && (versionOlder kernel.kernel.version "5.6")) kernel.wireguard + ++ optional usingAwg kernel.amneziawg; + boot.kernelModules = optional usingWg "wireguard" ++ optional usingAwg "amneziawg"; + environment.systemPackages = optional usingWg pkgs.wireguard-tools ++ optional usingAwg pkgs.amneziawg-tools; systemd.services = mkIf (!cfg.useNetworkd) ( (mapAttrs' generateInterfaceUnit cfg.interfaces) diff --git a/nixos/tests/wireguard/amneziawg.nix b/nixos/tests/wireguard/amneziawg.nix new file mode 100644 index 000000000000..40ab0e002fec --- /dev/null +++ b/nixos/tests/wireguard/amneziawg.nix @@ -0,0 +1,111 @@ +import ../make-test-python.nix ( + { + pkgs, + lib, + kernelPackages ? null, + ... + }: + let + wg-snakeoil-keys = import ./snakeoil-keys.nix; + peer = (import ./make-peer.nix) { inherit lib; }; + extraOptions = { + Jc = 5; + Jmin = 10; + Jmax = 42; + S1 = 60; + S2 = 90; + }; + in + { + name = "amneziawg"; + meta = with pkgs.lib.maintainers; { + maintainers = [ + averyanalex + azahi + ]; + }; + + nodes = { + peer0 = peer { + ip4 = "192.168.0.1"; + ip6 = "fd00::1"; + extraConfig = { + boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; }; + networking.firewall.allowedUDPPorts = [ 23542 ]; + networking.wireguard.interfaces.wg0 = { + type = "amneziawg"; + ips = [ + "10.23.42.1/32" + "fc00::1/128" + ]; + listenPort = 23542; + + inherit (wg-snakeoil-keys.peer0) privateKey; + + peers = lib.singleton { + allowedIPs = [ + "10.23.42.2/32" + "fc00::2/128" + ]; + + inherit (wg-snakeoil-keys.peer1) publicKey; + }; + + inherit extraOptions; + }; + }; + }; + + peer1 = peer { + ip4 = "192.168.0.2"; + ip6 = "fd00::2"; + extraConfig = { + boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; }; + networking.wireguard.interfaces.wg0 = { + type = "amneziawg"; + ips = [ + "10.23.42.2/32" + "fc00::2/128" + ]; + listenPort = 23542; + allowedIPsAsRoutes = false; + + inherit (wg-snakeoil-keys.peer1) privateKey; + + peers = lib.singleton { + allowedIPs = [ + "0.0.0.0/0" + "::/0" + ]; + endpoint = "192.168.0.1:23542"; + persistentKeepalive = 25; + + inherit (wg-snakeoil-keys.peer0) publicKey; + }; + + postSetup = + let + inherit (pkgs) iproute2; + in + '' + ${iproute2}/bin/ip route replace 10.23.42.1/32 dev wg0 + ${iproute2}/bin/ip route replace fc00::1/128 dev wg0 + ''; + + inherit extraOptions; + }; + }; + }; + }; + + testScript = '' + start_all() + + peer0.wait_for_unit("wireguard-wg0.service") + peer1.wait_for_unit("wireguard-wg0.service") + + peer1.succeed("ping -c5 fc00::1") + peer1.succeed("ping -c5 10.23.42.1") + ''; + } +) diff --git a/nixos/tests/wireguard/default.nix b/nixos/tests/wireguard/default.nix index b5da59d07ba9..1202e48b297c 100644 --- a/nixos/tests/wireguard/default.nix +++ b/nixos/tests/wireguard/default.nix @@ -10,6 +10,7 @@ with pkgs.lib; let tests = let callTest = p: args: import p ({ inherit system pkgs; } // args); in { basic = callTest ./basic.nix; + amneziawg = callTest ./amneziawg.nix; namespaces = callTest ./namespaces.nix; networkd = callTest ./networkd.nix; wg-quick = callTest ./wg-quick.nix;