{% extends "base.html" %} {% from "_macros.html" import no_data with context %} {% block title %}CIDR Planner{% endblock %} {% macro status_badge(v) -%} {% if v.status == 'rejected' %}rejected {% elif v.status == 'warning' %}check warnings {% else %}no conflict found{% endif %} {%- endmacro %} {% macro reasons(v) -%} {% if v.rejects or v.warnings %} {% endif %} {%- endmacro %} {% macro snippet(id, text) -%}
{{ text }}
{%- endmacro %} {% block content %}

CIDR Planner

{% if not snap %} {{ no_data() }} {% elif not vpc %}
No VPC in this snapshot{{ ' (or in scope)' if scope_count }}.
{% else %}

Read-only: everything below is computed from the latest snapshot and proposed as Terraform to copy. Nothing is created in AWS.

{% if error %}
{{ error }}
{% endif %}

CIDR map

{% for m in plan.maps %}

{{ m.cidr }} {{ 'primary' if m.primary else 'secondary' }} · {{ m.allocated }} of {{ m.size }} addresses in subnets · {{ m.free_total }} free in {{ m.free_range_count }} range(s) · fragmentation {{ '%.2f' % m.fragmentation }}

subnetfree
{% for g in m.segments %} {% endfor %}
BlockSubnet / freeAZAddresses
{{ g.cidr }} {% if g.free %}free{% else %}{{ g.subnet_id }} {{ g.name }}{% endif %} {{ g.az }} {{ 2 ** (32 - (g.cidr.split('/')[1] | int)) }}
{% for p in sizes %}{% set n, first = m.per_size[p] %} {% endfor %}
SizeAligned blocks freeFirst free
/{{ p }}{{ n }}{{ first or '–' }}

Largest free block: {{ m.largest_free or 'none' }}. Fragmentation = 1 − largest contiguous free range ÷ total free (0: all free space in one range; near 1: scattered small gaps).

{% endfor %}

Fit subnets

Request subnet sizes (and AZs); each is placed in an aligned block of its size from the free space of every VPC CIDR (largest first, smallest block that fits). AWS reserves 5 addresses per subnet.

{% for i in range(fit_rows) %}{% set r = requests[i] if i < requests | length else none %}
{% endfor %} {% for az in plan.azs %}
{% if plan.fit %}
{% if plan.fit.fits %}

fits All {{ plan.fit.placements | length }} subnet(s) fit in the free space.

{% else %}

doesn't fit {{ plan.fit.missing | length }} of {{ plan.fit.placements | length }} subnet(s) don't fit → need a secondary CIDR (at least /{{ plan.fit.needed_prefix() }}).

{% endif %} {% for p in plan.fit.placements %} {% endfor %}
RequestedAZProposed CIDRUsable IPs
/{{ p.request.prefix }}{{ p.request.az }} {{ p.cidr or '' }}{% if not p.cidr %}no room{% endif %} {{ p.usable }}
{% if plan.fit_tf %}{{ snippet('tf-fit', plan.fit_tf) }}{% endif %} {% if plan.overflow %}

With a secondary CIDR {{ plan.overflow.cidr }} {{ status_badge(plan.overflow) }}

{{ reasons(plan.overflow) }} {% for p in plan.overflow_fit.placements %}{% endfor %}
RequestedAZProposed CIDR
/{{ p.request.prefix }}{{ p.request.az }}{{ p.cidr }}
{{ snippet('tf-overflow', plan.overflow_tf) }} {% elif not plan.fit.fits %}

No conflict-free secondary CIDR candidate found automatically; check one below.

{% endif %}
{% endif %}

Secondary CIDR

{% for r in requests %}{% endfor %}
Candidates are checked against every account's VPCs known to IPLens and the Transit Gateway / VPC peering routes of the latest service crawl (Visual → Extended → Crawl services), plus AWS secondary-CIDR restrictions. Accounts that were never refreshed or crawled are not covered.
{% set ns = namespace(i=0) %} {% for v in ([plan.custom] if plan.custom else []) + plan.candidates %}{% set ns.i = ns.i + 1 %}
{{ v.cidr }} {{ status_badge(v) }} {% if v.pool %}from {{ v.pool }}{{ ' (shared address space, RFC 6598)' if v.pool == '100.64.0.0/10' }} {% else %}your CIDR{% endif %} {{ reasons(v) }} {% if v.status != 'rejected' %}{{ snippet('tf-cand-' ~ ns.i, tf_secondary(vpc.vpc_id, v.cidr)) }}{% endif %}
{% else %}
No free /{{ prefix }} candidate in the primary's RFC 1918 range or 100.64.0.0/10.
{% endfor %} {% endif %} {% endblock %}