IP address management: CIDR pools and per-pool allocation
0

Configure Feed

Select the types of activity you want to include in your feed.

OCaml 83.0%
Go 7.1%
Dune 2.6%
Shell 0.9%
Other 6.3%
8 1 0

Clone this repository

https://tangled.org/gazagnaire.org/ocaml-ipam https://tangled.org/did:plc:ndfnggjxmwgnb3nztfbau7re
git@git.recoil.org:gazagnaire.org/ocaml-ipam git@git.recoil.org:did:plc:ndfnggjxmwgnb3nztfbau7re

For self-hosted knots, clone URLs may differ based on your setup.



README.md

ipam#

IP address management for OCaml.

ipam allocates IP addresses and CIDR pools, modelled on Docker libnetwork's two-level IPAM:

  • a pool hands out individual addresses from a single CIDR range;
  • an allocator carves pools out of predefined networks (or accepts caller-supplied CIDRs), rejects overlaps, and routes address requests to the owning pool.

Occupancy is tracked in EWAH-compressed bitmaps indexed by host ordinal, so a sparsely used /8 or /64 costs only as much as the addresses actually in use. Network and broadcast addresses (IPv4) and the Subnet-Router anycast address (IPv6) are reserved per RFC 3021 and RFC 4291. Every operation is pure: mutating functions return an updated value, so allocator state can be snapshotted and persisted by the caller.

Installation#

$ opam install ipam

If opam cannot find the package, add the overlay repository first:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install ipam

Usage#

Pools#

A pool wraps a single CIDR. Its capacity excludes the reserved addresses, and request hands out the lowest free host:

# let p = Ipam.Pool.v (Ipaddr.Prefix.of_string_exn "192.168.1.0/24") in
  Ipam.Pool.capacity p
- : int = 254
# let p = Ipam.Pool.v (Ipaddr.Prefix.of_string_exn "192.168.1.0/24") in
  match Ipam.Pool.request p with
  | Ok (addr, _) -> Ipaddr.to_string addr
  | Error e -> Ipam.string_of_error e
- : string = "192.168.1.1"

A /31 carries no network or broadcast reservation (RFC 3021), so both addresses are usable:

# let p = Ipam.Pool.v (Ipaddr.Prefix.of_string_exn "10.0.0.0/31") in
  Ipam.Pool.capacity p
- : int = 2

Allocator#

The allocator carves a pool dynamically from its predefined networks (libnetwork's defaults, here the first local-scope network):

# let t = Ipam.v () in
  match Ipam.request_pool ~space:"local" t with
  | Ok (_id, prefix, _t) -> Ipaddr.Prefix.to_string prefix
  | Error e -> Ipam.string_of_error e
- : string = "172.17.0.0/16"

Allocate a static pool, then request an address from it:

# let t = Ipam.v () in
  match Ipam.request_pool ~pool:(Ipaddr.Prefix.of_string_exn "10.10.0.0/24") t with
  | Error e -> Ipam.string_of_error e
  | Ok (id, _, t) -> (
      match Ipam.request_address t id with
      | Ok (addr, _) -> Ipaddr.to_string addr
      | Error e -> Ipam.string_of_error e)
- : string = "10.10.0.1"

IPv6 reserves the all-zeros Subnet-Router anycast address (RFC 4291), so allocation starts at ::1:

# let p = Ipam.Pool.v (Ipaddr.Prefix.of_string_exn "fd00::/64") in
  match Ipam.Pool.request p with
  | Ok (addr, _) -> Ipaddr.to_string addr
  | Error e -> Ipam.string_of_error e
- : string = "fd00::1"

IPv6 range limit#

Addresses are indexed by their offset from the pool's network address in an int-keyed bitmap, so a pool manages at most Ipam.Pool.max_addresses (2^61) addresses. IPv4 ranges always fit. An IPv6 range wider than that only ever allocates from its first max_addresses addresses; the rest report Out_of_range. In practice a /64 host pool is allocated from sequentially and never approaches the limit.

Interoperability#

test/interop/moby/ replays an operation trace generated by Docker libnetwork's default IPAM driver and checks ipam produces identical pool subnetting and address allocations. Regenerate the trace (needs the Go toolchain) with:

$ dune build @traces

License#

ISC, see LICENSE.md.