2023-11-26 01:29:44 +00:00
|
|
|
{ lib, config, vpns, ... }:
|
2022-10-16 19:07:16 +01:00
|
|
|
let
|
2023-11-26 01:29:44 +00:00
|
|
|
inherit (builtins) any attrValues;
|
|
|
|
inherit (lib) optionalString mapAttrsToList concatStringsSep concatMapStringsSep filterAttrs mkIf mkMerge;
|
|
|
|
inherit (lib.my) isIPv6 mkOpt';
|
2022-10-16 19:07:16 +01:00
|
|
|
|
|
|
|
vxlanPort = 4789;
|
|
|
|
|
|
|
|
selfName = config.system.name;
|
|
|
|
memberMeshes = filterAttrs (_: c: c.peers ? "${selfName}") vpns.l2;
|
|
|
|
|
|
|
|
info = mesh: {
|
|
|
|
ownAddr = mesh.peers."${selfName}".addr;
|
|
|
|
otherPeers = filterAttrs (n: _: n != selfName) mesh.peers;
|
|
|
|
};
|
|
|
|
|
|
|
|
mkNetConfig = name: mesh: with info mesh; {
|
|
|
|
netdevs."30-l2mesh-${name}" = {
|
|
|
|
netdevConfig = {
|
|
|
|
Name = mesh.interface;
|
|
|
|
Kind = "vxlan";
|
|
|
|
};
|
|
|
|
vxlanConfig = {
|
|
|
|
VNI = mesh.vni;
|
|
|
|
Local = ownAddr;
|
|
|
|
MacLearning = true;
|
|
|
|
DestinationPort = vxlanPort;
|
2023-11-26 01:29:44 +00:00
|
|
|
PortRange = "${toString vxlanPort}-${toString (vxlanPort + 1)}";
|
2022-10-16 19:07:16 +01:00
|
|
|
Independent = true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
networks."90-l2mesh-${name}" = {
|
|
|
|
matchConfig.Name = mesh.interface;
|
2023-11-26 01:29:44 +00:00
|
|
|
linkConfig.MTUBytes =
|
|
|
|
let
|
|
|
|
espOverhead =
|
|
|
|
if (!mesh.security.enable) then 0
|
|
|
|
else
|
2024-03-23 12:04:07 +00:00
|
|
|
# UDP encap + SPI + seq + IV + pad / header + ICV
|
|
|
|
(if mesh.udpEncapsulation then 8 else 0) + 4 + 4 + (if mesh.security.encrypt then 8 else 0) + 2 + 16;
|
2023-11-26 01:29:44 +00:00
|
|
|
# UDP + VXLAN + Ethernet + L3 (IPv4/IPv6)
|
|
|
|
overhead = espOverhead + 8 + 8 + 14 + mesh.l3Overhead;
|
|
|
|
in
|
|
|
|
toString (mesh.baseMTU - overhead);
|
2022-10-16 19:07:16 +01:00
|
|
|
|
2023-11-26 01:29:44 +00:00
|
|
|
bridgeFDBs = mapAttrsToList (n: peer: {
|
2024-11-30 17:45:59 +00:00
|
|
|
MACAddress = "00:00:00:00:00:00";
|
|
|
|
Destination = peer.addr;
|
2023-11-26 01:29:44 +00:00
|
|
|
}) otherPeers;
|
|
|
|
};
|
2022-10-16 19:07:16 +01:00
|
|
|
};
|
|
|
|
|
2023-11-26 01:29:44 +00:00
|
|
|
vxlanAllow = vni: "udp dport ${toString vxlanPort} @th,96,24 ${toString vni} accept";
|
2022-10-16 19:07:16 +01:00
|
|
|
mkFirewallConfig = name: mesh: with info mesh;
|
|
|
|
let
|
|
|
|
netProto = if (isIPv6 ownAddr) then "ip6" else "ip";
|
|
|
|
in
|
|
|
|
''
|
|
|
|
table inet filter {
|
|
|
|
chain l2mesh-${name} {
|
2023-11-26 01:29:44 +00:00
|
|
|
${optionalString mesh.security.enable ''
|
|
|
|
udp dport isakmp accept
|
2024-03-23 12:04:07 +00:00
|
|
|
${if mesh.udpEncapsulation then ''
|
|
|
|
udp dport ipsec-nat-t accept
|
|
|
|
'' else ''
|
|
|
|
meta l4proto esp accept
|
|
|
|
''}
|
2023-11-26 01:29:44 +00:00
|
|
|
''}
|
|
|
|
${optionalString (!mesh.security.enable) (vxlanAllow mesh.vni)}
|
2022-10-16 19:07:16 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
chain input {
|
|
|
|
${netProto} daddr ${ownAddr} ${netProto} saddr { ${concatStringsSep ", " (mapAttrsToList (_: p: p.addr) otherPeers)} } jump l2mesh-${name}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
'';
|
2023-11-26 01:29:44 +00:00
|
|
|
|
|
|
|
mkLibreswanConfig = name: mesh: with info mesh; {
|
|
|
|
enable = true;
|
|
|
|
connections = mkMerge (mapAttrsToList
|
|
|
|
(pName: peer: {
|
|
|
|
"l2mesh-${name}-${pName}" = ''
|
|
|
|
keyexchange=ike
|
|
|
|
hostaddrfamily=ipv${if mesh.ipv6 then "6" else "4"}
|
|
|
|
type=transport
|
|
|
|
|
|
|
|
left=${ownAddr}
|
|
|
|
leftprotoport=udp/${toString vxlanPort}
|
|
|
|
right=${peer.addr}
|
|
|
|
rightprotoport=udp/${toString vxlanPort}
|
|
|
|
rightupdown=
|
|
|
|
|
|
|
|
auto=start
|
|
|
|
authby=secret
|
|
|
|
phase2=esp
|
|
|
|
esp=${if mesh.security.encrypt then "aes_gcm256" else "null-sha256"}
|
|
|
|
ikev2=yes
|
|
|
|
modecfgpull=no
|
2024-03-23 12:04:07 +00:00
|
|
|
encapsulation=${if mesh.udpEncapsulation then "yes" else "no"}
|
2023-11-26 01:29:44 +00:00
|
|
|
'';
|
|
|
|
})
|
|
|
|
otherPeers);
|
|
|
|
};
|
|
|
|
genSecrets = name: mesh: with info mesh; concatMapStringsSep "\n" (p: ''
|
|
|
|
echo "${ownAddr} ${p.addr} : PSK \"$(< "${config.my.vpns.l2.pskFiles.${name}}")\"" >> /run/l2mesh.secrets
|
|
|
|
'') (attrValues otherPeers);
|
|
|
|
anySecurity = any (c: c.security.enable) (attrValues memberMeshes);
|
2022-10-16 19:07:16 +01:00
|
|
|
in
|
|
|
|
{
|
2023-11-26 01:29:44 +00:00
|
|
|
options = {
|
|
|
|
my.vpns.l2 = with lib.types; {
|
|
|
|
pskFiles = mkOpt' (attrsOf str) { } "PSK files for secured meshes.";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-10-16 19:07:16 +01:00
|
|
|
config = {
|
|
|
|
systemd.network = mkMerge (mapAttrsToList mkNetConfig memberMeshes);
|
2023-11-26 01:29:44 +00:00
|
|
|
|
|
|
|
environment.etc."ipsec.d/l2mesh.secrets" = mkIf anySecurity {
|
|
|
|
source = "/run/l2mesh.secrets";
|
|
|
|
};
|
|
|
|
systemd.services.ipsec = mkIf anySecurity {
|
|
|
|
preStart = ''
|
|
|
|
oldUmask="$(umask)"
|
|
|
|
umask 006
|
|
|
|
|
|
|
|
> /run/l2mesh.secrets
|
|
|
|
${concatStringsSep "\n" (mapAttrsToList genSecrets memberMeshes)}
|
|
|
|
|
|
|
|
umask "$oldUmask"
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
services.libreswan = mkMerge (mapAttrsToList mkLibreswanConfig (filterAttrs (_: c: c.security.enable) memberMeshes));
|
2022-10-16 19:07:16 +01:00
|
|
|
my.firewall.extraRules = concatStringsSep "\n" (mapAttrsToList mkFirewallConfig (filterAttrs (_: c: c.firewall) memberMeshes));
|
|
|
|
};
|
|
|
|
}
|