From 199edf85ada8d7273166a2548a44977dad21f791 Mon Sep 17 00:00:00 2001 From: sjat Date: Tue, 9 Jun 2026 12:38:04 +0200 Subject: [PATCH] fix(vlans): robust bridge-IP removal; record cutover + gotchas RouterOS 'find ... address=' never matches an ip/address value, so the legacy-bridge-IP removal is now a :foreach get-and-compare. Refresh the committed export.rsc to the post-cutover config (flat VLAN 30 + isolated mgmt VLAN 99 on ether8, vlan-filtering on). Spec updated with execution notes (NM autoconnect flap, the find-address quirk, and the commit-confirmed detached-flip technique used). Co-Authored-By: Claude Opus 4.8 (1M context) --- backups/crs310-maker/export.rsc | 38 ++++++++++--------- .../2026-06-09-crs310-flat-mgmtvlan-design.md | 19 ++++++++++ .../tasks/vlans.yml | 9 +++-- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/backups/crs310-maker/export.rsc b/backups/crs310-maker/export.rsc index d2f42d5..f072abf 100644 --- a/backups/crs310-maker/export.rsc +++ b/backups/crs310-maker/export.rsc @@ -1,26 +1,30 @@ -# 2025-09-11 09:49:07 by RouterOS 7.19.6 +# 2025-09-11 10:03:39 by RouterOS 7.19.6 # software id = 73S3-5F2W # # model = CRS310-8G+2S+ # serial number = HM40B8TDNDD /interface bridge -add admin-mac=D0:EA:11:24:F4:AA auto-mac=no comment=defconf name=bridge +add admin-mac=D0:EA:11:24:F4:AA auto-mac=no comment=defconf name=bridge \ + vlan-filtering=yes +/interface vlan +add interface=bridge name=vlan-mgmt vlan-id=99 /interface bridge port -add bridge=bridge comment=defconf interface=ether1 -add bridge=bridge comment=defconf interface=ether2 -add bridge=bridge comment=defconf interface=ether3 -add bridge=bridge comment=defconf interface=ether4 -add bridge=bridge comment=defconf interface=ether5 -add bridge=bridge comment=defconf interface=ether6 -add bridge=bridge comment=defconf interface=ether7 -add bridge=bridge comment=defconf interface=ether8 -add bridge=bridge comment=defconf interface=sfp-sfpplus1 -add bridge=bridge comment=defconf interface=sfp-sfpplus2 +add bridge=bridge comment=defconf interface=ether1 pvid=30 +add bridge=bridge comment=defconf interface=ether2 pvid=30 +add bridge=bridge comment=defconf interface=ether3 pvid=30 +add bridge=bridge comment=defconf interface=ether4 pvid=30 +add bridge=bridge comment=defconf interface=ether5 pvid=30 +add bridge=bridge comment=defconf interface=ether6 pvid=30 +add bridge=bridge comment=defconf interface=ether7 pvid=30 +add bridge=bridge comment=defconf interface=ether8 pvid=99 +add bridge=bridge comment=defconf interface=sfp-sfpplus1 pvid=30 +add bridge=bridge comment=defconf interface=sfp-sfpplus2 pvid=30 +/interface bridge vlan +add bridge=bridge untagged="ether1,ether2,ether3,ether4,ether5,ether6,ether7,s\ + fp-sfpplus1,sfp-sfpplus2" vlan-ids=30 +add bridge=bridge tagged=bridge untagged=ether8 vlan-ids=99 /ip address -add address=192.168.88.1/24 comment=defconf interface=bridge network=\ - 192.168.88.0 -/ip dns -set servers=10.0.99.1 +add address=192.168.88.1/24 interface=vlan-mgmt network=192.168.88.0 /ip service set ftp disabled=yes set telnet disabled=yes @@ -29,7 +33,5 @@ set api disabled=yes set api-ssl disabled=yes /system identity set name=crs310-maker -/system ntp client -set enabled=yes /system ntp client servers add address=10.0.99.1 diff --git a/docs/superpowers/specs/2026-06-09-crs310-flat-mgmtvlan-design.md b/docs/superpowers/specs/2026-06-09-crs310-flat-mgmtvlan-design.md index baca8c8..3127bf4 100644 --- a/docs/superpowers/specs/2026-06-09-crs310-flat-mgmtvlan-design.md +++ b/docs/superpowers/specs/2026-06-09-crs310-flat-mgmtvlan-design.md @@ -91,6 +91,25 @@ all-access topology (DATA untagged = data ports, CPU tagged only on MGMT). - **Removing the legacy bridge IP** is the delicate step — done while the new `vlan-mgmt` IP is the same address, before filtering, with the connection watched. +## Execution notes (applied 2026-06-09) + +Cutover completed; switch is VLAN-filtered with isolated mgmt reachable on `ether8`. +`play_switch.yml` runs idempotently over the new mgmt path. Two gotchas surfaced: + +- **NetworkManager autoconnect flap:** moving mamba's cable bounced the link; NM + re-selected the DHCP profile and dropped the static mgmt IP. Fixed by making + `crs310-bench` (192.168.88.2) sticky (`autoconnect yes`, priority 10) and turning + `autoconnect off` on `Wired connection 1`. +- **RouterOS `find ... address=` never matches** an `/ip/address` value (returns + 0 even on an exact string). The first flip therefore failed to remove the defconf IP + off `bridge`, duplicating `192.168.88.1` onto `vlan-mgmt` and breaking ARP. Fix: remove + by `[find interface=bridge]`, or match via `:foreach`+`/ip/address/get $a address`. +- **The flip was run as a detached, self-reverting on-device job** (`:execute { … :delay + 240s; :if ($mgmtok=false) do={ revert } }`) — a commit-confirmed pattern. The first + (failed) attempt auto-healed at the timer; the corrected attempt was confirmed by + setting `:global mgmtok true` within the window. Recommended for any future + `vlan-filtering`/mgmt-IP change made over the network. + ## Out of scope Real inter-VLAN segmentation, the SFP+ 10G uplink/trunk, and any upstream router VLAN diff --git a/roles/makerfloss.mikrotik_switch/tasks/vlans.yml b/roles/makerfloss.mikrotik_switch/tasks/vlans.yml index 4d54da8..bb3c898 100644 --- a/roles/makerfloss.mikrotik_switch/tasks/vlans.yml +++ b/roles/makerfloss.mikrotik_switch/tasks/vlans.yml @@ -82,13 +82,14 @@ # detached flip (see the design doc's runbook), not by a straight play run. In steady # state both tasks below are no-ops. - name: Remove the legacy management IP from the bare bridge interface + # NOTE: RouterOS `find ... address=` does NOT match an ip/address prefix value + # (it returns 0 even on an exact string), so match by get-and-compare instead. community.routeros.command: commands: - >- - :if ([:len [/ip/address/find interface="{{ switch_bridge_name }}" - address="{{ switch_mgmt_address }}"]] > 0) - do={ /ip/address/remove [find interface="{{ switch_bridge_name }}" - address="{{ switch_mgmt_address }}"] } + :foreach a in=[/ip/address/find interface="{{ switch_bridge_name }}"] + do={ :if ([/ip/address/get $a address]="{{ switch_mgmt_address }}") + do={ /ip/address/remove $a } } changed_when: false - name: Assign the management IP address to vlan-mgmt