diff --git a/interface-definitions/protocols-static-arp.xml.in b/interface-definitions/protocols-static-arp.xml.in
index e5e8a9ad9..8b1b3b5e1 100644
--- a/interface-definitions/protocols-static-arp.xml.in
+++ b/interface-definitions/protocols-static-arp.xml.in
@@ -1,37 +1,51 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="protocols">
     <children>
       <node name="static">
         <children>
-          <tagNode name="arp" owner="${vyos_conf_scripts_dir}/arp.py">
+          <node name="arp" owner="${vyos_conf_scripts_dir}/arp.py">
             <properties>
               <help>Static ARP translation</help>
-              <valueHelp>
-                <format>ipv4</format>
-                <description>IPv4 destination address</description>
-              </valueHelp>
-              <constraint>
-                <validator name="ipv4-address"/>
-              </constraint>
             </properties>
             <children>
-              <leafNode name="hwaddr">
+              <tagNode name="interface">
                 <properties>
-                  <help>Translation MAC address</help>
+                  <help>Interface configuration</help>
+                  <completionHelp>
+                    <script>${vyos_completion_dir}/list_interfaces.py</script>
+                  </completionHelp>
                   <valueHelp>
-                    <format>macaddr</format>
-                    <description>Hardware (MAC) address</description>
+                    <format>txt</format>
+                    <description>Interface name</description>
                   </valueHelp>
                   <constraint>
-                    <validator name="mac-address"/>
+                    <validator name="interface-name"/>
                   </constraint>
                 </properties>
-              </leafNode>
+                <children>
+                  <tagNode name="address">
+                    <properties>
+                      <help>IP address for static ARP entry</help>
+                      <valueHelp>
+                        <format>ipv4</format>
+                        <description>IPv4 destination address</description>
+                      </valueHelp>
+                      <constraint>
+                        <validator name="ipv4-address"/>
+                      </constraint>
+                    </properties>
+                    <children>
+                      #include <include/generic-description.xml.i>
+                      #include <include/interface/mac.xml.i>
+                    </children>
+                  </tagNode>
+                </children>
+              </tagNode>
             </children>
-          </tagNode>
+          </node>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 3d62d269c..e6f89954f 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -1,112 +1,140 @@
 interfaces {
     ethernet eth0 {
         address 192.168.0.1/24
         duplex auto
         smp-affinity auto
         speed auto
     }
     ethernet eth1 {
-        address 100.64.0.0/31
         duplex auto
         smp-affinity auto
         speed auto
     }
     ethernet eth2 {
-        address 100.100.0.1/24
         duplex auto
         smp-affinity auto
         speed auto
+        vif 100 {
+            address 100.100.0.1/24
+        }
+        vif-s 200 {
+            address 100.64.200.254/24
+            vif-c 201 {
+                address 100.64.201.254/24
+            }
+            vif-c 202 {
+                address 100.64.202.254/24
+            }
+        }
     }
     loopback lo {
     }
 }
 protocols {
     static {
         arp 192.168.0.20 {
             hwaddr 00:50:00:00:00:20
         }
         arp 192.168.0.30 {
             hwaddr 00:50:00:00:00:30
         }
         arp 192.168.0.40 {
             hwaddr 00:50:00:00:00:40
         }
         arp 100.100.0.2 {
             hwaddr 00:50:00:00:02:02
         }
         arp 100.100.0.3 {
             hwaddr 00:50:00:00:02:03
         }
         arp 100.100.0.4 {
             hwaddr 00:50:00:00:02:04
         }
+        arp 100.64.200.1 {
+            hwaddr 00:50:00:00:00:01
+        }
+        arp 100.64.200.2 {
+            hwaddr 00:50:00:00:00:02
+        }
+        arp 100.64.201.10 {
+            hwaddr 00:50:00:00:00:10
+        }
+        arp 100.64.201.20 {
+            hwaddr 00:50:00:00:00:20
+        }
+        arp 100.64.202.30 {
+            hwaddr 00:50:00:00:00:30
+        }
+        arp 100.64.202.40 {
+            hwaddr 00:50:00:00:00:40
+        }
         route 0.0.0.0/0 {
             next-hop 100.64.0.1 {
             }
         }
     }
 }
 service {
     dhcp-server {
         shared-network-name LAN {
             authoritative
             subnet 192.168.0.0/24 {
                 default-router 192.168.0.1
                 dns-server 192.168.0.1
                 domain-name vyos.net
                 domain-search vyos.net
                 range LANDynamic {
                     start 192.168.0.20
                     stop 192.168.0.240
                 }
             }
         }
     }
     dns {
         forwarding {
             allow-from 192.168.0.0/16
             cache-size 10000
             dnssec off
             listen-address 192.168.0.1
         }
     }
     ssh {
         ciphers aes128-ctr,aes192-ctr,aes256-ctr
         ciphers chacha20-poly1305@openssh.com,rijndael-cbc@lysator.liu.se
         listen-address 192.168.0.1
         key-exchange curve25519-sha256@libssh.org
         key-exchange diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256
         port 22
     }
 }
 system {
     config-management {
         commit-revisions 100
     }
     console {
         device ttyS0 {
             speed 115200
         }
     }
     host-name vyos
     login {
         user vyos {
             authentication {
                 encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
                 plaintext-password ""
             }
         }
     }
     name-server 192.168.0.1
     syslog {
         global {
             facility all {
                 level info
             }
         }
     }
     time-zone Europe/Berlin
 }
 /* Warning: Do not remove the following line. */
 /* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */
 /* Release version: 1.2.6 */
diff --git a/smoketest/scripts/cli/test_protocols_static_arp.py b/smoketest/scripts/cli/test_protocols_static_arp.py
index 6663ade96..b61d8f854 100755
--- a/smoketest/scripts/cli/test_protocols_static_arp.py
+++ b/smoketest/scripts/cli/test_protocols_static_arp.py
@@ -1,88 +1,88 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2022 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 json
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.util import cmd
 
 base_path = ['protocols', 'static', 'arp']
 interface = 'eth0'
 address = '192.0.2.1/24'
 
 class TestARP(VyOSUnitTestSHIM.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestARP, cls).setUpClass()
 
         # ensure we can also run this test on a live system - so lets clean
         # out the current configuration :)
         cls.cli_delete(cls, base_path)
 
         # we need a L2 interface with a L3 address to properly configure ARP entries
         cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', address])
 
     @classmethod
     def tearDownClass(cls):
         # cleanuop L2 interface
         cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', address])
         cls.cli_commit(cls)
 
         super(TestARP, cls).tearDownClass()
 
     def tearDown(self):
         # delete test config
         self.cli_delete(base_path)
         self.cli_commit()
 
     def test_static_arp(self):
         test_data = {
-            '192.0.2.10' : { 'lladdr' : '00:01:02:03:04:0a' },
-            '192.0.2.11' : { 'lladdr' : '00:01:02:03:04:0b' },
-            '192.0.2.12' : { 'lladdr' : '00:01:02:03:04:0c' },
-            '192.0.2.13' : { 'lladdr' : '00:01:02:03:04:0d' },
-            '192.0.2.14' : { 'lladdr' : '00:01:02:03:04:0e' },
-            '192.0.2.15' : { 'lladdr' : '00:01:02:03:04:0f' },
+            '192.0.2.10' : { 'mac' : '00:01:02:03:04:0a' },
+            '192.0.2.11' : { 'mac' : '00:01:02:03:04:0b' },
+            '192.0.2.12' : { 'mac' : '00:01:02:03:04:0c' },
+            '192.0.2.13' : { 'mac' : '00:01:02:03:04:0d' },
+            '192.0.2.14' : { 'mac' : '00:01:02:03:04:0e' },
+            '192.0.2.15' : { 'mac' : '00:01:02:03:04:0f' },
         }
 
         for host, host_config in test_data.items():
-            self.cli_set(base_path + [host, 'hwaddr', host_config['lladdr']])
+            self.cli_set(base_path + ['interface', interface, 'address', host, 'mac', host_config['mac']])
 
         self.cli_commit()
 
         arp_table = json.loads(cmd('ip -j -4 neigh show'))
         for host, host_config in test_data.items():
             # As we search within a list of hosts we need to mark if it was
             # found or not. This ensures all hosts from test_data are processed
             found = False
             for entry in arp_table:
                 # Other ARP entry - not related to this testcase
                 if entry['dst'] not in list(test_data):
                     continue
 
                 if entry['dst'] == host:
-                    self.assertEqual(entry['lladdr'], host_config['lladdr'])
+                    self.assertEqual(entry['lladdr'], host_config['mac'])
                     self.assertEqual(entry['dev'], interface)
                     found = True
 
             if found == False:
                 print(entry)
             self.assertTrue(found)
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py
index 51a08bee5..1cd8f5451 100755
--- a/src/conf_mode/arp.py
+++ b/src/conf_mode/arp.py
@@ -1,66 +1,74 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-2022 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 node_changed
 from vyos.util import call
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     base = ['protocols', 'static', 'arp']
-    arp = conf.get_config_dict(base)
-    tmp = node_changed(conf, base)
-    if tmp: arp.update({'removed' : node_changed(conf, base)})
+    arp = conf.get_config_dict(base, get_first_key=True)
+
+    if 'interface' in arp:
+        for interface in arp['interface']:
+            tmp = node_changed(conf, base + ['interface', interface, 'address'], recursive=True)
+            if tmp: arp['interface'][interface].update({'address_old' : tmp})
 
     return arp
 
 def verify(arp):
     pass
 
 def generate(arp):
     pass
 
 def apply(arp):
     if not arp:
         return None
 
-    if 'removed' in arp:
-        for host in arp['removed']:
-            call(f'arp --delete {host}')
+    if 'interface' in arp:
+        for interface, interface_config in arp['interface'].items():
+            # Delete old static ARP assignments first
+            if 'address_old' in interface_config:
+                for address in interface_config['address_old']:
+                    call(f'ip neigh del {address} dev {interface}')
 
-    if 'arp' in arp:
-        for host, host_config in arp['arp'].items():
-            mac = host_config['hwaddr']
-            call(f'arp --set {host} {mac}')
+            # Add new static ARP entries to interface
+            if 'address' not in interface_config:
+                continue
+            for address, address_config in interface_config['address'].items():
+                mac = address_config['mac']
+                call(f'ip neigh add {address} lladdr {mac} dev {interface}')
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/migration-scripts/system/23-to-24 b/src/migration-scripts/system/23-to-24
new file mode 100755
index 000000000..5ea71d51a
--- /dev/null
+++ b/src/migration-scripts/system/23-to-24
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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
+
+from ipaddress import ip_interface
+from ipaddress import ip_address
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+    print("Must specify file name!")
+    exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+    config_file = f.read()
+
+base = ['protocols', 'static', 'arp']
+tmp_base = ['protocols', 'static', 'arp-tmp']
+config = ConfigTree(config_file)
+
+def fixup_cli(config, path, interface):
+    if config.exists(path + ['address']):
+        for address in config.return_values(path + ['address']):
+            tmp = ip_interface(address)
+            if ip_address(host) in tmp.network.hosts():
+                mac = config.return_value(tmp_base + [host, 'hwaddr'])
+                iface_path = ['protocols', 'static', 'arp', 'interface']
+                config.set(iface_path + [interface, 'address', host, 'mac'], value=mac)
+                config.set_tag(iface_path)
+                config.set_tag(iface_path + [interface, 'address'])
+                continue
+
+if not config.exists(base):
+    # Nothing to do
+    exit(0)
+
+# We need a temporary copy of the config tree as the original one needs to be
+# deleted first due to a change iun thge tagNode structure.
+config.copy(base, tmp_base)
+config.delete(base)
+
+for host in config.list_nodes(tmp_base):
+    for type in config.list_nodes(['interfaces']):
+        for interface in config.list_nodes(['interfaces', type]):
+            if_base = ['interfaces', type, interface]
+            fixup_cli(config, if_base, interface)
+
+            if config.exists(if_base + ['vif']):
+                for vif in config.list_nodes(if_base + ['vif']):
+                    vif_base = ['interfaces', type, interface, 'vif', vif]
+                    fixup_cli(config, vif_base, f'{interface}.{vif}')
+
+            if config.exists(if_base + ['vif-s']):
+                for vif_s in config.list_nodes(if_base + ['vif-s']):
+                    vif_s_base = ['interfaces', type, interface, 'vif-s', vif_s]
+                    fixup_cli(config, vif_s_base, f'{interface}.{vif_s}')
+
+                    if config.exists(if_base + ['vif-s', vif_s, 'vif-c']):
+                        for vif_c in config.list_nodes(if_base + ['vif-s', vif_s, 'vif-c']):
+                            vif_c_base = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c', vif_c]
+                            fixup_cli(config, vif_c_base, f'{interface}.{vif_s}.{vif_c}')
+
+config.delete(tmp_base)
+
+try:
+    with open(file_name, 'w') as f:
+        f.write(config.to_string())
+except OSError as e:
+    print(f'Failed to save the modified config: {e}')
+    exit(1)