492 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			492 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						||
 | 
						||
with lib;
 | 
						||
 | 
						||
let
 | 
						||
  cfg = config.networking.networkmanager;
 | 
						||
 | 
						||
  basePackages = with pkgs; [
 | 
						||
    crda
 | 
						||
    modemmanager
 | 
						||
    networkmanager
 | 
						||
    networkmanager-fortisslvpn
 | 
						||
    networkmanager-iodine
 | 
						||
    networkmanager-l2tp
 | 
						||
    networkmanager-openconnect
 | 
						||
    networkmanager-openvpn
 | 
						||
    networkmanager-vpnc
 | 
						||
   ] ++ optional (!delegateWireless && !enableIwd) wpa_supplicant;
 | 
						||
 | 
						||
  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];
 | 
						||
 | 
						||
  enableIwd = cfg.wifi.backend == "iwd";
 | 
						||
 | 
						||
  configFile = pkgs.writeText "NetworkManager.conf" ''
 | 
						||
    [main]
 | 
						||
    plugins=keyfile
 | 
						||
    dhcp=${cfg.dhcp}
 | 
						||
    dns=${cfg.dns}
 | 
						||
    # If resolvconf is disabled that means that resolv.conf is managed by some other module.
 | 
						||
    rc-manager=${if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"}
 | 
						||
 | 
						||
    [keyfile]
 | 
						||
    ${optionalString (cfg.unmanaged != [])
 | 
						||
      ''unmanaged-devices=${lib.concatStringsSep ";" cfg.unmanaged}''}
 | 
						||
 | 
						||
    [logging]
 | 
						||
    level=${cfg.logLevel}
 | 
						||
    audit=${lib.boolToString config.security.audit.enable}
 | 
						||
 | 
						||
    [connection]
 | 
						||
    ipv6.ip6-privacy=2
 | 
						||
    ethernet.cloned-mac-address=${cfg.ethernet.macAddress}
 | 
						||
    wifi.cloned-mac-address=${cfg.wifi.macAddress}
 | 
						||
    ${optionalString (cfg.wifi.powersave != null)
 | 
						||
      ''wifi.powersave=${if cfg.wifi.powersave then "3" else "2"}''}
 | 
						||
 | 
						||
    [device]
 | 
						||
    wifi.scan-rand-mac-address=${if cfg.wifi.scanRandMacAddress then "yes" else "no"}
 | 
						||
    wifi.backend=${cfg.wifi.backend}
 | 
						||
 | 
						||
    ${cfg.extraConfig}
 | 
						||
  '';
 | 
						||
 | 
						||
  /*
 | 
						||
    [network-manager]
 | 
						||
    Identity=unix-group:networkmanager
 | 
						||
    Action=org.freedesktop.NetworkManager.*
 | 
						||
    ResultAny=yes
 | 
						||
    ResultInactive=no
 | 
						||
    ResultActive=yes
 | 
						||
 | 
						||
    [modem-manager]
 | 
						||
    Identity=unix-group:networkmanager
 | 
						||
    Action=org.freedesktop.ModemManager*
 | 
						||
    ResultAny=yes
 | 
						||
    ResultInactive=no
 | 
						||
    ResultActive=yes
 | 
						||
  */
 | 
						||
  polkitConf = ''
 | 
						||
    polkit.addRule(function(action, subject) {
 | 
						||
      if (
 | 
						||
        subject.isInGroup("networkmanager")
 | 
						||
        && (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
 | 
						||
            || action.id.indexOf("org.freedesktop.ModemManager")  == 0
 | 
						||
        ))
 | 
						||
          { return polkit.Result.YES; }
 | 
						||
    });
 | 
						||
  '';
 | 
						||
 | 
						||
  ns = xs: pkgs.writeText "nameservers" (
 | 
						||
    concatStrings (map (s: "nameserver ${s}\n") xs)
 | 
						||
  );
 | 
						||
 | 
						||
  overrideNameserversScript = pkgs.writeScript "02overridedns" ''
 | 
						||
    #!/bin/sh
 | 
						||
    PATH=${with pkgs; makeBinPath [ gnused gnugrep coreutils ]}
 | 
						||
    tmp=$(mktemp)
 | 
						||
    sed '/nameserver /d' /etc/resolv.conf > $tmp
 | 
						||
    grep 'nameserver ' /etc/resolv.conf | \
 | 
						||
      grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
 | 
						||
    cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf
 | 
						||
    rm -f $tmp $tmp.ns
 | 
						||
  '';
 | 
						||
 | 
						||
  dispatcherTypesSubdirMap = {
 | 
						||
    basic = "";
 | 
						||
    pre-up = "pre-up.d/";
 | 
						||
    pre-down = "pre-down.d/";
 | 
						||
  };
 | 
						||
 | 
						||
  macAddressOpt = mkOption {
 | 
						||
    type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
 | 
						||
    default = "preserve";
 | 
						||
    example = "00:11:22:33:44:55";
 | 
						||
    description = ''
 | 
						||
      Set the MAC address of the interface.
 | 
						||
      <variablelist>
 | 
						||
        <varlistentry>
 | 
						||
          <term>"XX:XX:XX:XX:XX:XX"</term>
 | 
						||
          <listitem><para>MAC address of the interface</para></listitem>
 | 
						||
        </varlistentry>
 | 
						||
        <varlistentry>
 | 
						||
          <term><literal>"permanent"</literal></term>
 | 
						||
          <listitem><para>Use the permanent MAC address of the device</para></listitem>
 | 
						||
        </varlistentry>
 | 
						||
        <varlistentry>
 | 
						||
          <term><literal>"preserve"</literal></term>
 | 
						||
          <listitem><para>Don’t change the MAC address of the device upon activation</para></listitem>
 | 
						||
        </varlistentry>
 | 
						||
        <varlistentry>
 | 
						||
          <term><literal>"random"</literal></term>
 | 
						||
          <listitem><para>Generate a randomized value upon each connect</para></listitem>
 | 
						||
        </varlistentry>
 | 
						||
        <varlistentry>
 | 
						||
          <term><literal>"stable"</literal></term>
 | 
						||
          <listitem><para>Generate a stable, hashed MAC address</para></listitem>
 | 
						||
        </varlistentry>
 | 
						||
      </variablelist>
 | 
						||
    '';
 | 
						||
  };
 | 
						||
 | 
						||
in {
 | 
						||
 | 
						||
  meta = {
 | 
						||
    maintainers = teams.freedesktop.members;
 | 
						||
  };
 | 
						||
 | 
						||
  ###### interface
 | 
						||
 | 
						||
  options = {
 | 
						||
 | 
						||
    networking.networkmanager = {
 | 
						||
 | 
						||
      enable = mkOption {
 | 
						||
        type = types.bool;
 | 
						||
        default = false;
 | 
						||
        description = ''
 | 
						||
          Whether to use NetworkManager to obtain an IP address and other
 | 
						||
          configuration for all network interfaces that are not manually
 | 
						||
          configured. If enabled, a group <literal>networkmanager</literal>
 | 
						||
          will be created. Add all users that should have permission
 | 
						||
          to change network settings to this group.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      extraConfig = mkOption {
 | 
						||
        type = types.lines;
 | 
						||
        default = "";
 | 
						||
        description = ''
 | 
						||
          Configuration appended to the generated NetworkManager.conf.
 | 
						||
          Refer to
 | 
						||
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
 | 
						||
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
 | 
						||
          </link>
 | 
						||
          or
 | 
						||
          <citerefentry>
 | 
						||
            <refentrytitle>NetworkManager.conf</refentrytitle>
 | 
						||
            <manvolnum>5</manvolnum>
 | 
						||
          </citerefentry>
 | 
						||
          for more information.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      unmanaged = mkOption {
 | 
						||
        type = types.listOf types.str;
 | 
						||
        default = [];
 | 
						||
        description = ''
 | 
						||
          List of interfaces that will not be managed by NetworkManager.
 | 
						||
          Interface name can be specified here, but if you need more fidelity,
 | 
						||
          refer to
 | 
						||
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec">
 | 
						||
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
 | 
						||
          </link>
 | 
						||
          or the "Device List Format" Appendix of
 | 
						||
          <citerefentry>
 | 
						||
            <refentrytitle>NetworkManager.conf</refentrytitle>
 | 
						||
            <manvolnum>5</manvolnum>
 | 
						||
          </citerefentry>.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      packages = mkOption {
 | 
						||
        type = types.listOf types.package;
 | 
						||
        default = [ ];
 | 
						||
        description = ''
 | 
						||
          Extra packages that provide NetworkManager plugins.
 | 
						||
        '';
 | 
						||
        apply = list: basePackages ++ list;
 | 
						||
      };
 | 
						||
 | 
						||
      dhcp = mkOption {
 | 
						||
        type = types.enum [ "dhclient" "dhcpcd" "internal" ];
 | 
						||
        default = "internal";
 | 
						||
        description = ''
 | 
						||
          Which program (or internal library) should be used for DHCP.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      logLevel = mkOption {
 | 
						||
        type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
 | 
						||
        default = "WARN";
 | 
						||
        description = ''
 | 
						||
          Set the default logging verbosity level.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      appendNameservers = mkOption {
 | 
						||
        type = types.listOf types.str;
 | 
						||
        default = [];
 | 
						||
        description = ''
 | 
						||
          A list of name servers that should be appended
 | 
						||
          to the ones configured in NetworkManager or received by DHCP.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      insertNameservers = mkOption {
 | 
						||
        type = types.listOf types.str;
 | 
						||
        default = [];
 | 
						||
        description = ''
 | 
						||
          A list of name servers that should be inserted before
 | 
						||
          the ones configured in NetworkManager or received by DHCP.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      ethernet.macAddress = macAddressOpt;
 | 
						||
 | 
						||
      wifi = {
 | 
						||
        macAddress = macAddressOpt;
 | 
						||
 | 
						||
        backend = mkOption {
 | 
						||
          type = types.enum [ "wpa_supplicant" "iwd" ];
 | 
						||
          default = "wpa_supplicant";
 | 
						||
          description = ''
 | 
						||
            Specify the Wi-Fi backend used for the device.
 | 
						||
            Currently supported are <option>wpa_supplicant</option> or <option>iwd</option> (experimental).
 | 
						||
          '';
 | 
						||
        };
 | 
						||
 | 
						||
        powersave = mkOption {
 | 
						||
          type = types.nullOr types.bool;
 | 
						||
          default = null;
 | 
						||
          description = ''
 | 
						||
            Whether to enable Wi-Fi power saving.
 | 
						||
          '';
 | 
						||
        };
 | 
						||
 | 
						||
        scanRandMacAddress = mkOption {
 | 
						||
          type = types.bool;
 | 
						||
          default = true;
 | 
						||
          description = ''
 | 
						||
            Whether to enable MAC address randomization of a Wi-Fi device
 | 
						||
            during scanning.
 | 
						||
          '';
 | 
						||
        };
 | 
						||
      };
 | 
						||
 | 
						||
      dns = mkOption {
 | 
						||
        type = types.enum [ "default" "dnsmasq" "unbound" "systemd-resolved" "none" ];
 | 
						||
        default = "default";
 | 
						||
        description = ''
 | 
						||
          Set the DNS (<literal>resolv.conf</literal>) processing mode.
 | 
						||
          </para>
 | 
						||
          <para>
 | 
						||
          A description of these modes can be found in the main section of
 | 
						||
          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
 | 
						||
            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
 | 
						||
          </link>
 | 
						||
          or in
 | 
						||
          <citerefentry>
 | 
						||
            <refentrytitle>NetworkManager.conf</refentrytitle>
 | 
						||
            <manvolnum>5</manvolnum>
 | 
						||
          </citerefentry>.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      dispatcherScripts = mkOption {
 | 
						||
        type = types.listOf (types.submodule {
 | 
						||
          options = {
 | 
						||
            source = mkOption {
 | 
						||
              type = types.path;
 | 
						||
              description = ''
 | 
						||
                Path to the hook script.
 | 
						||
              '';
 | 
						||
            };
 | 
						||
 | 
						||
            type = mkOption {
 | 
						||
              type = types.enum (attrNames dispatcherTypesSubdirMap);
 | 
						||
              default = "basic";
 | 
						||
              description = ''
 | 
						||
                Dispatcher hook type. Look up the hooks described at
 | 
						||
                <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.html">https://developer.gnome.org/NetworkManager/stable/NetworkManager.html</link>
 | 
						||
                and choose the type depending on the output folder.
 | 
						||
                You should then filter the event type (e.g., "up"/"down") from within your script.
 | 
						||
              '';
 | 
						||
            };
 | 
						||
          };
 | 
						||
        });
 | 
						||
        default = [];
 | 
						||
        example = literalExample ''
 | 
						||
        [ {
 | 
						||
              source = pkgs.writeText "upHook" '''
 | 
						||
 | 
						||
                if [ "$2" != "up" ]; then
 | 
						||
                    logger "exit: event $2 != up"
 | 
						||
                    exit
 | 
						||
                fi
 | 
						||
 | 
						||
                # coreutils and iproute are in PATH too
 | 
						||
                logger "Device $DEVICE_IFACE coming up"
 | 
						||
            ''';
 | 
						||
            type = "basic";
 | 
						||
        } ]'';
 | 
						||
        description = ''
 | 
						||
          A list of scripts which will be executed in response to  network  events.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
 | 
						||
      enableStrongSwan = mkOption {
 | 
						||
        type = types.bool;
 | 
						||
        default = false;
 | 
						||
        description = ''
 | 
						||
          Enable the StrongSwan plugin.
 | 
						||
          </para><para>
 | 
						||
          If you enable this option the
 | 
						||
          <literal>networkmanager_strongswan</literal> plugin will be added to
 | 
						||
          the <option>networking.networkmanager.packages</option> option
 | 
						||
          so you don't need to to that yourself.
 | 
						||
        '';
 | 
						||
      };
 | 
						||
    };
 | 
						||
  };
 | 
						||
 | 
						||
  imports = [
 | 
						||
    (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
 | 
						||
    (mkRemovedOptionModule ["networking" "networkmanager" "dynamicHosts"] ''
 | 
						||
      This option was removed because allowing (multiple) regular users to
 | 
						||
      override host entries affecting the whole system opens up a huge attack
 | 
						||
      vector. There seem to be very rare cases where this might be useful.
 | 
						||
      Consider setting system-wide host entries using networking.hosts, provide
 | 
						||
      them via the DNS server in your network, or use environment.etc
 | 
						||
      to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir.
 | 
						||
    '')
 | 
						||
  ];
 | 
						||
 | 
						||
 | 
						||
  ###### implementation
 | 
						||
 | 
						||
  config = mkIf cfg.enable {
 | 
						||
 | 
						||
    assertions = [
 | 
						||
      { assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [];
 | 
						||
        message = ''
 | 
						||
          You can not use networking.networkmanager with networking.wireless.
 | 
						||
          Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
 | 
						||
        '';
 | 
						||
      }
 | 
						||
    ];
 | 
						||
 | 
						||
    environment.etc = with pkgs; {
 | 
						||
      "NetworkManager/NetworkManager.conf".source = configFile;
 | 
						||
 | 
						||
      "NetworkManager/VPN/nm-openvpn-service.name".source =
 | 
						||
        "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
 | 
						||
 | 
						||
      "NetworkManager/VPN/nm-vpnc-service.name".source =
 | 
						||
        "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
 | 
						||
 | 
						||
      "NetworkManager/VPN/nm-openconnect-service.name".source =
 | 
						||
        "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
 | 
						||
 | 
						||
      "NetworkManager/VPN/nm-fortisslvpn-service.name".source =
 | 
						||
        "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
 | 
						||
 | 
						||
      "NetworkManager/VPN/nm-l2tp-service.name".source =
 | 
						||
        "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
 | 
						||
 | 
						||
      "NetworkManager/VPN/nm-iodine-service.name".source =
 | 
						||
        "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
 | 
						||
      }
 | 
						||
      // optionalAttrs (cfg.appendNameservers != [] || cfg.insertNameservers != [])
 | 
						||
         {
 | 
						||
           "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
 | 
						||
         }
 | 
						||
      // optionalAttrs cfg.enableStrongSwan
 | 
						||
         {
 | 
						||
           "NetworkManager/VPN/nm-strongswan-service.name".source =
 | 
						||
             "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
 | 
						||
         }
 | 
						||
      // listToAttrs (lib.imap1 (i: s:
 | 
						||
         {
 | 
						||
            name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
 | 
						||
            value = { mode = "0544"; inherit (s) source; };
 | 
						||
         }) cfg.dispatcherScripts);
 | 
						||
 | 
						||
    environment.systemPackages = cfg.packages;
 | 
						||
 | 
						||
    users.groups = {
 | 
						||
      networkmanager.gid = config.ids.gids.networkmanager;
 | 
						||
      nm-openvpn.gid = config.ids.gids.nm-openvpn;
 | 
						||
    };
 | 
						||
 | 
						||
    users.users = {
 | 
						||
      nm-openvpn = {
 | 
						||
        uid = config.ids.uids.nm-openvpn;
 | 
						||
        extraGroups = [ "networkmanager" ];
 | 
						||
      };
 | 
						||
      nm-iodine = {
 | 
						||
        isSystemUser = true;
 | 
						||
        group = "networkmanager";
 | 
						||
      };
 | 
						||
    };
 | 
						||
 | 
						||
    systemd.packages = cfg.packages;
 | 
						||
 | 
						||
    systemd.tmpfiles.rules = [
 | 
						||
      "d /etc/NetworkManager/system-connections 0700 root root -"
 | 
						||
      "d /etc/ipsec.d 0700 root root -"
 | 
						||
      "d /var/lib/NetworkManager-fortisslvpn 0700 root root -"
 | 
						||
 | 
						||
      "d /var/lib/dhclient 0755 root root -"
 | 
						||
      "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
 | 
						||
    ];
 | 
						||
 | 
						||
    systemd.services.NetworkManager = {
 | 
						||
      wantedBy = [ "network.target" ];
 | 
						||
      restartTriggers = [ configFile ];
 | 
						||
 | 
						||
      aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
 | 
						||
 | 
						||
      serviceConfig = {
 | 
						||
        StateDirectory = "NetworkManager";
 | 
						||
        StateDirectoryMode = 755; # not sure if this really needs to be 755
 | 
						||
      };
 | 
						||
    };
 | 
						||
 | 
						||
    systemd.services.NetworkManager-wait-online = {
 | 
						||
      wantedBy = [ "network-online.target" ];
 | 
						||
    };
 | 
						||
 | 
						||
    systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
 | 
						||
 | 
						||
    # override unit as recommended by upstream - see https://github.com/NixOS/nixpkgs/issues/88089
 | 
						||
    # TODO: keep an eye on modem-manager releases as this will eventually be added to the upstream unit
 | 
						||
    systemd.services.ModemManager.serviceConfig.ExecStart = [
 | 
						||
      ""
 | 
						||
      "${pkgs.modemmanager}/sbin/ModemManager --filter-policy=STRICT"
 | 
						||
    ];
 | 
						||
 | 
						||
    systemd.services.NetworkManager-dispatcher = {
 | 
						||
      wantedBy = [ "network.target" ];
 | 
						||
      restartTriggers = [ configFile overrideNameserversScript ];
 | 
						||
 | 
						||
      # useful binaries for user-specified hooks
 | 
						||
      path = [ pkgs.iproute pkgs.utillinux pkgs.coreutils ];
 | 
						||
      aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ];
 | 
						||
    };
 | 
						||
 | 
						||
    # Turn off NixOS' network management when networking is managed entirely by NetworkManager
 | 
						||
    networking = mkMerge [
 | 
						||
      (mkIf (!delegateWireless) {
 | 
						||
        useDHCP = false;
 | 
						||
      })
 | 
						||
 | 
						||
      (mkIf cfg.enableStrongSwan {
 | 
						||
        networkmanager.packages = [ pkgs.networkmanager_strongswan ];
 | 
						||
      })
 | 
						||
 | 
						||
      (mkIf enableIwd {
 | 
						||
        wireless.iwd.enable = true;
 | 
						||
      })
 | 
						||
    ];
 | 
						||
 | 
						||
    security.polkit.extraConfig = polkitConf;
 | 
						||
 | 
						||
    services.dbus.packages = cfg.packages
 | 
						||
      ++ optional cfg.enableStrongSwan pkgs.strongswanNM
 | 
						||
      ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;
 | 
						||
 | 
						||
    services.udev.packages = cfg.packages;
 | 
						||
  };
 | 
						||
}
 |