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.
This commit is contained in:
pennae 2021-08-11 11:02:47 +02:00 committed by tomberek
parent eebfe7199d
commit f29ea2d15d
7 changed files with 211 additions and 1 deletions

View File

@ -1535,6 +1535,19 @@ Superuser created successfully.
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>

View File

@ -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.

View File

@ -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
<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"; };
@ -1449,6 +1471,10 @@ let
[Tunnel]
${attrsToSection def.tunnelConfig}
''
+ optionalString (def.fooOverUDPConfig != { }) ''
[FooOverUDP]
${attrsToSection def.fooOverUDPConfig}
''
+ optionalString (def.peerConfig != { }) ''
[Peer]
${attrsToSection def.peerConfig}

View File

@ -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
// {

View File

@ -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 = {

View File

@ -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 <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 ''
@ -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

View File

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