Starting from scratch with Netbox IPAM
Published: 2024-05-11 17:12:17

---
title: Starting from scratch with Netbox IPAM
published: true
date: 2024-05-11 09:00:00 UTC
tags: 
canonical_url: https://blog.engyak.co/2024/05/netbox-ipam-getting-started/
---

> Spreadsheets are not an adequate method to manage IP addressing

## Different IP design strategies

### IPv4

### Bogons, and the basics

There are a number of valid and invalid prefixes for use internally within an enterprise. Here's a list of _invalid_ prefixes in the global routing table; of those, the RFC 1918 prefixes are available for use:

| Prefix | RFC | Usable Internally? |
| --- | --- | --- |
| 0.0.0.0 | [1122](https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.3) | 🤪 Everybody more or less agreed not to use it |
| 10.0.0.0/8 | [1918](https://datatracker.ietf.org/doc/html/rfc1918) | ✅ Use this block for _large_ prefix allocations |
| 100.64.0.0/10 | [6598](https://datatracker.ietf.org/doc/html/rfc6598) | 🙄 CG-NAT, can _technically_ be used, but will break in random cloud applications |
| 127.0.0.0/8 | [1122](https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.3) | ❌ loopback |
| 169.254.0.0/16 | [3927](https://datatracker.ietf.org/doc/html/rfc3927) | ✅, but don't allocate it (APIPA) |
| 172.16.0.0/12 | [1918](https://datatracker.ietf.org/doc/html/rfc1918) | ✅ Use this block for _medium_ prefix allocations |
| 192.0.0.0/24 | [5736](https://datatracker.ietf.org/doc/html/rfc5736) | ❌ IETF skunkworks |
| 192.0.2.0/24 | [5737](https://datatracker.ietf.org/doc/html/rfc5737) | ❌ Carrier test networks |
| 192.88.99.0/24 | [3068](https://datatracker.ietf.org/doc/html/rfc3068) | ❌ 6to4 relays |
| 192.168.0.0/16 | [1918](https://datatracker.ietf.org/doc/html/rfc1918) | ✅ Avoid this block for _enterprises_, it'll collide with home networks when people use VPN |
| 198.18.0.0/15 | [2544](https://datatracker.ietf.org/doc/html/rfc2544) | ❌ device benchmarking |
| 198.51.100.0/24 | [5737](https://datatracker.ietf.org/doc/html/rfc5737) | ❌ Carrier test networks |
| 203.0.113.0/24 | [5737](https://datatracker.ietf.org/doc/html/rfc5737) | ❌ Carrier test networks |
| 224.0.0.0/4 | [3171](https://datatracker.ietf.org/doc/html/rfc3171) | ❌ multicast |
| 240.0.0.0/4 | [1122](https://datatracker.ietf.org/doc/html/rfc1112#section-4) | 🤯 madlad play, might work, might not. Linux seems to live in this space just fine |

All of these prefixes _must_ be dropped at any network perimeter, e.g. firewalls, extranet routers, to prevent internal traffic or misconfigured NATs from leaking. It also prevents protocol abuse, which is a cheap and easy way to improve security.

In multi-site networks, dropping **all** of these prefixes would be wise - an ethernet loop + APIPA can turn a switching issue into a network-wide outage pretty easily. [Longest Prefix Match](https://en.wikipedia.org/wiki/Longest_prefix_match) can ensure that any allocated networks remain reachable.

Each of these prefixes should be created in Netbox, so you can use it as a reference later. I'd recommend _tagging_ them with some form of hint to indicate usability, e.g.:

- `IP:Usable`
- `IP:Unusable`

As you get more familiar with the API/search, tag-based filters become incredibly handy.

### IPv6

IPv6 is quite easy. All valid _routable_ addresses fall under one allocated prefix:

`2000::/3`

This means that you can implement a "default route" that won't accidentally leak bogons like in IPv4, but with a much simpler approach. Instead of implementing `::0/0` for your default route, use `2000::/3`.

If you insist on using private addressing, I'd encourage a thorough review of why - but this is the prefix available:

`fc00::/7`

Link-local addressing, or addressing that is "always on" regardless of prefix allocation, is also allocated a specific prefix. This prevents the need of a _bunch_ of little helper protocols that simply don't need to exist, or become standardized. Traffic like Router Advertisements(RA), Routing Protocols, First Hop Redundancy Protocols have a distinct source address that can be pinged even before a network is online.

It's also incredibly handy when bootstrapping new devices! All that's required is some form of helper on the default gateway to act as an SSH proxy and some neighbor discovery, and you suddenly have always-on remote management.

`fe80::/10`

Multicast also has its own prefix:

`ff80::/8`

This is much simpler, but where to _get_ IPv6 addressing can be more complex. If it's in a lab environment and doesn't need internet access, `fc00::/7` is just fine to use.

The recommended method for acquiring an IPv6 prefix is to request it with [DHCP-PD](https://www.isc.org/blogs/dhcpv6-prefix-length-mode/) or to request it through a [tunnel broker](https://tunnelbroker.net/).

There's one more "gotcha" to keep in mind with IPv6 - weird stuff breaks if you go with a longer prefix than /64. I'd strongly encourage avoiding cutesy CIDR block allocations like /120 or /65; that's an IPv4 solution to a problem IPv6 doesn't have. Just request enough IP addressing for your site instead.

## Constructing an IP hierarchy

For the purposes of this post, we're going to use the following language to describe a _network address_:

```
1{{ prefix }}{{ subprefix }}{{ host bits }}/{{ prefix length }}
2 10.99. 100. 0 /24

```

The major first step here is to decide _how_ to break down your addressing. There are two major paths to follow:

- _Location_ based addressing is used when _prefix scale_ is a concern. If you need to summarize routes on routers due to RIB limits, this is the way to go. This can be for a few reasons:
  - "I have a lot of routes / lot of sites and am worried about RIB capacity in my hardware"
    - Most enterprise equipment can handle 16-64k routes; if this is not enough, follow this approach
    - ISPs will follow this path
    - Cloud providers will follow this path
- _Purpose_ based addressing is used when _perimeter security_ is a concern. Easy summarization to a common prefix per "network role" allows for straightforward firewall policy creation, including a number of microsegmentation tools that may have laughably low table capacities.
  - "I want to keep my workloads separated from each other"
    - Financial services will follow this path
    - Healthcare will follow this path

Committing to one or the other before allocating blocks will simplify your life later.

**Note: With IPv6, only the largest of organizations (ones that need more than 2^8 or 2^16 networks per site) will need to allocate their own top-level prefix. It's easier to just run DHCP-PD and ask for a /56 or /48.**

## Guidance on prefix allocation

To assess the proper 1918/bogon prefix for use, first assess the number of prefixes you would need as a ceiling:

```
1num_sites*num_network_roles

```

Attempt to select a site that will fit this prefix count with a minumum of 80% buffer (leaving a reserve for point-to-point connects, etc.)

I would highly encourage _not_ getting creative with CIDR prefix lengths in IPv4-land. If possible, try and stick to `/24` for a subprefix. [IPv6 does not support prefix lengths longer than `/64` particularly well](https://datatracker.ietf.org/doc/html/rfc4862) (with specific exceptions for point-to-point, `/126` or `/127` depending on hardware), and using prefixes like `/65` for access segments will lead to trouble with end devices like Android.

It's much simpler to translate the `/24` in question linearly to a `/64` and using that calculation to estimate what IPv6 prefix size you want. It's **also much simpler to troubleshoot and maintain if you don't build a pile of weird stuff, even if it makes you feel smart!**

As a starting point, it's good to set up a set of standard t-shirt sizes for networks in `{{ IPv4 }}/{{ IPv6 }} format. Here's an example:

- Large Site/Role: `/16`/`/56`
- Medium Site/Role: `/18`/`/60`
- Small Site/Role: `/22`/`/62`
- "Normal" subprefix: `/24`/`/64`
- "Small" subprefix: `/26`/`/64`
- Point-to-point: `/31`/`/127`

**Note: Service providers don't always support weird DHCP-PD sizes, so options may be limited to the above.**

**Note: Service providers are typically pretty generous with prefix allocations, and keep in mind a /56 is roughly equivalent to a /16. I'd recommend allocating a /56 per site in production or in the lab whenever permitted.**

## Automating it

Once you have sizes set, it's actually pretty easy to let go of your artisanal, hand-crafted prefixes and automate aggressively. With Netbox and Ansible, it's incredibly easy to leverage the [`netbox.netbox.netbox_prefix` module](https://docs.ansible.com/ansible/latest/collections/netbox/netbox/netbox_prefix_module.html#ansible-collections-netbox-netbox-netbox-prefix-module). The following example will grab a `/24` from `10.99.0.0/16`:

```
 1- name: "Example Ansible Playbook"
 2 connection: local
 3 hosts: localhost
 4 gather_facts: False
 5 tasks:
 6 - name: "Get next available prefix"
 7 netbox.netbox.netbox_prefix:
 8 netbox_url: "{{ netbox_url }}"
 9 netbox_token: "{{ netbox_token }}"
10 data:
11 parent: "10.99.0.0/16"
12 prefix_length: 24
13 state: present
14 first_available: yes

```

It's extremely rewarding to design, deploy, and automate an IP design in this manner - and you'll find that automation is considerably easier if _what_ to automate is well-defined.