{ pkgs, lib, ... }: let ipcalcFn = input: command: let runCmd = pkgs.runCommand "ipcalc-${command}" {} "export `${pkgs.ipcalc}/bin/ipcalc ${input} --${command}`; echo \$${lib.toUpper command} > $out;"; in lib.removeSuffix "\n" (builtins.readFile runCmd); in { namespaceGlobalService = { "ns.vmix@" = { description = "network namespace %I for vmix"; before = [ "network.target" ]; path = with pkgs; [ iproute2 utillinux ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; PrivateNetwork = true; ExecStart = (pkgs.writeShellScript "ns.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"; }; }; }; mkNetworkService = name: cfg: let netCfg = cfg // { inherit name; }; lanInterfaceName = "brx-${netCfg.name}"; lanInterfaceIPAddress = ipcalcFn netCfg.ipv4Range "minaddr"; hostIPAddressOnLan = ipcalcFn netCfg.ipv4Range "maxaddr"; networkAddress = ipcalcFn netCfg.ipv4Range "network"; netmask = ipcalcFn netCfg.ipv4Range "netmask"; networkPrefix = builtins.elemAt (lib.splitString "/" netCfg.ipv4Range) 1; namespace = if netCfg.namespace != null then "${netCfg.namespace}.vmix" else ""; createLanInterface = pkgs.writeShellScript "create-lan-${netCfg.name}-vmix" '' ip link add ${lanInterfaceName} type bridge ip address add ${lanInterfaceIPAddress}/${networkPrefix} dev ${lanInterfaceName} ip link set ${lanInterfaceName} up ''; deleteLanInterface = pkgs.writeShellScript "delete-lan-${netCfg.name}-vmix" "ip link del ${lanInterfaceName}"; lanDomainName = "${netCfg.name}.vmix"; lanDnsmasqConf = pkgs.writeText "dnsmasq-${netCfg.name}.conf" '' listen-address=${lanInterfaceIPAddress} dhcp-range=${netCfg.dhcp.startAddress},${netCfg.dhcp.endAddress},${netmask},12h dhcp-option=3,${hostIPAddressOnLan} interface=${lanInterfaceName} bind-interfaces except-interface=lo dhcp-authoritative domain=${lanDomainName} domain-needed localise-queries no-hosts expand-hosts dhcp-leasefile=/tmp/dhcp.leases '' + lib.concatStringsSep "\n" (lib.optionals (netCfg.dns.upstream != []) ([ "no-resolv" ] ++ (lib.map (dnsServer: "server ${dnsServer}") netCfg.dns.upstream))); vethToHostInNS = "vh-${netCfg.name}"; vethOnHostToNS = "vn-${netCfg.name}"; createWanInterface = pkgs.writeShellScript "create-wan-${netCfg.name}-vmix" '' ip link add ${vethOnHostToNS} type veth peer name ${vethToHostInNS} ip address add ${hostIPAddressOnLan}/${networkPrefix} dev ${vethOnHostToNS} #iptables -A FORWARD -i ${vethOnHostToNS} -j ACCEPT #iptables -A FORWARD -o ${vethOnHostToNS} -j ACCEPT #iptables -A INPUT -i ${vethOnHostToNS} -j DROP iptables -t nat -A POSTROUTING -s ${networkAddress}/${networkPrefix} -j MASQUERADE ip link set ${vethToHostInNS} netns ${netCfg.namespace} ip netns exec ${netCfg.namespace} ip link set ${vethToHostInNS} master ${lanInterfaceName} ip link set ${vethOnHostToNS} up ip netns exec ${netCfg.namespace} ip link set ${vethToHostInNS} up ''; deleteWanInterface = ""; in { "lan.net.vmix@${netCfg.name}" = lib.recursiveUpdate { wantedBy = lib.optional netCfg.startOnBoot [ "net.vmix.target" ]; path = with pkgs; [ iproute2 ]; serviceConfig = { ExecStartPre = createLanInterface; ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq -d -C ${lanDnsmasqConf}"; ExecReload = pkgs.writeShellScript "reload-dnsmasq" "kill -HUP $MAINPID"; ExecStopPost = deleteLanInterface; Restart = "on-failure"; RestartSec = "5"; PrivateTmp = true; ProtectSystem = true; ProtectHome = true; }; } (lib.optionalAttrs (netCfg.namespace != null) rec { bindsTo = [ "ns.vmix@${netCfg.namespace}.service" ]; after = bindsTo; unitConfig.JoinsNamespaceOf = "ns.vmix@${netCfg.namespace}.service"; serviceConfig.PrivateNetwork = true; }); "wan.net.vmix@${netCfg.name}" = rec { wantedBy = lib.optional netCfg.startOnBoot [ "net.vmix.target" ]; path = with pkgs; [ iproute2 iptables ]; bindsTo = [ "lan.net.vmix@${netCfg.name}.service" ]; after = bindsTo; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = createWanInterface; ExecStop = deleteWanInterface; }; }; }; }