665 lines
11 KiB
Nix
665 lines
11 KiB
Nix
{
|
|
lib,
|
|
internal,
|
|
ip,
|
|
}:
|
|
|
|
let
|
|
inherit (builtins)
|
|
elemAt
|
|
filter
|
|
map
|
|
split
|
|
;
|
|
inherit (lib) toInt;
|
|
inherit (internal) pow2 intRange;
|
|
|
|
in
|
|
rec {
|
|
/**
|
|
Parse a CIDR string into { address; prefixLen }.
|
|
Also accepts attrsets (returns as-is).
|
|
|
|
# Type
|
|
|
|
```
|
|
parse :: (String | { address :: Int; prefixLen :: Int; }) -> { address :: Int; prefixLen :: Int; }
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
parse "192.168.1.0/24"
|
|
=> { address = 3232235776; prefixLen = 24; }
|
|
```
|
|
*/
|
|
parse =
|
|
cidr:
|
|
if builtins.isAttrs cidr then
|
|
cidr
|
|
else
|
|
let
|
|
parts = filter builtins.isString (split "/" cidr);
|
|
addr = ip.parse (elemAt parts 0);
|
|
prefix = toInt (elemAt parts 1);
|
|
in
|
|
{
|
|
address = addr;
|
|
prefixLen = prefix;
|
|
};
|
|
|
|
/**
|
|
Create a CIDR from IP and prefix length.
|
|
Returns { address; prefixLen }.
|
|
|
|
# Type
|
|
|
|
```
|
|
make :: (String | Int) -> Int -> { address :: Int; prefixLen :: Int; }
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
make "192.168.1.0" 24
|
|
=> { address = 3232235776; prefixLen = 24; }
|
|
```
|
|
*/
|
|
make = addr: prefixLen: {
|
|
address = ip.parse addr;
|
|
inherit prefixLen;
|
|
};
|
|
|
|
/**
|
|
Format a CIDR as string.
|
|
|
|
# Type
|
|
|
|
```
|
|
format :: (String | { address :: Int; prefixLen :: Int; }) -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
format { address = 3232235776; prefixLen = 24; }
|
|
=> "192.168.1.0/24"
|
|
```
|
|
*/
|
|
format =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
in
|
|
"${ip.format c.address}/${toString c.prefixLen}";
|
|
|
|
/**
|
|
Calculate netmask from prefix length (as integer).
|
|
|
|
# Type
|
|
|
|
```
|
|
netmask :: Int -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
netmask 24
|
|
=> 4294967040
|
|
```
|
|
*/
|
|
netmask =
|
|
prefixLen:
|
|
if prefixLen == 0 then
|
|
0
|
|
else
|
|
let
|
|
# Create mask with prefixLen 1s followed by (32-prefixLen) 0s
|
|
hostBits = 32 - prefixLen;
|
|
in
|
|
4294967295 - (pow2 hostBits - 1);
|
|
|
|
/**
|
|
Calculate netmask from prefix length (as string).
|
|
|
|
# Type
|
|
|
|
```
|
|
netmaskStr :: Int -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
netmaskStr 24
|
|
=> "255.255.255.0"
|
|
```
|
|
*/
|
|
netmaskStr = prefixLen: ip.format (netmask prefixLen);
|
|
|
|
/**
|
|
Calculate wildcard mask (inverse of netmask).
|
|
|
|
# Type
|
|
|
|
```
|
|
wildcard :: Int -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
wildcard 24
|
|
=> 255
|
|
```
|
|
*/
|
|
wildcard =
|
|
prefixLen:
|
|
let
|
|
hostBits = 32 - prefixLen;
|
|
in
|
|
pow2 hostBits - 1;
|
|
|
|
/**
|
|
Calculate wildcard mask as string.
|
|
|
|
# Type
|
|
|
|
```
|
|
wildcardStr :: Int -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
wildcardStr 24
|
|
=> "0.0.0.255"
|
|
```
|
|
*/
|
|
wildcardStr = prefixLen: ip.format (wildcard prefixLen);
|
|
|
|
/**
|
|
Get network address of a CIDR (as integer).
|
|
|
|
# Type
|
|
|
|
```
|
|
network :: (String | { address :: Int; prefixLen :: Int; }) -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
network "192.168.1.50/24"
|
|
=> 3232235776
|
|
```
|
|
*/
|
|
network =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
mask = netmask c.prefixLen;
|
|
in
|
|
builtins.bitAnd c.address mask;
|
|
|
|
/**
|
|
Get network address of a CIDR (as string).
|
|
|
|
# Type
|
|
|
|
```
|
|
networkStr :: (String | { address :: Int; prefixLen :: Int; }) -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
networkStr "192.168.1.50/24"
|
|
=> "192.168.1.0"
|
|
```
|
|
*/
|
|
networkStr = cidr: ip.format (network cidr);
|
|
|
|
/**
|
|
Get broadcast address of a CIDR (as integer).
|
|
|
|
# Type
|
|
|
|
```
|
|
broadcast :: (String | { address :: Int; prefixLen :: Int; }) -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
broadcast "192.168.1.0/24"
|
|
=> 3232236031
|
|
```
|
|
*/
|
|
broadcast =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
wild = wildcard c.prefixLen;
|
|
net = network cidr;
|
|
in
|
|
builtins.bitOr net wild;
|
|
|
|
/**
|
|
Get broadcast address of a CIDR (as string).
|
|
|
|
# Type
|
|
|
|
```
|
|
broadcastStr :: (String | { address :: Int; prefixLen :: Int; }) -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
broadcastStr "192.168.1.0/24"
|
|
=> "192.168.1.255"
|
|
```
|
|
*/
|
|
broadcastStr = cidr: ip.format (broadcast cidr);
|
|
|
|
/**
|
|
Get first usable host address (as integer).
|
|
For /31 and /32, returns network address.
|
|
|
|
# Type
|
|
|
|
```
|
|
firstHost :: (String | { address :: Int; prefixLen :: Int; }) -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
firstHost "192.168.1.0/24"
|
|
=> 3232235777
|
|
```
|
|
*/
|
|
firstHost =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
net = network cidr;
|
|
in
|
|
if c.prefixLen >= 31 then net else net + 1;
|
|
|
|
/**
|
|
Get first usable host address (as string).
|
|
|
|
# Type
|
|
|
|
```
|
|
firstHostStr :: (String | { address :: Int; prefixLen :: Int; }) -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
firstHostStr "192.168.1.0/24"
|
|
=> "192.168.1.1"
|
|
```
|
|
*/
|
|
firstHostStr = cidr: ip.format (firstHost cidr);
|
|
|
|
/**
|
|
Get last usable host address (as integer).
|
|
For /31 and /32, returns broadcast address.
|
|
|
|
# Type
|
|
|
|
```
|
|
lastHost :: (String | { address :: Int; prefixLen :: Int; }) -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
lastHost "192.168.1.0/24"
|
|
=> 3232236030
|
|
```
|
|
*/
|
|
lastHost =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
bcast = broadcast cidr;
|
|
in
|
|
if c.prefixLen >= 31 then bcast else bcast - 1;
|
|
|
|
/**
|
|
Get last usable host address (as string).
|
|
|
|
# Type
|
|
|
|
```
|
|
lastHostStr :: (String | { address :: Int; prefixLen :: Int; }) -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
lastHostStr "192.168.1.0/24"
|
|
=> "192.168.1.254"
|
|
```
|
|
*/
|
|
lastHostStr = cidr: ip.format (lastHost cidr);
|
|
|
|
/**
|
|
Get total number of addresses in CIDR.
|
|
|
|
# Type
|
|
|
|
```
|
|
size :: (String | { address :: Int; prefixLen :: Int; }) -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
size "192.168.1.0/24"
|
|
=> 256
|
|
```
|
|
*/
|
|
size =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
in
|
|
pow2 (32 - c.prefixLen);
|
|
|
|
/**
|
|
Get number of usable host addresses.
|
|
Excludes network and broadcast for normal subnets.
|
|
/31 returns 2, /32 returns 1 (special cases per RFC 3021).
|
|
|
|
# Type
|
|
|
|
```
|
|
hostCount :: (String | { address :: Int; prefixLen :: Int; }) -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
hostCount "192.168.1.0/24"
|
|
=> 254
|
|
```
|
|
*/
|
|
hostCount =
|
|
cidr:
|
|
let
|
|
c = parse cidr;
|
|
total = size cidr;
|
|
in
|
|
if c.prefixLen == 32 then
|
|
1
|
|
else if c.prefixLen == 31 then
|
|
2
|
|
else if total <= 2 then
|
|
0
|
|
else
|
|
total - 2;
|
|
|
|
/**
|
|
Check if an IP address is within a CIDR.
|
|
|
|
# Type
|
|
|
|
```
|
|
contains :: (String | { address :: Int; prefixLen :: Int; }) -> (String | Int) -> Bool
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
contains "10.0.0.0/8" "10.1.2.3"
|
|
=> true
|
|
```
|
|
*/
|
|
contains =
|
|
cidr: addr:
|
|
let
|
|
ipInt = ip.parse addr;
|
|
net = network cidr;
|
|
bcast = broadcast cidr;
|
|
in
|
|
ipInt >= net && ipInt <= bcast;
|
|
|
|
/**
|
|
Check if cidr2 is a subnet of cidr1.
|
|
|
|
# Type
|
|
|
|
```
|
|
isSubnetOf :: (String | { address :: Int; prefixLen :: Int; }) -> (String | { address :: Int; prefixLen :: Int; }) -> Bool
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
isSubnetOf "10.0.0.0/8" "10.1.0.0/16"
|
|
=> true
|
|
```
|
|
*/
|
|
isSubnetOf =
|
|
cidr1: cidr2:
|
|
let
|
|
c1 = parse cidr1;
|
|
c2 = parse cidr2;
|
|
net1 = network cidr1;
|
|
bcast1 = broadcast cidr1;
|
|
net2 = network cidr2;
|
|
bcast2 = broadcast cidr2;
|
|
in
|
|
c2.prefixLen >= c1.prefixLen && net2 >= net1 && bcast2 <= bcast1;
|
|
|
|
/**
|
|
Get the Nth host in a CIDR (0-indexed from network address).
|
|
|
|
# Type
|
|
|
|
```
|
|
host :: (String | { address :: Int; prefixLen :: Int; }) -> Int -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
host "192.168.1.0/24" 5
|
|
=> 3232235781
|
|
```
|
|
*/
|
|
host = cidr: n: (network cidr) + n;
|
|
|
|
/**
|
|
Get the Nth host in a CIDR (as string).
|
|
|
|
# Type
|
|
|
|
```
|
|
hostStr :: (String | { address :: Int; prefixLen :: Int; }) -> Int -> String
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
hostStr "192.168.1.0/24" 5
|
|
=> "192.168.1.5"
|
|
```
|
|
*/
|
|
hostStr = cidr: n: ip.format (host cidr n);
|
|
|
|
/**
|
|
Subdivide a CIDR into smaller subnets.
|
|
Returns the Nth subnet with the new prefix length.
|
|
|
|
# Type
|
|
|
|
```
|
|
subnet :: (String | { address :: Int; prefixLen :: Int; }) -> Int -> Int -> { address :: Int; prefixLen :: Int; }
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
subnet "192.168.0.0/24" 26 0
|
|
=> { address = 3232235520; prefixLen = 26; }
|
|
```
|
|
*/
|
|
subnet =
|
|
cidr: newPrefix: n:
|
|
let
|
|
net = network cidr;
|
|
subnetSize = pow2 (32 - newPrefix);
|
|
subnetStart = net + (n * subnetSize);
|
|
in
|
|
{
|
|
address = subnetStart;
|
|
prefixLen = newPrefix;
|
|
};
|
|
|
|
/**
|
|
Get all subnets when subdividing.
|
|
|
|
# Type
|
|
|
|
```
|
|
subnets :: (String | { address :: Int; prefixLen :: Int; }) -> Int -> [{ address :: Int; prefixLen :: Int; }]
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
subnets "192.168.0.0/24" 26
|
|
=> [ { address = 3232235520; prefixLen = 26; } ... ] # 4 subnets
|
|
```
|
|
*/
|
|
subnets =
|
|
cidr: newPrefix:
|
|
let
|
|
c = parse cidr;
|
|
numSubnets = pow2 (newPrefix - c.prefixLen);
|
|
in
|
|
map (n: subnet cidr newPrefix n) (intRange 0 (numSubnets - 1));
|
|
|
|
/**
|
|
Get all subnets as strings.
|
|
|
|
# Type
|
|
|
|
```
|
|
subnetsStr :: (String | { address :: Int; prefixLen :: Int; }) -> Int -> [String]
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
subnetsStr "192.168.0.0/24" 26
|
|
=> [ "192.168.0.0/26" "192.168.0.64/26" "192.168.0.128/26" "192.168.0.192/26" ]
|
|
```
|
|
*/
|
|
subnetsStr = cidr: newPrefix: map format (subnets cidr newPrefix);
|
|
|
|
/**
|
|
Check if two CIDRs overlap.
|
|
|
|
# Type
|
|
|
|
```
|
|
overlaps :: (String | { address :: Int; prefixLen :: Int; }) -> (String | { address :: Int; prefixLen :: Int; }) -> Bool
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
overlaps "192.168.1.0/24" "192.168.1.128/25"
|
|
=> true
|
|
```
|
|
*/
|
|
overlaps =
|
|
cidr1: cidr2:
|
|
let
|
|
net1 = network cidr1;
|
|
bcast1 = broadcast cidr1;
|
|
net2 = network cidr2;
|
|
bcast2 = broadcast cidr2;
|
|
in
|
|
!(bcast1 < net2 || bcast2 < net1);
|
|
|
|
/**
|
|
Get prefix length from number of required hosts.
|
|
|
|
# Type
|
|
|
|
```
|
|
prefixForHosts :: Int -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
prefixForHosts 100
|
|
=> 25
|
|
```
|
|
*/
|
|
prefixForHosts =
|
|
hosts:
|
|
let
|
|
findPrefix =
|
|
prefix:
|
|
if prefix < 0 then
|
|
0
|
|
else if
|
|
hostCount {
|
|
address = 0;
|
|
prefixLen = prefix;
|
|
} >= hosts
|
|
then
|
|
prefix
|
|
else
|
|
findPrefix (prefix - 1);
|
|
in
|
|
findPrefix 32;
|
|
|
|
/**
|
|
Get prefix length from number of required addresses.
|
|
|
|
# Type
|
|
|
|
```
|
|
prefixForSize :: Int -> Int
|
|
```
|
|
|
|
# Example
|
|
|
|
```nix
|
|
prefixForSize 256
|
|
=> 24
|
|
```
|
|
*/
|
|
prefixForSize =
|
|
addresses:
|
|
let
|
|
findPrefix =
|
|
prefix:
|
|
if prefix < 0 then
|
|
0
|
|
else if
|
|
size {
|
|
address = 0;
|
|
prefixLen = prefix;
|
|
} >= addresses
|
|
then
|
|
prefix
|
|
else
|
|
findPrefix (prefix - 1);
|
|
in
|
|
findPrefix 32;
|
|
}
|