diff --git a/src/etc/rc.syshook.d/carp/20-openvpn-instances b/src/etc/rc.syshook.d/carp/20-openvpn-instances
new file mode 100755
index 000000000..675a7a0ca
--- /dev/null
+++ b/src/etc/rc.syshook.d/carp/20-openvpn-instances
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+configctl -dq openvpn configure
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml b/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml
index 08263d8e8..0215d4c93 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml
+++ b/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml
@@ -129,6 +129,14 @@
my.remote.local dead:beaf:: my.remote.local:1494 [dead:beaf::]:1494 192.168.1.1:1494
+
+ instance.carp_depend_on
+
+ dropdown
+
+ The carp VHID to depend on, when this virtual address is not in master state,
+ the instance will be shutdown.
+
header
diff --git a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/VirtualIPField.php b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/VirtualIPField.php
index 4b0db34d9..0864b3006 100644
--- a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/VirtualIPField.php
+++ b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/VirtualIPField.php
@@ -41,6 +41,11 @@ class VirtualIPField extends BaseListField
*/
private $vipType = "*";
+ /**
+ * @var boolean legacy key usage
+ */
+ private $isLegacyKey = true;
+
/**
* @var array cached collected certs
*/
@@ -55,6 +60,18 @@ class VirtualIPField extends BaseListField
$this->vipType = $value;
}
+ /**
+ * as this field type is used to hook legacy fields and MVC ones, specify a key here.
+ * default it uses a legacy (subnet) key.
+ * @param $value string vip type
+ */
+ public function setKey($value)
+ {
+ if (strtolower($value) == 'mvc') {
+ $this->isLegacyKey = false;
+ }
+ }
+
/**
* generate validation data (list of virtual ips)
*/
@@ -83,7 +100,12 @@ class VirtualIPField extends BaseListField
} else {
$caption = sprintf(gettext("[%s] %s on %s"), $vip->subnet, $vip->descr, $intf_name);
}
- self::$internalStaticOptionList[$this->vipType][(string)$vip->subnet] = $caption;
+ if ($this->isLegacyKey) {
+ $key = (string)$vip->subnet;
+ } else {
+ $key = (string)$vip->attributes()['uuid'];
+ }
+ self::$internalStaticOptionList[$this->vipType][$key] = $caption;
}
}
natcasesort(self::$internalStaticOptionList[$this->vipType]);
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml b/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml
index 64ddc83f3..55daa7986 100644
--- a/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml
@@ -381,6 +381,11 @@
,
Y
+
+ carp
+ N
+ mvc
+
N
diff --git a/src/opnsense/scripts/openvpn/ovpn_service_control.php b/src/opnsense/scripts/openvpn/ovpn_service_control.php
index f0000d178..612fafe27 100755
--- a/src/opnsense/scripts/openvpn/ovpn_service_control.php
+++ b/src/opnsense/scripts/openvpn/ovpn_service_control.php
@@ -91,6 +91,27 @@ function ovpn_instance_stats($instance, $fhandle)
return $data;
}
+function get_vhid_status()
+{
+ $vhids = [];
+ $uuids = [];
+ foreach ((new OPNsense\Interfaces\Vip())->vip->iterateItems() as $id => $item) {
+ if ($item->mode == 'carp') {
+ $uuids[(string)$item->vhid] = $id;
+ }
+ }
+ foreach (legacy_interfaces_details() as $ifdata) {
+ if (!empty($ifdata['carp'])) {
+ foreach ($ifdata['carp'] as $data) {
+ if (isset($uuids[$data['vhid']])) {
+ $vhids[$uuids[$data['vhid']]] = $data['status'];
+ }
+ }
+ }
+ }
+ return $vhids;
+}
+
$opts = getopt('ah', [], $optind);
$args = array_slice($argv, $optind);
@@ -109,6 +130,7 @@ if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', '
if ($action != 'stop') {
$mdl->generateInstanceConfig($instance_id);
}
+ $vhids = $action == 'configure' ? get_vhid_status() : [];
$instance_ids = [];
foreach ($mdl->Instances->Instance->iterateItems() as $key => $node) {
if (empty((string)$node->enabled)) {
@@ -133,7 +155,15 @@ if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', '
ovpn_start($node, $statHandle);
break;
case 'configure':
- if ($instance_stats['has_changed'] || !isvalidpid($node->pidFilename)) {
+ $carp_down = false;
+ if ((string)$node->role == 'client' && !empty($vhids[(string)$node->carp_depend_on])) {
+ $carp_down = $vhids[(string)$node->carp_depend_on] != 'MASTER';
+ }
+ if ($carp_down) {
+ if (isvalidpid($node->pidFilename)) {
+ ovpn_stop($node);
+ }
+ } elseif ($instance_stats['has_changed'] || !isvalidpid($node->pidFilename)) {
ovpn_stop($node);
ovpn_start($node, $statHandle);
}