nixos/networking: add options for configuring a GRE tunnel

Add `networking.greTunnels` option that allows a GRE tunnel to be
configured in NixOS.
This commit is contained in:
Matthew Leach 2021-12-07 15:44:00 +00:00
parent b56d7a70a7
commit 5ce7061945
4 changed files with 180 additions and 0 deletions

View File

@ -532,6 +532,33 @@ let
'';
});
createGreDevice = n: v: nameValuePair "${n}-netdev"
(let
deps = deviceDependency v.dev;
in
{ description = "GRE Tunnel Interface ${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 = ''
# Remove Dead Interfaces
ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
ip link add name "${n}" type ${v.type} \
${optionalString (v.remote != null) "remote \"${v.remote}\""} \
${optionalString (v.local != null) "local \"${v.local}\""} \
${optionalString (v.dev != null) "dev \"${v.dev}\""}
ip link set "${n}" up
'';
postStop = ''
ip link delete "${n}" || true
'';
});
createVlanDevice = n: v: nameValuePair "${n}-netdev"
(let
deps = deviceDependency v.interface;
@ -570,6 +597,7 @@ let
// mapAttrs' createMacvlanDevice cfg.macvlans
// mapAttrs' createFouEncapsulation cfg.fooOverUDP
// mapAttrs' createSitDevice cfg.sits
// mapAttrs' createGreDevice cfg.greTunnels
// mapAttrs' createVlanDevice cfg.vlans
// {
network-setup = networkSetup;

View File

@ -18,6 +18,7 @@ let
concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
++ map (sit: sit.dev) (attrValues cfg.sits)
++ map (gre: gre.dev) (attrValues cfg.greTunnels)
++ map (vlan: vlan.interface) (attrValues cfg.vlans)
# add dependency to physical or independently created vswitch member interface
# TODO: warn the user that any address configured on those interfaces will be useless
@ -245,6 +246,25 @@ in
} ]);
};
})))
(mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = gre.type;
};
tunnelConfig =
(optionalAttrs (gre.remote != null) {
Remote = gre.remote;
}) // (optionalAttrs (gre.local != null) {
Local = gre.local;
});
};
networks = mkIf (gre.dev != null) {
"40-${gre.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
tunnel = [ name ];
} ]);
};
})))
(mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
netdevs."40-${name}" = {
netdevConfig = {

View File

@ -9,6 +9,7 @@ let
interfaces = attrValues cfg.interfaces;
hasVirtuals = any (i: i.virtual) interfaces;
hasSits = cfg.sits != { };
hasGres = cfg.greTunnels != { };
hasBonds = cfg.bonds != { };
hasFous = cfg.fooOverUDP != { }
|| filterAttrs (_: s: s.encapsulation != null) cfg.sits != { };
@ -996,6 +997,65 @@ in
});
};
networking.greTunnels = mkOption {
default = { };
example = literalExpression ''
{
greBridge = {
remote = "10.0.0.1";
local = "10.0.0.22";
dev = "enp4s0f0";
type = "tap";
};
}
'';
description = ''
This option allows you to define Generic Routing Encapsulation (GRE) tunnels.
'';
type = with types; attrsOf (submodule {
options = {
remote = mkOption {
type = types.nullOr types.str;
default = null;
example = "10.0.0.1";
description = ''
The address of the remote endpoint to forward traffic over.
'';
};
local = mkOption {
type = types.nullOr types.str;
default = null;
example = "10.0.0.22";
description = ''
The address of the local endpoint which the remote
side should send packets to.
'';
};
dev = mkOption {
type = types.nullOr types.str;
default = null;
example = "enp4s0f0";
description = ''
The underlying network device on which the tunnel resides.
'';
};
type = mkOption {
type = with types; enum [ "tun" "tap" ];
default = "tap";
example = "tap";
apply = v: if v == "tun" then "gre" else "gretap";
description = ''
Whether the tunnel routes layer 2 (tap) or layer 3 (tun) traffic.
'';
};
};
});
};
networking.vlans = mkOption {
default = { };
example = literalExpression ''
@ -1225,6 +1285,7 @@ in
boot.kernelModules = [ ]
++ optional hasVirtuals "tun"
++ optional hasSits "sit"
++ optional hasGres "gre"
++ optional hasBonds "bonding"
++ optional hasFous "fou";

View File

@ -489,6 +489,77 @@ let
client2.wait_until_succeeds("ping -c 1 fc00::2")
'';
};
gre = let
node = { pkgs, ... }: with pkgs.lib; {
networking = {
useNetworkd = networkd;
useDHCP = false;
};
};
in {
name = "GRE";
nodes.client1 = args@{ pkgs, ... }:
mkMerge [
(node args)
{
virtualisation.vlans = [ 1 2 ];
networking = {
greTunnels = {
greTunnel = {
local = "192.168.2.1";
remote = "192.168.2.2";
dev = "eth2";
type = "tap";
};
};
bridges.bridge.interfaces = [ "greTunnel" "eth1" ];
interfaces.eth1.ipv4.addresses = mkOverride 0 [];
interfaces.bridge.ipv4.addresses = mkOverride 0 [
{ address = "192.168.1.1"; prefixLength = 24; }
];
};
}
];
nodes.client2 = args@{ pkgs, ... }:
mkMerge [
(node args)
{
virtualisation.vlans = [ 2 3 ];
networking = {
greTunnels = {
greTunnel = {
local = "192.168.2.2";
remote = "192.168.2.1";
dev = "eth1";
type = "tap";
};
};
bridges.bridge.interfaces = [ "greTunnel" "eth2" ];
interfaces.eth2.ipv4.addresses = mkOverride 0 [];
interfaces.bridge.ipv4.addresses = mkOverride 0 [
{ address = "192.168.1.2"; prefixLength = 24; }
];
};
}
];
testScript = { ... }:
''
start_all()
with subtest("Wait for networking to be configured"):
client1.wait_for_unit("network.target")
client2.wait_for_unit("network.target")
# Print diagnostic information
client1.succeed("ip addr >&2")
client2.succeed("ip addr >&2")
with subtest("Test GRE tunnel bridge over VLAN"):
client1.wait_until_succeeds("ping -c 1 192.168.1.2")
client2.wait_until_succeeds("ping -c 1 192.168.1.1")
'';
};
vlan = let
node = address: { pkgs, ... }: with pkgs.lib; {
#virtualisation.vlans = [ 1 ];