1379 lines
40 KiB
OCaml
1379 lines
40 KiB
OCaml
|
|
(*
|
||
|
|
* Copyright (c) 2013-2015 David Sheets <sheets@alum.mit.edu>
|
||
|
|
*
|
||
|
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
|
* copyright notice and this permission notice appear in all copies.
|
||
|
|
*
|
||
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
|
*
|
||
|
|
*)
|
||
|
|
|
||
|
|
exception Parse_error of string * string
|
||
|
|
|
||
|
|
type scope = Point | Interface | Link | Admin | Site | Organization | Global
|
||
|
|
|
||
|
|
let try_with_result fn a =
|
||
|
|
try Ok (fn a) with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
|
||
|
|
|
||
|
|
let failwith_msg = function Ok x -> x | Error (`Msg m) -> failwith m
|
||
|
|
|
||
|
|
let string_of_scope = function
|
||
|
|
| Point -> "point"
|
||
|
|
| Interface -> "interface"
|
||
|
|
| Link -> "link"
|
||
|
|
| Admin -> "admin"
|
||
|
|
| Site -> "site"
|
||
|
|
| Organization -> "organization"
|
||
|
|
| Global -> "global"
|
||
|
|
|
||
|
|
let scope_of_string = function
|
||
|
|
| "point" -> Ok Point
|
||
|
|
| "interface" -> Ok Interface
|
||
|
|
| "link" -> Ok Link
|
||
|
|
| "admin" -> Ok Admin
|
||
|
|
| "site" -> Ok Site
|
||
|
|
| "organization" -> Ok Organization
|
||
|
|
| "global" -> Ok Global
|
||
|
|
| s -> Error (`Msg ("unknown scope: " ^ s))
|
||
|
|
|
||
|
|
let pp_scope fmt s = Format.pp_print_string fmt (string_of_scope s)
|
||
|
|
let ( ~| ) = Int32.of_int
|
||
|
|
let ( |~ ) = Int32.to_int
|
||
|
|
let ( &&& ) x y = Int32.logand x y
|
||
|
|
let ( ||| ) x y = Int32.logor x y
|
||
|
|
let ( <|< ) x y = Int32.shift_left x y
|
||
|
|
let ( >|> ) x y = Int32.shift_right_logical x y
|
||
|
|
let ( >! ) x y = x >|> y &&& 0xFF_l
|
||
|
|
let ( <! ) x y = x &&& 0xFF_l <|< y
|
||
|
|
let need_more x = Parse_error ("not enough data", x)
|
||
|
|
let char_0 = int_of_char '0'
|
||
|
|
let char_a = int_of_char 'a'
|
||
|
|
let char_A = int_of_char 'A'
|
||
|
|
|
||
|
|
let int_of_char c =
|
||
|
|
match c with
|
||
|
|
| '0' .. '9' -> Stdlib.int_of_char c - char_0
|
||
|
|
| 'a' .. 'f' -> 10 + Stdlib.int_of_char c - char_a
|
||
|
|
| 'A' .. 'F' -> 10 + Stdlib.int_of_char c - char_A
|
||
|
|
| _ -> -1
|
||
|
|
|
||
|
|
let bad_char i s =
|
||
|
|
let msg = Printf.sprintf "invalid character '%c' at %d" s.[i] i in
|
||
|
|
Parse_error (msg, s)
|
||
|
|
|
||
|
|
let octal_notation s =
|
||
|
|
let msg = Printf.sprintf "octal notation disallowed" in
|
||
|
|
Parse_error (msg, s)
|
||
|
|
|
||
|
|
let is_number base n = n >= 0 && n < base
|
||
|
|
|
||
|
|
let parse_int base s i =
|
||
|
|
let len = String.length s in
|
||
|
|
let rec next prev =
|
||
|
|
let j = !i in
|
||
|
|
if j >= len then prev
|
||
|
|
else
|
||
|
|
let c = s.[j] in
|
||
|
|
let k = int_of_char c in
|
||
|
|
if is_number base k then (
|
||
|
|
incr i;
|
||
|
|
next ((prev * base) + k))
|
||
|
|
else prev
|
||
|
|
in
|
||
|
|
let i = !i in
|
||
|
|
if i < len then
|
||
|
|
if is_number base (int_of_char s.[i]) then next 0 else raise (bad_char i s)
|
||
|
|
else raise (need_more s)
|
||
|
|
|
||
|
|
let parse_dec_int s i = parse_int 10 s i
|
||
|
|
let parse_hex_int s i = parse_int 16 s i
|
||
|
|
|
||
|
|
let expect_char s i c =
|
||
|
|
if !i < String.length s then
|
||
|
|
if s.[!i] <> c then raise (bad_char !i s) else incr i
|
||
|
|
else raise (need_more s)
|
||
|
|
|
||
|
|
let expect_end s i = if String.length s <= !i then () else raise (bad_char !i s)
|
||
|
|
|
||
|
|
let reject_octal s i =
|
||
|
|
if !i + 1 < String.length s then
|
||
|
|
if s.[!i] == '0' && is_number 10 (int_of_char s.[!i + 1]) then
|
||
|
|
raise (octal_notation s)
|
||
|
|
|
||
|
|
module V4 = struct
|
||
|
|
type t = int32
|
||
|
|
|
||
|
|
let compare = Int32.unsigned_compare
|
||
|
|
let make a b c d = ~|a <! 24 ||| (~|b <! 16) ||| (~|c <! 8 ||| (~|d <! 0))
|
||
|
|
|
||
|
|
(* parsing *)
|
||
|
|
|
||
|
|
let parse_dotted_quad s i =
|
||
|
|
reject_octal s i;
|
||
|
|
let a = parse_dec_int s i in
|
||
|
|
expect_char s i '.';
|
||
|
|
reject_octal s i;
|
||
|
|
let b = parse_dec_int s i in
|
||
|
|
expect_char s i '.';
|
||
|
|
reject_octal s i;
|
||
|
|
let c = parse_dec_int s i in
|
||
|
|
expect_char s i '.';
|
||
|
|
reject_octal s i;
|
||
|
|
let d = parse_dec_int s i in
|
||
|
|
let valid a = a land 0xff <> a in
|
||
|
|
if valid a then raise (Parse_error ("first octet out of bounds", s))
|
||
|
|
else if valid b then raise (Parse_error ("second octet out of bounds", s))
|
||
|
|
else if valid c then raise (Parse_error ("third octet out of bounds", s))
|
||
|
|
else if valid d then raise (Parse_error ("fourth octet out of bounds", s))
|
||
|
|
else make a b c d
|
||
|
|
|
||
|
|
(* string conversion *)
|
||
|
|
|
||
|
|
let of_string_raw = parse_dotted_quad
|
||
|
|
|
||
|
|
let of_string_exn s =
|
||
|
|
let o = ref 0 in
|
||
|
|
let x = of_string_raw s o in
|
||
|
|
expect_end s o;
|
||
|
|
x
|
||
|
|
|
||
|
|
let of_string s = try_with_result of_string_exn s
|
||
|
|
|
||
|
|
let with_port_of_string ~default s =
|
||
|
|
try
|
||
|
|
let len = String.length s and o = ref 0 in
|
||
|
|
let ipv4 = of_string_raw s o in
|
||
|
|
if !o < len && s.[!o] = ':' then (
|
||
|
|
incr o;
|
||
|
|
let port = parse_dec_int s o in
|
||
|
|
expect_end s o;
|
||
|
|
Ok (ipv4, port))
|
||
|
|
else (
|
||
|
|
expect_end s o;
|
||
|
|
Ok (ipv4, default))
|
||
|
|
with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
|
||
|
|
|
||
|
|
let to_buffer b i =
|
||
|
|
Printf.bprintf b "%ld.%ld.%ld.%ld" (i >! 24) (i >! 16) (i >! 8) (i >! 0)
|
||
|
|
|
||
|
|
let to_string i =
|
||
|
|
let b = Buffer.create 15 in
|
||
|
|
to_buffer b i;
|
||
|
|
Buffer.contents b
|
||
|
|
|
||
|
|
let pp ppf i = Format.fprintf ppf "%s" (to_string i)
|
||
|
|
|
||
|
|
(* Octets conversion *)
|
||
|
|
|
||
|
|
let of_octets_exn ?(off = 0) bs =
|
||
|
|
try
|
||
|
|
make
|
||
|
|
(Char.code bs.[0 + off])
|
||
|
|
(Char.code bs.[1 + off])
|
||
|
|
(Char.code bs.[2 + off])
|
||
|
|
(Char.code bs.[3 + off])
|
||
|
|
with _ -> raise (need_more bs)
|
||
|
|
|
||
|
|
let of_octets ?off bs = try_with_result (of_octets_exn ?off) bs
|
||
|
|
|
||
|
|
let write_octets_exn ?(off = 0) i b =
|
||
|
|
try
|
||
|
|
Bytes.set b (0 + off) (Char.chr (( |~ ) (i >! 24)));
|
||
|
|
Bytes.set b (1 + off) (Char.chr (( |~ ) (i >! 16)));
|
||
|
|
Bytes.set b (2 + off) (Char.chr (( |~ ) (i >! 8)));
|
||
|
|
Bytes.set b (3 + off) (Char.chr (( |~ ) (i >! 0)))
|
||
|
|
with _ -> raise (need_more (Bytes.to_string b))
|
||
|
|
|
||
|
|
let write_octets ?off i bs = try_with_result (write_octets_exn ?off i) bs
|
||
|
|
|
||
|
|
let to_octets i =
|
||
|
|
String.init 4 (function
|
||
|
|
| 0 -> Char.chr (( |~ ) (i >! 24))
|
||
|
|
| 1 -> Char.chr (( |~ ) (i >! 16))
|
||
|
|
| 2 -> Char.chr (( |~ ) (i >! 8))
|
||
|
|
| 3 -> Char.chr (( |~ ) (i >! 0))
|
||
|
|
| _ -> assert false)
|
||
|
|
|
||
|
|
(* Int32 *)
|
||
|
|
let of_int32 i = i
|
||
|
|
let to_int32 i = i
|
||
|
|
|
||
|
|
(* Int16 *)
|
||
|
|
let of_int16 (a, b) = ~|a <|< 16 ||| ~|b
|
||
|
|
let to_int16 a = (( |~ ) (a >|> 16), ( |~ ) (a &&& 0xFF_FF_l))
|
||
|
|
|
||
|
|
(* MAC *)
|
||
|
|
(* {{:http://tools.ietf.org/html/rfc1112#section-6.2}RFC 1112}. *)
|
||
|
|
let multicast_to_mac i =
|
||
|
|
let macb = Bytes.create 6 in
|
||
|
|
Bytes.set macb 0 (Char.chr 0x01);
|
||
|
|
Bytes.set macb 1 (Char.chr 0x00);
|
||
|
|
Bytes.set macb 2 (Char.chr 0x5E);
|
||
|
|
Bytes.set macb 3 (Char.chr (( |~ ) (i >|> 16 &&& 0x7F_l)));
|
||
|
|
Bytes.set macb 4 (Char.chr (( |~ ) (i >! 8)));
|
||
|
|
Bytes.set macb 5 (Char.chr (( |~ ) (i >! 0)));
|
||
|
|
Macaddr.of_octets_exn (Bytes.to_string macb)
|
||
|
|
|
||
|
|
(* Host *)
|
||
|
|
let to_domain_name i =
|
||
|
|
let name =
|
||
|
|
[
|
||
|
|
Int32.to_string (i >! 0);
|
||
|
|
Int32.to_string (i >! 8);
|
||
|
|
Int32.to_string (i >! 16);
|
||
|
|
Int32.to_string (i >! 24);
|
||
|
|
"in-addr";
|
||
|
|
"arpa";
|
||
|
|
]
|
||
|
|
in
|
||
|
|
Domain_name.(host_exn (of_strings_exn name))
|
||
|
|
|
||
|
|
let of_domain_name n =
|
||
|
|
match Domain_name.to_strings n with
|
||
|
|
| [ a; b; c; d; in_addr; arpa ]
|
||
|
|
when Domain_name.(
|
||
|
|
equal_label arpa "arpa" && equal_label in_addr "in-addr") -> (
|
||
|
|
let conv bits data =
|
||
|
|
let i = Int32.of_int (parse_dec_int data (ref 0)) in
|
||
|
|
if i > 0xFFl then
|
||
|
|
raise (Parse_error ("label with a too big number", data))
|
||
|
|
else i <! bits
|
||
|
|
in
|
||
|
|
try
|
||
|
|
let ( + ) = Int32.add in
|
||
|
|
Some (conv 0 a + conv 8 b + conv 16 c + conv 24 d)
|
||
|
|
with Parse_error _ -> None)
|
||
|
|
| _ -> None
|
||
|
|
|
||
|
|
let succ t =
|
||
|
|
if Int32.equal t 0xFF_FF_FF_FFl then
|
||
|
|
Error (`Msg "Ipaddr: highest address has been reached")
|
||
|
|
else Ok (Int32.succ t)
|
||
|
|
|
||
|
|
let pred t =
|
||
|
|
if Int32.equal t 0x00_00_00_00l then
|
||
|
|
Error (`Msg "Ipaddr: lowest address has been reached")
|
||
|
|
else Ok (Int32.pred t)
|
||
|
|
|
||
|
|
(* constant *)
|
||
|
|
|
||
|
|
let any = make 0 0 0 0
|
||
|
|
let unspecified = make 0 0 0 0
|
||
|
|
let broadcast = make 255 255 255 255
|
||
|
|
let localhost = make 127 0 0 1
|
||
|
|
let nodes = make 224 0 0 1
|
||
|
|
let routers = make 224 0 0 2
|
||
|
|
|
||
|
|
module Prefix = struct
|
||
|
|
type addr = t
|
||
|
|
type t = addr * int
|
||
|
|
|
||
|
|
let compare (pre, sz) (pre', sz') =
|
||
|
|
let c = compare pre pre' in
|
||
|
|
if c = 0 then Stdlib.compare sz sz' else c
|
||
|
|
|
||
|
|
let ip = make
|
||
|
|
|
||
|
|
let mask sz =
|
||
|
|
if sz <= 0 then 0_l
|
||
|
|
else if sz >= 32 then 0x0_FF_FF_FF_FF_l
|
||
|
|
else 0x0_FF_FF_FF_FF_l <|< 32 - sz
|
||
|
|
|
||
|
|
let prefix (pre, sz) = (pre &&& mask sz, sz)
|
||
|
|
let make sz pre = (pre, sz)
|
||
|
|
|
||
|
|
let network_address (pre, sz) addr =
|
||
|
|
pre &&& mask sz ||| (addr &&& Int32.lognot (mask sz))
|
||
|
|
|
||
|
|
(* string conversion *)
|
||
|
|
|
||
|
|
let _of_string_raw s i =
|
||
|
|
let quad = of_string_raw s i in
|
||
|
|
expect_char s i '/';
|
||
|
|
let p = parse_dec_int s i in
|
||
|
|
if p > 32 || p < 0 then raise (Parse_error ("invalid prefix size", s));
|
||
|
|
(p, quad)
|
||
|
|
|
||
|
|
let of_string_raw s i =
|
||
|
|
let p, quad = _of_string_raw s i in
|
||
|
|
make p quad
|
||
|
|
|
||
|
|
let _of_string_exn s =
|
||
|
|
let i = ref 0 in
|
||
|
|
let res = _of_string_raw s i in
|
||
|
|
expect_end s i;
|
||
|
|
res
|
||
|
|
|
||
|
|
let of_string_exn s =
|
||
|
|
let p, quad = _of_string_exn s in
|
||
|
|
make p quad
|
||
|
|
|
||
|
|
let of_string s = try_with_result of_string_exn s
|
||
|
|
|
||
|
|
let _of_netmask_exn ~netmask address =
|
||
|
|
let rec find_greatest_one bits i =
|
||
|
|
if bits = 0_l then i - 1 else find_greatest_one (bits >|> 1) (i + 1)
|
||
|
|
in
|
||
|
|
let one = netmask &&& Int32.neg netmask in
|
||
|
|
let sz = 32 - find_greatest_one one (if one = 0_l then 33 else 0) in
|
||
|
|
if netmask <> mask sz then
|
||
|
|
raise (Parse_error ("invalid netmask", to_string netmask))
|
||
|
|
else make sz address
|
||
|
|
|
||
|
|
let of_netmask_exn ~netmask ~address = _of_netmask_exn ~netmask address
|
||
|
|
|
||
|
|
let of_netmask ~netmask ~address =
|
||
|
|
try_with_result (_of_netmask_exn ~netmask) address
|
||
|
|
|
||
|
|
let to_buffer buf (pre, sz) = Printf.bprintf buf "%a/%d" to_buffer pre sz
|
||
|
|
|
||
|
|
let to_string subnet =
|
||
|
|
let b = Buffer.create 18 in
|
||
|
|
to_buffer b subnet;
|
||
|
|
Buffer.contents b
|
||
|
|
|
||
|
|
let pp ppf i = Format.fprintf ppf "%s" (to_string i)
|
||
|
|
|
||
|
|
let mem ip (pre, sz) =
|
||
|
|
let m = mask sz in
|
||
|
|
ip &&& m = (pre &&& m)
|
||
|
|
|
||
|
|
let subset ~subnet:(pre1, sz1) ~network:(pre2, sz2) =
|
||
|
|
sz1 >= sz2 && mem pre1 (pre2, sz2)
|
||
|
|
|
||
|
|
let of_addr ip = make 32 ip
|
||
|
|
let global = make 0 (ip 0 0 0 0)
|
||
|
|
let relative = make 8 (ip 0 0 0 0)
|
||
|
|
let loopback = make 8 (ip 127 0 0 0)
|
||
|
|
let link = make 16 (ip 169 254 0 0)
|
||
|
|
let multicast = make 4 (ip 224 0 0 0)
|
||
|
|
let multicast_org = make 14 (ip 239 192 0 0)
|
||
|
|
let multicast_admin = make 16 (ip 239 255 0 0)
|
||
|
|
let multicast_link = make 24 (ip 224 0 0 0)
|
||
|
|
|
||
|
|
(* http://tools.ietf.org/html/rfc2365 *)
|
||
|
|
|
||
|
|
let private_10 = make 8 (ip 10 0 0 0)
|
||
|
|
let private_172 = make 12 (ip 172 16 0 0)
|
||
|
|
let private_192 = make 16 (ip 192 168 0 0)
|
||
|
|
|
||
|
|
let private_blocks =
|
||
|
|
[ loopback; link; private_10; private_172; private_192 ]
|
||
|
|
|
||
|
|
let broadcast (pre, sz) =
|
||
|
|
Int32.logor pre (Int32.logxor (mask sz) 0xFF_FF_FF_FFl)
|
||
|
|
|
||
|
|
let network (pre, sz) = pre &&& mask sz
|
||
|
|
let address (addr, _) = addr
|
||
|
|
let bits (_, sz) = sz
|
||
|
|
let netmask subnet = mask (bits subnet)
|
||
|
|
let hostmask cidr = Int32.logxor (netmask cidr) 0xFF_FF_FF_FFl
|
||
|
|
|
||
|
|
let first ((_, sz) as cidr) =
|
||
|
|
if sz > 30 then network cidr else network cidr |> succ |> failwith_msg
|
||
|
|
|
||
|
|
let last ((_, sz) as cidr) =
|
||
|
|
if sz > 30 then broadcast cidr else broadcast cidr |> pred |> failwith_msg
|
||
|
|
|
||
|
|
let hosts ?(usable = true) ((_, sz) as cidr) =
|
||
|
|
let rec iter_seq start stop =
|
||
|
|
if compare (start, 32) (stop, 32) > 0 then Seq.Nil
|
||
|
|
else
|
||
|
|
match succ start with
|
||
|
|
| Ok start_succ -> Seq.Cons (start, fun () -> iter_seq start_succ stop)
|
||
|
|
| Error _ -> Seq.Cons (start, fun () -> Seq.Nil)
|
||
|
|
in
|
||
|
|
if usable && sz = 32 then fun () -> Seq.Nil
|
||
|
|
else
|
||
|
|
let start, stop =
|
||
|
|
if usable then (first cidr, last cidr)
|
||
|
|
else (network cidr, broadcast cidr)
|
||
|
|
in
|
||
|
|
fun () -> iter_seq start stop
|
||
|
|
|
||
|
|
let subnets n ((_, sz) as cidr) =
|
||
|
|
let rec iter_seq start stop steps =
|
||
|
|
if compare (start, 32) (stop, 32) > 0 then Seq.Nil
|
||
|
|
else
|
||
|
|
let prefix = make n start in
|
||
|
|
let start_succ = Int32.add start steps in
|
||
|
|
if start_succ = 0l then Seq.Cons (prefix, fun () -> Seq.Nil)
|
||
|
|
else Seq.Cons (prefix, fun () -> iter_seq start_succ stop steps)
|
||
|
|
in
|
||
|
|
if sz > n || n > 32 then fun () -> Seq.Nil
|
||
|
|
else
|
||
|
|
let start = network cidr in
|
||
|
|
let stop = broadcast cidr in
|
||
|
|
let steps = Int32.add (hostmask cidr) 1l >|> n - sz in
|
||
|
|
fun () -> iter_seq start stop steps
|
||
|
|
end
|
||
|
|
|
||
|
|
(* TODO: this could be optimized with something trie-like *)
|
||
|
|
let scope i =
|
||
|
|
let mem = Prefix.mem i in
|
||
|
|
if mem Prefix.loopback then Interface
|
||
|
|
else if mem Prefix.link then Link
|
||
|
|
else if List.exists mem Prefix.private_blocks then Organization
|
||
|
|
else if i = unspecified then Point
|
||
|
|
else if i = broadcast then Admin
|
||
|
|
else if mem Prefix.relative then Admin
|
||
|
|
else if mem Prefix.multicast then
|
||
|
|
if mem Prefix.multicast_org then Organization
|
||
|
|
else if mem Prefix.multicast_admin then Admin
|
||
|
|
else if mem Prefix.multicast_link then Link
|
||
|
|
else Global
|
||
|
|
else Global
|
||
|
|
|
||
|
|
let is_global i = scope i = Global
|
||
|
|
let is_multicast i = Prefix.(mem i multicast)
|
||
|
|
let is_private i = scope i <> Global
|
||
|
|
|
||
|
|
module Set = Set.Make (struct
|
||
|
|
type nonrec t = t
|
||
|
|
|
||
|
|
let compare (a : t) (b : t) = compare a b
|
||
|
|
end)
|
||
|
|
|
||
|
|
module Map = Map.Make (struct
|
||
|
|
type nonrec t = t
|
||
|
|
|
||
|
|
let compare (a : t) (b : t) = compare a b
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
module S128 : sig
|
||
|
|
exception Overflow
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val zero : t [@@ocaml.warning "-32"]
|
||
|
|
val max_int : t
|
||
|
|
val compare : t -> t -> int
|
||
|
|
val equal : t -> t -> bool
|
||
|
|
val fold_left : ('a -> int -> 'a) -> 'a -> t -> 'a
|
||
|
|
val of_octets_exn : string -> t
|
||
|
|
val to_octets : t -> string
|
||
|
|
val of_int64 : int64 * int64 -> t
|
||
|
|
val to_int64 : t -> int64 * int64
|
||
|
|
val of_int32 : int32 * int32 * int32 * int32 -> t
|
||
|
|
val to_int32 : t -> int32 * int32 * int32 * int32
|
||
|
|
val of_int16 : int * int * int * int * int * int * int * int -> t
|
||
|
|
val to_int16 : t -> int * int * int * int * int * int * int * int
|
||
|
|
val add_exn : t -> t -> t [@@ocaml.warning "-32"]
|
||
|
|
val pred_exn : t -> t [@@ocaml.warning "-32"]
|
||
|
|
val add : t -> t -> t option
|
||
|
|
val logand : t -> t -> t
|
||
|
|
val logor : t -> t -> t
|
||
|
|
val logxor : t -> t -> t
|
||
|
|
val lognot : t -> t
|
||
|
|
|
||
|
|
module Byte : sig
|
||
|
|
val get_lsbits : int -> int -> int [@@ocaml.warning "-32"]
|
||
|
|
val get_msbits : int -> int -> int [@@ocaml.warning "-32"]
|
||
|
|
val set_msbits : int -> int -> int -> int [@@ocaml.warning "-32"]
|
||
|
|
val fold_left : ('a -> bool -> 'a) -> 'a -> int -> 'a
|
||
|
|
end
|
||
|
|
|
||
|
|
val shift_right : t -> int -> t
|
||
|
|
val shift_left : t -> int -> t
|
||
|
|
val write_octets_exn : ?off:int -> t -> bytes -> unit
|
||
|
|
val succ_exn : t -> t
|
||
|
|
val succ : t -> (t, [> `Msg of string ]) result
|
||
|
|
val pred : t -> (t, [> `Msg of string ]) result
|
||
|
|
end = struct
|
||
|
|
exception Overflow
|
||
|
|
|
||
|
|
type t = string
|
||
|
|
|
||
|
|
let mk_zero () = Bytes.make 16 '\x00'
|
||
|
|
let zero = Bytes.unsafe_to_string (mk_zero ())
|
||
|
|
let max_int = String.make 16 '\xff'
|
||
|
|
let compare = String.compare
|
||
|
|
let equal = String.equal
|
||
|
|
|
||
|
|
let fold_left f init s =
|
||
|
|
(* With OCaml>=4.13.0:
|
||
|
|
[String.fold_left (fun acc c -> f acc (Char.code c)) init s] *)
|
||
|
|
let a = ref init in
|
||
|
|
for i = 0 to 15 do
|
||
|
|
a := f !a (Char.code (String.get s i))
|
||
|
|
done;
|
||
|
|
!a
|
||
|
|
|
||
|
|
let iteri_right2 f x y =
|
||
|
|
for i = 15 downto 0 do
|
||
|
|
let x' = Char.code (String.get x i) in
|
||
|
|
let y' = Char.code (String.get y i) in
|
||
|
|
f i x' y'
|
||
|
|
done
|
||
|
|
|
||
|
|
let of_octets_exn s =
|
||
|
|
if String.length s <> 16 then invalid_arg "not 16 bytes long";
|
||
|
|
s
|
||
|
|
|
||
|
|
let to_octets = Fun.id
|
||
|
|
|
||
|
|
let of_int64 (a, b) =
|
||
|
|
let b' = mk_zero () in
|
||
|
|
Bytes.set_int64_be b' 0 a;
|
||
|
|
Bytes.set_int64_be b' 8 b;
|
||
|
|
Bytes.unsafe_to_string b'
|
||
|
|
|
||
|
|
let to_int64 s =
|
||
|
|
(* with OCaml>=4.13, use String.get_int64_be *)
|
||
|
|
let b = Bytes.unsafe_of_string s in
|
||
|
|
(Bytes.get_int64_be b 0, Bytes.get_int64_be b 8)
|
||
|
|
|
||
|
|
let of_int32 (a, b, c, d) =
|
||
|
|
let b' = mk_zero () in
|
||
|
|
Bytes.set_int32_be b' 0 a;
|
||
|
|
Bytes.set_int32_be b' 4 b;
|
||
|
|
Bytes.set_int32_be b' 8 c;
|
||
|
|
Bytes.set_int32_be b' 12 d;
|
||
|
|
Bytes.unsafe_to_string b'
|
||
|
|
|
||
|
|
let to_int32 s =
|
||
|
|
(* with OCaml>=4.13, use String.get_int32_be *)
|
||
|
|
let b = Bytes.unsafe_of_string s in
|
||
|
|
( Bytes.get_int32_be b 0,
|
||
|
|
Bytes.get_int32_be b 4,
|
||
|
|
Bytes.get_int32_be b 8,
|
||
|
|
Bytes.get_int32_be b 12 )
|
||
|
|
|
||
|
|
let of_int16 (a, b, c, d, e, f, g, h) =
|
||
|
|
let b' = mk_zero () in
|
||
|
|
Bytes.set_uint16_be b' 0 a;
|
||
|
|
Bytes.set_uint16_be b' 2 b;
|
||
|
|
Bytes.set_uint16_be b' 4 c;
|
||
|
|
Bytes.set_uint16_be b' 6 d;
|
||
|
|
Bytes.set_uint16_be b' 8 e;
|
||
|
|
Bytes.set_uint16_be b' 10 f;
|
||
|
|
Bytes.set_uint16_be b' 12 g;
|
||
|
|
Bytes.set_uint16_be b' 14 h;
|
||
|
|
Bytes.unsafe_to_string b'
|
||
|
|
|
||
|
|
let to_int16 s =
|
||
|
|
(* with OCaml>=4.13, use String.get_uint16_be *)
|
||
|
|
let b = Bytes.unsafe_of_string s in
|
||
|
|
( Bytes.get_uint16_be b 0,
|
||
|
|
Bytes.get_uint16_be b 2,
|
||
|
|
Bytes.get_uint16_be b 4,
|
||
|
|
Bytes.get_uint16_be b 6,
|
||
|
|
Bytes.get_uint16_be b 8,
|
||
|
|
Bytes.get_uint16_be b 10,
|
||
|
|
Bytes.get_uint16_be b 12,
|
||
|
|
Bytes.get_uint16_be b 14 )
|
||
|
|
|
||
|
|
let add_exn x y =
|
||
|
|
let b = mk_zero () in
|
||
|
|
let carry = ref 0 in
|
||
|
|
iteri_right2
|
||
|
|
(fun i x' y' ->
|
||
|
|
let sum = x' + y' + !carry in
|
||
|
|
if sum >= 256 then (
|
||
|
|
carry := 1;
|
||
|
|
Bytes.set_uint8 b i (sum - 256))
|
||
|
|
else (
|
||
|
|
carry := 0;
|
||
|
|
Bytes.set_uint8 b i sum))
|
||
|
|
x y;
|
||
|
|
if !carry <> 0 then raise Overflow else Bytes.unsafe_to_string b
|
||
|
|
|
||
|
|
let add x y = try Some (add_exn x y) with Overflow -> None
|
||
|
|
|
||
|
|
let pred_exn x =
|
||
|
|
if equal x zero then raise Overflow;
|
||
|
|
let b = Bytes.of_string x in
|
||
|
|
let rec go i =
|
||
|
|
Bytes.set_uint8 b i (Char.code (String.get x i) - 1);
|
||
|
|
if Char.code (String.get x i) = 0 then go (Stdlib.pred i)
|
||
|
|
in
|
||
|
|
go 15;
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
|
||
|
|
let logand x y =
|
||
|
|
let b = mk_zero () in
|
||
|
|
iteri_right2 (fun i x y -> Bytes.set_uint8 b i (x land y)) x y;
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
|
||
|
|
let logor x y =
|
||
|
|
let b = mk_zero () in
|
||
|
|
iteri_right2 (fun i x y -> Bytes.set_uint8 b i (x lor y)) x y;
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
|
||
|
|
let logxor x y =
|
||
|
|
let b = mk_zero () in
|
||
|
|
iteri_right2 (fun i x y -> Bytes.set_uint8 b i (x lxor y)) x y;
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
|
||
|
|
let lognot x =
|
||
|
|
let b = mk_zero () in
|
||
|
|
String.iteri
|
||
|
|
(fun i _ -> Bytes.set_uint8 b i (lnot (Char.code (String.get x i))))
|
||
|
|
x;
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
|
||
|
|
module Byte = struct
|
||
|
|
(* Extract the [n] least significant bits from [i] *)
|
||
|
|
let get_lsbits n i =
|
||
|
|
if n <= 0 || n > 8 then invalid_arg "out of bounds";
|
||
|
|
i land ((1 lsl n) - 1)
|
||
|
|
|
||
|
|
(* Extract the [n] most significant bits from [i] *)
|
||
|
|
let get_msbits n i =
|
||
|
|
if n <= 0 || n > 8 then invalid_arg "out of bounds";
|
||
|
|
(i land (255 lsl (8 - n))) lsr (8 - n)
|
||
|
|
|
||
|
|
(* Set value [x] in [i]'s [n] most significant bits *)
|
||
|
|
let set_msbits n x i =
|
||
|
|
if n < 0 || n > 8 then raise (Invalid_argument "n must be >= 0 && <= 8")
|
||
|
|
else if n = 0 then i
|
||
|
|
else if n = 8 then x
|
||
|
|
else (x lsl (8 - n)) lor i
|
||
|
|
|
||
|
|
(* set bits are represented as true *)
|
||
|
|
let fold_left f a i =
|
||
|
|
let bitmask = ref 0b1000_0000 in
|
||
|
|
let a' = ref a in
|
||
|
|
for _ = 0 to 7 do
|
||
|
|
a' := f !a' (i land !bitmask > 0);
|
||
|
|
bitmask := !bitmask lsr 1
|
||
|
|
done;
|
||
|
|
!a'
|
||
|
|
end
|
||
|
|
|
||
|
|
let shift_right x n =
|
||
|
|
match n with
|
||
|
|
| 0 -> x
|
||
|
|
| 128 -> zero
|
||
|
|
| n when n > 0 && n < 128 ->
|
||
|
|
let b = mk_zero () in
|
||
|
|
let shift_bytes, shift_bits = (n / 8, n mod 8) in
|
||
|
|
(if shift_bits = 0 then
|
||
|
|
Bytes.blit_string x 0 b shift_bytes (16 - shift_bytes)
|
||
|
|
else
|
||
|
|
let carry = ref 0 in
|
||
|
|
for i = 0 to 15 - shift_bytes do
|
||
|
|
let x' = Char.code (String.get x i) in
|
||
|
|
let new_carry = Byte.get_lsbits shift_bits x' in
|
||
|
|
let shifted_value = x' lsr shift_bits in
|
||
|
|
let new_value = Byte.set_msbits shift_bits !carry shifted_value in
|
||
|
|
Bytes.set_uint8 b (i + shift_bytes) new_value;
|
||
|
|
carry := new_carry
|
||
|
|
done);
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
| _ -> raise (Invalid_argument "n must be >= 0 && <= 128")
|
||
|
|
|
||
|
|
let shift_left x n =
|
||
|
|
match n with
|
||
|
|
| 0 -> x
|
||
|
|
| 128 -> zero
|
||
|
|
| n when n > 0 && n < 128 ->
|
||
|
|
let b = mk_zero () in
|
||
|
|
let shift_bytes, shift_bits = (n / 8, n mod 8) in
|
||
|
|
(if shift_bits = 0 then
|
||
|
|
Bytes.blit_string x shift_bytes b 0 (16 - shift_bytes)
|
||
|
|
else
|
||
|
|
let carry = ref 0 in
|
||
|
|
for i = 15 downto 0 + shift_bytes do
|
||
|
|
let x' = Char.code (String.get x i) in
|
||
|
|
let new_carry = Byte.get_msbits shift_bits x' in
|
||
|
|
let shifted_value = x' lsl shift_bits in
|
||
|
|
let new_value = shifted_value lor !carry in
|
||
|
|
Bytes.set_uint8 b (i - shift_bytes) new_value;
|
||
|
|
carry := new_carry
|
||
|
|
done);
|
||
|
|
Bytes.unsafe_to_string b
|
||
|
|
| _ -> raise (Invalid_argument "n must be >= 0 && <= 128")
|
||
|
|
|
||
|
|
let write_octets_exn ?(off = 0) s dest =
|
||
|
|
if Bytes.length dest - off < 16 then
|
||
|
|
raise (Parse_error ("larger including offset than target bytes", s))
|
||
|
|
else Bytes.blit_string s 0 dest off (String.length s)
|
||
|
|
|
||
|
|
let succ_exn x = add_exn x (of_int64 (0L, 1L))
|
||
|
|
|
||
|
|
let succ x =
|
||
|
|
try Ok (succ_exn x)
|
||
|
|
with Overflow -> Error (`Msg "Ipaddr: highest address has been reached")
|
||
|
|
|
||
|
|
let pred x =
|
||
|
|
try Ok (pred_exn x)
|
||
|
|
with Overflow | Invalid_argument _ ->
|
||
|
|
Error (`Msg "Ipaddr: lowest address has been reached")
|
||
|
|
end
|
||
|
|
|
||
|
|
module V6 = struct
|
||
|
|
include S128
|
||
|
|
|
||
|
|
let make a b c d e f g h = of_int16 (a, b, c, d, e, f, g, h)
|
||
|
|
|
||
|
|
(* parsing *)
|
||
|
|
let parse_ipv6 s i =
|
||
|
|
let compressed = ref false in
|
||
|
|
(* :: *)
|
||
|
|
let len = String.length s in
|
||
|
|
if len < !i + 1 then raise (need_more s);
|
||
|
|
let use_bracket = s.[!i] = '[' in
|
||
|
|
if use_bracket then incr i;
|
||
|
|
if len < !i + 2 then raise (need_more s);
|
||
|
|
(* check if it starts with :: *)
|
||
|
|
let l =
|
||
|
|
if s.[!i] = ':' then (
|
||
|
|
incr i;
|
||
|
|
if s.[!i] = ':' then (
|
||
|
|
compressed := true;
|
||
|
|
incr i;
|
||
|
|
[ -1 ])
|
||
|
|
else raise (bad_char !i s))
|
||
|
|
else []
|
||
|
|
in
|
||
|
|
|
||
|
|
let rec loop nb acc =
|
||
|
|
if nb >= 8 then acc
|
||
|
|
else if !i >= len then acc
|
||
|
|
else
|
||
|
|
let pos = !i in
|
||
|
|
let x = try parse_hex_int s i with _ -> -1 in
|
||
|
|
if x < 0 then acc
|
||
|
|
else if nb = 7 then x :: acc
|
||
|
|
else if !i < len && s.[!i] = ':' then (
|
||
|
|
incr i;
|
||
|
|
if !i < len then
|
||
|
|
if s.[!i] = ':' then
|
||
|
|
if !compressed then (
|
||
|
|
decr i;
|
||
|
|
x :: acc (* trailing :: *))
|
||
|
|
else (
|
||
|
|
compressed := true;
|
||
|
|
incr i;
|
||
|
|
loop (nb + 2) (-1 :: x :: acc))
|
||
|
|
else if is_number 16 (int_of_char s.[!i]) then
|
||
|
|
loop (nb + 1) (x :: acc)
|
||
|
|
else raise (bad_char !i s)
|
||
|
|
else raise (need_more s))
|
||
|
|
else if !i < len && s.[!i] = '.' then (
|
||
|
|
i := pos;
|
||
|
|
let v4 = V4.of_string_raw s i in
|
||
|
|
let hi, lo = V4.to_int16 v4 in
|
||
|
|
lo :: hi :: acc)
|
||
|
|
else x :: acc
|
||
|
|
in
|
||
|
|
|
||
|
|
let res = loop (List.length l) l in
|
||
|
|
let res_len = List.length res in
|
||
|
|
if res_len > 8 then raise (Parse_error ("too many components", s))
|
||
|
|
else if res_len = 0 then raise (need_more s)
|
||
|
|
else
|
||
|
|
let a = Array.make 8 0 in
|
||
|
|
let missing =
|
||
|
|
if !compressed then 8 - (res_len - 1)
|
||
|
|
else if res_len <> 8 then
|
||
|
|
if !i < len then raise (bad_char !i s) else raise (need_more s)
|
||
|
|
else 0
|
||
|
|
in
|
||
|
|
let _ =
|
||
|
|
List.fold_left
|
||
|
|
(fun i x ->
|
||
|
|
if x = -1 then i - missing
|
||
|
|
else (
|
||
|
|
if x land 0xffff <> x then
|
||
|
|
raise
|
||
|
|
(Parse_error (Printf.sprintf "component %d out of bounds" i, s));
|
||
|
|
a.(i) <- x;
|
||
|
|
i - 1))
|
||
|
|
7 res
|
||
|
|
in
|
||
|
|
if use_bracket then expect_char s i ']';
|
||
|
|
a
|
||
|
|
|
||
|
|
(* string conversion *)
|
||
|
|
|
||
|
|
let of_string_raw s offset =
|
||
|
|
let a = parse_ipv6 s offset in
|
||
|
|
make a.(0) a.(1) a.(2) a.(3) a.(4) a.(5) a.(6) a.(7)
|
||
|
|
|
||
|
|
let of_string_exn s =
|
||
|
|
let o = ref 0 in
|
||
|
|
let x = of_string_raw s o in
|
||
|
|
expect_end s o;
|
||
|
|
x
|
||
|
|
|
||
|
|
let of_string s = try_with_result of_string_exn s
|
||
|
|
|
||
|
|
let with_port_of_string ~default s =
|
||
|
|
let len = String.length s and o = ref 0 in
|
||
|
|
try
|
||
|
|
let ipv6 = of_string_raw s o in
|
||
|
|
if !o < len && s.[!o] = ':' then (
|
||
|
|
incr o;
|
||
|
|
let port = parse_dec_int s o in
|
||
|
|
expect_end s o;
|
||
|
|
Ok (ipv6, port))
|
||
|
|
else (
|
||
|
|
expect_end s o;
|
||
|
|
Ok (ipv6, default))
|
||
|
|
with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
|
||
|
|
|
||
|
|
(* http://tools.ietf.org/html/rfc5952 *)
|
||
|
|
let to_buffer buf addr =
|
||
|
|
let ((a, b, c, d, e, f, g, h) as comp) = to_int16 addr in
|
||
|
|
|
||
|
|
let v4 =
|
||
|
|
match comp with 0, 0, 0, 0, 0, 0xffff, _, _ -> true | _ -> false
|
||
|
|
in
|
||
|
|
|
||
|
|
let rec loop elide zeros acc = function
|
||
|
|
| 0 :: xs -> loop elide (zeros - 1) acc xs
|
||
|
|
| n :: xs when zeros = 0 -> loop elide 0 (n :: acc) xs
|
||
|
|
| n :: xs -> loop (min elide zeros) 0 (n :: zeros :: acc) xs
|
||
|
|
| [] ->
|
||
|
|
let elide = min elide zeros in
|
||
|
|
( (if elide < -1 then Some elide else None),
|
||
|
|
if zeros = 0 then acc else zeros :: acc )
|
||
|
|
in
|
||
|
|
|
||
|
|
let elide, l = loop 0 0 [] [ h; g; f; e; d; c; b; a ] in
|
||
|
|
assert (match elide with Some x when x < -8 -> false | _ -> true);
|
||
|
|
|
||
|
|
let rec cons_zeros l x =
|
||
|
|
if x >= 0 then l else cons_zeros (Some 0 :: l) (x + 1)
|
||
|
|
in
|
||
|
|
|
||
|
|
let _, lrev =
|
||
|
|
List.fold_left
|
||
|
|
(fun (patt, l) x ->
|
||
|
|
if Some x = patt then (None, None :: l)
|
||
|
|
else if x < 0 then (patt, cons_zeros l x)
|
||
|
|
else (patt, Some x :: l))
|
||
|
|
(elide, []) l
|
||
|
|
in
|
||
|
|
|
||
|
|
let rec fill = function
|
||
|
|
| [ Some hi; Some lo ] when v4 ->
|
||
|
|
let addr = V4.of_int16 (hi, lo) in
|
||
|
|
V4.to_buffer buf addr
|
||
|
|
| None :: xs ->
|
||
|
|
Buffer.add_string buf "::";
|
||
|
|
fill xs
|
||
|
|
| [ Some n ] -> Printf.bprintf buf "%x" n
|
||
|
|
| Some n :: None :: xs ->
|
||
|
|
Printf.bprintf buf "%x::" n;
|
||
|
|
fill xs
|
||
|
|
| Some n :: xs ->
|
||
|
|
Printf.bprintf buf "%x:" n;
|
||
|
|
fill xs
|
||
|
|
| [] -> ()
|
||
|
|
in
|
||
|
|
fill (List.rev lrev)
|
||
|
|
|
||
|
|
let to_string l =
|
||
|
|
let buf = Buffer.create 39 in
|
||
|
|
to_buffer buf l;
|
||
|
|
Buffer.contents buf
|
||
|
|
|
||
|
|
let pp ppf i = Format.fprintf ppf "%s" (to_string i)
|
||
|
|
|
||
|
|
(* byte conversion *)
|
||
|
|
|
||
|
|
let of_octets_exn ?(off = 0) bs =
|
||
|
|
if String.length bs - off < 16 then raise (need_more bs)
|
||
|
|
else S128.of_octets_exn (String.sub bs off 16)
|
||
|
|
|
||
|
|
let of_octets ?off bs = try_with_result (of_octets_exn ?off) bs
|
||
|
|
let write_octets ?off i bs = try_with_result (write_octets_exn ?off i) bs
|
||
|
|
let to_octets = S128.to_octets
|
||
|
|
|
||
|
|
(* MAC *)
|
||
|
|
(* {{:https://tools.ietf.org/html/rfc2464#section-7}RFC 2464}. *)
|
||
|
|
let multicast_to_mac s =
|
||
|
|
let macb = Bytes.make 6 (Char.chr 0x33) in
|
||
|
|
Bytes.blit_string (S128.to_octets s) 12 macb 2 4;
|
||
|
|
Macaddr.of_octets_exn (Bytes.to_string macb)
|
||
|
|
|
||
|
|
(* Host *)
|
||
|
|
let to_domain_name b =
|
||
|
|
let hexstr_of_int = Printf.sprintf "%x" in
|
||
|
|
let name =
|
||
|
|
S128.fold_left
|
||
|
|
(fun acc b ->
|
||
|
|
let x = hexstr_of_int (b land ((1 lsl 4) - 1)) in
|
||
|
|
let y = hexstr_of_int (b lsr 4) in
|
||
|
|
x :: y :: acc)
|
||
|
|
[ "ip6"; "arpa" ] b
|
||
|
|
in
|
||
|
|
Domain_name.(host_exn (of_strings_exn name))
|
||
|
|
|
||
|
|
let of_domain_name n =
|
||
|
|
let int_of_char_string = function
|
||
|
|
| "0" -> 0
|
||
|
|
| "1" -> 1
|
||
|
|
| "2" -> 2
|
||
|
|
| "3" -> 3
|
||
|
|
| "4" -> 4
|
||
|
|
| "5" -> 5
|
||
|
|
| "6" -> 6
|
||
|
|
| "7" -> 7
|
||
|
|
| "8" -> 8
|
||
|
|
| "9" -> 9
|
||
|
|
| "a" -> 10
|
||
|
|
| "b" -> 11
|
||
|
|
| "c" -> 12
|
||
|
|
| "d" -> 13
|
||
|
|
| "e" -> 14
|
||
|
|
| "f" -> 15
|
||
|
|
| _ -> failwith "int_of_char_string: invalid hexadecimal string"
|
||
|
|
in
|
||
|
|
let labels = Domain_name.to_array n in
|
||
|
|
if
|
||
|
|
Array.length labels = 34
|
||
|
|
&& Domain_name.equal_label labels.(0) "arpa"
|
||
|
|
&& Domain_name.equal_label labels.(1) "ip6"
|
||
|
|
then
|
||
|
|
let b = Bytes.create 16 in
|
||
|
|
try
|
||
|
|
for bi = 0 to 15 do
|
||
|
|
let i = 2 * Int.succ bi in
|
||
|
|
let x = int_of_char_string labels.(i) in
|
||
|
|
let y = int_of_char_string labels.(i + 1) in
|
||
|
|
Bytes.set_uint8 b bi (Int.logor (Int.shift_left x 4) y)
|
||
|
|
done;
|
||
|
|
Some (S128.of_octets_exn (Bytes.unsafe_to_string b))
|
||
|
|
with Failure _ -> None
|
||
|
|
else None
|
||
|
|
|
||
|
|
(* constant *)
|
||
|
|
|
||
|
|
let unspecified = make 0 0 0 0 0 0 0 0
|
||
|
|
let localhost = make 0 0 0 0 0 0 0 1
|
||
|
|
let interface_nodes = make 0xff01 0 0 0 0 0 0 1
|
||
|
|
let link_nodes = make 0xff02 0 0 0 0 0 0 1
|
||
|
|
let interface_routers = make 0xff01 0 0 0 0 0 0 2
|
||
|
|
let link_routers = make 0xff02 0 0 0 0 0 0 2
|
||
|
|
let site_routers = make 0xff05 0 0 0 0 0 0 2
|
||
|
|
|
||
|
|
module Prefix = struct
|
||
|
|
type addr = t
|
||
|
|
type t = addr * int
|
||
|
|
|
||
|
|
let compare (pre, sz) (pre', sz') =
|
||
|
|
let c = compare pre pre' in
|
||
|
|
if c = 0 then Stdlib.compare sz sz' else c
|
||
|
|
|
||
|
|
let ip = make
|
||
|
|
let mask sz = shift_left max_int (128 - sz)
|
||
|
|
let prefix (pre, sz) = (logand pre (mask sz), sz)
|
||
|
|
let make sz pre = (pre, sz)
|
||
|
|
|
||
|
|
let network_address (pre, sz) addr =
|
||
|
|
logor (logand pre (mask sz)) (logand addr (lognot (mask sz)))
|
||
|
|
|
||
|
|
let _of_string_raw s i =
|
||
|
|
let v6 = of_string_raw s i in
|
||
|
|
expect_char s i '/';
|
||
|
|
let p = parse_dec_int s i in
|
||
|
|
if p > 128 || p < 0 then raise (Parse_error ("invalid prefix size", s));
|
||
|
|
(p, v6)
|
||
|
|
|
||
|
|
let of_string_raw s i =
|
||
|
|
let p, v6 = _of_string_raw s i in
|
||
|
|
make p v6
|
||
|
|
|
||
|
|
let _of_string_exn s =
|
||
|
|
let i = ref 0 in
|
||
|
|
let res = _of_string_raw s i in
|
||
|
|
expect_end s i;
|
||
|
|
res
|
||
|
|
|
||
|
|
let of_string_exn s =
|
||
|
|
let p, v6 = _of_string_exn s in
|
||
|
|
make p v6
|
||
|
|
|
||
|
|
let of_string s = try_with_result of_string_exn s
|
||
|
|
|
||
|
|
let _of_netmask_exn ~netmask address =
|
||
|
|
let count_bits bits is_last_bit_set i =
|
||
|
|
S128.Byte.fold_left
|
||
|
|
(fun (a, is_last_bit_set) e ->
|
||
|
|
match (is_last_bit_set, e) with
|
||
|
|
| true, false | false, false -> (a, false)
|
||
|
|
| true, true -> (a + 1, true)
|
||
|
|
| false, true ->
|
||
|
|
(* netmask is not contiguous *)
|
||
|
|
raise (Parse_error ("invalid netmask", to_string netmask)))
|
||
|
|
(bits, is_last_bit_set) i
|
||
|
|
in
|
||
|
|
let nm_bits_set, _ =
|
||
|
|
S128.fold_left
|
||
|
|
(fun (a, is_last_bit_set) e -> count_bits a is_last_bit_set e)
|
||
|
|
(0, true) netmask
|
||
|
|
in
|
||
|
|
make nm_bits_set address
|
||
|
|
|
||
|
|
let of_netmask_exn ~netmask ~address = _of_netmask_exn ~netmask address
|
||
|
|
|
||
|
|
let of_netmask ~netmask ~address =
|
||
|
|
try_with_result (_of_netmask_exn ~netmask) address
|
||
|
|
|
||
|
|
let to_buffer buf (pre, sz) = Printf.bprintf buf "%a/%d" to_buffer pre sz
|
||
|
|
|
||
|
|
let to_string subnet =
|
||
|
|
let buf = Buffer.create 43 in
|
||
|
|
to_buffer buf subnet;
|
||
|
|
Buffer.contents buf
|
||
|
|
|
||
|
|
let pp ppf i = Format.fprintf ppf "%s" (to_string i)
|
||
|
|
|
||
|
|
let mem ip (pre, sz) =
|
||
|
|
let m = mask sz in
|
||
|
|
logand ip m = logand pre m
|
||
|
|
|
||
|
|
let subset ~subnet:(pre1, sz1) ~network:(pre2, sz2) =
|
||
|
|
sz1 >= sz2 && mem pre1 (pre2, sz2)
|
||
|
|
|
||
|
|
let of_addr ip = make 128 ip
|
||
|
|
let global_unicast_001 = make 3 (ip 0x2000 0 0 0 0 0 0 0)
|
||
|
|
let link = make 64 (ip 0xfe80 0 0 0 0 0 0 0)
|
||
|
|
let unique_local = make 7 (ip 0xfc00 0 0 0 0 0 0 0)
|
||
|
|
let multicast = make 8 (ip 0xff00 0 0 0 0 0 0 0)
|
||
|
|
let ipv4_mapped = make 96 (ip 0 0 0 0 0 0xffff 0 0)
|
||
|
|
let noneui64_interface = make 3 (ip 0x0000 0 0 0 0 0 0 0)
|
||
|
|
let solicited_node = make 104 (ip 0xff02 0 0 0 0 1 0xff00 0)
|
||
|
|
let network (pre, sz) = logand pre (mask sz)
|
||
|
|
let address (addr, _) = addr
|
||
|
|
let bits (_, sz) = sz
|
||
|
|
let netmask subnet = mask (bits subnet)
|
||
|
|
let hostmask cidr = S128.logxor (netmask cidr) S128.max_int
|
||
|
|
|
||
|
|
let first ((_, sz) as cidr) =
|
||
|
|
if sz > 126 then network cidr else network cidr |> succ |> failwith_msg
|
||
|
|
|
||
|
|
let last ((_, sz) as cidr) =
|
||
|
|
let ffff = S128.max_int in
|
||
|
|
logor (network cidr) (S128.shift_right ffff sz)
|
||
|
|
|
||
|
|
let hosts ?(usable = true) ((_, sz) as cidr) =
|
||
|
|
let rec iter_seq start stop =
|
||
|
|
if S128.compare start stop > 0 then Seq.Nil
|
||
|
|
else
|
||
|
|
match succ start with
|
||
|
|
| Ok start_succ -> Seq.Cons (start, fun () -> iter_seq start_succ stop)
|
||
|
|
| Error _ -> Seq.Cons (start, fun () -> Seq.Nil)
|
||
|
|
in
|
||
|
|
if usable && sz = 128 then fun () -> Seq.Nil
|
||
|
|
else
|
||
|
|
let start, stop =
|
||
|
|
if usable then (first cidr, last cidr) else (network cidr, last cidr)
|
||
|
|
in
|
||
|
|
fun () -> iter_seq start stop
|
||
|
|
|
||
|
|
let subnets n ((_, sz) as cidr) =
|
||
|
|
let rec iter_seq start stop steps =
|
||
|
|
if S128.compare start stop > 0 then Seq.Nil
|
||
|
|
else
|
||
|
|
let prefix = make n start in
|
||
|
|
if S128.equal start stop then Seq.Cons (prefix, fun () -> Seq.Nil)
|
||
|
|
else
|
||
|
|
match S128.add start steps with
|
||
|
|
| None -> Seq.Cons (prefix, fun () -> Seq.Nil)
|
||
|
|
| Some start_succ ->
|
||
|
|
Seq.Cons (prefix, fun () -> iter_seq start_succ stop steps)
|
||
|
|
in
|
||
|
|
if sz > n || n > 128 then fun () -> Seq.Nil
|
||
|
|
else
|
||
|
|
let start = network cidr in
|
||
|
|
let stop = last cidr in
|
||
|
|
let steps = S128.(succ_exn (shift_right (hostmask cidr) (n - sz))) in
|
||
|
|
fun () -> iter_seq start stop steps
|
||
|
|
end
|
||
|
|
|
||
|
|
(* TODO: This could be optimized with something trie-like *)
|
||
|
|
let scope i =
|
||
|
|
let mem = Prefix.mem i in
|
||
|
|
if mem Prefix.global_unicast_001 then Global
|
||
|
|
else if
|
||
|
|
mem Prefix.ipv4_mapped (* rfc says they are technically global but... *)
|
||
|
|
then
|
||
|
|
V4.scope
|
||
|
|
(let _, _, _, v4 = to_int32 i in
|
||
|
|
V4.of_int32 v4)
|
||
|
|
else if mem Prefix.multicast then
|
||
|
|
let x, _, _, _, _, _, _, _ = to_int16 i in
|
||
|
|
match x land 0xf with
|
||
|
|
| 0 -> Point
|
||
|
|
| 1 -> Interface
|
||
|
|
| 2 | 3 -> Link
|
||
|
|
| 4 -> Admin
|
||
|
|
| 5 | 6 | 7 -> Site
|
||
|
|
| 8 | 9 | 10 | 11 | 12 | 13 -> Organization
|
||
|
|
| 14 | 15 -> Global
|
||
|
|
| _ -> assert false
|
||
|
|
else if mem Prefix.link then Link
|
||
|
|
else if mem Prefix.unique_local then Global
|
||
|
|
else if i = localhost then Interface
|
||
|
|
else if i = unspecified then Point
|
||
|
|
else Global
|
||
|
|
|
||
|
|
let link_address_of_mac =
|
||
|
|
let c b i = Char.code b.[i] in
|
||
|
|
fun mac ->
|
||
|
|
let bmac = Macaddr.to_octets mac in
|
||
|
|
let c_0 = c bmac 0 lxor 2 in
|
||
|
|
let addr =
|
||
|
|
make 0 0 0 0
|
||
|
|
((c_0 lsl 8) + c bmac 1)
|
||
|
|
((c bmac 2 lsl 8) + 0xff)
|
||
|
|
(0xfe00 + c bmac 3)
|
||
|
|
((c bmac 4 lsl 8) + c bmac 5)
|
||
|
|
in
|
||
|
|
Prefix.(network_address link addr)
|
||
|
|
|
||
|
|
let is_global i = scope i = Global
|
||
|
|
let is_multicast i = Prefix.(mem i multicast)
|
||
|
|
let is_private i = scope i <> Global
|
||
|
|
|
||
|
|
module Set = Set.Make (struct
|
||
|
|
type nonrec t = t
|
||
|
|
|
||
|
|
let compare (a : t) (b : t) = compare a b
|
||
|
|
end)
|
||
|
|
|
||
|
|
module Map = Map.Make (struct
|
||
|
|
type nonrec t = t
|
||
|
|
|
||
|
|
let compare (a : t) (b : t) = compare a b
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
type ('v4, 'v6) v4v6 = V4 of 'v4 | V6 of 'v6
|
||
|
|
type t = (V4.t, V6.t) v4v6
|
||
|
|
|
||
|
|
let compare a b =
|
||
|
|
match (a, b) with
|
||
|
|
| V4 a, V4 b -> V4.compare a b
|
||
|
|
| V6 a, V6 b -> V6.compare a b
|
||
|
|
| V4 _, V6 _ -> -1
|
||
|
|
| V6 _, V4 _ -> 1
|
||
|
|
|
||
|
|
module Set = Set.Make (struct
|
||
|
|
type nonrec t = t
|
||
|
|
|
||
|
|
let compare (a : t) (b : t) = compare a b
|
||
|
|
end)
|
||
|
|
|
||
|
|
module Map = Map.Make (struct
|
||
|
|
type nonrec t = t
|
||
|
|
|
||
|
|
let compare (a : t) (b : t) = compare a b
|
||
|
|
end)
|
||
|
|
|
||
|
|
let to_string = function V4 x -> V4.to_string x | V6 x -> V6.to_string x
|
||
|
|
|
||
|
|
let to_buffer buf = function
|
||
|
|
| V4 x -> V4.to_buffer buf x
|
||
|
|
| V6 x -> V6.to_buffer buf x
|
||
|
|
|
||
|
|
let pp ppf i = Format.fprintf ppf "%s" (to_string i)
|
||
|
|
|
||
|
|
let of_string_raw s offset =
|
||
|
|
let len = String.length s in
|
||
|
|
if len < !offset + 1 then raise (need_more s);
|
||
|
|
match s.[0] with
|
||
|
|
| '[' -> V6 (V6.of_string_raw s offset)
|
||
|
|
| _ -> (
|
||
|
|
let pos = !offset in
|
||
|
|
try V4 (V4.of_string_raw s offset)
|
||
|
|
with Parse_error (v4_msg, _) -> (
|
||
|
|
offset := pos;
|
||
|
|
try V6 (V6.of_string_raw s offset)
|
||
|
|
with Parse_error (v6_msg, s) ->
|
||
|
|
let msg =
|
||
|
|
Printf.sprintf "not an IPv4 address: %s\nnot an IPv6 address: %s"
|
||
|
|
v4_msg v6_msg
|
||
|
|
in
|
||
|
|
raise (Parse_error (msg, s))))
|
||
|
|
|
||
|
|
let of_string_exn s =
|
||
|
|
let o = ref 0 in
|
||
|
|
let x = of_string_raw s o in
|
||
|
|
expect_end s o;
|
||
|
|
x
|
||
|
|
|
||
|
|
let of_string s = try_with_result of_string_exn s
|
||
|
|
|
||
|
|
let with_port_of_string ~default s =
|
||
|
|
let len = String.length s and o = ref 0 in
|
||
|
|
try
|
||
|
|
let ipv6 = of_string_raw s o in
|
||
|
|
if !o < len && s.[!o] = ':' then (
|
||
|
|
incr o;
|
||
|
|
let port = parse_dec_int s o in
|
||
|
|
expect_end s o;
|
||
|
|
Ok (ipv6, port))
|
||
|
|
else (
|
||
|
|
expect_end s o;
|
||
|
|
Ok (ipv6, default))
|
||
|
|
with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
|
||
|
|
|
||
|
|
let of_octets_exn bs =
|
||
|
|
match String.length bs with
|
||
|
|
| 4 -> V4 (V4.of_octets_exn bs)
|
||
|
|
| 16 -> V6 (V6.of_octets_exn bs)
|
||
|
|
| _ -> raise (Parse_error ("octets must be of length 4 or 16", bs))
|
||
|
|
|
||
|
|
let of_octets bs = try_with_result of_octets_exn bs
|
||
|
|
let to_octets i = match i with V4 p -> V4.to_octets p | V6 p -> V6.to_octets p
|
||
|
|
|
||
|
|
let v6_of_v4 v4 =
|
||
|
|
V6.(Prefix.(network_address ipv4_mapped (of_int32 (0l, 0l, 0l, v4))))
|
||
|
|
|
||
|
|
let v4_of_v6 v6 =
|
||
|
|
if V6.Prefix.(mem v6 ipv4_mapped) then
|
||
|
|
let _, _, _, v4 = V6.to_int32 v6 in
|
||
|
|
Some V4.(of_int32 v4)
|
||
|
|
else None
|
||
|
|
|
||
|
|
let to_v4 = function V4 v4 -> Some v4 | V6 v6 -> v4_of_v6 v6
|
||
|
|
let to_v6 = function V4 v4 -> v6_of_v4 v4 | V6 v6 -> v6
|
||
|
|
let scope = function V4 v4 -> V4.scope v4 | V6 v6 -> V6.scope v6
|
||
|
|
let is_global = function V4 v4 -> V4.is_global v4 | V6 v6 -> V6.is_global v6
|
||
|
|
|
||
|
|
let is_multicast = function
|
||
|
|
| V4 v4 -> V4.is_multicast v4
|
||
|
|
| V6 v6 -> V6.is_multicast v6
|
||
|
|
|
||
|
|
let is_private = function
|
||
|
|
| V4 v4 -> V4.is_private v4
|
||
|
|
| V6 v6 -> V6.is_private v6
|
||
|
|
|
||
|
|
let multicast_to_mac = function
|
||
|
|
| V4 v4 -> V4.multicast_to_mac v4
|
||
|
|
| V6 v6 -> V6.multicast_to_mac v6
|
||
|
|
|
||
|
|
let to_domain_name = function
|
||
|
|
| V4 v4 -> V4.to_domain_name v4
|
||
|
|
| V6 v6 -> V6.to_domain_name v6
|
||
|
|
|
||
|
|
let of_domain_name n =
|
||
|
|
match Domain_name.count_labels n with
|
||
|
|
| 6 -> (
|
||
|
|
match V4.of_domain_name n with None -> None | Some x -> Some (V4 x))
|
||
|
|
| 34 -> (
|
||
|
|
match V6.of_domain_name n with None -> None | Some x -> Some (V6 x))
|
||
|
|
| _ -> None
|
||
|
|
|
||
|
|
let succ = function
|
||
|
|
| V4 addr -> Result.map (fun v -> V4 v) (V4.succ addr)
|
||
|
|
| V6 addr -> Result.map (fun v -> V6 v) (V6.succ addr)
|
||
|
|
|
||
|
|
let pred = function
|
||
|
|
| V4 addr -> Result.map (fun v -> V4 v) (V4.pred addr)
|
||
|
|
| V6 addr -> Result.map (fun v -> V6 v) (V6.pred addr)
|
||
|
|
|
||
|
|
module Prefix = struct
|
||
|
|
module Addr = struct
|
||
|
|
let to_v6 = to_v6
|
||
|
|
end
|
||
|
|
|
||
|
|
type addr = t
|
||
|
|
type t = (V4.Prefix.t, V6.Prefix.t) v4v6
|
||
|
|
|
||
|
|
let compare a b =
|
||
|
|
match (a, b) with
|
||
|
|
| V4 a, V4 b -> V4.Prefix.compare a b
|
||
|
|
| V6 a, V6 b -> V6.Prefix.compare a b
|
||
|
|
| V4 _, V6 _ -> -1
|
||
|
|
| V6 _, V4 _ -> 1
|
||
|
|
|
||
|
|
let of_string_raw s offset =
|
||
|
|
let len = String.length s in
|
||
|
|
if len < !offset + 1 then raise (need_more s);
|
||
|
|
match s.[0] with
|
||
|
|
| '[' -> V6 (V6.Prefix.of_string_raw s offset)
|
||
|
|
| _ -> (
|
||
|
|
let pos = !offset in
|
||
|
|
try V4 (V4.Prefix.of_string_raw s offset)
|
||
|
|
with Parse_error (v4_msg, _) -> (
|
||
|
|
offset := pos;
|
||
|
|
try V6 (V6.Prefix.of_string_raw s offset)
|
||
|
|
with Parse_error (v6_msg, s) ->
|
||
|
|
let msg =
|
||
|
|
Printf.sprintf "not an IPv4 prefix: %s\nnot an IPv6 prefix: %s"
|
||
|
|
v4_msg v6_msg
|
||
|
|
in
|
||
|
|
raise (Parse_error (msg, s))))
|
||
|
|
|
||
|
|
let of_string_exn s =
|
||
|
|
let o = ref 0 in
|
||
|
|
let x = of_string_raw s o in
|
||
|
|
expect_end s o;
|
||
|
|
x
|
||
|
|
|
||
|
|
let of_string s = try_with_result of_string_exn s
|
||
|
|
|
||
|
|
let v6_of_v4 v4 =
|
||
|
|
V6.Prefix.make (96 + V4.Prefix.bits v4) (v6_of_v4 (V4.Prefix.network v4))
|
||
|
|
|
||
|
|
let v4_of_v6 v6 =
|
||
|
|
match v4_of_v6 (V6.Prefix.network v6) with
|
||
|
|
| Some v4 -> Some (V4.Prefix.make (V6.Prefix.bits v6 - 96) v4)
|
||
|
|
| None -> None
|
||
|
|
|
||
|
|
let to_v4 = function V4 v4 -> Some v4 | V6 v6 -> v4_of_v6 v6
|
||
|
|
let to_v6 = function V4 v4 -> v6_of_v4 v4 | V6 v6 -> v6
|
||
|
|
let mem ip prefix = V6.Prefix.mem (Addr.to_v6 ip) (to_v6 prefix)
|
||
|
|
|
||
|
|
let subset ~subnet ~network =
|
||
|
|
V6.Prefix.subset ~subnet:(to_v6 subnet) ~network:(to_v6 network)
|
||
|
|
|
||
|
|
let of_addr = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.of_addr p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.of_addr p)
|
||
|
|
|
||
|
|
let to_string = function
|
||
|
|
| V4 p -> V4.Prefix.to_string p
|
||
|
|
| V6 p -> V6.Prefix.to_string p
|
||
|
|
|
||
|
|
let to_buffer buf = function
|
||
|
|
| V4 p -> V4.Prefix.to_buffer buf p
|
||
|
|
| V6 p -> V6.Prefix.to_buffer buf p
|
||
|
|
|
||
|
|
let network = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.network p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.network p)
|
||
|
|
|
||
|
|
let netmask = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.netmask p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.netmask p)
|
||
|
|
|
||
|
|
let address = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.address p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.address p)
|
||
|
|
|
||
|
|
let bits = function V4 p -> V4.Prefix.bits p | V6 p -> V6.Prefix.bits p
|
||
|
|
let pp ppf i = Format.fprintf ppf "%s" (to_string i)
|
||
|
|
|
||
|
|
let first = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.first p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.first p)
|
||
|
|
|
||
|
|
let last = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.last p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.last p)
|
||
|
|
|
||
|
|
let hosts ?(usable = true) = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.hosts ~usable p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.hosts ~usable p)
|
||
|
|
|
||
|
|
let subnets n = function
|
||
|
|
| V4 p -> V4 (V4.Prefix.subnets n p)
|
||
|
|
| V6 p -> V6 (V6.Prefix.subnets n p)
|
||
|
|
end
|