10   cfg = config.networking.wireguard;
 
  11   opt = options.networking.wireguard;
 
  17           type = with types; listOf (submodule peerOpts);
 
  20           enable = mkEnableOption ''
 
  21             announcing of peers' endpoints.
 
  23           listenPort = mkOption {
 
  25             defaultText = "<option>networking.wireguard.listenPort</option> or 51820 if null";
 
  27               TCP port on which to listen for peers queries for other peers' endpoints.
 
  30               This exposes the public key and current (or last known) endpoint address of all the peers
 
  31               configured in the WireGuard interface, to all the peers,
 
  32               and to any host connected to the peers or announcing peer
 
  33               able to query the WireGuard interface
 
  34               by spoofing the allowedIPs of any peer.
 
  42           listenPort = mkDefault (if config.listenPort == null then 51820 else config.listenPort);
 
  51           enable = mkEnableOption ''
 
  52             receiving the enpoint of other peers from a periodic TCP connection.
 
  53             That connection is meant to be to a peer using <option>networking.wireguard.peersAnnouncing.enable</option>.
 
  55             This is especially useful to punch-through non-symmetric NATs
 
  56             effectively establishing peer-to-peer connectivity:
 
  57             the peers initiate a Wireguard connection to the announcing peer,
 
  58             then use that Wireguard tunnel to query the endpoints of the other peers
 
  59             to establish Wireguard tunnels to them,
 
  60             using UDP hole punching when both peers are behind non-symmetric NATs.
 
  62             Note that it can also work for two peers behind the same NAT
 
  63             if that NAT can reflect (to its internal network) connections
 
  64             from its internal network to its external IP address,
 
  65             which may require to set persistentKeepalive as low as 1
 
  66             and/or redirecting at least one host's WireGuard port, eg. with UPnP.
 
  70             defaultText = "address of the first item of the peer's <option>allowedIPs</option>";
 
  72               Address at which to contact the announcing peer,
 
  73               configured on the announcing peer's <option>networking.wireguard.ips</option>.
 
  78             defaultText = "port of the peer's endpoint";
 
  80               TCP port on which to contact the peer announcing other peers.
 
  81               This is configured on the announcing peer's <option>networking.wireguard.peersAnnouncing.listenPort</option>.
 
  84           refreshSeconds = mkOption {
 
  85             type = with types; int;
 
  89               Time to wait between two successive queries of the endpoints known by the peer.
 
  96           addr = mkDefault (head (builtins.match "^\([^/]*\).*$" (head (config.allowedIPs))));
 
  97           port = mkDefault (toInt (head (builtins.match "^.*:\([0-9]*\)$" config.endpoint)));
 
 102   keyToUnitName = replaceStrings [ "/" "-" " " "+" "=" ] [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
 
 104   peerUnitServiceName =
 
 105     interfaceName: publicKey: dynamicRefreshEnabled:
 
 107       unitName = keyToUnitName publicKey;
 
 108       refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
 
 110     "wireguard-${interfaceName}-peer-${unitName}${refreshSuffix}";
 
 113   # - systemd-analyze security wireguard-${iface}-peers-announcing@
 
 114   # - systemd-analyze security wireguard-${iface}-endpoints-updater-${public_key}.service
 
 115   # Note that PrivateUsers=true would be too restrictive wrt. capabilities.
 
 116   peerUpdateSecurity = {
 
 117     IPAddressDeny = "any";
 
 119     AmbientCapabilities = [ "CAP_NET_ADMIN" ];
 
 120     CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
 
 121     RestrictNamespaces = true;
 
 123     NoNewPrivileges = true;
 
 124     PrivateDevices = true;
 
 126     ProtectControlGroups = true;
 
 128     ProtectKernelLogs = true;
 
 129     ProtectKernelModules = true;
 
 130     ProtectKernelTunables = true;
 
 131     ProtectProc = "invisible";
 
 132     SystemCallArchitectures = "native";
 
 133     # Remove (likely) unused groups from the basic @system-service group
 
 144     RestrictRealtime = true;
 
 145     LockPersonality = true;
 
 146     MemoryDenyWriteExecute = true;
 
 148     ProtectHostname = true;
 
 152   generatePeersAnnouncingSocket =
 
 154     nameValuePair "wireguard-${name}-peers-announcing" {
 
 155       enable = values.peersAnnouncing.enable;
 
 156       listenStreams = [ (toString values.peersAnnouncing.listenPort) ];
 
 157       socketConfig.Accept = true;
 
 158       # Basic firewalling restricting answers to peers
 
 159       # querying an internal IP address of the announcing peer.
 
 160       # Note that IPv4 addresses can be spoofed using other interfaces unless
 
 161       # sysctl net.ipv4.conf.${name}.rp_filter=1
 
 162       socketConfig.BindToDevice = name;
 
 163       socketConfig.IPAddressAllow = map (peer: peer.allowedIPs) values.peers;
 
 164       socketConfig.IPAddressDeny = "any";
 
 165       socketConfig.MaxConnectionsPerSource = 1;
 
 166       socketConfig.ReusePort = true;
 
 167       wantedBy = [ "sockets.target" ];
 
 170   generatePeersAnnouncingUnit =
 
 172     nameValuePair "wireguard-${name}-peers-announcing@" {
 
 173       description = "WireGuard Peers Announcing - ${name}";
 
 174       requires = [ "wireguard-${name}.service" ];
 
 175       after = [ "wireguard-${name}.service" ];
 
 177       serviceConfig = mkMerge [
 
 181           ExecStart = "${pkgs.wireguard-tools}/bin/wg show '${name}' endpoints";
 
 182           StandardInput = "null";
 
 183           StandardOutput = "socket";
 
 184           RestrictAddressFamilies = "";
 
 186         (mkIf (values.interfaceNamespace != null) {
 
 187           NetworkNamespacePath = "/var/run/netns/${values.interfaceNamespace}";
 
 192   generateEndpointsUpdaterUnit =
 
 199       dynamicRefreshEnabled = peer.dynamicEndpointRefreshSeconds != 0;
 
 200       peerService = peerUnitServiceName interfaceName peer.publicKey dynamicRefreshEnabled;
 
 202     nameValuePair "wireguard-${interfaceName}-endpoints-updater-${keyToUnitName peer.publicKey}" {
 
 203       description = "WireGuard ${interfaceName} Endpoints Updater - ${peer.publicKey}";
 
 204       requires = [ "${peerService}.service" ];
 
 205       after = [ "${peerService}.service" ];
 
 206       wantedBy = [ "${peerService}.service" ];
 
 207       path = with pkgs; [ wireguard-tools ];
 
 210         StartLimitIntervalSec = 0;
 
 212       serviceConfig = mkMerge [
 
 216           IPAddressAllow = [ peer.endpointsUpdater.addr ];
 
 217           RestrictAddressFamilies = [
 
 222           Restart = "on-failure";
 
 224         (mkIf (interfaceCfg.interfaceNamespace != null) {
 
 225           NetworkNamespacePath = "/var/run/netns/${interfaceCfg.interfaceNamespace}";
 
 229       # Query the peer announcing other peers
 
 230       # for setting the current endpoint of all configured peers
 
 231       # (but the announcing peer).
 
 232       # Note that socat is used instead of libressl's netcat
 
 233       # (which would require MemoryDenyWriteExecute=true to load libtls.so)
 
 234       # or netcat-gnu (which does not work on ARM and has last been released in 2004).
 
 236         wait="${toString peer.endpointsUpdater.refreshSeconds}"
 
 237         declare -A configured_keys
 
 239           concatMapStringsSep " " (
 
 240             p: optionalString (p.publicKey != peer.publicKey) "[${p.publicKey}]=set"
 
 245           # Set stdin to socat's stdout
 
 246           exec < <(exec ${pkgs.socat}/bin/socat STDOUT \
 
 247             "TCP:${with peer.endpointsUpdater; addr + ":" + toString port}")
 
 249           # Update the endpoint of each configured peer
 
 250           while read -t "$wait" -n 128 -r public_key endpoint x; do
 
 251             if [ "$endpoint" != "(none)" -a "''${configured_keys[$public_key]}" ]; then
 
 252               wg set "${interfaceName}" peer "$public_key" endpoint "$endpoint"
 
 261   all_peers = flatten (
 
 263       interfaceName: interfaceCfg:
 
 264       map (peer: { inherit interfaceName interfaceCfg peer; }) interfaceCfg.peers
 
 269   options.networking.wireguard = {
 
 270     interfaces = mkOption {
 
 271       type = with types; attrsOf (submodule interfaceOpts);
 
 275     systemd.sockets = mapAttrs' generatePeersAnnouncingSocket cfg.interfaces;
 
 278       (mapAttrs' generatePeersAnnouncingUnit cfg.interfaces)
 
 280         map generateEndpointsUpdaterUnit (filter ({ peer, ... }: peer.endpointsUpdater.enable) all_peers)