# 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)

```bash
# 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.

```rsc
# 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

```rsc
# 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:

```rsc
/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

```rsc
# 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

```rsc
/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.

```rsc
/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

```rsc
/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).

```rsc
/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

```rsc
/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

```rsc
/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:

```rsc
/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

```rsc
# 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

```bash
# 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.
