network/CLAUDE.md
2026-05-08 17:47:42 -05:00

13 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

This is a network documentation and discovery project that integrates with NetBox.

NetBox Integration

  • NetBox URL: https://netbox.vntx.net/
  • API Key: e50298f7fd20f7fd6f1931f635511b34f6e8cfde
  • Purpose: Network documentation and discovery

Development Guidelines

NetBox API Usage

  • Use the provided API key for authentication with NetBox
  • The NetBox instance is located at https://netbox.vntx.net/
  • Follow NetBox API documentation for proper endpoint usage
  • The netbox api token is in env var NETBOX_KEY

Security Notes

  • Never commit API keys directly in code files
  • Use environment variables or configuration files for sensitive data
  • The API key provided should be stored securely

Common Tasks

NetBox API Connection

When connecting to NetBox, use:

  • Base URL: https://netbox.vntx.net/api/
  • Authentication header: Authorization: Token e50298f7fd20f7fd6f1931f635511b34f6e8cfde

MikroTik Router Connection

Connect to MikroTik routers using API-SSL:

  • Protocol: API-SSL (port 8729)
  • Username: grahamro (read-only)
  • Password: cFKhz8q5gPLoucMbcT1Iy58r3IXgc3
  • Example - Verona Router: 10.254.254.101

Use the mikrotik_connect.py script to connect and retrieve router information:

python3 mikrotik_connect.py

The script handles SSL connection, authentication, and can retrieve:

  • IP addresses and subnets
  • Interface configurations
  • Routing tables
  • PPPoE connections

Router Access Credentials

MikroTik Routers

  • Read-only access via API-SSL: username grahamro, password cFKhz8q5gPLoucMbcT1Iy58r3IXgc3

Verona Routers

  • Verona router is 10.254.254.101

Additional Router IP Addresses

  • Climax router: 10.254.254.102
  • Culleoka router: 10.254.254.104

NetBox Site and Device Creation Process

Creating a new site and router in NetBox:

  1. Create Site: Use create_verona_site_and_router.py as template

    • Site name and slug (lowercase, hyphenated)
    • Status: 'active'
    • Comments describing the site
  2. Create Device:

    • Manufacturer: MikroTik
    • Device Type: RouterBOARD
    • Device Role: Router
    • Primary IP: Router's loopback IP (e.g., 10.254.254.101/32)
  3. Add Network Data: Use update_netbox_verona.py as template

    • Creates prefixes with proper roles (Infrastructure, Customer, Management, Loopback)
    • Creates interfaces on the device
    • Associates IP addresses with interfaces

API Authentication

  • Always use environment variable NETBOX_KEY for API token
  • Fallback to hardcoded token only if env var not set

Generic Scripts for Network Management

1. Create Site Only in NetBox

# Basic usage
python3 create_site_only.py <site_name>

# With options
python3 create_site_only.py 380 --comments "Central site with multiple routers" --address "380 Main St"

2. Create Site and Router in NetBox

# Basic usage
python3 create_site_and_router.py <site_name> <router_ip>

# With options
python3 create_site_and_router.py Climax 10.254.254.102 --router-name climax-core --physical-address "123 Tower Rd"

# For sites with multiple routers, create site first, then add each router
python3 create_site_only.py 380 --comments "Multi-router site"
python3 create_site_and_router.py 380 10.254.254.105 --router-name 380-core-router
python3 create_site_and_router.py 380 10.254.254.106 --router-name 380-edge-router

3. Get MikroTik Router Configuration

# Basic usage (uses default read-only credentials)
python3 get_mikrotik_router_data.py <router_ip>

# Save to specific file
python3 get_mikrotik_router_data.py 10.254.254.102 -o climax_config.json

# Output JSON to stdout
python3 get_mikrotik_router_data.py 10.254.254.102 --json

# Custom credentials
python3 get_mikrotik_router_data.py 10.254.254.102 -u admin -p secretpass

The script retrieves:

  • Router identity
  • IP addresses and subnets
  • Active interfaces
  • VLANs
  • PPPoE servers
  • Static routes

4. Get MikroTik Router Data (Basic/Older RouterOS)

For older RouterOS versions or routers with many IPs (like CGNAT):

python3 get_mikrotik_basic_data.py <router_ip> -o router_data.json

This simplified script:

  • Groups CGNAT addresses into a single subnet entry
  • Focuses on key interfaces only
  • Works better with older RouterOS versions
  • Handles large configurations without timing out

5. Get All Network Devices

# Get all devices categorized by type
python3 get_all_network_devices.py <router_ip>

# Show all devices including "Other" category
python3 get_all_network_devices.py <router_ip> --show-all

# Save to JSON file
python3 get_all_network_devices.py <router_ip> -o devices.json

This script retrieves DHCP leases and ARP table to identify:

  • Ubiquiti access points and devices
  • MikroTik devices
  • Other network devices

Network Topology Patterns

Access Point Placement

  • Access points are always placed in the top /24 of the management subnet for each tower
  • Example: For management subnet 10.10.16.0/20, APs are in 10.10.31.0/24 (the last /24 in that range)
  • Formula: For subnet X.Y.Z.0/20, APs are in X.Y.(Z+15).0/24

Ubiquiti MAC Prefixes

Common MAC address prefixes for Ubiquiti devices:

  • 00:04:56 (legacy)
  • 00:27:22 (legacy)
  • 04:18:D6
  • 24:A4:3C
  • 68:72:51
  • 80:2A:A8
  • F0:9F:C2
  • FC:EC:DA

MPLS / LDP

FastTrack is incompatible with MPLS on RouterOS 7

FastTrack bypasses the IP forwarding path that MPLS push/pop runs on, so any flow that gets fasttracked on a router whose path uses an MPLS-enabled interface can break — packets either hit the wrong interface or never get labeled, which presents as black-holing for specific source subnets that weren't fasttracked before. Symptoms: pings/SSH/TCP from one source IP work but the same destination is unreachable from another source on the same router; loopback-sourced traffic works but vlan-interface-sourced doesn't.

Fix: before each action=fasttrack-connection rule in chain=forward, add accept rules that match the MPLS-bound interface(s) so those flows never enter the fasttrack path:

/ip firewall filter
add chain=forward action=accept in-interface=<mpls-iface>  comment="bypass fasttrack for MPLS spine (in)"  place-before=<fasttrack-id>
add chain=forward action=accept out-interface=<mpls-iface> comment="bypass fasttrack for MPLS spine (out)" place-before=<fasttrack-id>

Customer→internet flows continue to fasttrack normally; only flows traversing the MPLS spine bypass it.

LDP doesn't label OSPF Type-5 externals by default

Prefixes redistributed via redistribute=connected (e.g., a /27 customer WAN handoff like 204.110.191.0/27) appear as Type-5 external LSAs and don't get LDP label bindings. Forward path to a labeled destination still works, but the return path is plain IP. If you need labeled bidirectional reach for a redistributed prefix, configure an LDP advertise-filter that explicitly includes it.

MPLS-MTU is the labeled-frame cap, not the IP-payload cap

mpls-mtu=1500 caps the labeled frame at 1500 bytes, which means an inner IP payload is limited to 1496 bytes — so 1500-byte DF customer traffic gets icmp-frag-needed. Use mpls-mtu=1508 for a 1500-byte IP payload + 4-byte label, with 4 bytes of headroom for one more stacked label. The AF11/AF24 radio l2mtu is 2024, so 1508 fits comfortably.

Fleet-wide MPLS topology

LDP runs IPv4-only across every backbone link in the network. Every backbone port has mpls-mtu=1508 set explicitly and a fasttrack-bypass pair (in/out) above the fasttrack-connection rule on both endpoints. Documented in mikrotik-tool/mpls.md.

verona ──AF11── climax ──AF24── core ──AF11── culleoka
                  │              │              │
                  │ AF11         │ AF11         │ AF11 (DOWN: power injector unplugged)
                  │              │              │
                 494           newhope ──AF24── lowry
                                 │
                                 │ 60 GHz
                                 │
                                982

Wait — that diagram's links are: climax↔494 (AF11), core↔newhope (AF11), core↔982 (60 GHz), newhope↔lowry (AF24). The climax↔culleoka direct AF11 is currently down at the radio (physical issue), so culleoka traffic transits via core.

Fleet Topology

Routers and loopbacks

All ROS7 routers run RouterOS 7.21.4 long-term (post-2026-05-08 fleet upgrade). Edge runs ROS 6.49.18 (legacy, no MPLS, ignore for the spine).

Router Loopback (10.254.254.x) Hardware Site name
verona .101 CCR2004-16G-2S+ (arm64) verona
climax .102 CCR2004-16G-2S+ (arm64) climax
culleoka .104 CCR1009-7G-1C-1S+ (tile) culleoka
newhope .108 CCR1009-7G-1C-1S+ (tile) newhope
lowry .109 (tile) lowrycrossing
982 .110 (CCR, tile) 982
494 .111 (CCR, tile) 494
core .253 (CCR, arm64) at 380 core/380
edge .254 (legacy, ROS 6.49.18) edge

Tile-arch boxes can run MPLS but not ZeroTier (no .npk for tile).

Every link below has IPv4 LDP enabled at both ends, mpls-mtu=1508, and fasttrack-bypass rules in both directions on both routers.

Link Type A-side iface B-side iface /29 subnet l2mtu
verona↔climax AF11 verona ether3-climax-11ghz climax ether6-verona-11ghz 10.250.1.24/29 2024
climax↔core AF24 climax ether4-380-airfiber24 core ether5-climax 10.250.1.88/29 2024
climax↔494 AF11 climax ether5-494 494 ether2-climax 10.250.1.64/29 1580
climax↔culleoka AF11 climax ether3-culleoka-11ghz culleoka ether1-climax-11ghz 10.250.1.8/29 2024 (link DOWN)
core↔culleoka AF11 core ether6-culleoka-11ghz culleoka ether6-380-11ghz 10.250.1.48/29 2024
core↔newhope AF11 core ether4-newhope newhope ether2-380 10.250.1.56/29 9000
core↔982 60 GHz core ether1-982-60ghz 982 ether7-380 10.250.1.32/29 9000
newhope↔lowry AF24 newhope ether6-lowrycrossing lowry ether1-newhope 10.250.1.104/29 9000
core↔edge wired core sfp-sfpplus1-edge-preseem + ether3-edge-direct edge ports 204.110.191.x n/a

l2mtu mismatches across the fleet are intentional per platform: AF11 base ports default to 2024 on CCR2004 / 1580 on smaller CCRs; jumbo-capable links (60 GHz, AF24-with-jumbo, fiber) go to 9000. Always raise both sides symmetrically when changing l2mtu — single-side raises usually work because Ethernet receivers accept anything ≤ their cap, but symmetric is the rule.

IGP / routing

  • OSPFv2 area backbone-v2 (id 0.0.0.0) on all spine links, SHA-512 auth with auth-id=1 and a shared key. PTP type, BFD enabled where supported.
  • OSPFv3 area backbone-v3 for IPv6 (some interfaces only).
  • All instances redistribute=connected with passthrough filters (/routing filter rule chain=ospf-out rule="accept;").
  • Verona has a static default to 10.250.1.30 (climax) backing up the OSPF default — keep this; bouncing OSPF on verona doesn't blackhole it.
  • Distance-1 static routes also exist on climax for 204.110.191.0/27 so the home /27 has guaranteed return path even if OSPF redistribution hiccups.

Management subnets per tower

10.10.x.0/20 per site, top /24 reserved for APs (see Access Point Placement section). Authoritative mapping is in mikrotik-tool/inventory.yaml. Quick reference:

  • verona: 10.10.0.0/20
  • altoga (behind verona, no router): 10.10.16.0/20
  • climax: 10.10.48.0/20
  • core/380: 10.10.64.0/20
  • culleoka: 10.10.96.0/20
  • 982: 10.10.128.0/20
  • newhope: 10.10.144.0/20
  • 494: 10.10.160.0/20
  • lowry: 10.10.80.0/20

CGNAT pools: 100.64.x.x/22 per tower (see inventory.yaml / subnets.yaml).

graham's home network gotcha

graham's home connects to verona via vlan9_sfpplus1 carrying 204.110.191.0/27 (home router at .1, verona at .30). This /27 is a subnet of the verona hotspot's covered range (204.110.188.0/22). After any verona reboot, ensure /ip hotspot ip-binding has an entry: address=204.110.191.0/27 type=bypassed comment="graham home /27" — without it, hotspot drops all 204.110.191.x traffic in hs-unauth-to chain with icmp-host-prohibited. Symptom is "I can reach verona but nothing past it" from the home network.

IPv6 plan

Per-tower /44s + central server LAN at 2606:1c80::/64 on edge. Full allocation plan in mikrotik-tool/ipv6.md. NetBox has these as IPAM prefixes.

Claude Assistant Guidelines

  • Any time Claude learns something new, automatically add it to CLAUDE.md

Development Best Practices

  • When making scripts, keep them as generic and reusable as possible