netviz GitHub

Address-space health with netviz ipam

netviz list subnets enumerates the prefixes an inventory happens to contain. It does not say whether the address plan is healthy. netviz ipam answers the three questions that come next:

How full is every prefix? What is left inside one? And what is broken?

netviz ipam [OPTIONS]

Nothing is probed and no device is contacted. Every number comes from the same prefix derivation the layer-3 diagram draws and the validator reasons about — netviz.subnets — so a utilisation figure, a subnet node in a rendering and an addressing finding can never tell three different stories.


Contents


Utilisation

With no options, netviz ipam prints one row per derived prefix:

$ netviz -i examples/campus ipam --family ipv4
VRF   PREFIX           IP  VLANS  HOSTS  USED  FREE    UTIL  DEVICES
----  ---------------  --  -----  -----  ----  ----  ------  -------
-     10.1.0.0/30       4  -          2     2     0  100.0%        2
-     10.1.10.0/24      4  10       254     3   251    1.2%        3
-     10.1.20.0/24      4  20       254     2   252    0.8%        2
-     10.2.0.0/30       4  -          2     2     0  100.0%        2
...
-     192.0.2.1/32      4  -          1     1     0  100.0%        1
...
-     198.51.100.8/30   4  -          2     2     0  100.0%        2
mgmt  10.1.99.0/24      4  99       254     4   250    1.6%        4
mgmt  10.2.99.0/24      4  99       254     3   251    1.2%        3
mgmt  10.3.99.0/24      4  99       254     3   251    1.2%        3

conflicts
no problems found
Column Meaning
VRF The routing instance the prefix is in, shown only when something is in one; - is the global instance.
PREFIX The derived prefix. Grouping is by prefix and instance, exactly as list subnets groups.
IP Address family, 4 or 6.
VLANS Every VLAN an interface addressed in the prefix belongs to, compacted (10,20,99 / 100-104). - for a routed or untagged prefix.
HOSTS Usable host addresses — see sizing below.
USED Distinct addresses the inventory configures inside the prefix.
FREE HOSTS - USED.
UTIL USED / HOSTS, yellow past 80 %, red past 95 %.
DEVICES Distinct elements holding an address in the prefix.

Rows are sorted by family, then network address, then prefix length — the same deterministic order list subnets and the layer-3 graph use, so two runs of the command over an unchanged tree produce byte-identical output.

Routing instances

A VRF is a routing table of its own, so it is an address space of its own (docs/schema.md §16.1). Everything on this page therefore works per instance:

--free and --next-free answer for every instance at once, which is the conservative reading: a block that is free in one table and used in another is not one to hand out without saying which table was meant.

Sizing

HOSTS is not 2^n. The rules are the ones the protocols actually specify:

Prefix Usable Why
IPv4 /1/30 2^n - 2 The all-zeros address is the network, the all-ones the broadcast.
IPv4 /31 2 RFC 3021 gives both addresses to the two routers of a point-to-point link.
IPv4 /32 1 A host route.
IPv6 /1/126 2^n - 1 No broadcast address, but RFC 4291 §2.6.1 reserves the all-zeros interface identifier as the subnet-router anycast address.
IPv6 /127 2 RFC 6164, the IPv6 point-to-point link.
IPv6 /128 1 A host route.

An IPv6 prefix is too large to print, so anything with 20 or more host bits is rendered as a power of two — a /64 shows 2^64, not twenty digits. A prefix that is in use but rounds to zero is shown as <0.1% rather than 0.0%, because "empty" and "two hosts in a /64" are different facts and only one of them means the prefix can be reclaimed.

USED counts distinct addresses, not placements: an address configured on two elements is one address two elements are fighting over, and it occupies one slot either way. The fight itself is reported as a conflict.


The same plan as a diagram

Everything netviz ipam prints is a table. The same numbers are also a picture: --layer ipam (docs/rendering.md) draws one box per prefix, nested inside the block it was carved out of, each with a utilisation bar and the free blocks left in it.

$ netviz -i examples/docker render --layer ipam -f svg -o plan.svg
rendered 13 node(s) and 2 edge(s) as svg at layer ipam to plan.svg

It is the same derivation — this module sizes the prefixes and the renderer draws what it says — so the diagram and this report cannot disagree. The layer is offered by netviz web in its switcher, by render -f html behind the same switcher as every other layer, and by netviz report --layer ipam.


Free space

--free PREFIX subtracts what is allocated from a prefix and prints the holes as the fewest CIDR blocks that cover them:

$ netviz -i examples/campus ipam --free 10.1.0.0/22
BLOCK          IP  HOSTS
-------------  --  -----
10.1.0.4/30     4      2
10.1.0.8/29     4      6
10.1.0.16/28    4     14
10.1.0.32/27    4     30
10.1.0.64/26    4     62
10.1.0.128/25   4    126
10.1.1.0/24     4    254
10.1.2.0/23     4    510
free space in 10.1.0.0/22: 8 block(s), 1 allocation(s) already carved out

Allocation happens a subnet at a time: a prefix nested inside PREFIX consumes the whole of itself, because the 250 free addresses in a /24 holding four hosts are not space anyone will hand to another department. 10.1.0.0/30 above is what swallowed the first four addresses.

An address that falls inside PREFIX while its own prefix does not — a summary configured as 10.0.0.1/8 inside a 10.0.0.0/16 plan — cannot consume its prefix without consuming the whole plan, so it consumes a host route instead.

Adjacent free blocks are fused before they are reported: two free /25s appear as the /24 they form, because that is the block that can actually be handed out. PREFIX may be written with host bits set — --free 10.1.0.1/22 means the 10.1.0.0/22 you were looking at.


Finding the next block

--next-free is the operation an engineer actually performs when adding a device. It prints one prefix and nothing else, so it composes:

$ netviz -i examples/campus ipam --next-free 10.1.0.0/16
10.1.1.0/24
$ netviz -i examples/campus ipam --next-free 10.1.10.0/23 --size /26
10.1.11.0/26
$ netviz -i examples/campus ipam --next-free 2001:db8:1::/48
2001:db8:1:1::/64

--size accepts 24 or /24. Left out, it defaults to a /24 for IPv4 and a /64 for IPv6 — RFC 4291 §2.5.4 makes the /64 the unit of an IPv6 plan, since SLAAC does not work in anything longer.

The search walks the free list in address order, so the block it returns is the lowest one available. It never enumerates candidates, which is what makes --next-free 2001:db8::/32 --size 64 — a search across 2^32 possible blocks — return immediately.

When there is no room, the command says so on stderr and exits 1:

$ netviz -i examples/campus ipam --next-free 10.1.10.0/24 --size 8
error: no free /8 inside 10.1.10.0/24; run 'netviz ipam --free 10.1.10.0/24' to see what is left

Aggregation

--aggregate collapses sibling prefixes that between them fill their supernet, so a large inventory produces a summary rather than a wall of /24s:

$ netviz -i examples/campus ipam --aggregate --family ipv6
PREFIX              IP  VLANS  HOSTS  USED  FREE    UTIL  DEVICES  PARTS
------------------  --  -----  -----  ----  ----  ------  -------  -----
2001:db8::1/128      6  -          1     1     0  100.0%        1      -
2001:db8::2/127      6  -          2     2     0  100.0%        2      2
2001:db8:1::/64      6  -       2^64     2  2^64   <0.1%        2      -
...
2001:db8:ff:2::/63   6  -       2^65     4  2^65   <0.1%        4      2

conflicts
no problems found

PARTS is how many declared prefixes the row stands for. Two prefixes are siblings when they are the two halves of one supernet, and a supernet is only collapsed when both halves are declared — at that point it holds no address the plan has not already accounted for. A supernet with one half declared is left alone, because collapsing it would let the summary claim space that is in fact free. The pass repeats to a fixed point, so four adjacent /26s become one /24 rather than two /25s.

The aggregate's HOSTS is the sum of its children's, not the capacity of the supernet. Two /25s really do lose four addresses to network and broadcast between them where a /24 drawn over them loses two; the sum is what the plan can hold, so it is what is reported.


Conflicts

The second half of the default report is the address-plan conflicts. They are not computed here: netviz ipam calls netviz.validate and filters the findings to the rules that are about addressing. There is one implementation of "is this address plan sound", and netviz validate and netviz ipam are two views of it — so a suppression in netviz.toml or a netviz/ignore annotation silences a conflict here exactly as it does there, and a rule re-graded per inventory is reported at the severity the inventory chose.

Conflict Rule Reimplemented?
A duplicate host address within a prefix, in one broadcast domain E004 (NV-A004) No — the existing rule is called.
The same address claimed twice within a prefix, across broadcast domains W106 (NV-A009) No — the existing rule is called.
Prefixes that overlap but are not nested W130 (NV-A010) New rule.
A nested prefix whose parent is declared in a different VLAN W131 (NV-A011) New rule.
An address configured outside every prefix on its link W132 (NV-A012) New rule.
A gateway / first hop outside its own subnet E020 (NV-A013) New rule.

The first two rows are the "duplicate host addresses within a prefix" check. The validator already distinguishes a clash inside one broadcast domain — an error, because nothing about it is deliberate — from the same address claimed across two, which is a warning because a prefix re-used per VLAN is a real design. Reproducing that distinction here would have meant a second implementation that could disagree with the first, so the two existing rules are called instead.

$ netviz -i tests/fixtures/invalid/w131-nested-prefix-other-domain.yaml ipam
PREFIX       IP  VLANS  HOSTS  USED   FREE   UTIL  DEVICES
-----------  --  -----  -----  ----  -----  -----  -------
10.0.0.0/16   4  10     65534     2  65532  <0.1%        2
10.0.5.0/24   4  20       254     2    252   0.8%        2

conflicts
warnings (1):
  w131-nested-prefix-other-domain.yaml#3:73  W131  subnet '10.0.5.0/24' sits inside '10.0.0.0/16', but the two are used in different broadcast domains: '10.0.5.0/24' in VLAN 20 and '10.0.0.0/16' in VLAN 10. Hosts in '10.0.0.0/16' treat every address of '10.0.5.0/24' as on-link, so they will ARP for it instead of routing to it.

1 warning

--conflicts prints the list on its own, without the utilisation table.

Why "overlapping but not nested" is a VLAN question

Two CIDR prefixes are always either disjoint or nested — that is what makes CIDR a tree. The overlap an operator means by "these subnets overlap" is therefore not about the bits: it is one prefix claimed by two segments that cannot reach each other. W130 reports exactly that, and W131 reports the nested variant where the parent is on a different VLAN from the child.

Both rules only compare interfaces that declare a vlan block. A host on an access port declares none — its broadcast domain is a property of the switch it is cabled to, not of its own document — so counting "untagged" as a domain of its own would fire on the ordinary pairing of a router sub-interface with the hosts it serves.

Declaring a gateway

E020 needs something to check, so the ipv4/ipv6 containers carry an optional gateway:

interfaces:
  - name: eth0
    type: ethernet
    mtu: 1500
    ipv4:
      addresses: [10.0.0.10/24]
      gateway: 10.0.0.1          # must be inside 10.0.0.0/24
    ipv6:
      addresses: [2001:db8::10/64]
      gateway: fe80::1           # link-local: on-link by definition, exempt

It is written without a prefix length, and it is the one field of those containers RFC 8344 does not define — a default route lives in ietf-routing, not in ietf-ip. See §6.2.3 of the schema.


Output formats

-F (also spelled --format or --output-format) takes table, json or csv.

JSON carries both halves of the default report in one document:

$ netviz -i examples/campus ipam -F json | jq '.subnets[0], (.conflicts|length)'
{
  "prefix": "10.1.0.0/30",
  "family": "ipv4",
  "vlans": [],
  "capacity": 2,
  "assigned": 2,
  "free": 0,
  "utilisation": 1.0,
  "devices": 2,
  "aggregated": []
}
0

capacity and free are exact integers, not the abbreviated 2^64 the table prints. utilisation is a fraction in [0, 1], or null for a prefix with no capacity. aggregated lists the prefixes an --aggregate row stands for. Conflict entries have the same shape as the findings array of netviz validate -F json.

CSV holds one table, because that is what a CSV is. The default emits the utilisation rows — the half a spreadsheet or an awk script wants — and notes on stderr how many conflicts were left out:

$ netviz -i examples/campus ipam -F csv --family ipv6
prefix,family,vlans,capacity,assigned,free,utilisation,devices
2001:db8::1/128,ipv6,,1,1,0,1.000000,1
...

For the conflicts as CSV, ask for them on their own:

$ netviz -i examples/campus ipam --conflicts -F csv
rule,alias,severity,element,file,message

--free and --next-free honour all three formats too. Line endings are \n rather than CSV's nominal \r\n, to match every other format this CLI writes.

The global --color / --no-color and --quiet flags apply throughout: colour is dropped when the stream is not a terminal or NO_COLOR is set, and --quiet silences the stderr commentary without ever touching the data on stdout.


Options

Option Default Effect
--free PREFIX List the unallocated CIDR blocks inside PREFIX instead of the utilisation table.
--next-free PREFIX Print the first free block inside PREFIX, and nothing else.
--size LENGTH /24 (v4), /64 (v6) Block size --next-free looks for. 24 and /24 both work.
--aggregate off Collapse sibling prefixes that fill their supernet into one row.
--conflicts off Report only the conflicts, without the utilisation table.
--family {all,ipv4,ipv6} all Restrict the utilisation table to one address family.
-F, --format, --output-format table table, json or csv.

--free and --next-free ask different questions and are rejected together, as are --size without --next-free and --aggregate, --conflicts or --family alongside a free-space query. A flag that was quietly ignored would be worse than an error: you would believe you had asked for something you did not get.


Exit codes

Code Meaning
0 The report was produced; nothing is reported as an error.
1 A conflict is reported at error severity, or --next-free found no room.
2 The command line does not make sense (click's own exit code).

A warning-severity conflict does not fail the run — use netviz validate --strict for a gate that treats every finding as fatal.