at 25.11-pre 14 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 dhcpcd = if !config.boot.isContainer then pkgs.dhcpcd else pkgs.dhcpcd.override { udev = null; }; 10 11 cfg = config.networking.dhcpcd; 12 13 interfaces = lib.attrValues config.networking.interfaces; 14 15 enableDHCP = 16 config.networking.dhcpcd.enable 17 && (config.networking.useDHCP || lib.any (i: i.useDHCP == true) interfaces); 18 19 useResolvConf = config.networking.resolvconf.enable; 20 21 # Don't start dhcpcd on explicitly configured interfaces or on 22 # interfaces that are part of a bridge, bond or sit device. 23 ignoredInterfaces = 24 map (i: i.name) ( 25 lib.filter (i: if i.useDHCP != null then !i.useDHCP else i.ipv4.addresses != [ ]) interfaces 26 ) 27 ++ lib.mapAttrsToList (i: _: i) config.networking.sits 28 ++ lib.concatLists (lib.attrValues (lib.mapAttrs (n: v: v.interfaces) config.networking.bridges)) 29 ++ lib.flatten ( 30 lib.concatMap ( 31 i: lib.attrNames (lib.filterAttrs (_: config: config.type != "internal") i.interfaces) 32 ) (lib.attrValues config.networking.vswitches) 33 ) 34 ++ lib.concatLists (lib.attrValues (lib.mapAttrs (n: v: v.interfaces) config.networking.bonds)) 35 ++ config.networking.dhcpcd.denyInterfaces; 36 37 arrayAppendOrNull = 38 a1: a2: 39 if a1 == null && a2 == null then 40 null 41 else if a1 == null then 42 a2 43 else if a2 == null then 44 a1 45 else 46 a1 ++ a2; 47 48 # If dhcp is disabled but explicit interfaces are enabled, 49 # we need to provide dhcp just for those interfaces. 50 allowInterfaces = arrayAppendOrNull cfg.allowInterfaces ( 51 if !config.networking.useDHCP && enableDHCP then 52 map (i: i.name) (lib.filter (i: i.useDHCP == true) interfaces) 53 else 54 null 55 ); 56 57 staticIPv6Addresses = map (i: i.name) (lib.filter (i: i.ipv6.addresses != [ ]) interfaces); 58 59 noIPv6rs = lib.concatStringsSep "\n" ( 60 map (name: '' 61 interface ${name} 62 noipv6rs 63 '') staticIPv6Addresses 64 ); 65 66 # Config file adapted from the one that ships with dhcpcd. 67 dhcpcdConf = pkgs.writeText "dhcpcd.conf" '' 68 # Inform the DHCP server of our hostname for DDNS. 69 hostname 70 71 # A list of options to request from the DHCP server. 72 option domain_name_servers, domain_name, domain_search 73 option classless_static_routes, ntp_servers, interface_mtu 74 75 # A ServerID is required by RFC2131. 76 # Commented out because of many non-compliant DHCP servers in the wild :( 77 #require dhcp_server_identifier 78 79 # A hook script is provided to lookup the hostname if not set by 80 # the DHCP server, but it should not be run by default. 81 nohook lookup-hostname 82 83 # Ignore peth* devices; on Xen, they're renamed physical 84 # Ethernet cards used for bridging. Likewise for vif* and tap* 85 # (Xen) and virbr* and vnet* (libvirt). 86 denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit* 87 88 # Use the list of allowed interfaces if specified 89 ${lib.optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"} 90 91 # Immediately fork to background if specified, otherwise wait for IP address to be assigned 92 ${ 93 { 94 background = "background"; 95 any = "waitip"; 96 ipv4 = "waitip 4"; 97 ipv6 = "waitip 6"; 98 both = "waitip 4\nwaitip 6"; 99 if-carrier-up = ""; 100 } 101 .${cfg.wait} 102 } 103 104 ${lib.optionalString (config.networking.enableIPv6 == false) '' 105 # Don't solicit or accept IPv6 Router Advertisements and DHCPv6 if disabled IPv6 106 noipv6 107 ''} 108 109 ${lib.optionalString ( 110 config.networking.enableIPv6 && cfg.IPv6rs == null && staticIPv6Addresses != [ ] 111 ) noIPv6rs} 112 ${lib.optionalString (config.networking.enableIPv6 && cfg.IPv6rs == false) '' 113 noipv6rs 114 ''} 115 ${lib.optionalString cfg.setHostname "option host_name"} 116 117 ${cfg.extraConfig} 118 ''; 119 120in 121 122{ 123 124 ###### interface 125 126 options = { 127 128 networking.dhcpcd.enable = lib.mkOption { 129 type = lib.types.bool; 130 default = true; 131 description = '' 132 Whether to enable dhcpcd for device configuration. This is mainly to 133 explicitly disable dhcpcd (for example when using networkd). 134 ''; 135 }; 136 137 networking.dhcpcd.persistent = lib.mkOption { 138 type = lib.types.bool; 139 default = false; 140 description = '' 141 Whether to leave interfaces configured on dhcpcd daemon 142 shutdown. Set to true if you have your root or store mounted 143 over the network or this machine accepts SSH connections 144 through DHCP interfaces and clients should be notified when 145 it shuts down. 146 ''; 147 }; 148 149 networking.dhcpcd.setHostname = lib.mkOption { 150 type = lib.types.bool; 151 default = true; 152 description = '' 153 Whether to set the machine hostname based on the information 154 received from the DHCP server. 155 156 ::: {.note} 157 The hostname will be changed only if the current one is 158 the empty string, `localhost` or `nixos`. 159 160 Polkit ([](#opt-security.polkit.enable)) is also required. 161 ::: 162 ''; 163 }; 164 165 networking.dhcpcd.denyInterfaces = lib.mkOption { 166 type = lib.types.listOf lib.types.str; 167 default = [ ]; 168 description = '' 169 Disable the DHCP client for any interface whose name matches 170 any of the shell glob patterns in this list. The purpose of 171 this option is to blacklist virtual interfaces such as those 172 created by Xen, libvirt, LXC, etc. 173 ''; 174 }; 175 176 networking.dhcpcd.allowInterfaces = lib.mkOption { 177 type = lib.types.nullOr (lib.types.listOf lib.types.str); 178 default = null; 179 description = '' 180 Enable the DHCP client for any interface whose name matches 181 any of the shell glob patterns in this list. Any interface not 182 explicitly matched by this pattern will be denied. This pattern only 183 applies when non-null. 184 ''; 185 }; 186 187 networking.dhcpcd.extraConfig = lib.mkOption { 188 type = lib.types.lines; 189 default = ""; 190 description = '' 191 Literal string to append to the config file generated for dhcpcd. 192 ''; 193 }; 194 195 networking.dhcpcd.IPv6rs = lib.mkOption { 196 type = lib.types.nullOr lib.types.bool; 197 default = null; 198 description = '' 199 Force enable or disable solicitation and receipt of IPv6 Router Advertisements. 200 This is required, for example, when using a static unique local IPv6 address (ULA) 201 and global IPv6 address auto-configuration with SLAAC. 202 ''; 203 }; 204 205 networking.dhcpcd.allowSetuid = lib.mkOption { 206 type = lib.types.bool; 207 default = false; 208 description = '' 209 Whether to relax the security sandbox to allow running setuid 210 binaries (e.g. `sudo`) in the dhcpcd hooks. 211 ''; 212 }; 213 214 networking.dhcpcd.runHook = lib.mkOption { 215 type = lib.types.lines; 216 default = ""; 217 example = "if [[ $reason =~ BOUND ]]; then echo $interface: Routers are $new_routers - were $old_routers; fi"; 218 description = '' 219 Shell code that will be run after all other hooks. See 220 `man dhcpcd-run-hooks` for details on what is possible. 221 222 ::: {.note} 223 To use sudo or similar tools in your script you may have to set: 224 225 networking.dhcpcd.allowSetuid = true; 226 227 In addition, as most of the filesystem is inaccessible to dhcpcd 228 by default, you may want to define some exceptions, e.g. 229 230 systemd.services.dhcpcd.serviceConfig.ReadOnlyPaths = [ 231 "/run/user/1000/bus" # to send desktop notifications 232 ]; 233 ::: 234 ''; 235 }; 236 237 networking.dhcpcd.wait = lib.mkOption { 238 type = lib.types.enum [ 239 "background" 240 "any" 241 "ipv4" 242 "ipv6" 243 "both" 244 "if-carrier-up" 245 ]; 246 default = "any"; 247 description = '' 248 This option specifies when the dhcpcd service will fork to background. 249 If set to "background", dhcpcd will fork to background immediately. 250 If set to "ipv4" or "ipv6", dhcpcd will wait for the corresponding IP 251 address to be assigned. If set to "any", dhcpcd will wait for any type 252 (IPv4 or IPv6) to be assigned. If set to "both", dhcpcd will wait for 253 both an IPv4 and an IPv6 address before forking. 254 The option "if-carrier-up" is equivalent to "any" if either ethernet 255 is plugged or WiFi is powered, and to "background" otherwise. 256 ''; 257 }; 258 259 }; 260 261 ###### implementation 262 263 config = lib.mkIf enableDHCP { 264 265 systemd.services.dhcpcd = 266 let 267 cfgN = config.networking; 268 hasDefaultGatewaySet = 269 (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "") 270 && (!cfgN.enableIPv6 || (cfgN.defaultGateway6 != null && cfgN.defaultGateway6.address != "")); 271 in 272 { 273 description = "DHCP Client"; 274 275 documentation = [ "man:dhcpcd(8)" ]; 276 277 wantedBy = [ "multi-user.target" ] ++ lib.optional (!hasDefaultGatewaySet) "network-online.target"; 278 wants = [ 279 "network.target" 280 "resolvconf.service" 281 ]; 282 after = [ "resolvconf.service" ]; 283 before = [ "network-online.target" ]; 284 285 restartTriggers = [ cfg.runHook ]; 286 287 # Stopping dhcpcd during a reconfiguration is undesirable 288 # because it brings down the network interfaces configured by 289 # dhcpcd. So do a "systemctl restart" instead. 290 stopIfChanged = false; 291 292 path = 293 [ 294 dhcpcd 295 config.networking.resolvconf.package 296 ] 297 ++ lib.optional cfg.setHostname ( 298 pkgs.writeShellScriptBin "hostname" '' 299 ${lib.getExe' pkgs.systemd "hostnamectl"} set-hostname --transient $1 300 '' 301 ); 302 303 unitConfig.ConditionCapability = "CAP_NET_ADMIN"; 304 305 serviceConfig = { 306 Type = "forking"; 307 PIDFile = "/run/dhcpcd/pid"; 308 SupplementaryGroups = lib.optional useResolvConf "resolvconf"; 309 User = "dhcpcd"; 310 Group = "dhcpcd"; 311 StateDirectory = "dhcpcd"; 312 RuntimeDirectory = "dhcpcd"; 313 314 ExecStartPre = "+${pkgs.writeShellScript "migrate-dhcpcd" '' 315 # migrate from old database directory 316 if test -f /var/db/dhcpcd/duid; then 317 echo 'migrating DHCP leases from /var/db/dhcpcd to /var/lib/dhcpcd ...' 318 mv /var/db/dhcpcd/* -t /var/lib/dhcpcd 319 chown dhcpcd:dhcpcd /var/lib/dhcpcd/* 320 rmdir /var/db/dhcpcd || true 321 echo done 322 fi 323 ''}"; 324 325 ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${lib.optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}"; 326 ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind"; 327 Restart = "always"; 328 AmbientCapabilities = [ 329 "CAP_NET_ADMIN" 330 "CAP_NET_RAW" 331 "CAP_NET_BIND_SERVICE" 332 ]; 333 CapabilityBoundingSet = lib.optionals (!cfg.allowSetuid) [ 334 "CAP_NET_ADMIN" 335 "CAP_NET_RAW" 336 "CAP_NET_BIND_SERVICE" 337 ]; 338 ReadWritePaths = 339 [ "/proc/sys/net/ipv4" ] 340 ++ lib.optional cfgN.enableIPv6 "/proc/sys/net/ipv6" 341 ++ lib.optionals useResolvConf ( 342 [ "/run/resolvconf" ] ++ config.networking.resolvconf.subscriberFiles 343 ); 344 DeviceAllow = ""; 345 LockPersonality = true; 346 MemoryDenyWriteExecute = true; 347 NoNewPrivileges = lib.mkDefault (!cfg.allowSetuid); # may be disabled for sudo in runHook 348 PrivateDevices = true; 349 PrivateMounts = true; 350 PrivateTmp = true; 351 PrivateUsers = false; 352 ProtectClock = true; 353 ProtectControlGroups = true; 354 ProtectHome = "tmpfs"; # allow exceptions to be added to ReadOnlyPaths, etc. 355 ProtectHostname = true; 356 ProtectKernelLogs = true; 357 ProtectKernelModules = true; 358 ProtectKernelTunables = true; 359 ProtectProc = "invisible"; 360 ProtectSystem = "strict"; 361 RemoveIPC = true; 362 RestrictAddressFamilies = [ 363 "AF_UNIX" 364 "AF_INET" 365 "AF_INET6" 366 "AF_NETLINK" 367 "AF_PACKET" 368 ]; 369 RestrictNamespaces = true; 370 RestrictRealtime = true; 371 RestrictSUIDSGID = true; 372 SystemCallFilter = 373 [ 374 "@system-service" 375 "~@aio" 376 "~@keyring" 377 "~@memlock" 378 "~@mount" 379 ] 380 ++ lib.optionals (!cfg.allowSetuid) [ 381 "~@privileged" 382 "~@resources" 383 ]; 384 SystemCallArchitectures = "native"; 385 UMask = "0027"; 386 }; 387 }; 388 389 # Note: the service could run with `DynamicUser`, however that makes 390 # impossible (for no good reason, see systemd issue #20495) to disable 391 # `NoNewPrivileges` or `ProtectHome`, which users may want to in order 392 # to run certain scripts in `networking.dhcpcd.runHook`. 393 users.users.dhcpcd = { 394 isSystemUser = true; 395 group = "dhcpcd"; 396 }; 397 users.groups.dhcpcd = { }; 398 399 environment.systemPackages = [ dhcpcd ]; 400 401 environment.etc."dhcpcd.exit-hook".text = cfg.runHook; 402 403 powerManagement.resumeCommands = lib.mkIf config.systemd.services.dhcpcd.enable '' 404 # Tell dhcpcd to rebind its interfaces if it's running. 405 /run/current-system/systemd/bin/systemctl reload dhcpcd.service 406 ''; 407 408 security.polkit.extraConfig = lib.mkMerge [ 409 (lib.mkIf config.services.resolved.enable '' 410 polkit.addRule(function(action, subject) { 411 if (action.id == 'org.freedesktop.resolve1.revert' || 412 action.id == 'org.freedesktop.resolve1.set-dns-servers' || 413 action.id == 'org.freedesktop.resolve1.set-domains') { 414 if (subject.user == 'dhcpcd') { 415 return polkit.Result.YES; 416 } 417 } 418 }); 419 '') 420 (lib.mkIf cfg.setHostname '' 421 polkit.addRule(function(action, subject) { 422 if (action.id == 'org.freedesktop.hostname1.set-hostname' && 423 subject.user == 'dhcpcd') { 424 return polkit.Result.YES; 425 } 426 }); 427 '') 428 ]; 429 430 }; 431 432}