diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index 73baf71f5ef5..c0f407e11a93 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -1535,6 +1535,19 @@ Superuser created successfully. release notes for changes and upgrade instructions. + + + The systemd.network module has gained + support for the FooOverUDP link type. + + + + + The networking module has a new + networking.fooOverUDP option to configure + Foo-over-UDP encapsulations. + + diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index b7fa2cf0f254..6dadcde3ccc8 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -443,3 +443,7 @@ In addition to numerous new and upgraded packages, this release has the followin - Three new options, [xdg.mime.addedAssociations](#opt-xdg.mime.addedAssociations), [xdg.mime.defaultApplications](#opt-xdg.mime.defaultApplications), and [xdg.mime.removedAssociations](#opt-xdg.mime.removedAssociations) have been added to the [xdg.mime](#opt-xdg.mime.enable) module to allow the configuration of `/etc/xdg/mimeapps.list`. - Kopia was upgraded from 0.8.x to 0.9.x. Please read the [upstream release notes](https://github.com/kopia/kopia/releases/tag/v0.9.0) for changes and upgrade instructions. + +- The `systemd.network` module has gained support for the FooOverUDP link type. + +- The `networking` module has a new `networking.fooOverUDP` option to configure Foo-over-UDP encapsulations. diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 51e105bf6276..662dfe2db989 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -250,6 +250,16 @@ let (assertRange "ERSPANIndex" 1 1048575) ]; + sectionFooOverUDP = checkUnitConfig "FooOverUDP" [ + (assertOnlyFields [ + "Port" + "Encapsulation" + "Protocol" + ]) + (assertPort "Port") + (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"]) + ]; + sectionPeer = checkUnitConfig "Peer" [ (assertOnlyFields [ "Name" @@ -919,6 +929,18 @@ let ''; }; + fooOverUDPConfig = mkOption { + default = { }; + example = { Port = 9001; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionFooOverUDP; + description = '' + Each attribute in this set specifies an option in the + [FooOverUDP] section of the unit. See + systemd.netdev + 5 for details. + ''; + }; + peerConfig = mkOption { default = {}; example = { Name = "veth2"; }; @@ -1449,6 +1471,10 @@ let [Tunnel] ${attrsToSection def.tunnelConfig} '' + + optionalString (def.fooOverUDPConfig != { }) '' + [FooOverUDP] + ${attrsToSection def.fooOverUDPConfig} + '' + optionalString (def.peerConfig != { }) '' [Peer] ${attrsToSection def.peerConfig} diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index 79624ec7072c..055580e3ea2f 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -466,6 +466,39 @@ let ''; }); + createFouEncapsulation = n: v: nameValuePair "${n}-fou-encap" + (let + # if we have a device to bind to we can wait for its addresses to be + # configured, otherwise external sequencing is required. + deps = optionals (v.local != null && v.local.dev != null) + (deviceDependency v.local.dev ++ [ "network-addresses-${v.local.dev}.service" ]); + fouSpec = "port ${toString v.port} ${ + if v.protocol != null then "ipproto ${toString v.protocol}" else "gue" + } ${ + optionalString (v.local != null) "local ${escapeShellArg v.local.address} ${ + optionalString (v.local.dev != null) "dev ${escapeShellArg v.local.dev}" + }" + }"; + in + { description = "FOU endpoint ${n}"; + wantedBy = [ "network-setup.service" (subsystemDevice n) ]; + bindsTo = deps; + partOf = [ "network-setup.service" ]; + after = [ "network-pre.target" ] ++ deps; + before = [ "network-setup.service" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.iproute2 ]; + script = '' + # always remove previous incarnation since show can't filter + ip fou del ${fouSpec} >/dev/null 2>&1 || true + ip fou add ${fouSpec} + ''; + postStop = '' + ip fou del ${fouSpec} || true + ''; + }); + createSitDevice = n: v: nameValuePair "${n}-netdev" (let deps = deviceDependency v.dev; @@ -530,6 +563,7 @@ let // mapAttrs' createVswitchDevice cfg.vswitches // mapAttrs' createBondDevice cfg.bonds // mapAttrs' createMacvlanDevice cfg.macvlans + // mapAttrs' createFouEncapsulation cfg.fooOverUDP // mapAttrs' createSitDevice cfg.sits // mapAttrs' createVlanDevice cfg.vlans // { diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index 225f9dc67fcc..516764b87db6 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -47,6 +47,9 @@ in } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: { assertion = !rstp; message = "networking.bridges.${n}.rstp is not supported by networkd."; + }) ++ flip mapAttrsToList cfg.fooOverUDP (n: { local, ... }: { + assertion = local == null; + message = "networking.fooOverUDP.${n}.local is not supported by networkd."; }); networking.dhcpcd.enable = mkDefault false; @@ -194,6 +197,23 @@ in macvlan = [ name ]; } ]); }))) + (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "fou"; + }; + # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes, + # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature + # in networkd. + fooOverUDPConfig = { + Port = fou.port; + Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation"; + } // (optionalAttrs (fou.protocol != null) { + Protocol = fou.protocol; + }); + }; + }))) (mkMerge (flip mapAttrsToList cfg.sits (name: sit: { netdevs."40-${name}" = { netdevConfig = { diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 313b0fac7dab..a3b41326168a 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -10,6 +10,7 @@ let hasVirtuals = any (i: i.virtual) interfaces; hasSits = cfg.sits != { }; hasBonds = cfg.bonds != { }; + hasFous = cfg.fooOverUDP != { }; slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds) ++ concatMap (i: i.interfaces) (attrValues cfg.bridges) @@ -823,6 +824,71 @@ in }); }; + networking.fooOverUDP = mkOption { + default = { }; + example = + { + primary = { port = 9001; local = { address = "192.0.2.1"; dev = "eth0"; }; }; + backup = { port = 9002; }; + }; + description = '' + This option allows you to configure Foo Over UDP and Generic UDP Encapsulation + endpoints. See ip-fou + 8 for details. + ''; + type = with types; attrsOf (submodule { + options = { + port = mkOption { + type = port; + description = '' + Local port of the encapsulation UDP socket. + ''; + }; + + protocol = mkOption { + type = nullOr (ints.between 1 255); + default = null; + description = '' + Protocol number of the encapsulated packets. Specifying null + (the default) creates a GUE endpoint, specifying a protocol number will create + a FOU endpoint. + ''; + }; + + local = mkOption { + type = nullOr (submodule { + options = { + address = mkOption { + type = types.str; + description = '' + Local address to bind to. The address must be available when the FOU + endpoint is created, using the scripted network setup this can be achieved + either by setting dev or adding dependency information to + systemd.services.<name>-fou-encap; it isn't supported + when using networkd. + ''; + }; + + dev = mkOption { + type = nullOr str; + default = null; + example = "eth0"; + description = '' + Network device to bind to. + ''; + }; + }; + }); + default = null; + example = { address = "203.0.113.22"; }; + description = '' + Local address (and optionally device) to bind to using the given port. + ''; + }; + }; + }); + }; + networking.sits = mkOption { default = { }; example = literalExpression '' @@ -1116,7 +1182,8 @@ in boot.kernelModules = [ ] ++ optional hasVirtuals "tun" ++ optional hasSits "sit" - ++ optional hasBonds "bonding"; + ++ optional hasBonds "bonding" + ++ optional hasFous "fou"; boot.extraModprobeConfig = # This setting is intentional as it prevents default bond devices diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix index 8b947ddf0cf4..fdcf67f1126c 100644 --- a/nixos/tests/networking.nix +++ b/nixos/tests/networking.nix @@ -380,6 +380,52 @@ let router.wait_until_succeeds("ping -c 1 192.168.1.3") ''; }; + fou = { + name = "foo-over-udp"; + nodes.machine = { ... }: { + virtualisation.vlans = [ 1 ]; + networking = { + useNetworkd = networkd; + useDHCP = false; + interfaces.eth1.ipv4.addresses = mkOverride 0 + [ { address = "192.168.1.1"; prefixLength = 24; } ]; + fooOverUDP = { + fou1 = { port = 9001; }; + fou2 = { port = 9002; protocol = 41; }; + fou3 = mkIf (!networkd) + { port = 9003; local.address = "192.168.1.1"; }; + fou4 = mkIf (!networkd) + { port = 9004; local = { address = "192.168.1.1"; dev = "eth1"; }; }; + }; + }; + systemd.services = { + fou3-fou-encap.after = optional (!networkd) "network-addresses-eth1.service"; + }; + }; + testScript = { ... }: + '' + import json + + machine.wait_for_unit("network.target") + fous = json.loads(machine.succeed("ip -json fou show")) + assert {"port": 9001, "gue": None, "family": "inet"} in fous, "fou1 exists" + assert {"port": 9002, "ipproto": 41, "family": "inet"} in fous, "fou2 exists" + '' + optionalString (!networkd) '' + assert { + "port": 9003, + "gue": None, + "family": "inet", + "local": "192.168.1.1", + } in fous, "fou3 exists" + assert { + "port": 9004, + "gue": None, + "family": "inet", + "local": "192.168.1.1", + "dev": "eth1", + } in fous, "fou4 exists" + ''; + }; sit = let node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; { virtualisation.vlans = [ 1 ];