10 inherit (lib.attrsets)
23 inherit (lib.modules) mkIf;
24 inherit (lib.options) literalExpression mkOption;
25 inherit (lib.strings) hasInfix replaceStrings;
26 inherit (lib.trivial) flip pipe;
28 removeNulls = filterAttrs (_: v: v != null);
30 escapeCredentialName = input: replaceStrings [ "\\" ] [ "_" ] input;
32 privateKeyCredential = interfaceName: escapeCredentialName "wireguard-${interfaceName}-private-key";
33 presharedKeyCredential =
34 interfaceName: peer: escapeCredentialName "wireguard-${interfaceName}-${peer.name}-preshared-key";
36 interfaceCredentials =
37 interfaceName: interface:
38 [ "${privateKeyCredential interfaceName}:${interface.privateKeyFile}" ]
39 ++ pipe interface.peers [
40 (filter (peer: peer.presharedKeyFile != null))
41 (map (peer: "${presharedKeyCredential interfaceName peer}:${peer.presharedKeyFile}"))
46 nameValuePair "40-${name}" {
47 netdevConfig = removeNulls {
50 MTUBytes = interface.mtu;
52 wireguardConfig = removeNulls {
53 PrivateKey = "@${privateKeyCredential name}";
54 ListenPort = interface.listenPort;
55 FirewallMark = interface.fwMark;
56 RouteTable = if interface.allowedIPsAsRoutes then interface.table else null;
57 RouteMetric = interface.metric;
59 wireguardPeers = map (generateWireguardPeer name) interface.peers;
62 generateWireguardPeer =
65 PublicKey = peer.publicKey;
67 if peer.presharedKeyFile == null then null else "@${presharedKeyCredential interfaceName peer}";
68 AllowedIPs = peer.allowedIPs;
69 Endpoint = peer.endpoint;
70 PersistentKeepalive = peer.persistentKeepalive;
73 generateNetwork = name: interface: {
74 matchConfig.Name = name;
75 address = interface.ips;
78 cfg = config.networking.wireguard;
80 refreshEnabledInterfaces = filterAttrs (
81 name: interface: interface.dynamicEndpointRefreshSeconds != 0
84 generateRefreshTimer =
86 nameValuePair "wireguard-dynamic-refresh-${name}" {
87 partOf = [ "wireguard-dynamic-refresh-${name}.service" ];
88 wantedBy = [ "timers.target" ];
89 description = "Wireguard dynamic endpoint refresh (${name}) timer";
90 timerConfig.OnBootSec = interface.dynamicEndpointRefreshSeconds;
91 timerConfig.OnUnitInactiveSec = interface.dynamicEndpointRefreshSeconds;
94 generateRefreshService =
96 nameValuePair "wireguard-dynamic-refresh-${name}" {
97 description = "Wireguard dynamic endpoint refresh (${name})";
98 after = [ "network-online.target" ];
99 wants = [ "network-online.target" ];
104 # networkd doesn't provide a mechanism for refreshing endpoints.
105 # See: https://github.com/systemd/systemd/issues/9911
106 # This hack does the job but takes down the whole interface to do it.
108 ip link delete ${name}
115 meta.maintainers = [ lib.maintainers.majiir ];
117 options.networking.wireguard = {
118 useNetworkd = mkOption {
119 default = config.networking.useNetworkd;
120 defaultText = literalExpression "config.networking.useNetworkd";
123 Whether to use networkd as the network configuration backend for
124 Wireguard instead of the legacy script-based system.
127 Some options have slightly different behavior with the networkd and
128 script-based backends. Check the documentation for each Wireguard
129 option you use before enabling this option.
135 config = mkIf (cfg.enable && cfg.useNetworkd) {
137 # TODO: Some of these options may be possible to support in networkd.
139 # privateKey and presharedKey are trivial to support, but we deliberately
140 # don't in order to discourage putting secrets in the /nix store.
142 # generatePrivateKeyFile can be supported if we can order a service before
143 # networkd configures interfaces. There is also a systemd feature request
144 # for key generation: https://github.com/systemd/systemd/issues/14282
146 # preSetup, postSetup, preShutdown and postShutdown may be possible, but
147 # networkd is not likely to support script hooks like this directly. See:
148 # https://github.com/systemd/systemd/issues/11629
150 # socketNamespace and interfaceNamespace can be implemented once networkd
151 # supports setting a netdev's namespace. See:
152 # https://github.com/systemd/systemd/issues/11103
153 # https://github.com/systemd/systemd/pull/14915
155 assertions = concatLists (
156 flip mapAttrsToList cfg.interfaces (
159 # Interface assertions
161 assertion = interface.privateKey == null;
162 message = "networking.wireguard.interfaces.${name}.privateKey cannot be used with networkd. Use privateKeyFile instead.";
165 assertion = !interface.generatePrivateKeyFile;
166 message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile cannot be used with networkd.";
169 assertion = interface.preSetup == "";
170 message = "networking.wireguard.interfaces.${name}.preSetup cannot be used with networkd.";
173 assertion = interface.postSetup == "";
174 message = "networking.wireguard.interfaces.${name}.postSetup cannot be used with networkd.";
177 assertion = interface.preShutdown == "";
178 message = "networking.wireguard.interfaces.${name}.preShutdown cannot be used with networkd.";
181 assertion = interface.postShutdown == "";
182 message = "networking.wireguard.interfaces.${name}.postShutdown cannot be used with networkd.";
185 assertion = interface.socketNamespace == null;
186 message = "networking.wireguard.interfaces.${name}.socketNamespace cannot be used with networkd.";
189 assertion = interface.interfaceNamespace == null;
190 message = "networking.wireguard.interfaces.${name}.interfaceNamespace cannot be used with networkd.";
193 ++ flip concatMap interface.ips (ip: [
196 assertion = hasInfix "/" ip;
197 message = "networking.wireguard.interfaces.${name}.ips value \"${ip}\" requires a subnet (e.g. 192.0.2.1/32) with networkd.";
200 ++ flip concatMap interface.peers (peer: [
203 assertion = peer.presharedKey == null;
204 message = "networking.wireguard.interfaces.${name}.peers[].presharedKey cannot be used with networkd. Use presharedKeyFile instead.";
207 assertion = peer.dynamicEndpointRefreshSeconds == null;
208 message = "networking.wireguard.interfaces.${name}.peers[].dynamicEndpointRefreshSeconds cannot be used with networkd. Use networking.wireguard.interfaces.${name}.dynamicEndpointRefreshSeconds instead.";
211 assertion = peer.dynamicEndpointRefreshRestartSeconds == null;
212 message = "networking.wireguard.interfaces.${name}.peers[].dynamicEndpointRefreshRestartSeconds cannot be used with networkd.";
220 netdevs = mapAttrs' generateNetdev cfg.interfaces;
221 networks = mapAttrs generateNetwork cfg.interfaces;
224 systemd.timers = mapAttrs' generateRefreshTimer refreshEnabledInterfaces;
225 systemd.services = (mapAttrs' generateRefreshService refreshEnabledInterfaces) // {
226 systemd-networkd.serviceConfig.LoadCredential = flatten (
227 mapAttrsToList interfaceCredentials cfg.interfaces