lib/network: init
This commit is contained in:
parent
8ef7d74438
commit
7e5f71e494
|
@ -7,6 +7,7 @@ let
|
|||
callLibs = file: import file { inherit inputs lib self; };
|
||||
in
|
||||
{
|
||||
network = callLibs ./network;
|
||||
path = callLibs ./path.nix;
|
||||
}
|
||||
);
|
||||
|
|
334
lib/network/default.nix
Normal file
334
lib/network/default.nix
Normal file
|
@ -0,0 +1,334 @@
|
|||
# Portions of this file are sourced from
|
||||
# https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba
|
||||
{ lib, ... }:
|
||||
let
|
||||
inherit (import ./internal.nix { inherit lib; }) typechecks builders implementations;
|
||||
in
|
||||
{
|
||||
ip = {
|
||||
# add :: (ip | mac | integer) -> ip -> ip
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Adding integer to IPv4:
|
||||
# > net.ip.add 100 "10.0.0.1"
|
||||
# "10.0.0.101"
|
||||
#
|
||||
# Adding IPv4 to IPv4:
|
||||
# > net.ip.add "127.0.0.1" "10.0.0.1"
|
||||
# "137.0.0.2"
|
||||
#
|
||||
# Adding IPv6 to IPv4:
|
||||
# > net.ip.add "::cafe:beef" "10.0.0.1"
|
||||
# "212.254.186.191"
|
||||
#
|
||||
# Adding MAC to IPv4 (overflows):
|
||||
# > net.ip.add "fe:ed:fa:ce:f0:0d" "10.0.0.1"
|
||||
# "4.206.240.14"
|
||||
#
|
||||
# Adding integer to IPv6:
|
||||
# > net.ip.add 100 "dead:cafe:beef::"
|
||||
# "dead:cafe:beef::64"
|
||||
#
|
||||
# Adding IPv4 to to IPv6:
|
||||
# > net.ip.add "127.0.0.1" "dead:cafe:beef::"
|
||||
# "dead:cafe:beef::7f00:1"
|
||||
#
|
||||
# Adding MAC to IPv6:
|
||||
# > net.ip.add "fe:ed:fa:ce:f0:0d" "dead:cafe:beef::"
|
||||
# "dead:cafe:beef::feed:face:f00d"
|
||||
add =
|
||||
delta: ip:
|
||||
let
|
||||
function = "net.ip.add";
|
||||
delta' = typechecks.numeric function "delta" delta;
|
||||
ip' = typechecks.ip function "ip" ip;
|
||||
in
|
||||
builders.ip (implementations.ip.add delta' ip');
|
||||
|
||||
# diff :: ip -> ip -> (integer | ipv6)
|
||||
#
|
||||
# net.ip.diff is the reverse of net.ip.add:
|
||||
#
|
||||
# net.ip.diff (net.ip.add a b) a = b
|
||||
# net.ip.diff (net.ip.add a b) b = a
|
||||
#
|
||||
# The difference between net.ip.diff and net.ip.subtract is that
|
||||
# net.ip.diff will try its best to return an integer (falling back
|
||||
# to an IPv6 if the result is too big to fit in an integer). This is
|
||||
# useful if you have two hosts that you know are on the same network
|
||||
# and you just want to calculate the offset between them — a result
|
||||
# like "0.0.0.10" is not very useful (which is what you would get
|
||||
# from net.ip.subtract).
|
||||
diff =
|
||||
minuend: subtrahend:
|
||||
let
|
||||
function = "net.ip.diff";
|
||||
minuend' = typechecks.ip function "minuend" minuend;
|
||||
subtrahend' = typechecks.ip function "subtrahend" subtrahend;
|
||||
result = implementations.ip.diff minuend' subtrahend';
|
||||
in
|
||||
if result ? ipv6 then builders.ipv6 result else result;
|
||||
|
||||
# subtract :: (ip | mac | integer) -> ip -> ip
|
||||
#
|
||||
# net.ip.subtract is also the reverse of net.ip.add:
|
||||
#
|
||||
# net.ip.subtract a (net.ip.add a b) = b
|
||||
# net.ip.subtract b (net.ip.add a b) = a
|
||||
#
|
||||
# The difference between net.ip.subtract and net.ip.diff is that
|
||||
# net.ip.subtract will always return the same type as its "ip"
|
||||
# parameter. Its implementation takes the "delta" parameter,
|
||||
# coerces it to be the same type as the "ip" paramter, negates it
|
||||
# (using two's complement), and then adds it to "ip".
|
||||
subtract =
|
||||
delta: ip:
|
||||
let
|
||||
function = "net.ip.subtract";
|
||||
delta' = typechecks.numeric function "delta" delta;
|
||||
ip' = typechecks.ip function "ip" ip;
|
||||
in
|
||||
builders.ip (implementations.ip.subtract delta' ip');
|
||||
};
|
||||
|
||||
mac = {
|
||||
# add :: (ip | mac | integer) -> mac -> mac
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Adding integer to MAC:
|
||||
# > net.mac.add 100 "fe:ed:fa:ce:f0:0d"
|
||||
# "fe:ed:fa:ce:f0:71"
|
||||
#
|
||||
# Adding IPv4 to MAC:
|
||||
# > net.mac.add "127.0.0.1" "fe:ed:fa:ce:f0:0d"
|
||||
# "fe:ee:79:ce:f0:0e"
|
||||
#
|
||||
# Adding IPv6 to MAC:
|
||||
# > net.mac.add "::cafe:beef" "fe:ed:fa:ce:f0:0d"
|
||||
# "fe:ee:c5:cd:aa:cb
|
||||
#
|
||||
# Adding MAC to MAC:
|
||||
# > net.mac.add "fe:ed:fa:00:00:00" "00:00:00:ce:f0:0d"
|
||||
# "fe:ed:fa:ce:f0:0d"
|
||||
add =
|
||||
delta: mac:
|
||||
let
|
||||
function = "net.mac.add";
|
||||
delta' = typechecks.numeric function "delta" delta;
|
||||
mac' = typechecks.mac function "mac" mac;
|
||||
in
|
||||
builders.mac (implementations.mac.add delta' mac');
|
||||
|
||||
# diff :: mac -> mac -> integer
|
||||
#
|
||||
# net.mac.diff is the reverse of net.mac.add:
|
||||
#
|
||||
# net.mac.diff (net.mac.add a b) a = b
|
||||
# net.mac.diff (net.mac.add a b) b = a
|
||||
#
|
||||
# The difference between net.mac.diff and net.mac.subtract is that
|
||||
# net.mac.diff will always return an integer.
|
||||
diff =
|
||||
minuend: subtrahend:
|
||||
let
|
||||
function = "net.mac.diff";
|
||||
minuend' = typechecks.mac function "minuend" minuend;
|
||||
subtrahend' = typechecks.mac function "subtrahend" subtrahend;
|
||||
in
|
||||
implementations.mac.diff minuend' subtrahend';
|
||||
|
||||
# subtract :: (ip | mac | integer) -> mac -> mac
|
||||
#
|
||||
# net.mac.subtract is also the reverse of net.ip.add:
|
||||
#
|
||||
# net.mac.subtract a (net.mac.add a b) = b
|
||||
# net.mac.subtract b (net.mac.add a b) = a
|
||||
#
|
||||
# The difference between net.mac.subtract and net.mac.diff is that
|
||||
# net.mac.subtract will always return a MAC address.
|
||||
subtract =
|
||||
delta: mac:
|
||||
let
|
||||
function = "net.mac.subtract";
|
||||
delta' = typechecks.numeric function "delta" delta;
|
||||
mac' = typechecks.mac function "mac" mac;
|
||||
in
|
||||
builders.mac (implementations.mac.subtract delta' mac');
|
||||
};
|
||||
|
||||
cidr = {
|
||||
# add :: (ip | mac | integer) -> cidr -> cidr
|
||||
#
|
||||
# > net.cidr.add 2 "127.0.0.0/8"
|
||||
# "129.0.0.0/8"
|
||||
#
|
||||
# > net.cidr.add (-2) "127.0.0.0/8"
|
||||
# "125.0.0.0/8"
|
||||
add =
|
||||
delta: cidr:
|
||||
let
|
||||
function = "net.cidr.add";
|
||||
delta' = typechecks.numeric function "delta" delta;
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
builders.cidr (implementations.cidr.add delta' cidr');
|
||||
|
||||
# child :: cidr -> cidr -> bool
|
||||
#
|
||||
# > net.cidr.child "10.10.10.0/24" "10.0.0.0/8"
|
||||
# true
|
||||
#
|
||||
# > net.cidr.child "127.0.0.0/8" "10.0.0.0/8"
|
||||
# false
|
||||
child =
|
||||
subcidr: cidr:
|
||||
let
|
||||
function = "net.cidr.child";
|
||||
subcidr' = typechecks.cidr function "subcidr" subcidr;
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
implementations.cidr.child subcidr' cidr';
|
||||
|
||||
# contains :: ip -> cidr -> bool
|
||||
#
|
||||
# > net.cidr.contains "127.0.0.1" "127.0.0.0/8"
|
||||
# true
|
||||
#
|
||||
# > net.cidr.contains "127.0.0.1" "192.168.0.0/16"
|
||||
# false
|
||||
contains =
|
||||
ip: cidr:
|
||||
let
|
||||
function = "net.cidr.contains";
|
||||
ip' = typechecks.ip function "ip" ip;
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
implementations.cidr.contains ip' cidr';
|
||||
|
||||
# capacity :: cidr -> integer
|
||||
#
|
||||
# > net.cidr.capacity "172.16.0.0/12"
|
||||
# 1048576
|
||||
#
|
||||
# > net.cidr.capacity "dead:cafe:beef::/96"
|
||||
# 4294967296
|
||||
#
|
||||
# > net.cidr.capacity "dead:cafe:beef::/48" (saturates to maxBound)
|
||||
# 9223372036854775807
|
||||
capacity =
|
||||
cidr:
|
||||
let
|
||||
function = "net.cidr.capacity";
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
implementations.cidr.capacity cidr';
|
||||
|
||||
# host :: (ip | mac | integer) -> cidr -> ip
|
||||
#
|
||||
# > net.cidr.host 10000 "10.0.0.0/8"
|
||||
# 10.0.39.16
|
||||
#
|
||||
# > net.cidr.host 10000 "dead:cafe:beef::/64"
|
||||
# "dead:cafe:beef::2710"
|
||||
#
|
||||
# net.cidr.host "127.0.0.1" "dead:cafe:beef::/48"
|
||||
# > "dead:cafe:beef::7f00:1"
|
||||
#
|
||||
# Inpsired by:
|
||||
# https://www.terraform.io/docs/configuration/functions/cidrhost.html
|
||||
host =
|
||||
hostnum: cidr:
|
||||
let
|
||||
function = "net.cidr.host";
|
||||
hostnum' = typechecks.numeric function "hostnum" hostnum;
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
builders.ip (implementations.cidr.host hostnum' cidr');
|
||||
|
||||
# length :: cidr -> integer
|
||||
#
|
||||
# > net.cidr.length "127.0.0.0/8"
|
||||
# 8
|
||||
#
|
||||
# > net.cidr.length "dead:cafe:beef::/48"
|
||||
# 48
|
||||
length =
|
||||
cidr:
|
||||
let
|
||||
function = "net.cidr.length";
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
implementations.cidr.length cidr';
|
||||
|
||||
# make :: integer -> ip -> cidr
|
||||
#
|
||||
# > net.cidr.make 24 "192.168.0.150"
|
||||
# "192.168.0.0/24"
|
||||
#
|
||||
# > net.cidr.make 40 "dead:cafe:beef::feed:face:f00d"
|
||||
# "dead:cafe:be00::/40"
|
||||
make =
|
||||
length: base:
|
||||
let
|
||||
function = "net.cidr.make";
|
||||
length' = typechecks.int function "length" length;
|
||||
base' = typechecks.ip function "base" base;
|
||||
in
|
||||
builders.cidr (implementations.cidr.make length' base');
|
||||
|
||||
# netmask :: cidr -> ip
|
||||
#
|
||||
# > net.cidr.netmask "192.168.0.0/24"
|
||||
# "255.255.255.0"
|
||||
#
|
||||
# > net.cidr.netmask "dead:cafe:beef::/64"
|
||||
# "ffff:ffff:ffff:ffff::"
|
||||
netmask =
|
||||
cidr:
|
||||
let
|
||||
function = "net.cidr.netmask";
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
builders.ip (implementations.cidr.netmask cidr');
|
||||
|
||||
# size :: cidr -> integer
|
||||
#
|
||||
# > net.cidr.size "127.0.0.0/8"
|
||||
# 24
|
||||
#
|
||||
# > net.cidr.size "dead:cafe:beef::/48"
|
||||
# 80
|
||||
size =
|
||||
cidr:
|
||||
let
|
||||
function = "net.cidr.size";
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
implementations.cidr.size cidr';
|
||||
|
||||
# subnet :: integer -> (ip | mac | integer) -> cidr -> cidr
|
||||
#
|
||||
# > net.cidr.subnet 4 2 "172.16.0.0/12"
|
||||
# "172.18.0.0/16"
|
||||
#
|
||||
# > net.cidr.subnet 4 15 "10.1.2.0/24"
|
||||
# "10.1.2.240/28"
|
||||
#
|
||||
# > net.cidr.subnet 16 162 "fd00:fd12:3456:7890::/56"
|
||||
# "fd00:fd12:3456:7800:a200::/72"
|
||||
#
|
||||
# Inspired by:
|
||||
# https://www.terraform.io/docs/configuration/functions/cidrsubnet.html
|
||||
subnet =
|
||||
length: netnum: cidr:
|
||||
let
|
||||
function = "net.cidr.subnet";
|
||||
length' = typechecks.int function "length" length;
|
||||
netnum' = typechecks.numeric function "netnum" netnum;
|
||||
cidr' = typechecks.cidr function "cidr" cidr;
|
||||
in
|
||||
builders.cidr (implementations.cidr.subnet length' netnum' cidr');
|
||||
};
|
||||
}
|
917
lib/network/internal.nix
Normal file
917
lib/network/internal.nix
Normal file
|
@ -0,0 +1,917 @@
|
|||
# Portions of this file are sourced from
|
||||
# https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba
|
||||
{ ... }:
|
||||
let
|
||||
list = {
|
||||
cons = a: b: [ a ] ++ b;
|
||||
};
|
||||
|
||||
bit = rec {
|
||||
shift =
|
||||
n: x:
|
||||
if n < 0 then
|
||||
x * math.pow 2 (-n)
|
||||
else
|
||||
let
|
||||
safeDiv = n: d: if d == 0 then 0 else n / d;
|
||||
d = math.pow 2 n;
|
||||
in
|
||||
if x < 0 then not (safeDiv (not x) d) else safeDiv x d;
|
||||
|
||||
left = n: shift (-n);
|
||||
|
||||
right = shift;
|
||||
|
||||
and = builtins.bitAnd;
|
||||
|
||||
or = builtins.bitOr;
|
||||
|
||||
xor = builtins.bitXor;
|
||||
|
||||
not = xor (-1);
|
||||
|
||||
mask = n: and (left n 1 - 1);
|
||||
};
|
||||
|
||||
math = rec {
|
||||
max = a: b: if a > b then a else b;
|
||||
|
||||
min = a: b: if a < b then a else b;
|
||||
|
||||
clamp =
|
||||
a: b: c:
|
||||
max a (min b c);
|
||||
|
||||
pow =
|
||||
x: n:
|
||||
if n == 0 then
|
||||
1
|
||||
else if bit.and n 1 != 0 then
|
||||
x * pow (x * x) ((n - 1) / 2)
|
||||
else
|
||||
pow (x * x) (n / 2);
|
||||
};
|
||||
|
||||
parsers =
|
||||
let
|
||||
# fmap :: (a -> b) -> parser a -> parser b
|
||||
fmap = f: ma: bind ma (a: pure (f a));
|
||||
|
||||
# pure :: a -> parser a
|
||||
pure = a: string: {
|
||||
leftovers = string;
|
||||
result = a;
|
||||
};
|
||||
|
||||
# liftA2 :: (a -> b -> c) -> parser a -> parser b -> parser c
|
||||
liftA2 =
|
||||
f: ma: mb:
|
||||
bind ma (a: bind mb (b: pure (f a b)));
|
||||
liftA3 =
|
||||
f: a: b:
|
||||
ap (liftA2 f a b);
|
||||
liftA4 =
|
||||
f: a: b: c:
|
||||
ap (liftA3 f a b c);
|
||||
liftA5 =
|
||||
f: a: b: c: d:
|
||||
ap (liftA4 f a b c d);
|
||||
liftA6 =
|
||||
f: a: b: c: d: e:
|
||||
ap (liftA5 f a b c d e);
|
||||
|
||||
# ap :: parser (a -> b) -> parser a -> parser b
|
||||
ap = liftA2 (a: a);
|
||||
|
||||
# then_ :: parser a -> parser b -> parser b
|
||||
then_ = liftA2 (_a: b: b);
|
||||
|
||||
# empty :: parser a
|
||||
empty = _string: null;
|
||||
|
||||
# alt :: parser a -> parser a -> parser a
|
||||
alt =
|
||||
left: right: string:
|
||||
let
|
||||
result = left string;
|
||||
in
|
||||
if builtins.isNull result then right string else result;
|
||||
|
||||
# guard :: bool -> parser {}
|
||||
guard = condition: if condition then pure { } else empty;
|
||||
|
||||
# mfilter :: (a -> bool) -> parser a -> parser a
|
||||
mfilter = f: parser: bind parser (a: then_ (guard (f a)) (pure a));
|
||||
|
||||
# some :: parser a -> parser [a]
|
||||
some = v: liftA2 list.cons v (many v);
|
||||
|
||||
# many :: parser a -> parser [a]
|
||||
many = v: alt (some v) (pure [ ]);
|
||||
|
||||
# bind :: parser a -> (a -> parser b) -> parser b
|
||||
bind =
|
||||
parser: f: string:
|
||||
let
|
||||
a = parser string;
|
||||
in
|
||||
if builtins.isNull a then null else f a.result a.leftovers;
|
||||
|
||||
# run :: parser a -> string -> maybe a
|
||||
run =
|
||||
parser: string:
|
||||
let
|
||||
result = parser string;
|
||||
in
|
||||
if builtins.isNull result || result.leftovers != "" then null else result.result;
|
||||
|
||||
next =
|
||||
string:
|
||||
if string == "" then
|
||||
null
|
||||
else
|
||||
{
|
||||
leftovers = builtins.substring 1 (-1) string;
|
||||
result = builtins.substring 0 1 string;
|
||||
};
|
||||
|
||||
# Count how many characters were consumed by a parser
|
||||
count =
|
||||
parser: string:
|
||||
let
|
||||
result = parser string;
|
||||
in
|
||||
if builtins.isNull result then
|
||||
null
|
||||
else
|
||||
result
|
||||
// {
|
||||
result = {
|
||||
inherit (result) result;
|
||||
count = with result; builtins.stringLength string - builtins.stringLength leftovers;
|
||||
};
|
||||
};
|
||||
|
||||
# Limit the parser to n characters at most
|
||||
limit = n: parser: fmap (a: a.result) (mfilter (a: a.count <= n) (count parser));
|
||||
|
||||
# Ensure the parser consumes exactly n characters
|
||||
exactly = n: parser: fmap (a: a.result) (mfilter (a: a.count == n) (count parser));
|
||||
|
||||
char = c: bind next (c': guard (c == c'));
|
||||
|
||||
string =
|
||||
css:
|
||||
if css == "" then
|
||||
pure { }
|
||||
else
|
||||
let
|
||||
c = builtins.substring 0 1 css;
|
||||
cs = builtins.substring 1 (-1) css;
|
||||
in
|
||||
then_ (char c) (string cs);
|
||||
|
||||
digit = set: bind next (c: then_ (guard (builtins.hasAttr c set)) (pure (builtins.getAttr c set)));
|
||||
|
||||
decimalDigits = {
|
||||
"0" = 0;
|
||||
"1" = 1;
|
||||
"2" = 2;
|
||||
"3" = 3;
|
||||
"4" = 4;
|
||||
"5" = 5;
|
||||
"6" = 6;
|
||||
"7" = 7;
|
||||
"8" = 8;
|
||||
"9" = 9;
|
||||
};
|
||||
|
||||
hexadecimalDigits = decimalDigits // {
|
||||
"a" = 10;
|
||||
"b" = 11;
|
||||
"c" = 12;
|
||||
"d" = 13;
|
||||
"e" = 14;
|
||||
"f" = 15;
|
||||
"A" = 10;
|
||||
"B" = 11;
|
||||
"C" = 12;
|
||||
"D" = 13;
|
||||
"E" = 14;
|
||||
"F" = 15;
|
||||
};
|
||||
|
||||
fromDecimalDigits = builtins.foldl' (a: c: a * 10 + c) 0;
|
||||
fromHexadecimalDigits = builtins.foldl' (a: bit.or (bit.left 4 a)) 0;
|
||||
|
||||
# disallow leading zeros
|
||||
decimal = bind (digit decimalDigits) (
|
||||
n:
|
||||
if n == 0 then
|
||||
pure 0
|
||||
else
|
||||
fmap (ns: fromDecimalDigits (list.cons n ns)) (many (digit decimalDigits))
|
||||
);
|
||||
|
||||
hexadecimal = fmap fromHexadecimalDigits (some (digit hexadecimalDigits));
|
||||
|
||||
ipv4 =
|
||||
let
|
||||
dot = char ".";
|
||||
|
||||
octet = mfilter (n: n < 256) decimal;
|
||||
|
||||
octet' = then_ dot octet;
|
||||
|
||||
fromOctets = a: b: c: d: {
|
||||
ipv4 = bit.or (bit.left 8 (bit.or (bit.left 8 (bit.or (bit.left 8 a) b)) c)) d;
|
||||
};
|
||||
in
|
||||
liftA4 fromOctets octet octet' octet' octet';
|
||||
|
||||
# This is more or less a literal translation of
|
||||
# https://hackage.haskell.org/package/ip/docs/src/Net.IPv6.html#parser
|
||||
ipv6 =
|
||||
let
|
||||
colon = char ":";
|
||||
|
||||
hextet = limit 4 hexadecimal;
|
||||
|
||||
fromHextets =
|
||||
hextets:
|
||||
if builtins.length hextets != 8 then
|
||||
empty
|
||||
else
|
||||
let
|
||||
a = builtins.elemAt hextets 0;
|
||||
b = builtins.elemAt hextets 1;
|
||||
c = builtins.elemAt hextets 2;
|
||||
d = builtins.elemAt hextets 3;
|
||||
e = builtins.elemAt hextets 4;
|
||||
f = builtins.elemAt hextets 5;
|
||||
g = builtins.elemAt hextets 6;
|
||||
h = builtins.elemAt hextets 7;
|
||||
in
|
||||
pure {
|
||||
ipv6 = {
|
||||
a = bit.or (bit.left 16 a) b;
|
||||
b = bit.or (bit.left 16 c) d;
|
||||
c = bit.or (bit.left 16 e) f;
|
||||
d = bit.or (bit.left 16 g) h;
|
||||
};
|
||||
};
|
||||
|
||||
ipv4' = fmap (
|
||||
address:
|
||||
let
|
||||
upper = bit.right 16 address.ipv4;
|
||||
lower = bit.mask 16 address.ipv4;
|
||||
in
|
||||
[
|
||||
upper
|
||||
lower
|
||||
]
|
||||
) ipv4;
|
||||
|
||||
part =
|
||||
n:
|
||||
let
|
||||
n' = n + 1;
|
||||
hex = liftA2 list.cons hextet (then_ colon (alt (then_ colon (doubleColon n')) (part n')));
|
||||
in
|
||||
if n == 7 then
|
||||
fmap (a: [ a ]) hextet
|
||||
else if n == 6 then
|
||||
alt ipv4' hex
|
||||
else
|
||||
hex;
|
||||
|
||||
doubleColon =
|
||||
n:
|
||||
bind (alt afterDoubleColon (pure [ ])) (
|
||||
rest:
|
||||
let
|
||||
missing = 8 - n - builtins.length rest;
|
||||
in
|
||||
if missing < 0 then empty else pure (builtins.genList (_: 0) missing ++ rest)
|
||||
);
|
||||
|
||||
afterDoubleColon = alt ipv4' (
|
||||
liftA2 list.cons hextet (alt (then_ colon afterDoubleColon) (pure [ ]))
|
||||
);
|
||||
in
|
||||
bind (alt (then_ (string "::") (doubleColon 0)) (part 0)) fromHextets;
|
||||
|
||||
cidrv4 = liftA2 (base: length: implementations.cidr.make length base) ipv4 (
|
||||
then_ (char "/") (mfilter (n: n <= 32) decimal)
|
||||
);
|
||||
|
||||
cidrv6 = liftA2 (base: length: implementations.cidr.make length base) ipv6 (
|
||||
then_ (char "/") (mfilter (n: n <= 128) decimal)
|
||||
);
|
||||
|
||||
mac =
|
||||
let
|
||||
colon = char ":";
|
||||
|
||||
octet = exactly 2 hexadecimal;
|
||||
|
||||
octet' = then_ colon octet;
|
||||
|
||||
fromOctets = a: b: c: d: e: f: {
|
||||
mac = bit.or (bit.left 8 (
|
||||
bit.or (bit.left 8 (bit.or (bit.left 8 (bit.or (bit.left 8 (bit.or (bit.left 8 a) b)) c)) d)) e
|
||||
)) f;
|
||||
};
|
||||
in
|
||||
liftA6 fromOctets octet octet' octet' octet' octet' octet';
|
||||
in
|
||||
{
|
||||
ipv4 = run ipv4;
|
||||
ipv6 = run ipv6;
|
||||
ip = run (alt ipv4 ipv6);
|
||||
cidrv4 = run cidrv4;
|
||||
cidrv6 = run cidrv6;
|
||||
cidr = run (alt cidrv4 cidrv6);
|
||||
mac = run mac;
|
||||
numeric = run (alt (alt ipv4 ipv6) mac);
|
||||
};
|
||||
|
||||
builders =
|
||||
let
|
||||
ipv4 =
|
||||
address:
|
||||
let
|
||||
abcd = address.ipv4;
|
||||
abc = bit.right 8 abcd;
|
||||
ab = bit.right 8 abc;
|
||||
a = bit.right 8 ab;
|
||||
b = bit.mask 8 ab;
|
||||
c = bit.mask 8 abc;
|
||||
d = bit.mask 8 abcd;
|
||||
in
|
||||
builtins.concatStringsSep "." (
|
||||
map toString [
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
]
|
||||
);
|
||||
|
||||
# This is more or less a literal translation of
|
||||
# https://hackage.haskell.org/package/ip/docs/src/Net.IPv6.html#encode
|
||||
ipv6 =
|
||||
address:
|
||||
let
|
||||
digits = "0123456789abcdef";
|
||||
|
||||
toHexString =
|
||||
n:
|
||||
let
|
||||
rest = bit.right 4 n;
|
||||
current = bit.mask 4 n;
|
||||
prefix = if rest == 0 then "" else toHexString rest;
|
||||
in
|
||||
"${prefix}${builtins.substring current 1 digits}";
|
||||
in
|
||||
if (with address.ipv6; a == 0 && b == 0 && c == 0 && d > 65535) then
|
||||
"::${ipv4 { ipv4 = address.ipv6.d; }}"
|
||||
else if (with address.ipv6; a == 0 && b == 0 && c == 65535) then
|
||||
"::ffff:${ipv4 { ipv4 = address.ipv6.d; }}"
|
||||
else
|
||||
let
|
||||
a = bit.right 16 address.ipv6.a;
|
||||
b = bit.mask 16 address.ipv6.a;
|
||||
c = bit.right 16 address.ipv6.b;
|
||||
d = bit.mask 16 address.ipv6.b;
|
||||
e = bit.right 16 address.ipv6.c;
|
||||
f = bit.mask 16 address.ipv6.c;
|
||||
g = bit.right 16 address.ipv6.d;
|
||||
h = bit.mask 16 address.ipv6.d;
|
||||
|
||||
hextets = [
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
];
|
||||
|
||||
# calculate the position and size of the longest sequence of
|
||||
# zeroes within the list of hextets
|
||||
longest =
|
||||
let
|
||||
go =
|
||||
i: current: best:
|
||||
if i < builtins.length hextets then
|
||||
let
|
||||
n = builtins.elemAt hextets i;
|
||||
|
||||
current' =
|
||||
if n == 0 then
|
||||
if builtins.isNull current then
|
||||
{
|
||||
size = 1;
|
||||
position = i;
|
||||
}
|
||||
else
|
||||
current
|
||||
// {
|
||||
size = current.size + 1;
|
||||
}
|
||||
else
|
||||
null;
|
||||
|
||||
best' =
|
||||
if n == 0 then
|
||||
if builtins.isNull best then
|
||||
current'
|
||||
else if current'.size > best.size then
|
||||
current'
|
||||
else
|
||||
best
|
||||
else
|
||||
best;
|
||||
in
|
||||
go (i + 1) current' best'
|
||||
else
|
||||
best;
|
||||
in
|
||||
go 0 null null;
|
||||
|
||||
format = hextets: builtins.concatStringsSep ":" (map toHexString hextets);
|
||||
in
|
||||
if builtins.isNull longest then
|
||||
format hextets
|
||||
else
|
||||
let
|
||||
sublist =
|
||||
i: length: xs:
|
||||
map (builtins.elemAt xs) (builtins.genList (x: x + i) length);
|
||||
|
||||
end = longest.position + longest.size;
|
||||
|
||||
before = sublist 0 longest.position hextets;
|
||||
|
||||
after = sublist end (builtins.length hextets - end) hextets;
|
||||
in
|
||||
"${format before}::${format after}";
|
||||
|
||||
ip = address: if address ? ipv4 then ipv4 address else ipv6 address;
|
||||
|
||||
cidrv4 = cidr: "${ipv4 cidr.base}/${toString cidr.length}";
|
||||
|
||||
cidrv6 = cidr: "${ipv6 cidr.base}/${toString cidr.length}";
|
||||
|
||||
cidr = cidr: "${ip cidr.base}/${toString cidr.length}";
|
||||
|
||||
mac =
|
||||
address:
|
||||
let
|
||||
digits = "0123456789abcdef";
|
||||
octet =
|
||||
n:
|
||||
let
|
||||
upper = bit.right 4 n;
|
||||
lower = bit.mask 4 n;
|
||||
in
|
||||
"${builtins.substring upper 1 digits}${builtins.substring lower 1 digits}";
|
||||
in
|
||||
let
|
||||
a = bit.mask 8 (bit.right 40 address.mac);
|
||||
b = bit.mask 8 (bit.right 32 address.mac);
|
||||
c = bit.mask 8 (bit.right 24 address.mac);
|
||||
d = bit.mask 8 (bit.right 16 address.mac);
|
||||
e = bit.mask 8 (bit.right 8 address.mac);
|
||||
f = bit.mask 8 (bit.right 0 address.mac);
|
||||
in
|
||||
"${octet a}:${octet b}:${octet c}:${octet d}:${octet e}:${octet f}";
|
||||
in
|
||||
{
|
||||
inherit
|
||||
ipv4
|
||||
ipv6
|
||||
ip
|
||||
cidrv4
|
||||
cidrv6
|
||||
cidr
|
||||
mac
|
||||
;
|
||||
};
|
||||
|
||||
arithmetic = rec {
|
||||
# or :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
or =
|
||||
a_: b:
|
||||
let
|
||||
a = coerce b a_;
|
||||
in
|
||||
if a ? ipv6 then
|
||||
{
|
||||
ipv6 = {
|
||||
a = bit.or a.ipv6.a b.ipv6.a;
|
||||
b = bit.or a.ipv6.b b.ipv6.b;
|
||||
c = bit.or a.ipv6.c b.ipv6.c;
|
||||
d = bit.or a.ipv6.d b.ipv6.d;
|
||||
};
|
||||
}
|
||||
else if a ? ipv4 then
|
||||
{
|
||||
ipv4 = bit.or a.ipv4 b.ipv4;
|
||||
}
|
||||
else if a ? mac then
|
||||
{
|
||||
mac = bit.or a.mac b.mac;
|
||||
}
|
||||
else
|
||||
bit.or a b;
|
||||
|
||||
# and :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
and =
|
||||
a_: b:
|
||||
let
|
||||
a = coerce b a_;
|
||||
in
|
||||
if a ? ipv6 then
|
||||
{
|
||||
ipv6 = {
|
||||
a = bit.and a.ipv6.a b.ipv6.a;
|
||||
b = bit.and a.ipv6.b b.ipv6.b;
|
||||
c = bit.and a.ipv6.c b.ipv6.c;
|
||||
d = bit.and a.ipv6.d b.ipv6.d;
|
||||
};
|
||||
}
|
||||
else if a ? ipv4 then
|
||||
{
|
||||
ipv4 = bit.and a.ipv4 b.ipv4;
|
||||
}
|
||||
else if a ? mac then
|
||||
{
|
||||
mac = bit.and a.mac b.mac;
|
||||
}
|
||||
else
|
||||
bit.and a b;
|
||||
|
||||
# not :: (ip | mac | integer) -> (ip | mac | integer)
|
||||
not =
|
||||
a:
|
||||
if a ? ipv6 then
|
||||
{
|
||||
ipv6 = {
|
||||
a = bit.mask 32 (bit.not a.ipv6.a);
|
||||
b = bit.mask 32 (bit.not a.ipv6.b);
|
||||
c = bit.mask 32 (bit.not a.ipv6.c);
|
||||
d = bit.mask 32 (bit.not a.ipv6.d);
|
||||
};
|
||||
}
|
||||
else if a ? ipv4 then
|
||||
{
|
||||
ipv4 = bit.mask 32 (bit.not a.ipv4);
|
||||
}
|
||||
else if a ? mac then
|
||||
{
|
||||
mac = bit.mask 48 (bit.not a.mac);
|
||||
}
|
||||
else
|
||||
bit.not a;
|
||||
|
||||
# add :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
add =
|
||||
let
|
||||
split = a: {
|
||||
fst = bit.mask 32 (bit.right 32 a);
|
||||
snd = bit.mask 32 a;
|
||||
};
|
||||
in
|
||||
a_: b:
|
||||
let
|
||||
a = coerce b a_;
|
||||
in
|
||||
if a ? ipv6 then
|
||||
let
|
||||
a' = split (a.ipv6.a + b.ipv6.a + b'.fst);
|
||||
b' = split (a.ipv6.b + b.ipv6.b + c'.fst);
|
||||
c' = split (a.ipv6.c + b.ipv6.c + d'.fst);
|
||||
d' = split (a.ipv6.d + b.ipv6.d);
|
||||
in
|
||||
{
|
||||
ipv6 = {
|
||||
a = a'.snd;
|
||||
b = b'.snd;
|
||||
c = c'.snd;
|
||||
d = d'.snd;
|
||||
};
|
||||
}
|
||||
else if a ? ipv4 then
|
||||
{
|
||||
ipv4 = bit.mask 32 (a.ipv4 + b.ipv4);
|
||||
}
|
||||
else if a ? mac then
|
||||
{
|
||||
mac = bit.mask 48 (a.mac + b.mac);
|
||||
}
|
||||
else
|
||||
a + b;
|
||||
|
||||
# subtract :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
subtract = a: b: add (add 1 (not (coerce b a))) b;
|
||||
|
||||
# diff :: (ip | mac | integer) -> (ip | mac | integer) -> (ipv6 | integer)
|
||||
diff =
|
||||
a: b:
|
||||
let
|
||||
toIPv6 = coerce ({ ipv6.a = 0; });
|
||||
result = (subtract b (toIPv6 a)).ipv6;
|
||||
max32 = bit.left 32 1 - 1;
|
||||
in
|
||||
if
|
||||
result.a == 0 && result.b == 0 && bit.right 31 result.c == 0
|
||||
|| result.a == max32 && result.b == max32 && bit.right 31 result.c == 1
|
||||
then
|
||||
bit.or (bit.left 32 result.c) result.d
|
||||
else
|
||||
{
|
||||
ipv6 = result;
|
||||
};
|
||||
|
||||
# left :: integer -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
left = i: right (-i);
|
||||
|
||||
# right :: integer -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
right =
|
||||
let
|
||||
step = i: x: {
|
||||
_1 = bit.mask 32 (bit.right (i + 96) x);
|
||||
_2 = bit.mask 32 (bit.right (i + 64) x);
|
||||
_3 = bit.mask 32 (bit.right (i + 32) x);
|
||||
_4 = bit.mask 32 (bit.right i x);
|
||||
_5 = bit.mask 32 (bit.right (i - 32) x);
|
||||
_6 = bit.mask 32 (bit.right (i - 64) x);
|
||||
_7 = bit.mask 32 (bit.right (i - 96) x);
|
||||
};
|
||||
ors = builtins.foldl' bit.or 0;
|
||||
in
|
||||
i: x:
|
||||
if x ? ipv6 then
|
||||
let
|
||||
a' = step i x.ipv6.a;
|
||||
b' = step i x.ipv6.b;
|
||||
c' = step i x.ipv6.c;
|
||||
d' = step i x.ipv6.d;
|
||||
in
|
||||
{
|
||||
ipv6 = {
|
||||
a = ors [
|
||||
a'._4
|
||||
b'._3
|
||||
c'._2
|
||||
d'._1
|
||||
];
|
||||
b = ors [
|
||||
a'._5
|
||||
b'._4
|
||||
c'._3
|
||||
d'._2
|
||||
];
|
||||
c = ors [
|
||||
a'._6
|
||||
b'._5
|
||||
c'._4
|
||||
d'._3
|
||||
];
|
||||
d = ors [
|
||||
a'._7
|
||||
b'._6
|
||||
c'._5
|
||||
d'._4
|
||||
];
|
||||
};
|
||||
}
|
||||
else if x ? ipv4 then
|
||||
{
|
||||
ipv4 = bit.mask 32 (bit.right i x.ipv4);
|
||||
}
|
||||
else if x ? mac then
|
||||
{
|
||||
mac = bit.mask 48 (bit.right i x.mac);
|
||||
}
|
||||
else
|
||||
bit.right i x;
|
||||
|
||||
# shadow :: integer -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
shadow = n: a: and (right n (left n (coerce a (-1)))) a;
|
||||
|
||||
# coshadow :: integer -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
coshadow = n: a: and (not (right n (left n (coerce a (-1))))) a;
|
||||
|
||||
# coerce :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer)
|
||||
coerce =
|
||||
target: value:
|
||||
if target ? ipv6 then
|
||||
if value ? ipv6 then
|
||||
value
|
||||
else if value ? ipv4 then
|
||||
{
|
||||
ipv6 = {
|
||||
a = 0;
|
||||
b = 0;
|
||||
c = 0;
|
||||
d = value.ipv4;
|
||||
};
|
||||
}
|
||||
else if value ? mac then
|
||||
{
|
||||
ipv6 = {
|
||||
a = 0;
|
||||
b = 0;
|
||||
c = bit.right 32 value.mac;
|
||||
d = bit.mask 32 value.mac;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ipv6 = {
|
||||
a = bit.mask 32 (bit.right 96 value);
|
||||
b = bit.mask 32 (bit.right 64 value);
|
||||
c = bit.mask 32 (bit.right 32 value);
|
||||
d = bit.mask 32 value;
|
||||
};
|
||||
}
|
||||
else if target ? ipv4 then
|
||||
if value ? ipv6 then
|
||||
{
|
||||
ipv4 = value.ipv6.d;
|
||||
}
|
||||
else if value ? ipv4 then
|
||||
value
|
||||
else if value ? mac then
|
||||
{
|
||||
ipv4 = bit.mask 32 value.mac;
|
||||
}
|
||||
else
|
||||
{
|
||||
ipv4 = bit.mask 32 value;
|
||||
}
|
||||
else if target ? mac then
|
||||
if value ? ipv6 then
|
||||
{
|
||||
mac = bit.or (bit.left 32 (bit.mask 16 value.ipv6.c)) value.ipv6.d;
|
||||
}
|
||||
else if value ? ipv4 then
|
||||
{
|
||||
mac = value.ipv4;
|
||||
}
|
||||
else if value ? mac then
|
||||
value
|
||||
else
|
||||
{
|
||||
mac = bit.mask 48 value;
|
||||
}
|
||||
else if value ? ipv6 then
|
||||
builtins.foldl' bit.or 0 [
|
||||
(bit.left 96 value.ipv6.a)
|
||||
(bit.left 64 value.ipv6.b)
|
||||
(bit.left 32 value.ipv6.c)
|
||||
value.ipv6.d
|
||||
]
|
||||
else if value ? ipv4 then
|
||||
value.ipv4
|
||||
else if value ? mac then
|
||||
value.mac
|
||||
else
|
||||
value;
|
||||
};
|
||||
|
||||
implementations = {
|
||||
ip = {
|
||||
# add :: (ip | mac | integer) -> ip -> ip
|
||||
add = arithmetic.add;
|
||||
|
||||
# diff :: ip -> ip -> (ipv6 | integer)
|
||||
diff = arithmetic.diff;
|
||||
|
||||
# subtract :: (ip | mac | integer) -> ip -> ip
|
||||
subtract = arithmetic.subtract;
|
||||
};
|
||||
|
||||
mac = {
|
||||
# add :: (ip | mac | integer) -> mac -> mac
|
||||
add = arithmetic.add;
|
||||
|
||||
# diff :: mac -> mac -> (ipv6 | integer)
|
||||
diff = arithmetic.diff;
|
||||
|
||||
# subtract :: (ip | mac | integer) -> mac -> mac
|
||||
subtract = arithmetic.subtract;
|
||||
};
|
||||
|
||||
cidr = rec {
|
||||
# add :: (ip | mac | integer) -> cidr -> cidr
|
||||
add =
|
||||
delta: cidr:
|
||||
let
|
||||
size' = size cidr;
|
||||
in
|
||||
{
|
||||
base = arithmetic.left size' (arithmetic.add delta (arithmetic.right size' cidr.base));
|
||||
inherit (cidr) length;
|
||||
};
|
||||
|
||||
# capacity :: cidr -> integer
|
||||
capacity =
|
||||
cidr:
|
||||
let
|
||||
size' = size cidr;
|
||||
in
|
||||
if size' > 62 then
|
||||
9223372036854775807 # maxBound to prevent overflow
|
||||
else
|
||||
bit.left size' 1;
|
||||
|
||||
# child :: cidr -> cidr -> bool
|
||||
child = subcidr: cidr: length subcidr > length cidr && contains (host 0 subcidr) cidr;
|
||||
|
||||
# contains :: ip -> cidr -> bool
|
||||
contains = ip: cidr: host 0 (make cidr.length ip) == host 0 cidr;
|
||||
|
||||
# host :: (ip | mac | integer) -> cidr -> ip
|
||||
host =
|
||||
index: cidr:
|
||||
let
|
||||
index' = arithmetic.coerce cidr.base index;
|
||||
in
|
||||
arithmetic.or (arithmetic.shadow cidr.length index') cidr.base;
|
||||
|
||||
# length :: cidr -> integer
|
||||
length = cidr: cidr.length;
|
||||
|
||||
# netmask :: cidr -> ip
|
||||
netmask = cidr: arithmetic.coshadow cidr.length (arithmetic.coerce cidr.base (-1));
|
||||
|
||||
# size :: cidr -> integer
|
||||
size = cidr: (if cidr.base ? ipv6 then 128 else 32) - cidr.length;
|
||||
|
||||
# subnet :: integer -> (ip | mac | integer) -> cidr -> cidr
|
||||
subnet =
|
||||
length: index: cidr:
|
||||
let
|
||||
length' = cidr.length + length;
|
||||
index' = arithmetic.coerce cidr.base index;
|
||||
size = (if cidr.base ? ipv6 then 128 else 32) - length';
|
||||
in
|
||||
make length' (host (arithmetic.left size index') cidr);
|
||||
|
||||
# make :: integer -> ip -> cidr
|
||||
make =
|
||||
length: base:
|
||||
let
|
||||
length' = math.clamp 0 (if base ? ipv6 then 128 else 32) length;
|
||||
in
|
||||
{
|
||||
base = arithmetic.coshadow length' base;
|
||||
length = length';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
typechecks =
|
||||
let
|
||||
fail =
|
||||
description: function: argument:
|
||||
builtins.throw "${function}: ${argument} parameter must be ${description}";
|
||||
|
||||
meta =
|
||||
parser: description: function: argument: input:
|
||||
let
|
||||
error = fail description function argument;
|
||||
in
|
||||
if !builtins.isString input then
|
||||
error
|
||||
else
|
||||
let
|
||||
result = parser input;
|
||||
in
|
||||
if builtins.isNull result then error else result;
|
||||
in
|
||||
{
|
||||
int =
|
||||
function: argument: input:
|
||||
if builtins.isInt input then input else fail "an integer" function argument;
|
||||
ip = meta parsers.ip "an IPv4 or IPv6 address";
|
||||
cidr = meta parsers.cidr "an IPv4 or IPv6 address range in CIDR notation";
|
||||
mac = meta parsers.mac "a MAC address";
|
||||
numeric =
|
||||
function: argument: input:
|
||||
if builtins.isInt input then
|
||||
input
|
||||
else
|
||||
meta parsers.numeric "an integer or IPv4, IPv6 or MAC address" function argument input;
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit typechecks builders implementations;
|
||||
}
|
Loading…
Reference in a new issue