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 ];