{ 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 networkmanager-sstp ] ++ optional (!delegateWireless && !enableIwd) wpa_supplicant; delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != []; enableIwd = cfg.wifi.backend == "iwd"; mkValue = v: if v == true then "yes" else if v == false then "no" else if lib.isInt v then toString v else v; mkSection = name: attrs: '' [${name}] ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${mkValue v}") (lib.filterAttrs (k: v: v != null) attrs)) } ''; configFile = pkgs.writeText "NetworkManager.conf" (lib.concatStringsSep "\n" [ (mkSection "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"; firewall-backend = cfg.firewallBackend; }) (mkSection "keyfile" { unmanaged-devices = if cfg.unmanaged == [] then null else lib.concatStringsSep ";" cfg.unmanaged; }) (mkSection "logging" { audit = config.security.audit.enable; level = cfg.logLevel; }) (mkSection "connection" cfg.connectionConfig) (mkSection "device" { "wifi.scan-rand-mac-address" = cfg.wifi.scanRandMacAddress; "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. "XX:XX:XX:XX:XX:XX" MAC address of the interface "permanent" Use the permanent MAC address of the device "preserve" Don’t change the MAC address of the device upon activation "random" Generate a randomized value upon each connect "stable" Generate a stable, hashed MAC address ''; }; 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 networkmanager will be created. Add all users that should have permission to change network settings to this group. ''; }; connectionConfig = mkOption { type = with types; attrsOf (nullOr (oneOf [ bool int str ])); default = {}; description = '' Configuration for the [connection] section of NetworkManager.conf. Refer to https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#id-1.2.3.11 or NetworkManager.conf 5 for more information. ''; }; extraConfig = mkOption { type = types.lines; default = ""; description = '' Configuration appended to the generated NetworkManager.conf. Refer to https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html or NetworkManager.conf 5 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 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec or the "Device List Format" Appendix of NetworkManager.conf 5 . ''; }; 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. ''; }; firewallBackend = mkOption { type = types.enum [ "iptables" "nftables" "none" ]; default = "iptables"; description = '' Which firewall backend should be used for configuring masquerading with shared mode. If set to none, NetworkManager doesn't manage the configuration at all. ''; }; 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 or (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 (resolv.conf) processing mode. A description of these modes can be found in the main section of https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html or in NetworkManager.conf 5 . ''; }; 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 https://developer.gnome.org/NetworkManager/stable/NetworkManager.html 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. If you enable this option the networkmanager_strongswan plugin will be added to the 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 unmanaged 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"; "NetworkManager/VPN/nm-sstp-service.name".source = "${networkmanager-sstp}/lib/NetworkManager/VPN/nm-sstp-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.iproute2 pkgs.util-linux 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; }) { networkmanager.connectionConfig = { "ipv6.ip6-privacy" = 2; "ethernet.cloned-mac-address" = cfg.ethernet.macAddress; "wifi.cloned-mac-address" = cfg.wifi.macAddress; "wifi.powersave" = if cfg.wifi.powersave == null then null else if cfg.wifi.powersave then 3 else 2; }; } ]; boot.kernelModules = [ "ctr" ]; 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; }; }