215 lines
9 KiB
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;
|
|
}
|