Add ability to manipulate MACs

Special care is taken for bondethernet, where the MAC changes when
the first member is added to it. BondEthernet requires its MAC to
be set in the bondethernets section, disallowing the MAC of individual
members to be set.

Also write a dumper for MACs of all types. Update integration test
cases to stress the MAC changes on loops, bonds, and phys.
This commit is contained in:
Pim van Pelt
2022-04-10 09:54:51 +00:00
parent 656f2ce883
commit a7545ac5af
12 changed files with 71 additions and 8 deletions

View File

@ -1,6 +1,7 @@
bondethernets: bondethernets:
BondEthernet0: BondEthernet0:
interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ] interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ]
mac: 02:b0:b0:01:02:03
mode: xor mode: xor
load-balance: l2 load-balance: l2
@ -14,6 +15,7 @@ interfaces:
HundredGigabitEthernet12/0/0: HundredGigabitEthernet12/0/0:
lcp: "ice12-0-0" lcp: "ice12-0-0"
mac: f2:01:00:12:00:00
mtu: 9000 mtu: 9000
addresses: [ 192.0.2.17/30, 2001:db8:3::1/64 ] addresses: [ 192.0.2.17/30, 2001:db8:3::1/64 ]
sub-interfaces: sub-interfaces:
@ -74,6 +76,7 @@ loopbacks:
loop1: loop1:
lcp: "bvi1" lcp: "bvi1"
mtu: 2000 mtu: 2000
mac: 02:de:ad:01:be:ef
addresses: [ 10.0.1.1/24, 2001:db8:1::1/64 ] addresses: [ 10.0.1.1/24, 2001:db8:1::1/64 ]
bridgedomains: bridgedomains:

View File

@ -1,17 +1,21 @@
interfaces: interfaces:
GigabitEthernet3/0/0: GigabitEthernet3/0/0:
mtu: 1500 mtu: 1500
mac: 00:25:90:0c:05:00
state: down state: down
description: Not Used description: Not Used
GigabitEthernet3/0/1: GigabitEthernet3/0/1:
mtu: 1500 mtu: 1500
mac: 00:25:90:0c:05:01
state: down state: down
description: Not Used description: Not Used
HundredGigabitEthernet12/0/0: HundredGigabitEthernet12/0/0:
mtu: 1500 mtu: 1500
mac: b4:96:91:b3:b1:10
state: down state: down
description: Not Used description: Not Used
HundredGigabitEthernet12/0/1: HundredGigabitEthernet12/0/1:
mtu: 1500 mtu: 1500
mac: b4:96:91:b3:b1:11
state: down state: down
description: Not Used description: Not Used

View File

@ -70,6 +70,7 @@ interfaces:
loopbacks: loopbacks:
loop0: loop0:
lcp: "lo0" lcp: "lo0"
mac: de:ad:00:be:ef:00
addresses: [ 10.0.0.1/32, 2001:db8::1/128 ] addresses: [ 10.0.0.1/32, 2001:db8::1/128 ]
loop1: loop1:
mtu: 2000 mtu: 2000

View File

@ -1,5 +1,6 @@
bondethernets: bondethernets:
BondEthernet0: BondEthernet0:
mac: 02:b0:b0:00:00:01
interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ] interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ]
interfaces: interfaces:

View File

@ -1,5 +1,6 @@
bondethernets: bondethernets:
BondEthernet1: BondEthernet1:
mac: 02:b0:b0:00:00:02
interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ] interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ]
mode: round-robin mode: round-robin

View File

@ -1,5 +1,6 @@
interfaces: interfaces:
GigabitEthernet3/0/0: GigabitEthernet3/0/0:
mac: 12:00:ba:03:00:00
mtu: 9216 mtu: 9216
sub-interfaces: sub-interfaces:
100: 100:

View File

@ -1,5 +1,6 @@
interfaces: interfaces:
GigabitEthernet3/0/0: GigabitEthernet3/0/0:
mac: 02:ff:ba:03:00:00
mtu: 9216 mtu: 9216
sub-interfaces: sub-interfaces:
100: 100:

View File

@ -45,6 +45,7 @@ interfaces:
loopbacks: loopbacks:
loop11: loop11:
mtu: 3000 mtu: 3000
mac: de:ad:00:be:ef:11
lcp: "bvi11" lcp: "bvi11"
addresses: [ 2001:db8:1::1/64, 192.0.2.1/30 ] addresses: [ 2001:db8:1::1/64, 192.0.2.1/30 ]

View File

@ -1,5 +1,6 @@
bondethernets: bondethernets:
BondEthernet0: BondEthernet0:
mac: 02:b0:b0:00:00:00
interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ] interfaces: [ GigabitEthernet3/0/0, GigabitEthernet3/0/1 ]
interfaces: interfaces:
@ -11,6 +12,7 @@ interfaces:
description: "LAG #2" description: "LAG #2"
HundredGigabitEthernet12/0/0: HundredGigabitEthernet12/0/0:
mac: 02:ff:ba:12:00:00
lcp: "ice0" lcp: "ice0"
HundredGigabitEthernet12/0/1: HundredGigabitEthernet12/0/1:

View File

@ -25,5 +25,9 @@
echo " - Checking that from $j to $j is empty" echo " - Checking that from $j to $j is empty"
../vppcfg plan -s ../schema.yaml -c $j -o /tmp/vppcfg-exec_${j}_${j}_null ../vppcfg plan -s ../schema.yaml -c $j -o /tmp/vppcfg-exec_${j}_${j}_null
[ -s /tmp/vppcfg-exec_${j}_${j}_null ] && {
echo " - ERROR Transition is not empty"
cat /tmp/vppcfg-exec_${j}_${j}_null
}
done done
done done

View File

@ -613,6 +613,9 @@ class Reconciler():
continue continue
instance = int(ifname[4:]) instance = int(ifname[4:])
cli="create loopback interface instance %d" % (instance) cli="create loopback interface instance %d" % (instance)
ifname, iface = loopback.get_by_name(self.cfg, ifname)
if 'mac' in iface:
cli += " mac %s" % iface['mac']
self.cli['create'].append(cli); self.cli['create'].append(cli);
return True return True
@ -627,6 +630,8 @@ class Reconciler():
lb = bondethernet.get_lb(self.cfg, ifname) lb = bondethernet.get_lb(self.cfg, ifname)
if lb: if lb:
cli += " load-balance %s" % lb cli += " load-balance %s" % lb
if 'mac' in iface:
cli += " hw-addr %s" % iface['mac']
self.cli['create'].append(cli); self.cli['create'].append(cli);
return True return True
@ -727,6 +732,9 @@ class Reconciler():
def sync(self): def sync(self):
ret = True ret = True
if not self.sync_loopbacks():
self.logger.warning("Could not sync Loopbacks in VPP")
ret = False
if not self.sync_bondethernets(): if not self.sync_bondethernets():
self.logger.warning("Could not sync bondethernets in VPP") self.logger.warning("Could not sync bondethernets in VPP")
ret = False ret = False
@ -742,18 +750,46 @@ class Reconciler():
if not self.sync_addresses(): if not self.sync_addresses():
self.logger.warning("Could not sync interface addresses in VPP") self.logger.warning("Could not sync interface addresses in VPP")
ret = False ret = False
if not self.sync_phys():
self.logger.warning("Could not sync PHYs in VPP")
ret = False
if not self.sync_admin_state(): if not self.sync_admin_state():
self.logger.warning("Could not sync interface adminstate in VPP") self.logger.warning("Could not sync interface adminstate in VPP")
ret = False ret = False
return ret return ret
def sync_loopbacks(self):
for ifname in loopback.get_loopbacks(self.cfg):
if not ifname in self.vpp.cache['interface_names']:
## New loopback
continue
vpp_iface = self.vpp.cache['interface_names'][ifname]
config_ifname, config_iface = loopback.get_by_name(self.cfg, ifname)
if 'mac' in config_iface and config_iface['mac'] != str(vpp_iface.l2_address):
cli="set interface mac address %s %s" % (config_ifname, config_iface['mac'])
self.cli['sync'].append(cli)
return True
def sync_phys(self):
for ifname in interface.get_phys(self.cfg):
if not ifname in self.vpp.cache['interface_names']:
## New interface
continue
vpp_iface = self.vpp.cache['interface_names'][ifname]
config_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
if 'mac' in config_iface and config_iface['mac'] != str(vpp_iface.l2_address):
cli="set interface mac address %s %s" % (config_ifname, config_iface['mac'])
self.cli['sync'].append(cli)
return True
def sync_bondethernets(self): def sync_bondethernets(self):
for ifname in bondethernet.get_bondethernets(self.cfg): for ifname in bondethernet.get_bondethernets(self.cfg):
if ifname in self.vpp.cache['interface_names']: if ifname in self.vpp.cache['interface_names']:
vpp_bond_sw_if_index = self.vpp.cache['interface_names'][ifname].sw_if_index vpp_iface = self.vpp.cache['interface_names'][ifname]
vpp_members = [self.vpp.cache['interfaces'][x].interface_name for x in self.vpp.cache['bondethernet_members'][vpp_bond_sw_if_index]] vpp_members = [self.vpp.cache['interfaces'][x].interface_name for x in self.vpp.cache['bondethernet_members'][vpp_iface.sw_if_index]]
else: else:
## New BondEthernet ## New BondEthernet
vpp_iface = None
vpp_members = [] vpp_members = []
config_bond_ifname, config_bond_iface = bondethernet.get_by_name(self.cfg, ifname) config_bond_ifname, config_bond_iface = bondethernet.get_by_name(self.cfg, ifname)
@ -769,7 +805,10 @@ class Reconciler():
bondmac = member_iface.l2_address bondmac = member_iface.l2_address
cli="bond add %s %s" % (config_bond_ifname, member_iface.interface_name) cli="bond add %s %s" % (config_bond_ifname, member_iface.interface_name)
self.cli['sync'].append(cli); self.cli['sync'].append(cli);
if bondmac and 'lcp' in config_iface: if vpp_iface and 'mac' in config_iface and str(vpp_iface.l2_address) != config_iface['mac']:
cli="set interface mac address %s %s" % (config_ifname, config_iface['mac'])
self.cli['sync'].append(cli);
elif bondmac and 'lcp' in config_iface:
## TODO(pim) - Ensure LCP has the same MAC as the BondEthernet ## TODO(pim) - Ensure LCP has the same MAC as the BondEthernet
## VPP, when creating a BondEthernet, will give it an ephemeral MAC. Then, when the ## VPP, when creating a BondEthernet, will give it an ephemeral MAC. Then, when the
## first member is enslaved, the MAC address changes to that of the first member. ## first member is enslaved, the MAC address changes to that of the first member.

View File

@ -272,17 +272,19 @@ class VPPApiDumper(VPPApi):
def cache_to_config(self): def cache_to_config(self):
config = {"loopbacks": {}, "bondethernets": {}, "interfaces": {}, "bridgedomains": {}, "vxlan_tunnels": {} } config = {"loopbacks": {}, "bondethernets": {}, "interfaces": {}, "bridgedomains": {}, "vxlan_tunnels": {} }
for idx, iface in self.cache['bondethernets'].items(): for idx, bond_iface in self.cache['bondethernets'].items():
bond = {"description": ""} bond = {"description": ""}
if iface.sw_if_index in self.cache['bondethernet_members']: if bond_iface.sw_if_index in self.cache['bondethernet_members']:
members = [self.cache['interfaces'][x].interface_name for x in self.cache['bondethernet_members'][iface.sw_if_index]] members = [self.cache['interfaces'][x].interface_name for x in self.cache['bondethernet_members'][bond_iface.sw_if_index]]
if len(members) > 0: if len(members) > 0:
bond['interfaces'] = members bond['interfaces'] = members
mode = bondethernet.int_to_mode(iface.mode) mode = bondethernet.int_to_mode(bond_iface.mode)
bond['mode'] = mode bond['mode'] = mode
if mode in ['xor', 'lacp']: if mode in ['xor', 'lacp']:
bond['load-balance'] = bondethernet.int_to_lb(iface.lb) bond['load-balance'] = bondethernet.int_to_lb(bond_iface.lb)
iface = self.cache['interfaces'][bond_iface.sw_if_index]
bond['mac'] = str(iface.l2_address)
config['bondethernets'][iface.interface_name] = bond config['bondethernets'][iface.interface_name] = bond
for numtags in [ 0, 1, 2 ]: for numtags in [ 0, 1, 2 ]:
@ -296,6 +298,7 @@ class VPPApiDumper(VPPApi):
continue continue
loop = {"description": ""} loop = {"description": ""}
loop['mtu'] = iface.mtu[0] loop['mtu'] = iface.mtu[0]
loop['mac'] = str(iface.l2_address)
if iface.sw_if_index in self.cache['lcps']: if iface.sw_if_index in self.cache['lcps']:
loop['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name loop['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name
if iface.sw_if_index in self.cache['interface_addresses']: if iface.sw_if_index in self.cache['interface_addresses']:
@ -315,6 +318,8 @@ class VPPApiDumper(VPPApi):
if not self.cache['interfaces'][idx].flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP if not self.cache['interfaces'][idx].flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP
i['state'] = 'down' i['state'] = 'down'
if iface.interface_dev_type == 'dpdk':
i['mac'] = str(iface.l2_address)
i['mtu'] = iface.mtu[0] i['mtu'] = iface.mtu[0]
if iface.sub_number_of_tags == 0: if iface.sub_number_of_tags == 0:
config['interfaces'][iface.interface_name] = i config['interfaces'][iface.interface_name] = i