diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 49 | ||||
| -rwxr-xr-x | src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook | 77 | 
2 files changed, 64 insertions, 62 deletions
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 388f2a709..ebfb21903 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -75,7 +75,7 @@ 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: @@ -94,6 +94,7 @@ def get_config(config=None):                                   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']) @@ -229,6 +230,32 @@ def verify(ipsec):      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") @@ -386,6 +413,8 @@ def verify(ipsec):                  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: @@ -394,7 +423,7 @@ def verify(ipsec):                      sleep(dhcp_wait_sleep)                  if not address: -                    ipsec['dhcp_no_address'][peer] = dhcp_interface +                    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 @@ -503,9 +532,9 @@ def generate(ipsec):          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) @@ -522,13 +551,23 @@ def generate(ipsec):      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 peer in ipsec['dhcp_no_address']: +            if f'peer_{peer}' in ipsec['dhcp_no_address']:                  continue              if peer_conf['authentication']['mode'] == 'x509': 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 e6edc1ac3..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 @@ -14,69 +14,32 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -if [ "$reason" == "REBOOT" ] || [ "$reason" == "EXPIRE" ]; then -    return 0 -fi - -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 -        return 0 -    fi +if ! { [ -f $DHCP_HOOK_IFLIST ] && grep -qw $interface $DHCP_HOOK_IFLIST; }; then +    exit 0  fi -if [ "$old_ip_address" == "$new_ip_address" ] && [ "$reason" == "BOUND" ]; then -    return 0 +# 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 +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" - -def ipsec_down(ip_address): -    # This prevents the need to restart ipsec and kill all active connections, only the stale connection is closed -    status = cmd('sudo ipsec statusall') -    connection_name = None -    for line in status.split("\n"): -        if line.find(ip_address) > 0: -            regex_match = re.search(r'(peer_[^:\[]+)', line) -            if regex_match: -                connection_name = regex_match[1] -                break -    if connection_name: -        call(f'sudo ipsec down {connection_name}') +# 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) -        found = False -        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 - -        if found: -            write_file(SWANCTL_CONF, conf_lines) -            ipsec_down(old_ip) -            call('sudo ipsec rereadall') -            call('sudo ipsec reload') -            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  | 
