RouterOS ports and kernel names
The kernel log names netdevs (eth0, eth5), RouterOS names interfaces
(ether1, sfp-sfpplus1), and they do not agree. This page answers which
cable a kernel-log record is about. During the loop case
study this cost real time: eth1 in the log looks
like it should be ether1, and it is not.
Measured on RB5009UG+S+ · 4 × 1.4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · · agent at 10 Hz in an ephemeral privileged container
Later observations are dated where they appear.
Why it has to be measured
Section titled “Why it has to be measured”RouterOS exposes no mapping, and the container cannot read one. Network devices
are namespaced, so /sys/class/net inside the container shows only lo and the
veth; /sys/class/mdio_bus holds only fixed-0 and /sys/class/phy is empty.
privileged=yes does not change that. RouterOS’s names live in RouterOS’s
configuration, not in the kernel.
Measure it on a dead port
Section titled “Measure it on a dead port”Pick a port that is definitely carrying nothing, toggle it, and read the name the kernel prints. Find a genuinely dead port first — zero packets in both directions, for its whole life:
/interface/print stats where name="ether6" or name="ether7"Then, with the agent running:
/interface/ethernet/disable [find name="ether6"]:delay 4s/interface/ethernet/enable [find name="ether6"]The kernel said:
[6] br0: port 7(eth5) entered blocking state[4] eth5: set isolation from 0 to 1[4] eth5: set isolation from 1 to 0So RouterOS ether6 is kernel eth5. Repeating on ether7 gave eth6: a
−1 offset, seen at two points.
The RB5009 table
Section titled “The RB5009 table”The mapping is a shift by one — RouterOS numbers ports from 1 and the kernel
from 0, the switch chip included (switch0 is the switch=switch1 every port
reports):
| RouterOS | kernel | how known |
|---|---|---|
ether1 |
eth0 |
inferred |
ether2 |
eth1 |
measured — the case-study loop, 2026-09-13 |
ether3 … ether5 |
eth2 … eth4 |
inferred |
ether6 |
eth5 |
measured — flapped 2026-09-15 |
ether7 |
eth6 |
measured — flapped 2026-09-15 |
ether8 |
eth7 |
inferred |
sfp-sfpplus1 |
eth8 |
inferred, last in the enumeration |
switch1 |
switch0 |
inferred |
Scroll sideways to see every column
The ether6 and ether7 pairs were measured on 2026-09-15. Both ports are
commented Unused and neither was RUNNING — no cable, no link — so each was
disabled and enabled again over the API while the agent read /dev/kmsg. The
kernel logged br0: port 7(eth5) entered disabled state inside ether6’s
window and port 7(eth6) inside ether7’s, nine seconds apart, which is what
rules out reading one flap twice. No traffic was interrupted and both ports were
back within four seconds.
That makes three measured pairs, all on the same shift by one, which is what the six inferred rows rest on.
The inferred pairs rest on two observations from a read-only
/ (2026-09-13, the date the table’s own evidence
string carries): the nine ports carry consecutive MAC addresses, …:55 for
ether1 through …:5D for sfp-sfpplus1, in RouterOS’s own enumeration order;
and all nine report switch=switch1 against the kernel’s one switch0.
Two cautions
Section titled “Two cautions”- The name is reliable; the bridge port number is not. Both flaps reported
port 7, because the kernel reuses port slots when a port leaves and rejoins the bridge. Match oneth5, never onport 7. - The offset is a property of this model’s driver, not a rule. Re-measure on
a different device rather than assuming, and be careful with the intuition that
the SFP+ must be
eth0— here it is last, not first. The device tree does not help either: it shows the SoC’sethernet@0with three MACs, onlyeth0enabled (the 10 G uplink) andeth1/eth2disabled, while the nine front-panel ports are netdevs the switch driver creates at runtime (the device tree was parsed on 2026-09-14). The kernel-log names are the runtime ones.
What the agent does with the table
Section titled “What the agent does with the table”The agent reads the board model from the device tree at start —
/proc/device-tree/model reads RB5009 even unprivileged — and, on a board in
its table, names ports without asking RouterOS:
- every kernel-log record whose text names a port carries both names and what
happened to that port:
iface,ros_ifaceandkindon the event,portandkindtags on the InfluxDB rows, andiface=eth1 ros_iface=ether2 port_on a Loki line;event=own-address /metricsgainsmikroscope_, with the RouterOS name askmsg_ port_ records_ total{port,kind,level} porton a board in the table, the kernel name on a board that is not, and no port series at all on a device whose device tree reports no model. It is a subset ofmikroscope_, not a partition of it: records naming no port are absent from it;kmsg_ records_ total - the collector’s
link-flapdetection is keyed by the port name and reads the same classification: a flap islink-upandlink-downrecords on one port, counted, not the text parsed a second time; mikroscope statusprints the board and whether it has a map, for exampleeth1 (ether2);/healthzand/capabilitiescarry the board, and/capabilitiesandmikroscope_device_infocarry the table’s evidence string asports_from, so nobody has to take the mapping on trust.
An unknown board gets no port names, not guessed ones. The shift by one is
not applied to a board nobody has measured, because a confidently wrong port name
sends someone to the wrong cable. On such a board status says so and asks for
the pair: bring one port down, see which ethN the log names, and send that pair
with the board string.
What kind of event it was
Section titled “What kind of event it was”A port name alone does not say what happened to the port, so every record that
names one is classified as well, by procfs.KmsgKind over the record’s text.
The kinds, and the record shapes the RouterOS kernel prints (RB5009, kernel
5.6.3, records seen 2026-09-12 … 2026-09-15):
kind |
the record |
|---|---|
link-up |
eth8: link up, 1Gbps, full-duplex, eth1: Link is Up - 1Gbps/Full, eth1: phy link up |
link-down |
eth1: link down |
stp-<state> |
br0: port 2(eth1) entered blocking state — blocking, listening, learning, forwarding, disabled |
own-address |
br0: received packet on eth1 with own address as source address (addr:…, vlan:0) — the layer-2 loop signature |
other |
anything else that names a port, including eth1: link becomes ready, which is IPv6 address configuration noticing the carrier and not a transition of its own |
Scroll sideways to see every column
The agent classifies at read time and ships kind in the record, and a
collector in front of an older agent whose records carry none classifies them
itself with the same function. That is the case on the reference router today:
the agent running there has not been redeployed with this build, so its
/metrics carries no kind label yet and the collector does the work.
Four records are not four faults. A normal link-up is followed by
stp-blocking, stp-learning and stp-forwarding as the bridge walks the port
back into service.
From the default name to the current one
Section titled “From the default name to the current one”The table maps to RouterOS’s default names. A port renamed on the router
(ether5 → WAN) has a name the table cannot know, so the collector asks the
API tier instead. Its interface inventory — read once before the first kernel
pull and again every --labels-every, five minutes by default — holds each
interface’s factory default name, its current name, its comment and its
interface lists, and the collector uses it on every record that names a port to:
- replace the default name with the current one, so an operator who renamed
ether5toWANreadsWANon the event, on the InfluxDBporttag, on the Loki line and in thelink-flapkey; - attach
label, the port’s comment, androle, its interface lists — so theeth5flap arrives asether6, labelUnused, rather than as a netdev number somebody has to look up.
Without an API tier the record keeps the board’s default name and gets no label, which is what it can support.
Where a kernel message becomes a cable
Section titled “Where a kernel message becomes a cable”Two panels in the dashboards’ Kernel log row are where the name, the kind and the comment meet:
- Port events from the kernel log, per port and kind — bars per bin, one
series per port and kind, from
sum by (port, kind) (increase(mikroscope_on Prometheus and from thekmsg_ port_ records_ total[$_ _ interval])) port/kindtags on InfluxDB; - Port events in the window, per port — a table with one row per port that
the log named: the port, its
labelandrole, and a column per kind (link down, link up, own address (loop), STP blocking, STP disabled, STP learning, STP forwarding, other).
Both are known-empty panels — a quiet set of ports is the healthy state — and
the Prometheus form of each reads the collector’s /metrics, whose copy of the
family carries a kind on every port record however the agent shipped it. In an InfluxDB store the kind column exists only once a first port
record classified by kind has been written, so both queries were validated on
2026-09-16 against a synthetic table in the same InfluxDB 3, the live store
having no such column yet.
Two alert rules ship beside them, in both provisioning files. Both read
/dev/kmsg through the agent and ask RouterOS nothing:
mikroscope-l2-loop, critical: anyown-addressrecord in five minutes. On the reference RB5009 that signature ran at 1.49 records/s for hours on 2026-09-12 while every RouterOS counter looked healthy.mikroscope-port-link-down, warning: anylink-downrecord in five minutes — the single event, wherelink-flapcovers the repeats.
The InfluxDB form of each needs a store that has already held one port record classified by kind; before that the query fails at planning time.
The port table all of this rests on carries its own limit.