nixos/network-interfaces: improve IPv6 support (#417150)

This commit is contained in:
Michele Guerini Rocco
2025-07-23 16:51:24 +02:00
committed by GitHub
6 changed files with 538 additions and 77 deletions

View File

@@ -145,6 +145,15 @@
- `services.monero` now includes the `environmentFile` option for adding secrets to the Monero daemon config. - `services.monero` now includes the `environmentFile` option for adding secrets to the Monero daemon config.
- The new option [networking.ipips](#opt-networking.ipips) has been added to create IP within IP kind of tunnels (including 4in6, ip6ip6 and ipip).
With the existing [networking.sits](#opt-networking.sits) option (6in4), it is now possible to create all combinations of IPv4 and IPv6 encapsulation.
- It is now possible to configure the default source address using the new options [networking.defaultGateway.source](#opt-networking.defaultGateway.source),
[networking.defaultGateway6.source](#opt-networking.defaultGateway6.source).
- Potential race conditions in the network setup when using `networking.interfaces` have been fixed by disabling duplicate address detection (DAD)
for statically configured IPv6 addresses.
- `amdgpu` kernel driver overdrive mode can now be enabled by setting [hardware.amdgpu.overdrive.enable](#opt-hardware.amdgpu.overdrive.enable) and customized through [hardware.amdgpu.overdrive.ppfeaturemask](#opt-hardware.amdgpu.overdrive.ppfeaturemask). - `amdgpu` kernel driver overdrive mode can now be enabled by setting [hardware.amdgpu.overdrive.enable](#opt-hardware.amdgpu.overdrive.enable) and customized through [hardware.amdgpu.overdrive.ppfeaturemask](#opt-hardware.amdgpu.overdrive.ppfeaturemask).
This allows for fine-grained control over the GPU's performance and maybe required by overclocking softwares like Corectrl and Lact. These new options replace old options such as {option}`programs.corectrl.gpuOverclock.enable` and {option}`programs.tuxclocker.enableAMD`. This allows for fine-grained control over the GPU's performance and maybe required by overclocking softwares like Corectrl and Lact. These new options replace old options such as {option}`programs.corectrl.gpuOverclock.enable` and {option}`programs.tuxclocker.enableAMD`.

View File

@@ -42,6 +42,14 @@ let
ip link del dev "${i}" 2>/dev/null || true ip link del dev "${i}" 2>/dev/null || true
''; '';
formatIpArgs =
args:
lib.pipe args [
(lib.filterAttrs (n: v: v != null))
(lib.mapAttrsToList (n: v: "${n} ${toString v}"))
(lib.concatStringsSep " ")
];
# warn that these attributes are deprecated (2017-2-2) # warn that these attributes are deprecated (2017-2-2)
# Should be removed in the release after next # Should be removed in the release after next
bondDeprecation = rec { bondDeprecation = rec {
@@ -99,6 +107,7 @@ let
|| (hasAttr dev cfg.bonds) || (hasAttr dev cfg.bonds)
|| (hasAttr dev cfg.macvlans) || (hasAttr dev cfg.macvlans)
|| (hasAttr dev cfg.sits) || (hasAttr dev cfg.sits)
|| (hasAttr dev cfg.ipips)
|| (hasAttr dev cfg.vlans) || (hasAttr dev cfg.vlans)
|| (hasAttr dev cfg.greTunnels) || (hasAttr dev cfg.greTunnels)
|| (hasAttr dev cfg.vswitches) || (hasAttr dev cfg.vswitches)
@@ -160,39 +169,41 @@ let
EOF EOF
''} ''}
# Set the default gateway. # Set the default gateway
${optionalString (cfg.defaultGateway != null && cfg.defaultGateway.address != "") '' ${flip concatMapStrings
${optionalString (cfg.defaultGateway.interface != null) '' [
ip route replace ${cfg.defaultGateway.address} dev ${cfg.defaultGateway.interface} ${ {
optionalString (cfg.defaultGateway.metric != null) "metric ${toString cfg.defaultGateway.metric}" version = "-4";
} proto static gateway = cfg.defaultGateway;
''} }
ip route replace default ${ {
optionalString (cfg.defaultGateway.metric != null) "metric ${toString cfg.defaultGateway.metric}" version = "-6";
} via "${cfg.defaultGateway.address}" ${ gateway = cfg.defaultGateway6;
optionalString ( }
cfg.defaultGatewayWindowSize != null ]
) "window ${toString cfg.defaultGatewayWindowSize}" (
} ${ { version, gateway }:
optionalString (cfg.defaultGateway.interface != null) "dev ${cfg.defaultGateway.interface}" optionalString (gateway != null && gateway.address != "") ''
} proto static ${optionalString (gateway.interface != null) ''
''} ip ${version} route replace ${gateway.address} proto static ${
${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "") '' formatIpArgs {
${optionalString (cfg.defaultGateway6.interface != null) '' metric = gateway.metric;
ip -6 route replace ${cfg.defaultGateway6.address} dev ${cfg.defaultGateway6.interface} ${ dev = gateway.interface;
optionalString (cfg.defaultGateway6.metric != null) "metric ${toString cfg.defaultGateway6.metric}" }
} proto static }
''} ''}
ip -6 route replace default ${ ip ${version} route replace default proto static ${
optionalString (cfg.defaultGateway6.metric != null) "metric ${toString cfg.defaultGateway6.metric}" formatIpArgs {
} via "${cfg.defaultGateway6.address}" ${ metric = gateway.metric;
optionalString ( via = gateway.address;
cfg.defaultGatewayWindowSize != null window = cfg.defaultGatewayWindowSize;
) "window ${toString cfg.defaultGatewayWindowSize}" dev = gateway.interface;
} ${ src = gateway.source;
optionalString (cfg.defaultGateway6.interface != null) "dev ${cfg.defaultGateway6.interface}" }
} proto static }
''} ''
)
}
''; '';
}; };
@@ -240,10 +251,10 @@ let
'' ''
echo "${cidr}" >> $state echo "${cidr}" >> $state
echo -n "adding address ${cidr}... " echo -n "adding address ${cidr}... "
if out=$(ip addr replace "${cidr}" dev "${i.name}" 2>&1); then if out=$(ip addr replace "${cidr}" dev "${i.name}" nodad 2>&1); then
echo "done" echo "done"
else else
echo "'ip addr replace \"${cidr}\" dev \"${i.name}\"' failed: $out" echo "'ip addr replace \"${cidr}\" dev \"${i.name}\"' nodad failed: $out"
exit 1 exit 1
fi fi
'' ''
@@ -617,7 +628,7 @@ let
deps = deviceDependency v.dev; deps = deviceDependency v.dev;
in in
{ {
description = "6-to-4 Tunnel Interface ${n}"; description = "IPv6 in IPv4 Tunnel Interface ${n}";
wantedBy = [ wantedBy = [
"network-setup.service" "network-setup.service"
(subsystemDevice n) (subsystemDevice n)
@@ -631,18 +642,65 @@ let
script = '' script = ''
# Remove Dead Interfaces # Remove Dead Interfaces
ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}" ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
ip link add name "${n}" type sit \ ip link add name "${n}" type sit ${
${optionalString (v.remote != null) "remote \"${v.remote}\""} \ formatIpArgs {
${optionalString (v.local != null) "local \"${v.local}\""} \ inherit (v)
${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \ remote
${optionalString (v.dev != null) "dev \"${v.dev}\""} \ local
${optionalString (v.encapsulation != null) ttl
"encap ${v.encapsulation.type} encap-dport ${toString v.encapsulation.port} ${ dev
optionalString ( ;
v.encapsulation.sourcePort != null encap = if v.encapsulation.type == "6in4" then null else v.encapsulation.type;
) "encap-sport ${toString v.encapsulation.sourcePort}" encap-dport = v.encapsulation.port;
}" encap-sport = v.encapsulation.sourcePort;
} }
}
ip link set dev "${n}" up
'';
postStop = ''
ip link delete dev "${n}" || true
'';
}
);
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 ip link set dev "${n}" up
''; '';
postStop = '' postStop = ''
@@ -732,6 +790,7 @@ let
// mapAttrs' createMacvlanDevice cfg.macvlans // mapAttrs' createMacvlanDevice cfg.macvlans
// mapAttrs' createFouEncapsulation cfg.fooOverUDP // mapAttrs' createFouEncapsulation cfg.fooOverUDP
// mapAttrs' createSitDevice cfg.sits // mapAttrs' createSitDevice cfg.sits
// mapAttrs' createIpipDevice cfg.ipips
// mapAttrs' createGreDevice cfg.greTunnels // mapAttrs' createGreDevice cfg.greTunnels
// mapAttrs' createVlanDevice cfg.vlans // mapAttrs' createVlanDevice cfg.vlans
// { // {
@@ -748,6 +807,9 @@ let
in in
{ {
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
config = mkMerge [ config = mkMerge [
bondWarnings bondWarnings
(mkIf (!cfg.useNetworkd) normalConfig) (mkIf (!cfg.useNetworkd) normalConfig)

View File

@@ -24,6 +24,7 @@ let
concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
++ map (sit: sit.dev) (attrValues cfg.sits) ++ map (sit: sit.dev) (attrValues cfg.sits)
++ map (ipip: ipip.dev) (attrValues cfg.ipips)
++ map (gre: gre.dev) (attrValues cfg.greTunnels) ++ map (gre: gre.dev) (attrValues cfg.greTunnels)
++ map (vlan: vlan.interface) (attrValues cfg.vlans) ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
# add dependency to physical or independently created vswitch member interface # add dependency to physical or independently created vswitch member interface
@@ -46,6 +47,9 @@ let
// optionalAttrs (gateway.metric != null) { // optionalAttrs (gateway.metric != null) {
Metric = gateway.metric; Metric = gateway.metric;
} }
// optionalAttrs (gateway.source != null) {
PreferredSource = gateway.source;
}
) )
]; ];
}; };
@@ -435,7 +439,7 @@ in
// (optionalAttrs (sit.ttl != null) { // (optionalAttrs (sit.ttl != null) {
TTL = sit.ttl; TTL = sit.ttl;
}) })
// (optionalAttrs (sit.encapsulation != null) ( // (optionalAttrs (sit.encapsulation.type != "6in4") (
{ {
FooOverUDP = true; FooOverUDP = true;
Encapsulation = if sit.encapsulation.type == "fou" then "FooOverUDP" else "GenericUDPEncapsulation"; Encapsulation = if sit.encapsulation.type == "fou" then "FooOverUDP" else "GenericUDPEncapsulation";
@@ -454,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 ( (mkMerge (
flip mapAttrsToList cfg.greTunnels ( flip mapAttrsToList cfg.greTunnels (
name: gre: { name: gre: {

View File

@@ -19,7 +19,8 @@ let
hasSits = cfg.sits != { }; hasSits = cfg.sits != { };
hasGres = cfg.greTunnels != { }; hasGres = cfg.greTunnels != { };
hasBonds = cfg.bonds != { }; hasBonds = cfg.bonds != { };
hasFous = cfg.fooOverUDP != { } || filterAttrs (_: s: s.encapsulation != null) cfg.sits != { }; hasFous =
cfg.fooOverUDP != { } || filterAttrs (_: s: s.encapsulation.type != "6in4") cfg.sits != { };
slaves = slaves =
concatMap (i: i.interfaces) (attrValues cfg.bonds) concatMap (i: i.interfaces) (attrValues cfg.bonds)
@@ -180,6 +181,12 @@ let
description = "The default gateway metric/preference."; description = "The default gateway metric/preference.";
}; };
source = mkOption {
type = types.nullOr types.str;
default = null;
description = "The default source address.";
};
}; };
}; };
@@ -656,6 +663,7 @@ in
example = { example = {
address = "131.211.84.1"; address = "131.211.84.1";
interface = "enp3s0"; interface = "enp3s0";
source = "131.211.84.2";
}; };
type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts)); type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
description = '' description = ''
@@ -669,6 +677,7 @@ in
example = { example = {
address = "2001:4d0:1e04:895::1"; address = "2001:4d0:1e04:895::1";
interface = "enp3s0"; interface = "enp3s0";
source = "2001:4d0:1e04:895::2";
}; };
type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts)); type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
description = '' description = ''
@@ -1134,6 +1143,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 { networking.sits = mkOption {
default = { }; default = { };
example = literalExpression '' example = literalExpression ''
@@ -1151,7 +1258,8 @@ in
} }
''; '';
description = '' description = ''
This option allows you to define 6-to-4 interfaces which should be automatically created. This option allows you to define interfaces encapsulating IPv6
packets within IPv4 packets; which should be automatically created.
''; '';
type = type =
with types; with types;
@@ -1195,50 +1303,76 @@ in
''; '';
}; };
encapsulation = encapsulation = mkOption {
with types; type = types.nullOr (
mkOption { types.submodule {
type = nullOr (submodule {
options = { options = {
type = mkOption { type = mkOption {
type = enum [ type = types.enum [
"6in4"
"fou" "fou"
"gue" "gue"
]; ];
default = "6in4";
description = '' description = ''
Selects encapsulation type. See Select the encapsulation type:
{manpage}`ip-link(8)` for details.
- `6in4`: the IPv6 packets are encapsulated using the
6in4 protocol (formerly known as SIT, RFC 4213);
- `gue`: the IPv6 packets are encapsulated in UDP packets
using the Generic UDP Encapsulation (GUE) scheme;
- `foo`: the IPv6 packets are encapsulated in UDP packets
using the Foo over UDP (FOU) scheme.
''; '';
}; };
port = mkOption { port = mkOption {
type = port; type = types.nullOr types.port;
default = null;
example = 9001; example = 9001;
description = '' description = ''
Destination port for encapsulated packets. Destination port when using UDP encapsulation.
''; '';
}; };
sourcePort = mkOption { sourcePort = mkOption {
type = nullOr types.port; type = types.nullOr types.port;
default = null; default = null;
example = 9002; example = 9002;
description = '' description = ''
Source port for encapsulated packets. Will be chosen automatically by Source port when using UDP encapsulation.
the kernel if unset. Will be chosen automatically by the kernel if unset.
''; '';
}; };
}; };
}); }
default = null; );
example = { apply =
type = "fou"; x:
port = 9001; if x == null then
}; lib.warn
description = '' ''
Configures encapsulation in UDP packets. The option networking.sits.*.encapsulation no longer accepts `null`
''; as a valid value. To fix this warning simply remove this definition.
''
{
type = "6in4";
port = null;
sourcePort = null;
}
else
x;
default = { };
example = {
type = "fou";
port = 9001;
}; };
description = ''
Configures the type of encapsulation.
'';
};
}; };
@@ -1541,6 +1675,8 @@ in
###### implementation ###### implementation
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
config = { config = {
warnings = warnings =

View File

@@ -144,7 +144,7 @@ rec {
(onFullSupported "nixos.tests.networking.scripted.macvlan") (onFullSupported "nixos.tests.networking.scripted.macvlan")
(onFullSupported "nixos.tests.networking.scripted.privacy") (onFullSupported "nixos.tests.networking.scripted.privacy")
(onFullSupported "nixos.tests.networking.scripted.routes") (onFullSupported "nixos.tests.networking.scripted.routes")
(onFullSupported "nixos.tests.networking.scripted.sit") (onFullSupported "nixos.tests.networking.scripted.sit-fou")
(onFullSupported "nixos.tests.networking.scripted.static") (onFullSupported "nixos.tests.networking.scripted.static")
(onFullSupported "nixos.tests.networking.scripted.virtual") (onFullSupported "nixos.tests.networking.scripted.virtual")
(onFullSupported "nixos.tests.networking.scripted.vlan") (onFullSupported "nixos.tests.networking.scripted.vlan")
@@ -158,7 +158,7 @@ rec {
#(onFullSupported "nixos.tests.networking.networkd.macvlan") #(onFullSupported "nixos.tests.networking.networkd.macvlan")
(onFullSupported "nixos.tests.networking.networkd.privacy") (onFullSupported "nixos.tests.networking.networkd.privacy")
(onFullSupported "nixos.tests.networking.networkd.routes") (onFullSupported "nixos.tests.networking.networkd.routes")
(onFullSupported "nixos.tests.networking.networkd.sit") (onFullSupported "nixos.tests.networking.networkd.sit-fou")
(onFullSupported "nixos.tests.networking.networkd.static") (onFullSupported "nixos.tests.networking.networkd.static")
(onFullSupported "nixos.tests.networking.networkd.virtual") (onFullSupported "nixos.tests.networking.networkd.virtual")
(onFullSupported "nixos.tests.networking.networkd.vlan") (onFullSupported "nixos.tests.networking.networkd.vlan")

View File

@@ -39,11 +39,23 @@ let
defaultGateway = { defaultGateway = {
address = "192.168.1.1"; address = "192.168.1.1";
interface = "enp1s0"; interface = "enp1s0";
source = "192.168.1.3";
}; };
defaultGateway6 = { defaultGateway6 = {
address = "fd00:1234:5678:1::1"; address = "fd00:1234:5678:1::1";
interface = "enp1s0"; interface = "enp1s0";
source = "fd00:1234:5678:1::3";
}; };
interfaces.enp1s0.ipv6.addresses = [
{
address = "fd00:1234:5678:1::2";
prefixLength = 64;
}
{
address = "fd00:1234:5678:1::3";
prefixLength = 128;
}
];
interfaces.enp1s0.ipv4.addresses = [ interfaces.enp1s0.ipv4.addresses = [
{ {
address = "192.168.1.2"; address = "192.168.1.2";
@@ -89,7 +101,11 @@ let
with subtest("Test default gateway"): with subtest("Test default gateway"):
client.wait_until_succeeds("ping -c 1 192.168.3.1") client.wait_until_succeeds("ping -c 1 192.168.3.1")
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1") client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
with subtest("Test default addresses"):
client.succeed("ip -4 route show default | grep -q 'src 192.168.1.3'")
client.succeed("ip -6 route show default | grep -q 'src fd00:1234:5678:1::3'")
''; '';
}; };
routeType = { routeType = {
@@ -481,7 +497,73 @@ let
} in fous, "fou4 exists" } in fous, "fou4 exists"
''; '';
}; };
sit = sit-6in4 =
let
node =
{
address4,
remote,
address6,
}:
{
virtualisation.interfaces.enp1s0.vlan = 1;
networking = {
useNetworkd = networkd;
useDHCP = false;
sits.sit = {
inherit remote;
local = address4;
dev = "enp1s0";
};
nftables.enable = true;
firewall.extraInputRules = "meta l4proto 41 accept";
interfaces.enp1s0.ipv4.addresses = lib.mkOverride 0 [
{
address = address4;
prefixLength = 24;
}
];
interfaces.sit.ipv6.addresses = lib.mkOverride 0 [
{
address = address6;
prefixLength = 64;
}
];
};
};
in
{
name = "Sit-6in4";
nodes.client1 = node {
address4 = "192.168.1.1";
remote = "192.168.1.2";
address6 = "fc00::1";
};
nodes.client2 = node {
address4 = "192.168.1.2";
remote = "192.168.1.1";
address6 = "fc00::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 ipv6"):
client1.wait_until_succeeds("ping -c 1 fc00::1")
client1.wait_until_succeeds("ping -c 1 fc00::2")
client2.wait_until_succeeds("ping -c 1 fc00::1")
client2.wait_until_succeeds("ping -c 1 fc00::2")
'';
};
sit-fou =
let let
node = node =
{ {
@@ -516,7 +598,7 @@ let
}; };
in in
{ {
name = "Sit"; name = "Sit-fou";
# note on firewalling: the two nodes are explicitly asymmetric. # note on firewalling: the two nodes are explicitly asymmetric.
# client1 sends SIT packets in UDP, but accepts only proto-41 incoming. # client1 sends SIT packets in UDP, but accepts only proto-41 incoming.
# client2 does the reverse, sending in proto-41 and accepting only UDP incoming. # client2 does the reverse, sending in proto-41 and accepting only UDP incoming.
@@ -531,7 +613,8 @@ let
} args) } args)
{ {
networking = { networking = {
firewall.extraCommands = "iptables -A INPUT -p 41 -j ACCEPT"; nftables.enable = true;
firewall.extraInputRules = "meta l4proto 41 accept";
sits.sit.encapsulation = { sits.sit.encapsulation = {
type = "fou"; type = "fou";
port = 9001; port = 9001;
@@ -576,6 +659,140 @@ let
client2.wait_until_succeeds("ping -c 1 fc00::2") 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 = gre =
let let
node = node =
@@ -1241,6 +1458,7 @@ lib.mapAttrs (lib.const (
attrs attrs
// { // {
name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}"; name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
} }
) )
)) testCases )) testCases