nixpkgs/nixos/modules/services/networking/wireguard-networkd.nix
Majiir Paktu a5de36518f nixos/wireguard-networkd: init
Adds a networkd backend for the networking.wireguard options.
2024-12-07 20:01:17 -05:00

208 lines
7.3 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
inherit (lib) types;
inherit (lib.attrsets)
filterAttrs
mapAttrs
mapAttrs'
mapAttrsToList
nameValuePair
;
inherit (lib.lists) concatMap concatLists;
inherit (lib.modules) mkIf;
inherit (lib.options) literalExpression mkOption;
inherit (lib.strings) hasInfix;
inherit (lib.trivial) flip;
removeNulls = filterAttrs (_: v: v != null);
generateNetdev =
name: interface:
nameValuePair "40-${name}" {
netdevConfig = removeNulls {
Kind = "wireguard";
Name = name;
MTUBytes = interface.mtu;
};
wireguardConfig = removeNulls {
PrivateKeyFile = interface.privateKeyFile;
ListenPort = interface.listenPort;
FirewallMark = interface.fwMark;
RouteTable = if interface.allowedIPsAsRoutes then interface.table else null;
RouteMetric = interface.metric;
};
wireguardPeers = map generateWireguardPeer interface.peers;
};
generateWireguardPeer =
peer:
removeNulls {
PublicKey = peer.publicKey;
PresharedKeyFile = peer.presharedKeyFile;
AllowedIPs = peer.allowedIPs;
Endpoint = peer.endpoint;
PersistentKeepalive = peer.persistentKeepalive;
};
generateNetwork = name: interface: {
matchConfig.Name = name;
address = interface.ips;
};
cfg = config.networking.wireguard;
refreshEnabledInterfaces = filterAttrs (
name: interface: interface.dynamicEndpointRefreshSeconds != 0
) cfg.interfaces;
generateRefreshTimer =
name: interface:
nameValuePair "wireguard-dynamic-refresh-${name}" {
partOf = [ "wireguard-dynamic-refresh-${name}.service" ];
wantedBy = [ "timers.target" ];
description = "Wireguard dynamic endpoint refresh (${name}) timer";
timerConfig.OnBootSec = interface.dynamicEndpointRefreshSeconds;
timerConfig.OnUnitInactiveSec = interface.dynamicEndpointRefreshSeconds;
};
generateRefreshService =
name: interface:
nameValuePair "wireguard-dynamic-refresh-${name}" {
description = "Wireguard dynamic endpoint refresh (${name})";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = with pkgs; [
iproute2
systemd
];
# networkd doesn't provide a mechanism for refreshing endpoints.
# See: https://github.com/systemd/systemd/issues/9911
# This hack does the job but takes down the whole interface to do it.
script = ''
ip link delete ${name}
networkctl reload
'';
};
in
{
meta.maintainers = [ lib.maintainers.majiir ];
options.networking.wireguard = {
useNetworkd = mkOption {
default = config.networking.useNetworkd;
defaultText = literalExpression "config.networking.useNetworkd";
type = types.bool;
description = ''
Whether to use networkd as the network configuration backend for
Wireguard instead of the legacy script-based system.
::: {.warning}
Some options have slightly different behavior with the networkd and
script-based backends. Check the documentation for each Wireguard
option you use before enabling this option.
:::
'';
};
};
config = mkIf (cfg.enable && cfg.useNetworkd) {
# TODO: Some of these options may be possible to support in networkd.
#
# privateKey and presharedKey are trivial to support, but we deliberately
# don't in order to discourage putting secrets in the /nix store.
#
# generatePrivateKeyFile can be supported if we can order a service before
# networkd configures interfaces. There is also a systemd feature request
# for key generation: https://github.com/systemd/systemd/issues/14282
#
# preSetup, postSetup, preShutdown and postShutdown may be possible, but
# networkd is not likely to support script hooks like this directly. See:
# https://github.com/systemd/systemd/issues/11629
#
# socketNamespace and interfaceNamespace can be implemented once networkd
# supports setting a netdev's namespace. See:
# https://github.com/systemd/systemd/issues/11629
# https://github.com/systemd/systemd/pull/14915
assertions = concatLists (
flip mapAttrsToList cfg.interfaces (
name: interface:
[
# Interface assertions
{
assertion = interface.privateKey == null;
message = "networking.wireguard.interfaces.${name}.privateKey cannot be used with networkd. Use privateKeyFile instead.";
}
{
assertion = !interface.generatePrivateKeyFile;
message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile cannot be used with networkd.";
}
{
assertion = interface.preSetup == "";
message = "networking.wireguard.interfaces.${name}.preSetup cannot be used with networkd.";
}
{
assertion = interface.postSetup == "";
message = "networking.wireguard.interfaces.${name}.postSetup cannot be used with networkd.";
}
{
assertion = interface.preShutdown == "";
message = "networking.wireguard.interfaces.${name}.preShutdown cannot be used with networkd.";
}
{
assertion = interface.postShutdown == "";
message = "networking.wireguard.interfaces.${name}.postShutdown cannot be used with networkd.";
}
{
assertion = interface.socketNamespace == null;
message = "networking.wireguard.interfaces.${name}.socketNamespace cannot be used with networkd.";
}
{
assertion = interface.interfaceNamespace == null;
message = "networking.wireguard.interfaces.${name}.interfaceNamespace cannot be used with networkd.";
}
]
++ flip concatMap interface.ips (ip: [
# IP assertions
{
assertion = hasInfix "/" ip;
message = "networking.wireguard.interfaces.${name}.ips value \"${ip}\" requires a subnet (e.g. 192.0.2.1/32) with networkd.";
}
])
++ flip concatMap interface.peers (peer: [
# Peer assertions
{
assertion = peer.presharedKey == null;
message = "networking.wireguard.interfaces.${name}.peers[].presharedKey cannot be used with networkd. Use presharedKeyFile instead.";
}
{
assertion = peer.dynamicEndpointRefreshSeconds == null;
message = "networking.wireguard.interfaces.${name}.peers[].dynamicEndpointRefreshSeconds cannot be used with networkd. Use networking.wireguard.interfaces.${name}.dynamicEndpointRefreshSeconds instead.";
}
{
assertion = peer.dynamicEndpointRefreshRestartSeconds == null;
message = "networking.wireguard.interfaces.${name}.peers[].dynamicEndpointRefreshRestartSeconds cannot be used with networkd.";
}
])
)
);
systemd.network = {
enable = true;
netdevs = mapAttrs' generateNetdev cfg.interfaces;
networks = mapAttrs generateNetwork cfg.interfaces;
};
systemd.timers = mapAttrs' generateRefreshTimer refreshEnabledInterfaces;
systemd.services = mapAttrs' generateRefreshService refreshEnabledInterfaces;
};
}