MRP_Test frames arrive every 10-20 ms
Monitor the gap between test frames to detect timing problems before they cause outages.
The previous chapter explained how MRP operates: the MRM blocks a port, sends test frames, and unblocks on failure. Understanding MRP at the frame level enables you to diagnose ring problems that HiOS diagnostics alone cannot explain.
HiOS reports “Ring Open” but all cables are connected. The event log shows topology changes every few seconds. The ring recovers but some devices are unreachable. These problems require looking at the actual MRP frames on the wire to determine what is happening and why.
All MRP frames use EtherType 0x88E3 and destination MAC 01:15:4E:00:00:01 (MRP multicast). The payload starts with a 2-byte Frame Type field:
| Frame Type | Value | Sent by | Purpose |
|---|---|---|---|
| MRP_Test | 0x0001 | MRM | Verify ring integrity every 10 to 20 ms |
| MRP_TopologyChange | 0x0002 | MRM | Trigger MAC table flush on all MRCs |
| MRP_LinkDown | 0x0003 | MRC | Notify MRM of link failure immediately |
| MRP_LinkUp | 0x0004 | MRC | Notify MRM of link recovery |
| Bytes | Field |
|---|---|
| 0 to 1 | Frame Type (0x0001) |
| 2 to 3 | Version (0x0001) |
| 4 to 5 | Sequence Number |
| 6 to 21 | Domain UUID (16 bytes) |
| 22 to 23 | Interval (test interval in ms x 10) |
| 24 to 25 | Transition count |
The sequence number increments with each test frame. A gap in the sequence indicates a lost frame. The interval field confirms the configured test timing.
The following script captures MRP frames on a mirror port and decodes each frame type, sequence number, and domain UUID.
from scapy.all import sniff, Etherimport struct, time
MRP_FRAME_TYPES = { 0x0001: "MRP_Test", 0x0002: "MRP_TopologyChange", 0x0003: "MRP_LinkDown", 0x0004: "MRP_LinkUp",}
def parse_mrp_frame(pkt): if not pkt.haslayer(Ether) or pkt[Ether].type != 0x88E3: return payload = bytes(pkt[Ether].payload) if len(payload) < 4: return frame_type = struct.unpack_from(">H", payload, 0)[0] name = MRP_FRAME_TYPES.get(frame_type, f"UNKNOWN(0x{frame_type:04x})") src_mac = pkt[Ether].src ts = time.strftime("%H:%M:%S") print(f"[{ts}] {name:25s} from {src_mac}", end="") if frame_type == 0x0001 and len(payload) >= 24: seq = struct.unpack_from(">H", payload, 4)[0] uuid = payload[6:22].hex("-") print(f" seq={seq} uuid={uuid[:23]}") elif frame_type == 0x0002: print(f" MAC TABLE FLUSH TRIGGERED") elif frame_type in (0x0003, 0x0004): print(f" {'LINK DOWN' if frame_type == 0x0003 else 'LINK UP'}") else: print()
sniff(iface="eth0", filter="ether proto 0x88e3", prn=parse_mrp_frame, store=False)Running this during a ring failover shows the exact sequence: MRP_LinkDown from the MRC adjacent to the failure, followed by MRP_TopologyChange from the MRM. If MRP_TopologyChange does not appear, the MRM is not reacting, which points to a VLAN or UUID mismatch.
The following script tracks MRP_Test frame timing to detect ring problems before they cause outages. It alerts when test frames stop arriving or when topology changes occur too frequently.
from scapy.all import sniff, Etherimport struct, time, threadingfrom collections import deque
class MRPRingMonitor: def __init__(self, iface: str, expected_interval_ms: float = 20.0, alert_threshold_ms: float = 60.0): self.iface = iface self.expected_ms = expected_interval_ms self.alert_ms = alert_threshold_ms self.last_test_time: float | None = None self.test_intervals: deque = deque(maxlen=100) self.topology_changes = 0
def _handle_frame(self, pkt): if not pkt.haslayer(Ether) or pkt[Ether].type != 0x88E3: return payload = bytes(pkt[Ether].payload) if len(payload) < 2: return frame_type = struct.unpack_from(">H", payload, 0)[0] now = time.time() if frame_type == 0x0001: if self.last_test_time is not None: interval_ms = (now - self.last_test_time) * 1000 self.test_intervals.append(interval_ms) if interval_ms > self.alert_ms: print(f"Test frame gap: {interval_ms:.0f} ms (threshold: {self.alert_ms} ms)") self.last_test_time = now elif frame_type == 0x0002: self.topology_changes += 1 print(f"Topology change #{self.topology_changes} at {time.strftime('%H:%M:%S')}") elif frame_type == 0x0003: print(f"Ring OPEN at {time.strftime('%H:%M:%S')}")
def start(self): print(f"MRP Ring Monitor on {self.iface}") sniff(iface=self.iface, filter="ether proto 0x88e3", prn=self._handle_frame, store=False)
MRPRingMonitor(iface="eth0").start()More than 5 topology changes per minute indicates a flapping link or dual MRM. The monitor makes this visible immediately.
Two Ring Managers cause continuous ring oscillation. Only the MRM sends MRP_Test frames. If test frames arrive from two different source MACs, the ring has two MRMs.
from scapy.all import sniff, Etherimport structfrom collections import defaultdict
mrm_candidates: dict[str, int] = defaultdict(int)
def detect_dual_mrm(pkt): if not pkt.haslayer(Ether) or pkt[Ether].type != 0x88E3: return payload = bytes(pkt[Ether].payload) if len(payload) < 2: return if struct.unpack_from(">H", payload, 0)[0] == 0x0001: src = pkt[Ether].src mrm_candidates[src] += 1 if len(mrm_candidates) > 1: print(f"DUAL MRM DETECTED: {list(mrm_candidates.keys())}")
sniff(iface="eth0", filter="ether proto 0x88e3", prn=detect_dual_mrm, count=100, store=False)If this script prints two MAC addresses, reconfigure one of the switches from Manager to Client immediately.
MRP_Test frames arrive every 10-20 ms
Monitor the gap between test frames to detect timing problems before they cause outages.
MRP_TopologyChange triggers MAC flush
More than 5 topology changes per minute indicates a flapping link or dual MRM.
Dual MRM is detectable from the wire
Only the MRM sends MRP_Test frames. Two source MACs means two MRMs.
Understanding MRP frames enables diagnosis. Understanding MRP timing enables optimization. The next chapter covers MRP timing parameters, convergence time calculation, and how to tune MRP for faster recovery.