Skip to content

Add LAG for RouterOS7 - #3851

Draft
snuffy22 wants to merge 2 commits into
ipspace:devfrom
snuffy22:routeros7-lags
Draft

Add LAG for RouterOS7#3851
snuffy22 wants to merge 2 commits into
ipspace:devfrom
snuffy22:routeros7-lags

Conversation

@snuffy22

@snuffy22 snuffy22 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This enables LAGs for RouterOS7

Had to create a new quirk for this process.

With RouterOS7 bonds, apparently you must increase the MTU of the slave interfaces by 4 bytes for the VLAN header.
Yet putting a VLAN on a ethernet interface directly does not need this and keeps the default 1500 MTU.

Retested the VLAN and new LAG integration tests below.

LAG integration tests
(py3-snuff) netlab@netlab:~/snuffy-netlab/tests/integration$ ./device-module-test -p clab -d routeros7 lag/
Pre-test cleanup:
09:39:19 lag/01-l3-lag.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
09:40:51 lag/02-lag-vlan-trunk.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
09:42:16 lag/03-l3-lag-passive.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
09:44:00 lag/04-lag-vlan-routed-trunk.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
09:45:33 lag/10-mlag.yml: create(FAIL)
09:45:34 lag/11-mlag-anycast.yml: create(FAIL)
(py3-snuff) netlab@netlab:~/snuffy-netlab/tests/integration$
VLAN integration tests
(py3-snuff) netlab@netlab:~/snuffy-netlab/tests/integration$ ./device-module-test -d routeros7 -p clab vlan/
Pre-test cleanup:
07:57:08 vlan/01-vlan-bridge-single.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
07:58:23 vlan/02-vlan-bridge-multiple.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
07:59:52 vlan/21-vlan-irb-single.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:01:18 vlan/22-vlan-irb-multiple.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:02:43 vlan/23-vlan-mixed-multiple.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:04:11 vlan/31-vlan-bridge-trunk.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:07:47 vlan/32-vlan-bridge-trunk-router.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:10:01 vlan/33-vlan-irb-trunk.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:14:05 vlan/41-vlan-bridge-native.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:17:06 vlan/42-vlan-irb-native.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:20:18 vlan/51-vlan-routed-trunk.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:22:24 vlan/52-vlan-vrf-lite.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:24:29 vlan/61-vlan-routed-native.yml: create(ok) up(ok) config(ok) validate(ok) cleanup(ok) OK
08:26:34 vlan/62-vlan-mixed-trunk.yml: create(FAIL)
08:26:35 vlan/63-vlan-mixed-native.yml: create(FAIL)
08:26:35 vlan/70-vlan-1-trunk.yml: create(FAIL)
(py3-snuff) netlab@netlab:~/snuffy-netlab/tests/integration$

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You over-engineered the quirk. We don't update the interface MTU based on the VLAN MTU it carries (maybe we should, but for the moment, the user must handle that).

I would therefore only check if a LAG interface has any vlan attributes, and if so, increase the MTU of the LAG members to LAG MTU + 4.

On a totally unrelated note: just wondering what problem you were solving with the "dynamic=no" option in the VLAN find command?

@snuffy22

snuffy22 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Hello Ivan,

I will make the quirk more basic.

As for why dynamic=no,

Below is output from the 03-lag-passive test.

As we can see we have a dynamic entry as well as a non dynamic for both pvids. The one we want to edit is the non-dynamic one.

[admin@dut] > /interface/bridge/vlan/print
Flags: D - DYNAMIC
Columns: BRIDGE, VLAN-IDS, CURRENT-TAGGED, CURRENT-UNTAGGED
#   BRIDGE  VLAN-IDS  CURRENT-TAGGED  CURRENT-UNTAGGED
;;; added by pvid
0 D switch         1                  switch
;;; added by pvid
1 D switch      1000                  bond1
;;; added by pvid
2 D switch      1001                  bond2
3   switch      1000  switch
4   switch      1001  switch
[admin@dut] >

@snuffy22

snuffy22 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

I tried a much simpler version of the check.
But this ended up missing the routed interfaces aka 04-lag-vlan-routed-trunk.yml

def adjust_lag_vlan_mtu2(node: Box) -> None:
  for lag in node.interfaces:
    if lag.get('type') != 'lag':
      continue

    if 'vlan' not in lag:
      continue

    required_mtu = lag.get('mtu',1500) + 4

    for member in node.interfaces:
      if member.get('lag._parentindex') == lag.lag.ifindex:
        member.mtu = max(member.get('mtu',1500),required_mtu,)

Have updated with slightly more concise check now, but still works for all lag tests

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things I'd fix to make life easier for my future self ;)

When creating a LAG that uses VLANs, we need to add 4 bytes to the parent interface
for the VLAN Header so the VLAN MTU is the expected size ie. 1500 vs 1496
'''
for lag in node.interfaces:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've created an O(n^2) solution for an O(n) problem. What you could do instead is:

  • Iterate over all LAG interfaces, collect their maximum MTU, store it into a dict based on ifindex
  • Iterate over all L3 VLAN subinterfaces, adjust the maximum MTU of the LAG interface based on parent_ifindex (if the parent_ifindex is in the LAG_MTU dict)
  • Iterate over all member interfaces, adjust their MTU from the LAG_MTU dict.

for the VLAN Header so the VLAN MTU is the expected size ie. 1500 vs 1496
'''
for lag in node.interfaces:
if lag.get('type') != 'lag':

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is hard to read as you use "lag" to mean "an interface that could be a LAG interface. How about "lag_intf" or some such, so it's clear you're dealing with an interface.

required_mtu = lag.get('mtu', 1500) + 4

# Routed VLAN subinterfaces stored separately from the LAG.
for vlan in node.interfaces:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is even worse from the naming perspective, as it looks like you're iterating over VLANs

continue

for member in node.interfaces:
if member.get('lag._parentindex') == lag.lag.ifindex:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, member of what? How about "lag_member".

if not required_mtu:
continue

for member in node.interfaces:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the code be cleaner to read if you used list comprehension? For example:

for lag_member in [ intf for intf in node.interfaces if intf.get('lag._parentindex') ]:

I'm not leaning one way or the other, just wondering what would be easier to read.

if member.get('lag._parentindex') == lag.lag.ifindex:
member.mtu = max(member.get('mtu', 1500),required_mtu,)

def adjust_lag_vlan_mtu2(node: Box) -> None:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a leftover

@snuffy22

snuffy22 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Sorry about the extra code in the upload, thought I had removed it.

I will work on your other suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants