Skip to content

Commit

Permalink
Linux interface bonding: Fix gateway routes (#1774)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbemmel authored Jan 7, 2025
1 parent 901ecc3 commit d24faa8
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 29 deletions.
3 changes: 1 addition & 2 deletions netsim/extra/bonding/linux-clab.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ set -e

{# Bonding device is created by 'initial' module - add members #}
{% for intf in interfaces|default([]) if intf.bonding.members is defined %}
ip link set dev {{ intf.ifname }} down
{# Don't bring the bond interface down, it will loose any routes #}
{% for member in intf.bonding.members %}
ip link set dev {{ member }} down
ip link set dev {{ member }} master {{ intf.ifname }}
ip link set dev {{ member }} up
{% endfor %}
ip link set dev {{ intf.ifname }} up
{% endfor %}
62 changes: 36 additions & 26 deletions netsim/extra/bonding/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

_config_name = 'bonding'

ATTS_TO_MOVE = ['ipv4','ipv6','vlan','_vlan_mode','vlan_name','gateway']

'''
add_bond_interfaces - append interface data to node.interfaces for bonding template to read and implement
'''
Expand All @@ -29,17 +31,42 @@ def add_bond_interfaces(node: Box, bonds: typing.Dict[int,Box], topology: Box) -
}
if 'primary' in bond:
bond_if['bonding']['primary'] = bond['primary']
for af in ['ipv4','ipv6']:
for af in ATTS_TO_MOVE:
if af in bond:
bond_if[af] = bond[af] # Take the first one, if any
node.interfaces.append(bond_if)

"""
Update the link and all neighbors with the new interface name
"""
def update_neighbors( node: Box, intf: Box, topology: Box ) -> bool:
link = topology.links[ intf.linkindex-1 ]
if 'virtual_interface' in intf or link.node_count!=2:
log.error( f"{intf.name}: 'bonding.ifindex' can only be applied to interfaces on direct p2p links",
category=log.IncorrectAttr,module=_config_name)
return False

intf.neighbors = [ { 'node': n.node, 'ifname': n.ifname } for n in intf.neighbors ] # Clear any IP addresses from neighbors
bond_interface_name = topology.defaults.bonding.bond_interface_name
ifname = strings.eval_format(bond_interface_name, intf)
for if2 in link.interfaces:
if if2.node==node.name:
if2.ifname = ifname
else:
nb = topology.nodes[if2.node]
for if3 in nb.interfaces:
for n2 in if3.neighbors:
if n2.node==node.name:
n2.ifname = ifname
return True

'''
post_transform hook
post_link_transform hook
Apply plugin config to nodes with interfaces marked with 'bonding.ifindex', for devices that support this plugin
Apply plugin config to nodes with interfaces marked with 'bonding.ifindex', for devices that support this plugin.
Executes after IP addresses are assigned, but before vlan gateways are fixed
'''
def post_transform(topology: Box) -> None:
def post_link_transform(topology: Box) -> None:
global _config_name
bond_mode = topology.get('bonding.mode','active-backup')
bonds : Box = data.get_empty_box() # Map of bonds per node, indexed by bonding.ifindex
Expand All @@ -54,43 +81,26 @@ def post_transform(topology: Box) -> None:
category=log.IncorrectAttr,module=_config_name)
continue

link = topology.links[ intf.linkindex-1 ]
if 'virtual_interface' in intf or link.node_count!=2:
log.error( f"{intf.name}: 'bonding.ifindex' can only be applied to interfaces on direct p2p links",
category=log.IncorrectAttr,module=_config_name)
if not update_neighbors(node,intf,topology):
continue

clone = data.get_box(intf)
if node.name in bonds and bond_ifindex in bonds[node.name]:
bonds[node.name][bond_ifindex]['members'].append( clone.ifname )
for att in ['ipv4','ipv6']:
for att in ATTS_TO_MOVE:
intf.pop(att,None)
else:
mode = intf.get('bonding.mode',bond_mode)
bonds[node.name][bond_ifindex] = { 'neighbors': intf.neighbors, 'members': [ clone.ifname ], 'mode': mode }
for att in ['ipv4','ipv6']: # Move any ips (from first member link)
for att in ATTS_TO_MOVE: # Move any ips (from first member link)
if att in intf:
bonds[node.name][bond_ifindex][att] = intf.pop(att,None)
if intf.get('bonding.primary',False):
bonds[node.name][bond_ifindex]['primary'] = intf.ifname

#
# Clean up interface neighbors leaving only directly reachable peers (as opposed to - say -
# VLAN neighbors in the form of other hosts), moving the rest to bond[x].neighbors
#
intf.neighbors = [ { 'ifname': i.ifname, 'node': i.node } for i in link.interfaces if i.node!=node.name ]
intf.prefix = False # L2 interface
intf.pop('name',None)

# Interface neighbors may need to be updated to reflect the new bonded interface
bond_interface_name = topology.defaults.bonding.bond_interface_name
for node in topology.nodes.values(): # For each node
if node.name in bonds: # ...that has 1 or more bonds
for bond in bonds[node.name].values(): # ...for each bond
for i in bond.neighbors: # ...for each neighbor of that bond
if i.node in bonds: # ...check if the node also has bonds
for i2,b2 in bonds[i.node].items(): # If so, for each such bond
if i.ifname in b2['members']: # if the interface connecting to <node> is a member
i.ifname = strings.eval_format(bond_interface_name, { 'ifindex': i2 })
continue
add_bond_interfaces(node,bonds[node.name],topology)
api.node_config(node,_config_name) # Remember that we have to do extra configuration

4 changes: 3 additions & 1 deletion netsim/modules/vlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,9 @@ def fix_vlan_gateways(topology: Box) -> None:
if node.get('role') != 'host': # Fix first-hop gateways only for hosts
continue
for intf in node.get('interfaces',[]): # Iterate over all interfaces
if intf.get('gateway.ipv4',None): # ... that don't have an IPv4 gateway
if not intf.get('ipv4',None): # ... that are IP interfaces
continue
if intf.get('gateway.ipv4',None): # ... that don't already have an IPv4 gateway
continue

gw_found = False
Expand Down

0 comments on commit d24faa8

Please sign in to comment.