--- a/etc/inc/interfaces.inc +++ b/etc/inc/interfaces.inc @@ -1,12 +1,71 @@ - // Create IPsec interface - if (does_interface_exist($ipsecif)) { - mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " destroy"); + /* + * Preserve an existing IPv4 VTI when its runtime configuration already + * matches the configured reqid, outer tunnel, and inner addresses. + */ + $vti_reconfigure = true; + $vti_exists = does_interface_exist($ipsecif); + if ($vti_exists) { + $ifconfig_output = array(); + $ifconfig_status = 0; + exec("/sbin/ifconfig -f inet:cidr " . escapeshellarg($ipsecif), + $ifconfig_output, $ifconfig_status); + $ifconfig_output = implode("\n", $ifconfig_output); + $expected_reqid = $ipsec_reqid_base + $reqids[$ipsecif]; + $outer_inet = is_ipaddrv6($left_spec) ? "inet6" : "inet"; + + $vti_matches = + ($ifconfig_status === 0) && + preg_match('/^' . preg_quote($ipsecif, '/') . + ':.*<[^>]*\bUP\b[^>]*>/m', $ifconfig_output) && + preg_match('/^\s*reqid:\s*' . preg_quote((string)$expected_reqid, '/') . + '\s*$/m', $ifconfig_output) && + preg_match('/^\s*tunnel\s+' . preg_quote($outer_inet, '/') . '\s+' . + preg_quote($left_spec, '/') . '\s+-->\s+' . + preg_quote($right_spec, '/') . '\s*$/m', $ifconfig_output); + + $expected_v4_count = 0; + foreach ($addrs as $addr) { + if (is_v6($addr['left'])) { + $vti_matches = false; + break; + } + $left_addr = explode('/', $addr['left'], 2); + $right_addr = explode('/', $addr['right'], 2); + if (empty($left_addr[0]) || empty($left_addr[1]) || + empty($right_addr[0])) { + $vti_matches = false; + break; + } + $expected_v4_count++; + if (!preg_match('/^\s*inet\s+' . + preg_quote($left_addr[0], '/') . '\s+-->\s+' . + preg_quote($right_addr[0], '/') . '\/' . + preg_quote($left_addr[1], '/') . '\s*$/m', + $ifconfig_output)) { + $vti_matches = false; + break; + } + } + preg_match_all('/^\s*inet\s+/m', $ifconfig_output, $current_v4); + if (count($current_v4[0]) !== $expected_v4_count) { + $vti_matches = false; + } + if ($vti_matches) { + $vti_reconfigure = false; + log_error(sprintf("IPsec VTI %s unchanged, preserving interface", $ipsecif)); + } } - mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " create reqid " . escapeshellarg($ipsec_reqid_base + $reqids[$ipsecif])); - /* Apply the outer tunnel addresses to the interface */ - $inet = is_ipaddrv6($left_spec) ? "inet6" : "inet"; - mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " {$inet} tunnel " . escapeshellarg($left_spec) . " " . escapeshellarg($right_spec) . " up"); + if ($vti_reconfigure) { + if ($vti_exists) { + mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " destroy"); + } + mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " create reqid " . escapeshellarg($ipsec_reqid_base + $reqids[$ipsecif])); + + /* Apply the outer tunnel addresses to the interface */ + $inet = is_ipaddrv6($left_spec) ? "inet6" : "inet"; + mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " {$inet} tunnel " . escapeshellarg($left_spec) . " " . escapeshellarg($right_spec) . " up"); + } /* Loop through all of the addresses for this interface and apply them as needed */ foreach ($addrs as $addr) { @@ -21,7 +80,9 @@ $right = escapeshellarg((explode('/', $addr['right'], 2))[0]); } - mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " {$inet} " . escapeshellarg($addr['left']) . " " . $right . $addr['alias']); + if ($vti_reconfigure) { + mwexec("/sbin/ifconfig " . escapeshellarg($ipsecif) . " {$inet} " . escapeshellarg($addr['left']) . " " . $right . $addr['alias']); + } /* If alias is empty, this is the first address on the interface and should be used as the gateway. */ if (empty($addr['alias'])) { file_put_contents("/tmp/{$ipsecif}_router{$gwtype}", (explode('/', $addr['right'], 2))[0]);