Files
nix-ip-utils/lib/cidr.nix
2026-02-20 20:07:52 +00:00

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;
}