at master 24 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 cfg = config.networking.networkmanager; 12 ini = pkgs.formats.ini { }; 13 14 delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [ ]; 15 16 enableIwd = cfg.wifi.backend == "iwd"; 17 18 configAttrs = lib.recursiveUpdate { 19 main = { 20 plugins = "keyfile"; 21 inherit (cfg) dhcp dns; 22 # If resolvconf is disabled that means that resolv.conf is managed by some other module. 23 rc-manager = if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"; 24 }; 25 keyfile = { 26 unmanaged-devices = if cfg.unmanaged == [ ] then null else lib.concatStringsSep ";" cfg.unmanaged; 27 }; 28 logging = { 29 audit = config.security.audit.enable; 30 level = cfg.logLevel; 31 }; 32 connection = cfg.connectionConfig; 33 device = { 34 "wifi.scan-rand-mac-address" = cfg.wifi.scanRandMacAddress; 35 "wifi.backend" = cfg.wifi.backend; 36 }; 37 } cfg.settings; 38 configFile = ini.generate "NetworkManager.conf" configAttrs; 39 40 /* 41 [network-manager] 42 Identity=unix-group:networkmanager 43 Action=org.freedesktop.NetworkManager.* 44 ResultAny=yes 45 ResultInactive=no 46 ResultActive=yes 47 */ 48 polkitConf = '' 49 polkit.addRule(function(action, subject) { 50 if ( 51 subject.isInGroup("networkmanager") 52 && action.id.indexOf("org.freedesktop.NetworkManager.") == 0 53 ) 54 { return polkit.Result.YES; } 55 }); 56 ''; 57 58 ns = xs: pkgs.writeText "nameservers" (concatStrings (map (s: "nameserver ${s}\n") xs)); 59 60 overrideNameserversScript = pkgs.writeScript "02overridedns" '' 61 #!/bin/sh 62 PATH=${ 63 with pkgs; 64 makeBinPath [ 65 gnused 66 gnugrep 67 coreutils 68 ] 69 } 70 tmp=$(mktemp) 71 sed '/nameserver /d' /etc/resolv.conf > $tmp 72 grep 'nameserver ' /etc/resolv.conf | \ 73 grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns 74 cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf 75 rm -f $tmp $tmp.ns 76 ''; 77 78 dispatcherTypesSubdirMap = { 79 basic = ""; 80 pre-up = "pre-up.d/"; 81 pre-down = "pre-down.d/"; 82 }; 83 84 macAddressOptWifi = mkOption { 85 type = types.either types.str ( 86 types.enum [ 87 "permanent" 88 "preserve" 89 "random" 90 "stable" 91 "stable-ssid" 92 ] 93 ); 94 default = "preserve"; 95 example = "00:11:22:33:44:55"; 96 description = '' 97 Set the MAC address of the interface. 98 99 - `"XX:XX:XX:XX:XX:XX"`: MAC address of the interface 100 - `"permanent"`: Use the permanent MAC address of the device 101 - `"preserve"`: Dont change the MAC address of the device upon activation 102 - `"random"`: Generate a randomized value upon each connect 103 - `"stable"`: Generate a stable, hashed MAC address 104 - `"stable-ssid"`: Generate a stable MAC addressed based on Wi-Fi network 105 ''; 106 }; 107 108 macAddressOptEth = mkOption { 109 type = types.either types.str ( 110 types.enum [ 111 "permanent" 112 "preserve" 113 "random" 114 "stable" 115 ] 116 ); 117 default = "preserve"; 118 example = "00:11:22:33:44:55"; 119 description = '' 120 Set the MAC address of the interface. 121 122 - `"XX:XX:XX:XX:XX:XX"`: MAC address of the interface 123 - `"permanent"`: Use the permanent MAC address of the device 124 - `"preserve"`: Dont change the MAC address of the device upon activation 125 - `"random"`: Generate a randomized value upon each connect 126 - `"stable"`: Generate a stable, hashed MAC address 127 ''; 128 }; 129 130 concatPluginAttrs = attr: lib.concatMap (plugin: plugin.${attr} or [ ]) cfg.plugins; 131 pluginRuntimeDeps = concatPluginAttrs "networkManagerRuntimeDeps"; 132 pluginDbusDeps = concatPluginAttrs "networkManagerDbusDeps"; 133 pluginTmpfilesRules = concatPluginAttrs "networkManagerTmpfilesRules"; 134 135 packages = [ 136 cfg.package 137 ] 138 ++ cfg.plugins 139 ++ pluginRuntimeDeps 140 ++ lib.optionals (!delegateWireless && !enableIwd) [ 141 pkgs.wpa_supplicant 142 ]; 143in 144{ 145 146 meta = { 147 maintainers = teams.freedesktop.members ++ [ 148 lib.maintainers.frontear 149 ]; 150 }; 151 152 ###### interface 153 154 options = { 155 156 networking.networkmanager = { 157 158 enable = mkOption { 159 type = types.bool; 160 default = false; 161 description = '' 162 Whether to use NetworkManager to obtain an IP address and other 163 configuration for all network interfaces that are not manually 164 configured. If enabled, a group `networkmanager` 165 will be created. Add all users that should have permission 166 to change network settings to this group. 167 ''; 168 }; 169 170 package = mkPackageOption pkgs "networkmanager" { }; 171 172 connectionConfig = mkOption { 173 type = 174 with types; 175 attrsOf ( 176 nullOr (oneOf [ 177 bool 178 int 179 str 180 ]) 181 ); 182 default = { }; 183 description = '' 184 Configuration for the [connection] section of NetworkManager.conf. 185 Refer to 186 [ 187 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#id-1.2.3.11 188 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html) 189 or 190 {manpage}`NetworkManager.conf(5)` 191 for more information. 192 ''; 193 }; 194 195 settings = mkOption { 196 type = ini.type; 197 default = { }; 198 description = '' 199 Configuration added to the generated NetworkManager.conf, note that you can overwrite settings with this. 200 Refer to 201 [ 202 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html 203 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html) 204 or 205 {manpage}`NetworkManager.conf(5)` 206 for more information. 207 ''; 208 }; 209 210 unmanaged = mkOption { 211 type = types.listOf types.str; 212 default = [ ]; 213 description = '' 214 List of interfaces that will not be managed by NetworkManager. 215 Interface name can be specified here, but if you need more fidelity, 216 refer to 217 [ 218 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec 219 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec) 220 or the "Device List Format" Appendix of 221 {manpage}`NetworkManager.conf(5)`. 222 ''; 223 }; 224 225 plugins = mkOption { 226 type = 227 let 228 networkManagerPluginPackage = types.package // { 229 description = "NetworkManager plugin package"; 230 check = 231 p: 232 lib.assertMsg 233 (types.package.check p && p ? networkManagerPlugin && lib.isString p.networkManagerPlugin) 234 '' 235 Package ${p.name}, is not a NetworkManager plugin. 236 Those need to have a networkManagerPlugin attribute. 237 ''; 238 }; 239 in 240 types.listOf networkManagerPluginPackage; 241 default = [ ]; 242 example = literalExpression '' 243 [ 244 networkmanager-fortisslvpn 245 networkmanager-iodine 246 networkmanager-l2tp 247 networkmanager-openconnect 248 networkmanager-openvpn 249 networkmanager-sstp 250 networkmanager-strongswan 251 networkmanager-vpnc 252 ] 253 ''; 254 description = '' 255 List of plugin packages to install. 256 257 See <https://search.nixos.org/packages?query=networkmanager-> for available plugin packages. 258 and <https://networkmanager.dev/docs/vpn/> for an overview over builtin and external plugins 259 and their support status. 260 ''; 261 }; 262 263 dhcp = mkOption { 264 type = types.enum [ 265 "dhcpcd" 266 "internal" 267 ]; 268 default = "internal"; 269 description = '' 270 Which program (or internal library) should be used for DHCP. 271 ''; 272 }; 273 274 logLevel = mkOption { 275 type = types.enum [ 276 "OFF" 277 "ERR" 278 "WARN" 279 "INFO" 280 "DEBUG" 281 "TRACE" 282 ]; 283 default = "WARN"; 284 description = '' 285 Set the default logging verbosity level. 286 ''; 287 }; 288 289 appendNameservers = mkOption { 290 type = types.listOf types.str; 291 default = [ ]; 292 description = '' 293 A list of name servers that should be appended 294 to the ones configured in NetworkManager or received by DHCP. 295 ''; 296 }; 297 298 insertNameservers = mkOption { 299 type = types.listOf types.str; 300 default = [ ]; 301 description = '' 302 A list of name servers that should be inserted before 303 the ones configured in NetworkManager or received by DHCP. 304 ''; 305 }; 306 307 ethernet.macAddress = macAddressOptEth; 308 309 wifi = { 310 macAddress = macAddressOptWifi; 311 312 backend = mkOption { 313 type = types.enum [ 314 "wpa_supplicant" 315 "iwd" 316 ]; 317 default = "wpa_supplicant"; 318 description = '' 319 Specify the Wi-Fi backend used for the device. 320 Currently supported are {option}`wpa_supplicant` or {option}`iwd` (experimental). 321 ''; 322 }; 323 324 powersave = mkOption { 325 type = types.nullOr types.bool; 326 default = null; 327 description = '' 328 Whether to enable Wi-Fi power saving. 329 ''; 330 }; 331 332 scanRandMacAddress = mkOption { 333 type = types.bool; 334 default = true; 335 description = '' 336 Whether to enable MAC address randomization of a Wi-Fi device 337 during scanning. 338 ''; 339 }; 340 }; 341 342 dns = mkOption { 343 type = types.enum [ 344 "default" 345 "dnsmasq" 346 "systemd-resolved" 347 "none" 348 ]; 349 default = "default"; 350 description = '' 351 Set the DNS (`resolv.conf`) processing mode. 352 353 A description of these modes can be found in the main section of 354 [ 355 https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html 356 ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html) 357 or in 358 {manpage}`NetworkManager.conf(5)`. 359 ''; 360 }; 361 362 dispatcherScripts = mkOption { 363 type = types.listOf ( 364 types.submodule { 365 options = { 366 source = mkOption { 367 type = types.path; 368 description = '' 369 Path to the hook script. 370 ''; 371 }; 372 373 type = mkOption { 374 type = types.enum (attrNames dispatcherTypesSubdirMap); 375 default = "basic"; 376 description = '' 377 Dispatcher hook type. Look up the hooks described at 378 [https://developer.gnome.org/NetworkManager/stable/NetworkManager.html](https://developer.gnome.org/NetworkManager/stable/NetworkManager.html) 379 and choose the type depending on the output folder. 380 You should then filter the event type (e.g., "up"/"down") from within your script. 381 ''; 382 }; 383 }; 384 } 385 ); 386 default = [ ]; 387 example = literalExpression '' 388 [ { 389 source = pkgs.writeText "upHook" ''' 390 if [ "$2" != "up" ]; then 391 logger "exit: event $2 != up" 392 exit 393 fi 394 395 # coreutils and iproute are in PATH too 396 logger "Device $DEVICE_IFACE coming up" 397 '''; 398 type = "basic"; 399 } ] 400 ''; 401 description = '' 402 A list of scripts which will be executed in response to network events. 403 ''; 404 }; 405 406 ensureProfiles = { 407 profiles = 408 with lib.types; 409 mkOption { 410 type = attrsOf (submodule { 411 freeformType = ini.type; 412 413 options = { 414 connection = { 415 id = lib.mkOption { 416 type = str; 417 description = "This is the name that will be displayed by NetworkManager and GUIs."; 418 }; 419 type = lib.mkOption { 420 type = str; 421 description = "The connection type defines the connection kind, like vpn, wireguard, gsm, wifi and more."; 422 example = "vpn"; 423 }; 424 }; 425 }; 426 }); 427 apply = (lib.filterAttrsRecursive (n: v: v != { })); 428 default = { }; 429 example = { 430 home-wifi = { 431 connection = { 432 id = "home-wifi"; 433 type = "wifi"; 434 permissions = ""; 435 }; 436 wifi = { 437 mac-address-blacklist = ""; 438 mode = "infrastructure"; 439 ssid = "Home Wi-Fi"; 440 }; 441 wifi-security = { 442 auth-alg = "open"; 443 key-mgmt = "wpa-psk"; 444 psk = "$HOME_WIFI_PASSWORD"; 445 }; 446 ipv4 = { 447 dns-search = ""; 448 method = "auto"; 449 }; 450 ipv6 = { 451 addr-gen-mode = "stable-privacy"; 452 dns-search = ""; 453 method = "auto"; 454 }; 455 }; 456 }; 457 description = '' 458 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). 459 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. 460 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. 461 And if you edit a declarative profile NetworkManager will move it to the persistent storage and treat it like a ad-hoc one, 462 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. 463 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. 464 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. 465 ''; 466 }; 467 environmentFiles = mkOption { 468 default = [ ]; 469 type = types.listOf types.path; 470 example = [ "/run/secrets/network-manager.env" ]; 471 description = '' 472 Files to load as environment file. Environment variables from this file 473 will be substituted into the static configuration file using [envsubst](https://github.com/a8m/envsubst). 474 ''; 475 }; 476 }; 477 }; 478 }; 479 480 imports = [ 481 (mkRenamedOptionModule 482 [ "networking" "networkmanager" "packages" ] 483 [ "networking" "networkmanager" "plugins" ] 484 ) 485 (mkRenamedOptionModule 486 [ "networking" "networkmanager" "useDnsmasq" ] 487 [ "networking" "networkmanager" "dns" ] 488 ) 489 (mkRemovedOptionModule [ "networking" "networkmanager" "extraConfig" ] '' 490 This option was removed in favour of `networking.networkmanager.settings`, 491 which accepts structured nix-code equivalent to the ini 492 and allows for overriding settings. 493 Example patch: 494 ```patch 495 networking.networkmanager = { 496 - extraConfig = ''' 497 - [main] 498 - no-auto-default=* 499 - ''' 500 + settings.main.no-auto-default = "*"; 501 }; 502 ``` 503 '') 504 (mkRemovedOptionModule [ "networking" "networkmanager" "enableFccUnlock" ] '' 505 This option was removed, because using bundled FCC unlock scripts is risky, 506 might conflict with vendor-provided unlock scripts, and should 507 be a conscious decision on a per-device basis. 508 Instead it's recommended to use the 509 `networking.modemmanager.fccUnlockScripts` option. 510 '') 511 (mkRemovedOptionModule [ "networking" "networkmanager" "dynamicHosts" ] '' 512 This option was removed because allowing (multiple) regular users to 513 override host entries affecting the whole system opens up a huge attack 514 vector. There seem to be very rare cases where this might be useful. 515 Consider setting system-wide host entries using networking.hosts, provide 516 them via the DNS server in your network, or use environment.etc 517 to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir. 518 '') 519 (mkRemovedOptionModule [ "networking" "networkmanager" "firewallBackend" ] '' 520 This option was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally. 521 '') 522 (mkRenamedOptionModule 523 [ "networking" "networkmanager" "fccUnlockScripts" ] 524 [ "networking" "modemmanager" "fccUnlockScripts" ] 525 ) 526 (mkRemovedOptionModule [ 527 "networking" 528 "networkmanager" 529 "enableStrongSwan" 530 ] "Pass `pkgs.networkmanager-strongswan` into `networking.networkmanager.plugins` instead.") 531 (mkRemovedOptionModule [ 532 "networking" 533 "networkmanager" 534 "enableDefaultPlugins" 535 ] "Configure the required plugins explicitly in `networking.networkmanager.plugins`.") 536 ]; 537 538 ###### implementation 539 540 config = mkIf cfg.enable { 541 542 assertions = [ 543 { 544 assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [ ]; 545 message = '' 546 You can not use networking.networkmanager with networking.wireless. 547 Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager. 548 ''; 549 } 550 ]; 551 552 hardware.wirelessRegulatoryDatabase = true; 553 554 environment.etc = { 555 "NetworkManager/NetworkManager.conf".source = configFile; 556 557 # The networkmanager-l2tp plugin expects /etc/ipsec.secrets to include /etc/ipsec.d/ipsec.nm-l2tp.secrets; 558 # see https://github.com/NixOS/nixpkgs/issues/64965 559 "ipsec.secrets".text = '' 560 include ipsec.d/ipsec.nm-l2tp.secrets 561 ''; 562 } 563 // builtins.listToAttrs ( 564 map ( 565 pkg: 566 nameValuePair "NetworkManager/${pkg.networkManagerPlugin}" { 567 source = "${pkg}/lib/NetworkManager/${pkg.networkManagerPlugin}"; 568 } 569 ) cfg.plugins 570 ) 571 // optionalAttrs (cfg.appendNameservers != [ ] || cfg.insertNameservers != [ ]) { 572 "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript; 573 } 574 // listToAttrs ( 575 lib.imap1 (i: s: { 576 name = "NetworkManager/dispatcher.d/${ 577 dispatcherTypesSubdirMap.${s.type} 578 }03userscript${lib.fixedWidthNumber 4 i}"; 579 value = { 580 mode = "0544"; 581 inherit (s) source; 582 }; 583 }) cfg.dispatcherScripts 584 ); 585 586 environment.systemPackages = packages; 587 588 users.groups = { 589 networkmanager.gid = config.ids.gids.networkmanager; 590 nm-openvpn.gid = config.ids.gids.nm-openvpn; 591 }; 592 593 users.users = { 594 nm-openvpn = { 595 uid = config.ids.uids.nm-openvpn; 596 group = "nm-openvpn"; 597 extraGroups = [ "networkmanager" ]; 598 }; 599 nm-iodine = { 600 isSystemUser = true; 601 group = "networkmanager"; 602 }; 603 }; 604 605 systemd.packages = packages; 606 607 systemd.tmpfiles.rules = [ 608 "d /etc/NetworkManager/system-connections 0700 root root -" 609 "d /var/lib/misc 0755 root root -" # for dnsmasq.leases 610 # ppp isn't able to mkdir that directory at runtime 611 "d /run/pppd/lock 0700 root root -" 612 ] 613 ++ pluginTmpfilesRules; 614 615 systemd.services.NetworkManager = { 616 wantedBy = [ "multi-user.target" ]; 617 restartTriggers = [ configFile ]; 618 619 aliases = [ "dbus-org.freedesktop.NetworkManager.service" ]; 620 621 serviceConfig = { 622 StateDirectory = "NetworkManager"; 623 StateDirectoryMode = 755; # not sure if this really needs to be 755 624 }; 625 }; 626 627 systemd.services.NetworkManager-wait-online = { 628 wantedBy = [ "network-online.target" ]; 629 }; 630 631 systemd.services.NetworkManager-dispatcher = { 632 wantedBy = [ "multi-user.target" ]; 633 restartTriggers = [ 634 configFile 635 overrideNameserversScript 636 ]; 637 638 # useful binaries for user-specified hooks 639 path = [ 640 pkgs.iproute2 641 pkgs.util-linux 642 pkgs.coreutils 643 ]; 644 aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ]; 645 }; 646 647 systemd.services.NetworkManager-ensure-profiles = mkIf (cfg.ensureProfiles.profiles != { }) { 648 description = "Ensure that NetworkManager declarative profiles are created"; 649 wantedBy = [ "multi-user.target" ]; 650 before = [ "network-online.target" ]; 651 after = [ "NetworkManager.service" ]; 652 path = pluginRuntimeDeps; 653 script = 654 let 655 path = id: "/run/NetworkManager/system-connections/${id}.nmconnection"; 656 in 657 '' 658 mkdir -p /run/NetworkManager/system-connections 659 '' 660 + lib.concatMapStringsSep "\n" (profile: '' 661 ${pkgs.envsubst}/bin/envsubst -i ${ini.generate (lib.escapeShellArg profile.n) profile.v} > ${path (lib.escapeShellArg profile.n)} 662 '') (lib.mapAttrsToList (n: v: { inherit n v; }) cfg.ensureProfiles.profiles) 663 + '' 664 ${cfg.package}/bin/nmcli connection reload 665 ''; 666 serviceConfig = { 667 EnvironmentFile = cfg.ensureProfiles.environmentFiles; 668 UMask = "0177"; 669 Type = "oneshot"; 670 }; 671 }; 672 673 # Turn off NixOS' network management when networking is managed entirely by NetworkManager 674 networking = mkMerge [ 675 (mkIf (!delegateWireless) { 676 useDHCP = false; 677 }) 678 679 (mkIf enableIwd { 680 wireless.iwd.enable = true; 681 }) 682 683 { 684 modemmanager.enable = lib.mkDefault true; 685 686 networkmanager.connectionConfig = { 687 "ethernet.cloned-mac-address" = cfg.ethernet.macAddress; 688 "wifi.cloned-mac-address" = cfg.wifi.macAddress; 689 "wifi.powersave" = lib.mkIf (cfg.wifi.powersave != null) (if cfg.wifi.powersave then 3 else 2); 690 }; 691 } 692 ]; 693 694 boot.kernelModules = [ "ctr" ]; 695 696 security.polkit.enable = true; 697 security.polkit.extraConfig = polkitConf; 698 699 services.dbus.packages = packages ++ pluginDbusDeps ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq; 700 701 services.udev.packages = packages; 702 703 systemd.services.NetworkManager.path = pluginRuntimeDeps; 704 }; 705}