network/mikrotik-tool/edge-upgrade.md
2026-05-11 09:54:13 -05:00

13 KiB

edge → RouterOS 7 upgrade runbook

Edge is a CCR2004-1G-12S+2XS currently on 6.49.18 (the only ROS6 box left in the fleet). The auto-converter handles /ip firewall, /ip address, /ip route, /interface, /snmp, /system, and most NAT correctly. The following areas need manual pre-staging because the ROS7 converter either silently drops them or rewrites them into dead code:

  • /routing filter/routing filter rule (BGP/OSPF filter chains)
  • /routing bgp instance + /routing bgp peer/routing bgp instance + /routing bgp template + /routing bgp connection
  • /routing ospf instance + /routing ospf interface + /routing ospf nbma-neighbor/routing ospf instance + /routing ospf area + /routing ospf interface-template + /routing ospf static-neighbor
  • /routing bfd interface/routing bfd configuration
  • /mpls ldp set → /mpls ldp add + /mpls ldp interface
  • /routing rip (delete entirely)

Reference for ROS7 conventions: core.rsc, climax.rsc.


0. Pre-flight (before touching the router)

# from your workstation
cd /Users/graham/dev/network/mikrotik-tool
./mikrotik-tool export                          # snapshot fresh /export
cp edge.rsc edge.rsc.pre-ros7-$(date +%F)       # keep the ROS6 baseline

Verify console/out-of-band access (Spectrum link on sfp-sfpplus12 is the WAN — if BGP doesn't come back, you lose remote access via that path; iBGP to core via preseem is the management fallback).

Confirm OSPF auth shared key from one of the peers (climax or core) so the new /routing ospf interface-template can be configured with matching auth. Edge's ROS6 export doesn't include the secret.


1. Stage One: in-place fixes on ROS6 (optional cleanups)

These are pure ROS6 commands you can run now to reduce churn during the upgrade.

# Normalize mpls-mtu to fleet standard 1508 (currently 1530)
/mpls interface
set [find interface=sfp-sfpplus8-server-switch] mpls-mtu=1508
set [find default=yes] mpls-mtu=1508

# Add IPv4 anchor for BGP origination
# In ROS6, /routing bgp network injects 204.110.188.0/22 directly.
# In ROS7, we need the prefix to exist in the routing table for it to be
# advertised. Adding the anchor now is harmless on ROS6.
/ip route
add dst-address=204.110.188.0/22 type=blackhole distance=200 \
    comment="anchor for BGP origination to TWC (ROS7 needs RIB entry)"

2. Upgrade to RouterOS 7

# On the router (Winbox/SSH):
/system package update
set channel=long-term
check-for-updates
# verify it offers 7.21.4 (the fleet standard, see CLAUDE.md fleet topology)
install
# router reboots

After reboot, verify version and that interfaces came back:

/system resource print
/interface print
/ip address print

OSPF/BGP will be broken at this point — the auto-converter has run but filter chains, BGP connections, and OSPF templates are either missing or wrong. Static routes still work, so the Spectrum default + the static backbones to climax keep the box reachable.


3. Clear the auto-converted garbage

# BGP - remove auto-converted artifacts
/routing bgp connection
remove [find]
/routing bgp template
remove [find name!=default]

# OSPF - remove auto-converted artifacts
/routing ospf interface-template
remove [find]
/routing ospf static-neighbor
remove [find]
/routing ospf area
remove [find name!=backbone]
/routing ospf instance
remove [find name!=default-v2]

# Filter chains - remove anything auto-converted
/routing filter rule
remove [find]

# LDP - remove auto-converted artifacts
/mpls ldp interface
remove [find]
/mpls ldp
remove [find]

# BFD - remove auto-converted artifacts
/routing bfd configuration
remove [find]

# RIP - delete it, never coming back
/routing rip
# (RIP package is not installed by default on ROS7, may already be gone)

# Old BGP network announcements (replaced by RIB anchor + filter chain)
/routing bgp network
remove [find]   # if the menu still exists in your ROS7 build

4. Stage the ROS7 routing config

4a. Routing IDs and instances

/routing id
add disabled=no id=10.254.254.254 name=id-1 select-dynamic-id=""

/routing bgp instance
add as=393837 cluster-id=10.254.254.254 name=bgp-instance-1 \
    router-id=10.254.254.254

/routing bgp template
set default as=393837 disabled=no

/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 \
    originate-default=always

/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v3 \
    out-filter-chain=ospf-out redistribute=connected,static \
    router-id=id-1 routing-table=main version=3 \
    originate-default=always

/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
add disabled=no instance=default-v3 name=backbone-v3

4b. Filter chains (THE default-deny fix)

Auto-converter does NOT bridge these — must be added explicitly and referenced from the instances/connections.

/routing filter rule
# OSPF: accept everything in both directions
add chain=ospf-in  disabled=no rule="accept"
add chain=ospf-out disabled=no rule="accept"

# eBGP to TWC: inbound = default route only
add chain=twc-in disabled=no rule=\
    "if (dst==0.0.0.0/0 && dst-len==0) { accept } else { reject }"

# eBGP to TWC: outbound = only our /22 and our v6 /32
add chain=twc-out disabled=no rule=\
    "if ((dst==204.110.188.0/22 && dst-len==22) || (dst==2606:1c80::/32 && dst-len==32)) { accept } else { reject }"

# iBGP-RR (v6 transport, v4+v6 NLRI): RR client filters
add chain=v6-rr-in  disabled=no rule=\
    "if (dst==0.0.0.0/0 || dst==::/0 || dst-len==44 || dst-len==64 || dst-len==128) { accept } else { reject }"
add chain=v6-rr-out disabled=no rule=\
    "if (dst==0.0.0.0/0 || dst==::/0 || dst-len==44 || dst-len==64 || dst-len==128) { accept } else { reject }"

Note: dst-len==44 covers per-tower IPv6 allocations, ==64 covers per-link/server LANs, ==128 covers loopbacks. Adjust if your v6 plan in ipv6.md uses different prefix lengths.

4c. BGP connections

/routing bgp connection
# eBGP to TWC, v4
add disabled=no afi=ip as=393837 name=twc \
    instance=bgp-instance-1 \
    local.address=71.41.226.118 \
    remote.address=71.41.226.117 .as=11427 \
    input.filter=twc-in \
    output.filter-chain=twc-out \
    output.network=204.110.188.0/22 \
    output.redistribute=""

# eBGP to TWC, v6
add disabled=no afi=ipv6 as=393837 name=twc-v6 \
    instance=bgp-instance-1 \
    local.address=2605:6000:0:8::f:373 \
    remote.address=2605:6000:0:8::f:372 .as=11427 \
    input.filter=twc-in \
    output.filter-chain=twc-out \
    output.network=2606:1c80::/32 \
    output.redistribute=""

# iBGP-RR to core (v4+v6 NLRI over v6 transport, edge is the RR)
add disabled=no afi=ip,ipv6 as=393837 \
    comment="iBGP-RR to core" \
    instance=bgp-instance-1 \
    local.address=2606:1c80:0:10::254 .role=ibgp-rr \
    remote.address=2606:1c80:0:10::253 .as=393837 \
    multihop=yes \
    nexthop-choice=force-self \
    input.filter=v6-rr-in \
    output.filter-chain=v6-rr-out \
    output.redistribute=connected,static,ospf \
    output.default-originate=always \
    name=ibgp-rr-core

If/when you add more RR clients (climax, culleoka, etc. peering directly to edge), they each get their own /routing bgp connection block mirroring the ibgp-rr-core pattern with .role=ibgp-rr on edge's side.

4d. OSPF interface templates and static neighbors

Replicate the per-interface settings from the old /routing ospf interface block. Adjust auth values to match the rest of the fleet (verify shared key from climax or core first).

/routing ospf interface-template
# Preseem (primary path to core) - wired, BFD on
add disabled=no area=backbone-v2 auth-id=1 cost=10 \
    interfaces=sfp-sfpplus11-preseem priority=1 type=ptp use-bfd=yes

# Direct backup to core - wired, BFD on
add disabled=no area=backbone-v2 auth-id=1 cost=100 \
    interfaces=sfp-sfpplus7-core-direct priority=1 type=ptp use-bfd=yes

# Server switch VLAN9 (loopback / server LAN)
add disabled=no area=backbone-v2 \
    interfaces=sfp-sfpplus8-server-switch type=broadcast

add disabled=no area=backbone-v2 passive=yes \
    interfaces=vlan9_sfpplus8 type=broadcast

# Spectrum WAN - passive (we don't run OSPF to TWC)
add disabled=no area=backbone-v2 passive=yes \
    interfaces=sfp-sfpplus12-spectrum type=broadcast

# IPv6 area equivalents on the same wired links
add disabled=no area=backbone-v3 cost=10 \
    interfaces=sfp-sfpplus11-preseem type=ptp use-bfd=yes
add disabled=no area=backbone-v3 cost=100 \
    interfaces=sfp-sfpplus7-core-direct type=ptp use-bfd=yes
add disabled=no area=backbone-v3 \
    interfaces=sfp-sfpplus8-server-switch type=broadcast

/routing ospf static-neighbor
# Core (preseem path)
add disabled=no area=backbone-v2 \
    address=204.110.191.185%sfp-sfpplus11-preseem
# Core (direct path) - if you want OSPF on this link as a hot standby
add disabled=no area=backbone-v2 \
    address=204.110.191.181%sfp-sfpplus7-core-direct

Do not add the old NBMA neighbors 10.250.1.94, 10.250.1.57, 10.250.1.201, 10.250.2.1 — those were stale wireless backbone neighbors that don't apply to edge (edge has no wireless backbone links).

4e. BFD

/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5 \
    vrf=main

Per the fleet note in CLAUDE.md, BFD on wireless backbone links is problematic — edge has none, so this global config is fine.

4f. MPLS / LDP

/mpls interface
# (mpls-mtu was already set in stage 1, but verify)
set [find interface=sfp-sfpplus7-core-direct] mpls-mtu=1508
set [find interface=sfp-sfpplus11-preseem] mpls-mtu=1508
set [find interface=sfp-sfpplus8-server-switch] mpls-mtu=1508

/mpls ldp
add disabled=no lsr-id=10.254.254.254 \
    transport-addresses=10.254.254.254 vrf=main

/mpls ldp interface
add interface=sfp-sfpplus7-core-direct
add interface=sfp-sfpplus11-preseem
add interface=sfp-sfpplus8-server-switch

LDP accept/advertise filtering: the ROS6 export was set to only advertise the loopback /32 (/mpls ldp advertise-filter add prefix=10.254.254.254/32

  • add advertise=no). If you want to preserve that behavior, add a filter chain:
/routing filter rule
add chain=ldp-advertise disabled=no rule=\
    "if (dst==10.254.254.254/32 && dst-len==32) { accept } else { reject }"
# Reference from /mpls ldp - syntax varies by ROS7 version, check
# /mpls ldp print detail after adding the rule

For most fleets this is over-engineering — LDP advertising all loopbacks is normally fine. Skip unless you have a specific reason.


5. Verification

# Interfaces and addressing
/interface print
/ip address print

# OSPF adjacencies
/routing ospf neighbor print
# expect: core via sfp-sfpplus11-preseem (Full), optionally
# core via sfp-sfpplus7-core-direct (Full)

# OSPF routes
/ip route print where ospf
/ipv6 route print where ospf

# BGP sessions
/routing bgp session print
# expect: twc (Established), twc-v6 (Established), ibgp-rr-core (Established)

# BGP advertisements to TWC
/routing bgp advertisements print peer=twc
# expect: 204.110.188.0/22 advertised
/routing bgp advertisements print peer=twc-v6
# expect: 2606:1c80::/32 advertised

# BGP routes received
/routing route print where bgp
# expect: 0.0.0.0/0 from TWC v4, ::/0 from TWC v6, plus customer prefixes
# from iBGP-RR

# LDP sessions
/mpls ldp neighbor print
# expect: session to core via direct + preseem links

# LDP-installed labels
/mpls forwarding-table print

# BFD sessions
/routing bfd session print
# expect: up to core via preseem and direct

# Full reachability check
/ping 10.254.254.253                              # core loopback
/ping 10.254.254.101                              # verona loopback
/tool traceroute 10.254.254.102 src-address=10.254.254.254  # to climax via MPLS

6. If things go wrong

The Spectrum default route (/ip route add distance=1 gateway=71.41.226.117) and the manually-pinned statics in /ip route (lines 508-531 of the old export) keep edge reachable from the Internet even without BGP/OSPF. From inside the network, the static 204.110.191.182/30 link to core stays up and is OSPF-independent.

Rollback path: /system package downgrade with the prior 6.49.18 npk available in the file store. Keep edge.rsc.pre-ros7-<date> around to re-paste any pieces the upgrade mangles unrecoverably.


7. Post-upgrade cleanup

# back on the workstation
cd /Users/graham/dev/network/mikrotik-tool
./mikrotik-tool export       # fresh edge.rsc on ROS7
git add edge.rsc edge-upgrade.md
git commit -m "edge: ROS7 upgrade"

Update CLAUDE.md to remove the "Edge runs ROS 6.49.18 (legacy, no MPLS, ignore for the spine)" note once edge is fully on ROS7 with MPLS in the spine.