network options working for basic functionality

This commit is contained in:
Git Sagar 2024-06-03 20:36:30 -03:00
parent e4cdc2cae5
commit 392375b046
9 changed files with 348 additions and 221 deletions

159
nixos/network/config.nix Normal file
View file

@ -0,0 +1,159 @@
{ 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;
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";
};
};
};
mkLanService = networkName: 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;
createLanInterface = pkgs.writeShellScript "create-lan-${lanCfg.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-${lanCfg.name}-vmix" "ip link del ${lanInterfaceName}";
lanDomainName = "${lanCfg.name}.vmix";
lanDnsmasqConf = pkgs.writeText "dnsmasq-${lanCfg.name}.conf" (''
listen-address=${lanInterfaceIPAddress}
dhcp-range=${lanCfg.ipv4.dhcp.startAddress},${lanCfg.ipv4.dhcp.endAddress},${netmask},12h
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 (lanCfg.ipv4.dns.upstream != []) ([ "no-resolv" ] ++ (builtins.map (dnsServer: "server=${dnsServer}") lanCfg.ipv4.dns.upstream)))
);
in
{
"lan.net.vmix@${lanCfg.name}.${lanCfg.namespace}" = rec {
bindsTo = [ "ns.net.vmix@${lanCfg.namespace}.service" ];
after = bindsTo;
wantedBy = [ "net.vmix@${lanCfg.namespace}.target" ];
unitConfig.JoinsNamespaceOf = "ns.net.vmix@${lanCfg.namespace}.service";
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;
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}
'';
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; };
in
(lib.concatMapAttrs (mkLanService netCfg.name) netCfg.lans)
// (mkWanService netCfg.name (netCfg.wan // { ipv4.range = (mkVethIPv4Range netCfg.index vmixCfg.global.net.wan.ipv4.range); }))
// (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);
in
{
config.systemd.services = namespaceGlobalService // networkServices;
}

16
nixos/network/default.nix Normal file
View file

@ -0,0 +1,16 @@
args@{ config, pkgs, lib, vmixLib, ... }:
with lib;
{
options.vmix.global.net.wan.ipv4.range = lib.mkOption {
type = types.strMatching vmixLib.network.regex.cidr4;
default = "172.27.72.0/24"; # enough to create 64x /30 networks for veth pairs used for wan interfaces
};
options.vmix.networks = lib.mkOption {
type = types.attrsOf
(types.submodule (import ./options.nix args));
default = { };
};
imports = [ (import ./config.nix args) ];
}

156
nixos/network/options.nix Normal file
View file

@ -0,0 +1,156 @@
{ config, pkgs, lib, vmixLib, ... }:
with lib;
with vmixLib.network;
{
options = {
bridges.macvlans = mkOption {
type = types.attrsOf (types.submodule {
options = {
uplink.iface = mkOption {
type = types.str;
};
uplink.namespace = mkOption {
type = types.nullOr types.str;
default = null;
};
namespace = mkOption {
type = types.nullOr types.str;
default = null;
};
ipv4.static.address = mkOption {
type = types.nullOr (types.strMatching regex.ipOrCidr4);
default = null;
};
ipv4.static.gateway = mkOption {
type = types.nullOr (types.strMatching regex.ipv4);
default = null;
};
ipv4.dhcp.client = mkOption {
type = types.bool;
default = false;
};
ipv4.dhcp.gateway = mkOption {
type = types.bool;
default = false;
};
};
});
};
bridges.macvtaps = mkOption {
type = types.attrsOf (types.submodule {
options = {
uplink.iface = mkOption {
type = types.str;
};
uplink.namespace = mkOption {
type = types.nullOr types.str;
default = null;
};
many = mkOption {
type = types.bool;
default = true;
};
};
});
};
wan = {
enable = mkOption {
type = types.bool;
default = true;
};
host.wan.enable = mkOption {
type = types.bool;
default = true;
};
host.wan.masquerade = mkOption {
type = types.bool;
default = true;
};
host.lan.enable = mkOption {
type = types.bool;
default = true;
};
host.lan.masquerade = mkOption {
type = types.bool;
default = true;
};
host.self.enable = mkOption {
type = types.bool;
default = true;
};
};
lans = mkOption {
type = types.attrsOf (types.submodule {
options.ipv4 = {
range = mkOption {
type = types.strMatching regex.cidr4;
description = "IPv4 Range in x.x.x.x/y format to be assigned to the network.";
};
address = mkOption {
type = types.nullOr (types.strMatching regex.ipv4);
default = null;
description = "IPv4 address to attach to the bridge interface of this Lan.";
};
dhcp.enable = mkOption {
type = types.bool;
default = true;
description = "Whether to start a DHCP server within this network.";
};
dhcp.startAddress = mkOption {
type = types.nullOr (types.strMatching regex.ipv4);
description = "Starting IP Address for DHCP clients.";
};
dhcp.endAddress = mkOption {
type = types.nullOr (types.strMatching regex.ipv4);
description = "Ending IP Address for DHCP clients.";
};
dns.upstream = mkOption {
type = types.listOf (types.strMatching regex.ipv4);
default = [];
description = "List of IP Addresses of DNS servers to use as upstream DNS servers in the DHCP/DNS server. If left empty, it will use host's DNS servers";
};
dns.useHostResolvConf = mkOption {
type = types.bool;
default = true;
description = "Whether to use host's /etc/resolv.conf for upstream DNS queries.";
};
dns.zonefiles = mkOption {
default = null;
description = "Additional zonefiles to add for the DNS server";
};
};
});
};
# routes.internal.add = mkOption {
# description = "Additional routes to add on the internal network";
# };
# routes.host.add = mkOption {
# description = "Addtional routes to add on the host's network namespace";
# };
};
}