diff --git a/nixos/doc/manual/configuration/ad-hoc-network-config.section.md b/nixos/doc/manual/configuration/ad-hoc-network-config.section.md index 7ef24825526d..611f3e3956ee 100644 --- a/nixos/doc/manual/configuration/ad-hoc-network-config.section.md +++ b/nixos/doc/manual/configuration/ad-hoc-network-config.section.md @@ -1,14 +1,24 @@ # Ad-Hoc Configuration {#ad-hoc-network-config} -You can use [](#opt-networking.localCommands) to -specify shell commands to be run at the end of `network-setup.service`. This -is useful for doing network configuration not covered by the existing NixOS -modules. For instance, to statically configure an IPv6 address: +You can use [](#opt-networking.localCommands) to specify shell commands to be +run after the network interfaces have been created, but not necessarily fully +configured. +This is useful for doing network configuration not covered by the existing +NixOS modules. For example, you can create a network namespace and a pair +of virtual ethernet devices like this: ```nix { networking.localCommands = '' - ip -6 addr add 2001:610:685:1::1/64 dev eth0 + ip netns add mynet + ip link add name veth-in type veth peer name veth-out + ip link set dev veth-out netns mynet ''; } ``` + +::: {.note} +The commands should ideally be idempotent, so it's recommended to perform +cleanups of the state you create (e.g. virtual interfaces), or at least make +sure possible failures are handled. +::: diff --git a/nixos/doc/manual/configuration/ipv4-config.section.md b/nixos/doc/manual/configuration/ipv4-config.section.md index ed0db9da8c3e..ceb9a350b680 100644 --- a/nixos/doc/manual/configuration/ipv4-config.section.md +++ b/nixos/doc/manual/configuration/ipv4-config.section.md @@ -26,9 +26,16 @@ servers: ``` ::: {.note} -Statically configured interfaces are set up by the systemd service -`interface-name-cfg.service`. The default gateway and name server -configuration is performed by `network-setup.service`. +Addresses and routes for statically configured interfaces and the default +gateway are set up by systemd services named +`network-addresses-.service`. The name servers configuration, +instead, is performed by `network-local-commands.service` using resolvconf. +::: + +::: {.note} +If needed, for example if addresses/routes were added/removed, +you can reset the network configuration by running +`systemctl restart networking-scripted.target` ::: The host name is set using [](#opt-networking.hostName): diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index a652c7a3657c..6cbc62da8950 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -300,6 +300,13 @@ See . Note for NetworkManager users: before these changes NetworkManager used to spawn its own wpa_supplicant daemon, but now it relies on `networking.wireless`. So, if you had `networking.wireless.enable = false` in your configuration, you should remove that line. +- Some implementation details of the NixOS network-interfaces module have been changed: + + - In the "scripted" backend, `network-setup.service` has been removed and the network configuration services are now part of `network.target`, which is now directly pulled into `multi-user.target`. + - Interface addresses, routes and default gateways are now configured asynchronously as soon as the underlying network devices become available (fixes issue [#154737](https://github.com/NixOS/nixpkgs/issues/154737)). + - In both "networkd" and "scripted" backends, the configuration of name servers is now part of `network-local-commands.service` (fixes issue [#445496](https://github.com/NixOS/nixpkgs/issues/445496)). + - The issue that resulted in a completely unconfigured network if both `resolvconf` was disabled and no default gateway configured, has also been fixed. + - `kratos` has been updated from 1.3.1 to [25.4.0](https://github.com/ory/kratos/releases/tag/v25.4.0). Upstream switched to a new versioning scheme (year.major.minor). Notable breaking changes: - The `migrate sql` CLI command is now `migrate sql up` diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index 4b457e078e77..4bf274d4157f 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -51,6 +51,71 @@ let (lib.concatStringsSep " ") ]; + # Converts an IPv4 address literal to a list of bits + parseAddr.ipv4 = + addr: + let + pad = b: lib.replicate (8 - builtins.length b) 0 ++ b; + toBin = n: pad (lib.toBaseDigits 2 (lib.toInt n)); + in + lib.concatMap toBin (builtins.splitVersion addr); + + # Converts an IPv6 address literal to a list of bits + parseAddr.ipv6 = + addr: + let + pad = b: lib.replicate (16 - builtins.length b) 0 ++ b; + fromHex = n: (builtins.fromTOML "n = 0x${n}").n; + toBin = n: pad (lib.toBaseDigits 2 (fromHex n)); + normal = (lib.network.ipv6.fromString addr).address; + in + lib.concatMap toBin (lib.splitString ":" normal); + + # Checks if `addr` is part of the `net` subnet + inSubnet = + v: net: addr: + let + prefix = lib.take net.prefixLength (parseAddr.${v} net.address); + match = lib.zipListsWith (a: b: a == b) prefix (parseAddr.${v} addr); + in + lib.all lib.id match; + + # Checks if the netmask of all addresses on interface `iface` includes + # the IP address of `gateway` + # + # Note: this is used to check whether networking.defaultGateway relies on + # the given interface, either explicitly, via the `interface` (optional), + # or explicitly, by using an address in a subnet of this interface. + # + # Configuration of the default gateway is then performed as part of that + # interface setup in `configureAddrs`, below. + isGateway = + v: gateway: iface: + lib.any lib.id ( + [ (iface.name == gateway.interface) ] + ++ map (net: inSubnet v net gateway.address) iface.${v}.addresses + ); + + # Checks if `gateway` uses an address from `iface` as default source + # + # Note: this is needed to delay the configuration of the gateway and default + # source until the right interfaces and address have been set up, otherwise + # the commands will fail. + hasSource = + v: gateway: iface: + builtins.elem gateway.source (map (i: i.address) iface.${v}.addresses); + + # Interfaces corresponding to the default gateways + gateway4Iface = builtins.filter (isGateway "ipv4" cfg.defaultGateway) interfaces; + gateway6Iface = builtins.filter (isGateway "ipv6" cfg.defaultGateway6) interfaces; + + # Interfaces corresponding to the default source addresses + # + # Note: the use of `head` here is safe because these expressions + # are evaluated only when `needsSourceIface`, see `configureAddrs` below. + source4Iface = builtins.head (builtins.filter (hasSource "ipv4" cfg.defaultGateway) interfaces); + source6Iface = builtins.head (builtins.filter (hasSource "ipv6" cfg.defaultGateway6) interfaces); + # warn that these attributes are deprecated (2017-2-2) # Should be removed in the release after next bondDeprecation = rec { @@ -118,121 +183,71 @@ let else optional (!config.boot.isContainer) (subsystemDevice dev); - hasDefaultGatewaySet = - (cfg.defaultGateway != null && cfg.defaultGateway.address != "") - || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != ""); - - needNetworkSetup = - cfg.resolvconf.enable || cfg.defaultGateway != null || cfg.defaultGateway6 != null; - - networkLocalCommands = lib.mkIf needNetworkSetup { - after = [ "network-setup.service" ]; - bindsTo = [ "network-setup.service" ]; - }; - - networkSetup = lib.mkIf needNetworkSetup { - description = "Networking Setup"; - - after = [ "network-pre.target" ]; - before = [ - "network.target" - "shutdown.target" - ]; - wants = [ "network.target" ]; - # exclude bridges from the partOf relationship to fix container networking bug #47210 - partOf = map (i: "network-addresses-${i.name}.service") ( - filter (i: !(hasAttr i.name cfg.bridges)) interfaces - ); - conflicts = [ "shutdown.target" ]; - wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target"; - - unitConfig.ConditionCapability = "CAP_NET_ADMIN"; - - path = [ pkgs.iproute2 ]; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - - unitConfig.DefaultDependencies = false; - - script = '' - ${optionalString config.networking.resolvconf.enable '' - # Set the static DNS configuration, if given. - ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <, create a job ‘network-addresses-.service" - # that performs static address configuration. It has a "wants" - # dependency on ‘.service’, which is supposed to create - # the interface and need not exist (i.e. for hardware - # interfaces). It has a binds-to dependency on the actual - # network device, so it only gets started after the interface - # has appeared, and it's stopped when the interface - # disappears. + # For each interface , creates a network-addresses-.service + # job that performs static address configuration. + # + # It has a Wants dependency on -netdev.service, which creates + # create the interface, or on a device unit (for hardware interfaces). + # It also has a BindsTo dependency on the device unit: so, it only gets + # started after the interface has appeared and it's stopped when the + # interface disappears. + # + # Unless in a container, the job is not made part of network.target, so + # if an interface is not found (e.g. a USB interface not plugged in) it + # will not hang the boot sequence. + # + # If the interface is the default gateway, the job will also set the + # default gateway and delay network-online.target. configureAddrs = i: let ips = interfaceIps i; + isDefaultGateway4 = cfg.defaultGateway != null && builtins.elem i gateway4Iface; + isDefaultGateway6 = cfg.defaultGateway6 != null && builtins.elem i gateway6Iface; + needsSourceIface4 = + isDefaultGateway4 && cfg.defaultGateway.source != null && i.name != source4Iface.name; + needsSourceIface6 = + isDefaultGateway6 && cfg.defaultGateway6.source != null && i.name != source6Iface.name; + + configureGateway = + version: gateway: + optionalString (gateway.address != "") '' + echo -n "setting ${i.name} as default IPv${version} gateway... " + ${optionalString (gateway.interface != null) '' + ip -${version} route replace ${gateway.address} proto static ${ + formatIpArgs { + metric = gateway.metric; + dev = gateway.interface; + } + } + ''} + ip -${version} route replace default proto static ${ + formatIpArgs { + metric = gateway.metric; + via = gateway.address; + window = cfg.defaultGatewayWindowSize; + dev = gateway.interface; + src = gateway.source; + } + } + echo "done" + ''; in nameValuePair "network-addresses-${i.name}" { description = "Address configuration of ${i.name}"; - wantedBy = [ - "network-setup.service" - "network.target" - ]; - # order before network-setup because the routes that are configured - # there may need ip addresses configured - before = [ "network-setup.service" ]; + + wantedBy = + deviceDependency i.name + ++ optional config.boot.isContainer "network.target" + ++ optional (isDefaultGateway4 || isDefaultGateway6) "network-online.target"; bindsTo = deviceDependency i.name; - after = [ "network-pre.target" ] ++ (deviceDependency i.name); + partOf = [ "networking-scripted.target" ]; + after = [ + "network-pre.target" + ] + ++ optional needsSourceIface4 "network-addresses-${source4Iface.name}.service" + ++ optional needsSourceIface6 "network-addresses-${source6Iface.name}.service" + ++ deviceDependency i.name; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; # Restart rather than stop+start this unit to prevent the @@ -284,6 +299,10 @@ let fi '' )} + + # Set the default gateway + ${optionalString isDefaultGateway4 (configureGateway "4" cfg.defaultGateway)} + ${optionalString isDefaultGateway6 (configureGateway "6" cfg.defaultGateway6)} ''; preStop = '' state="/run/nixos/network/routes/${i.name}" @@ -311,13 +330,13 @@ let nameValuePair "${i.name}-netdev" { description = "Virtual Network Interface ${i.name}"; bindsTo = optional (!config.boot.isContainer) "dev-net-tun.device"; + partOf = [ "networking-scripted.target" ]; after = optional (!config.boot.isContainer) "dev-net-tun.device" ++ [ "network-pre.target" ]; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice i.name) ]; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; path = [ pkgs.iproute2 ]; serviceConfig = { Type = "oneshot"; @@ -343,18 +362,21 @@ let description = "Bridge Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps ++ optional v.rstp "mstpd.service"; - partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service"; + partOf = [ + "network.target" + "networking-scripted.target" + ] + ++ optional v.rstp "mstpd.service"; after = [ "network-pre.target" ] ++ deps ++ optional v.rstp "mstpd.service" ++ map (i: "network-addresses-${i}.service") v.interfaces; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -448,15 +470,14 @@ let description = "Open vSwitch Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ] ++ internalConfigs; - # before = [ "network-setup.service" ]; - # should work without internalConfigs dependencies because address/link configuration depends - # on the device, which is created by ovs-vswitchd with type=internal, but it does not... - before = [ "network-setup.service" ] ++ internalConfigs; - partOf = [ "network-setup.service" ]; # shutdown the bridge when network is shutdown + before = [ "network.target" ] ++ internalConfigs; + partOf = [ + "network.target" + "networking-scripted.target" + ]; # shutdown the bridge when network is shutdown bindsTo = [ "ovs-vswitchd.service" ]; # requires ovs-vswitchd to be alive at all times after = [ "network-pre.target" @@ -521,12 +542,12 @@ let description = "Bond Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps ++ map (i: "network-addresses-${i}.service") v.interfaces; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ @@ -570,12 +591,12 @@ let description = "MACVLAN Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -602,12 +623,12 @@ let description = "IPVLAN Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -647,12 +668,12 @@ let description = "FOU endpoint ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -677,12 +698,11 @@ let description = "IPv6 in IPv4 Tunnel Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -720,12 +740,12 @@ let description = "IP in IP Tunnel Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -768,12 +788,12 @@ let description = "GRE Tunnel Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -803,13 +823,15 @@ let description = "VLAN Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; - partOf = [ "network-setup.service" ]; + partOf = [ + "network.target" + "networking-scripted.target" + ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -845,10 +867,27 @@ let // mapAttrs' createGreDevice cfg.greTunnels // mapAttrs' createVlanDevice cfg.vlans // { - network-setup = networkSetup; - network-local-commands = networkLocalCommands; + network-local-commands = { + after = [ "network-pre.target" ]; + wantedBy = [ "network.target" ]; + }; }; + # Note: the scripted networking backend consistent of many + # independent services that are linked to the network.target. + # Since there is no daemon (e.g systemd-networkd) that is + # started as part of the system and pulls in network.target. + # Thus, to start these services we link network.target directly + # to multi-user.target, this has the same result. + systemd.targets.network.wantedBy = [ "multi-user.target" ]; + + # This target serves no purpose during the boot, but can be + # used to quickly reset the network configuration by running + # systemctl restart networking-scripted.target + systemd.targets.networking-scripted = { + description = "NixOS scripted networking setup"; + }; + services.udev.extraRules = '' KERNEL=="tun", TAG+="systemd" ''; diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 2b01843b82dc..4887256b7be1 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -747,10 +747,9 @@ in default = ""; example = "text=anything; echo You can put $text here."; description = '' - Shell commands to be executed at the end of the - `network-setup` systemd service. Note that if - you are using DHCP to obtain the network configuration, - interfaces may not be fully configured yet. + Shell commands to be executed after all the network + interfaces have been created, but not necessarily + fully configured. ''; }; @@ -1853,6 +1852,20 @@ in ''; }; }; + + networking.localCommands = lib.mkIf config.networking.resolvconf.enable '' + # Set the static DNS configuration, if given. + ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <