Personal Nix setup

Add dynamic IP calculation for networking

Changed files
+401 -50
lib
modules
+320
lib/ipv4.nix
···
+
/* See: https://github.com/djacu/nixpkgs/blob/adb22cf/lib/network.nix */
+
{ lib, ... }:
+
+
let
+
/*
+
Converts an IP address from a list of ints to a string.
+
+
Type: prettyIp :: [ Int ] -> String
+
+
Examples:
+
prettyIp [ 192 168 70 9 ]
+
=> "192.168.70.9"
+
*/
+
prettyIp = addr:
+
lib.concatStringsSep "." (builtins.map builtins.toString addr);
+
+
/*
+
Given a bit mask, return the associated subnet mask.
+
+
Type: bitMaskToSubnetMask :: Int -> [ Int ]
+
+
Examples:
+
bitMaskToSubnetMask 15
+
=> [ 255 254 0 0 ]
+
bitMaskToSubnetMask 24
+
=> [ 255 255 255 0 ]
+
*/
+
bitMaskToSubnetMask = bitMask: let
+
numOctets = 4;
+
octetBits = 8;
+
octetMin = 0;
+
octetMax = 255;
+
# How many initial parts of the mask are full (=255)
+
fullParts = bitMask / octetBits;
+
in
+
lib.genList (
+
idx:
+
# Fill up initial full parts
+
if idx < fullParts
+
then octetMax
+
# If we're above the first non-full part, fill with 0
+
else if fullParts < idx
+
then octetMin
+
# First non-full part generation
+
else _genPartialMask (lib.mod bitMask octetBits)
+
)
+
numOctets;
+
+
/*
+
Generate a the partial portion of a subnet mask.
+
+
Type: _genPartialMask :: Int -> Int
+
+
Examples:
+
_genPartialMask 0
+
=> 0
+
_genPartialMask 1
+
=> 128
+
_genPartialMask 2
+
=> 192
+
_genPartialMask 3
+
=> 224
+
_genPartialMask 4
+
=> 240
+
_genPartialMask 5
+
=> 248
+
_genPartialMask 6
+
=> 252
+
_genPartialMask 7
+
=> 254
+
*/
+
_genPartialMask = n:
+
if n == 0
+
then 0
+
else _genPartialMask (n - 1) / 2 + 128;
+
+
/*
+
Given a subnet mask, return the associated bit mask.
+
+
Type: subnetMaskToBitMask :: [ Int ] -> Int
+
+
Examples:
+
subnetMaskToBitMask [ 255 254 0 0 ]
+
=> 15
+
subnetMaskToBitMask [ 255 255 255 0 ]
+
=> 24
+
*/
+
subnetMaskToBitMask = subnetMask: let
+
partialBits = octet:
+
if octet == 0
+
then 0
+
else (lib.mod octet 2) + partialBits (octet / 2);
+
in
+
builtins.foldl'
+
(x: y: x + y)
+
0
+
(builtins.map partialBits subnetMask);
+
+
/*
+
Given a CIDR, return the IP Address.
+
+
Type: cidrToIpAddress :: String -> [ Int ]
+
+
Examples:
+
cidrToIpAddress "192.168.70.9/15"
+
=> [ 192 168 70 9 ]
+
*/
+
cidrToIpAddress = cidr: let
+
splitParts = lib.splitString "/" cidr;
+
addr = lib.elemAt splitParts 0;
+
parsed =
+
builtins.map
+
lib.toInt
+
(builtins.match "([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)" addr);
+
checkBounds = octet:
+
(octet >= 0) && (octet <= 255);
+
in
+
if (builtins.all checkBounds parsed)
+
then parsed
+
else builtins.throw "IP ${prettyIp addr} has out of bounds octet(s)";
+
+
/*
+
Given a CIDR, return the bitmask.
+
+
Type: cidrToBitMask :: String -> Int
+
+
Examples:
+
cidrToBitMask "192.168.70.9/15"
+
=> 15
+
*/
+
cidrToBitMask = cidr: let
+
splitParts = lib.splitString "/" cidr;
+
mask = lib.toInt (lib.elemAt splitParts 1);
+
checkBounds = mask:
+
(mask >= 0) && (mask <= 32);
+
in
+
if (checkBounds mask)
+
then mask
+
else builtins.throw "Bitmask ${builtins.toString mask} is invalid.";
+
+
/*
+
Given a CIDR, return the associated subnet mask.
+
+
Type: cidrToSubnetMask :: String -> [ Int ]
+
+
Examples:
+
cidrToSubnetMask "192.168.70.9/15"
+
=> [ 255 254 0 0 ]
+
*/
+
cidrToSubnetMask = cidr:
+
bitMaskToSubnetMask (cidrToBitMask cidr);
+
+
/*
+
Given a CIDR, return the associated network ID.
+
+
Type: cidrToNetworkId :: String -> [ Int ]
+
+
Examples:
+
cidrToNetworkId "192.168.70.9/15"
+
=> [ 192 168 0 0 ]
+
*/
+
cidrToNetworkId = cidr: let
+
ip = cidrToIpAddress cidr;
+
subnetMask = cidrToSubnetMask cidr;
+
in
+
lib.zipListsWith lib.bitAnd ip subnetMask;
+
+
/*
+
Given a CIDR, return the associated first usable IP address.
+
+
Type: cidrToFirstUsableIp :: String -> [ Int ]
+
+
Examples:
+
cidrToFirstUsableIp "192.168.70.9/15"
+
=> [ 192 168 0 1 ]
+
*/
+
cidrToFirstUsableIp = cidr: let
+
networkId = cidrToNetworkId cidr;
+
in
+
incrementIp networkId 1;
+
+
/*
+
Given a CIDR, return the associated broadcast address.
+
+
Type: cidrToBroadcastAddress :: String -> [ Int ]
+
+
Examples:
+
cidrToBroadcastAddress "192.168.70.9/15"
+
=> [ 192 169 255 255 ]
+
*/
+
cidrToBroadcastAddress = cidr: let
+
subnetMask = cidrToSubnetMask cidr;
+
networkId = cidrToNetworkId cidr;
+
in
+
getBroadcastAddress networkId subnetMask;
+
+
/*
+
Given a network ID and subnet mask, return the associated broadcast address.
+
+
Type: getBroadcastAddress :: [ Int ] -> [ Int ] -> [ Int ]
+
+
Examples:
+
getBroadcastAddress [ 192 168 0 0 ] [ 255 254 0 0 ]
+
=> [ 192 169 255 255 ]
+
*/
+
getBroadcastAddress = networkId: subnetMask:
+
lib.zipListsWith (nid: mask: 255 - mask + nid) networkId subnetMask;
+
+
/*
+
Given a CIDR, return the associated last usable IP address.
+
+
Type: cidrToLastUsableIp :: String -> [ Int ]
+
+
Examples:
+
cidrToLastUsableIp "192.168.70.9/15"
+
=> [ 192 169 255 254 ]
+
*/
+
cidrToLastUsableIp = cidr: let
+
broadcast = cidrToBroadcastAddress cidr;
+
in
+
incrementIp broadcast (-1);
+
+
/*
+
Increment the last octet of a given IP address.
+
+
Type: incrementIp :: [ Int ] -> Int -> [ Int ]
+
+
Examples:
+
incrementIp [ 192 168 70 9 ] 3
+
=> [ 192 168 70 12 ]
+
incrementIp [ 192 168 70 9 ] (-2)
+
=> [ 192 168 70 7 ]
+
*/
+
incrementIp = addr: offset: let
+
lastOctet = lib.last addr;
+
firstThree = lib.init addr;
+
in
+
firstThree ++ [(lastOctet + offset)];
+
+
/*
+
Given an IP address and bit mask, return the associated CIDR.
+
+
Type: ipAndBitMaskToCidr :: [ Int ] -> Int -> String
+
+
Examples:
+
ipAndBitMaskToCidr [ 192 168 70 9 ] 15
+
=> "192.168.70.9/15"
+
*/
+
ipAndBitMaskToCidr = addr: bitMask:
+
lib.concatStringsSep "/"
+
[
+
(prettyIp addr)
+
(builtins.toString bitMask)
+
];
+
+
/*
+
Given an IP address and subnet mask, return the associated CIDR.
+
+
Type: ipAndSubnetMaskToCidr :: [ Int ] -> Int -> String
+
+
Examples:
+
ipAndSubnetMaskToCidr [ 192 168 70 9 ] [ 255 254 0 0 ]
+
=> "192.168.70.9/15"
+
*/
+
ipAndSubnetMaskToCidr = addr: subnetMask:
+
ipAndBitMaskToCidr addr (subnetMaskToBitMask subnetMask);
+
+
/*
+
Given a CIDR, return an attribute set of:
+
the IP Address,
+
the bit mask,
+
the first usable IP address,
+
the last usable IP address,
+
the network ID,
+
the subnet mask,
+
the broadcast address.
+
+
Type: getNetworkProperties :: str -> attrset
+
+
Examples:
+
getNetworkProperties "192.168.70.9/15"
+
=> {
+
bitMask = 15;
+
broadcast = "192.169.255.255";
+
firstUsableIp = "192.168.0.1";
+
ipAddress = "192.168.70.9";
+
lastUsableIp = "192.169.255.254";
+
networkId = "192.168.0.0";
+
subnetMask = "255.254.0.0";
+
}
+
*/
+
getNetworkProperties = cidr: let
+
ipAddress = prettyIp (cidrToIpAddress cidr);
+
bitMask = cidrToBitMask cidr;
+
firstUsableIp = prettyIp (cidrToFirstUsableIp cidr);
+
lastUsableIp = prettyIp (cidrToLastUsableIp cidr);
+
networkId = prettyIp (cidrToNetworkId cidr);
+
subnetMask = prettyIp (cidrToSubnetMask cidr);
+
broadcast = prettyIp (cidrToBroadcastAddress cidr);
+
in {inherit ipAddress bitMask firstUsableIp lastUsableIp networkId subnetMask broadcast;};
+
in {
+
ipv4 = {
+
inherit
+
prettyIp
+
incrementIp
+
bitMaskToSubnetMask
+
subnetMaskToBitMask
+
ipAndBitMaskToCidr
+
ipAndSubnetMaskToCidr
+
cidrToIpAddress
+
cidrToBitMask
+
cidrToFirstUsableIp
+
cidrToLastUsableIp
+
cidrToNetworkId
+
cidrToSubnetMask
+
cidrToBroadcastAddress
+
getNetworkProperties
+
;
+
};
+
}
+45 -29
modules/router/dnsmasq.nix
···
with lib;
let
+
inherit (import ../../lib/ipv4.nix inputs) ipv4;
+
cfg = config.modules.router;
+
intern = cfg.interfaces.internal;
+
extern = cfg.interfaces.external;
leaseType = types.submodule {
options = {
···
else [ "1.1.1.1" "1.0.0.1" ];
dhcpHost = builtins.map (lease: "${lease.macAddress},${lease.ipAddress}") cfg.dnsmasq.leases;
+
+
dhcpIPv4Range = let
+
subnetMask = ipv4.prettyIp (ipv4.cidrToSubnetMask intern.cidr);
+
firstIP = ipv4.prettyIp (ipv4.incrementIp (ipv4.cidrToFirstUsableIp intern.cidr) 1);
+
lastIP = ipv4.prettyIp (ipv4.cidrToLastUsableIp intern.cidr);
+
in "${firstIP}, ${lastIP}, ${subnetMask}, 12h";
+
+
localDomains = builtins.map (host: "/${host}/${cfg.address}") cfg.dnsmasq.localDomains;
in {
options.modules.router = {
dnsmasq = {
···
type = types.bool;
};
-
leases = lib.mkOption {
+
leases = mkOption {
default = [];
-
type = lib.types.listOf leaseType;
+
type = types.listOf leaseType;
description = "List of reserved IP address leases";
};
+
+
localDomains = lib.mkOption {
+
default = [];
+
type = types.listOf types.str;
+
};
};
};
config = mkIf cfg.dnsmasq.enable {
+
modules.router.dnsmasq.localDomains = [
+
"time.apple.com"
+
"time1.apple.com"
+
"time2.apple.com"
+
"time3.apple.com"
+
"time4.apple.com"
+
"time5.apple.com"
+
"time6.apple.com"
+
"time7.apple.com"
+
"time.euro.apple.com"
+
"time.windows.com"
+
"0.android.pool.ntp.org"
+
"1.android.pool.ntp.org"
+
"2.android.pool.ntp.org"
+
"3.android.pool.ntp.org"
+
];
+
networking.nameservers = [ "127.0.0.1" ];
services.resolved.extraConfig = mkDefault ''
···
addn-hosts = "/etc/hosts";
dhcp-range = [
-
"10.0.0.2, 10.0.0.255, 255.255.255.0, 12h"
-
"tag:${cfg.interfaces.internal}, ::1, constructor:${cfg.interfaces.internal}, ra-names, slaac, 12h"
+
dhcpIPv4Range
+
"tag:${intern.name}, ::1, constructor:${intern.name}, ra-names, slaac, 12h"
];
dhcp-option = [
"option6:information-refresh-time, 6h"
-
"option:router,10.0.0.1"
-
"ra-param=${cfg.interfaces.internal},high,0,0"
-
];
-
-
dhcp-option = mkIf cfg.timeserver.enable [
-
"option:ntp-server,10.0.0.1"
-
];
+
"option:router,${cfg.address}"
+
"ra-param=${intern.name},high,0,0"
+
] ++ (
+
if cfg.timeserver.enable then [ "option:ntp-server,${cfg.address}" ] else []
+
);
dhcp-host = dhcpHost;
# listen only on intern0 by excluding extern0
-
except-interface = cfg.interfaces.external;
+
except-interface = extern.name;
# set the DHCP server to authoritative and rapic commit mode
dhcp-authoritative = true;
···
# Detect attempts by Verisign to send queries to unregistered hosts
bogus-nxdomain = "64.94.110.11";
-
address = [
-
"/cola.fable-pancake.ts.net/10.0.0.1"
-
"/time.apple.com/10.0.0.1"
-
"/time1.apple.com/10.0.0.1"
-
"/time2.apple.com/10.0.0.1"
-
"/time3.apple.com/10.0.0.1"
-
"/time4.apple.com/10.0.0.1"
-
"/time5.apple.com/10.0.0.1"
-
"/time6.apple.com/10.0.0.1"
-
"/time7.apple.com/10.0.0.1"
-
"/time.euro.apple.com/10.0.0.1"
-
"/time.windows.com/10.0.0.1"
-
"/0.android.pool.ntp.org/10.0.0.1"
-
"/1.android.pool.ntp.org/10.0.0.1"
-
"/2.android.pool.ntp.org/10.0.0.1"
-
"/3.android.pool.ntp.org/10.0.0.1"
-
];
+
address = localDomains;
};
};
};
+26 -16
modules/router/network.nix
···
-
{ lib, config, ... }:
+
{ lib, config, ... } @ inputs:
with lib;
let
+
inherit (import ../../lib/ipv4.nix inputs) ipv4;
+
cfg = config.modules.router;
interfaceType = types.submodule {
···
type = types.str;
example = "00:00:00:00:00:00";
};
+
cidr = mkOption {
+
type = types.str;
+
default = "0.0.0.0/0";
+
example = "10.0.0.1/24";
+
};
};
};
-
extern0 = cfg.interfaces.external.name;
-
extern0MAC = cfg.interfaces.external.macAddress;
-
intern0 = cfg.interfaces.internal.name;
-
intern0MAC = cfg.interfaces.internal.macAddress;
+
extern = cfg.interfaces.external;
+
intern = cfg.interfaces.internal;
in {
options.modules.router = {
+
address = {
+
type = types.str;
+
default = ipv4.prettyIp (ipv4.cidrToIpAddress intern.cidr);
+
example = "127.0.0.1";
+
};
interfaces = {
external = interfaceType;
internal = interfaceType;
···
config = mkIf cfg.enable {
services.irqbalance.enable = true;
-
networking.firewall.trustedInterfaces = [ "lo" intern0 ];
+
networking.firewall.trustedInterfaces = [ "lo" intern.name ];
systemd.network = {
enable = true;
-
links."10-${extern0}" = {
-
matchConfig.PermanentMACAddress = extern0MAC;
+
links."10-${extern.name}" = {
+
matchConfig.PermanentMACAddress = extern.macAddress;
linkConfig = {
Description = "External Network Interface";
-
Name = extern0;
+
Name = extern.name;
# MACAddress = "64:20:9f:16:70:a6";
MTUBytes = "1500";
};
};
-
links."11-${intern0}" = {
-
matchConfig.PermanentMACAddress = intern0MAC;
+
links."11-${intern.name}" = {
+
matchConfig.PermanentMACAddress = intern.macAddress;
linkConfig = {
Description = "Internal Network Interface";
-
Name = intern0;
+
Name = intern.name;
MTUBytes = "1500";
};
};
-
networks."10-${extern0}" = {
+
networks."10-${extern.name}" = {
name = extern0;
networkConfig = {
DHCP = "ipv4";
···
};
};
-
networks."11-${intern0}" = {
-
name = intern0;
+
networks."11-${intern.name}" = {
+
name = intern.name;
networkConfig = {
-
Address = "10.0.0.1/24";
+
Address = cfg.address;
DHCPServer = false;
IPForward = true;
ConfigureWithoutCarrier = true;
+6 -4
modules/router/upnp.nix
···
with lib;
let
cfg = config.modules.router;
+
extern = cfg.interfaces.external;
+
intern = cfg.interfaces.internal;
in {
options.modules.router = {
upnp = {
···
enable = true;
upnp = true;
natpmp = true;
-
internalIPs = [ cfg.interfaces.internal.name ];
-
externalInterface = cfg.interfaces.external.name;
+
internalIPs = [ intern.name ];
+
externalInterface = extern.name;
appendConfig = ''
secure_mode=yes
notify_interval=60
clean_ruleset_interval=600
uuid=78b8b903-83c1-4036-8fcd-f64aee25baca
-
allow 1024-65535 10.0.0.0/24 1024-65535
-
deny 0-65535 0.0.0.0/0 0-65535
+
allow 1024-65535 ${intern.cidr} 1024-65535
+
deny 0-65535 ${extern.cidr} 0-65535
'';
};
};
+4 -1
modules/server/tailscale.nix
···
let
cfgRoot = config.modules.server;
cfg = config.modules.server.tailscale;
+
address = config.modules.router.address;
in {
options.modules.server.tailscale = {
enable = mkOption {
···
};
config = mkIf cfg.enable && cfgRoot.enable {
+
modules.router.dnsmasq.localDomains = [ "${hostname}.fable-pancake.ts.net" ];
+
networking = {
domain = "fable-pancake.ts.net";
firewall.trustedInterfaces = [ "tailscale0" ];
-
hosts."10.0.0.1" = [ "${hostname}.fable-pancake.ts.net" hostname ];
+
hosts."${address}" = [ "${hostname}.fable-pancake.ts.net" hostname ];
};
age.secrets."tailscale" = {