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