diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in
index 92c0911db..e6baed590 100644
--- a/interface-definitions/interfaces_bonding.xml.in
+++ b/interface-definitions/interfaces_bonding.xml.in
@@ -1,289 +1,301 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="interfaces">
     <children>
       <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/interfaces_bonding.py">
         <properties>
           <help>Bonding Interface/Link Aggregation</help>
           <priority>320</priority>
           <constraint>
             <regex>bond[0-9]+</regex>
           </constraint>
           <constraintErrorMessage>Bonding interface must be named bondN</constraintErrorMessage>
           <valueHelp>
             <format>bondN</format>
             <description>Bonding interface name</description>
           </valueHelp>
         </properties>
         <children>
           #include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
           <node name="arp-monitor">
             <properties>
               <help>ARP link monitoring parameters</help>
             </properties>
             <children>
               <leafNode name="interval">
                 <properties>
                   <help>ARP link monitoring interval</help>
                   <valueHelp>
                     <format>u32</format>
                     <description>Specifies the ARP link monitoring frequency in milliseconds</description>
                   </valueHelp>
                   <constraint>
                     <validator name="numeric" argument="--range 0-4294967295"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="target">
                 <properties>
                   <help>IP address used for ARP monitoring</help>
                   <valueHelp>
                     <format>ipv4</format>
                     <description>Specify IPv4 address of ARP requests when interval is enabled</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv4-address"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </node>
           #include <include/generic-description.xml.i>
           #include <include/interface/dhcp-options.xml.i>
           #include <include/interface/dhcpv6-options.xml.i>
           #include <include/interface/disable-link-detect.xml.i>
           #include <include/interface/disable.xml.i>
           #include <include/interface/vrf.xml.i>
           #include <include/interface/mirror.xml.i>
           <node name="evpn">
             <properties>
               <help>EVPN Multihoming</help>
             </properties>
             <children>
               <leafNode name="es-df-pref">
                 <properties>
                   <help>Preference value used for designated forwarder (DF) election</help>
                   <valueHelp>
                     <format>u32:1-65535</format>
                     <description>DF Preference value</description>
                   </valueHelp>
                   <constraint>
                     <validator name="numeric" argument="--range 1-65535"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="es-id">
                 <properties>
                   <help>Ethernet segment identifier</help>
                   <valueHelp>
                     <format>u32:1-16777215</format>
                     <description>Local discriminator</description>
                   </valueHelp>
                   <valueHelp>
                     <format>txt</format>
                     <description>10-byte ID - 00:11:22:33:44:55:AA:BB:CC:DD</description>
                   </valueHelp>
                   <constraint>
                     <validator name="numeric" argument="--range 1-65535"/>
                     <regex>([0-9A-Fa-f][0-9A-Fa-f]:){9}[0-9A-Fa-f][0-9A-Fa-f]</regex>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="es-sys-mac">
                 <properties>
                   <help>Ethernet segment system MAC</help>
                   <valueHelp>
                     <format>macaddr</format>
                     <description>MAC address</description>
                   </valueHelp>
                   <constraint>
                     <validator name="mac-address"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="uplink">
                 <properties>
                   <help>Uplink to the VXLAN core</help>
                   <valueless/>
                 </properties>
               </leafNode>
             </children>
           </node>
           <leafNode name="hash-policy">
             <properties>
               <help>Bonding transmit hash policy</help>
               <completionHelp>
                 <list>layer2 layer2+3 layer3+4 encap2+3 encap3+4</list>
               </completionHelp>
               <valueHelp>
                 <format>layer2</format>
                 <description>use MAC addresses to generate the hash</description>
               </valueHelp>
               <valueHelp>
                 <format>layer2+3</format>
                 <description>combine MAC address and IP address to make hash</description>
               </valueHelp>
               <valueHelp>
                 <format>layer3+4</format>
                 <description>combine IP address and port to make hash</description>
               </valueHelp>
               <valueHelp>
                 <format>encap2+3</format>
                 <description>combine encapsulated MAC address and IP address to make hash</description>
               </valueHelp>
               <valueHelp>
                 <format>encap3+4</format>
                 <description>combine encapsulated IP address and port to make hash</description>
               </valueHelp>
               <constraint>
                 <regex>(layer2\+3|layer3\+4|layer2|encap2\+3|encap3\+4)</regex>
               </constraint>
               <constraintErrorMessage>hash-policy must be layer2 layer2+3 layer3+4 encap2+3 or encap3+4</constraintErrorMessage>
             </properties>
             <defaultValue>layer2</defaultValue>
           </leafNode>
           #include <include/interface/ipv4-options.xml.i>
           #include <include/interface/ipv6-options.xml.i>
           #include <include/interface/mac.xml.i>
           <leafNode name="mii-mon-interval">
             <properties>
               <help>Specifies the MII link monitoring frequency in milliseconds</help>
               <valueHelp>
                 <format>u32:0</format>
                 <description>Disable MII link monitoring</description>
               </valueHelp>
               <valueHelp>
                 <format>u32:50-1000</format>
                 <description>MII link monitoring frequency in milliseconds</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 0-0 --range 50-1000"/>
               </constraint>
             </properties>
             <defaultValue>100</defaultValue>
           </leafNode>
           <leafNode name="min-links">
             <properties>
               <help>Minimum number of member interfaces required up before enabling bond</help>
               <valueHelp>
                 <format>u32:0-16</format>
                 <description>Minimum number of member interfaces required up before enabling bond</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 0-16"/>
               </constraint>
             </properties>
             <defaultValue>0</defaultValue>
           </leafNode>
+          <leafNode name="system-mac">
+            <properties>
+              <help>System MAC address for 802.3ad</help>
+              <valueHelp>
+                <format>macaddr</format>
+                <description>MAC address</description>
+              </valueHelp>
+              <constraint>
+                <validator name="mac-address"/>
+              </constraint>
+            </properties>
+          </leafNode>
           <leafNode name="lacp-rate">
             <properties>
               <help>Rate in which we will ask our link partner to transmit LACPDU packets</help>
               <completionHelp>
                 <list>slow fast</list>
               </completionHelp>
               <valueHelp>
                 <format>slow</format>
                 <description>Request partner to transmit LACPDUs every 30 seconds</description>
               </valueHelp>
               <valueHelp>
                 <format>fast</format>
                 <description>Request partner to transmit LACPDUs every 1 second</description>
               </valueHelp>
               <constraint>
                 <regex>(slow|fast)</regex>
               </constraint>
             </properties>
             <defaultValue>slow</defaultValue>
           </leafNode>
           <leafNode name="mode">
             <properties>
               <help>Bonding mode</help>
               <completionHelp>
                 <list>802.3ad active-backup broadcast round-robin transmit-load-balance adaptive-load-balance xor-hash</list>
               </completionHelp>
               <valueHelp>
                 <format>802.3ad</format>
                 <description>IEEE 802.3ad Dynamic link aggregation</description>
               </valueHelp>
               <valueHelp>
                 <format>active-backup</format>
                 <description>Fault tolerant: only one slave in the bond is active</description>
               </valueHelp>
               <valueHelp>
                 <format>broadcast</format>
                 <description>Fault tolerant: transmits everything on all slave interfaces</description>
               </valueHelp>
               <valueHelp>
                 <format>round-robin</format>
                 <description>Load balance: transmit packets in sequential order</description>
               </valueHelp>
               <valueHelp>
                 <format>transmit-load-balance</format>
                 <description>Load balance: adapts based on transmit load and speed</description>
               </valueHelp>
               <valueHelp>
                 <format>adaptive-load-balance</format>
                 <description>Load balance: adapts based on transmit and receive plus ARP</description>
               </valueHelp>
               <valueHelp>
                 <format>xor-hash</format>
                 <description>Distribute based on MAC address</description>
               </valueHelp>
               <constraint>
                 <regex>(802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash)</regex>
               </constraint>
               <constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor</constraintErrorMessage>
             </properties>
             <defaultValue>802.3ad</defaultValue>
           </leafNode>
           <node name="member">
             <properties>
               <help>Bridge member interfaces</help>
             </properties>
             <children>
               <leafNode name="interface">
                 <properties>
                   <help>Member interface name</help>
                   <completionHelp>
                     <script>${vyos_completion_dir}/list_interfaces --bondable</script>
                   </completionHelp>
                   <valueHelp>
                     <format>txt</format>
                     <description>Interface name</description>
                   </valueHelp>
                   <constraint>
                     #include <include/constraint/interface-name.xml.i>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </node>
           #include <include/interface/mtu-68-16000.xml.i>
           <leafNode name="mtu">
             <defaultValue>1500</defaultValue>
           </leafNode>
           <leafNode name="primary">
             <properties>
               <help>Primary device interface</help>
               <completionHelp>
                 <script>${vyos_completion_dir}/list_interfaces --bondable</script>
               </completionHelp>
               <valueHelp>
                 <format>txt</format>
                 <description>Interface name</description>
               </valueHelp>
               <constraint>
                 #include <include/constraint/interface-name.xml.i>
               </constraint>
             </properties>
           </leafNode>
           #include <include/interface/redirect.xml.i>
           #include <include/interface/vif-s.xml.i>
           #include <include/interface/vif.xml.i>
         </children>
       </tagNode>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index c6d0f1cff..b8ea90049 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -1,476 +1,506 @@
 # Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from vyos.ifconfig.interface import Interface
 from vyos.utils.dict import dict_search
 from vyos.utils.assertion import assert_list
+from vyos.utils.assertion import assert_mac
 from vyos.utils.assertion import assert_positive
 
 @Interface.register
 class BondIf(Interface):
     """
     The Linux bonding driver provides a method for aggregating multiple network
     interfaces into a single logical "bonded" interface. The behavior of the
     bonded interfaces depends upon the mode; generally speaking, modes provide
     either hot standby or load balancing services. Additionally, link integrity
     monitoring may be performed.
     """
 
     iftype = 'bond'
     definition = {
         **Interface.definition,
         ** {
             'section': 'bonding',
             'prefixes': ['bond', ],
             'broadcast': True,
             'bridgeable': True,
         },
     }
 
     _sysfs_set = {**Interface._sysfs_set, **{
         'bond_hash_policy': {
             'validate': lambda v: assert_list(v, ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']),
             'location': '/sys/class/net/{ifname}/bonding/xmit_hash_policy',
         },
         'bond_min_links': {
             'validate': assert_positive,
             'location': '/sys/class/net/{ifname}/bonding/min_links',
         },
         'bond_lacp_rate': {
             'validate': lambda v: assert_list(v, ['slow', 'fast']),
             'location': '/sys/class/net/{ifname}/bonding/lacp_rate',
         },
+        'bond_system_mac': {
+            'validate': lambda v: assert_mac(v, test_all_zero=False),
+            'location': '/sys/class/net/{ifname}/bonding/ad_actor_system',
+        },
         'bond_miimon': {
             'validate': assert_positive,
             'location': '/sys/class/net/{ifname}/bonding/miimon'
         },
         'bond_arp_interval': {
             'validate': assert_positive,
             'location': '/sys/class/net/{ifname}/bonding/arp_interval'
         },
         'bond_arp_ip_target': {
             # XXX: no validation of the IP
             'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
         },
         'bond_add_port': {
             'location': '/sys/class/net/{ifname}/bonding/slaves',
         },
         'bond_del_port': {
             'location': '/sys/class/net/{ifname}/bonding/slaves',
         },
         'bond_primary': {
             'convert': lambda name: name if name else '\0',
             'location': '/sys/class/net/{ifname}/bonding/primary',
         },
         'bond_mode': {
             'validate': lambda v: assert_list(v, ['balance-rr', 'active-backup', 'balance-xor', 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb']),
             'location': '/sys/class/net/{ifname}/bonding/mode',
         },
     }}
 
     _sysfs_get = {**Interface._sysfs_get, **{
         'bond_arp_ip_target': {
             'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
         },
         'bond_mode': {
             'location': '/sys/class/net/{ifname}/bonding/mode',
         }
     }}
 
     @staticmethod
     def get_inherit_bond_options() -> list:
         """
         Returns list of option
         which are inherited from bond interface to member interfaces
         :return: List of interface options
         :rtype: list
         """
         options = [
             'mtu'
         ]
         return options
 
     def remove(self):
         """
         Remove interface from operating system. Removing the interface
         deconfigures all assigned IP addresses and clear possible DHCP(v6)
         client processes.
         Example:
         >>> from vyos.ifconfig import Interface
         >>> i = Interface('eth0')
         >>> i.remove()
         """
         # when a bond member gets deleted, all members are placed in A/D state
         # even when they are enabled inside CLI. This will make the config
         # and system look async.
         slave_list = []
         for s in self.get_slaves():
             slave = {
                 'ifname': s,
                 'state': Interface(s).get_admin_state()
             }
             slave_list.append(slave)
 
         # remove bond master which places members in disabled state
         super().remove()
 
         # replicate previous interface state before bond destruction back to
         # physical interface
         for slave in slave_list:
              i = Interface(slave['ifname'])
              i.set_admin_state(slave['state'])
 
     def set_hash_policy(self, mode):
         """
         Selects the transmit hash policy to use for slave selection in
         balance-xor, 802.3ad, and tlb modes. Possible values are: layer2,
         layer2+3, layer3+4, encap2+3, encap3+4.
 
         The default value is layer2
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_hash_policy('layer2+3')
         """
         self.set_interface('bond_hash_policy', mode)
 
     def set_min_links(self, number):
         """
         Specifies the minimum number of links that must be active before
 	    asserting carrier. It is similar to the Cisco EtherChannel min-links
 	    feature. This allows setting the minimum number of member ports that
 	    must be up (link-up state) before marking the bond device as up
 	    (carrier on). This is useful for situations where higher level services
 	    such as clustering want to ensure a minimum number of low bandwidth
 	    links are active before switchover. This option only affect 802.3ad
 	    mode.
 
 	    The default value is 0. This will cause carrier to be asserted (for
 	    802.3ad mode) whenever there is an active aggregator, regardless of the
 	    number of available links in that aggregator. Note that, because an
 	    aggregator cannot be active without at least one available link,
 	    setting this option to 0 or to 1 has the exact same effect.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_min_links('0')
         """
         self.set_interface('bond_min_links', number)
 
     def set_lacp_rate(self, slow_fast):
         """
         Option specifying the rate in which we'll ask our link partner
 	    to transmit LACPDU packets in 802.3ad mode.  Possible values
 	    are:
 
 	    slow or 0
 		    Request partner to transmit LACPDUs every 30 seconds
 
 	    fast or 1
 		    Request partner to transmit LACPDUs every 1 second
 
 	    The default is slow.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_lacp_rate('slow')
         """
         self.set_interface('bond_lacp_rate', slow_fast)
 
     def set_miimon_interval(self, interval):
         """
         Specifies the MII link monitoring frequency in milliseconds. This
         determines how often the link state of each slave is inspected for link
         failures. A value of zero disables MII link monitoring. A value of 100
         is a good starting point.
 
         The default value is 0.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_miimon_interval('100')
         """
         return self.set_interface('bond_miimon', interval)
 
     def set_arp_interval(self, interval):
         """
         Specifies the ARP link monitoring frequency in milliseconds.
 
         The ARP monitor works by periodically checking the slave devices
         to determine whether they have sent or received traffic recently
         (the precise criteria depends upon the bonding mode, and the
         state of the slave). Regular traffic is generated via ARP probes
         issued for the addresses specified by the arp_ip_target option.
 
         If ARP monitoring is used in an etherchannel compatible mode
         (modes 0 and 2), the switch should be configured in a mode that
         evenly distributes packets across all links. If the switch is
         configured to distribute the packets in an XOR fashion, all
         replies from the ARP targets will be received on the same link
         which could cause the other team members to fail.
 
         value of 0 disables ARP monitoring. The default value is 0.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_arp_interval('100')
         """
         return self.set_interface('bond_arp_interval', interval)
 
     def get_arp_ip_target(self):
         """
         Specifies the IP addresses to use as ARP monitoring peers when
         arp_interval is > 0. These are the targets of the ARP request sent to
         determine the health of the link to the targets. Specify these values
         in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by
         a comma. At least one IP address must be given for ARP monitoring to
         function. The maximum number of targets that can be specified is 16.
 
         The default value is no IP addresses.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').get_arp_ip_target()
         '192.0.2.1'
         """
         # As this function might also be called from update() of a VLAN interface
         # we must check if the bond_arp_ip_target retrieval worked or not - as this
         # can not be set for a bond vif interface
         try:
             return self.get_interface('bond_arp_ip_target')
         except FileNotFoundError:
             return ''
 
     def set_arp_ip_target(self, target):
         """
         Specifies the IP addresses to use as ARP monitoring peers when
         arp_interval is > 0. These are the targets of the ARP request sent to
         determine the health of the link to the targets. Specify these values
         in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by
         a comma. At least one IP address must be given for ARP monitoring to
         function. The maximum number of targets that can be specified is 16.
 
         The default value is no IP addresses.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_arp_ip_target('192.0.2.1')
         >>> BondIf('bond0').get_arp_ip_target()
         '192.0.2.1'
         """
         return self.set_interface('bond_arp_ip_target', target)
 
     def add_port(self, interface):
         """
         Enslave physical interface to bond.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').add_port('eth0')
         >>> BondIf('bond0').add_port('eth1')
         """
 
         # From drivers/net/bonding/bond_main.c:
         # ...
         # bond_set_slave_link_state(new_slave,
         #      BOND_LINK_UP,
         #      BOND_SLAVE_NOTIFY_NOW);
         # ...
         #
         # The kernel will ALWAYS place new bond members in "up" state regardless
         # what the CLI will tell us!
 
         # Physical interface must be in admin down state before they can be
         # enslaved. If this is not the case an error will be shown:
         # bond0: eth0 is up - this may be due to an out of date ifenslave
         slave = Interface(interface)
         slave_state = slave.get_admin_state()
         if slave_state == 'up':
             slave.set_admin_state('down')
 
         ret = self.set_interface('bond_add_port', f'+{interface}')
         # The kernel will ALWAYS place new bond members in "up" state regardless
         # what the LI is configured for - thus we place the interface in its
         # desired state
         slave.set_admin_state(slave_state)
         return ret
 
     def del_port(self, interface):
         """
         Remove physical port from bond
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').del_port('eth1')
         """
         return self.set_interface('bond_del_port', f'-{interface}')
 
     def get_slaves(self):
         """
         Return a list with all configured slave interfaces on this bond.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').get_slaves()
         ['eth1', 'eth2']
         """
         enslaved_ifs = []
         # retrieve real enslaved interfaces from OS kernel
         sysfs_bond = '/sys/class/net/{}'.format(self.config['ifname'])
         if os.path.isdir(sysfs_bond):
             for directory in os.listdir(sysfs_bond):
                 if 'lower_' in directory:
                     enslaved_ifs.append(directory.replace('lower_', ''))
 
         return enslaved_ifs
 
     def get_mode(self):
         """
         Return bond operation mode.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').get_mode()
         '802.3ad'
         """
         mode = self.get_interface('bond_mode')
         # mode is now "802.3ad 4", we are only interested in "802.3ad"
         return mode.split()[0]
 
     def set_primary(self, interface):
         """
         A string (eth0, eth2, etc) specifying which slave is the primary
         device. The specified device will always be the active slave while it
         is available. Only when the primary is off-line will alternate devices
         be used. This is useful when one slave is preferred over another, e.g.,
         when one slave has higher throughput than another.
 
         The primary option is only valid for active-backup, balance-tlb and
         balance-alb mode.
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_primary('eth2')
         """
         return self.set_interface('bond_primary', interface)
 
     def set_mode(self, mode):
         """
         Specifies one of the bonding policies. The default is balance-rr
         (round robin).
 
         Possible values are: balance-rr, active-backup, balance-xor,
         broadcast, 802.3ad, balance-tlb, balance-alb
 
         NOTE: the bonding mode can not be changed when the bond itself has
         slaves
 
         Example:
         >>> from vyos.ifconfig import BondIf
         >>> BondIf('bond0').set_mode('802.3ad')
         """
         return self.set_interface('bond_mode', mode)
 
+    def set_system_mac(self, mac):
+        """
+        In an AD system, this specifies the mac-address for the actor in
+	    protocol packet exchanges (LACPDUs). The value cannot be NULL or
+	    multicast. It is preferred to have the local-admin bit set for this
+	    mac but driver does not enforce it. If the value is not given then
+	    system defaults to using the masters' mac address as actors' system
+	    address.
+
+	    This parameter has effect only in 802.3ad mode and is available through
+	    SysFs interface.
+
+        Example:
+        >>> from vyos.ifconfig import BondIf
+        >>> BondIf('bond0').set_system_mac('00:50:ab:cd:ef:01')
+        """
+        return self.set_interface('bond_system_mac', mac)
+
     def update(self, config):
         """ General helper function which works on a dictionary retrived by
         get_config_dict(). It's main intention is to consolidate the scattered
         interface setup code and provide a single point of entry when workin
         on any interface. """
 
         # use ref-counting function to place an interface into admin down state.
         # set_admin_state_up() must be called the same amount of times else the
         # interface won't come up. This can/should be used to prevent link flapping
         # when changing interface parameters require the interface to be down.
         # We will disable it once before reconfiguration and enable it afterwards.
         if 'shutdown_required' in config:
             self.set_admin_state('down')
 
         # Specifies the MII link monitoring frequency in milliseconds
         value = config.get('mii_mon_interval')
         self.set_miimon_interval(value)
 
         # Bonding transmit hash policy
         value = config.get('hash_policy')
         if value: self.set_hash_policy(value)
 
         # Minimum number of member interfaces
         value = config.get('min_links')
         if value: self.set_min_links(value)
 
         # Some interface options can only be changed if the interface is
         # administratively down
         if self.get_admin_state() == 'down':
             # Remove ALL bond member interfaces
             for interface in self.get_slaves():
                 self.del_port(interface)
 
                 # Restore correct interface status based on config
                 if dict_search(f'member.interface.{interface}.disable', config) is not None or \
                    dict_search(f'member.interface_remove.{interface}.disable', config) is not None:
                     Interface(interface).set_admin_state('down')
                 else:
                     Interface(interface).set_admin_state('up')
 
             # Bonding policy/mode - default value, always present
-            mode = config.get('mode')
-            self.set_mode(mode)
+            self.set_mode(config['mode'])
 
             # LACPDU transmission rate - default value
-            if mode == '802.3ad':
+            if config['mode'] == '802.3ad':
                 self.set_lacp_rate(config.get('lacp_rate'))
 
-            if mode not in ['802.3ad', 'balance-tlb', 'balance-alb']:
+            if config['mode'] not in ['802.3ad', 'balance-tlb', 'balance-alb']:
                 tmp = dict_search('arp_monitor.interval', config)
                 value = tmp if (tmp != None) else '0'
                 self.set_arp_interval(value)
 
                 # ARP monitor targets need to be synchronized between sysfs and CLI.
                 # Unfortunately an address can't be send twice to sysfs as this will
                 # result in the following exception:  OSError: [Errno 22] Invalid argument.
                 #
                 # We remove ALL addresses prior to adding new ones, this will remove
                 # addresses manually added by the user too - but as we are limited to 16 adresses
                 # from the kernel side this looks valid to me. We won't run into an error
                 # when a user added manual adresses which would result in having more
                 # then 16 adresses in total.
                 arp_tgt_addr = list(map(str, self.get_arp_ip_target().split()))
                 for addr in arp_tgt_addr:
                     self.set_arp_ip_target('-' + addr)
 
                 # Add configured ARP target addresses
                 value = dict_search('arp_monitor.target', config)
                 if isinstance(value, str):
                     value = [value]
                 if value:
                     for addr in value:
                         self.set_arp_ip_target('+' + addr)
 
             # Add (enslave) interfaces to bond
             value = dict_search('member.interface', config)
             for interface in (value or []):
                 # if we've come here we already verified the interface
                 # does not have an addresses configured so just flush
                 # any remaining ones
                 Interface(interface).flush_addrs()
                 self.add_port(interface)
 
+        # Add system mac address for 802.3ad - default address is all zero
+        # mode is always present (defaultValue)
+        if config['mode'] == '802.3ad':
+            mac = '00:00:00:00:00:00'
+            if 'system_mac' in config:
+                mac = config['system_mac']
+            self.set_system_mac(mac)
+
         # Primary device interface - must be set after 'mode'
         value = config.get('primary')
         if value: self.set_primary(value)
 
         # call base class first
         super().update(config)
diff --git a/python/vyos/utils/assertion.py b/python/vyos/utils/assertion.py
index 1aaa54dff..c7fa220c3 100644
--- a/python/vyos/utils/assertion.py
+++ b/python/vyos/utils/assertion.py
@@ -1,81 +1,81 @@
 # Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 def assert_boolean(b):
     if int(b) not in (0, 1):
         raise ValueError(f'Value {b} out of range')
 
 def assert_range(value, lower=0, count=3):
     if int(value, 16) not in range(lower, lower+count):
         raise ValueError("Value out of range")
 
 def assert_list(s, l):
     if s not in l:
         o = ' or '.join([f'"{n}"' for n in l])
         raise ValueError(f'state must be {o}, got {s}')
 
 def assert_number(n):
     if not str(n).isnumeric():
         raise ValueError(f'{n} must be a number')
 
 def assert_positive(n, smaller=0):
     assert_number(n)
     if int(n) < smaller:
         raise ValueError(f'{n} is smaller than {smaller}')
 
 def assert_mtu(mtu, ifname):
     assert_number(mtu)
 
     import json
     from vyos.utils.process import cmd
     out = cmd(f'ip -j -d link show dev {ifname}')
     # [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"pfifo_fast","operstate":"UP","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"ether","address":"08:00:27:d9:5b:04","broadcast":"ff:ff:ff:ff:ff:ff","promiscuity":0,"min_mtu":46,"max_mtu":16110,"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535}]
     parsed = json.loads(out)[0]
     min_mtu = int(parsed.get('min_mtu', '0'))
     # cur_mtu = parsed.get('mtu',0),
     max_mtu = int(parsed.get('max_mtu', '0'))
     cur_mtu = int(mtu)
 
     if (min_mtu and cur_mtu < min_mtu) or cur_mtu < 68:
         raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} < {min_mtu}')
     if (max_mtu and cur_mtu > max_mtu) or cur_mtu > 65536:
         raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} > {max_mtu}')
 
-def assert_mac(m):
+def assert_mac(m, test_all_zero=True):
     split = m.split(':')
     size = len(split)
 
     # a mac address consits out of 6 octets
     if size != 6:
         raise ValueError(f'wrong number of MAC octets ({size}): {m}')
 
     octets = []
     try:
         for octet in split:
             octets.append(int(octet, 16))
     except ValueError:
         raise ValueError(f'invalid hex number "{octet}" in : {m}')
 
     # validate against the first mac address byte if it's a multicast
     # address
     if octets[0] & 1:
         raise ValueError(f'{m} is a multicast MAC address')
 
     # overall mac address is not allowed to be 00:00:00:00:00:00
-    if sum(octets) == 0:
+    if test_all_zero and sum(octets) == 0:
         raise ValueError('00:00:00:00:00:00 is not a valid MAC address')
 
     if octets[:5] == (0, 0, 94, 0, 1):
         raise ValueError(f'{m} is a VRRP MAC address')
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index 419de774a..f436424b8 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -1,285 +1,313 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 import unittest
 
 from base_interfaces_test import BasicInterfaceTest
 
 from vyos.ifconfig import Section
 from vyos.ifconfig.interface import Interface
 from vyos.configsession import ConfigSessionError
 from vyos.utils.network import get_interface_config
 from vyos.utils.file import read_file
 
 class BondingInterfaceTest(BasicInterfaceTest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls._base_path = ['interfaces', 'bonding']
         cls._mirror_interfaces = ['dum21354']
         cls._members = []
 
         # we need to filter out VLAN interfaces identified by a dot (.)
         # in their name - just in case!
         if 'TEST_ETH' in os.environ:
             cls._members = os.environ['TEST_ETH'].split()
         else:
             for tmp in Section.interfaces('ethernet', vlan=False):
                 cls._members.append(tmp)
 
         cls._options = {'bond0' : []}
         for member in cls._members:
             cls._options['bond0'].append(f'member interface {member}')
         cls._interfaces = list(cls._options)
 
         # call base-classes classmethod
         super(BondingInterfaceTest, cls).setUpClass()
 
     def test_add_single_ip_address(self):
         super().test_add_single_ip_address()
 
         for interface in self._interfaces:
             slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split()
             self.assertListEqual(slaves, self._members)
 
     def test_vif_8021q_interfaces(self):
         super().test_vif_8021q_interfaces()
 
         for interface in self._interfaces:
             slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split()
             self.assertListEqual(slaves, self._members)
 
     def test_bonding_remove_member(self):
         # T2515: when removing a bond member the previously enslaved/member
         # interface must be in its former admin-up/down state. Here we ensure
         # that it is admin-up as it was admin-up before.
 
         # configure member interfaces
         for interface in self._interfaces:
             for option in self._options.get(interface, []):
                 self.cli_set(self._base_path + [interface] + option.split())
 
         self.cli_commit()
 
         # remove single bond member port
         for interface in self._interfaces:
             remove_member = self._members[0]
             self.cli_delete(self._base_path + [interface, 'member', 'interface', remove_member])
 
         self.cli_commit()
 
         # removed member port must be admin-up
         for interface in self._interfaces:
             remove_member = self._members[0]
             state = Interface(remove_member).get_admin_state()
             self.assertEqual('up', state)
 
     def test_bonding_min_links(self):
         # configure member interfaces
         min_links = len(self._interfaces)
         for interface in self._interfaces:
             for option in self._options.get(interface, []):
                 self.cli_set(self._base_path + [interface] + option.split())
 
             self.cli_set(self._base_path + [interface, 'min-links', str(min_links)])
 
         self.cli_commit()
 
         # verify config
         for interface in self._interfaces:
             tmp = get_interface_config(interface)
 
             self.assertEqual(min_links, tmp['linkinfo']['info_data']['min_links'])
             # check LACP default rate
             self.assertEqual('slow',    tmp['linkinfo']['info_data']['ad_lacp_rate'])
 
     def test_bonding_lacp_rate(self):
         # configure member interfaces
         lacp_rate = 'fast'
         for interface in self._interfaces:
             for option in self._options.get(interface, []):
                 self.cli_set(self._base_path + [interface] + option.split())
 
             self.cli_set(self._base_path + [interface, 'lacp-rate', lacp_rate])
 
         self.cli_commit()
 
         # verify config
         for interface in self._interfaces:
             tmp = get_interface_config(interface)
 
             # check LACP minimum links (default value)
             self.assertEqual(0,         tmp['linkinfo']['info_data']['min_links'])
             self.assertEqual(lacp_rate, tmp['linkinfo']['info_data']['ad_lacp_rate'])
 
     def test_bonding_hash_policy(self):
         # Define available bonding hash policies
         hash_policies = ['layer2', 'layer2+3', 'encap2+3', 'encap3+4']
         for hash_policy in hash_policies:
             for interface in self._interfaces:
                 for option in self._options.get(interface, []):
                     self.cli_set(self._base_path + [interface] + option.split())
 
                 self.cli_set(self._base_path + [interface, 'hash-policy', hash_policy])
 
             self.cli_commit()
 
             # verify config
             for interface in self._interfaces:
                 defined_policy = read_file(f'/sys/class/net/{interface}/bonding/xmit_hash_policy').split()
                 self.assertEqual(defined_policy[0], hash_policy)
 
     def test_bonding_mii_monitoring_interval(self):
         for interface in self._interfaces:
             for option in self._options.get(interface, []):
                 self.cli_set(self._base_path + [interface] + option.split())
 
         self.cli_commit()
 
         # verify default
         for interface in self._interfaces:
             tmp = read_file(f'/sys/class/net/{interface}/bonding/miimon').split()
             self.assertIn('100', tmp)
 
         mii_mon = '250'
         for interface in self._interfaces:
             self.cli_set(self._base_path + [interface, 'mii-mon-interval', mii_mon])
 
         self.cli_commit()
 
         # verify new CLI value
         for interface in self._interfaces:
             tmp = read_file(f'/sys/class/net/{interface}/bonding/miimon').split()
             self.assertIn(mii_mon, tmp)
 
     def test_bonding_multi_use_member(self):
         # Define available bonding hash policies
         for interface in ['bond10', 'bond20']:
             for member in self._members:
                 self.cli_set(self._base_path + [interface, 'member', 'interface', member])
 
         # check validate() - can not use the same member interfaces multiple times
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_delete(self._base_path + ['bond20'])
 
         self.cli_commit()
 
     def test_bonding_source_interface(self):
         # Re-use member interface that is already a source-interface
         bond = 'bond99'
         pppoe = 'pppoe98756'
         member = next(iter(self._members))
 
         self.cli_set(self._base_path + [bond, 'member', 'interface', member])
         self.cli_set(['interfaces', 'pppoe', pppoe, 'source-interface', member])
 
         # check validate() - can not add interface to bond, it is the source-interface of ...
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_delete(['interfaces', 'pppoe', pppoe])
         self.cli_commit()
 
         # verify config
         slaves = read_file(f'/sys/class/net/{bond}/bonding/slaves').split()
         self.assertIn(member, slaves)
 
     def test_bonding_source_bridge_interface(self):
         # Re-use member interface that is already a source-interface
         bond = 'bond1097'
         bridge = 'br6327'
         member = next(iter(self._members))
 
         self.cli_set(self._base_path + [bond, 'member', 'interface', member])
         self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', member])
 
         # check validate() - can not add interface to bond, it is a member of bridge ...
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_delete(['interfaces', 'bridge', bridge])
         self.cli_commit()
 
         # verify config
         slaves = read_file(f'/sys/class/net/{bond}/bonding/slaves').split()
         self.assertIn(member, slaves)
 
     def test_bonding_uniq_member_description(self):
         ethernet_path = ['interfaces', 'ethernet']
         for interface in self._interfaces:
             for option in self._options.get(interface, []):
                 self.cli_set(self._base_path + [interface] + option.split())
 
             self.cli_commit()
 
         # Add any changes on bonding members
         # For example add description on separate ethX interfaces
         for interface in self._interfaces:
             for member in self._members:
                 self.cli_set(ethernet_path + [member, 'description', member + '_interface'])
 
             self.cli_commit()
 
         # verify config
         for interface in self._interfaces:
             slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split()
             for member in self._members:
                 self.assertIn(member, slaves)
 
+    def test_bonding_system_mac(self):
+        # configure member interfaces and system-mac
+        default_system_mac = '00:00:00:00:00:00' # default MAC is all zeroes
+        system_mac = '00:50:ab:cd:ef:11'
+
+        for interface in self._interfaces:
+            for option in self._options.get(interface, []):
+                self.cli_set(self._base_path + [interface] + option.split())
+
+            self.cli_set(self._base_path + [interface, 'system-mac', system_mac])
+
+        self.cli_commit()
+
+        # verify config
+        for interface in self._interfaces:
+            tmp = read_file(f'/sys/class/net/{interface}/bonding/ad_actor_system')
+            self.assertIn(tmp, system_mac)
+
+        for interface in self._interfaces:
+            self.cli_delete(self._base_path + [interface, 'system-mac'])
+
+        self.cli_commit()
+
+        # verify default value
+        for interface in self._interfaces:
+            tmp = read_file(f'/sys/class/net/{interface}/bonding/ad_actor_system')
+            self.assertIn(tmp, default_system_mac)
+
     def test_bonding_evpn_multihoming(self):
         id = '5'
         for interface in self._interfaces:
             for option in self._options.get(interface, []):
                 self.cli_set(self._base_path + [interface] + option.split())
 
             self.cli_set(self._base_path + [interface, 'evpn', 'es-id', id])
             self.cli_set(self._base_path + [interface, 'evpn', 'es-df-pref', id])
             self.cli_set(self._base_path + [interface, 'evpn', 'es-sys-mac', f'00:12:34:56:78:0{id}'])
             self.cli_set(self._base_path + [interface, 'evpn', 'uplink'])
 
             id = int(id) + 1
 
         self.cli_commit()
 
         id = '5'
         for interface in self._interfaces:
             frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra')
 
             self.assertIn(f' evpn mh es-id {id}', frrconfig)
             self.assertIn(f' evpn mh es-df-pref {id}', frrconfig)
             self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig)
             self.assertIn(f' evpn mh uplink', frrconfig)
 
             id = int(id) + 1
 
         for interface in self._interfaces:
             self.cli_delete(self._base_path + [interface, 'evpn', 'es-id'])
             self.cli_delete(self._base_path + [interface, 'evpn', 'es-df-pref'])
 
         self.cli_commit()
 
         id = '5'
         for interface in self._interfaces:
             frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra')
             self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig)
             self.assertIn(f' evpn mh uplink', frrconfig)
 
             id = int(id) + 1
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 371b219c0..5e5d5fba1 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -1,292 +1,303 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2019-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sys import exit
 
 from vyos.config import Config
 from vyos.configdict import get_interface_dict
 from vyos.configdict import is_node_changed
 from vyos.configdict import leaf_node_changed
 from vyos.configdict import is_member
 from vyos.configdict import is_source_interface
 from vyos.configverify import verify_address
 from vyos.configverify import verify_bridge_delete
 from vyos.configverify import verify_dhcpv6
 from vyos.configverify import verify_mirror_redirect
 from vyos.configverify import verify_mtu_ipv6
 from vyos.configverify import verify_vlan_config
 from vyos.configverify import verify_vrf
 from vyos.ifconfig import BondIf
 from vyos.ifconfig.ethernet import EthernetIf
 from vyos.ifconfig import Section
 from vyos.template import render_to_string
+from vyos.utils.assertion import assert_mac
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_to_paths_values
 from vyos.utils.network import interface_exists
 from vyos.configdict import has_address_configured
 from vyos.configdict import has_vrf_configured
 from vyos.configdep import set_dependents, call_dependents
 from vyos import ConfigError
 from vyos import frr
 from vyos import airbag
 airbag.enable()
 
 def get_bond_mode(mode):
     if mode == 'round-robin':
         return 'balance-rr'
     elif mode == 'active-backup':
         return 'active-backup'
     elif mode == 'xor-hash':
         return 'balance-xor'
     elif mode == 'broadcast':
         return 'broadcast'
     elif mode == '802.3ad':
         return '802.3ad'
     elif mode == 'transmit-load-balance':
         return 'balance-tlb'
     elif mode == 'adaptive-load-balance':
         return 'balance-alb'
     else:
         raise ConfigError(f'invalid bond mode "{mode}"')
 
 def get_config(config=None):
     """
     Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
     interface name will be added or a deleted flag
     """
     if config:
         conf = config
     else:
         conf = Config()
     base = ['interfaces', 'bonding']
     ifname, bond = get_interface_dict(conf, base)
 
     # To make our own life easier transfor the list of member interfaces
     # into a dictionary - we will use this to add additional information
     # later on for each member
     if 'member' in bond and 'interface' in bond['member']:
         # convert list of member interfaces to a dictionary
         bond['member']['interface'] = {k: {} for k in bond['member']['interface']}
 
     if 'mode' in bond:
         bond['mode'] = get_bond_mode(bond['mode'])
 
     tmp = is_node_changed(conf, base + [ifname, 'mode'])
     if tmp: bond['shutdown_required'] = {}
 
     tmp = is_node_changed(conf, base + [ifname, 'lacp-rate'])
     if tmp: bond['shutdown_required'] = {}
 
     # determine which members have been removed
     interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
     # Reset config level to interfaces
     old_level = conf.get_level()
     conf.set_level(['interfaces'])
 
     if interfaces_removed:
         bond['shutdown_required'] = {}
         if 'member' not in bond:
             bond['member'] = {}
 
         tmp = {}
         for interface in interfaces_removed:
             # if member is deleted from bond, add dependencies to call
             # ethernet commit again in apply function
             # to apply options under ethernet section
             set_dependents('ethernet', conf, interface)
             section = Section.section(interface) # this will be 'ethernet' for 'eth0'
             if conf.exists([section, interface, 'disable']):
                 tmp[interface] = {'disable': ''}
             else:
                 tmp[interface] = {}
 
         # also present the interfaces to be removed from the bond as dictionary
         bond['member']['interface_remove'] = tmp
 
     # Restore existing config level
     conf.set_level(old_level)
 
     if dict_search('member.interface', bond):
         for interface, interface_config in bond['member']['interface'].items():
 
             interface_ethernet_config = conf.get_config_dict(
                 ['interfaces', 'ethernet', interface],
                 key_mangling=('-', '_'),
                 get_first_key=True,
                 no_tag_node_value_mangle=True,
                 with_defaults=False,
                 with_recursive_defaults=False)
 
             interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config)
 
             # Check if member interface is a new member
             if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]):
                 bond['shutdown_required'] = {}
                 interface_config['new_added'] = {}
 
             # Check if member interface is disabled
             conf.set_level(['interfaces'])
 
             section = Section.section(interface) # this will be 'ethernet' for 'eth0'
             if conf.exists([section, interface, 'disable']):
                 interface_config['disable'] = ''
 
             conf.set_level(old_level)
 
             # Check if member interface is already member of another bridge
             tmp = is_member(conf, interface, 'bridge')
             if tmp: interface_config['is_bridge_member'] = tmp
 
             # Check if member interface is already member of a bond
             tmp = is_member(conf, interface, 'bonding')
             for tmp in is_member(conf, interface, 'bonding'):
                 if bond['ifname'] == tmp:
                     continue
                 interface_config['is_bond_member'] = tmp
 
             # Check if member interface is used as source-interface on another interface
             tmp = is_source_interface(conf, interface)
             if tmp: interface_config['is_source_interface'] = tmp
 
             # bond members must not have an assigned address
             tmp = has_address_configured(conf, interface)
             if tmp: interface_config['has_address'] = {}
 
             # bond members must not have a VRF attached
             tmp = has_vrf_configured(conf, interface)
             if tmp: interface_config['has_vrf'] = {}
     return bond
 
 
 def verify(bond):
     if 'deleted' in bond:
         verify_bridge_delete(bond)
         return None
 
     if 'arp_monitor' in bond:
         if 'target' in bond['arp_monitor'] and len(bond['arp_monitor']['target']) > 16:
             raise ConfigError('The maximum number of arp-monitor targets is 16')
 
         if 'interval' in bond['arp_monitor'] and int(bond['arp_monitor']['interval']) > 0:
             if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']:
                 raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \
                                   'transmit-load-balance or adaptive-load-balance')
 
     if 'primary' in bond:
         if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']:
             raise ConfigError('Option primary - mode dependency failed, not'
                               'supported in mode {mode}!'.format(**bond))
 
     verify_mtu_ipv6(bond)
     verify_address(bond)
     verify_dhcpv6(bond)
     verify_vrf(bond)
     verify_mirror_redirect(bond)
 
     # use common function to verify VLAN configuration
     verify_vlan_config(bond)
 
     bond_name = bond['ifname']
     if dict_search('member.interface', bond):
         for interface, interface_config in bond['member']['interface'].items():
             error_msg = f'Can not add interface "{interface}" to bond, '
 
             if interface == 'lo':
                 raise ConfigError('Loopback interface "lo" can not be added to a bond')
 
             if not interface_exists(interface):
                 raise ConfigError(error_msg + 'it does not exist!')
 
             if 'is_bridge_member' in interface_config:
                 tmp = next(iter(interface_config['is_bridge_member']))
                 raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!')
 
             if 'is_bond_member' in interface_config:
                 tmp = next(iter(interface_config['is_bond_member']))
                 raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!')
 
             if 'is_source_interface' in interface_config:
                 tmp = interface_config['is_source_interface']
                 raise ConfigError(error_msg + f'it is the source-interface of "{tmp}"!')
 
             if 'has_address' in interface_config:
                 raise ConfigError(error_msg + 'it has an address assigned!')
 
             if 'has_vrf' in interface_config:
                 raise ConfigError(error_msg + 'it has a VRF assigned!')
 
             if 'new_added' in interface_config and 'config_paths' in interface_config:
                 for option_path, option_value in interface_config['config_paths'].items():
                     if option_path in EthernetIf.get_bond_member_allowed_options() :
                         continue
                     if option_path in BondIf.get_inherit_bond_options():
                         continue
                     raise ConfigError(error_msg + f'it has a "{option_path.replace(".", " ")}" assigned!')
 
     if 'primary' in bond:
         if bond['primary'] not in bond['member']['interface']:
             raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface')
 
         if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']:
             raise ConfigError('primary interface only works for mode active-backup, ' \
                               'transmit-load-balance or adaptive-load-balance')
 
+    if 'system_mac' in bond:
+        if bond['mode'] != '802.3ad':
+            raise ConfigError('Actor MAC address only available in 802.3ad mode!')
+
+        system_mac = bond['system_mac']
+        try:
+            assert_mac(system_mac, test_all_zero=False)
+        except:
+            raise ConfigError(f'Cannot use a multicast MAC address "{system_mac}" as system-mac!')
+
     return None
 
 def generate(bond):
     bond['frr_zebra_config'] = ''
     if 'deleted' not in bond:
         bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond)
     return None
 
 def apply(bond):
     ifname = bond['ifname']
     b = BondIf(ifname)
     if 'deleted' in bond:
         # delete interface
         b.remove()
     else:
         b.update(bond)
 
     if dict_search('member.interface_remove', bond):
         try:
             call_dependents()
         except ConfigError:
             raise ConfigError('Error in updating ethernet interface '
                               'after deleting it from bond')
 
     zebra_daemon = 'zebra'
     # Save original configuration prior to starting any commit actions
     frr_cfg = frr.FRRConfig()
 
     # The route-map used for the FIB (zebra) is part of the zebra daemon
     frr_cfg.load_configuration(zebra_daemon)
     frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True)
     if 'frr_zebra_config' in bond:
         frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config'])
     frr_cfg.commit_configuration(zebra_daemon)
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)