DEV Community

Cover image for Don't Let Your IPAM Drift: Managing Leased IPv4 the API-First Way
Artem Kohanevich
Artem Kohanevich

Posted on

Don't Let Your IPAM Drift: Managing Leased IPv4 the API-First Way

Almost every IPAM mess I've debugged traces back to one root cause: the record and the reality drifted apart. Someone assigned an address and forgot to log it. A block expired and nothing flagged it. The spreadsheet said one thing, the routers said another.

With owned IPv4 you can usually recover from that. With leased IPv4 you have less room, because a leased block carries three things owned space doesn't: an expiry date, a reputation you inherited, and an owner who isn't you. You either manage those as data, or they end up managing you.

Here's how I'd wire it up.

Start with the data model

Before reaching for any tool, decide what a leased block actually is in your system. At minimum:

prefix: 203.0.113.0/24
status: active
lease:
  owner: ACME-LIR
  start: 2026-06-01
  expiry: 2027-06-01
  loa_reference: LOA-2026-0481
routing:
  origin_asn: 64500
  roa_status: valid          # valid | invalid | unknown
  irr_registered: true
reputation:
  status: clean              # clean | listed | watch
  last_checked: 2026-06-15
Enter fullscreen mode Exit fullscreen mode

The three fields that make leased space different - lease.expiry, lease.owner, and reputation.status - are exactly the ones a generic IPAM setup tends to leave out. Don't leave them out.

Model it in NetBox, and let the API do the writing

NetBox is the tool I reach for here: open-source, IPAM plus DCIM, and an API that covers everything the UI does. Define the lease fields once as custom fields (lease_owner, lease_expiry, loa_reference, roa_status, reputation), then register a freshly leased block in a single call:

import pynetbox

nb = pynetbox.api("https://netbox.internal", token="<token>")

nb.ipam.prefixes.create({
    "prefix": "203.0.113.0/24",
    "status": "active",
    "description": "Leased /24 - web tier",
    "custom_fields": {
        "lease_owner": "ACME-LIR",
        "lease_expiry": "2027-06-01",
        "loa_reference": "LOA-2026-0481",
        "roa_status": "valid",
        "reputation": "clean",
    },
})
Enter fullscreen mode Exit fullscreen mode

Now the lease metadata lives next to the prefix, not in a contract PDF nobody opens.

Allocate addresses through the API, not by hand

Manual assignment is the single biggest source of drift. Close that gap by allocating from NetBox's next-available-IP endpoint, so the assignment and the record are the same operation:

prefix = nb.ipam.prefixes.get(prefix="203.0.113.0/24")

ip = prefix.available_ips.create({
    "description": "web-01",
    "dns_name": "web-01.customer.example",
})

print(ip.address)   # -> 203.0.113.5/24
Enter fullscreen mode Exit fullscreen mode

There's no window where inventory and reality disagree, because writing the record is the allocation. Wire this into your provisioning flow and the "who's on this address?" problem stops existing.

Verify routing before you announce, and know who owns the ROA

This is the leased-space gotcha that trips up strong teams: you cannot create your own ROA for leased space. Only the resource holder - the block owner, or the platform you leased from - can create the Route Origin Authorization, update the IRR route objects, and issue the LOA. Your job is to confirm it's all in place and that your origin validates before you announce a single route.

A quick scripted check against RIPEstat:

curl -s "https://stat.ripe.net/data/rpki-validation/data.json?resource=AS64500&prefix=203.0.113.0/24" \
  | jq -r '.data.status'
# -> valid
Enter fullscreen mode Exit fullscreen mode

If that returns invalid or unknown, stop and fix it with the owner before the prefix goes live - otherwise every network running ROV will drop your routes. Fold the result straight back into routing.roa_status so your inventory reflects what the routing table actually believes.

Treat reputation as a scheduled job

You inherit a block's history, so check it before assignment and keep checking on a timer. The DNSBL mechanism is simple enough to script and stay list-agnostic - point $DNSBL_ZONE at whichever current lists you trust:

check_dnsbl() {
  local ip="$1" zone="$2"
  local reversed
  reversed=$(awk -F. '{print $4"."$3"."$2"."$1}' <<< "$ip")

  if dig +short "${reversed}.${zone}" | grep -q '^127\.'; then
    echo "LISTED $ip ($zone)"
  else
    echo "clean  $ip ($zone)"
  fi
}

check_dnsbl 203.0.113.5 "$DNSBL_ZONE"
Enter fullscreen mode Exit fullscreen mode

Run it across the block on a schedule, write the result and timestamp back into reputation.status / last_checked, and alert on any change. Keep mail-sending IPs on separate, warmed space so one customer's mistake doesn't taint a block shared with everyone else.

Catch renewals before they catch you

The authorizations that let you announce - the LOA and the ROA - are tied to the lease term. Let a lease lapse and you don't lose paperwork, you lose routing. So query for blocks nearing expiry on a cron and push them somewhere a human will actually look:

from datetime import date, timedelta
import pynetbox

nb = pynetbox.api("https://netbox.internal", token="<token>")
horizon = date.today() + timedelta(days=60)

for p in nb.ipam.prefixes.filter(status="active"):
    expiry = p.custom_fields.get("lease_expiry")
    if expiry and date.fromisoformat(expiry) <= horizon:
        print(f"Renewal due: {p.prefix} - {p.custom_fields['lease_owner']} - {expiry}")
Enter fullscreen mode Exit fullscreen mode

Pipe that into Slack or your ticketing system and renewals become a planned task instead of a 2 a.m. incident.

Which tool?

  • phpIPAM - free, light, supports custom fields, fine for smaller footprints you're happy to self-host.
  • NetBox - open-source, API-first, the one I'd build automation on (everything above assumes it).
  • SolarWinds IPAM - mid-market, with discovery and conflict detection if you want the tool to reconcile itself against the network.
  • Infoblox - enterprise DDI, integrated DNS/DHCP/IPAM, priced for scale.

Whatever you pick, the test is the same: can it store lease owner, expiry, and reputation, and can you write to it from code? If yes, you can keep records and reality in lockstep. If no, you'll be back to debugging drift.

That's the whole philosophy - model the lease as data, let the API do the writing, and put reputation and renewals on a timer. The unglamorous automation is exactly what lets you scale leased IPv4 without surprises.

If you want the longer, less code-heavy treatment - the full lease-to-deployment workflow, a setup checklist for new blocks, and a deeper tool comparison - I wrote it up here: IP Address Management for Leased IPv4: Best Practices for Hosting Providers.

Top comments (0)