nixos/networking: add foo-over-udp endpoint support

allows configuration of foo-over-udp decapsulation endpoints. sadly networkd
seems to lack the features necessary to support local and peer address
configuration, so those are only supported when using scripted configuration.

pennae f29ea2d1 eebfe719

Changed files
+211 -1
nixos
+13
nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
···
release notes</link> for changes and upgrade instructions.
</para>
</listitem>
+
<listitem>
+
<para>
+
The <literal>systemd.network</literal> module has gained
+
support for the FooOverUDP link type.
+
</para>
+
</listitem>
+
<listitem>
+
<para>
+
The <literal>networking</literal> module has a new
+
<literal>networking.fooOverUDP</literal> option to configure
+
Foo-over-UDP encapsulations.
+
</para>
+
</listitem>
</itemizedlist>
</section>
</section>
+4
nixos/doc/manual/release-notes/rl-2111.section.md
···
- 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.
+26
nixos/modules/system/boot/networkd.nix
···
(assertRange "ERSPANIndex" 1 1048575)
];
+
sectionFooOverUDP = checkUnitConfig "FooOverUDP" [
+
(assertOnlyFields [
+
"Port"
+
"Encapsulation"
+
"Protocol"
+
])
+
(assertPort "Port")
+
(assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"])
+
];
+
sectionPeer = checkUnitConfig "Peer" [
(assertOnlyFields [
"Name"
···
'';
};
+
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
+
<literal>[FooOverUDP]</literal> section of the unit. See
+
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+
<manvolnum>5</manvolnum></citerefentry> for details.
+
'';
+
};
+
peerConfig = mkOption {
default = {};
example = { Name = "veth2"; };
···
+ optionalString (def.tunnelConfig != { }) ''
[Tunnel]
${attrsToSection def.tunnelConfig}
+
''
+
+ optionalString (def.fooOverUDPConfig != { }) ''
+
[FooOverUDP]
+
${attrsToSection def.fooOverUDPConfig}
''
+ optionalString (def.peerConfig != { }) ''
[Peer]
+34
nixos/modules/tasks/network-interfaces-scripted.nix
···
'';
});
+
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;
···
// mapAttrs' createVswitchDevice cfg.vswitches
// mapAttrs' createBondDevice cfg.bonds
// mapAttrs' createMacvlanDevice cfg.macvlans
+
// mapAttrs' createFouEncapsulation cfg.fooOverUDP
// mapAttrs' createSitDevice cfg.sits
// mapAttrs' createVlanDevice cfg.vlans
// {
+20
nixos/modules/tasks/network-interfaces-systemd.nix
···
} ] ++ 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;
···
networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
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}" = {
+68 -1
nixos/modules/tasks/network-interfaces.nix
···
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)
···
});
};
+
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 <citerefentry><refentrytitle>ip-fou</refentrytitle>
+
<manvolnum>8</manvolnum></citerefentry> 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 <literal>null</literal>
+
(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 <literal>dev</literal> or adding dependency information to
+
<literal>systemd.services.&lt;name&gt;-fou-encap</literal>; 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 ''
···
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
+46
nixos/tests/networking.nix
···
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 ];