Skip to content

11.2 MRP Roles and Frames

The previous chapter explained how MRP operates: the MRM blocks a port, sends test frames, and unblocks on detected inoperability. Understanding MRP at the frame level enables diagnosis of ring issues that HiOS diagnostics alone do not explain.

HiOS reports “Ring Open” but cables are connected. The event log shows topology changes every few seconds. The ring recovers but some devices are unreachable. These situations require looking at the actual MRP frames on the wire to determine the cause.

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 TypeValueSent byPurpose
MRP_Test0x0001MRMVerify ring integrity every 10 to 20 ms
MRP_TopologyChange0x0002MRMTrigger MAC table flush on MRCs
MRP_LinkDown0x0003MRCNotify MRM of link inoperability immediately
MRP_LinkUp0x0004MRCNotify MRM of link recovery
BytesField
0 to 1Frame Type (0x0001)
2 to 3Version (0x0001)
4 to 5Sequence Number
6 to 21Domain UUID (16 bytes)
22 to 23Interval (test interval in ms x 10)
24 to 25Transition 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, Ether
import 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 script during a ring failover shows the exact sequence: MRP_LinkDown from the MRC adjacent to the inoperability, followed by MRP_TopologyChange from the MRM. If MRP_TopologyChange does not appear, then the MRM is not reacting. This behavior points to a VLAN or UUID mismatch.

The following script tracks MRP_Test frame timing to detect ring issues before the issues cause outages. The script alerts when test frames stop arriving or when topology changes occur too frequently.

from scapy.all import sniff, Ether
import struct, time, threading
from 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 condition visible immediately.

2 Ring Managers cause continuous ring oscillation. Only the MRM sends MRP_Test frames. If test frames arrive from 2 different source MACs, then the ring has 2 MRMs.

from scapy.all import sniff, Ether
import struct
from 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 2 MAC addresses, then reconfigure 1 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 issues before the issues 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. 2 source MACs means 2 MRMs.

Understanding MRP frames enables diagnosis. Understanding MRP timing enables optimization. The next chapter covers MRP timing parameters, convergence time calculation, and tuning MRP for faster recovery.

  • IEC 62439-2:2016 — Media Redundancy Protocol (MRP)
  • Hirschmann. (2023). User Manual: HiOS MRP Configuration. Belden/Hirschmann.