1 { config, lib, pkgs, ... }:
6 cfg = config.networking.networkmanager;
7 ini = pkgs.formats.ini { };
9 delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [ ];
11 enableIwd = cfg.wifi.backend == "iwd";
13 configAttrs = lib.recursiveUpdate {
16 inherit (cfg) dhcp dns;
17 # If resolvconf is disabled that means that resolv.conf is managed by some other module.
19 if config.networking.resolvconf.enable then "resolvconf"
24 if cfg.unmanaged == [ ] then null
25 else lib.concatStringsSep ";" cfg.unmanaged;
28 audit = config.security.audit.enable;
31 connection = cfg.connectionConfig;
33 "wifi.scan-rand-mac-address" = cfg.wifi.scanRandMacAddress;
34 "wifi.backend" = cfg.wifi.backend;
37 configFile = ini.generate "NetworkManager.conf" configAttrs;
41 Identity=unix-group:networkmanager
42 Action=org.freedesktop.NetworkManager.*
48 Identity=unix-group:networkmanager
49 Action=org.freedesktop.ModemManager*
55 polkit.addRule(function(action, subject) {
57 subject.isInGroup("networkmanager")
58 && (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
59 || action.id.indexOf("org.freedesktop.ModemManager") == 0
61 { return polkit.Result.YES; }
65 ns = xs: pkgs.writeText "nameservers" (
66 concatStrings (map (s: "nameserver ${s}\n") xs)
69 overrideNameserversScript = pkgs.writeScript "02overridedns" ''
71 PATH=${with pkgs; makeBinPath [ gnused gnugrep coreutils ]}
73 sed '/nameserver /d' /etc/resolv.conf > $tmp
74 grep 'nameserver ' /etc/resolv.conf | \
75 grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
76 cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf
80 dispatcherTypesSubdirMap = {
83 pre-down = "pre-down.d/";
86 macAddressOptWifi = mkOption {
87 type = types.either types.str (types.enum [ "permanent" "preserve" "random" "stable" "stable-ssid" ]);
89 example = "00:11:22:33:44:55";
91 Set the MAC address of the interface.
93 - `"XX:XX:XX:XX:XX:XX"`: MAC address of the interface
94 - `"permanent"`: Use the permanent MAC address of the device
95 - `"preserve"`: Don’t change the MAC address of the device upon activation
96 - `"random"`: Generate a randomized value upon each connect
97 - `"stable"`: Generate a stable, hashed MAC address
98 - `"stable-ssid"`: Generate a stable MAC addressed based on Wi-Fi network
102 macAddressOptEth = mkOption {
103 type = types.either types.str (types.enum [ "permanent" "preserve" "random" "stable" ]);
104 default = "preserve";
105 example = "00:11:22:33:44:55";
107 Set the MAC address of the interface.
109 - `"XX:XX:XX:XX:XX:XX"`: MAC address of the interface
110 - `"permanent"`: Use the permanent MAC address of the device
111 - `"preserve"`: Don’t change the MAC address of the device upon activation
112 - `"random"`: Generate a randomized value upon each connect
113 - `"stable"`: Generate a stable, hashed MAC address
122 ++ lib.optionals (!delegateWireless && !enableIwd) [
130 maintainers = teams.freedesktop.members;
137 networking.networkmanager = {
143 Whether to use NetworkManager to obtain an IP address and other
144 configuration for all network interfaces that are not manually
145 configured. If enabled, a group `networkmanager`
146 will be created. Add all users that should have permission
147 to change network settings to this group.
151 connectionConfig = mkOption {
152 type = with types; attrsOf (nullOr (oneOf [
159 Configuration for the [connection] section of NetworkManager.conf.
162 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#id-1.2.3.11
163 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html)
165 {manpage}`NetworkManager.conf(5)`
166 for more information.
170 settings = mkOption {
174 Configuration added to the generated NetworkManager.conf, note that you can overwrite settings with this.
177 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
178 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html)
180 {manpage}`NetworkManager.conf(5)`
181 for more information.
185 unmanaged = mkOption {
186 type = types.listOf types.str;
189 List of interfaces that will not be managed by NetworkManager.
190 Interface name can be specified here, but if you need more fidelity,
193 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
194 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec)
195 or the "Device List Format" Appendix of
196 {manpage}`NetworkManager.conf(5)`.
203 networkManagerPluginPackage = types.package // {
204 description = "NetworkManager plug-in";
208 (types.package.check p
209 && p ? networkManagerPlugin
210 && lib.isString p.networkManagerPlugin)
212 Package ‘${p.name}’, is not a NetworkManager plug-in.
213 Those need to have a ‘networkManagerPlugin’ attribute.
217 types.listOf networkManagerPluginPackage;
220 List of NetworkManager plug-ins to enable.
221 Some plug-ins are enabled by the NetworkManager module by default.
226 type = types.enum [ "dhcpcd" "internal" ];
227 default = "internal";
229 Which program (or internal library) should be used for DHCP.
233 logLevel = mkOption {
234 type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
237 Set the default logging verbosity level.
241 appendNameservers = mkOption {
242 type = types.listOf types.str;
245 A list of name servers that should be appended
246 to the ones configured in NetworkManager or received by DHCP.
250 insertNameservers = mkOption {
251 type = types.listOf types.str;
254 A list of name servers that should be inserted before
255 the ones configured in NetworkManager or received by DHCP.
259 ethernet.macAddress = macAddressOptEth;
262 macAddress = macAddressOptWifi;
265 type = types.enum [ "wpa_supplicant" "iwd" ];
266 default = "wpa_supplicant";
268 Specify the Wi-Fi backend used for the device.
269 Currently supported are {option}`wpa_supplicant` or {option}`iwd` (experimental).
273 powersave = mkOption {
274 type = types.nullOr types.bool;
277 Whether to enable Wi-Fi power saving.
281 scanRandMacAddress = mkOption {
285 Whether to enable MAC address randomization of a Wi-Fi device
292 type = types.enum [ "default" "dnsmasq" "systemd-resolved" "none" ];
295 Set the DNS (`resolv.conf`) processing mode.
297 A description of these modes can be found in the main section of
299 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
300 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html)
302 {manpage}`NetworkManager.conf(5)`.
306 dispatcherScripts = mkOption {
307 type = types.listOf (types.submodule {
312 Path to the hook script.
317 type = types.enum (attrNames dispatcherTypesSubdirMap);
320 Dispatcher hook type. Look up the hooks described at
321 [https://developer.gnome.org/NetworkManager/stable/NetworkManager.html](https://developer.gnome.org/NetworkManager/stable/NetworkManager.html)
322 and choose the type depending on the output folder.
323 You should then filter the event type (e.g., "up"/"down") from within your script.
329 example = literalExpression ''
331 source = pkgs.writeText "upHook" '''
332 if [ "$2" != "up" ]; then
333 logger "exit: event $2 != up"
337 # coreutils and iproute are in PATH too
338 logger "Device $DEVICE_IFACE coming up"
344 A list of scripts which will be executed in response to network events.
348 enableStrongSwan = mkOption {
352 Enable the StrongSwan plugin.
354 If you enable this option the
355 `networkmanager_strongswan` plugin will be added to
356 the {option}`networking.networkmanager.plugins` option
357 so you don't need to do that yourself.
361 fccUnlockScripts = mkOption {
362 type = types.listOf (types.submodule {
366 description = "vid:pid of either the PCI or USB vendor and product ID";
370 description = "Path to the unlock script";
375 example = literalExpression ''[{ id = "03f0:4e1d"; path = "''${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/03f0:4e1d"; }]'';
377 List of FCC unlock scripts to enable on the system, behaving as described in
378 https://modemmanager.org/docs/modemmanager/fcc-unlock/#integration-with-third-party-fcc-unlock-tools.
382 profiles = with lib.types; mkOption {
383 type = attrsOf (submodule {
384 freeformType = ini.type;
390 description = "This is the name that will be displayed by NetworkManager and GUIs.";
392 type = lib.mkOption {
394 description = "The connection type defines the connection kind, like vpn, wireguard, gsm, wifi and more.";
400 apply = (lib.filterAttrsRecursive (n: v: v != { }));
410 mac-address-blacklist = "";
411 mode = "infrastructure";
416 key-mgmt = "wpa-psk";
417 psk = "$HOME_WIFI_PASSWORD";
424 addr-gen-mode = "stable-privacy";
431 Declaratively define NetworkManager profiles. You can find information about the generated file format [here](https://networkmanager.dev/docs/api/latest/nm-settings-keyfile.html) and [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/assembly_networkmanager-connection-profiles-in-keyfile-format_configuring-and-managing-networking).
432 You current profiles which are most likely stored in `/etc/NetworkManager/system-connections` and there is [a tool](https://github.com/janik-haag/nm2nix) to convert them to the needed nix code.
433 If you add a new ad-hoc connection via a GUI or nmtui or anything similar it should just work together with the declarative ones.
434 And if you edit a declarative profile NetworkManager will move it to the persistent storage and treat it like a ad-hoc one,
435 but there will be two profiles as soon as the systemd unit from this option runs again which can be confusing since NetworkManager tools will start displaying two profiles with the same name and probably a bit different settings depending on what you edited.
436 A profile won't be deleted even if it's removed from the config until the system reboots because that's when NetworkManager clears it's temp directory.
437 If `networking.resolvconf.enable` is true, attributes affecting the name resolution (such as `ignore-auto-dns`) may not end up changing `/etc/resolv.conf` as expected when other name services (for example `networking.dhcpcd`) are enabled. Run `resolvconf -l` in the terminal to see what each service produces.
440 environmentFiles = mkOption {
442 type = types.listOf types.path;
443 example = [ "/run/secrets/network-manager.env" ];
445 Files to load as environment file. Environment variables from this file
446 will be substituted into the static configuration file using [envsubst](https://github.com/a8m/envsubst).
454 (mkRenamedOptionModule
455 [ "networking" "networkmanager" "packages" ]
456 [ "networking" "networkmanager" "plugins" ]
458 (mkRenamedOptionModule
459 [ "networking" "networkmanager" "useDnsmasq" ]
460 [ "networking" "networkmanager" "dns" ]
462 (mkRemovedOptionModule [ "networking" "networkmanager" "extraConfig" ] ''
463 This option was removed in favour of `networking.networkmanager.settings`,
464 which accepts structured nix-code equivalent to the ini
465 and allows for overriding settings.
468 networking.networkmanager = {
473 + settings.main.no-auto-default = "*";
478 (mkRemovedOptionModule [ "networking" "networkmanager" "enableFccUnlock" ] ''
479 This option was removed, because using bundled FCC unlock scripts is risky,
480 might conflict with vendor-provided unlock scripts, and should
481 be a conscious decision on a per-device basis.
482 Instead it's recommended to use the
483 `networking.networkmanager.fccUnlockScripts` option.
485 (mkRemovedOptionModule [ "networking" "networkmanager" "dynamicHosts" ] ''
486 This option was removed because allowing (multiple) regular users to
487 override host entries affecting the whole system opens up a huge attack
488 vector. There seem to be very rare cases where this might be useful.
489 Consider setting system-wide host entries using networking.hosts, provide
490 them via the DNS server in your network, or use environment.etc
491 to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir.
493 (mkRemovedOptionModule [ "networking" "networkmanager" "firewallBackend" ] ''
494 This option was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally.
499 ###### implementation
501 config = mkIf cfg.enable {
505 assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [ ];
507 You can not use networking.networkmanager with networking.wireless.
508 Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
513 hardware.wirelessRegulatoryDatabase = true;
516 "NetworkManager/NetworkManager.conf".source = configFile;
518 # The networkmanager-l2tp plugin expects /etc/ipsec.secrets to include /etc/ipsec.d/ipsec.nm-l2tp.secrets;
519 # see https://github.com/NixOS/nixpkgs/issues/64965
520 "ipsec.secrets".text = ''
521 include ipsec.d/ipsec.nm-l2tp.secrets
524 // builtins.listToAttrs (map
525 (pkg: nameValuePair "NetworkManager/${pkg.networkManagerPlugin}" {
526 source = "${pkg}/lib/NetworkManager/${pkg.networkManagerPlugin}";
529 // builtins.listToAttrs (map
530 (e: nameValuePair "ModemManager/fcc-unlock.d/${e.id}" {
533 cfg.fccUnlockScripts)
534 // optionalAttrs (cfg.appendNameservers != [ ] || cfg.insertNameservers != [ ])
536 "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
538 // listToAttrs (lib.imap1
541 name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
542 value = { mode = "0544"; inherit (s) source; };
544 cfg.dispatcherScripts);
546 environment.systemPackages = packages;
549 networkmanager.gid = config.ids.gids.networkmanager;
550 nm-openvpn.gid = config.ids.gids.nm-openvpn;
555 uid = config.ids.uids.nm-openvpn;
556 group = "nm-openvpn";
557 extraGroups = [ "networkmanager" ];
561 group = "networkmanager";
565 systemd.packages = packages;
567 systemd.tmpfiles.rules = [
568 "d /etc/NetworkManager/system-connections 0700 root root -"
569 "d /etc/ipsec.d 0700 root root -"
570 "d /var/lib/NetworkManager-fortisslvpn 0700 root root -"
572 "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
573 # ppp isn't able to mkdir that directory at runtime
574 "d /run/pppd/lock 0700 root root -"
577 systemd.services.NetworkManager = {
578 wantedBy = [ "network.target" ];
579 restartTriggers = [ configFile ];
581 aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
584 StateDirectory = "NetworkManager";
585 StateDirectoryMode = 755; # not sure if this really needs to be 755
589 systemd.services.NetworkManager-wait-online = {
590 wantedBy = [ "network-online.target" ];
593 systemd.services.ModemManager = {
594 aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
595 path = lib.optionals (cfg.fccUnlockScripts != []) [ pkgs.libqmi pkgs.libmbim ];
598 systemd.services.NetworkManager-dispatcher = {
599 wantedBy = [ "network.target" ];
600 restartTriggers = [ configFile overrideNameserversScript ];
602 # useful binaries for user-specified hooks
603 path = [ pkgs.iproute2 pkgs.util-linux pkgs.coreutils ];
604 aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ];
607 systemd.services.NetworkManager-ensure-profiles = mkIf (cfg.ensureProfiles.profiles != { }) {
608 description = "Ensure that NetworkManager declarative profiles are created";
609 wantedBy = [ "multi-user.target" ];
610 before = [ "network-online.target" ];
611 after = [ "NetworkManager.service" ];
613 path = id: "/run/NetworkManager/system-connections/${id}.nmconnection";
615 mkdir -p /run/NetworkManager/system-connections
616 '' + lib.concatMapStringsSep "\n"
618 ${pkgs.envsubst}/bin/envsubst -i ${ini.generate (lib.escapeShellArg profile.n) profile.v} > ${path (lib.escapeShellArg profile.n)}
619 '') (lib.mapAttrsToList (n: v: { inherit n v; }) cfg.ensureProfiles.profiles)
621 ${pkgs.networkmanager}/bin/nmcli connection reload
624 EnvironmentFile = cfg.ensureProfiles.environmentFiles;
630 # Turn off NixOS' network management when networking is managed entirely by NetworkManager
631 networking = mkMerge [
632 (mkIf (!delegateWireless) {
637 networkmanager.plugins = with pkgs; [
638 networkmanager-fortisslvpn
639 networkmanager-iodine
641 networkmanager-openconnect
642 networkmanager-openvpn
648 (mkIf cfg.enableStrongSwan {
649 networkmanager.plugins = [ pkgs.networkmanager_strongswan ];
653 wireless.iwd.enable = true;
657 networkmanager.connectionConfig = {
658 "ethernet.cloned-mac-address" = cfg.ethernet.macAddress;
659 "wifi.cloned-mac-address" = cfg.wifi.macAddress;
661 if cfg.wifi.powersave == null then null
662 else if cfg.wifi.powersave then 3
668 boot.kernelModules = [ "ctr" ];
670 security.polkit.enable = true;
671 security.polkit.extraConfig = polkitConf;
673 services.dbus.packages = packages
674 ++ optional cfg.enableStrongSwan pkgs.strongswanNM
675 ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;
677 services.udev.packages = packages;