diff options
author | Seth Mos <seth.mos@xs4all.nl> | 2009-01-15 09:00:30 +0000 |
---|---|---|
committer | Seth Mos <seth.mos@xs4all.nl> | 2009-01-15 09:00:30 +0000 |
commit | bf92bc791de6b04246c7a2f67945ce1412906d95 (patch) | |
tree | 7f0663d3aeeeb1773e9e6bb0127517d3310b2b11 /etc | |
parent | e07ff7c0a5872668da5a65d9c2b1cdedd64cb56d (diff) | |
download | pfsense-bf92bc791de6b04246c7a2f67945ce1412906d95.zip pfsense-bf92bc791de6b04246c7a2f67945ce1412906d95.tar.gz |
- Add proper support for using hostnames for the remote IPsec gateway.
- Make IPsec reloading granular, this resolves the long standing issue
that a IPsec reload will cause all tunnels to drop.
- Change IPsec edit screen description for remote gateway that a IP
address or hostname is allowed here. We already accepted hostnames
before.
- Add /etc/rc.newipsecdns, when a hostname changes IP we invoke this
script to remove the old tunnel and setup the new one.
Diffstat (limited to 'etc')
-rw-r--r-- | etc/inc/pfsense-utils.inc | 95 | ||||
-rw-r--r-- | etc/inc/util.inc | 22 | ||||
-rw-r--r-- | etc/inc/vpn.inc | 300 | ||||
-rwxr-xr-x | etc/rc.newipsecdns | 52 |
4 files changed, 424 insertions, 45 deletions
diff --git a/etc/inc/pfsense-utils.inc b/etc/inc/pfsense-utils.inc index 56ab757..2794543 100644 --- a/etc/inc/pfsense-utils.inc +++ b/etc/inc/pfsense-utils.inc @@ -1434,7 +1434,7 @@ function find_interface_ip($interface, $flush = false) { } function guess_interface_from_ip($ipaddress) { - $ret = exec_command("/usr/bin/netstat -rn | /usr/bin/awk '/^{$ipaddress}/ {print \$6}'"); + $ret = exec_command("/usr/bin/netstat -rn | /usr/bin/awk '/^{$ipaddress}/ {print $6}'"); if(empty($ret)) { return false; } @@ -3740,4 +3740,97 @@ function safe_write_file($file, $content, $force_binary) { return true; } +/* Write out all the found IP addresses to a file + * so we can compare it on change */ +function add_hostname_to_watch($hostname) { + if(!is_dir("/var/db/dnscache")) { + mkdir("/var/db/dnscache"); + } + if((is_fqdn($hostname)) && (!is_ipaddr($hostname))) { + $domrecords = array(); + $domips = array(); + exec("host -t A $hostname", $domrecords, $rethost); + if($rethost == 0) { + foreach($domrecords as $domr) { + $doml = explode(" ", $domr); + $domip = $doml[3]; + /* fill array with domain ip addresses */ + if(is_ipaddr($domip)) { + $domips[] = $domip; + } + } + } + sort($domips); + $contents = ""; + if(! empty($domips)) { + foreach($domips as $ip) { + $contents .= "$ip\n"; + } + } + file_put_contents("/var/db/dnscache/$hostname", $contents); + } +} + +/* Compare the current hostname DNS to the DNS cache we made + * if it has changed we return the old records + * if no change we return true */ +function compare_hostname_to_dnscache($hostname) { + if(!is_dir("/var/db/dnscache")) { + mkdir("/var/db/dnscache"); + } + $hostname = trim($hostname); + if(is_readable("/var/db/dnscache/{$hostname}")) { + $oldcontents = file_get_contents("/var/db/dnscache/{$hostname}"); + } else { + $oldcontents = ""; + } + if((is_fqdn($hostname)) && (!is_ipaddr($hostname))) { + $domrecords = array(); + $domips = array(); + exec("host -t A $hostname", $domrecords, $rethost); + if($rethost == 0) { + foreach($domrecords as $domr) { + $doml = explode(" ", $domr); + $domip = $doml[3]; + /* fill array with domain ip addresses */ + if(is_ipaddr($domip)) { + $domips[] = $domip; + } + } + } + sort($domips); + $contents = ""; + if(! empty($domips)) { + foreach($domips as $ip) { + $contents .= "$ip\n"; + } + } + } + + if(trim($oldcontents) != trim($contents)) { + log_error("DNSCACHE: Found old IP {$oldcontents} and new IP {$contents}"); + return ($oldcontents); + } else { + return false; + } +} + +function is_fqdn($fqdn) { + $hostname = false; + if(preg_match("/[-A-Z0-9\.]+\.[-A-Z0-9\.]+/i", $fqdn)) { + $hostname = true; + } + if(preg_match("/\.\./", $fqdn)) { + $hostname = false; + } + if(preg_match("/^\./i", $fqdn)) { + $hostname = false; + } + if(preg_match("/\//i", $fqdn)) { + $hostname = false; + } + return($hostname); +} + + ?> diff --git a/etc/inc/util.inc b/etc/inc/util.inc index 022777d..1fbc0bf 100644 --- a/etc/inc/util.inc +++ b/etc/inc/util.inc @@ -566,4 +566,24 @@ function mac_format($clientmac) { } } -?>
\ No newline at end of file +function resolve_retry($hostname, $retries = 5) { + + if (is_ipaddr($hostname)) + return $hostname; + + for ($i = 0; $i < $retries; $i++) { + $ip = gethostbyname($hostname); + + if ($ip && $ip != $hostname) { + /* success */ + return $ip; + } + + sleep(1); + } + + return false; +} + + +?> diff --git a/etc/inc/vpn.inc b/etc/inc/vpn.inc index b2ddefc..2cd4043 100644 --- a/etc/inc/vpn.inc +++ b/etc/inc/vpn.inc @@ -104,19 +104,6 @@ function vpn_ipsec_configure($ipchg = false) { unlink_if_exists("/var/db/ipsecpinghosts"); touch("/var/db/ipsecpinghosts"); - if($g['booting'] == true) { - /* determine if we should load the via padlock module */ - $dmesg_boot = `cat /var/log/dmesg.boot | grep CPU`; - if(stristr($dmesg_boot, "ACE") == true) { - //echo "Enabling [VIA Padlock] ..."; - //mwexec("/sbin/kldload padlock"); - //mwexec("/sbin/sysctl net.inet.ipsec.crypto_support=1"); - //mwexec("/usr/local/sbin/setkey -F"); - //mwexec("/usr/local/sbin/setkey -FP"); - //echo " done.\n"; - } - } - if(isset($config['ipsec']['preferredoldsa'])) { mwexec("/sbin/sysctl net.key.preferred_oldsa=0"); } else { @@ -143,6 +130,8 @@ function vpn_ipsec_configure($ipchg = false) { /* kill racoon */ mwexec("/usr/bin/killall racoon", true); + killbypid("{$g['varrun_path']}/dnswatch-ipsec.pid"); + /* wait for process to die */ sleep(2); @@ -164,7 +153,7 @@ function vpn_ipsec_configure($ipchg = false) { if (isset($ipseccfg['enable'])) { /* fastforwarding is not compatible with ipsec tunnels */ - system("/sbin/sysctl net.inet.ip.fastforwarding=0 >/dev/null 2>&1"); + mwexec("/sbin/sysctl net.inet.ip.fastforwarding=0"); if (!$curwanip) { /* IP address not configured yet, exit */ @@ -180,6 +169,9 @@ function vpn_ipsec_configure($ipchg = false) { if ((is_array($ipseccfg['tunnel']) && count($ipseccfg['tunnel'])) || isset($ipseccfg['mobileclients']['enable'])) { + $dnswatch_list = array(); + $rgmap = array(); + if (is_array($ipseccfg['tunnel']) && count($ipseccfg['tunnel'])) { /* generate spd.conf */ @@ -200,30 +192,47 @@ function vpn_ipsec_configure($ipchg = false) { continue; $ep = vpn_endpoint_determine($tunnel, $curwanip); + /* see if this tunnel has a hostname for the remote-gateway, and if so, + * try to resolve it now and add it to the list for dnswatch */ + if (!is_ipaddr($tunnel['remote-gateway'])) { + $dnswatch_list[] = $tunnel['remote-gateway']; + $rgip = resolve_retry($tunnel['remote-gateway']); + add_hostname_to_watch($tunnel['remote-gateway']); + if (!$rgip) { + log_error("Could not deterimine VPN endpoint for {$tunnel['descr']}"); + continue; + } + } else { + $rgip = $tunnel['remote-gateway']; + } + $rgmap[$tunnel['remote-gateway']] = $rgip; if (!$ep) { log_error("Could not deterimine VPN endpoint for {$tunnel['descr']}"); continue; } + vpn_localnet_determine($tunnel['local-subnet'], $sa, $sn); if(is_domain($tunnel['remote-gateway'])) { $tmp = gethostbyname($tunnel['remote-gateway']); - if($tmp) + if($tmp) { $tunnel['remote-gateway'] = $tmp; + } } /* add entry to host pinger */ if ($tunnel['pinghost']) { $pfd = fopen("/var/db/ipsecpinghosts", "a"); $iflist = array("lan" => "lan", "wan" => "wan"); - for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++) - $iflist['opt' . $i] = "opt{$i}"; - foreach ($iflist as $ifent => $ifname) { - $interface_ip = find_interface_ip($config['interfaces'][$ifname]['if']); - if (ip_in_subnet($interface_ip, $sa . "/" . $sn)) - $srcip = find_interface_ip($config['interfaces'][$ifname]['if']); - } + for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++) + $iflist['opt' . $i] = "opt{$i}"; + + foreach ($iflist as $ifent => $ifname) { + $interface_ip = find_interface_ip($config['interfaces'][$ifname]['if']); + if (ip_in_subnet($interface_ip, $sa . "/" . $sn)) + $srcip = find_interface_ip($config['interfaces'][$ifname]['if']); + } $dstip = $tunnel['pinghost']; fwrite($pfd, "$srcip|$dstip|3\n"); fclose($pfd); @@ -287,7 +296,10 @@ function vpn_ipsec_configure($ipchg = false) { return 1; } - $racoonconf = ""; + $racoonconf = "# This file is automatically generated. Do not edit\n"; + $racoonconf .= "listen {\n"; + $racoonconf .= " adminsock \"/var/run/racoon.sock\" \"root\" \"wheel\" 0660;\n"; + $racoonconf .= "}\n"; $racoonconf .= "path pre_shared_key \"{$g['varetc_path']}/psk.txt\";\n\n"; $racoonconf .= "path certificate \"{$g['varetc_path']}\";\n\n"; @@ -322,6 +334,10 @@ function vpn_ipsec_configure($ipchg = false) { if (isset($tunnel['disabled'])) continue; + $rgip = $rgmap[$tunnel['remote-gateway']]; + if (!$rgip) + continue; + $ep = vpn_endpoint_determine($tunnel, $curwanip); if (!$ep) continue; @@ -411,11 +427,11 @@ EOD; } } $racoonconf .= <<<EOD -remote {$tunnel['remote-gateway']} \{ +remote {$rgmap[$tunnel['remote-gateway']]} \{ exchange_mode {$tunnel['p1']['mode']}; my_identifier {$myidentt}{$myident}; {$certline} - peers_identifier address {$tunnel['remote-gateway']}; + peers_identifier address {$rgmap[$tunnel['remote-gateway']]}; initial_contact on; #dpd_delay 120; # DPD poll every 120 seconds ike_frag on; @@ -585,7 +601,12 @@ EOD; foreach ($ipseccfg['tunnel'] as $tunnel) { if (isset($tunnel['disabled'])) continue; - $pskconf .= "{$tunnel['remote-gateway']} {$tunnel['p1']['pre-shared-key']}\n"; + + $rgip = $rgmap[$tunnel['remote-gateway']]; + if (!$rgip) + continue; + + $pskconf .= "{$rgip} {$tunnel['p1']['pre-shared-key']}\n"; } } @@ -601,31 +622,41 @@ EOD; chmod("{$g['varetc_path']}/psk.txt", 0600); if(is_process_running("racoon")) { - /* flush SPD entries */ - mwexec("/usr/local/sbin/setkey -FP"); - sleep("0.1"); - mwexec("/usr/local/sbin/setkey -F"); - /* load SPD */ + log_error("IPSEC: Send a reload signal to the IPsec process"); sleep("0.1"); - mwexec("/usr/local/sbin/setkey -f {$g['varetc_path']}/spd.conf"); - /* We are already online, reload */ - sleep("0.1"); - mwexec("/usr/bin/killall -HUP racoon", true); + mwexec("/usr/local/sbin/racoonctl -s /var/run/racoon.sock reload-config", false); + // mwexec("/usr/bin/killall -HUP racoon", false); } else { /* flush SA + SPD entries */ - mwexec("/usr/local/sbin/setkey -FP"); + mwexec("/usr/local/sbin/setkey -FP", false); sleep("0.1"); - mwexec("/usr/local/sbin/setkey -F"); + mwexec("/usr/local/sbin/setkey -F", false); sleep("0.1"); /* start racoon */ - mwexec("/usr/local/sbin/racoon -f {$g['varetc_path']}/racoon.conf"); + mwexec("/usr/local/sbin/racoon -f {$g['varetc_path']}/racoon.conf", false); sleep("0.1"); /* load SPD */ - mwexec("/usr/local/sbin/setkey -f {$g['varetc_path']}/spd.conf"); + mwexec("/usr/local/sbin/setkey -f {$g['varetc_path']}/spd.conf", false); /* We are already online, reload */ sleep("0.1"); - mwexec("/usr/bin/killall -HUP racoon", true); + mwexec("/usr/bin/killall -HUP racoon", false); } + + /* start dnswatch, if necessary */ + if (count($dnswatch_list) > 0) { + $interval = 60; + if ($ipseccfg['dns-interval']) + $interval = $ipseccfg['dns-interval']; + + $hostnames = ""; + foreach ($dnswatch_list as $dns) + $hostnames .= " " . escapeshellarg($dns); + killbypid("{$g['varrun_path']}/dnswatch-ipsec.pid"); + mwexec("/usr/local/sbin/dnswatch {$g['varrun_path']}/dnswatch-ipsec.pid $interval " . + escapeshellarg("/etc/rc.newipsecdns") . $hostnames, false); + } + + } } @@ -1086,9 +1117,6 @@ function vpn_ipsec_force_reload() { $ipseccfg = $config['ipsec']; - /* kill any ipsec communications regardless when we are invoked */ - mwexec("/sbin/ifconfig enc0 down"); - /* kill racoon */ mwexec("/usr/bin/killall racoon", true); @@ -1109,4 +1137,190 @@ function vpn_ipsec_force_reload() { } +/* Walk the tunnels for hostname endpoints. If the hostnames + * resolve to a different IP now compared to the DNS cache + * we reload the policies if the endpoint has changed */ +function vpn_ipsec_refresh_policies() { + global $config; + global $g; + + $ipseccfg = $config['ipsec']; + + if (! isset($ipseccfg['enable'])) { + return true; + } + + /* Walk the Ipsec tunnel array */ + if (is_array($ipseccfg['tunnel']) && count($ipseccfg['tunnel'])) { + foreach ($ipseccfg['tunnel'] as $tunnel) { + if (isset($tunnel['disabled'])) { + continue; + } + if (is_ipaddr($tunnel['remote-gateway'])) { + continue; + } + + if (!is_ipaddr($tunnel['remote-gateway'])) { + $dnscache = compare_hostname_to_dnscache($tunnel['remote-gateway']); + $dnscache = trim($dnscache); + /* we should have the old IP addresses in the dnscache now */ + if($dnscache <> "") { + $oldtunnel = $tunnel; + $oldtunnel['remote-gateway'] = trim($dnscache); + reload_tunnel_spd_policy ($tunnel, $oldtunnel); + } + } + } + } + + /* process all generated spd.conf files from tmp which are left behind + * behind by either changes of dynamic tunnels or manual edits + * scandir() is only available in PHP5 */ + $tmpfiles = array(); + $dh = opendir($g['tmp_path']); + while (false !== ($filename = readdir($dh))) { + $tmpfiles[] = $filename; + } + sort($tmpfiles); + foreach($tmpfiles as $tmpfile) { + if(preg_match("/^spd.conf./", $tmpfile)) { + $ret = mwexec("/usr/local/sbin/setkey -f {$g['tmp_path']}/{$tmpfile} 2>&1", false); + if($ret == 0) { + unlink("{$g['tmp_path']}/{$tmpfile}"); + } else { + rename("{$g['tmp_path']}/{$tmpfile}", ("{$g['tmp_path']}/failed.{$tmpfile}")); + } + unlink("{$g['tmp_path']}/{$tmpfile}"); + } + } +} + +function reload_tunnel_spd_policy($tunnel, $oldtunnel) { + global $config; + global $g; + + /* if we are not passed a old tunnel array we create one */ + if(empty($oldtunnel)) { + $oldtunnel = $tunnel; + } + + $curwanip = get_current_wan_address(); + $sad_arr = return_ipsec_sad_array(); + + $ep = vpn_endpoint_determine($tunnel, $curwanip); + vpn_localnet_determine($tunnel['local-subnet'], $sa, $sn); + + /* make sure we pass the oldtunnel array with a IP for the remote gw */ + $oldgw = trim($oldtunnel['remote-gateway']); + $oldep = vpn_endpoint_determine($oldtunnel, $curwanip); + vpn_localnet_determine($oldtunnel['local-subnet'], $oldsa, $oldsn); + + /* see if this tunnel has a hostname for the remote-gateway, and if so, + * try to resolve it now and add it to the list for dnswatch */ + if (!is_ipaddr($tunnel['remote-gateway'])) { + $rgip = resolve_retry($tunnel['remote-gateway']); + add_hostname_to_watch($tunnel['remote-gateway']); + if (!$rgip) { + log_error("Could not determine VPN endpoint for {$tunnel['descr']}"); + return false; + } + } else { + $rgip = $tunnel['remote-gateway']; + } + if (!$ep) { + log_error("Could not determine VPN endpoint for {$tunnel['descr']}"); + return false; + } + + $spdconf = ""; + + /* Delete old SPD policies if there are changes between the old and new */ + if(($tunnel != $oldtunnel) && (is_ipaddr($oldgw))) { + $spdconf .= "spddelete {$oldsa}/{$oldsn} " . + "{$oldtunnel['remote-subnet']} any -P out ipsec " . + "{$oldtunnel['p2']['protocol']}/tunnel/{$oldep}-" . + "{$oldgw}/unique;\n"; + $spdconf .= "spddelete {$oldtunnel['remote-subnet']} " . + "{$oldsa}/{$oldsn} any -P in ipsec " . + "{$oldtunnel['p2']['protocol']}/tunnel/{$oldgw}-" . + "{$oldep}/unique;\n"; + + /* zap any existing SA entries */ + foreach($sad_arr as $sad) { + if(($sad['dst'] == $oldep) && ($sad['src'] == $oldgw)) { + $spdconf .= "delete {$oldep} {$oldgw} {$tunnel['p2']['protocol']} 0x{$sad['spi']};\n"; + } + if(($sad['src'] == $oldep) && ($sad['dst'] == $oldgw)) { + $spdconf .= "delete {$oldgw} {$oldep} {$tunnel['p2']['protocol']} 0x{$sad['spi']};\n"; + } + } + } + + /* Create new SPD entries for the new configuration */ + /* zap any existing SA entries beforehand */ + foreach($sad_arr as $sad) { + if(($sad['dst'] == $ep) && ($sad['src'] == $rgip)) { + $spdconf .= "delete {$ep} {$rgip} {$tunnel['p2']['protocol']} 0x{$sad['spi']};\n"; + } + if(($sad['src'] == $ep) && ($sad['dst'] == $rgip)) { + $spdconf .= "delete {$rgip} {$ep} {$tunnel['p2']['protocol']} 0x{$sad['spi']};\n"; + } + } + /* add new SPD policies to replace them */ + $spdconf .= "spdadd {$sa}/{$sn} " . + "{$tunnel['remote-subnet']} any -P out ipsec " . + "{$tunnel['p2']['protocol']}/tunnel/{$ep}-" . + "{$rgip}/unique;\n"; + $spdconf .= "spdadd {$tunnel['remote-subnet']} " . + "{$sa}/{$sn} any -P in ipsec " . + "{$tunnel['p2']['protocol']}/tunnel/{$rgip}-" . + "{$ep}/unique;\n"; + + log_error("IPSEC: Tunnel '{$tunnel['descr']}' has changed IP from '{$oldgw}' to '{$rgip}', reloading policy"); + + $now = time(); + $spdfile = tempnam("{$g['tmp_path']}", "spd.conf.reload.{$now}."); + /* generate temporary spd.conf */ + file_put_contents($spdfile, $spdconf); + return true; +} + +/* Dump SAD database to array */ +function return_ipsec_sad_array() { + /* query SAD */ + $fd = @popen("/usr/local/sbin/setkey -D", "r"); + $sad = array(); + if ($fd) { + while (!feof($fd)) { + $line = chop(fgets($fd)); + if (!$line) + continue; + if ($line == "No SAD entries.") + break; + if ($line[0] != "\t") { + if (is_array($cursa)) + $sad[] = $cursa; + $cursa = array(); + list($cursa['src'],$cursa['dst']) = explode(" ", $line); + $i = 0; + } else { + $linea = explode(" ", trim($line)); + if ($i == 1) { + $cursa['proto'] = $linea[0]; + $cursa['spi'] = substr($linea[2], strpos($linea[2], "x")+1, -1); + } else if ($i == 2) { + $cursa['ealgo'] = $linea[1]; + } else if ($i == 3) { + $cursa['aalgo'] = $linea[1]; + } + } + $i++; + } + if (is_array($cursa) && count($cursa)) + $sad[] = $cursa; + pclose($fd); + } + return($sad); +} + ?> diff --git a/etc/rc.newipsecdns b/etc/rc.newipsecdns new file mode 100755 index 0000000..7621a8e --- /dev/null +++ b/etc/rc.newipsecdns @@ -0,0 +1,52 @@ +#!/usr/local/bin/php -f +<?php +/* + $Id$ + part of m0n0wall (http://m0n0.ch/wall) + + Copyright (C) 2007 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + + /* parse the configuration and include all functions used below */ + require_once("config.inc"); + require_once("functions.inc"); + + /* make sure to wait until the boot scripts have finished */ + while (file_exists("{$g['varrun_path']}/booting")) { + sleep(1); + } + + log_error("IPSEC: One or more IPSEC tunnel endpoints has changed IP. Refreshing."); + /* We will walk the list of hostnames found in the ipsec tunnel + * configuration. Since we are already triggered by dnswatch + * that a hostname has changed we can proceed to compare the + * new IP address with the old address from the DNS cache. + */ + vpn_ipsec_refresh_policies(); + + vpn_ipsec_configure(); + +?> + |