nixos/networking-interfaces: add IPIP tunnels
This commit is contained in:
@@ -107,6 +107,7 @@ let
|
||||
|| (hasAttr dev cfg.bonds)
|
||||
|| (hasAttr dev cfg.macvlans)
|
||||
|| (hasAttr dev cfg.sits)
|
||||
|| (hasAttr dev cfg.ipips)
|
||||
|| (hasAttr dev cfg.vlans)
|
||||
|| (hasAttr dev cfg.greTunnels)
|
||||
|| (hasAttr dev cfg.vswitches)
|
||||
@@ -662,6 +663,52 @@ let
|
||||
}
|
||||
);
|
||||
|
||||
createIpipDevice =
|
||||
n: v:
|
||||
nameValuePair "${n}-netdev" (
|
||||
let
|
||||
deps = deviceDependency v.dev;
|
||||
in
|
||||
{
|
||||
description = "IP in IP Tunnel Interface ${n}";
|
||||
wantedBy = [
|
||||
"network-setup.service"
|
||||
(subsystemDevice n)
|
||||
];
|
||||
bindsTo = deps;
|
||||
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 dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
|
||||
ip tunnel add name "${n}" ${
|
||||
formatIpArgs {
|
||||
inherit (v)
|
||||
remote
|
||||
local
|
||||
ttl
|
||||
dev
|
||||
;
|
||||
mode =
|
||||
{
|
||||
"4in6" = "ipip6";
|
||||
"ipip" = "ipip";
|
||||
}
|
||||
.${v.encapsulation.type};
|
||||
encaplimit = if v.encapsulation.type == "ipip" then null else v.encapsulation.limit;
|
||||
}
|
||||
}
|
||||
ip link set dev "${n}" up
|
||||
'';
|
||||
postStop = ''
|
||||
ip link delete dev "${n}" || true
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
createGreDevice =
|
||||
n: v:
|
||||
nameValuePair "${n}-netdev" (
|
||||
@@ -743,6 +790,7 @@ let
|
||||
// mapAttrs' createMacvlanDevice cfg.macvlans
|
||||
// mapAttrs' createFouEncapsulation cfg.fooOverUDP
|
||||
// mapAttrs' createSitDevice cfg.sits
|
||||
// mapAttrs' createIpipDevice cfg.ipips
|
||||
// mapAttrs' createGreDevice cfg.greTunnels
|
||||
// mapAttrs' createVlanDevice cfg.vlans
|
||||
// {
|
||||
|
||||
@@ -24,6 +24,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 (ipip: ipip.dev) (attrValues cfg.ipips)
|
||||
++ map (gre: gre.dev) (attrValues cfg.greTunnels)
|
||||
++ map (vlan: vlan.interface) (attrValues cfg.vlans)
|
||||
# add dependency to physical or independently created vswitch member interface
|
||||
@@ -457,6 +458,38 @@ in
|
||||
}
|
||||
)
|
||||
))
|
||||
(mkMerge (
|
||||
flip mapAttrsToList cfg.ipips (
|
||||
name: ipip: {
|
||||
netdevs."40-${name}" = {
|
||||
netdevConfig = {
|
||||
Name = name;
|
||||
Kind = if ipip.encapsulation.type == "ipip" then "ipip" else "ip6tnl";
|
||||
};
|
||||
tunnelConfig =
|
||||
(optionalAttrs (ipip.remote != null) {
|
||||
Remote = ipip.remote;
|
||||
})
|
||||
// (optionalAttrs (ipip.local != null) {
|
||||
Local = ipip.local;
|
||||
})
|
||||
// (optionalAttrs (ipip.ttl != null) {
|
||||
TTL = ipip.ttl;
|
||||
})
|
||||
// (optionalAttrs (ipip.encapsulation.type != "ipip") {
|
||||
# IPv6 tunnel options
|
||||
Mode = if ipip.encapsulation.type == "4in6" then "ipip6" else "ip6ip6";
|
||||
EncapsulationLimit = ipip.encapsulation.type;
|
||||
});
|
||||
};
|
||||
networks = mkIf (ipip.dev != null) {
|
||||
"40-${ipip.dev}" = {
|
||||
tunnel = [ name ];
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
))
|
||||
(mkMerge (
|
||||
flip mapAttrsToList cfg.greTunnels (
|
||||
name: gre: {
|
||||
|
||||
@@ -1137,6 +1137,104 @@ in
|
||||
});
|
||||
};
|
||||
|
||||
networking.ipips = mkOption {
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
wan4in6 = {
|
||||
remote = "2001:db8::1";
|
||||
local = "2001:db8::3";
|
||||
dev = "wan6";
|
||||
encapsulation.type = "4in6";
|
||||
encapsulation.limit = 0;
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
This option allows you to define interfaces encapsulating IP
|
||||
packets within IP packets; which should be automatically created.
|
||||
|
||||
For example, this allows you to create 4in6 (RFC 2473)
|
||||
or IP within IP (RFC 2003) tunnels.
|
||||
'';
|
||||
type =
|
||||
with types;
|
||||
attrsOf (submodule {
|
||||
options = {
|
||||
|
||||
remote = mkOption {
|
||||
type = types.str;
|
||||
example = "2001:db8::1";
|
||||
description = ''
|
||||
The address of the remote endpoint to forward traffic over.
|
||||
'';
|
||||
};
|
||||
|
||||
local = mkOption {
|
||||
type = types.str;
|
||||
example = "2001:db8::3";
|
||||
description = ''
|
||||
The address of the local endpoint which the remote
|
||||
side should send packets to.
|
||||
'';
|
||||
};
|
||||
|
||||
ttl = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
example = 255;
|
||||
description = ''
|
||||
The time-to-live of the connection to the remote tunnel endpoint.
|
||||
'';
|
||||
};
|
||||
|
||||
dev = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "wan6";
|
||||
description = ''
|
||||
The underlying network device on which the tunnel resides.
|
||||
'';
|
||||
};
|
||||
|
||||
encapsulation.type = mkOption {
|
||||
type = types.enum [
|
||||
"ipip"
|
||||
"4in6"
|
||||
"ip6ip6"
|
||||
];
|
||||
default = "ipip";
|
||||
description = ''
|
||||
Select the encapsulation type:
|
||||
|
||||
- `ipip` to create an IPv4 within IPv4 tunnel (RFC 2003).
|
||||
|
||||
- `4in6` to create a 4in6 tunnel (RFC 2473);
|
||||
|
||||
- `ip6ip6` to create an IPv6 within IPv6 tunnel (RFC 2473);
|
||||
|
||||
::: {.note}
|
||||
For encapsulating IPv6 within IPv4 packets, see
|
||||
the ad-hoc {option}`networking.sits` option.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
encapsulation.limit = mkOption {
|
||||
type = types.either (types.enum [ "none" ]) types.ints.unsigned;
|
||||
default = 4;
|
||||
example = "none";
|
||||
description = ''
|
||||
For an IPv6-based tunnel, the maximum number of nested
|
||||
encapsulation to allow. 0 means no nesting, "none" unlimited.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
networking.sits = mkOption {
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
|
||||
@@ -659,6 +659,140 @@ let
|
||||
client2.wait_until_succeeds("ping -c 1 fc00::2")
|
||||
'';
|
||||
};
|
||||
ipip-4in6 =
|
||||
let
|
||||
node =
|
||||
{
|
||||
address4,
|
||||
remote,
|
||||
address6,
|
||||
}:
|
||||
{
|
||||
virtualisation.interfaces.enp1s0.vlan = 1;
|
||||
networking = {
|
||||
useNetworkd = networkd;
|
||||
useDHCP = false;
|
||||
ipips."4in6" = {
|
||||
inherit remote;
|
||||
local = address6;
|
||||
dev = "enp1s0";
|
||||
encapsulation.type = "4in6";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nftables.enable = true;
|
||||
firewall.extraInputRules = "meta l4proto ipip accept";
|
||||
interfaces.enp1s0.ipv6.addresses = lib.mkOverride 0 [
|
||||
{
|
||||
address = address6;
|
||||
prefixLength = 64;
|
||||
}
|
||||
];
|
||||
interfaces."4in6".ipv4.addresses = lib.mkOverride 0 [
|
||||
{
|
||||
address = address4;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "ipip-4in6";
|
||||
nodes.client1 = node {
|
||||
address6 = "fc00::1";
|
||||
address4 = "192.168.1.1";
|
||||
remote = "fc00::2";
|
||||
};
|
||||
nodes.client2 = node {
|
||||
address6 = "fc00::2";
|
||||
address4 = "192.168.1.2";
|
||||
remote = "fc00::1";
|
||||
};
|
||||
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 ipv6"):
|
||||
client1.wait_until_succeeds("ping -c 1 192.168.1.1")
|
||||
client1.wait_until_succeeds("ping -c 1 192.168.1.2")
|
||||
|
||||
client2.wait_until_succeeds("ping -c 1 192.168.1.1")
|
||||
client2.wait_until_succeeds("ping -c 1 192.168.1.2")
|
||||
'';
|
||||
};
|
||||
ipip =
|
||||
let
|
||||
node =
|
||||
{
|
||||
local,
|
||||
remote,
|
||||
address,
|
||||
}:
|
||||
{
|
||||
virtualisation.interfaces.enp1s0.vlan = 1;
|
||||
networking = {
|
||||
useNetworkd = networkd;
|
||||
useDHCP = false;
|
||||
ipips.ipip = {
|
||||
inherit local remote;
|
||||
dev = "enp1s0";
|
||||
encapsulation.type = "ipip";
|
||||
};
|
||||
nftables.enable = true;
|
||||
firewall.extraInputRules = "meta l4proto 4 accept";
|
||||
interfaces.enp1s0.ipv4.addresses = lib.mkOverride 0 [
|
||||
{
|
||||
address = local;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
interfaces.ipip.ipv4.addresses = lib.mkOverride 0 [
|
||||
{
|
||||
inherit address;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "ipip";
|
||||
nodes.client1 = node {
|
||||
local = "192.168.1.1";
|
||||
remote = "192.168.1.2";
|
||||
address = "192.168.10.1";
|
||||
};
|
||||
nodes.client2 = node {
|
||||
local = "192.168.1.2";
|
||||
remote = "192.168.1.1";
|
||||
address = "192.168.10.2";
|
||||
};
|
||||
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 IPIP tunnel"):
|
||||
client1.wait_until_succeeds("ping -c 1 192.168.10.1")
|
||||
client1.wait_until_succeeds("ping -c 1 192.168.10.2")
|
||||
|
||||
client2.wait_until_succeeds("ping -c 1 192.168.10.1")
|
||||
client2.wait_until_succeeds("ping -c 1 192.168.10.2")
|
||||
'';
|
||||
};
|
||||
gre =
|
||||
let
|
||||
node =
|
||||
|
||||
Reference in New Issue
Block a user