This commit is contained in:
2026-02-20 20:07:52 +00:00
commit 35f0f3b0a5
13 changed files with 3017 additions and 0 deletions

26
flake.lock generated Normal file
View File

@@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs-lib": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
}
}
},
"root": "root",
"version": 7
}

53
flake.nix Normal file
View File

@@ -0,0 +1,53 @@
{
description = "Pure Nix library for IPv4 operations";
inputs = {
nixpkgs-lib.url = "github:nix-community/nixpkgs.lib";
};
outputs =
{ self, nixpkgs-lib }:
let
# Import our library with nixpkgs.lib
ipLib = import ./lib { lib = nixpkgs-lib.lib; };
# Systems for checks and packages
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = f: nixpkgs-lib.lib.genAttrs systems f;
in
{
# Primary library output
lib = ipLib;
overlays = {
default = final: prev: {
lib = prev.lib.extend self.overlays.lib;
};
lib = final: prev: {
iputils = ipLib;
};
};
# Tests
checks = forAllSystems (
system:
let
lib = nixpkgs-lib.lib;
runTests = import ./tests {
inherit lib system;
ipLib = ipLib;
};
in
{
tests = runTests;
}
);
};
}

664
lib/cidr.nix Normal file
View File

@@ -0,0 +1,664 @@
{
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;
}

42
lib/default.nix Normal file
View File

@@ -0,0 +1,42 @@
{ lib }:
let
# Internal helpers
internal = import ./internal.nix { inherit lib; };
# IP address operations
ip = import ./ip.nix { inherit lib internal; };
# CIDR/subnet operations
cidr = import ./cidr.nix { inherit lib internal ip; };
# Validation functions
validate = import ./validate.nix {
inherit
lib
internal
ip
cidr
;
};
# Range/iteration functions
iterate = import ./iterate.nix {
inherit
lib
internal
ip
cidr
;
};
in
{
inherit
ip
cidr
validate
iterate
;
_internal = internal;
}

56
lib/internal.nix Normal file
View File

@@ -0,0 +1,56 @@
{ lib }:
let
inherit (builtins) elemAt genList;
inherit (lib) mod;
in
rec {
# Power of 2 calculation
# pow2 n returns 2^n
pow2 =
n:
if n == 0 then
1
else if n == 1 then
2
else
let
half = n / 2;
halfPow = pow2 half;
in
halfPow * halfPow * (if mod n 2 == 1 then 2 else 1);
# Convert list of 4 octets to 32-bit integer
# [192 168 1 1] -> 3232235777
octetsToInt =
octets:
let
a = elemAt octets 0;
b = elemAt octets 1;
c = elemAt octets 2;
d = elemAt octets 3;
in
a * 16777216 + b * 65536 + c * 256 + d;
# Convert 32-bit integer to list of 4 octets
# 3232235777 -> [192 168 1 1]
intToOctets =
ip:
let
a = ip / 16777216;
remainder1 = mod ip 16777216;
b = remainder1 / 65536;
remainder2 = mod remainder1 65536;
c = remainder2 / 256;
d = mod remainder2 256;
in
[
a
b
c
d
];
# Generate a list of integers from start to end (inclusive)
# More efficient than recursive approach for large ranges
intRange = start: end: if end < start then [ ] else genList (i: start + i) (end - start + 1);
}

392
lib/ip.nix Normal file
View File

@@ -0,0 +1,392 @@
{ lib, internal }:
let
inherit (builtins)
filter
map
split
;
inherit (lib) concatStringsSep toInt;
inherit (internal) octetsToInt intToOctets;
in
rec {
/**
Parse an IP address string to a 32-bit integer.
Also accepts integers (returns as-is).
# Type
```
parse :: (String | Int) -> Int
```
# Example
```nix
parse "192.168.1.1"
=> 3232235777
```
*/
parse =
ip:
if builtins.isInt ip then
ip
else
let
parts = filter builtins.isString (split "\\." ip);
octets = map toInt parts;
in
octetsToInt octets;
/**
Format a 32-bit integer as an IP address string.
Also accepts strings (returns as-is after validation).
# Type
```
format :: (Int | String) -> String
```
# Example
```nix
format 3232235777
=> "192.168.1.1"
```
*/
format =
ip: if builtins.isString ip then ip else concatStringsSep "." (map toString (intToOctets ip));
/**
Convert IP to list of octets.
# Type
```
toOctets :: (String | Int) -> [Int]
```
# Example
```nix
toOctets "192.168.1.1"
=> [192 168 1 1]
```
*/
toOctets = ip: intToOctets (parse ip);
/**
Convert list of octets to integer.
# Type
```
fromOctets :: [Int] -> Int
```
# Example
```nix
fromOctets [192 168 1 1]
=> 3232235777
```
*/
fromOctets = octetsToInt;
/**
Add offset to an IP address.
Returns integer; use format for string.
# Type
```
add :: (String | Int) -> Int -> Int
```
# Example
```nix
add "192.168.1.1" 10
=> 3232235787
```
*/
add = ip: offset: (parse ip) + offset;
/**
Add offset to an IP address, return as string.
# Type
```
addStr :: (String | Int) -> Int -> String
```
# Example
```nix
addStr "192.168.1.1" 10
=> "192.168.1.11"
```
*/
addStr = ip: offset: format (add ip offset);
/**
Subtract offset from an IP address.
Returns integer; use format for string.
# Type
```
subtract :: (String | Int) -> Int -> Int
```
# Example
```nix
subtract "192.168.1.10" 5
=> 3232235781
```
*/
subtract = ip: offset: (parse ip) - offset;
/**
Subtract offset from an IP address, return as string.
# Type
```
subtractStr :: (String | Int) -> Int -> String
```
# Example
```nix
subtractStr "192.168.1.10" 5
=> "192.168.1.5"
```
*/
subtractStr = ip: offset: format (subtract ip offset);
/**
Calculate difference between two IPs (ip1 - ip2).
# Type
```
diff :: (String | Int) -> (String | Int) -> Int
```
# Example
```nix
diff "192.168.1.10" "192.168.1.1"
=> 9
```
*/
diff = ip1: ip2: (parse ip1) - (parse ip2);
/**
Compare two IP addresses.
Returns -1 if ip1 < ip2, 0 if equal, 1 if ip1 > ip2.
# Type
```
compare :: (String | Int) -> (String | Int) -> Int
```
# Example
```nix
compare "192.168.1.1" "192.168.1.10"
=> -1
```
*/
compare =
ip1: ip2:
let
a = parse ip1;
b = parse ip2;
in
if a < b then
-1
else if a > b then
1
else
0;
/**
Less than comparison.
# Type
```
lt :: (String | Int) -> (String | Int) -> Bool
```
# Example
```nix
lt "192.168.1.1" "192.168.1.10"
=> true
```
*/
lt = ip1: ip2: (parse ip1) < (parse ip2);
/**
Less than or equal comparison.
# Type
```
lte :: (String | Int) -> (String | Int) -> Bool
```
# Example
```nix
lte "192.168.1.1" "192.168.1.1"
=> true
```
*/
lte = ip1: ip2: (parse ip1) <= (parse ip2);
/**
Greater than comparison.
# Type
```
gt :: (String | Int) -> (String | Int) -> Bool
```
# Example
```nix
gt "192.168.1.10" "192.168.1.1"
=> true
```
*/
gt = ip1: ip2: (parse ip1) > (parse ip2);
/**
Greater than or equal comparison.
# Type
```
gte :: (String | Int) -> (String | Int) -> Bool
```
# Example
```nix
gte "192.168.1.1" "192.168.1.1"
=> true
```
*/
gte = ip1: ip2: (parse ip1) >= (parse ip2);
/**
Equality comparison.
# Type
```
eq :: (String | Int) -> (String | Int) -> Bool
```
# Example
```nix
eq "192.168.1.1" 3232235777
=> true
```
*/
eq = ip1: ip2: (parse ip1) == (parse ip2);
/**
Get minimum of two IPs (returns integer).
# Type
```
min :: (String | Int) -> (String | Int) -> Int
```
# Example
```nix
min "192.168.1.10" "192.168.1.1"
=> 3232235777
```
*/
min =
ip1: ip2:
let
a = parse ip1;
b = parse ip2;
in
if a < b then a else b;
/**
Get minimum of two IPs (returns string).
# Type
```
minStr :: (String | Int) -> (String | Int) -> String
```
# Example
```nix
minStr "192.168.1.10" "192.168.1.1"
=> "192.168.1.1"
```
*/
minStr = ip1: ip2: format (min ip1 ip2);
/**
Get maximum of two IPs (returns integer).
# Type
```
max :: (String | Int) -> (String | Int) -> Int
```
# Example
```nix
max "192.168.1.1" "192.168.1.10"
=> 3232235786
```
*/
max =
ip1: ip2:
let
a = parse ip1;
b = parse ip2;
in
if a > b then a else b;
/**
Get maximum of two IPs (returns string).
# Type
```
maxStr :: (String | Int) -> (String | Int) -> String
```
# Example
```nix
maxStr "192.168.1.1" "192.168.1.10"
=> "192.168.1.10"
```
*/
maxStr = ip1: ip2: format (max ip1 ip2);
}

389
lib/iterate.nix Normal file
View File

@@ -0,0 +1,389 @@
{
lib,
internal,
ip,
cidr,
}:
let
inherit (builtins) genList;
inherit (lib) map;
in
rec {
/**
Generate a list of IP integers from start to end (inclusive).
# Type
```
range :: (String | Int) -> (String | Int) -> [Int]
```
# Example
```nix
range "192.168.1.1" "192.168.1.3"
=> [ 3232235777 3232235778 3232235779 ]
```
*/
range =
start: end:
let
startInt = ip.parse start;
endInt = ip.parse end;
count = endInt - startInt + 1;
in
if endInt < startInt then [ ] else genList (i: startInt + i) count;
/**
Generate a list of IP strings from start to end (inclusive).
# Type
```
rangeStr :: (String | Int) -> (String | Int) -> [String]
```
# Example
```nix
rangeStr "192.168.1.1" "192.168.1.3"
=> [ "192.168.1.1" "192.168.1.2" "192.168.1.3" ]
```
*/
rangeStr = start: end: map ip.format (range start end);
/**
Get all addresses in a CIDR (as integers).
# Type
```
cidrAddresses :: (String | { address :: Int; prefixLen :: Int; }) -> [Int]
```
# Example
```nix
cidrAddresses "192.168.1.0/30"
=> [ 3232235776 3232235777 3232235778 3232235779 ]
```
*/
cidrAddresses =
cidrStr:
let
net = cidr.network cidrStr;
bcast = cidr.broadcast cidrStr;
in
range net bcast;
/**
Get all addresses in a CIDR (as strings).
# Type
```
cidrAddressesStr :: (String | { address :: Int; prefixLen :: Int; }) -> [String]
```
# Example
```nix
cidrAddressesStr "192.168.1.0/30"
=> [ "192.168.1.0" "192.168.1.1" "192.168.1.2" "192.168.1.3" ]
```
*/
cidrAddressesStr = cidrStr: map ip.format (cidrAddresses cidrStr);
/**
Get all usable host addresses in a CIDR (as integers).
Excludes network and broadcast for /30 and larger.
# Type
```
cidrHosts :: (String | { address :: Int; prefixLen :: Int; }) -> [Int]
```
# Example
```nix
cidrHosts "192.168.1.0/30"
=> [ 3232235777 3232235778 ]
```
*/
cidrHosts =
cidrStr:
let
c = cidr.parse cidrStr;
first = cidr.firstHost cidrStr;
last = cidr.lastHost cidrStr;
in
if c.prefixLen >= 31 then range first last else range first last;
/**
Get all usable host addresses in a CIDR (as strings).
# Type
```
cidrHostsStr :: (String | { address :: Int; prefixLen :: Int; }) -> [String]
```
# Example
```nix
cidrHostsStr "192.168.1.0/30"
=> [ "192.168.1.1" "192.168.1.2" ]
```
*/
cidrHostsStr = cidrStr: map ip.format (cidrHosts cidrStr);
/**
Check if an IP is within a range (inclusive).
# Type
```
inRange :: (String | Int) -> (String | Int) -> (String | Int) -> Bool
```
# Example
```nix
inRange "192.168.1.1" "192.168.1.10" "192.168.1.5"
=> true
```
*/
inRange =
start: end: addr:
let
startInt = ip.parse start;
endInt = ip.parse end;
addrInt = ip.parse addr;
in
addrInt >= startInt && addrInt <= endInt;
/**
Count IPs in a range (inclusive).
# Type
```
countRange :: (String | Int) -> (String | Int) -> Int
```
# Example
```nix
countRange "192.168.1.1" "192.168.1.10"
=> 10
```
*/
countRange =
start: end:
let
startInt = ip.parse start;
endInt = ip.parse end;
in
if endInt < startInt then 0 else endInt - startInt + 1;
/**
Map a function over an IP range.
More memory-efficient than generating the full list first.
# Type
```
mapRange :: (Int -> a) -> (String | Int) -> (String | Int) -> [a]
```
# Example
```nix
mapRange (i: i * 2) "0.0.0.1" "0.0.0.3"
=> [ 2 4 6 ]
```
*/
mapRange =
f: start: end:
let
startInt = ip.parse start;
endInt = ip.parse end;
count = endInt - startInt + 1;
in
if endInt < startInt then [ ] else genList (i: f (startInt + i)) count;
/**
Map a function over a CIDR's addresses.
# Type
```
mapCidr :: (Int -> a) -> (String | { address :: Int; prefixLen :: Int; }) -> [a]
```
# Example
```nix
mapCidr ip.format "192.168.1.0/30"
=> [ "192.168.1.0" "192.168.1.1" "192.168.1.2" "192.168.1.3" ]
```
*/
mapCidr =
f: cidrStr:
let
net = cidr.network cidrStr;
bcast = cidr.broadcast cidrStr;
in
mapRange f net bcast;
/**
Map a function over a CIDR's usable hosts.
# Type
```
mapCidrHosts :: (Int -> a) -> (String | { address :: Int; prefixLen :: Int; }) -> [a]
```
# Example
```nix
mapCidrHosts ip.format "192.168.1.0/30"
=> [ "192.168.1.1" "192.168.1.2" ]
```
*/
mapCidrHosts =
f: cidrStr:
let
first = cidr.firstHost cidrStr;
last = cidr.lastHost cidrStr;
in
mapRange f first last;
/**
Filter IPs in a range.
# Type
```
filterRange :: (Int -> Bool) -> (String | Int) -> (String | Int) -> [Int]
```
# Example
```nix
filterRange (i: lib.mod i 2 == 0) "0.0.0.1" "0.0.0.5"
=> [ 2 4 ]
```
*/
filterRange =
pred: start: end:
let
startInt = ip.parse start;
endInt = ip.parse end;
count = endInt - startInt + 1;
allIps = if endInt < startInt then [ ] else genList (i: startInt + i) count;
in
builtins.filter pred allIps;
/**
Find first IP in range matching predicate.
# Type
```
findInRange :: (Int -> Bool) -> (String | Int) -> (String | Int) -> (Int | Null)
```
# Example
```nix
findInRange (i: lib.mod i 2 == 0) "0.0.0.1" "0.0.0.5"
=> 2
```
*/
findInRange =
pred: start: end:
let
startInt = ip.parse start;
endInt = ip.parse end;
find =
current:
if current > endInt then
null
else if pred current then
current
else
find (current + 1);
in
if endInt < startInt then null else find startInt;
/**
Find first IP in range matching predicate (as string).
# Type
```
findInRangeStr :: (Int -> Bool) -> (String | Int) -> (String | Int) -> (String | Null)
```
# Example
```nix
findInRangeStr (i: lib.mod i 2 == 0) "0.0.0.1" "0.0.0.5"
=> "0.0.0.2"
```
*/
findInRangeStr =
pred: start: end:
let
result = findInRange pred start end;
in
if result == null then null else ip.format result;
/**
Take first n IPs from a range.
# Type
```
takeRange :: Int -> (String | Int) -> (String | Int) -> [Int]
```
# Example
```nix
takeRange 2 "192.168.1.1" "192.168.1.10"
=> [ 3232235777 3232235778 ]
```
*/
takeRange =
n: start: end:
let
startInt = ip.parse start;
endInt = ip.parse end;
actualEnd = if startInt + n - 1 < endInt then startInt + n - 1 else endInt;
in
if endInt < startInt || n <= 0 then [ ] else range startInt actualEnd;
/**
Take first n IPs from a range (as strings).
# Type
```
takeRangeStr :: Int -> (String | Int) -> (String | Int) -> [String]
```
# Example
```nix
takeRangeStr 2 "192.168.1.1" "192.168.1.10"
=> [ "192.168.1.1" "192.168.1.2" ]
```
*/
takeRangeStr =
n: start: end:
map ip.format (takeRange n start end);
}

371
lib/validate.nix Normal file
View File

@@ -0,0 +1,371 @@
{
lib,
internal,
ip,
cidr,
}:
let
inherit (builtins)
elemAt
filter
length
match
split
tryEval
;
inherit (lib) all toInt;
in
rec {
/**
Check if a string is a valid IPv4 address.
# Type
```
isValidIp :: Any -> Bool
```
# Example
```nix
isValidIp "192.168.1.1"
=> true
```
*/
isValidIp =
str:
if !builtins.isString str then
false
else
let
# Match basic format: digits.digits.digits.digits
m = match "([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)" str;
in
if m == null then
false
else
let
octets = map toInt m;
validOctet = o: o >= 0 && o <= 255;
in
all validOctet octets;
/**
Check if a string is valid CIDR notation.
# Type
```
isValidCidr :: Any -> Bool
```
# Example
```nix
isValidCidr "192.168.1.0/24"
=> true
```
*/
isValidCidr =
str:
if !builtins.isString str then
false
else
let
parts = filter builtins.isString (split "/" str);
in
if length parts != 2 then
false
else
let
ipPart = elemAt parts 0;
prefixPart = elemAt parts 1;
prefixMatch = match "[0-9]+" prefixPart;
in
if !isValidIp ipPart then
false
else if prefixMatch == null then
false
else
let
prefix = toInt prefixPart;
in
prefix >= 0 && prefix <= 32;
/**
Try to parse an IP, return null on failure.
# Type
```
tryParseIp :: String -> (Int | Null)
```
# Example
```nix
tryParseIp "192.168.1.1"
=> 3232235777
```
*/
tryParseIp =
str:
let
result = tryEval (if isValidIp str then ip.parse str else throw "invalid");
in
if result.success then result.value else null;
/**
Try to parse a CIDR, return null on failure.
# Type
```
tryParseCidr :: String -> ({ address :: Int; prefixLen :: Int; } | Null)
```
# Example
```nix
tryParseCidr "192.168.1.0/24"
=> { address = 3232235776; prefixLen = 24; }
```
*/
tryParseCidr =
str:
let
result = tryEval (if isValidCidr str then cidr.parse str else throw "invalid");
in
if result.success then result.value else null;
/**
Check if IP is in RFC 1918 private address space.
Covers 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
# Type
```
isPrivate :: (String | Int) -> Bool
```
# Example
```nix
isPrivate "192.168.1.1"
=> true
```
*/
isPrivate =
addr:
let
ipInt = ip.parse addr;
in
cidr.contains "10.0.0.0/8" ipInt
|| cidr.contains "172.16.0.0/12" ipInt
|| cidr.contains "192.168.0.0/16" ipInt;
/**
Check if IP is loopback (127.0.0.0/8).
# Type
```
isLoopback :: (String | Int) -> Bool
```
# Example
```nix
isLoopback "127.0.0.1"
=> true
```
*/
isLoopback = addr: cidr.contains "127.0.0.0/8" (ip.parse addr);
/**
Check if IP is link-local (169.254.0.0/16).
# Type
```
isLinkLocal :: (String | Int) -> Bool
```
# Example
```nix
isLinkLocal "169.254.1.1"
=> true
```
*/
isLinkLocal = addr: cidr.contains "169.254.0.0/16" (ip.parse addr);
/**
Check if IP is multicast (224.0.0.0/4).
# Type
```
isMulticast :: (String | Int) -> Bool
```
# Example
```nix
isMulticast "224.0.0.1"
=> true
```
*/
isMulticast = addr: cidr.contains "224.0.0.0/4" (ip.parse addr);
/**
Check if IP is broadcast (255.255.255.255).
# Type
```
isBroadcast :: (String | Int) -> Bool
```
# Example
```nix
isBroadcast "255.255.255.255"
=> true
```
*/
isBroadcast = addr: (ip.parse addr) == 4294967295;
/**
Check if IP is unspecified (0.0.0.0).
# Type
```
isUnspecified :: (String | Int) -> Bool
```
# Example
```nix
isUnspecified "0.0.0.0"
=> true
```
*/
isUnspecified = addr: (ip.parse addr) == 0;
/**
Check if IP is documentation range.
Covers 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24.
# Type
```
isDocumentation :: (String | Int) -> Bool
```
# Example
```nix
isDocumentation "192.0.2.1"
=> true
```
*/
isDocumentation =
addr:
let
ipInt = ip.parse addr;
in
cidr.contains "192.0.2.0/24" ipInt
|| cidr.contains "198.51.100.0/24" ipInt
|| cidr.contains "203.0.113.0/24" ipInt;
/**
Check if IP is reserved for future use (240.0.0.0/4, except broadcast).
# Type
```
isReserved :: (String | Int) -> Bool
```
# Example
```nix
isReserved "240.0.0.1"
=> true
```
*/
isReserved =
addr:
let
ipInt = ip.parse addr;
in
cidr.contains "240.0.0.0/4" ipInt && ipInt != 4294967295;
/**
Check if IP is globally routable (not private, loopback, link-local, etc.).
# Type
```
isGlobalUnicast :: (String | Int) -> Bool
```
# Example
```nix
isGlobalUnicast "8.8.8.8"
=> true
```
*/
isGlobalUnicast =
addr:
!isPrivate addr
&& !isLoopback addr
&& !isLinkLocal addr
&& !isMulticast addr
&& !isBroadcast addr
&& !isUnspecified addr
&& !isDocumentation addr
&& !isReserved addr;
/**
Classify an IP address into its type.
Returns one of: "loopback", "private", "link-local", "multicast",
"broadcast", "unspecified", "documentation", "reserved", "global-unicast".
# Type
```
classify :: (String | Int) -> String
```
# Example
```nix
classify "192.168.1.1"
=> "private"
```
*/
classify =
addr:
if isLoopback addr then
"loopback"
else if isPrivate addr then
"private"
else if isLinkLocal addr then
"link-local"
else if isMulticast addr then
"multicast"
else if isBroadcast addr then
"broadcast"
else if isUnspecified addr then
"unspecified"
else if isDocumentation addr then
"documentation"
else if isReserved addr then
"reserved"
else
"global-unicast";
}

315
tests/cidr-tests.nix Normal file
View File

@@ -0,0 +1,315 @@
{
lib,
cidr,
}:
let
inherit (lib) runTests;
in
runTests {
testParseBasic = {
expr = cidr.parse "192.168.1.0/24";
expected = {
address = 3232235776;
prefixLen = 24;
};
};
testParseSlash32 = {
expr = cidr.parse "10.0.0.1/32";
expected = {
address = 167772161;
prefixLen = 32;
};
};
testParseSlash0 = {
expr = cidr.parse "0.0.0.0/0";
expected = {
address = 0;
prefixLen = 0;
};
};
testMakeBasic = {
expr = cidr.make "192.168.1.0" 24;
expected = {
address = 3232235776;
prefixLen = 24;
};
};
testFormatBasic = {
expr = cidr.format {
address = 3232235776;
prefixLen = 24;
};
expected = "192.168.1.0/24";
};
testNetmask24 = {
expr = cidr.netmaskStr 24;
expected = "255.255.255.0";
};
testNetmask16 = {
expr = cidr.netmaskStr 16;
expected = "255.255.0.0";
};
testNetmask8 = {
expr = cidr.netmaskStr 8;
expected = "255.0.0.0";
};
testNetmask32 = {
expr = cidr.netmaskStr 32;
expected = "255.255.255.255";
};
testNetmask0 = {
expr = cidr.netmaskStr 0;
expected = "0.0.0.0";
};
testNetmask25 = {
expr = cidr.netmaskStr 25;
expected = "255.255.255.128";
};
testWildcard24 = {
expr = cidr.wildcardStr 24;
expected = "0.0.0.255";
};
testWildcard16 = {
expr = cidr.wildcardStr 16;
expected = "0.0.255.255";
};
testNetworkBasic = {
expr = cidr.networkStr "192.168.1.50/24";
expected = "192.168.1.0";
};
testNetworkAlreadyNetwork = {
expr = cidr.networkStr "192.168.1.0/24";
expected = "192.168.1.0";
};
testNetworkSlash16 = {
expr = cidr.networkStr "172.16.45.67/16";
expected = "172.16.0.0";
};
testBroadcastBasic = {
expr = cidr.broadcastStr "192.168.1.0/24";
expected = "192.168.1.255";
};
testBroadcastSlash16 = {
expr = cidr.broadcastStr "172.16.0.0/16";
expected = "172.16.255.255";
};
testBroadcastSlash30 = {
expr = cidr.broadcastStr "192.168.1.0/30";
expected = "192.168.1.3";
};
testFirstHostBasic = {
expr = cidr.firstHostStr "192.168.1.0/24";
expected = "192.168.1.1";
};
testLastHostBasic = {
expr = cidr.lastHostStr "192.168.1.0/24";
expected = "192.168.1.254";
};
testFirstHostSlash31 = {
expr = cidr.firstHostStr "192.168.1.0/31";
expected = "192.168.1.0";
};
testLastHostSlash31 = {
expr = cidr.lastHostStr "192.168.1.0/31";
expected = "192.168.1.1";
};
testFirstHostSlash32 = {
expr = cidr.firstHostStr "192.168.1.1/32";
expected = "192.168.1.1";
};
testSize24 = {
expr = cidr.size "192.168.1.0/24";
expected = 256;
};
testSize16 = {
expr = cidr.size "172.16.0.0/16";
expected = 65536;
};
testSize32 = {
expr = cidr.size "192.168.1.1/32";
expected = 1;
};
testSize30 = {
expr = cidr.size "192.168.1.0/30";
expected = 4;
};
testHostCount24 = {
expr = cidr.hostCount "192.168.1.0/24";
expected = 254;
};
testHostCount30 = {
expr = cidr.hostCount "192.168.1.0/30";
expected = 2;
};
testHostCount31 = {
expr = cidr.hostCount "192.168.1.0/31";
expected = 2;
};
testHostCount32 = {
expr = cidr.hostCount "192.168.1.1/32";
expected = 1;
};
testContainsTrue = {
expr = cidr.contains "10.0.0.0/8" "10.1.2.3";
expected = true;
};
testContainsFalse = {
expr = cidr.contains "10.0.0.0/8" "192.168.1.1";
expected = false;
};
testContainsNetwork = {
expr = cidr.contains "192.168.1.0/24" "192.168.1.0";
expected = true;
};
testContainsBroadcast = {
expr = cidr.contains "192.168.1.0/24" "192.168.1.255";
expected = true;
};
testContainsEdgeFalse = {
expr = cidr.contains "192.168.1.0/24" "192.168.2.0";
expected = false;
};
testIsSubnetOfTrue = {
expr = cidr.isSubnetOf "10.0.0.0/8" "10.1.0.0/16";
expected = true;
};
testIsSubnetOfFalse = {
expr = cidr.isSubnetOf "10.0.0.0/8" "192.168.0.0/16";
expected = false;
};
testIsSubnetOfSame = {
expr = cidr.isSubnetOf "10.0.0.0/8" "10.0.0.0/8";
expected = true;
};
testIsSubnetOfLarger = {
expr = cidr.isSubnetOf "10.1.0.0/16" "10.0.0.0/8";
expected = false;
};
testHostBasic = {
expr = cidr.hostStr "192.168.1.0/24" 5;
expected = "192.168.1.5";
};
testHostZero = {
expr = cidr.hostStr "192.168.1.0/24" 0;
expected = "192.168.1.0";
};
testHostLast = {
expr = cidr.hostStr "192.168.1.0/24" 255;
expected = "192.168.1.255";
};
testSubnetFirst = {
expr = cidr.format (cidr.subnet "192.168.0.0/24" 26 0);
expected = "192.168.0.0/26";
};
testSubnetSecond = {
expr = cidr.format (cidr.subnet "192.168.0.0/24" 26 1);
expected = "192.168.0.64/26";
};
testSubnetLast = {
expr = cidr.format (cidr.subnet "192.168.0.0/24" 26 3);
expected = "192.168.0.192/26";
};
testSubnetsCount = {
expr = builtins.length (cidr.subnets "192.168.0.0/24" 26);
expected = 4;
};
testSubnetsStr = {
expr = cidr.subnetsStr "192.168.0.0/24" 26;
expected = [
"192.168.0.0/26"
"192.168.0.64/26"
"192.168.0.128/26"
"192.168.0.192/26"
];
};
testOverlapsTrue = {
expr = cidr.overlaps "192.168.1.0/24" "192.168.0.0/16";
expected = true;
};
testOverlapsFalse = {
expr = cidr.overlaps "192.168.1.0/24" "10.0.0.0/8";
expected = false;
};
testOverlapsAdjacent = {
expr = cidr.overlaps "192.168.1.0/24" "192.168.2.0/24";
expected = false;
};
testPrefixForHosts100 = {
expr = cidr.prefixForHosts 100;
expected = 25;
};
testPrefixForHosts254 = {
expr = cidr.prefixForHosts 254;
expected = 24;
};
testPrefixForHosts1 = {
expr = cidr.prefixForHosts 1;
expected = 32;
};
testPrefixForSize256 = {
expr = cidr.prefixForSize 256;
expected = 24;
};
testPrefixForSize1 = {
expr = cidr.prefixForSize 1;
expected = 32;
};
}

43
tests/default.nix Normal file
View File

@@ -0,0 +1,43 @@
{
lib,
ipLib,
system,
}:
let
ipTests = import ./ip-tests.nix {
inherit lib;
ip = ipLib.ip;
};
cidrTests = import ./cidr-tests.nix {
inherit lib;
cidr = ipLib.cidr;
};
validateTests = import ./validate-tests.nix {
inherit lib;
validate = ipLib.validate;
};
iterateTests = import ./iterate-tests.nix {
inherit lib;
iterate = ipLib.iterate;
ip = ipLib.ip;
};
allTests = ipTests ++ cidrTests ++ validateTests ++ iterateTests;
in
if allTests == [ ] then
derivation {
name = "nix-ip-utils-tests";
inherit system;
builder = "/bin/sh";
args = [
"-c"
"echo 'passed!' > $out"
];
}
else
throw "Tests failed: ${builtins.toJSON allTests}"

205
tests/ip-tests.nix Normal file
View File

@@ -0,0 +1,205 @@
{ lib, ip }:
let
inherit (lib) runTests;
in
runTests {
testParseBasic = {
expr = ip.parse "192.168.1.1";
expected = 3232235777;
};
testParseZeros = {
expr = ip.parse "0.0.0.0";
expected = 0;
};
testParseMax = {
expr = ip.parse "255.255.255.255";
expected = 4294967295;
};
testParseLoopback = {
expr = ip.parse "127.0.0.1";
expected = 2130706433;
};
testParseTen = {
expr = ip.parse "10.0.0.1";
expected = 167772161;
};
testParsePassthroughInt = {
expr = ip.parse 3232235777;
expected = 3232235777;
};
testFormatBasic = {
expr = ip.format 3232235777;
expected = "192.168.1.1";
};
testFormatZeros = {
expr = ip.format 0;
expected = "0.0.0.0";
};
testFormatMax = {
expr = ip.format 4294967295;
expected = "255.255.255.255";
};
testFormatPassthroughString = {
expr = ip.format "192.168.1.1";
expected = "192.168.1.1";
};
testToOctetsBasic = {
expr = ip.toOctets "192.168.1.1";
expected = [
192
168
1
1
];
};
testToOctetsFromInt = {
expr = ip.toOctets 3232235777;
expected = [
192
168
1
1
];
};
testToOctetsZeros = {
expr = ip.toOctets "0.0.0.0";
expected = [
0
0
0
0
];
};
testFromOctetsBasic = {
expr = ip.fromOctets [
192
168
1
1
];
expected = 3232235777;
};
testFromOctetsZeros = {
expr = ip.fromOctets [
0
0
0
0
];
expected = 0;
};
testFromOctetsTen = {
expr = ip.fromOctets [
10
0
0
1
];
expected = 167772161;
};
testAddBasic = {
expr = ip.add "192.168.1.1" 10;
expected = 3232235787;
};
testAddStrBasic = {
expr = ip.addStr "192.168.1.1" 10;
expected = "192.168.1.11";
};
testAddCrossOctet = {
expr = ip.addStr "192.168.1.250" 10;
expected = "192.168.2.4";
};
testSubtractBasic = {
expr = ip.subtract "192.168.1.10" 5;
expected = 3232235781;
};
testSubtractStrBasic = {
expr = ip.subtractStr "192.168.1.10" 5;
expected = "192.168.1.5";
};
testDiffBasic = {
expr = ip.diff "192.168.1.10" "192.168.1.1";
expected = 9;
};
testDiffNegative = {
expr = ip.diff "192.168.1.1" "192.168.1.10";
expected = -9;
};
testCompareLess = {
expr = ip.compare "10.0.0.1" "10.0.0.2";
expected = -1;
};
testCompareGreater = {
expr = ip.compare "10.0.0.2" "10.0.0.1";
expected = 1;
};
testCompareEqual = {
expr = ip.compare "10.0.0.1" "10.0.0.1";
expected = 0;
};
testLtTrue = {
expr = ip.lt "10.0.0.1" "10.0.0.2";
expected = true;
};
testLtFalse = {
expr = ip.lt "10.0.0.2" "10.0.0.1";
expected = false;
};
testGtTrue = {
expr = ip.gt "10.0.0.2" "10.0.0.1";
expected = true;
};
testEqTrue = {
expr = ip.eq "10.0.0.1" "10.0.0.1";
expected = true;
};
testEqFalse = {
expr = ip.eq "10.0.0.1" "10.0.0.2";
expected = false;
};
testMinStr = {
expr = ip.minStr "10.0.0.5" "10.0.0.2";
expected = "10.0.0.2";
};
testMaxStr = {
expr = ip.maxStr "10.0.0.5" "10.0.0.2";
expected = "10.0.0.5";
};
testRoundtrip = {
expr = ip.format (ip.parse "172.16.32.64");
expected = "172.16.32.64";
};
}

195
tests/iterate-tests.nix Normal file
View File

@@ -0,0 +1,195 @@
{
lib,
iterate,
ip,
}:
let
inherit (lib) runTests;
in
runTests {
testRangeBasic = {
expr = iterate.rangeStr "10.0.0.1" "10.0.0.3";
expected = [
"10.0.0.1"
"10.0.0.2"
"10.0.0.3"
];
};
testRangeSingle = {
expr = iterate.rangeStr "10.0.0.1" "10.0.0.1";
expected = [ "10.0.0.1" ];
};
testRangeEmpty = {
expr = iterate.rangeStr "10.0.0.5" "10.0.0.1";
expected = [ ];
};
testRangeCrossOctet = {
expr = iterate.rangeStr "10.0.0.254" "10.0.1.2";
expected = [
"10.0.0.254"
"10.0.0.255"
"10.0.1.0"
"10.0.1.1"
"10.0.1.2"
];
};
testCidrAddresses30 = {
expr = iterate.cidrAddressesStr "10.0.0.0/30";
expected = [
"10.0.0.0"
"10.0.0.1"
"10.0.0.2"
"10.0.0.3"
];
};
testCidrAddresses32 = {
expr = iterate.cidrAddressesStr "10.0.0.1/32";
expected = [ "10.0.0.1" ];
};
testCidrAddressesCount = {
expr = builtins.length (iterate.cidrAddresses "10.0.0.0/28");
expected = 16;
};
testCidrHosts30 = {
expr = iterate.cidrHostsStr "10.0.0.0/30";
expected = [
"10.0.0.1"
"10.0.0.2"
];
};
testCidrHosts31 = {
expr = iterate.cidrHostsStr "10.0.0.0/31";
expected = [
"10.0.0.0"
"10.0.0.1"
];
};
testCidrHosts32 = {
expr = iterate.cidrHostsStr "10.0.0.1/32";
expected = [ "10.0.0.1" ];
};
testCidrHostsCount = {
expr = builtins.length (iterate.cidrHosts "10.0.0.0/28");
expected = 14;
};
testInRangeTrue = {
expr = iterate.inRange "10.0.0.1" "10.0.0.5" "10.0.0.3";
expected = true;
};
testInRangeStart = {
expr = iterate.inRange "10.0.0.1" "10.0.0.5" "10.0.0.1";
expected = true;
};
testInRangeEnd = {
expr = iterate.inRange "10.0.0.1" "10.0.0.5" "10.0.0.5";
expected = true;
};
testInRangeFalseLow = {
expr = iterate.inRange "10.0.0.2" "10.0.0.5" "10.0.0.1";
expected = false;
};
testInRangeFalseHigh = {
expr = iterate.inRange "10.0.0.1" "10.0.0.5" "10.0.0.6";
expected = false;
};
testCountRangeBasic = {
expr = iterate.countRange "10.0.0.1" "10.0.0.10";
expected = 10;
};
testCountRangeSingle = {
expr = iterate.countRange "10.0.0.1" "10.0.0.1";
expected = 1;
};
testCountRangeEmpty = {
expr = iterate.countRange "10.0.0.5" "10.0.0.1";
expected = 0;
};
testMapRangeFormat = {
expr = iterate.mapRange ip.format "10.0.0.1" "10.0.0.3";
expected = [
"10.0.0.1"
"10.0.0.2"
"10.0.0.3"
];
};
testMapRangeOctets = {
expr = iterate.mapRange ip.toOctets "10.0.0.1" "10.0.0.2";
expected = [
[
10
0
0
1
]
[
10
0
0
2
]
];
};
testFilterRangeEven = {
expr = builtins.map ip.format (iterate.filterRange (i: lib.mod i 2 == 0) "10.0.0.1" "10.0.0.5");
expected = [
"10.0.0.2"
"10.0.0.4"
];
};
testTakeRangeBasic = {
expr = iterate.takeRangeStr 3 "10.0.0.1" "10.0.0.10";
expected = [
"10.0.0.1"
"10.0.0.2"
"10.0.0.3"
];
};
testTakeRangeMoreThanAvailable = {
expr = iterate.takeRangeStr 10 "10.0.0.1" "10.0.0.3";
expected = [
"10.0.0.1"
"10.0.0.2"
"10.0.0.3"
];
};
testTakeRangeZero = {
expr = iterate.takeRangeStr 0 "10.0.0.1" "10.0.0.10";
expected = [ ];
};
testFindInRangeBasic = {
expr = iterate.findInRangeStr (i: lib.mod i 5 == 0) "10.0.0.1" "10.0.0.10";
expected = "10.0.0.5";
};
testFindInRangeNotFound = {
expr = iterate.findInRange (i: i > 1000000000) "10.0.0.1" "10.0.0.10";
expected = null;
};
}

266
tests/validate-tests.nix Normal file
View File

@@ -0,0 +1,266 @@
{ lib, validate }:
let
inherit (lib) runTests;
in runTests {
testIsValidIpBasic = {
expr = validate.isValidIp "192.168.1.1";
expected = true;
};
testIsValidIpZeros = {
expr = validate.isValidIp "0.0.0.0";
expected = true;
};
testIsValidIpMax = {
expr = validate.isValidIp "255.255.255.255";
expected = true;
};
testIsValidIpInvalidOctet = {
expr = validate.isValidIp "256.1.1.1";
expected = false;
};
testIsValidIpTooFewOctets = {
expr = validate.isValidIp "192.168.1";
expected = false;
};
testIsValidIpTooManyOctets = {
expr = validate.isValidIp "192.168.1.1.1";
expected = false;
};
testIsValidIpEmpty = {
expr = validate.isValidIp "";
expected = false;
};
testIsValidIpLetters = {
expr = validate.isValidIp "abc.def.ghi.jkl";
expected = false;
};
testIsValidIpNegative = {
expr = validate.isValidIp "-1.0.0.0";
expected = false;
};
testIsValidIpNotString = {
expr = validate.isValidIp 12345;
expected = false;
};
testIsValidCidrBasic = {
expr = validate.isValidCidr "192.168.1.0/24";
expected = true;
};
testIsValidCidrSlash0 = {
expr = validate.isValidCidr "0.0.0.0/0";
expected = true;
};
testIsValidCidrSlash32 = {
expr = validate.isValidCidr "192.168.1.1/32";
expected = true;
};
testIsValidCidrInvalidPrefix = {
expr = validate.isValidCidr "192.168.1.0/33";
expected = false;
};
testIsValidCidrNoPrefix = {
expr = validate.isValidCidr "192.168.1.0";
expected = false;
};
testIsValidCidrInvalidIp = {
expr = validate.isValidCidr "256.168.1.0/24";
expected = false;
};
testIsValidCidrEmpty = {
expr = validate.isValidCidr "";
expected = false;
};
testIsValidCidrNotString = {
expr = validate.isValidCidr 12345;
expected = false;
};
testTryParseIpValid = {
expr = validate.tryParseIp "192.168.1.1";
expected = 3232235777;
};
testTryParseIpInvalid = {
expr = validate.tryParseIp "invalid";
expected = null;
};
testTryParseCidrValid = {
expr = validate.tryParseCidr "192.168.1.0/24";
expected = { address = 3232235776; prefixLen = 24; };
};
testTryParseCidrInvalid = {
expr = validate.tryParseCidr "invalid";
expected = null;
};
testIsPrivate10 = {
expr = validate.isPrivate "10.1.2.3";
expected = true;
};
testIsPrivate172 = {
expr = validate.isPrivate "172.16.5.10";
expected = true;
};
testIsPrivate172Edge = {
expr = validate.isPrivate "172.31.255.255";
expected = true;
};
testIsPrivate172Outside = {
expr = validate.isPrivate "172.32.0.0";
expected = false;
};
testIsPrivate192 = {
expr = validate.isPrivate "192.168.100.50";
expected = true;
};
testIsPrivatePublic = {
expr = validate.isPrivate "8.8.8.8";
expected = false;
};
testIsLoopback127 = {
expr = validate.isLoopback "127.0.0.1";
expected = true;
};
testIsLoopback127Any = {
expr = validate.isLoopback "127.255.255.254";
expected = true;
};
testIsLoopbackFalse = {
expr = validate.isLoopback "128.0.0.1";
expected = false;
};
testIsLinkLocalTrue = {
expr = validate.isLinkLocal "169.254.1.1";
expected = true;
};
testIsLinkLocalFalse = {
expr = validate.isLinkLocal "169.255.1.1";
expected = false;
};
testIsMulticastTrue = {
expr = validate.isMulticast "224.0.0.1";
expected = true;
};
testIsMulticastMax = {
expr = validate.isMulticast "239.255.255.255";
expected = true;
};
testIsMulticastFalse = {
expr = validate.isMulticast "223.255.255.255";
expected = false;
};
testIsBroadcastTrue = {
expr = validate.isBroadcast "255.255.255.255";
expected = true;
};
testIsBroadcastFalse = {
expr = validate.isBroadcast "255.255.255.254";
expected = false;
};
testIsUnspecifiedTrue = {
expr = validate.isUnspecified "0.0.0.0";
expected = true;
};
testIsUnspecifiedFalse = {
expr = validate.isUnspecified "0.0.0.1";
expected = false;
};
testIsDocumentation192 = {
expr = validate.isDocumentation "192.0.2.1";
expected = true;
};
testIsDocumentation198 = {
expr = validate.isDocumentation "198.51.100.50";
expected = true;
};
testIsDocumentation203 = {
expr = validate.isDocumentation "203.0.113.100";
expected = true;
};
testIsDocumentationFalse = {
expr = validate.isDocumentation "192.0.3.1";
expected = false;
};
testIsGlobalUnicastPublic = {
expr = validate.isGlobalUnicast "8.8.8.8";
expected = true;
};
testIsGlobalUnicastPrivate = {
expr = validate.isGlobalUnicast "192.168.1.1";
expected = false;
};
testIsGlobalUnicastLoopback = {
expr = validate.isGlobalUnicast "127.0.0.1";
expected = false;
};
testClassifyLoopback = {
expr = validate.classify "127.0.0.1";
expected = "loopback";
};
testClassifyPrivate = {
expr = validate.classify "192.168.1.1";
expected = "private";
};
testClassifyLinkLocal = {
expr = validate.classify "169.254.1.1";
expected = "link-local";
};
testClassifyMulticast = {
expr = validate.classify "224.0.0.1";
expected = "multicast";
};
testClassifyGlobal = {
expr = validate.classify "8.8.8.8";
expected = "global-unicast";
};
}