diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rwxr-xr-x | scripts/vpn-config.pl | 5 | ||||
-rwxr-xr-x | scripts/vti-up-down | 5 | ||||
-rwxr-xr-x | scripts/vyatta-ipsec-dhcp.pl | 104 | ||||
-rwxr-xr-x | scripts/vyatta-ipsec-dhcp.py | 220 | ||||
-rwxr-xr-x | scripts/vyatta-vti-config.pl | 40 |
6 files changed, 266 insertions, 112 deletions
diff --git a/Makefile.am b/Makefile.am index 1dc45b1..2da9b18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,7 +5,7 @@ etcudevdir = /etc/udev initddir = /etc/init.d logrotatedir = /etc/logrotate.d curverdir = $(sysconfdir)/config-migrate/current -bin_sudo_usersdir = $(bindir)/sudo-users +bin_sudo_usersdir = /usr/libexec/vyos/system sbin_SCRIPTS = @@ -14,7 +14,7 @@ sbin_SCRIPTS += scripts/dmvpn-config.pl sbin_SCRIPTS += scripts/vyatta-vpn-ppp-updown.pl sbin_SCRIPTS += scripts/vyatta-vti-config.pl -bin_sudo_users_SCRIPTS = scripts/vyatta-ipsec-dhcp.pl +bin_sudo_users_SCRIPTS = scripts/vyatta-ipsec-dhcp.py share_perl5_DATA = lib/Vyatta/VPN/Util.pm share_perl5_DATA += lib/Vyatta/VPN/vtiIntf.pm diff --git a/scripts/vpn-config.pl b/scripts/vpn-config.pl index ace33e8..d68e419 100755 --- a/scripts/vpn-config.pl +++ b/scripts/vpn-config.pl @@ -1078,6 +1078,9 @@ if ($vcVPN->exists('ipsec')) { vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"vti","bind"],"$vpn_cfg_err No interface bind specified for peer \"$peer\" vti\n"); } $genout .= "\tleftupdown=\"/usr/lib/ipsec/vti-up-down $tunName\"\n"; + if (defined($dhcp_iface)){ + $dhcp_if = $dhcp_if + 1; + } } # @@ -1522,7 +1525,7 @@ sub dhcp_hook { if ($dhcp_iface > 0){ $str =<<EOS; #!/bin/sh -/opt/vyatta/bin/sudo-users/vyatta-ipsec-dhcp.pl --interface=\"\$interface\" --new_ip=\"\$new_ip_address\" --reason=\"\$reason\" --old_ip=\"\$old_ip_address\" +/usr/libexec/vyos/system/vyatta-ipsec-dhcp.py --interface=\"\$interface\" --new_ip=\"\$new_ip_address\" --reason=\"\$reason\" --old_ip=\"\$old_ip_address\" EOS } my $hook = "/etc/dhcp/dhclient-exit-hooks.d/ipsecd"; diff --git a/scripts/vti-up-down b/scripts/vti-up-down index 8d363da..08e31c0 100755 --- a/scripts/vti-up-down +++ b/scripts/vti-up-down @@ -5,14 +5,15 @@ source /etc/default/vyatta source /etc/default/locale case "$PLUTO_VERB" in -route-client | up-client) +route-client | up-client | up-host) /bin/ip route delete default table 220 /opt/vyatta/sbin/vyatta-vti-config.pl --updown --intf=$1 --action=up ;; -down-client) +down-client | down-host) /opt/vyatta/sbin/vyatta-vti-config.pl --updown --intf=$1 --action=down ;; *) ;; esac exit 0 + diff --git a/scripts/vyatta-ipsec-dhcp.pl b/scripts/vyatta-ipsec-dhcp.pl deleted file mode 100755 index 6b8782c..0000000 --- a/scripts/vyatta-ipsec-dhcp.pl +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/perl -use Getopt::Long; -use strict; - -my $config_file = "/etc/ipsec.conf"; -my $secrets_file = "/etc/ipsec.secrets"; - -sub logger { - my $msg = pop(@_); - my $FACILITY = "daemon"; - my $LEVEL = "notice"; - my $TAG = "ipsec-dhclient-hook"; - my $LOGCMD = "logger -t $TAG -p $FACILITY.$LEVEL"; - system("$LOGCMD $msg"); -} - -my ($iface, $config_iface, $nip, $oip, $reason); -GetOptions("interface=s" => \$iface, - "new_ip=s" => \$nip, - "old_ip=s" => \$oip, - "reason=s" => \$reason); - -# check if an update is needed -exit(0) if (($oip eq $nip) && ($reason ne "BOUND")); -logger("DHCP address updated to $nip from $oip: Updating ipsec configuration."); - -# open ipsec config -open (my $FD, '<', $config_file); -my $header = ''; -my $footer = ''; -my $finheader = 0; -my %connhash = (); -my $curconn = ''; -foreach my $line (<$FD>){ - next if (($line =~/^\s*$/) && $finheader); - if ($line =~ /\#conn.*/){ - $curconn = ''; - next; - } - if ($line =~ /(peer-.*-tunnel.*)/){ - $finheader = 1; - my $connid = $1; - $curconn = $connid; - if (not exists $connhash{$connid}){ - $connhash{$connid} = { - _dhcp_iface => undef, - _lip => undef, - _lines => [] - }; - } - } elsif (($line =~ /dhcp-interface=(.*)/) && ($curconn ne '') ){ - $connhash{$curconn}->{_dhcp_iface}=$1; - } elsif (($line =~ /left=(.*)/) && ($curconn ne '') ){ - $connhash{$curconn}->{_lip}=$1; - } elsif (!$finheader){ - $header .= $line; - } elsif ($curconn ne ''){ - push (@{$connhash{"$curconn"}->{_lines}}, $line); - } elsif ($curconn eq ''){ - $footer .= $line; - } -} -close($FD); - -# output new ipsec.conf -open my $output_config, '>', $config_file - or die "Can't open $config_file: $!"; - -print ${output_config} "$header\n"; -foreach my $connid ( keys (%connhash)){ - print ${output_config} "conn $connid\n"; - if (defined($connhash{$connid}->{_dhcp_iface})){ - if ($connhash{$connid}->{_dhcp_iface} eq $iface){ - $connhash{$connid}->{_lip} = $nip; - } - print ${output_config} "\t\#dhcp-interface=$connhash{$connid}->{_dhcp_iface}\n"; - } - print ${output_config} "\tleft=$connhash{$connid}->{_lip}\n"; - foreach my $line (@{$connhash{$connid}->{_lines}}){ - print ${output_config} $line; - } - print ${output_config} "\#conn $connid\n\n"; -} -print ${output_config} "$footer\n"; -close $output_config; - -# change ipsec.secrets -open (my $FD, '<', $secrets_file); -my @lines = <$FD>; -close FD; -open my $output_secrets, '>', $secrets_file - or die "Can't open $secrets_file"; -foreach my $line (@lines){ - if (($line =~ /(.*)\#dhcp-interface=(.*)\#/) && ($2 eq $iface)){ - my $secretline = $1; - $nip = "#" if ($nip eq ''); - $secretline =~ /(.*?) (.*?) : PSK (.*)/; - $line = "$nip $2 : PSK $3\#dhcp-interface=$iface\#\n"; - } - print ${output_secrets} $line; -} -close $output_secrets; -system ("/usr/sbin/ipsec rereadall > /dev/null 2>&1"); -system ("/usr/sbin/ipsec update > /dev/null 2>&1"); diff --git a/scripts/vyatta-ipsec-dhcp.py b/scripts/vyatta-ipsec-dhcp.py new file mode 100755 index 0000000..5aaa86e --- /dev/null +++ b/scripts/vyatta-ipsec-dhcp.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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 re +import sys +import subprocess +import argparse +import syslog +import time +import vici + +import vyos.config + +config_file = "/etc/ipsec.conf"; +secrets_file = "/etc/ipsec.secrets"; + + +def parse_cli_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--interface", type=str) + parser.add_argument("--new_ip", type=str) + parser.add_argument("--old_ip", type=str) + parser.add_argument("--reason", type=str) + args = parser.parse_args() + return args + + +def ipsec_conf_r(): + header = '' + footer = '' + finheader = 0 + connlist = list() + conndict = dict() + curconn = '' + + with open(config_file) as f: + for line in f: + if re.search('^\s*$', line) and finheader: + continue + if re.search('\#conn.*', line): + curconn = '' + continue + if re.search('(peer-.*-tunnel.*)', line): + finheader = 1 + connid = re.search(r'(peer-.*-tunnel.*)', line).group(1) + curconn = connid + if connid not in connlist: + conndict[connid] = dict() + conndict[connid]['_dhcp_iface'] = None + conndict[connid]['_lip'] = None + conndict[connid]['_lines'] = list() + elif re.search('dhcp-interface=(.*)', line) and curconn != '': + conndict[connid]['_dhcp_iface'] = re.search(r'dhcp-interface=(.*)', line).group(1) + elif re.search('left=(.*)', line) and curconn != '': + conndict[connid]['_lip'] = re.search(r'left=(.*)', line).group(1) + elif not finheader: + header = header + line + elif curconn != '': + conndict[connid]['_lines'].append(line) + elif curconn == '': + footer = footer + line; + connlist.append(conndict) + return connlist, header, footer + + +def ipsec_conf_w(connlist, header, footer, interface, new_ip): + try: + with open(config_file, 'w') as f: + f.write('{0}\n'.format(header)) + for connid in connlist: + connname = next(iter(connid)) + f.write('conn {0}\n'.format(connname)) + if connid[connname]['_dhcp_iface']: + if connid[connname]['_dhcp_iface'] == interface: + if not new_ip: + new_ip = '' + connid[connname]['_lip'] = new_ip + f.write('\t#dhcp-interface={0}\n'.format(connid[connname]['_dhcp_iface'])) + f.write('\tleft={0}\n'.format(connid[connname]['_lip'])) + for line in connid[connname]['_lines']: + f.write('{0}'.format(line)) + f.write('#conn {0}\n\n'.format(connname)) + f.write('{0}\n'.format(footer)) + except EnvironmentError as e: + sys.exit('Can\'t open {0}: {1}'.format(config_file, e)) + + +def ipsec_sec_r(): + lines = [] + + with open(secrets_file) as f: + lines = [line for line in f] + return lines + + +def ipsec_sec_w(lines, interface, new_ip): + try: + with open(secrets_file, 'w') as f: + for line in lines: + if re.search('(.*)\#dhcp-interface=(.*)\#', line) and \ + re.search('(.*)\#dhcp-interface=(.*)\#', line).group(2) == interface: + secretline = re.search('(.*)\#dhcp-interface=(.*)\#', line).group(1) + if not new_ip: + new_ip = "#" + secline = re.search('(.*?) (.*?) : PSK (.*?) #dhcp', line) + line = '{0} {1} : PSK {2} #dhcp-interface={3}#\n'.format(new_ip, + secline.group(2), secline.group(3), interface) + f.write('{0}'.format(line)) + except EnvironmentError as e: + sys.exit('Can\'t open {0}: {1}'.format(config_file, e)) + + +def conn_list(): + v = vici.Session() + config = vyos.config.Config() + config_conns = config.list_effective_nodes("vpn ipsec site-to-site peer") + connup = [] + + + v = vici.Session() + for conn in v.list_sas(): + for key in conn: + for c_conn in config_conns: + if c_conn in key: + if config.return_effective_value("vpn ipsec site-to-site peer {0} dhcp-interface".format(c_conn)): + connup.append(key) + + return connup + + +def run(*popenargs, input=None, check=False, **kwargs): + if input is not None: + if 'stdin' in kwargs: + raise ValueError('stdin and input arguments may not both be used.') + kwargs['stdin'] = subprocess.PIPE + + process = subprocess.Popen(*popenargs, **kwargs) + try: + stdout, stderr = process.communicate(input) + except: + process.kill() + process.wait() + raise + retcode = process.poll() + if check and retcode: + raise subprocess.CalledProcessError( + retcode, process.args, output=stdout, stderr=stderr) + return retcode, stdout, stderr + + +def term_conn(active_conn): + v = vici.Session() + for conn in active_conn: + try: + list(v.terminate({"ike": conn, "force": "true"})) + except: + pass + + +def reload_conn(): + run(["/usr/sbin/ipsec", "rereadall"], stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL) + run(["/usr/sbin/ipsec", "update"], stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL) + + +def init_conn(active_conn, updated_conn): + v = vici.Session() + for conn in active_conn: + if conn not in updated_conn: + list(v.initiate({"child": conn, "timeout": "10000"})) + + +def main(): + args = parse_cli_args() + syslog.openlog('ipsec-dhclient-hook') + + syslog.syslog(syslog.LOG_NOTICE, 'Receive DHCP address updated to {0} from {1}' + ', reason: {2}.'.format(args.new_ip, args.old_ip, args.reason)) + + if args.old_ip == args.new_ip and args.reason != 'BOUND' or args.reason == 'REBOOT' or args.reason == 'EXPIRE': + syslog.syslog(syslog.LOG_NOTICE, 'No ipsec update needed.') + sys.exit(0) + + syslog.syslog(syslog.LOG_NOTICE, 'DHCP address updated to {0} from {1}: ' + ' Updating ipsec configuration, reason: {2}.'.format(args.new_ip, args.old_ip, args.reason)) + + connlist, header, footer = ipsec_conf_r() + ipsec_conf_w(connlist, header, footer, args.interface, args.new_ip) + + lines = ipsec_sec_r() + ipsec_sec_w(lines, args.interface, args.new_ip) + + if args.new_ip: + active_conn = conn_list() + term_conn(active_conn) + reload_conn() + time.sleep(5) + updated_conn = conn_list() + init_conn(active_conn, updated_conn) + + +if __name__ == '__main__': + main() diff --git a/scripts/vyatta-vti-config.pl b/scripts/vyatta-vti-config.pl index 81abf97..0886202 100755 --- a/scripts/vyatta-vti-config.pl +++ b/scripts/vyatta-vti-config.pl @@ -129,9 +129,12 @@ foreach my $peer (@peers) { my $change = 0; # Check local address is valid. - if (!defined($lip)) { - print STDERR "$vti_cfg_err local-address not defined.\n"; - exit -1; + my $dhcp_iface = $vcVPN->returnValue("ipsec site-to-site peer $peer dhcp-interface"); + if (defined($lip) && defined($dhcp_iface)){ + vti_die(["vpn","ipsec","site-to-site","peer",$peer],"$vti_cfg_err Only one of local-address or dhcp-interface may be defined"); + } + if (defined($dhcp_iface)){ + $lip = get_dhcp_addr($dhcp_iface, $peer); } if (!(validateType('ipv4', $lip, 'quiet') || validateType('ipv6', $lip, 'quiet')) || ($lip eq '0.0.0.0')) { @@ -232,9 +235,25 @@ sub vti_handle_updown { $vcIntf->setLevel('interfaces'); my $disabled = $vcIntf->existsOrig("vti $intfName disabled"); if (!defined($disabled) || !$disabled) { + my $vcVPN = new Vyatta::Config(); + $vcVPN->setLevel('vpn ipsec site-to-site'); + my @peers = $vcVPN->listOrigNodes('peer'); my $vtiInterface = new Vyatta::Interface($intfName); my $state = $vtiInterface->up(); if (!($state && ($action eq "up"))) { + if ($action eq "up") { + foreach my $peer (@peers) { + if (!$vcVPN->existsOrig("peer $peer vti bind $intfName")) { + next; + } + + my $dhcp_iface = $vcVPN->returnOrigValue("peer $peer dhcp-interface"); + if (defined($dhcp_iface)) { + my $lip = get_dhcp_addr($dhcp_iface, $peer); + system("sudo /sbin/ip tunnel change $intfName local $lip\n"); + } + } + } system("sudo /sbin/ip link set $intfName $action\n"); } } @@ -315,3 +334,18 @@ sub checkUnrefIntfVti { } } } + +sub get_dhcp_addr { + my ($dhcp_iface, $peer) = @_; + vti_die(["vpn","ipsec","site-to-site","peer",$peer,"dhcp-interface"],"$vti_cfg_err The specified interface is not configured for dhcp.") + if (!(Vyatta::Misc::is_dhcp_enabled($dhcp_iface,0))); + my @dhcp_addr = Vyatta::Misc::getIP($dhcp_iface,4); + my $addr = pop(@dhcp_addr); + if (!defined($addr)){ + $addr = ''; + return $addr; + } + @dhcp_addr = split(/\//, $addr); + $addr = $dhcp_addr[0]; + return $addr; +} |