372 lines
6.2 KiB
Nix
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";
|
|
}
|