WIP: network module
This commit is contained in:
parent
ade7b261ae
commit
e4cdc2cae5
4 changed files with 354 additions and 0 deletions
29
nixos/default.nix
Normal file
29
nixos/default.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
vmixLib = import ./../lib {inherit pkgs lib; };
|
||||
vmixCfg = config.vmix;
|
||||
vmixNetwork = import ./modules/network.nix { inherit config pkgs lib ;};
|
||||
vmixNetworkFunctions = import ./functions/network.nix { inherit pkgs lib ;};
|
||||
#vmixVM = import ./modules/network.nix { inherit config pkgs lib ;};
|
||||
in
|
||||
{
|
||||
options.vmix = {
|
||||
networks = lib.mkOption {
|
||||
type = types.attrsOf
|
||||
(types.submodule vmixNetwork);
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
with vmixNetworkFunctions;
|
||||
#with vmixVMFunctions;
|
||||
let
|
||||
networkServices = lib.concatMapAttrs mkNetworkService vmixCfg.networks;
|
||||
#vmServices = lib.concatMapAttrs mkVMService vmixCfg.vms;
|
||||
in
|
||||
{
|
||||
systemd.services = namespaceGlobalService // networkServices;
|
||||
};
|
||||
}
|
||||
121
nixos/functions/network.nix
Normal file
121
nixos/functions/network.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
{ 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
79
nixos/modules/network.nix
Normal file
79
nixos/modules/network.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
ipv4Regex =
|
||||
let
|
||||
compRegex = "(25[0-5]|(2[0-4]|10|1?[1-9])?[0-9])";
|
||||
in
|
||||
"(${compRegex}\\.){3}${compRegex}";
|
||||
|
||||
cidr4Regex = "${ipv4Regex}/(3[0-2]|[1-2]?[0-9])";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
startOnBoot = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to start this network on boot regardless if a VM is needing this network.";
|
||||
};
|
||||
|
||||
namespace = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Linux network namespace under which this network is created. If not declared, it will create under hosts network namespace.";
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type =types.enum [ "user" "nat" "natWANOnly" "routed" "routedWANOnly" "isolated" "bridge" ];
|
||||
description = ''
|
||||
Network types.
|
||||
- "user" is qemu slirp user network, which can be shared across multiple VMs if needed
|
||||
- "nat" is a NAT with an internal network, with a DHCP/DNS server, a domainsearch name and masqueraded access to the host's network
|
||||
- "natWANOnly" just like nat but no access to the host itself, or other networks on the host, while allowing WAN access through the hosts default gateway
|
||||
- "routed" is an internal network, with a DHCP/DNS server, a domainsearch name and routed inbound and outbound access to the host's network
|
||||
- "routedWANOnly" just like routed, but no access to the host itself, or other networks on the host, while allowing WAN inbound and outbound access through the hosts default gateway
|
||||
- "isolated" creates an internal network, a DHCP/DNS server, a domainsearch name with no access to host's network or WAN
|
||||
- "bridge" is a bridge with another network or a host's network interface
|
||||
'';
|
||||
};
|
||||
|
||||
ipv4Range = mkOption {
|
||||
type = types.strMatching cidr4Regex;
|
||||
description = "IPv4 Range in x.x.x.x/y format to be assigned to the network.";
|
||||
};
|
||||
|
||||
dhcp.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to start a DHCP server within this network.";
|
||||
};
|
||||
|
||||
dhcp.startAddress = mkOption {
|
||||
type = types.strMatching ipv4Regex;
|
||||
description = "Starting IP Address for DHCP clients.";
|
||||
};
|
||||
|
||||
dhcp.endAddress = mkOption {
|
||||
type = types.strMatching ipv4Regex;
|
||||
description = "Ending IP Address for DHCP clients.";
|
||||
};
|
||||
|
||||
dns.upstream = mkOption {
|
||||
type = types.listOf (types.strMatching ipv4Regex);
|
||||
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.zonefiles = mkOption {
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
125
nixos/modules/vm.nix
Normal file
125
nixos/modules/vm.nix
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
networkModule = import ./network.nix { inherit config pkgs lib ;};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
cpu =
|
||||
mkOption {
|
||||
type = types.str;
|
||||
default = "host";
|
||||
description = ''
|
||||
The CPU model to emulate.
|
||||
'';
|
||||
};
|
||||
|
||||
smp =
|
||||
mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 1;
|
||||
description = ''
|
||||
Specify the number of cores the guest is permitted to use.
|
||||
The number can be higher than the available cores on the
|
||||
host system.
|
||||
'';
|
||||
};
|
||||
|
||||
memSize =
|
||||
mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 1024;
|
||||
description = ''
|
||||
The memory size in megabytes of the virtual machine.
|
||||
'';
|
||||
};
|
||||
|
||||
diskSize =
|
||||
mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
The disk size in qemu params.
|
||||
'';
|
||||
};
|
||||
|
||||
osImage =
|
||||
mkOption {
|
||||
type = types.package;
|
||||
description = ''
|
||||
OS image stored in the nix store, either built with vmixLib.images or fetched from the internet.
|
||||
'';
|
||||
};
|
||||
|
||||
persistantOverlayImagePath =
|
||||
mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path where the persistant overlay is stored. If path is non-existant, a qcow2 image backed by the original osImage is created.
|
||||
'';
|
||||
};
|
||||
|
||||
startOnBoot =
|
||||
mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to start the VM automatically on system boot.
|
||||
'';
|
||||
default = true;
|
||||
};
|
||||
|
||||
sharedDirectories =
|
||||
lib.mkOption {
|
||||
type = types.attrsOf
|
||||
(types.submodule {
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = types.str;
|
||||
description = "The path of the directory to share, can be a shell variable";
|
||||
};
|
||||
target = lib.mkOption {
|
||||
type = types.str;
|
||||
description = "The mount point of the directory inside the virtual machine";
|
||||
};
|
||||
};
|
||||
});
|
||||
example = {
|
||||
my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
||||
networks = lib.mkOption {
|
||||
type = types.attrsOf
|
||||
(types.submodule networkModule);
|
||||
example = {
|
||||
my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
functions.mkVMService = name: cfg:
|
||||
let
|
||||
vmCfg = cfg // { inherit name };
|
||||
mkDiskImageFrom
|
||||
diskImage =
|
||||
qemuArgs = ''
|
||||
--name ${vmCfg.name} \
|
||||
--cpu ${vmCfg.cpu} \
|
||||
--smp ${vmCfg.cores} \
|
||||
--
|
||||
'';
|
||||
in
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
//storage
|
||||
//
|
||||
//networking - DNS, VPNs, Bridges, NATs, isolation, connecting with others,host
|
||||
//run adhoc
|
||||
//run and die
|
||||
//
|
||||
Loading…
Add table
Add a link
Reference in a new issue