928 lines
28 KiB
C
928 lines
28 KiB
C
/*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
* ipvarch.c - IPVarch: Internet Protocol Version Architect
|
|
*
|
|
* A small ANSI C (C89) command line tool for inspecting and planning
|
|
* IP addresses and IPv4 networks.
|
|
*
|
|
* Modes:
|
|
* ipvarch <address> inspect an IPv4 or IPv6 address
|
|
* ipvarch --plan-network <ipv4>/<prefix> print a network plan table
|
|
* ipvarch --random-ipv4network <scope> <prefix>
|
|
* generate a random valid plan
|
|
*
|
|
* Targets tcc (Tiny C Compiler); also builds with gcc -std=c89 -pedantic.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <time.h>
|
|
|
|
#include "ipvtbl.h"
|
|
#include "ipvarch_db.h"
|
|
|
|
typedef unsigned char byte;
|
|
|
|
typedef const char *Row[2];
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Helpers */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void die(const char *fmt, ...) {
|
|
va_list ap;
|
|
fprintf(stderr, "ipvarch: error: ");
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fputc('\n', stderr);
|
|
exit(1);
|
|
}
|
|
|
|
static void usage(void) {
|
|
printf("IPVarch - Internet Protocol Version Architect\n\n");
|
|
printf("Usage:\n");
|
|
printf(" ipvarch <address> inspect an IPv4/IPv6 address\n");
|
|
printf(" ipvarch --plan-network <ipv4>/<prefix> plan a network (/8 /16 /24 /32)\n");
|
|
printf(" ipvarch --random-ipv4network <scope> <prefix>\n");
|
|
printf(" random valid network plan\n\n");
|
|
printf(" ipvarch --use-network <cidr> <label> register a used network\n");
|
|
printf(" ipvarch --list-networks list registered networks\n");
|
|
printf(" ipvarch --forget-network <cidr> unregister a network\n\n");
|
|
printf("Examples:\n");
|
|
printf(" ipvarch 192.168.1.1\n");
|
|
printf(" ipvarch 2001:db8::1\n");
|
|
printf(" ipvarch --plan-network 172.16.0.0/16\n");
|
|
printf(" ipvarch --random-ipv4network private /24\n");
|
|
printf(" ipvarch --use-network 10.164.0.0/16 \"Main Homelab Bridge\"\n");
|
|
printf(" ipvarch --list-networks\n");
|
|
return;
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Parsing */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int parse_ipv4(const char *s, byte out[4]) {
|
|
const char *p = s;
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
int v = 0, digits = 0;
|
|
if (i > 0) {
|
|
if (*p != '.')
|
|
return 0;
|
|
p++;
|
|
}
|
|
while (*p >= '0' && *p <= '9') {
|
|
v = v * 10 + (*p - '0');
|
|
digits++;
|
|
p++;
|
|
if (digits > 3 || v > 255)
|
|
return 0;
|
|
}
|
|
if (digits == 0)
|
|
return 0;
|
|
out[i] = (byte)v;
|
|
}
|
|
if (*p != '\0')
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int hexval(int c) {
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
/* Parse a colon separated piece list such as "ffff:0:192.168.1.1".
|
|
* The final token may be an embedded IPv4 address (two groups).
|
|
* Returns count via *cnt and 1 on success. */
|
|
static int parse_piece(const char *piece, unsigned short *gr, int *cnt) {
|
|
char buf[48];
|
|
byte v4[4];
|
|
const char *p;
|
|
int n = 0;
|
|
|
|
if (strlen(piece) >= sizeof(buf))
|
|
return 0;
|
|
strcpy(buf, piece);
|
|
p = buf;
|
|
|
|
while (*p != '\0') {
|
|
const char *tok = p;
|
|
int hasdot = 0;
|
|
|
|
while (*p != '\0' && *p != ':') {
|
|
if (*p == '.')
|
|
hasdot = 1;
|
|
p++;
|
|
}
|
|
|
|
if (hasdot) {
|
|
if (*p != '\0') /* embedded IPv4 must be last */
|
|
return 0;
|
|
if (!parse_ipv4(tok, v4))
|
|
return 0;
|
|
if (n > 6)
|
|
return 0;
|
|
gr[n++] = (unsigned short)((v4[0] << 8) | v4[1]);
|
|
gr[n++] = (unsigned short)((v4[2] << 8) | v4[3]);
|
|
}
|
|
else {
|
|
int len = (int)(p - tok);
|
|
int j, val = 0;
|
|
if (len < 1 || len > 4)
|
|
return 0;
|
|
for (j = 0; j < len; j++) {
|
|
int hv = hexval((unsigned char)tok[j]);
|
|
if (hv < 0)
|
|
return 0;
|
|
val = (val << 4) | hv;
|
|
}
|
|
if (n >= 8)
|
|
return 0;
|
|
gr[n++] = (unsigned short)val;
|
|
}
|
|
|
|
if (*p == '\0')
|
|
break;
|
|
p++; /* skip ':' */
|
|
if (*p == '\0') /* trailing colon */
|
|
return 0;
|
|
}
|
|
|
|
*cnt = n;
|
|
return 1;
|
|
}
|
|
|
|
/* Parse an IPv6 address (RFC 4291 text form, compressed "::" allowed,
|
|
* embedded IPv4 in the tail allowed) into 16 bytes. */
|
|
static int parse_ipv6(const char *s, byte out[16]) {
|
|
char buf[80];
|
|
char *dd, *head, *tail;
|
|
unsigned short gr[16];
|
|
int nh = 0, nt = 0, total, i;
|
|
|
|
if (strlen(s) >= sizeof(buf))
|
|
return 0;
|
|
strcpy(buf, s);
|
|
|
|
dd = strstr(buf, "::");
|
|
if (dd != NULL) {
|
|
if (strstr(dd + 1, "::") != NULL)
|
|
return 0;
|
|
*dd = '\0';
|
|
head = buf;
|
|
tail = dd + 2;
|
|
/* An embedded IPv4 is only valid in the tail piece. */
|
|
if (strchr(head, '.') != NULL && strchr(head, ':') == NULL)
|
|
return 0;
|
|
}
|
|
else {
|
|
head = buf;
|
|
tail = NULL;
|
|
}
|
|
|
|
if (!parse_piece(head, gr, &nh))
|
|
return 0;
|
|
if (tail != NULL) {
|
|
if (!parse_piece(tail, gr + nh, &nt))
|
|
return 0;
|
|
}
|
|
total = nh + nt;
|
|
|
|
if (dd != NULL) {
|
|
if (total > 7) /* "::" must stand for zero groups */
|
|
return 0;
|
|
memset(out, 0, 16);
|
|
for (i = 0; i < nh; i++) {
|
|
out[2 * i] = (byte)(gr[i] >> 8);
|
|
out[2 * i + 1] = (byte)(gr[i] & 0xFF);
|
|
}
|
|
for (i = 0; i < nt; i++) {
|
|
int k = 8 - nt + i;
|
|
out[2 * k] = (byte)(gr[nh + i] >> 8);
|
|
out[2 * k + 1] = (byte)(gr[nh + i] & 0xFF);
|
|
}
|
|
}
|
|
else {
|
|
if (total != 8)
|
|
return 0;
|
|
for (i = 0; i < 8; i++) {
|
|
out[2 * i] = (byte)(gr[i] >> 8);
|
|
out[2 * i + 1] = (byte)(gr[i] & 0xFF);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Formatting */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void fmt_v4(char *dst, const byte a[4]) {
|
|
sprintf(dst, "%u.%u.%u.%u",
|
|
(unsigned)a[0], (unsigned)a[1], (unsigned)a[2], (unsigned)a[3]);
|
|
return;
|
|
}
|
|
|
|
/* Compressed RFC 5952 style text form. */
|
|
static void fmt_v6(char *dst, const byte a[16]) {
|
|
unsigned short g[8];
|
|
int i, zs = -1, zl = 0, bs = -1, bl = 0, first = 1, suppress = 0;
|
|
char *p = dst;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
g[i] = (unsigned short)((a[2 * i] << 8) | a[2 * i + 1]);
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
if (g[i] == 0) {
|
|
if (zl == 0)
|
|
zs = i;
|
|
zl++;
|
|
if (zl > bl) {
|
|
bl = zl;
|
|
bs = zs;
|
|
}
|
|
}
|
|
else {
|
|
zl = 0;
|
|
}
|
|
}
|
|
if (bl < 2) {
|
|
bs = 0;
|
|
bl = 0;
|
|
}
|
|
|
|
if (bl == 8) {
|
|
strcpy(dst, "::");
|
|
return;
|
|
}
|
|
|
|
i = 0;
|
|
while (i < 8) {
|
|
if (bl > 0 && i >= bs && i < bs + bl) {
|
|
*p++ = ':';
|
|
*p++ = ':';
|
|
suppress = 1; /* "::" replaces the join */
|
|
first = 0;
|
|
i = bs + bl;
|
|
continue;
|
|
}
|
|
if (!first && !suppress)
|
|
*p++ = ':';
|
|
suppress = 0;
|
|
p += sprintf(p, "%x", (unsigned)g[i]);
|
|
first = 0;
|
|
i++;
|
|
}
|
|
*p = '\0';
|
|
return;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Classification (rules per ipaddrinfo.html) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void classify_v4(const byte a[4], const char **cls, const char **scope) {
|
|
if (a[0] == 0) {
|
|
*cls = "0 (reserved)";
|
|
*scope = "Reserved (this network)";
|
|
}
|
|
else if (a[0] < 128) {
|
|
*cls = "A";
|
|
*scope = "Public";
|
|
}
|
|
else if (a[0] < 192) {
|
|
*cls = "B";
|
|
*scope = "Public";
|
|
}
|
|
else if (a[0] < 224) {
|
|
*cls = "C";
|
|
*scope = "Public";
|
|
}
|
|
else if (a[0] < 240) {
|
|
*cls = "D";
|
|
*scope = "Multicast";
|
|
}
|
|
else {
|
|
*cls = "E";
|
|
*scope = "Reserved";
|
|
}
|
|
|
|
if (a[0] == 127)
|
|
*scope = "Loopback";
|
|
else if (a[0] == 10)
|
|
*scope = "Private (RFC 1918)";
|
|
else if (a[0] == 172 && a[1] >= 16 && a[1] <= 31)
|
|
*scope = "Private (RFC 1918)";
|
|
else if (a[0] == 192 && a[1] == 168)
|
|
*scope = "Private (RFC 1918)";
|
|
else if (a[0] == 169 && a[1] == 254)
|
|
*scope = "Link-local (APIPA)";
|
|
else if (a[0] == 192 && a[1] == 0 && a[2] == 2)
|
|
*scope = "Documentation (TEST-NET-1)";
|
|
else if (a[0] == 198 && a[1] == 51 && a[2] == 100)
|
|
*scope = "Documentation (TEST-NET-2)";
|
|
else if (a[0] == 203 && a[1] == 0 && a[2] == 113)
|
|
*scope = "Documentation (TEST-NET-3)";
|
|
else if (a[0] == 100 && a[1] >= 64 && a[1] <= 127)
|
|
*scope = "Shared (CGNAT)";
|
|
else if (a[0] == 198 && (a[1] == 18 || a[1] == 19))
|
|
*scope = "Benchmarking";
|
|
return;
|
|
}
|
|
|
|
static void classify_v6(const byte a[16], const char **scope) {
|
|
int i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
if (a[i] != 0)
|
|
break;
|
|
}
|
|
if (i == 16) {
|
|
*scope = "Unspecified (::)";
|
|
return;
|
|
}
|
|
if (a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0 && a[4] == 0 &&
|
|
a[5] == 0 && a[6] == 0 && a[7] == 0 && a[8] == 0 && a[9] == 0 &&
|
|
a[10] == 0 && a[11] == 0 && a[12] == 0 && a[13] == 0 &&
|
|
a[14] == 0 && a[15] == 1) {
|
|
*scope = "Loopback (::1)";
|
|
return;
|
|
}
|
|
if (a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0 && a[4] == 0 &&
|
|
a[5] == 0 && a[6] == 0 && a[7] == 0 && a[8] == 0 && a[9] == 0 &&
|
|
a[10] == 0xFF && a[11] == 0xFF) {
|
|
*scope = "IPv4-mapped (::ffff:0:0/96)";
|
|
return;
|
|
}
|
|
if (a[0] == 0xFF) {
|
|
*scope = "Multicast (ff00::/8)";
|
|
return;
|
|
}
|
|
if (a[0] == 0xFE && (a[1] & 0xC0) == 0x80) {
|
|
*scope = "Link-local (fe80::/10)";
|
|
return;
|
|
}
|
|
if ((a[0] & 0xFE) == 0xFC) {
|
|
*scope = "Private (unique local, fc00::/7)";
|
|
return;
|
|
}
|
|
if (a[0] == 0x20 && a[1] == 0x01 && a[2] == 0x0D && a[3] == 0xB8) {
|
|
*scope = "Documentation (2001:db8::/32)";
|
|
return;
|
|
}
|
|
*scope = "Public (global unicast)";
|
|
return;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* IPv4 network math */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static unsigned long v4_to_u32(const byte a[4]) {
|
|
return ((unsigned long)a[0] << 24) | ((unsigned long)a[1] << 16) |
|
|
((unsigned long)a[2] << 8) | (unsigned long)a[3];
|
|
}
|
|
|
|
static void u32_to_v4(unsigned long u, byte out[4]) {
|
|
out[0] = (byte)((u >> 24) & 0xFF);
|
|
out[1] = (byte)((u >> 16) & 0xFF);
|
|
out[2] = (byte)((u >> 8) & 0xFF);
|
|
out[3] = (byte)(u & 0xFF);
|
|
return;
|
|
}
|
|
|
|
static void print_plan(unsigned long net, int prefix, const char *usedby) {
|
|
unsigned long mask, bc, cnt, firstu, lastu;
|
|
byte a[4], m4[4], b4[4], lo4[4], hi4[4];
|
|
char tmp1[24], tmp2[24];
|
|
char nb[24], mb[40], bb[24], rb[64], cb[24], gb[24], db[24], pb[64];
|
|
char sb[48], tb[80];
|
|
const char *cls, *scope;
|
|
Row rows[11];
|
|
int nr = 0;
|
|
|
|
mask = (prefix == 8) ? 0xFF000000ul :
|
|
(prefix == 16) ? 0xFFFF0000ul :
|
|
(prefix == 24) ? 0xFFFFFF00ul : 0xFFFFFFFFul;
|
|
bc = net | (~mask & 0xFFFFFFFFul);
|
|
|
|
u32_to_v4(net, a);
|
|
u32_to_v4(mask, m4);
|
|
classify_v4(a, &cls, &scope);
|
|
|
|
fmt_v4(nb, a);
|
|
fmt_v4(tmp1, m4);
|
|
sprintf(mb, "%s /%d", tmp1, prefix);
|
|
|
|
if (prefix == 32) {
|
|
strcpy(bb, "n/a (single host)");
|
|
strcpy(rb, nb); /* the host address itself */
|
|
strcpy(cb, "1");
|
|
strcpy(gb, "n/a (single host)");
|
|
strcpy(db, "n/a (single host)");
|
|
strcpy(pb, "disabled (single-host network)");
|
|
}
|
|
else {
|
|
firstu = net + 1;
|
|
lastu = bc - 1;
|
|
cnt = (1ul << (32 - prefix)) - 2;
|
|
|
|
u32_to_v4(bc, b4);
|
|
fmt_v4(bb, b4);
|
|
u32_to_v4(firstu, lo4);
|
|
fmt_v4(tmp1, lo4);
|
|
u32_to_v4(lastu, hi4);
|
|
fmt_v4(tmp2, hi4);
|
|
sprintf(rb, "%s - %s", tmp1, tmp2);
|
|
sprintf(cb, "%lu", cnt);
|
|
|
|
fmt_v4(gb, lo4); /* gateway: first usable */
|
|
u32_to_v4(firstu + 1, hi4);
|
|
fmt_v4(db, hi4); /* DNS/DHCP: next usable */
|
|
|
|
hi4[0] = a[0]; hi4[1] = a[1]; hi4[2] = a[2]; hi4[3] = 254;
|
|
lo4[3] = 100;
|
|
fmt_v4(tmp1, lo4);
|
|
fmt_v4(tmp2, hi4);
|
|
sprintf(pb, "%s - %s", tmp1, tmp2);
|
|
}
|
|
|
|
sprintf(sb, "%s", scope);
|
|
sprintf(tb, "IPVarch Network Plan - %s /%d", nb, prefix);
|
|
|
|
rows[nr][0] = "Network address"; rows[nr][1] = nb; nr++;
|
|
rows[nr][0] = "Netmask"; rows[nr][1] = mb; nr++;
|
|
rows[nr][0] = "Broadcast"; rows[nr][1] = bb; nr++;
|
|
rows[nr][0] = "Usable host range"; rows[nr][1] = rb; nr++;
|
|
rows[nr][0] = "Usable hosts"; rows[nr][1] = cb; nr++;
|
|
rows[nr][0] = "Class"; rows[nr][1] = cls; nr++;
|
|
rows[nr][0] = "Scope"; rows[nr][1] = sb; nr++;
|
|
rows[nr][0] = "Gateway IP"; rows[nr][1] = gb; nr++;
|
|
rows[nr][0] = "DNS/DHCP server"; rows[nr][1] = db; nr++;
|
|
rows[nr][0] = "DHCP pool"; rows[nr][1] = pb; nr++;
|
|
if (usedby != NULL && usedby[0] != '\0') {
|
|
rows[nr][0] = "Used by"; rows[nr][1] = usedby; nr++;
|
|
}
|
|
|
|
print_table(tb, rows, nr);
|
|
return;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Random network generation */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int is_ok_public(const byte a[4]) {
|
|
if (a[0] == 0 || a[0] == 10 || a[0] == 127)
|
|
return 0;
|
|
if (a[0] == 172 && a[1] >= 16 && a[1] <= 31)
|
|
return 0;
|
|
if (a[0] == 192 && a[1] == 168)
|
|
return 0;
|
|
if (a[0] == 169 && a[1] == 254)
|
|
return 0;
|
|
if (a[0] == 100 && a[1] >= 64 && a[1] <= 127)
|
|
return 0;
|
|
if (a[0] == 198 && (a[1] == 18 || a[1] == 19))
|
|
return 0;
|
|
if (a[0] == 192 && a[1] == 0 && a[2] == 2)
|
|
return 0;
|
|
if (a[0] == 198 && a[1] == 51 && a[2] == 100)
|
|
return 0;
|
|
if (a[0] == 203 && a[1] == 0 && a[2] == 113)
|
|
return 0;
|
|
if (a[0] >= 224)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void random_network(int prefix, int wantprivate, byte out[4]) {
|
|
int tries, r;
|
|
byte a[4];
|
|
|
|
for (tries = 0; tries < 10000; tries++) {
|
|
r = rand();
|
|
if (wantprivate) {
|
|
if (prefix == 8) {
|
|
/* Only one valid private /8 exists: 10.0.0.0/8 */
|
|
a[0] = 10; a[1] = 0; a[2] = 0; a[3] = 0;
|
|
}
|
|
else if (prefix == 16) {
|
|
if (r % 2) {
|
|
a[0] = 192; a[1] = 168; a[2] = rand() % 256;
|
|
}
|
|
else {
|
|
a[0] = 172; a[1] = 16 + rand() % 16; a[2] = rand() % 256;
|
|
}
|
|
a[3] = 0;
|
|
}
|
|
else {
|
|
r = rand() % 8;
|
|
if (r < 4) {
|
|
a[0] = 10; a[1] = rand() % 256; a[2] = rand() % 256;
|
|
}
|
|
else if (r < 6) {
|
|
a[0] = 172; a[1] = 16 + rand() % 16; a[2] = rand() % 256;
|
|
}
|
|
else {
|
|
a[0] = 192; a[1] = 168; a[2] = rand() % 256;
|
|
}
|
|
if (prefix == 24)
|
|
a[3] = 0;
|
|
else
|
|
a[3] = 1 + rand() % 254;
|
|
}
|
|
}
|
|
else {
|
|
a[0] = (rand() % 223) + 1; /* avoids class D/E and 0 */
|
|
if (a[0] == 10 || a[0] == 127)
|
|
continue;
|
|
a[1] = rand() % 256;
|
|
a[2] = rand() % 256;
|
|
a[3] = (prefix == 32) ? (1 + rand() % 254) : 0;
|
|
}
|
|
/* Zero the host bits for the chosen prefix. */
|
|
if (prefix == 8) { a[1] = 0; a[2] = 0; a[3] = 0; }
|
|
if (prefix == 16) { a[2] = 0; a[3] = 0; }
|
|
if (prefix == 24) { a[3] = 0; }
|
|
if (!wantprivate && !is_ok_public(a))
|
|
continue;
|
|
out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3];
|
|
return;
|
|
}
|
|
die("could not generate a suitable random network; please retry");
|
|
return;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Modes */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void mode_info(const char *arg) {
|
|
byte a[16];
|
|
const char *cls = "", *scope = "";
|
|
char nb[64], gb[96], ob[96];
|
|
int i;
|
|
unsigned short g[8];
|
|
char *q;
|
|
|
|
if (strchr(arg, '/') != NULL)
|
|
die("info mode takes a plain address; for subnets use --plan-network");
|
|
|
|
if (strchr(arg, ':') != NULL) {
|
|
if (!parse_ipv6(arg, a))
|
|
die("invalid IPv6 address: %s", arg);
|
|
fmt_v6(nb, a);
|
|
classify_v6(a, &scope);
|
|
for (i = 0; i < 8; i++)
|
|
g[i] = (unsigned short)((a[2 * i] << 8) | a[2 * i + 1]);
|
|
|
|
printf("Address: %s\n", nb);
|
|
printf("Type: IPv6\n");
|
|
printf("Class: n/a (IPv6 has no address classes)\n");
|
|
printf("Scope: %s\n", scope);
|
|
|
|
q = gb;
|
|
for (i = 0; i < 8; i++)
|
|
q += sprintf(q, "%s%04x", i > 0 ? " " : "", (unsigned)g[i]);
|
|
printf("Groups: %s\n", gb);
|
|
|
|
q = ob;
|
|
for (i = 0; i < 16; i++)
|
|
q += sprintf(q, "%s%u", i > 0 ? ", " : "", (unsigned)a[i]);
|
|
printf("Octets: %s\n", ob);
|
|
}
|
|
else {
|
|
byte a4[4];
|
|
if (!parse_ipv4(arg, a4))
|
|
die("invalid IPv4 or IPv6 address: %s", arg);
|
|
classify_v4(a4, &cls, &scope);
|
|
|
|
q = ob;
|
|
for (i = 0; i < 4; i++)
|
|
q += sprintf(q, "%s%u", i > 0 ? ", " : "", (unsigned)a4[i]);
|
|
|
|
printf("Address: %u.%u.%u.%u\n",
|
|
(unsigned)a4[0], (unsigned)a4[1], (unsigned)a4[2], (unsigned)a4[3]);
|
|
printf("Type: IPv4\n");
|
|
printf("Class: %s\n", cls);
|
|
printf("Scope: %s\n", scope);
|
|
printf("Octets: %s\n", ob);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* 32-bit network mask for the four supported plan prefixes. */
|
|
static unsigned long mask_of_prefix(int prefix) {
|
|
if (prefix == 0) return 0UL;
|
|
if (prefix <= 8) return 0xFF000000UL;
|
|
if (prefix <= 16) return 0xFFFF0000UL;
|
|
if (prefix <= 24) return 0xFFFFFF00UL;
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
/* Parse "<ipv4>/<0-32>" and canonicalize; dies on error, including
|
|
* host bits set (the message names the canonical network address). */
|
|
static void parse_network_arg(const char *arg, unsigned long *net,
|
|
int *prefix, int plan_only) {
|
|
char buf[80];
|
|
char *slash;
|
|
byte a[4];
|
|
unsigned long u, mask;
|
|
|
|
if (strlen(arg) >= sizeof(buf) - 1)
|
|
die("argument too long: %s", arg);
|
|
strcpy(buf, arg);
|
|
|
|
slash = strchr(buf, '/');
|
|
if (slash == NULL)
|
|
die("expected a subnet with a mask suffix (e.g. 192.168.1.0/24)");
|
|
*slash = '\0';
|
|
|
|
if (!parse_ipv4(buf, a))
|
|
die("invalid IPv4 address in subnet: %s", buf);
|
|
|
|
*prefix = atoi(slash + 1);
|
|
if (*prefix < 0 || *prefix > 32)
|
|
die("invalid prefix '%s' (expected a value of 0-32)", slash + 1);
|
|
|
|
if (plan_only && *prefix != 8 && *prefix != 16 &&
|
|
*prefix != 24 && *prefix != 32) {
|
|
char pbuf[16];
|
|
sprintf(pbuf, "/%d", *prefix);
|
|
die("unsupported prefix '%s' (supported: /8 /16 /24 /32)", pbuf);
|
|
}
|
|
|
|
mask = mask_of_prefix(*prefix);
|
|
u = v4_to_u32(a);
|
|
*net = u & mask;
|
|
|
|
if (*net != u) {
|
|
byte c[4];
|
|
u32_to_v4(*net, c);
|
|
die("host bits are set; the network address for that mask is %u.%u.%u.%u/%d",
|
|
(unsigned)c[0], (unsigned)c[1], (unsigned)c[2], (unsigned)c[3],
|
|
*prefix);
|
|
}
|
|
}
|
|
|
|
/* Canonical "<ipv4>/<prefix>" rendering of a parsed network. */
|
|
static void canonical_cidr(unsigned long net, int prefix, char *dst) {
|
|
byte a[4];
|
|
|
|
u32_to_v4(net, a);
|
|
fmt_v4(dst, a);
|
|
sprintf(dst + strlen(dst), "/%d", prefix);
|
|
}
|
|
|
|
static void mode_plan(const char *arg) {
|
|
char used[200];
|
|
unsigned long net;
|
|
int prefix;
|
|
|
|
parse_network_arg(arg, &net, &prefix, 1);
|
|
{
|
|
byte a[4];
|
|
u32_to_v4(net, a);
|
|
if (a[0] == 0)
|
|
die("0.0.0.0 networks are reserved and cannot be planned");
|
|
if (a[0] >= 224)
|
|
die("multicast and reserved networks (class D/E) cannot be planned");
|
|
}
|
|
|
|
used[0] = '\0';
|
|
if (db_init(0) == DB_IO)
|
|
fprintf(stderr, "ipvarch: warning: used-networks database ignored: %s\n",
|
|
db_last_error());
|
|
else if (db_used_by_plan(net, net | (0xFFFFFFFFUL ^ mask_of_prefix(prefix)),
|
|
used, (int)sizeof used) == DB_IO)
|
|
fprintf(stderr, "ipvarch: warning: used-networks database ignored: %s\n",
|
|
db_last_error());
|
|
|
|
print_plan(net, prefix, used);
|
|
return;
|
|
}
|
|
|
|
static void mode_random(const char *scope_s, const char *prefix_s) {
|
|
char lc[16];
|
|
const char *p;
|
|
byte net[4];
|
|
unsigned long u, mask;
|
|
int prefix, priv, ov, tries;
|
|
char *q;
|
|
|
|
if (strlen(scope_s) >= sizeof(lc))
|
|
die("invalid scope: %s", scope_s);
|
|
q = lc;
|
|
for (p = scope_s; *p != '\0'; p++)
|
|
*q++ = (char)((*p >= 'A' && *p <= 'Z') ? (*p - 'A' + 'a') : *p);
|
|
*q = '\0';
|
|
|
|
if (strcmp(lc, "private") == 0)
|
|
priv = 1;
|
|
else if (strcmp(lc, "public") == 0)
|
|
priv = 0;
|
|
else
|
|
die("invalid scope '%s' (expected private or public)", scope_s);
|
|
|
|
if (prefix_s[0] == '/')
|
|
prefix_s++;
|
|
prefix = atoi(prefix_s);
|
|
if (prefix != 8 && prefix != 16 && prefix != 24 && prefix != 32)
|
|
die("unsupported prefix '/%s' (supported: /8 /16 /24 /32)", prefix_s);
|
|
|
|
srand((unsigned)time(NULL));
|
|
if (db_init(0) == DB_IO)
|
|
fprintf(stderr, "ipvarch: warning: used-networks database ignored: %s\n",
|
|
db_last_error());
|
|
|
|
printf("Random network plan (--random-ipv4network %s /%d)\n\n",
|
|
priv ? "private" : "public", prefix);
|
|
|
|
mask = mask_of_prefix(prefix);
|
|
for (tries = 0; tries < 10000; tries++) {
|
|
random_network(prefix, priv, net);
|
|
u = v4_to_u32(net);
|
|
ov = db_overlaps_any(u, u | (0xFFFFFFFFUL ^ mask));
|
|
if (ov == DB_IO)
|
|
die("database error: %s", db_last_error());
|
|
if (ov == 0) {
|
|
print_plan(u, prefix, NULL);
|
|
return;
|
|
}
|
|
}
|
|
die("no free network available for %s /%d after 10000 attempts",
|
|
priv ? "private" : "public", prefix);
|
|
return;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Used-networks database modes */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#define LIST_MAX 512
|
|
|
|
static struct {
|
|
char cidr[24];
|
|
char label[80];
|
|
char created[24];
|
|
} g_lrows[LIST_MAX];
|
|
static int g_lrows_n = 0;
|
|
static int g_lrows_overflow = 0;
|
|
static int g_list_total = 0;
|
|
|
|
static void list_row_cb(const char *cidr, const char *label,
|
|
const char *created) {
|
|
if (g_lrows_n >= LIST_MAX) {
|
|
g_lrows_overflow = 1;
|
|
return;
|
|
}
|
|
strcpy(g_lrows[g_lrows_n].cidr, cidr);
|
|
strcpy(g_lrows[g_lrows_n].label, label);
|
|
strcpy(g_lrows[g_lrows_n].created, created);
|
|
g_lrows_n++;
|
|
}
|
|
|
|
static void die_db(const char *context) {
|
|
die("%s: %s (database: %s)", context, db_last_error(),
|
|
(db_path()[0] != '\0') ? db_path() : "(unresolved)");
|
|
}
|
|
|
|
static void mode_use_network(const char *cidr, const char *label) {
|
|
unsigned long net;
|
|
int prefix, rc;
|
|
char canon[24];
|
|
|
|
if (label[0] == '\0')
|
|
die("empty label");
|
|
if (strlen(label) > DB_LABEL_MAX)
|
|
die("label too long (max %d characters)", DB_LABEL_MAX);
|
|
|
|
parse_network_arg(cidr, &net, &prefix, 0);
|
|
canonical_cidr(net, prefix, canon);
|
|
|
|
if (db_init(1) != DB_OK)
|
|
die_db("cannot open used-networks database");
|
|
|
|
rc = db_add_network(net, prefix, canon, label);
|
|
if (rc == DB_DUPLICATE) {
|
|
char existing[80];
|
|
existing[0] = '\0';
|
|
if (db_get_label(canon, existing, (int)sizeof existing) != DB_OK)
|
|
existing[0] = '\0';
|
|
fprintf(stderr,
|
|
"warning: network %s is already registered as \"%s\"\n",
|
|
canon, existing);
|
|
exit(2);
|
|
}
|
|
if (rc != DB_OK)
|
|
die_db("cannot register network");
|
|
printf("Registered %s as \"%s\" in %s\n", canon, label, db_path());
|
|
}
|
|
|
|
static void mode_forget_network(const char *cidr) {
|
|
unsigned long net;
|
|
int prefix, rc;
|
|
char canon[24], existing[80];
|
|
|
|
parse_network_arg(cidr, &net, &prefix, 0);
|
|
canonical_cidr(net, prefix, canon);
|
|
|
|
if (db_init(1) != DB_OK)
|
|
die_db("cannot open used-networks database");
|
|
|
|
rc = db_forget_network(canon, existing, (int)sizeof existing);
|
|
if (rc == DB_NOT_FOUND) {
|
|
fprintf(stderr, "warning: network %s is not registered\n", canon);
|
|
exit(2);
|
|
}
|
|
if (rc != DB_OK)
|
|
die_db("cannot forget network");
|
|
printf("Removed %s (was \"%s\")\n", canon, existing);
|
|
}
|
|
|
|
static void mode_list_networks(void) {
|
|
const char *rows[LIST_MAX + 1][3];
|
|
int i, rc;
|
|
|
|
if (db_init(0) == DB_IO)
|
|
die_db("cannot open used-networks database");
|
|
if (db_count(&g_list_total) != DB_OK)
|
|
die_db("cannot read used-networks database");
|
|
if (g_list_total == 0) {
|
|
printf("No networks registered yet.\n");
|
|
printf("Add one with: ipvarch --use-network <cidr> <label>\n");
|
|
return;
|
|
}
|
|
|
|
rc = db_list_networks(&list_row_cb);
|
|
if (rc != DB_OK)
|
|
die_db("cannot read used-networks database");
|
|
|
|
rows[0][0] = "CIDR";
|
|
rows[0][1] = "Label";
|
|
rows[0][2] = "Registered";
|
|
for (i = 0; i < g_lrows_n; i++) {
|
|
rows[i + 1][0] = g_lrows[i].cidr;
|
|
rows[i + 1][1] = g_lrows[i].label;
|
|
rows[i + 1][2] = g_lrows[i].created;
|
|
}
|
|
print_table3("IPVarch - UsedNetworks", rows, g_lrows_n + 1);
|
|
if (g_lrows_overflow)
|
|
printf("... (%d more networks not shown)\n", g_list_total - g_lrows_n);
|
|
return;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc >= 2 &&
|
|
(strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 ||
|
|
strcmp(argv[1], "help") == 0)) {
|
|
usage();
|
|
return 0;
|
|
}
|
|
if (argc >= 2 && strcmp(argv[1], "--plan-network") == 0) {
|
|
if (argc != 3)
|
|
die("--plan-network takes exactly one argument: <ipv4>/<prefix>");
|
|
mode_plan(argv[2]);
|
|
return 0;
|
|
}
|
|
if (argc >= 2 && strcmp(argv[1], "--random-ipv4network") == 0) {
|
|
if (argc != 4)
|
|
die("--random-ipv4network takes two arguments: <private|public> </8|/16|/24|/32>");
|
|
mode_random(argv[2], argv[3]);
|
|
return 0;
|
|
}
|
|
if (argc >= 2 && strcmp(argv[1], "--use-network") == 0) {
|
|
if (argc != 4)
|
|
die("--use-network takes two arguments: <cidr> <label>");
|
|
mode_use_network(argv[2], argv[3]);
|
|
return 0;
|
|
}
|
|
if (argc >= 2 && strcmp(argv[1], "--list-networks") == 0) {
|
|
if (argc != 2)
|
|
die("--list-networks takes no arguments");
|
|
mode_list_networks();
|
|
return 0;
|
|
}
|
|
if (argc >= 2 && strcmp(argv[1], "--forget-network") == 0) {
|
|
if (argc != 3)
|
|
die("--forget-network takes exactly one argument: <cidr>");
|
|
mode_forget_network(argv[2]);
|
|
return 0;
|
|
}
|
|
if (argc == 2) {
|
|
mode_info(argv[1]);
|
|
return 0;
|
|
}
|
|
usage();
|
|
return 1;
|
|
}
|