init
This commit is contained in:
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal 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
53
flake.nix
Normal 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
664
lib/cidr.nix
Normal 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
42
lib/default.nix
Normal 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
56
lib/internal.nix
Normal 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
392
lib/ip.nix
Normal 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
389
lib/iterate.nix
Normal 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
371
lib/validate.nix
Normal 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
315
tests/cidr-tests.nix
Normal 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
43
tests/default.nix
Normal 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
205
tests/ip-tests.nix
Normal 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
195
tests/iterate-tests.nix
Normal 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
266
tests/validate-tests.nix
Normal 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";
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user