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

372 lines
6.2 KiB
Nix

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