diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2
index f38691884..58f0199fa 100644
--- a/data/templates/ipsec/swanctl/peer.j2
+++ b/data/templates/ipsec/swanctl/peer.j2
@@ -1,166 +1,166 @@
 {% macro conn(peer, peer_conf, ike_group, esp_group) %}
 {% set name = peer.replace("@", "") | dot_colon_to_dash %}
 {# peer needs to reference the global IKE configuration for certain values #}
 {% set ike = ike_group[peer_conf.ike_group] %}
     {{ name }} {
         proposals = {{ ike | get_esp_ike_cipher | join(',') }}
         version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}
 {% if peer_conf.virtual_address is vyos_defined %}
         vips = {{ peer_conf.virtual_address | join(', ') }}
 {% endif %}
-        local_addrs = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '%any' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} reset:{{ name }}
+        local_addrs = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '%any' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }}
         remote_addrs = {{ peer_conf.remote_address | join(",") if peer_conf.remote_address is vyos_defined and 'any' not in peer_conf.remote_address else '%any' }}
 {% if peer_conf.authentication.mode is vyos_defined('x509') %}
         send_cert = always
 {% endif %}
 {% if ike.dead_peer_detection is vyos_defined %}
         dpd_timeout = {{ ike.dead_peer_detection.timeout }}
         dpd_delay = {{ ike.dead_peer_detection.interval }}
 {% endif %}
 {% if ike.key_exchange is vyos_defined('ikev1') and ike.mode is vyos_defined('aggressive') %}
         aggressive = yes
 {% endif %}
         rekey_time = {{ ike.lifetime }}s
         mobike = {{ "no" if ike.disable_mobike is defined else "yes" }}
 {% if peer[0:1] == '@' %}
         keyingtries = 0
         reauth_time = 0
 {% elif peer_conf.connection_type is not vyos_defined or peer_conf.connection_type is vyos_defined('initiate') %}
         keyingtries = 0
 {% elif peer_conf.connection_type is vyos_defined('respond') %}
         keyingtries = 1
 {% endif %}
 {% if peer_conf.force_udp_encapsulation is vyos_defined %}
         encap = yes
 {% endif %}
         local {
 {% if peer_conf.authentication.local_id is vyos_defined %}
             id = "{{ peer_conf.authentication.local_id }}"
 {% endif %}
             auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }}
 {% if peer_conf.authentication.mode == 'x509' %}
             certs = {{ peer_conf.authentication.x509.certificate }}.pem
 {% elif peer_conf.authentication.mode == 'rsa' %}
             pubkeys = {{ peer_conf.authentication.rsa.local_key }}.pem
 {% endif %}
         }
         remote {
             id = "{{ peer_conf.authentication.remote_id }}"
             auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }}
 {% if peer_conf.authentication.mode == 'rsa' %}
             pubkeys = {{ peer_conf.authentication.rsa.remote_key }}.pem
 {% endif %}
         }
         children {
 {% if peer_conf.vti.bind is vyos_defined and peer_conf.tunnel is not vyos_defined %}
 {%     set vti_esp = esp_group[ peer_conf.vti.esp_group ] if peer_conf.vti.esp_group is vyos_defined else esp_group[ peer_conf.default_esp_group ] %}
             {{ name }}-vti {
                 esp_proposals = {{ vti_esp | get_esp_ike_cipher(ike) | join(',') }}
 {%     if vti_esp.life_bytes is vyos_defined %}
                 life_bytes = {{ vti_esp.life_bytes }}
 {%     endif %}
 {%     if vti_esp.life_packets is vyos_defined %}
                 life_packets = {{ vti_esp.life_packets }}
 {%     endif %}
                 life_time = {{ vti_esp.lifetime }}s
                 local_ts = 0.0.0.0/0,::/0
                 remote_ts = 0.0.0.0/0,::/0
                 updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}"
 {#              The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #}
 {#              Thus we simply shift the key by one to also support a vti0 interface #}
 {%     set if_id = peer_conf.vti.bind | replace('vti', '') | int + 1 %}
                 if_id_in = {{ if_id }}
                 if_id_out = {{ if_id }}
                 ipcomp = {{ 'yes' if vti_esp.compression is vyos_defined else 'no' }}
                 mode = {{ vti_esp.mode }}
 {%     if peer[0:1] == '@' %}
                 start_action = none
 {%     elif peer_conf.connection_type is not vyos_defined or peer_conf.connection_type is vyos_defined('initiate') %}
                 start_action = start
 {%     elif peer_conf.connection_type is vyos_defined('respond') %}
                 start_action = trap
 {%     elif peer_conf.connection_type is vyos_defined('none') %}
                 start_action = none
 {%     endif %}
 {%     if ike.dead_peer_detection is vyos_defined %}
                 dpd_action = {{ ike.dead_peer_detection.action }}
 {%     endif %}
                 close_action = {{ ike.close_action }}
 {%     if peer_conf.replay_window is vyos_defined %}
                 replay_window = {{ peer_conf.replay_window }}
 {%     endif %}
             }
 {% elif peer_conf.tunnel is vyos_defined %}
 {%     for tunnel_id, tunnel_conf in peer_conf.tunnel.items() if tunnel_conf.disable is not defined %}
 {%         set tunnel_esp_name = tunnel_conf.esp_group if tunnel_conf.esp_group is vyos_defined else peer_conf.default_esp_group %}
 {%         set tunnel_esp = esp_group[tunnel_esp_name] %}
 {%         set proto = tunnel_conf.protocol if tunnel_conf.protocol is vyos_defined else '' %}
 {%         set local_port = tunnel_conf.local.port if tunnel_conf.local.port is vyos_defined else '' %}
 {%         set local_suffix = '[{0}/{1}]'.format(proto, local_port) if proto or local_port else '' %}
 {%         set remote_port = tunnel_conf.remote.port if tunnel_conf.remote.port is vyos_defined else '' %}
 {%         set remote_suffix = '[{0}/{1}]'.format(proto, remote_port) if proto or remote_port else '' %}
             {{ name }}-tunnel-{{ tunnel_id }} {
                 esp_proposals = {{ tunnel_esp | get_esp_ike_cipher(ike) | join(',') }}
 {%         if tunnel_esp.life_bytes is vyos_defined %}
                 life_bytes = {{ tunnel_esp.life_bytes }}
 {%         endif %}
 {%         if tunnel_esp.life_packets is vyos_defined %}
                 life_packets = {{ tunnel_esp.life_packets }}
 {%         endif %}
                 life_time = {{ tunnel_esp.lifetime }}s
 {%         if tunnel_esp.mode is not defined or tunnel_esp.mode == 'tunnel' %}
 {%             if tunnel_conf.local.prefix is vyos_defined %}
 {%                 set local_prefix = tunnel_conf.local.prefix if 'any' not in tunnel_conf.local.prefix else ['0.0.0.0/0', '::/0'] %}
                 local_ts = {{ local_prefix | join(local_suffix + ",") }}{{ local_suffix }}
 {%             endif %}
 {%             if tunnel_conf.remote.prefix is vyos_defined %}
 {%                 set remote_prefix = tunnel_conf.remote.prefix if 'any' not in tunnel_conf.remote.prefix else ['0.0.0.0/0', '::/0'] %}
                 remote_ts = {{ remote_prefix | join(remote_suffix + ",") }}{{ remote_suffix }}
 {%             endif %}
 {%             if tunnel_conf.priority is vyos_defined %}
                 priority = {{ tunnel_conf.priority }}
 {%             endif %}
 {%         elif tunnel_esp.mode == 'transport' %}
                 local_ts = {{ peer_conf.local_address }}{{ local_suffix }}
                 remote_ts = {{ peer_conf.remote_address | join(",") }}{{ remote_suffix }}
 {%         endif %}
                 ipcomp = {{ 'yes' if tunnel_esp.compression is vyos_defined else 'no' }}
                 mode = {{ tunnel_esp.mode }}
 {%         if peer[0:1] == '@' %}
                 start_action = none
 {%         elif peer_conf.connection_type is not vyos_defined or peer_conf.connection_type is vyos_defined('initiate') %}
                 start_action = start
 {%         elif peer_conf.connection_type is vyos_defined('respond') %}
                 start_action = trap
 {%         elif peer_conf.connection_type is vyos_defined('none') %}
                 start_action = none
 {%         endif %}
 {%         if ike.dead_peer_detection is vyos_defined %}
                 dpd_action = {{ ike.dead_peer_detection.action }}
 {%         endif %}
                 close_action = {{ ike.close_action }}
 {%         if peer_conf.replay_window is vyos_defined %}
                 replay_window = {{ peer_conf.replay_window }}
 {%         endif %}
 {%         if peer_conf.vti.bind is vyos_defined %}
 {#             The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #}
 {#             Thus we simply shift the key by one to also support a vti0 interface #}
 {%             set if_id = peer_conf.vti.bind | replace('vti', '') | int + 1 %}
                 updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}"
                 if_id_in = {{ if_id }}
                 if_id_out = {{ if_id }}
 {%         endif %}
             }
 {%         if tunnel_conf.passthrough is vyos_defined %}
             {{ name }}-tunnel-{{ tunnel_id }}-passthrough {
                 local_ts = {{ tunnel_conf.passthrough | join(",") }}
                 remote_ts = {{ tunnel_conf.passthrough | join(",") }}
                 start_action = trap
                 mode = pass
             }
 {%         endif %}
 {%     endfor %}
 {% endif %}
         }
     }
 {% endmacro %}
diff --git a/data/templates/ipsec/swanctl/remote_access.j2 b/data/templates/ipsec/swanctl/remote_access.j2
index d1d6d2478..af7f2994e 100644
--- a/data/templates/ipsec/swanctl/remote_access.j2
+++ b/data/templates/ipsec/swanctl/remote_access.j2
@@ -1,56 +1,56 @@
 {% macro conn(name, rw_conf, ike_group, esp_group) %}
 {# peer needs to reference the global IKE configuration for certain values #}
 {% set ike = ike_group[rw_conf.ike_group] %}
 {% set esp = esp_group[rw_conf.esp_group] %}
     ra-{{ name }} {
         remote_addrs = %any
-        local_addrs = {{ rw_conf.local_address if rw_conf.local_address is not vyos_defined('any') else '%any' }} # dhcp:{{ rw_conf.dhcp_interface if rw_conf.dhcp_interface is vyos_defined else 'no' }} reset:ra-{{ name }}
+        local_addrs = {{ rw_conf.local_address if rw_conf.local_address is not vyos_defined('any') else '%any' }} # dhcp:{{ rw_conf.dhcp_interface if rw_conf.dhcp_interface is vyos_defined else 'no' }}
         proposals = {{ ike_group[rw_conf.ike_group] | get_esp_ike_cipher | join(',') }}
         version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}
         send_certreq = no
         rekey_time = {{ ike.lifetime }}s
         keyingtries = 0
 {% if rw_conf.unique is vyos_defined %}
         unique = {{ rw_conf.unique }}
 {% endif %}
 {% if rw_conf.pool is vyos_defined %}
         pools = {{ rw_conf.pool | join(',') }}
 {% endif %}
         local {
 {% if rw_conf.authentication.local_id is vyos_defined and rw_conf.authentication.use_x509_id is not vyos_defined %}
 {#          please use " quotes - else Apple iOS goes crazy #}
             id = "{{ rw_conf.authentication.local_id }}"
 {% endif %}
 {% if rw_conf.authentication.server_mode == 'x509' %}
             auth = pubkey
             certs = {{ rw_conf.authentication.x509.certificate }}.pem
 {% elif rw_conf.authentication.server_mode == 'pre-shared-secret' %}
             auth = psk
 {% endif %}
         }
         remote {
 {% if rw_conf.authentication.client_mode == 'x509' %}
             auth = pubkey
 {% elif rw_conf.authentication.client_mode.startswith("eap") %}
             auth = {{ rw_conf.authentication.client_mode }}
             eap_id = %any
 {% endif %}
         }
         children {
             ikev2-vpn  {
                 esp_proposals = {{ esp | get_esp_ike_cipher(ike) | join(',') }}
                 rekey_time = {{ esp.lifetime }}s
                 rand_time = 540s
                 dpd_action = clear
                 inactivity = {{ rw_conf.timeout }}
 {% if rw_conf.replay_window is vyos_defined %}
                 replay_window = {{ rw_conf.replay_window }}
 {% endif %}
 {% set local_prefix = rw_conf.local.prefix if rw_conf.local.prefix is vyos_defined else ['0.0.0.0/0', '::/0'] %}
 {% set local_port = rw_conf.local.port if rw_conf.local.port is vyos_defined else '' %}
 {% set local_suffix = '[%any/{1}]'.format(local_port) if local_port else '' %}
                 local_ts = {{ local_prefix | join(local_suffix + ",") }}{{ local_suffix }}
             }
         }
     }
 {% endmacro %}
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index 00f9e8f46..1a5e47144 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -1,963 +1,963 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-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/>.
 
 import os
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.configsession import ConfigSessionError
 from vyos.utils.process import call
 from vyos.utils.process import process_named_running
 from vyos.utils.file import read_file
 
 ethernet_path = ['interfaces', 'ethernet']
 tunnel_path = ['interfaces', 'tunnel']
 vti_path = ['interfaces', 'vti']
 nhrp_path = ['protocols', 'nhrp']
 base_path = ['vpn', 'ipsec']
 
 charon_file = '/etc/strongswan.d/charon.conf'
-dhcp_waiting_file = '/tmp/ipsec_dhcp_waiting'
+dhcp_interfaces_file = '/tmp/ipsec_dhcp_interfaces'
 swanctl_file = '/etc/swanctl/swanctl.conf'
 
 peer_ip = '203.0.113.45'
 connection_name = 'main-branch'
 local_id = 'left'
 remote_id = 'right'
 interface = 'eth1'
 vif = '100'
 esp_group = 'MyESPGroup'
 ike_group = 'MyIKEGroup'
 secret = 'MYSECRETKEY'
 PROCESS_NAME = 'charon-systemd'
 regex_uuid4 = '[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}'
 
 ca_name = 'MyVyOS-CA'
 ca_pem = """
 MIICMDCCAdegAwIBAgIUBCzIjYvD7SPbx5oU18IYg7NVxQ0wCgYIKoZIzj0EAwIw
 ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
 bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEgMB4GA1UEAwwXSVBTZWMgU21va2V0ZXN0
 IFJvb3QgQ0EwHhcNMjMwOTI0MTIwMzQxWhcNMzMwOTIxMTIwMzQxWjBnMQswCQYD
 VQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5
 MQ0wCwYDVQQKDARWeU9TMSAwHgYDVQQDDBdJUFNlYyBTbW9rZXRlc3QgUm9vdCBD
 QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEh8/yU572B3zmFxrGgHk+H7grYt
 EHUJodY3gXNWMHz0gySrbGhsGtECDfP/G+T4Suk7cuVzB1wnLocSafD8TcqjYTBf
 MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsG
 AQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUTYoQJNlk7X87/gRegHnCnPef39Aw
 CgYIKoZIzj0EAwIDRwAwRAIgX1spXjrUc10r3g/Zm4O31LU5O08J2vVqFo94zHE5
 0VgCIG4JK9Zg5O/yn4mYksZux7efiHRUzL2y2TXQ9IqrqM8W
 """
 
 int_ca_name = 'MyVyOS-IntCA'
 int_ca_pem = """
 MIICYDCCAgWgAwIBAgIUcFx2BVYErHI+SneyPYHijxXt1cgwCgYIKoZIzj0EAwIw
 ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
 bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEgMB4GA1UEAwwXSVBTZWMgU21va2V0ZXN0
 IFJvb3QgQ0EwHhcNMjMwOTI0MTIwNTE5WhcNMzMwOTIwMTIwNTE5WjBvMQswCQYD
 VQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5
 MQ0wCwYDVQQKDARWeU9TMSgwJgYDVQQDDB9JUFNlYyBTbW9rZXRlc3QgSW50ZXJt
 ZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIHw2G5dq3c715AcA
 tzR++dYu1fLRFmHzRGTZOT7hLrh2Fg4hnKFPLOeUA5Qi50xCvjJ9JnonTyy2RfRH
 axYizKOBhjCBgzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAd
 BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFC9KrFYtA+hO
 l7vdMbWxTMAyLB7BMB8GA1UdIwQYMBaAFE2KECTZZO1/O/4EXoB5wpz3n9/QMAoG
 CCqGSM49BAMCA0kAMEYCIQCnqWbElgOL9dGO3iLxasFNq/hM7vM/DzaiHi4BowxW
 0gIhAMohefNj+QgLfPhvyODHIPE9LMyfp7lJEaCC2K8PCSFD
 """
 
 peer_name = 'peer1'
 peer_cert = """
 MIICSTCCAfCgAwIBAgIUPxYleUgCo/glVVePze3QmAFgi6MwCgYIKoZIzj0EAwIw
 bzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
 bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEoMCYGA1UEAwwfSVBTZWMgU21va2V0ZXN0
 IEludGVybWVkaWF0ZSBDQTAeFw0yMzA5MjQxMjA2NDJaFw0yODA5MjIxMjA2NDJa
 MGQxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlT
 b21lLUNpdHkxDTALBgNVBAoMBFZ5T1MxHTAbBgNVBAMMFElQU2VjIFNtb2tldGVz
 dCBQZWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZJtuTDu84uy++GMwRNLl
 10JAXZxXQSDl+CdTWwjbQZURcdY+ia7BoaoYX/0VKPel3Se64rIUQQLQoY/9MJb9
 UKN1MHMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
 KwYBBQUHAwEwHQYDVR0OBBYEFNJCdnkm3cAmf04UwOKL7IqMJ6OXMB8GA1UdIwQY
 MBaAFC9KrFYtA+hOl7vdMbWxTMAyLB7BMAoGCCqGSM49BAMCA0cAMEQCIGVnDRUy
 UJ0U/deDvrBo1+AakZndkNAMN/XNo5a5GzhEAiBCY7E/3b0BIO8FiIbVB3iDcaxg
 g7ET2RgWxvhEoN3ZRw==
 """
 
 peer_key = """
 MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVDEZDK7q/T+tiJUV
 WLKS3ZYDfZ4lZv0C1gJpYq0gWP2hRANCAARkm25MO7zi7L74YzBE0uXXQkBdnFdB
 IOX4J1NbCNtBlRFx1j6JrsGhqhhf/RUo96XdJ7rishRBAtChj/0wlv1Q
 """
 
 swanctl_dir = '/etc/swanctl'
 CERT_PATH   = f'{swanctl_dir}/x509/'
 CA_PATH     = f'{swanctl_dir}/x509ca/'
 
 class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
     skip_process_check = False
 
     @classmethod
     def setUpClass(cls):
         super(TestVPNIPsec, 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)
         cls.cli_delete(cls, ['pki'])
 
         cls.cli_set(cls, base_path + ['interface', f'{interface}.{vif}'])
 
     @classmethod
     def tearDownClass(cls):
         super(TestVPNIPsec, cls).tearDownClass()
         cls.cli_delete(cls, base_path + ['interface', f'{interface}.{vif}'])
 
     def setUp(self):
         # Set IKE/ESP Groups
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes128'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha1'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes128'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha1'])
 
     def tearDown(self):
         # Check for running process
         if not self.skip_process_check:
             self.assertTrue(process_named_running(PROCESS_NAME))
         else:
             self.skip_process_check = False # Reset
 
         self.cli_delete(base_path)
         self.cli_delete(tunnel_path)
         self.cli_commit()
 
         # Check for no longer running process
         self.assertFalse(process_named_running(PROCESS_NAME))
 
     def setupPKI(self):
         self.cli_set(['pki', 'ca', ca_name, 'certificate', ca_pem.replace('\n','')])
         self.cli_set(['pki', 'ca', int_ca_name, 'certificate', int_ca_pem.replace('\n','')])
         self.cli_set(['pki', 'certificate', peer_name, 'certificate', peer_cert.replace('\n','')])
         self.cli_set(['pki', 'certificate', peer_name, 'private', 'key', peer_key.replace('\n','')])
 
     def tearDownPKI(self):
         self.cli_delete(['pki'])
 
     def test_dhcp_fail_handling(self):
         # Skip process check - connection is not created for this test
         self.skip_process_check = True
 
         # Interface for dhcp-interface
         self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server
 
         # vpn ipsec auth psk <tag> id <x.x.x.x>
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
 
         # Site to site
         peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
         self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
         self.cli_set(peer_base_path + ['ike-group', ike_group])
         self.cli_set(peer_base_path + ['default-esp-group', esp_group])
         self.cli_set(peer_base_path + ['dhcp-interface', f'{interface}.{vif}'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre'])
 
         self.cli_commit()
 
-        self.assertTrue(os.path.exists(dhcp_waiting_file))
+        self.assertTrue(os.path.exists(dhcp_interfaces_file))
 
-        dhcp_waiting = read_file(dhcp_waiting_file)
-        self.assertIn(f'{interface}.{vif}', dhcp_waiting) # Ensure dhcp-failed interface was added for dhclient hook
+        dhcp_interfaces = read_file(dhcp_interfaces_file)
+        self.assertIn(f'{interface}.{vif}', dhcp_interfaces) # Ensure dhcp interface was added for dhclient hook
 
         self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address'])
 
     def test_site_to_site(self):
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
 
         local_address = '192.0.2.10'
         priority = '20'
         life_bytes = '100000'
         life_packets = '2000000'
 
         # vpn ipsec auth psk <tag> id <x.x.x.x>
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
 
         # Site to site
         peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
 
         self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes])
         self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets])
 
         self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
         self.cli_set(peer_base_path + ['ike-group', ike_group])
         self.cli_set(peer_base_path + ['default-esp-group', esp_group])
         self.cli_set(peer_base_path + ['local-address', local_address])
         self.cli_set(peer_base_path + ['remote-address', peer_ip])
         self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'tcp'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'port', '443'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'port', '443'])
 
         self.cli_set(peer_base_path + ['tunnel', '2', 'local', 'prefix', '10.1.0.0/16'])
         self.cli_set(peer_base_path + ['tunnel', '2', 'remote', 'prefix', '10.2.0.0/16'])
         self.cli_set(peer_base_path + ['tunnel', '2', 'priority', priority])
 
         self.cli_commit()
 
         # Verify strongSwan configuration
         swanctl_conf = read_file(swanctl_file)
         swanctl_conf_lines = [
             f'version = 2',
             f'auth = psk',
             f'life_bytes = {life_bytes}',
             f'life_packets = {life_packets}',
             f'rekey_time = 28800s', # default value
             f'proposals = aes128-sha1-modp1024',
             f'esp_proposals = aes128-sha1-modp1024',
             f'life_time = 3600s', # default value
             f'local_addrs = {local_address} # dhcp:no',
             f'remote_addrs = {peer_ip}',
             f'mode = tunnel',
             f'{connection_name}-tunnel-1',
             f'local_ts = 172.16.10.0/24[tcp/443],172.16.11.0/24[tcp/443]',
             f'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]',
             f'mode = tunnel',
             f'{connection_name}-tunnel-2',
             f'local_ts = 10.1.0.0/16',
             f'remote_ts = 10.2.0.0/16',
             f'priority = {priority}',
             f'mode = tunnel',
             f'replay_window = 32',
         ]
         for line in swanctl_conf_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_secrets_lines = [
             f'id-{regex_uuid4} = "{local_id}"',
             f'id-{regex_uuid4} = "{remote_id}"',
             f'id-{regex_uuid4} = "{local_address}"',
             f'id-{regex_uuid4} = "{peer_ip}"',
             f'secret = "{secret}"'
         ]
         for line in swanctl_secrets_lines:
             self.assertRegex(swanctl_conf, fr'{line}')
 
 
     def test_site_to_site_vti(self):
         local_address = '192.0.2.10'
         vti = 'vti10'
         # IKE
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'disable-mobike'])
         # ESP
         self.cli_set(base_path + ['esp-group', esp_group, 'compression'])
         # VTI interface
         self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24'])
 
         # vpn ipsec auth psk <tag> id <x.x.x.x>
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
 
         # Site to site
         peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
         self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
         self.cli_set(peer_base_path + ['connection-type', 'none'])
         self.cli_set(peer_base_path + ['force-udp-encapsulation'])
         self.cli_set(peer_base_path + ['ike-group', ike_group])
         self.cli_set(peer_base_path + ['default-esp-group', esp_group])
         self.cli_set(peer_base_path + ['local-address', local_address])
         self.cli_set(peer_base_path + ['remote-address', peer_ip])
         self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24'])
         self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24'])
         self.cli_set(peer_base_path + ['vti', 'bind', vti])
         self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group])
 
         self.cli_commit()
 
         swanctl_conf = read_file(swanctl_file)
         if_id = vti.lstrip('vti')
         # The key defaults to 0 and will match any policies which similarly do
         # not have a lookup key configuration - thus we shift the key by one
         # to also support a vti0 interface
         if_id = str(int(if_id) +1)
         swanctl_conf_lines = [
             f'version = 2',
             f'auth = psk',
             f'proposals = aes128-sha1-modp1024',
             f'esp_proposals = aes128-sha1-modp1024',
             f'local_addrs = {local_address} # dhcp:no',
             f'mobike = no',
             f'remote_addrs = {peer_ip}',
             f'mode = tunnel',
             f'local_ts = 172.16.10.0/24,172.16.11.0/24',
             f'remote_ts = 172.17.10.0/24,172.17.11.0/24',
             f'ipcomp = yes',
             f'start_action = none',
             f'replay_window = 32',
             f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one
             f'if_id_out = {if_id}',
             f'updown = "/etc/ipsec.d/vti-up-down {vti}"'
         ]
         for line in swanctl_conf_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_secrets_lines = [
             f'id-{regex_uuid4} = "{local_id}"',
             f'id-{regex_uuid4} = "{remote_id}"',
             f'secret = "{secret}"'
         ]
         for line in swanctl_secrets_lines:
             self.assertRegex(swanctl_conf, fr'{line}')
 
 
     def test_dmvpn(self):
         tunnel_if = 'tun100'
         nhrp_secret = 'secret'
         ike_lifetime = '3600'
         esp_lifetime = '1800'
 
         # Tunnel
         self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29'])
         self.cli_set(tunnel_path + [tunnel_if, 'encapsulation', 'gre'])
         self.cli_set(tunnel_path + [tunnel_if, 'source-address', '192.0.2.1'])
         self.cli_set(tunnel_path + [tunnel_if, 'enable-multicast'])
         self.cli_set(tunnel_path + [tunnel_if, 'parameters', 'ip', 'key', '1'])
 
         # NHRP
         self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'cisco-authentication', nhrp_secret])
         self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'holding-time', '300'])
         self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'multicast', 'dynamic'])
         self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'redirect'])
         self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'shortcut'])
 
         # IKE/ESP Groups
         self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', esp_lifetime])
         self.cli_set(base_path + ['esp-group', esp_group, 'mode', 'transport'])
         self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'dh-group2'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha1'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', '3des'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'md5'])
 
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev1'])
         self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha1'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'prf', 'prfsha1'])
 
         # Profile
         self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'mode', 'pre-shared-secret'])
         self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'pre-shared-secret', nhrp_secret])
         self.cli_set(base_path + ['profile', 'NHRPVPN', 'bind', 'tunnel', tunnel_if])
         self.cli_set(base_path + ['profile', 'NHRPVPN', 'esp-group', esp_group])
         self.cli_set(base_path + ['profile', 'NHRPVPN', 'ike-group', ike_group])
 
         self.cli_commit()
 
         swanctl_conf = read_file(swanctl_file)
         swanctl_lines = [
             f'proposals = aes128-sha1-modp1024,aes256-sha1-prfsha1-modp1024',
             f'version = 1',
             f'rekey_time = {ike_lifetime}s',
             f'rekey_time = {esp_lifetime}s',
             f'esp_proposals = aes128-sha1-modp1024,aes256-sha1-modp1024,3des-md5-modp1024',
             f'local_ts = dynamic[gre]',
             f'remote_ts = dynamic[gre]',
             f'mode = transport',
             f'secret = {nhrp_secret}'
         ]
         for line in swanctl_lines:
             self.assertIn(line, swanctl_conf)
 
         # There is only one NHRP test so no need to delete this globally in tearDown()
         self.cli_delete(nhrp_path)
 
     def test_site_to_site_x509(self):
         # Enable PKI
         self.setupPKI()
 
         vti = 'vti20'
         self.cli_set(vti_path + [vti, 'address', '192.168.0.1/31'])
 
         peer_ip = '172.18.254.202'
         connection_name = 'office'
         local_address = '172.18.254.201'
         peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
 
         self.cli_set(peer_base_path + ['authentication', 'local-id', peer_name])
         self.cli_set(peer_base_path + ['authentication', 'mode', 'x509'])
         self.cli_set(peer_base_path + ['authentication', 'remote-id', 'peer2'])
         self.cli_set(peer_base_path + ['authentication', 'x509', 'ca-certificate', int_ca_name])
         self.cli_set(peer_base_path + ['authentication', 'x509', 'certificate', peer_name])
         self.cli_set(peer_base_path + ['connection-type', 'initiate'])
         self.cli_set(peer_base_path + ['ike-group', ike_group])
         self.cli_set(peer_base_path + ['ikev2-reauth', 'inherit'])
         self.cli_set(peer_base_path + ['local-address', local_address])
         self.cli_set(peer_base_path + ['remote-address', peer_ip])
         self.cli_set(peer_base_path + ['vti', 'bind', vti])
         self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group])
 
         self.cli_commit()
 
         swanctl_conf = read_file(swanctl_file)
         tmp = peer_ip.replace('.', '-')
         if_id = vti.lstrip('vti')
         # The key defaults to 0 and will match any policies which similarly do
         # not have a lookup key configuration - thus we shift the key by one
         # to also support a vti0 interface
         if_id = str(int(if_id) +1)
         swanctl_lines = [
             f'{connection_name}',
             f'version = 0', # key-exchange not set - defaulting to 0 for ikev1 and ikev2
             f'send_cert = always',
             f'mobike = yes',
             f'keyingtries = 0',
             f'id = "{peer_name}"',
             f'auth = pubkey',
             f'certs = {peer_name}.pem',
             f'proposals = aes128-sha1-modp1024',
             f'esp_proposals = aes128-sha1-modp1024',
             f'local_addrs = {local_address} # dhcp:no',
             f'remote_addrs = {peer_ip}',
             f'local_ts = 0.0.0.0/0,::/0',
             f'remote_ts = 0.0.0.0/0,::/0',
             f'updown = "/etc/ipsec.d/vti-up-down {vti}"',
             f'if_id_in = {if_id}', # will be 11 for vti10
             f'if_id_out = {if_id}',
             f'ipcomp = no',
             f'mode = tunnel',
             f'start_action = start',
         ]
         for line in swanctl_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_secrets_lines = [
             f'{connection_name}',
             f'file = {peer_name}.pem',
         ]
         for line in swanctl_secrets_lines:
             self.assertIn(line, swanctl_conf)
 
         # Check Root CA, Intermediate CA and Peer cert/key pair is present
         self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}_1.pem')))
         self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}_2.pem')))
         self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))
 
         # There is only one VTI test so no need to delete this globally in tearDown()
         self.cli_delete(vti_path)
 
         # Disable PKI
         self.tearDownPKI()
 
 
     def test_flex_vpn_vips(self):
         local_address = '192.0.2.5'
         local_id = 'vyos-r1'
         remote_id = 'vyos-r2'
         peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
 
         self.cli_set(tunnel_path + ['tun1', 'encapsulation', 'gre'])
         self.cli_set(tunnel_path + ['tun1', 'source-address', local_address])
 
         self.cli_set(base_path + ['interface', interface])
         self.cli_set(base_path + ['options', 'flexvpn'])
         self.cli_set(base_path + ['options', 'interface', 'tun1'])
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
 
         # vpn ipsec auth psk <tag> id <x.x.x.x>
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
         self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
 
         self.cli_set(peer_base_path + ['authentication', 'local-id', local_id])
         self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
         self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id])
         self.cli_set(peer_base_path + ['connection-type', 'initiate'])
         self.cli_set(peer_base_path + ['ike-group', ike_group])
         self.cli_set(peer_base_path + ['default-esp-group', esp_group])
         self.cli_set(peer_base_path + ['local-address', local_address])
         self.cli_set(peer_base_path + ['remote-address', peer_ip])
         self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre'])
 
         self.cli_set(peer_base_path + ['virtual-address', '203.0.113.55'])
         self.cli_set(peer_base_path + ['virtual-address', '203.0.113.56'])
 
         self.cli_commit()
 
         # Verify strongSwan configuration
         swanctl_conf = read_file(swanctl_file)
         swanctl_conf_lines = [
             f'version = 2',
             f'vips = 203.0.113.55, 203.0.113.56',
             f'life_time = 3600s', # default value
             f'local_addrs = {local_address} # dhcp:no',
             f'remote_addrs = {peer_ip}',
             f'{connection_name}-tunnel-1',
             f'mode = tunnel',
         ]
 
         for line in swanctl_conf_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_secrets_lines = [
             f'id-{regex_uuid4} = "{local_id}"',
             f'id-{regex_uuid4} = "{remote_id}"',
             f'id-{regex_uuid4} = "{peer_ip}"',
             f'id-{regex_uuid4} = "{local_address}"',
             f'secret = "{secret}"',
         ]
 
         for line in swanctl_secrets_lines:
             self.assertRegex(swanctl_conf, fr'{line}')
 
         # Verify charon configuration
         charon_conf = read_file(charon_file)
         charon_conf_lines = [
             f'# Cisco FlexVPN',
             f'cisco_flexvpn = yes',
             f'install_virtual_ip = yes',
             f'install_virtual_ip_on = tun1',
         ]
 
         for line in charon_conf_lines:
             self.assertIn(line, charon_conf)
 
 
     def test_remote_access(self):
         # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17
         self.setupPKI()
 
         ike_group = 'IKE-RW'
         esp_group = 'ESP-RW'
 
         conn_name = 'vyos-rw'
         local_address = '192.0.2.1'
         ip_pool_name = 'ra-rw-ipv4'
         username = 'vyos'
         password = 'secret'
         ike_lifetime = '7200'
         eap_lifetime = '3600'
         local_id = 'ipsec.vyos.net'
 
         name_servers = ['172.16.254.100', '172.16.254.101']
         prefix = '172.16.250.0/28'
 
         # IKE
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'hash', 'sha512'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'hash', 'sha256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'dh-group', '2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'hash', 'sha256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256'])
 
         # ESP
         self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
         self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'hash', 'sha512'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2',  'hash', 'sha384'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3',  'hash', 'sha256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4',  'hash', 'sha1'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
         # verify() - CA cert required for x509 auth
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])
 
         for ns in name_servers:
             self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns])
         self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix])
 
         self.cli_commit()
 
         # verify applied configuration
         swanctl_conf = read_file(swanctl_file)
         swanctl_lines = [
             f'{conn_name}',
             f'remote_addrs = %any',
             f'local_addrs = {local_address}',
             f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048',
             f'version = 2',
             f'send_certreq = no',
             f'rekey_time = {ike_lifetime}s',
             f'keyingtries = 0',
             f'pools = {ip_pool_name}',
             f'id = "{local_id}"',
             f'auth = pubkey',
             f'certs = peer1.pem',
             f'auth = eap-mschapv2',
             f'eap_id = %any',
             f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
             f'rekey_time = {eap_lifetime}s',
             f'rand_time = 540s',
             f'dpd_action = clear',
             f'replay_window = 32',
             f'inactivity = 28800',
             f'local_ts = 0.0.0.0/0,::/0',
         ]
         for line in swanctl_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_secrets_lines = [
             f'eap-{conn_name}-{username}',
             f'secret = "{password}"',
             f'id-{conn_name}-{username} = "{username}"',
         ]
         for line in swanctl_secrets_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_pool_lines = [
             f'{ip_pool_name}',
             f'addrs = {prefix}',
             f'dns = {",".join(name_servers)}',
         ]
         for line in swanctl_pool_lines:
             self.assertIn(line, swanctl_conf)
 
         # Check Root CA, Intermediate CA and Peer cert/key pair is present
         self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}_1.pem')))
         self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))
 
         self.tearDownPKI()
 
     def test_remote_access_eap_tls(self):
         # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17
         self.setupPKI()
 
         ike_group = 'IKE-RW'
         esp_group = 'ESP-RW'
 
         conn_name = 'vyos-rw'
         local_address = '192.0.2.1'
         ip_pool_name = 'ra-rw-ipv4'
         username = 'vyos'
         password = 'secret'
         ike_lifetime = '7200'
         eap_lifetime = '3600'
         local_id = 'ipsec.vyos.net'
 
         name_servers = ['172.16.254.100', '172.16.254.101']
         prefix = '172.16.250.0/28'
 
         # IKE
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'hash', 'sha512'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'hash', 'sha256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'dh-group', '2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'hash', 'sha256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256'])
 
         # ESP
         self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
         self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'hash', 'sha512'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2',  'hash', 'sha384'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3',  'hash', 'sha256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4',  'hash', 'sha1'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
         # Use EAP-TLS auth instead of default EAP-MSCHAPv2
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'eap-tls'])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
         # verify() - CA cert required for x509 auth
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])
 
         for ns in name_servers:
             self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns])
         self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix])
 
         self.cli_commit()
 
         # verify applied configuration
         swanctl_conf = read_file(swanctl_file)
         swanctl_lines = [
             f'{conn_name}',
             f'remote_addrs = %any',
             f'local_addrs = {local_address}',
             f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048',
             f'version = 2',
             f'send_certreq = no',
             f'rekey_time = {ike_lifetime}s',
             f'keyingtries = 0',
             f'pools = {ip_pool_name}',
             f'id = "{local_id}"',
             f'auth = pubkey',
             f'certs = peer1.pem',
             f'auth = eap-tls',
             f'eap_id = %any',
             f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
             f'rekey_time = {eap_lifetime}s',
             f'rand_time = 540s',
             f'dpd_action = clear',
             f'inactivity = 28800',
             f'local_ts = 0.0.0.0/0,::/0',
         ]
         for line in swanctl_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_pool_lines = [
             f'{ip_pool_name}',
             f'addrs = {prefix}',
             f'dns = {",".join(name_servers)}',
         ]
         for line in swanctl_pool_lines:
             self.assertIn(line, swanctl_conf)
 
         # Check Root CA, Intermediate CA and Peer cert/key pair is present
         self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}_1.pem')))
         self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))
 
         self.tearDownPKI()
 
     def test_remote_access_x509(self):
         # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17
         self.setupPKI()
 
         ike_group = 'IKE-RW'
         esp_group = 'ESP-RW'
 
         conn_name = 'vyos-rw'
         local_address = '192.0.2.1'
         ip_pool_name = 'ra-rw-ipv4'
         ike_lifetime = '7200'
         eap_lifetime = '3600'
         local_id = 'ipsec.vyos.net'
 
         name_servers = ['172.16.254.100', '172.16.254.101']
         prefix = '172.16.250.0/28'
 
         # IKE
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'hash', 'sha512'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2',  'hash', 'sha256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'dh-group', '2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3',  'hash', 'sha256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256'])
 
         # ESP
         self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
         self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'hash', 'sha512'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2',  'hash', 'sha384'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3',  'hash', 'sha256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4',  'hash', 'sha1'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
         # Use client-mode x509 instead of default EAP-MSCHAPv2
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'x509'])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
         # verify() - CA cert required for x509 auth
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])
 
         for ns in name_servers:
             self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns])
         self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix])
 
         self.cli_commit()
 
         # verify applied configuration
         swanctl_conf = read_file(swanctl_file)
         swanctl_lines = [
             f'{conn_name}',
             f'remote_addrs = %any',
             f'local_addrs = {local_address}',
             f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048',
             f'version = 2',
             f'send_certreq = no',
             f'rekey_time = {ike_lifetime}s',
             f'keyingtries = 0',
             f'pools = {ip_pool_name}',
             f'id = "{local_id}"',
             f'auth = pubkey',
             f'certs = peer1.pem',
             f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
             f'rekey_time = {eap_lifetime}s',
             f'rand_time = 540s',
             f'dpd_action = clear',
             f'inactivity = 28800',
             f'local_ts = 0.0.0.0/0,::/0',
         ]
         for line in swanctl_lines:
             self.assertIn(line, swanctl_conf)
 
         swanctl_unexpected_lines = [
             f'auth = eap-',
             f'eap_id'
         ]
         for unexpected_line in swanctl_unexpected_lines:
             self.assertNotIn(unexpected_line, swanctl_conf)
 
         swanctl_pool_lines = [
             f'{ip_pool_name}',
             f'addrs = {prefix}',
             f'dns = {",".join(name_servers)}',
         ]
         for line in swanctl_pool_lines:
             self.assertIn(line, swanctl_conf)
 
         # Check Root CA, Intermediate CA and Peer cert/key pair is present
         self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}_1.pem')))
         self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))
 
         self.tearDownPKI()
 
     def test_remote_access_dhcp_fail_handling(self):
         # Skip process check - connection is not created for this test
         self.skip_process_check = True
 
         # Interface for dhcp-interface
         self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server
 
         # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17
         self.setupPKI()
 
         ike_group = 'IKE-RW'
         esp_group = 'ESP-RW'
 
         conn_name = 'vyos-rw'
         ip_pool_name = 'ra-rw-ipv4'
         username = 'vyos'
         password = 'secret'
         ike_lifetime = '7200'
         eap_lifetime = '3600'
         local_id = 'ipsec.vyos.net'
 
         name_server = '172.16.254.100'
         prefix = '172.16.250.0/28'
 
         # IKE
         self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
         self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'dh-group', '14'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1',  'hash', 'sha512'])
 
         # ESP
         self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'encryption', 'aes256'])
         self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1',  'hash', 'sha512'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])
 
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'dhcp-interface', f'{interface}.{vif}'])
         self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])
         self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', name_server])
         self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix])
 
         self.cli_commit()
 
-        self.assertTrue(os.path.exists(dhcp_waiting_file))
+        self.assertTrue(os.path.exists(dhcp_interfaces_file))
 
-        dhcp_waiting = read_file(dhcp_waiting_file)
-        self.assertIn(f'{interface}.{vif}', dhcp_waiting) # Ensure dhcp-failed interface was added for dhclient hook
+        dhcp_interfaces = read_file(dhcp_interfaces_file)
+        self.assertIn(f'{interface}.{vif}', dhcp_interfaces) # Ensure dhcp interface was added for dhclient hook
 
         self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address'])
 
         self.tearDownPKI()
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 64d0f6d9d..ebfb21903 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -1,642 +1,647 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-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/>.
 
 import ipaddress
 import os
 import re
 import jmespath
 
 from sys import exit
 from time import sleep
 from time import time
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.configdep import set_dependents
 from vyos.configdep import call_dependents
 from vyos.configdict import leaf_node_changed
 from vyos.configverify import verify_interface_exists
 from vyos.configverify import dynamic_interface_pattern
 from vyos.defaults import directories
 from vyos.ifconfig import Interface
 from vyos.pki import encode_certificate
 from vyos.pki import encode_public_key
 from vyos.pki import find_chain
 from vyos.pki import load_certificate
 from vyos.pki import load_private_key
 from vyos.pki import wrap_certificate
 from vyos.pki import wrap_crl
 from vyos.pki import wrap_public_key
 from vyos.pki import wrap_private_key
 from vyos.template import ip_from_cidr
 from vyos.template import is_ipv4
 from vyos.template import is_ipv6
 from vyos.template import render
 from vyos.utils.network import is_ipv6_link_local
 from vyos.utils.network import interface_exists
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_search_args
 from vyos.utils.process import call
 from vyos.utils.process import run
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 dhcp_wait_attempts = 2
 dhcp_wait_sleep = 1
 
 swanctl_dir        = '/etc/swanctl'
 charon_conf        = '/etc/strongswan.d/charon.conf'
 charon_dhcp_conf   = '/etc/strongswan.d/charon/dhcp.conf'
 charon_radius_conf = '/etc/strongswan.d/charon/eap-radius.conf'
 interface_conf     = '/etc/strongswan.d/interfaces_use.conf'
 swanctl_conf       = f'{swanctl_dir}/swanctl.conf'
 
 default_install_routes = 'yes'
 
 vici_socket = '/var/run/charon.vici'
 
 CERT_PATH   = f'{swanctl_dir}/x509/'
 PUBKEY_PATH = f'{swanctl_dir}/pubkey/'
 KEY_PATH    = f'{swanctl_dir}/private/'
 CA_PATH     = f'{swanctl_dir}/x509ca/'
 CRL_PATH    = f'{swanctl_dir}/x509crl/'
 
-DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_waiting'
+DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_interfaces'
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['vpn', 'ipsec']
     l2tp_base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings']
     if not conf.exists(base):
         return None
 
     # retrieve common dictionary keys
     ipsec = conf.get_config_dict(base, key_mangling=('-', '_'),
                                  no_tag_node_value_mangle=True,
                                  get_first_key=True,
                                  with_recursive_defaults=True,
                                  with_pki=True)
 
+    ipsec['dhcp_interfaces'] = set()
     ipsec['dhcp_no_address'] = {}
     ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
     ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface'])
     ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel'])
 
     if ipsec['nhrp_exists']:
         set_dependents('nhrp', conf)
 
     tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'),
                                no_tag_node_value_mangle=True,
                                get_first_key=True)
     if tmp:
         ipsec['l2tp'] = conf.merge_defaults(tmp, recursive=True)
         ipsec['l2tp_outside_address'] = conf.return_value(['vpn', 'l2tp', 'remote-access', 'outside-address'])
         ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024'
         ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1'
 
     return ipsec
 
 def get_dhcp_address(iface):
     addresses = Interface(iface).get_addr()
     if not addresses:
         return None
     for address in addresses:
         if not is_ipv6_link_local(address):
             return ip_from_cidr(address)
     return None
 
 def verify_pki_x509(pki, x509_conf):
     if not pki or 'ca' not in pki or 'certificate' not in pki:
         raise ConfigError(f'PKI is not configured')
 
     ca_cert_name = x509_conf['ca_certificate']
     cert_name = x509_conf['certificate']
 
     if not dict_search_args(pki, 'ca', ca_cert_name, 'certificate'):
         raise ConfigError(f'Missing CA certificate on specified PKI CA certificate "{ca_cert_name}"')
 
     if not dict_search_args(pki, 'certificate', cert_name, 'certificate'):
         raise ConfigError(f'Missing certificate on specified PKI certificate "{cert_name}"')
 
     if not dict_search_args(pki, 'certificate', cert_name, 'private', 'key'):
         raise ConfigError(f'Missing private key on specified PKI certificate "{cert_name}"')
 
     return True
 
 def verify_pki_rsa(pki, rsa_conf):
     if not pki or 'key_pair' not in pki:
         raise ConfigError(f'PKI is not configured')
 
     local_key = rsa_conf['local_key']
     remote_key = rsa_conf['remote_key']
 
     if not dict_search_args(pki, 'key_pair', local_key, 'private', 'key'):
         raise ConfigError(f'Missing private key on specified local-key "{local_key}"')
 
     if not dict_search_args(pki, 'key_pair', remote_key, 'public', 'key'):
         raise ConfigError(f'Missing public key on specified remote-key "{remote_key}"')
 
     return True
 
 def verify(ipsec):
     if not ipsec:
         return None
 
     if 'authentication' in ipsec:
         if 'psk' in ipsec['authentication']:
             for psk, psk_config in ipsec['authentication']['psk'].items():
                 if 'id' not in psk_config or 'secret' not in psk_config:
                     raise ConfigError(f'Authentication psk "{psk}" missing "id" or "secret"')
 
     if 'interface' in ipsec:
         tmp = re.compile(dynamic_interface_pattern)
         for interface in ipsec['interface']:
             # exclude check interface for dynamic interfaces
             if tmp.match(interface):
                 if not interface_exists(interface):
                     Warning(f'Interface "{interface}" does not exist yet and cannot be used '
                             f'for IPsec until it is up!')
             else:
                 verify_interface_exists(interface)
 
     if 'l2tp' in ipsec:
         if 'esp_group' in ipsec['l2tp']:
             if 'esp_group' not in ipsec or ipsec['l2tp']['esp_group'] not in ipsec['esp_group']:
                 raise ConfigError(f"Invalid esp-group on L2TP remote-access config")
 
         if 'ike_group' in ipsec['l2tp']:
             if 'ike_group' not in ipsec or ipsec['l2tp']['ike_group'] not in ipsec['ike_group']:
                 raise ConfigError(f"Invalid ike-group on L2TP remote-access config")
 
         if 'authentication' not in ipsec['l2tp']:
             raise ConfigError(f'Missing authentication settings on L2TP remote-access config')
 
         if 'mode' not in ipsec['l2tp']['authentication']:
             raise ConfigError(f'Missing authentication mode on L2TP remote-access config')
 
         if not ipsec['l2tp_outside_address']:
             raise ConfigError(f'Missing outside-address on L2TP remote-access config')
 
         if ipsec['l2tp']['authentication']['mode'] == 'pre-shared-secret':
             if 'pre_shared_secret' not in ipsec['l2tp']['authentication']:
                 raise ConfigError(f'Missing pre shared secret on L2TP remote-access config')
 
         if ipsec['l2tp']['authentication']['mode'] == 'x509':
             if 'x509' not in ipsec['l2tp']['authentication']:
                 raise ConfigError(f'Missing x509 settings on L2TP remote-access config')
 
             x509 = ipsec['l2tp']['authentication']['x509']
 
             if 'ca_certificate' not in x509 or 'certificate' not in x509:
                 raise ConfigError(f'Missing x509 certificates on L2TP remote-access config')
 
             verify_pki_x509(ipsec['pki'], x509)
 
     if 'profile' in ipsec:
         for profile, profile_conf in ipsec['profile'].items():
             if 'esp_group' in profile_conf:
                 if 'esp_group' not in ipsec or profile_conf['esp_group'] not in ipsec['esp_group']:
                     raise ConfigError(f"Invalid esp-group on {profile} profile")
             else:
                 raise ConfigError(f"Missing esp-group on {profile} profile")
 
             if 'ike_group' in profile_conf:
                 if 'ike_group' not in ipsec or profile_conf['ike_group'] not in ipsec['ike_group']:
                     raise ConfigError(f"Invalid ike-group on {profile} profile")
             else:
                 raise ConfigError(f"Missing ike-group on {profile} profile")
 
             if 'authentication' not in profile_conf:
                 raise ConfigError(f"Missing authentication on {profile} profile")
 
     if 'remote_access' in ipsec:
         if 'connection' in ipsec['remote_access']:
             for name, ra_conf in ipsec['remote_access']['connection'].items():
                 if 'local_address' not in ra_conf and 'dhcp_interface' not in ra_conf:
                     raise ConfigError(f"Missing local-address or dhcp-interface on remote-access connection {name}")
 
                 if 'dhcp_interface' in ra_conf:
                     dhcp_interface = ra_conf['dhcp_interface']
 
                     verify_interface_exists(dhcp_interface)
                     dhcp_base = directories['isc_dhclient_dir']
 
                     if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'):
                         raise ConfigError(f"Invalid dhcp-interface on remote-access connection {name}")
 
+                    ipsec['dhcp_interfaces'].add(dhcp_interface)
+
                     address = get_dhcp_address(dhcp_interface)
                     count = 0
                     while not address and count < dhcp_wait_attempts:
                         address = get_dhcp_address(dhcp_interface)
                         count += 1
                         sleep(dhcp_wait_sleep)
 
                     if not address:
                         ipsec['dhcp_no_address'][f'ra_{name}'] = dhcp_interface
                         print(f"Failed to get address from dhcp-interface on remote-access connection {name} -- skipped")
                         continue
 
                 if 'esp_group' in ra_conf:
                     if 'esp_group' not in ipsec or ra_conf['esp_group'] not in ipsec['esp_group']:
                         raise ConfigError(f"Invalid esp-group on {name} remote-access config")
                 else:
                     raise ConfigError(f"Missing esp-group on {name} remote-access config")
 
                 if 'ike_group' in ra_conf:
                     if 'ike_group' not in ipsec or ra_conf['ike_group'] not in ipsec['ike_group']:
                         raise ConfigError(f"Invalid ike-group on {name} remote-access config")
 
                     ike = ra_conf['ike_group']
                     if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
                         raise ConfigError('IPsec remote-access connections requires IKEv2!')
 
                 else:
                     raise ConfigError(f"Missing ike-group on {name} remote-access config")
 
                 if 'authentication' not in ra_conf:
                     raise ConfigError(f"Missing authentication on {name} remote-access config")
 
                 if ra_conf['authentication']['server_mode'] == 'x509':
                     if 'x509' not in ra_conf['authentication']:
                         raise ConfigError(f"Missing x509 settings on {name} remote-access config")
 
                     x509 = ra_conf['authentication']['x509']
 
                     if 'ca_certificate' not in x509 or 'certificate' not in x509:
                         raise ConfigError(f"Missing x509 certificates on {name} remote-access config")
 
                     verify_pki_x509(ipsec['pki'], x509)
                 elif ra_conf['authentication']['server_mode'] == 'pre-shared-secret':
                     if 'pre_shared_secret' not in ra_conf['authentication']:
                         raise ConfigError(f"Missing pre-shared-key on {name} remote-access config")
 
                 if 'client_mode' not in ra_conf['authentication']:
                     raise ConfigError('Client authentication method is required!')
 
                 if dict_search('authentication.client_mode', ra_conf) == 'eap-radius':
                     if dict_search('remote_access.radius.server', ipsec) == None:
                         raise ConfigError('RADIUS authentication requires at least one server')
 
                 if 'pool' in ra_conf:
                     if {'dhcp', 'radius'} <= set(ra_conf['pool']):
                         raise ConfigError(f'Can not use both DHCP and RADIUS for address allocation '\
                                           f'at the same time for "{name}"!')
 
                     if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1:
                         raise ConfigError(f'Can not use DHCP and a predefined address pool for "{name}"!')
 
                     if 'radius' in ra_conf['pool'] and len(ra_conf['pool']) > 1:
                         raise ConfigError(f'Can not use RADIUS and a predefined address pool for "{name}"!')
 
                     for pool in ra_conf['pool']:
                         if pool == 'dhcp':
                             if dict_search('remote_access.dhcp.server', ipsec) == None:
                                 raise ConfigError('IPsec DHCP server is not configured!')
                         elif pool == 'radius':
                             if dict_search('remote_access.radius.server', ipsec) == None:
                                 raise ConfigError('IPsec RADIUS server is not configured!')
 
                             if dict_search('authentication.client_mode', ra_conf) != 'eap-radius':
                                 raise ConfigError('RADIUS IP pool requires eap-radius client authentication!')
 
                         elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']:
                             raise ConfigError(f'Requested pool "{pool}" does not exist!')
 
         if 'pool' in ipsec['remote_access']:
             for pool, pool_config in ipsec['remote_access']['pool'].items():
                 if 'prefix' not in pool_config:
                     raise ConfigError(f'Missing madatory prefix option for pool "{pool}"!')
 
                 if 'name_server' in pool_config:
                     if len(pool_config['name_server']) > 2:
                         raise ConfigError(f'Only two name-servers are supported for remote-access pool "{pool}"!')
 
                     for ns in pool_config['name_server']:
                         v4_addr_and_ns = is_ipv4(ns) and not is_ipv4(pool_config['prefix'])
                         v6_addr_and_ns = is_ipv6(ns) and not is_ipv6(pool_config['prefix'])
                         if v4_addr_and_ns or v6_addr_and_ns:
                            raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix and name-server adresses!')
 
                 if 'exclude' in pool_config:
                     for exclude in pool_config['exclude']:
                         v4_addr_and_exclude = is_ipv4(exclude) and not is_ipv4(pool_config['prefix'])
                         v6_addr_and_exclude = is_ipv6(exclude) and not is_ipv6(pool_config['prefix'])
                         if v4_addr_and_exclude or v6_addr_and_exclude:
                            raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix and exclude prefixes!')
 
         if 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
             for server, server_config in ipsec['remote_access']['radius']['server'].items():
                 if 'key' not in server_config:
                     raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
 
     if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
         for peer, peer_conf in ipsec['site_to_site']['peer'].items():
             has_default_esp = False
             # Peer name it is swanctl connection name and shouldn't contain dots or colons, T4118
             if bool(re.search(':|\.', peer)):
                 raise ConfigError(f'Incorrect peer name "{peer}" '
                                   f'Peer name can contain alpha-numeric letters, hyphen and underscore')
 
             if 'remote_address' not in peer_conf:
                 print(f'You should set correct remote-address "peer {peer} remote-address x.x.x.x"\n')
 
             if 'default_esp_group' in peer_conf:
                 has_default_esp = True
                 if 'esp_group' not in ipsec or peer_conf['default_esp_group'] not in ipsec['esp_group']:
                     raise ConfigError(f"Invalid esp-group on site-to-site peer {peer}")
 
             if 'ike_group' in peer_conf:
                 if 'ike_group' not in ipsec or peer_conf['ike_group'] not in ipsec['ike_group']:
                     raise ConfigError(f"Invalid ike-group on site-to-site peer {peer}")
             else:
                 raise ConfigError(f"Missing ike-group on site-to-site peer {peer}")
 
             if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']:
                 raise ConfigError(f"Missing authentication on site-to-site peer {peer}")
 
             if {'id', 'use_x509_id'} <= set(peer_conf['authentication']):
                 raise ConfigError(f"Manually set peer id and use-x509-id are mutually exclusive!")
 
             if peer_conf['authentication']['mode'] == 'x509':
                 if 'x509' not in peer_conf['authentication']:
                     raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}")
 
                 x509 = peer_conf['authentication']['x509']
 
                 if 'ca_certificate' not in x509 or 'certificate' not in x509:
                     raise ConfigError(f"Missing x509 certificates on site-to-site peer {peer}")
 
                 verify_pki_x509(ipsec['pki'], x509)
             elif peer_conf['authentication']['mode'] == 'rsa':
                 if 'rsa' not in peer_conf['authentication']:
                     raise ConfigError(f"Missing RSA settings on site-to-site peer {peer}")
 
                 rsa = peer_conf['authentication']['rsa']
 
                 if 'local_key' not in rsa:
                     raise ConfigError(f"Missing RSA local-key on site-to-site peer {peer}")
 
                 if 'remote_key' not in rsa:
                     raise ConfigError(f"Missing RSA remote-key on site-to-site peer {peer}")
 
                 verify_pki_rsa(ipsec['pki'], rsa)
 
             if 'local_address' not in peer_conf and 'dhcp_interface' not in peer_conf:
                 raise ConfigError(f"Missing local-address or dhcp-interface on site-to-site peer {peer}")
 
             if 'dhcp_interface' in peer_conf:
                 dhcp_interface = peer_conf['dhcp_interface']
 
                 verify_interface_exists(dhcp_interface)
                 dhcp_base = directories['isc_dhclient_dir']
 
                 if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'):
                     raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}")
 
+                ipsec['dhcp_interfaces'].add(dhcp_interface)
+
                 address = get_dhcp_address(dhcp_interface)
                 count = 0
                 while not address and count < dhcp_wait_attempts:
                     address = get_dhcp_address(dhcp_interface)
                     count += 1
                     sleep(dhcp_wait_sleep)
 
                 if not address:
                     ipsec['dhcp_no_address'][f'peer_{peer}'] = dhcp_interface
                     print(f"Failed to get address from dhcp-interface on site-to-site peer {peer} -- skipped")
                     continue
 
             if 'vti' in peer_conf:
                 if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf:
                     raise ConfigError(f"A single local-address or dhcp-interface is required when using VTI on site-to-site peer {peer}")
 
                 if dict_search('options.disable_route_autoinstall',
                                ipsec) == None:
                     Warning('It\'s recommended to use ipsec vti with the next command\n[set vpn ipsec option disable-route-autoinstall]')
 
                 if 'bind' in peer_conf['vti']:
                     vti_interface = peer_conf['vti']['bind']
                     if not interface_exists(vti_interface):
                         raise ConfigError(f'VTI interface {vti_interface} for site-to-site peer {peer} does not exist!')
 
             if 'vti' not in peer_conf and 'tunnel' not in peer_conf:
                 raise ConfigError(f"No VTI or tunnel specified on site-to-site peer {peer}")
 
             if 'tunnel' in peer_conf:
                 for tunnel, tunnel_conf in peer_conf['tunnel'].items():
                     if 'esp_group' not in tunnel_conf and not has_default_esp:
                         raise ConfigError(f"Missing esp-group on tunnel {tunnel} for site-to-site peer {peer}")
 
                     esp_group_name = tunnel_conf['esp_group'] if 'esp_group' in tunnel_conf else peer_conf['default_esp_group']
 
                     if esp_group_name not in ipsec['esp_group']:
                         raise ConfigError(f"Invalid esp-group on tunnel {tunnel} for site-to-site peer {peer}")
 
                     esp_group = ipsec['esp_group'][esp_group_name]
 
                     if 'mode' in esp_group and esp_group['mode'] == 'transport':
                         if 'protocol' in tunnel_conf and ((peer in ['any', '0.0.0.0']) or ('local_address' not in peer_conf or peer_conf['local_address'] in ['any', '0.0.0.0'])):
                             raise ConfigError(f"Fixed local-address or peer required when a protocol is defined with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
 
                         if ('local' in tunnel_conf and 'prefix' in tunnel_conf['local']) or ('remote' in tunnel_conf and 'prefix' in tunnel_conf['remote']):
                             raise ConfigError(f"Local/remote prefix cannot be used with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
 
 def cleanup_pki_files():
     for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH, PUBKEY_PATH]:
         if not os.path.exists(path):
             continue
         for file in os.listdir(path):
             file_path = os.path.join(path, file)
             if os.path.isfile(file_path):
                 os.unlink(file_path)
 
 def generate_pki_files_x509(pki, x509_conf):
     ca_cert_name = x509_conf['ca_certificate']
     ca_cert_data = dict_search_args(pki, 'ca', ca_cert_name, 'certificate')
     ca_cert_crls = dict_search_args(pki, 'ca', ca_cert_name, 'crl') or []
     ca_index = 1
     crl_index = 1
 
     ca_cert = load_certificate(ca_cert_data)
     pki_ca_certs = [load_certificate(ca['certificate']) for ca in pki['ca'].values()]
 
     ca_cert_chain = find_chain(ca_cert, pki_ca_certs)
 
     cert_name = x509_conf['certificate']
     cert_data = dict_search_args(pki, 'certificate', cert_name, 'certificate')
     key_data = dict_search_args(pki, 'certificate', cert_name, 'private', 'key')
     protected = 'passphrase' in x509_conf
 
     for ca_cert_obj in ca_cert_chain:
         with open(os.path.join(CA_PATH, f'{ca_cert_name}_{ca_index}.pem'), 'w') as f:
             f.write(encode_certificate(ca_cert_obj))
         ca_index += 1
 
     for crl in ca_cert_crls:
         with open(os.path.join(CRL_PATH, f'{ca_cert_name}_{crl_index}.pem'), 'w') as f:
             f.write(wrap_crl(crl))
         crl_index += 1
 
     with open(os.path.join(CERT_PATH, f'{cert_name}.pem'), 'w') as f:
         f.write(wrap_certificate(cert_data))
 
     with open(os.path.join(KEY_PATH, f'x509_{cert_name}.pem'), 'w') as f:
         f.write(wrap_private_key(key_data, protected))
 
 def generate_pki_files_rsa(pki, rsa_conf):
     local_key_name = rsa_conf['local_key']
     local_key_data = dict_search_args(pki, 'key_pair', local_key_name, 'private', 'key')
     protected = 'passphrase' in rsa_conf
     remote_key_name = rsa_conf['remote_key']
     remote_key_data = dict_search_args(pki, 'key_pair', remote_key_name, 'public', 'key')
 
     local_key = load_private_key(local_key_data, rsa_conf['passphrase'] if protected else None)
 
     with open(os.path.join(KEY_PATH, f'rsa_{local_key_name}.pem'), 'w') as f:
         f.write(wrap_private_key(local_key_data, protected))
 
     with open(os.path.join(PUBKEY_PATH, f'{local_key_name}.pem'), 'w') as f:
         f.write(encode_public_key(local_key.public_key()))
 
     with open(os.path.join(PUBKEY_PATH, f'{remote_key_name}.pem'), 'w') as f:
         f.write(wrap_public_key(remote_key_data))
 
 def generate(ipsec):
     cleanup_pki_files()
 
     if not ipsec:
         for config_file in [charon_dhcp_conf, charon_radius_conf, interface_conf, swanctl_conf]:
             if os.path.isfile(config_file):
                 os.unlink(config_file)
         render(charon_conf, 'ipsec/charon.j2', {'install_routes': default_install_routes})
         return
 
-    if ipsec['dhcp_no_address']:
+    if ipsec['dhcp_interfaces']:
         with open(DHCP_HOOK_IFLIST, 'w') as f:
-            f.write(" ".join(ipsec['dhcp_no_address'].values()))
+            f.write(" ".join(ipsec['dhcp_interfaces']))
     elif os.path.exists(DHCP_HOOK_IFLIST):
         os.unlink(DHCP_HOOK_IFLIST)
 
     for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH, PUBKEY_PATH]:
         if not os.path.exists(path):
             os.mkdir(path, mode=0o755)
 
     if not os.path.exists(KEY_PATH):
         os.mkdir(KEY_PATH, mode=0o700)
 
     if 'l2tp' in ipsec:
         if 'authentication' in ipsec['l2tp'] and 'x509' in ipsec['l2tp']['authentication']:
             generate_pki_files_x509(ipsec['pki'], ipsec['l2tp']['authentication']['x509'])
 
     if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']:
         for rw, rw_conf in ipsec['remote_access']['connection'].items():
             if f'ra_{rw}' in ipsec['dhcp_no_address']:
                 continue
 
             local_ip = ''
             if 'local_address' in rw_conf:
                 local_ip = rw_conf['local_address']
             elif 'dhcp_interface' in rw_conf:
                 local_ip = get_dhcp_address(rw_conf['dhcp_interface'])
 
             ipsec['remote_access']['connection'][rw]['local_address'] = local_ip
 
             if 'authentication' in rw_conf and 'x509' in rw_conf['authentication']:
                 generate_pki_files_x509(ipsec['pki'], rw_conf['authentication']['x509'])
 
     if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
         for peer, peer_conf in ipsec['site_to_site']['peer'].items():
             if f'peer_{peer}' in ipsec['dhcp_no_address']:
                 continue
 
             if peer_conf['authentication']['mode'] == 'x509':
                 generate_pki_files_x509(ipsec['pki'], peer_conf['authentication']['x509'])
             elif peer_conf['authentication']['mode'] == 'rsa':
                 generate_pki_files_rsa(ipsec['pki'], peer_conf['authentication']['rsa'])
 
             local_ip = ''
             if 'local_address' in peer_conf:
                 local_ip = peer_conf['local_address']
             elif 'dhcp_interface' in peer_conf:
                 local_ip = get_dhcp_address(peer_conf['dhcp_interface'])
 
             ipsec['site_to_site']['peer'][peer]['local_address'] = local_ip
 
             if 'tunnel' in peer_conf:
                 for tunnel, tunnel_conf in peer_conf['tunnel'].items():
                     local_prefixes = dict_search_args(tunnel_conf, 'local', 'prefix')
                     remote_prefixes = dict_search_args(tunnel_conf, 'remote', 'prefix')
 
                     if not local_prefixes or not remote_prefixes:
                         continue
 
                     passthrough = None
 
                     for local_prefix in local_prefixes:
                         for remote_prefix in remote_prefixes:
                             local_net = ipaddress.ip_network(local_prefix)
                             remote_net = ipaddress.ip_network(remote_prefix)
                             if local_net.overlaps(remote_net):
                                 if passthrough is None:
                                     passthrough = []
                                 passthrough.append(local_prefix)
 
                     ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
 
         # auth psk <tag> dhcp-interface <xxx>
         if jmespath.search('authentication.psk.*.dhcp_interface', ipsec):
             for psk, psk_config in ipsec['authentication']['psk'].items():
                 if 'dhcp_interface' in psk_config:
                     for iface in psk_config['dhcp_interface']:
                         id = get_dhcp_address(iface)
                         if id:
                             ipsec['authentication']['psk'][psk]['id'].append(id)
 
     render(charon_conf, 'ipsec/charon.j2', ipsec)
     render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.j2', ipsec)
     render(charon_radius_conf, 'ipsec/charon/eap-radius.conf.j2', ipsec)
     render(interface_conf, 'ipsec/interfaces_use.conf.j2', ipsec)
     render(swanctl_conf, 'ipsec/swanctl.conf.j2', ipsec)
 
 
 def apply(ipsec):
     systemd_service = 'strongswan.service'
     if not ipsec:
         call(f'systemctl stop {systemd_service}')
     else:
         call(f'systemctl reload-or-restart {systemd_service}')
 
         if ipsec.get('nhrp_exists', False):
             try:
                 call_dependents()
             except ConfigError:
                 # Ignore config errors on dependent due to being called too early. Example:
                 # ConfigError("ConfigError('Interface ethN requires an IP address!')")
                 pass
 
 
 if __name__ == '__main__':
     try:
         ipsec = get_config()
         verify(ipsec)
         generate(ipsec)
         apply(ipsec)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook
index 3f0c9cb7a..ebb100e8b 100755
--- a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook
@@ -1,73 +1,45 @@
 #!/bin/bash
 #
 # Copyright (C) 2021 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/>.
 
-DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting"
+DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_interfaces"
 
-if [ -f $DHCP_HOOK_IFLIST ] && [ "$reason" == "BOUND" ]; then
-    if grep -qw $interface $DHCP_HOOK_IFLIST; then
-        sudo rm $DHCP_HOOK_IFLIST
-        sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py
-        exit 0
-    fi
+if ! { [ -f $DHCP_HOOK_IFLIST ] && grep -qw $interface $DHCP_HOOK_IFLIST; }; then
+    exit 0
 fi
 
+# Re-generate the config on the following events:
+# - BOUND: always re-generate
+# - RENEW: re-generate if the IP address changed
+# - REBIND: re-generate if the IP address changed
 if [ "$reason" == "RENEW" ] || [ "$reason" == "REBIND" ]; then
     if [ "$old_ip_address" == "$new_ip_address" ]; then
         exit 0
     fi
-else
+elif [ "$reason" != "BOUND" ]; then
     exit 0
 fi
 
-python3 - <<PYEND
-import os
-import re
-
-from vyos.utils.process import call
-from vyos.utils.process import cmd
-from vyos.utils.file import read_file
-from vyos.utils.file import write_file
-
-SWANCTL_CONF="/etc/swanctl/swanctl.conf"
+# Best effort wait for any active commit to finish
+sudo python3 - <<PYEND
+from vyos.utils.commit import wait_for_commit_lock
 
 if __name__ == '__main__':
-    interface = os.getenv('interface')
-    new_ip = os.getenv('new_ip_address')
-    old_ip = os.getenv('old_ip_address')
-
-    if os.path.exists(SWANCTL_CONF):
-        conf_lines = read_file(SWANCTL_CONF).split("\n")
-        found = False
-        reset_conns = set()
-        to_match = f'# dhcp:{interface}'
-
-        for i, line in enumerate(conf_lines):
-            if line.find(to_match) > 0:
-                conf_lines[i] = line.replace(old_ip, new_ip)
-                found = True
-                regex_match = re.search(r'#.* reset:([-_a-zA-Z0-9|@]+)', line)
-                if regex_match:
-                   connection_name = regex_match[1]
-                   reset_conns.add(connection_name)
-
-        if found:
-            write_file(SWANCTL_CONF, "\n".join(conf_lines))
-            for connection_name in reset_conns:
-                call(f'sudo swanctl -t -i {connection_name}')
-            call('sudo swanctl -q')
-
+    wait_for_commit_lock()
     exit(0)
 PYEND
+
+# Now re-generate the config
+sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py