334 lines
No EOL
13 KiB
Markdown
334 lines
No EOL
13 KiB
Markdown
# 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:
|
|
```bash
|
|
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
|
|
```bash
|
|
# 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
|
|
```bash
|
|
# 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
|
|
```bash
|
|
# 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):
|
|
```bash
|
|
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
|
|
```bash
|
|
# 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).
|
|
|
|
### Backbone links
|
|
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 |