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.
main
pennae 3 years ago committed by tomberek
parent eebfe7199d
commit f29ea2d15d
  1. 13
      nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
  2. 4
      nixos/doc/manual/release-notes/rl-2111.section.md
  3. 26
      nixos/modules/system/boot/networkd.nix
  4. 34
      nixos/modules/tasks/network-interfaces-scripted.nix
  5. 20
      nixos/modules/tasks/network-interfaces-systemd.nix
  6. 69
      nixos/modules/tasks/network-interfaces.nix
  7. 46
      nixos/tests/networking.nix

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

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

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

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

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

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

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

Loading…
Cancel
Save