vmix.nix/nixos/network/config.nix

215 lines
9 KiB
Nix

{ config, pkgs, lib, vmixLib, ... }:
with vmixLib.network;
let
vmixCfg = config.vmix;
# creates a /30 network from available range for veth-pair wan interfaces
mkVethIPv4Range = index: availableIPv4Range:
let
vethIPv4RangeLength = 30;
in
(calc.cidr.subnet (vethIPv4RangeLength - (calc.cidr.length availableIPv4Range)) index availableIPv4Range);
namespaceGlobalService = {
"ns.net.vmix@" = {
description = "network namespace %I for vmix";
before = [ "network.target" ];
path = with pkgs; [ iproute2 utillinux ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
PrivateMounts = false;
PrivateNetwork = true;
ExecStart = (pkgs.writeShellScript "ns.net.vmix-start" ''
NAMESPACE="$1.vmix"
ip netns add $NAMESPACE
umount /var/run/netns/$NAMESPACE
mount --bind /proc/self/ns/net /var/run/netns/$NAMESPACE
'') + " %I";
ExecStop = "${pkgs.iproute2}/bin/ip netns del %I.vmix";
};
};
};
mkLanDomainName = networkName: lanName: lanCfg:
if (lanCfg.domain != null) then lanCfg.domain else "${lanName}.${networkName}.vmix";
mkLan = networkName: staticRoutes: lanName: cfg:
let
lanCfg = cfg // { name = lanName; namespace = "${networkName}"; };
lanInterfaceName = "brx-${lanCfg.name}";
lanInterfaceIPAddress = calc.cidr.host 1 lanCfg.ipv4.range;
netmask = calc.cidr.netmask lanCfg.ipv4.range;
networkPrefix = builtins.elemAt (lib.splitString "/" lanCfg.ipv4.range) 1;
dhcpStartAddress =
if (lanCfg.ipv4.dhcp.startAddress != null)
then lanCfg.ipv4.dhcp.startAddress
else (calc.cidr.host 2 lanCfg.ipv4.range);
dhcpEndAddress =
if (lanCfg.ipv4.dhcp.endAddress != null)
then lanCfg.ipv4.dhcp.endAddress
else (calc.cidr.host ((calc.cidr.capacity lanCfg.ipv4.range) - 2) lanCfg.ipv4.range);
createLanInterface = ''
ip link add ${lanInterfaceName} type bridge
ip address add ${lanInterfaceIPAddress}/${networkPrefix} dev ${lanInterfaceName}
ip link set ${lanInterfaceName} up
'';
deleteLanInterface = ''
ip link del ${lanInterfaceName}
'';
lanDomainName = mkLanDomainName networkName lanName lanCfg;
lanDnsmasqConf = ''
# lan ${lanName}
dhcp-range=${lanInterfaceName},${dhcpStartAddress},${dhcpEndAddress},${netmask},12h
domain=${lanDomainName},${lanInterfaceName}
dhcp-option=${lanInterfaceName},option:classless-static-route,${lib.concatStringsSep "," (builtins.map (route: "${route},${lanInterfaceIPAddress}") ([ "0.0.0.0/0" ] ++ (builtins.filter (route: route != lanCfg.ipv4.range) staticRoutes)))}
'' + (lib.optionalString (lanCfg.ipv4.dhcp.dns.nameservers != []) ("dhcp-option=${lanInterfaceName},option:dns-server,${(lib.concatStringsSep "," lanCfg.ipv4.dhcp.dns.nameservers)}\n"));
in
lanCfg // {
createIface = createLanInterface;
deleteIface = deleteLanInterface;
dnsmasqConf = lanDnsmasqConf;
domain = lanDomainName;
};
mkLansService = networkName: wanCfg: lansCfg:
let
dhcpLeaseFile="/tmp/vmix/lans.${networkName}.dhcp.leases";
staticRoutes = [ wanCfg.ipv4.range ] ++ (builtins.map (lanCfg: lanCfg.ipv4.range) (lib.attrValues lansCfg));
lansList = lib.attrValues(lib.mapAttrs (mkLan networkName staticRoutes) lansCfg);
dnsmasqConf = pkgs.writeText "dnsmasq-${networkName}.conf" (''
dhcp-host=*:*:*:*:*:*,id:*
except-interface=lo
dhcp-authoritative
localise-queries
no-hosts
expand-hosts
dhcp-leasefile=${dhcpLeaseFile}
filter-AAAA
address=/host/${calc.cidr.host 1 wanCfg.ipv4.range}
no-resolv
${lib.concatStringsSep "\n" (builtins.map (nameserver: "server=${nameserver}") wanCfg.dns.nameservers)}
'' + (lib.concatMapStrings (lan: lan.dnsmasqConf) lansList)
);
createLansInterfaces = pkgs.writeShellScript "create-lans-${networkName}-vmix" (''
# for dnsmasq temp files
mkdir -p /tmp/vmix
rm -f ${dhcpLeaseFile}
'' + (lib.concatMapStrings (lan: lan.createIface) lansList)
);
deleteLansInterfaces = pkgs.writeShellScript "delete-lans-${networkName}-vmix" (lib.concatMapStrings (lan: lan.deleteIface) lansList);
in
{
"lans.net.vmix@${networkName}" = rec {
bindsTo = [ "ns.net.vmix@${networkName}.service" ];
after = bindsTo;
wantedBy = [ "net.vmix@${networkName}.target" ];
unitConfig.JoinsNamespaceOf = "ns.net.vmix@${networkName}.service";
path = with pkgs; [ iproute2 ];
serviceConfig = {
ExecStartPre = createLansInterfaces;
ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq -d -C ${dnsmasqConf}";
ExecReload = pkgs.writeShellScript "reload-dnsmasq" "kill -HUP $MAINPID";
ExecStopPost = deleteLansInterfaces;
Restart = "on-failure";
RestartSec = "5";
PrivateTmp = true;
ProtectSystem = true;
ProtectHome = true;
PrivateNetwork = true;
};
};
};
mkWanService = networkName: cfg:
let
wanCfg = cfg // { namespace = networkName; };
vethInNSToHost.iface = "vhost";
vethOnHostToNS.iface = "vn-${wanCfg.namespace}";
vethOnHostToNS.ipv4.address = calc.cidr.host 1 wanCfg.ipv4.range;
vethInNSToHost.ipv4.address = calc.cidr.host 2 wanCfg.ipv4.range;
networkPrefix = builtins.elemAt (lib.splitString "/" wanCfg.ipv4.range) 1;
iptablesMark = builtins.toString (ipv4ToInt vethOnHostToNS.ipv4.address);
createWanCommands = ''
ip link add ${vethOnHostToNS.iface} type veth peer name ${vethInNSToHost.iface}
ip link set ${vethInNSToHost.iface} netns ${wanCfg.namespace}.vmix
ip address add ${vethOnHostToNS.ipv4.address}/${networkPrefix} dev ${vethOnHostToNS.iface}
ip netns exec ${wanCfg.namespace}.vmix ip address add ${vethInNSToHost.ipv4.address}/${networkPrefix} dev ${vethInNSToHost.iface}
iptables -A FORWARD -i ${vethOnHostToNS.iface} -j ACCEPT
iptables -A FORWARD -o ${vethOnHostToNS.iface} -j ACCEPT
#iptables -A INPUT -i ${vethOnHostToNS.iface} -j DROP
iptables -t mangle -A PREROUTING -i ${vethOnHostToNS.iface} -j MARK --set-mark ${iptablesMark}
iptables -t nat -A POSTROUTING -m mark --mark ${iptablesMark} -j MASQUERADE
ip link set ${vethOnHostToNS.iface} up
ip netns exec ${wanCfg.namespace}.vmix ip link set ${vethInNSToHost.iface} up
ip netns exec ${wanCfg.namespace}.vmix ip r add default via ${vethOnHostToNS.ipv4.address}
${lib.concatMapStrings (lanRange: "ip r add ${lanRange} via ${vethInNSToHost.ipv4.address} \n") wanCfg.lanRanges}
'';
createWan = pkgs.writeShellScript "create-wan-${wanCfg.namespace}-vmix" createWanCommands;
deleteWan =
let
createdIptablesRules = lib.filter (line: (lib.hasPrefix "iptables" line)) (lib.splitString "\n" createWanCommands);
delIptablesRules = builtins.map (rule: lib.replaceStrings [ "-A" ] [ "-D"] rule) createdIptablesRules;
in
pkgs.writeShellScript "delete-wan-${wanCfg.namespace}-vmix" (''
ip link del ${vethOnHostToNS.iface}
'' + (lib.concatStringsSep "\n" delIptablesRules));
in
{
"wan.net.vmix@${wanCfg.namespace}" = rec {
bindsTo = [ "ns.net.vmix@${wanCfg.namespace}.service" ];
after = bindsTo;
wantedBy = [ "net.vmix@${wanCfg.namespace}.target" ];
path = with pkgs; [ iproute2 iptables ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = createWan;
ExecStop = deleteWan;
};
};
};
mkMacvlanService = networkName: macvlanName: cfg:
{};
mkNetworkServices = networkName: cfg:
let
netCfg = cfg // { name = networkName; };
vethIPv4RangeForWan = mkVethIPv4Range netCfg.index vmixCfg.global.net.wan.ipv4.range;
wanCfg = netCfg.wan // { ipv4.range = vethIPv4RangeForWan; lanRanges = builtins.map (lan: lan.ipv4.range) (lib.attrValues netCfg.lans); };
in
(mkLansService netCfg.name wanCfg netCfg.lans)
// (mkWanService netCfg.name wanCfg)
// (lib.concatMapAttrs (mkMacvlanService netCfg.name) netCfg.bridges.macvlans);
networkNames = builtins.attrNames vmixCfg.networks;
networkServices = pkgs.unstable.lib.mergeAttrsList (lib.imap0 (index: networkName: (mkNetworkServices networkName (vmixCfg.networks.${networkName} // { inherit index;}))) networkNames);
networkTargets = lib.concatMapAttrs (networkName: netCfg: {
"net.vmix@${networkName}" = {
description = "Network ${networkName} for vmix";
bindsTo = [ "ns.net.vmix@${networkName}.service" "lans.net.vmix@${networkName}.service" "wan.net.vmix@${networkName}.service" ];
};
}) vmixCfg.networks;
in
{
config.systemd.services = namespaceGlobalService // networkServices;
config.systemd.targets = networkTargets;
config.boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
}