IPVarch/README.md

12 KiB
Raw Permalink Blame History

IPVarch — Internet Protocol Version Architect

A tiny ANSI C (C89, tcc-friendly) command-line tool for inspecting and planning IP addresses and IPv4 networks. Rules follow ipaddrinfo.html (IPv4 classes A–E, RFC 1918 private ranges, IPv6 unicast/multicast/ link-local/unique-local).

Usage

ipvarch <address>                              inspect an IPv4 or IPv6 address
ipvarch --plan-network <ipv4>/<prefix>         plan a network (/8 /16 /24 /32)
ipvarch --random-ipv4network <scope> <prefix>  random valid network plan
ipvarch --use-network <cidr> <label>           register a used network
ipvarch --list-networks                        list registered networks
ipvarch --forget-network <cidr>                unregister a network

Examples

$ ipvarch 192.168.1.1
Address: 192.168.1.1
Type:    IPv4
Class:   C
Scope:   Private (RFC 1918)
Octets:  192, 168, 1, 1

$ ipvarch 2001:db8::1
Address: 2001:db8::1
Type:    IPv6
Class:   n/a (IPv6 has no address classes)
Scope:   Public (global unicast)
Groups:  2001 0db8 0000 0000 0000 0000 0000 0001
Octets:  32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1

$ ipvarch --plan-network 192.168.1.0/24
┌───────────────────┬───────────────────────────────┐
│       IPVarch Network Plan - 192.168.1.0 /24      │
├───────────────────┼───────────────────────────────┤
│ Network address   │ 192.168.1.0                   │
│ Netmask           │ 255.255.255.0 /24             │
│ Broadcast         │ 192.168.1.255                 │
│ Usable host range │ 192.168.1.1 - 192.168.1.254   │
│ Usable hosts      │ 254                           │
│ Class             │ C                             │
│ Scope             │ Private (RFC 1918)            │
│ Gateway IP        │ 192.168.1.1                   │
│ DNS/DHCP server   │ 192.168.1.2                   │
│ DHCP pool         │ 192.168.1.100 - 192.168.1.254 │
└───────────────────┴───────────────────────────────┘

--plan-network takes literal CIDR masks (/8=255.0.0.0, /16, /24, /32=single host). Host bits must be zero (it tells you the canonical network address otherwise). The gateway is the first usable host, the DNS/DHCP server the second, and the DHCP pool is the fixed last-octet .100–.254 range.

--random-ipv4network private|public /8|/16|/24|/32 generates a random valid network constrained to real private space (10/8, 172.16/12, 192.168/16) or public/global-unicast space (never multicast, loopback, reserved, or documentation ranges) and prints the same plan table. Networks registered in the used-networks database (below) are skipped.

Used-networks database (IPAM)

IPVarch keeps a small SQLite-backed registry of networks that are already in use, so plans and random picks avoid them:

$ ipvarch --use-network 10.164.0.0/16 "Main Homelab Bridge"
Registered 10.164.0.0/16 as "Main Homelab Bridge" in .../networks.db

$ ipvarch --plan-network 10.164.9.0/24     # gains a row when overlapping
│ Used by           │ 10.164.0.0/16 ("Main Homelab Bridge") │

$ ipvarch --list-networks                  # 3-column overview table
$ ipvarch --forget-network 10.164.0.0/16
Removed 10.164.0.0/16 (was "Main Homelab Bridge")

Details:

  • --use-network accepts any prefix from /0 to /32; the address is canonicalized (host bits must be zero) and stored with the label (max 64 characters).
  • Any --plan-network that intersects a registered network shows a Used by: row naming it (plus +N more for additional overlaps).
  • --random-ipv4network rejects (and retries around) registered ranges; if no free network exists it errors out after 10000 attempts.
  • Registration is idempotent-checked: registering the exact same cidr again is a warning and exit code 2. Forgetting a cidr that is not registered (or registered under a different prefix length) is also a warning + exit code 2.

Database location

The database is a standard SQLite file, resolved as:

  1. $IPVARCH_DB — explicit path (used verbatim, created as needed);
  2. $XDG_CONFIG_HOME/ipvarch/networks.db;
  3. ~/.config/ipvarch/networks.db (the ipvarch directory is created automatically when registering a network).

Read-only modes (--plan-network, --list-networks, random) never create the file; if no database exists they simply behave as if nothing is registered. A database that cannot be opened is ignored with a stderr warning. Access is read-only for registration-free commands and read/write (creating the schema UsedNetworks) for register/forget.

Schema

The table layout lives in schema/used_networks.sql, the single source of truth. The Makefile compiles it into the binary at build time (as the generated schema_sql.c) — a purely development convenience: the shipped binary stays self-contained, and changing the schema means editing the SQL file and rebuilding. Only the live statements are embedded: leading -- comments and blank lines stay in the file for readers and are dropped from the generated array, which also keeps the embedded SQL free of double quotes so C escaping can never be the weak point of a build. The generated file is refreshed whenever the SQL file or the Makefile changes, so an edited generator can't be undone by a stale artifact. It is created with IF NOT EXISTS, so re-opening an initialised database is a no-op:

CREATE TABLE IF NOT EXISTS UsedNetworks (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 cidr TEXT NOT NULL UNIQUE,
 prefix INTEGER NOT NULL,
 start_hi INTEGER NOT NULL,
 start_lo INTEGER NOT NULL,
 end_hi INTEGER NOT NULL,
 end_lo INTEGER NOT NULL,
 label TEXT NOT NULL,
 created_at DATETIME DEFAULT CURRENT_TIMESTAMP)

Column reference:

Column Meaning
id autoincrement row key, present but unused by the tool logic
cidr canonical network text (a.b.c.d/p); UNIQUE — re-registering the same network fails here with a constraint violation (warning + exit 2)
prefix prefix length as registered (the registry accepts 0–32)
start_hi, start_lo first address of the covered range, split into two 16-bit halves
end_hi, end_lo last address of the covered range (network or-ed with the host bits)
label free-text label, max 64 characters, shown by --list-networks and the plan Used by: row
created_at registration timestamp (CURRENT_TIMESTAMP default)

Design notes:

  • The 32-bit network range is stored as two 16-bit INTEGER halves instead of one 64-bit value: the C code computes addresses in unsigned 32-bit math, and this split keeps the SQLite marshaling explicit (start_hi << 16 | start_lo on the way out).
  • Overlap detection is plain interval intersection over [start, end] performed in C (scan_used); the table needs no indexes or triggers for correctness.

Testing (Make + python3, stdlib only)

make              # build (compiler auto-detected: tcc, then gcc, then cc)
make CC=gcc       # force one compiler instead
make test         # full validity matrix: info, canonicalization, plan,
                  # random (statistical), used networks, invalid inputs,
                  # edge cases, usage
make test-sanitize  # rebuild with gcc/clang ASan+UBSan and run full matrix
make check        # strict C89 build: -std=c89 -pedantic -Wall -Wextra
make clean

Notes:

  • The runner prints PASS/FAIL per case and exits nonzero on failure.
  • Run a subset with python3 tests/runner.py --bin ./ipvarch --filter plan.
  • Add cases as simple dict entries in tests/cases.py.
  • Each run hands the test binary a fresh temporary IPVARCH_DB (use --keep-db to inspect it afterwards), so runs are isolated and your personal databases are never touched.
  • Memory safety runs through compiler instrumentation (tcc has no sanitizer support, so test-sanitize uses gcc/clang); the runner detects sanitizer aborts explicitly and reports them per case.

Exit codes

  • 0 — success
  • 1 — any error (bad address, bad prefix, bad usage); the message goes to stderr prefixed with ipvarch: error:.
  • 2 — warning paths of the database commands: duplicate registration (--use-network of an already-registered cidr) and forgetting a network that is not registered.

Layout and dependencies

  • ipvarch.c — modes, argument parsing, address math.
  • ipvtbl.c/.h — shared UTF-8 box-table renderer.
  • ipvarch_db.c/.h — used-networks SQLite backend.
  • vendor/sqlite3.c, vendor/sqlite3.h — vendored SQLite amalgamation 3.54.1 from https://www.sqlite.org/2026/sqlite-amalgamation-3540100.zip (public domain — free for any use, no attribution required); compiled separately with $(VCC), which follows the auto-detected $(CC) (tcc, then gcc, then cc), and tagged with it as vendor/sqlite3.<compiler>.o so switching toolchains can never reuse a stale object. Linked in, so the only third-party dependency is vendored and there is nothing to install.

Nothing links against libdl, libpthread or libm. The amalgamation is built with SQLITE_THREADSAFE=0, SQLITE_OMIT_LOAD_EXTENSION and SQLITE_OMIT_FLOATING_POINT (the SQL_DEFS variable in the Makefile), which removes every reference those libraries would otherwise serve — fabs is the only math symbol SQLite needs at all. So the binary depends on libc alone, which is what lets a tcc-only Buildroot box build it: glibc 2.34 and later folded dl and pthread into libc anyway, minimal distributions often ship no dev libraries at all, and tcc aborts the entire link when a -l flag cannot be satisfied. Nothing IPVarch uses is given up: the registry stores INTEGER and TEXT and never REAL, and the tool is single-threaded. If a future schema needs REAL columns or %f in sqlite3_mprintf, drop the last define and add -lm back. make check, the schema generator and the sanitizer runner all read the same flags from the Makefile, so no build path can drift out of configuration.

Code style

I write IPVarch in what I'll call a Stroustrup hybrid: an opening brace always stays inline with the statement it belongs to (function definitions included), while else always drops to its own line after the closing brace of the block it follows.

static int parse_ipv4(const char *s, byte out[4]) {
    if (ok) {
        return 1;
    }
    else {
        return 0;
    }
}

A floating { on its own line wastes vertical space, and a jammed } else { buries the branch in the middle of a line — this way I keep the density and still see every branch entry running down the left margin. It is formatting only, so no behaviour rests on it, but please keep the convention when patching.

License

IPVarch — the program and its test suite (ipvarch.c, ipvtbl.c, ipvtbl.h, ipvarch_db.c, ipvarch_db.h, tests/) — is licensed under the GNU General Public License version 3 or (at your option) any later version; see the LICENSE file for the full text.

Copyright © 2026 Mark Robillard Jr (MARKMENTAL)

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Notes on other material in this repository:

  • vendor/sqlite3.c and vendor/sqlite3.h are the public-domain SQLite amalgamation and remain unmodified and public domain; linking them imposes no license conditions and they are not covered by the GPL notice above.
  • ipaddrinfo.html is retained reference material describing the rules IPVarch implements; it is not part of the IPVarch source and not covered by this license.