Virtual IP MVC/API conversion (#6105)

closes #5984 refactors legacy pages, includes the following:

o remove type field as this seems to be redundant and confusing
o input form additions (show hide related fields)
o add button for carp type to select first unused vhid
o implement configure action, caching removed addresses in /tmp/delete_vip_{$uuid}.todo files (by the controller)
o add mode filter to search action and complete with relevant fields for our grid
o fix warning in interfaces.inc (interface_proxyarp_configure()), array creation issue
o add validation for addresses used in port forwards and outbound nat rules. previous version tried to rename forwards, we choose to be consistent when it comes to edit/delete.
o change ACL to use the new endpoints, remove "show only" ACL. we can always consider putting it back later, but the experience of only able to reach the grid likely won't be practical.
o remove old firewall_virtual_ip*.php files
This commit is contained in:
Ad Schellevis 2022-10-26 16:42:46 +02:00 committed by GitHub
parent 607faca9ba
commit 7d4597efe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 985 additions and 861 deletions

11
plist
View File

@ -346,12 +346,15 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/IPsec/forms/dialogKeyPair.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/IPsec/forms/dialogPSK.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/LoopbackSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VipSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VlanSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VxlanSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/LoopbackController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/VipController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/VlanController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/VxlanController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogLoopback.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogVip.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogVlan.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogVxlan.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/Api/ServiceController.php
@ -582,10 +585,14 @@
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_0.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_1.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/ACL/ACL.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VipInterfaceField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VipNetworkField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VlanInterfaceField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Loopback.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Loopback.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Menu/Menu.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Vip.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Vip.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Vlan.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Vlan.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/VxLan.php
@ -666,6 +673,7 @@
/usr/local/opnsense/mvc/app/views/OPNsense/IPsec/spd.volt
/usr/local/opnsense/mvc/app/views/OPNsense/IPsec/tunnels.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/loopback.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/vip.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/vlan.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/vxlan.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/index.volt
@ -829,6 +837,7 @@
/usr/local/opnsense/scripts/interfaces/ppp-linkdown.sh
/usr/local/opnsense/scripts/interfaces/ppp-linkup.sh
/usr/local/opnsense/scripts/interfaces/ppp-uptime.sh
/usr/local/opnsense/scripts/interfaces/reconfigure_vips.php
/usr/local/opnsense/scripts/interfaces/reconfigure_vlans.php
/usr/local/opnsense/scripts/interfaces/rtsold_resolvconf.sh
/usr/local/opnsense/scripts/interfaces/traffic_stats.php
@ -1841,8 +1850,6 @@
/usr/local/www/firewall_schedule_edit.php
/usr/local/www/firewall_scrub.php
/usr/local/www/firewall_scrub_edit.php
/usr/local/www/firewall_virtual_ip.php
/usr/local/www/firewall_virtual_ip_edit.php
/usr/local/www/foot.inc
/usr/local/www/getserviceproviders.php
/usr/local/www/guiconfig.inc

View File

@ -1303,7 +1303,7 @@ function interface_proxyarp_configure($interface = '')
if ($vipent['mode'] === "proxyarp") {
if (empty($interface) || $interface == $vipent['interface']) {
if (empty($paa[$vipent['interface']])) {
$paa[$proxyif] = array();
$paa[$vipent['interface']] =[];
}
$paa[$vipent['interface']][] = $vipent;
}

View File

@ -0,0 +1,202 @@
<?php
/*
* Copyright (C) 2022 Deciso B.V.
* 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.
*/
namespace OPNsense\Interfaces\Api;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\Base\UserException;
use OPNsense\Base\ApiMutableModelControllerBase;
class VipSettingsController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'vip';
protected static $internalModelClass = 'OPNsense\Interfaces\Vip';
/**
* extract network field into subnet + bits for model
*/
private function getVipOverlay()
{
$overlay = ['network' => ''];
$tmp = $this->request->getPost('vip');
if (!empty($tmp['network'])) {
$parts = explode('/', $tmp['network'], 2);
$overlay['subnet'] = $parts[0];
if (count($parts) < 2) {
$overlay['subnet_bits'] = strpos($parts[0], ':') !== false ? 128 : 32;
} else {
$overlay['subnet_bits'] = $parts[1];
}
}
return $overlay;
}
/**
* retrieve first unused VHID number
*/
public function getUnusedVhidAction()
{
$vhids = [];
foreach ($this->getModel()->vip->iterateItems() as $vip) {
if (!in_array((string)$vip->vhid, $vhids) && !empty((string)$vip->vhid)) {
$vhids[] = (string)$vip->vhid;
}
}
for ($i=1; $i <= 255; $i++) {
if (!in_array((string)$i, $vhids)) {
return ['vhid' => $i, 'status' => 'ok'];
}
}
return ['status' => 'not_found'];
}
/**
* remap subnet and subnet_bits to network (which represents combined field)
*/
private function handleFormValidations($response)
{
if (!empty($response['validations'])) {
foreach (array_keys($response['validations']) as $fieldname) {
if (in_array($fieldname, ['vip.subnet', 'vip.subnet_bits'])) {
if (empty($response['validations']['vip.network'])) {
$response['validations']['vip.network'] = [];
}
if (is_array($response['validations'][$fieldname])) {
$response['validations']['vip.network'] = array_merge(
$response['validations']['vip.network'],
$response['validations'][$fieldname]
);
} else {
$response['validations']['vip.network'][] = $response['validations'][$fieldname];
}
unset($response['validations'][$fieldname]);
}
}
}
return $response;
}
public function searchItemAction()
{
$mode = $this->request->getPost('mode');
$filter_funct = null;
if (!empty($mode)) {
$filter_funct = function ($record) use ($mode) {
return in_array($record->mode, $mode);
};
}
$result = $this->searchBase(
'vip',
['interface', 'mode', 'type', 'descr', 'subnet', 'subnet_bits', 'vhid', 'advbase', 'advskew'],
'descr',
$filter_funct
);
if (!empty($result['rows'])) {
foreach ($result['rows'] as &$row) {
$row['address'] = sprintf("%s/%s", $row['subnet'], $row['subnet_bits']);
$row['vhid_txt'] = $row['vhid'];
if ($row['mode'] == 'CARP') {
$row['vhid_txt'] = sprintf(
gettext('%s (freq. %s/%s)'),
$row['vhid'],
$row['advbase'],
$row['advskew']
);
}
}
}
return $result;
}
public function setItemAction($uuid)
{
$node = $this->getModel()->getNodeByReference('vip.' . $uuid);
$validations = [];
if ($node != null && explode('/', $_POST['vip']['network'])[0] != (string)$node->subnet) {
$validations = $this->getModel()->whereUsed((string)$node->subnet);
if (!empty($validations)) {
// XXX a bit unpractical, but we can not validate previous values from the model so
// we are obligated to return this as a single error (even if the form has other issues too)
return [
'result' => 'failed',
'validations' => [
'vip.network' => array_slice($validations, 0 , 2)
]
];
} elseif (!file_exists("/tmp/delete_vip_{$uuid}.todo")) {
file_put_contents("/tmp/delete_vip_{$uuid}.todo", (string)$node->subnet);
}
}
return $this->handleFormValidations($this->setBase('vip', 'vip', $uuid, $this->getVipOverlay()));
}
public function addItemAction()
{
return $this->handleFormValidations($this->addBase('vip', 'vip', $this->getVipOverlay()));
}
public function getItemAction($uuid = null)
{
$vip = $this->getBase('vip', 'vip', $uuid);
// Merge subnet + netmask into network field
if (!empty($vip['vip']) && !empty($vip['vip']['subnet'])) {
$vip['vip']['network'] = $vip['vip']['subnet'] . "/" . $vip['vip']['subnet_bits'];
} elseif (!empty($vip['vip'])) {
$vip['vip']['network'] = '';
}
unset($vip['vip']['subnet']);
unset($vip['vip']['subnet_bits']);
return $vip;
}
public function delItemAction($uuid)
{
$node = $this->getModel()->getNodeByReference('vip.' . $uuid);
$validations = $this->getModel()->whereUsed((string)$node->subnet);
if (!empty($validations)) {
throw new UserException(implode('<br/>', array_slice($validations, 0 , 5)), gettext("Item in use by"));
}
$response = $this->delBase("vip", $uuid);
if ($response['result'] ?? '' == 'deleted' && !file_exists("/tmp/delete_vip_{$uuid}.todo")) {
file_put_contents("/tmp/delete_vip_{$uuid}.todo", (string)$node->subnet);
}
return $response;
}
public function reconfigureAction()
{
$result = array("status" => "failed");
if ($this->request->isPost()) {
$result['status'] = strtolower(trim((new Backend())->configdRun('interface vip configure')));
}
return $result;
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* Copyright (C) 2022 Deciso B.V.
* 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.
*/
namespace OPNsense\Interfaces;
class VipController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Interface/vip');
$this->view->formDialogVip = $this->getForm("dialogVip");
}
}

View File

@ -0,0 +1,78 @@
<form>
<field>
<id>vip.mode</id>
<label>Mode</label>
<type>dropdown</type>
<help>Proxy ARP and other type Virtual IPs cannot be bound to by anything running on the firewall, such as IPsec, OpenVPN, etc. Use a CARP or IP Alias type address for these cases.</help>
</field>
<field>
<id>vip.interface</id>
<label>Interface</label>
<type>dropdown</type>
</field>
<field>
<id>vip.network</id>
<label>Network / Address</label>
<type>text</type>
<help>Provide an address and subnet to use. (e.g 192.168.0.1/24)</help>
</field>
<field>
<id>vip.gateway</id>
<label>Gateway</label>
<type>text</type>
<help>For some interface types a gateway is required to configure an IP Alias (ppp/pppoe/tun), leave this field empty for all other interface types.</help>
<advanced>true</advanced>
</field>
<field>
<id>vip.noexpand</id>
<label>Disable Expansion</label>
<type>checkbox</type>
<help>Disable expansion of this entry into IPs on NAT lists (e.g. 192.168.1.0/24 expands to 256 entries.</help>
<style>mode mode_proxyarp</style>
</field>
<field>
<id>vip.nobind</id>
<label>Deny service binding</label>
<type>checkbox</type>
<help>Assigning services to the virtual IP's interface will automatically include this address. Check to prevent binding to this address instead.</help>
<style>mode mode_carp mode_ipalias</style>
</field>
<field>
<id>vip.password</id>
<label>Password</label>
<type>password</type>
<style>mode mode_carp</style>
<help>Enter the VHID group password.</help>
</field>
<field>
<id>vip.vhid</id>
<label>VHID Group</label>
<type>text</type>
<style>mode mode_carp mode_ipalias</style>
<help>Enter the VHID group that the machines will share.</help>
</field>
<field>
<id>vip.advbase</id>
<label>advbase</label>
<type>text</type>
<style>mode mode_carp</style>
<help>Specifies the base of the advertisement interval in seconds. The acceptable values are 1 to 255.</help>
</field>
<field>
<id>vip.advskew</id>
<label>advskew</label>
<type>text</type>
<style>mode mode_carp advanced</style>
<help>
Specifies the skew to add to the base advertisement interval to make one host advertise slower than another host.
It is specified in 1/256 of seconds. The acceptable values are 0 to 254.
</help>
</field>
<field>
<id>vip.descr</id>
<label>Description</label>
<type>text</type>
<help>You may enter a description here for your reference (not parsed).</help>
<style>mode mode_carp</style>
</field>
</form>

View File

@ -309,15 +309,10 @@
<page-firewall-virtualipaddress-edit>
<name>Firewall: Virtual IP Address: Edit</name>
<patterns>
<pattern>firewall_virtual_ip_edit.php*</pattern>
<pattern>ui/interfaces/vip</pattern>
<pattern>api/interfaces/vip_settings/*</pattern>
</patterns>
</page-firewall-virtualipaddress-edit>
<page-firewall-virtualipaddresses>
<name>Firewall: Virtual IP Addresses</name>
<patterns>
<pattern>firewall_virtual_ip.php*</pattern>
</patterns>
</page-firewall-virtualipaddresses>
<page-diagnostics-logs-firewall-general>
<name>Diagnostics: Log: Firewall: General</name>
<patterns>

View File

@ -106,9 +106,7 @@
<Overview order="910" url="/status_interfaces.php" cssClass="fa fa-tasks fa-fw"/>
<Settings order="920" url="/system_advanced_network.php" cssClass="fa fa-cogs fa-fw"/>
<VIP order="930" VisibleName="Virtual IPs" cssClass="fa fa-clone fa-fw">
<Settings url="/firewall_virtual_ip.php">
<Edit url="/firewall_virtual_ip_edit.php*" visibility="hidden"/>
</Settings>
<Settings url="/ui/interfaces/vip"/>
<Status url="/carp_status.php">
<All url="/carp_status.php*" visibility="hidden"/>
</Status>

View File

@ -0,0 +1,58 @@
<?php
/**
* Copyright (C) 2022 Deciso B.V.
*
* 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.
*
*/
namespace OPNsense\Interfaces\FieldTypes;
use OPNsense\Base\FieldTypes\BaseListField;
use OPNsense\Core\Config;
class VipInterfaceField extends BaseListField
{
private static $interfaces = [];
protected function actionPostLoadingEvent()
{
if (empty(self::$interfaces)) {
$configHandle = Config::getInstance()->object();
if (!empty($configHandle->interfaces)) {
foreach ($configHandle->interfaces->children() as $ifname => $node) {
if (!empty((string)$node->type) && $node->type == 'group') {
continue;
} elseif (!empty((string)$node->if) && $node->if == 'enc0') {
continue;
}
$descr = !empty((string)$node->descr) ? (string)$node->descr : strtoupper($ifname);
self::$interfaces[$ifname] = $descr;
}
}
}
$this->internalOptionList = self::$interfaces;
return parent::actionPostLoadingEvent();
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* Copyright (C) 2022 Deciso B.V.
*
* 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.
*
*/
namespace OPNsense\Interfaces\FieldTypes;
use OPNsense\Base\FieldTypes\TextField;
use OPNsense\Base\Validators\CallbackValidator;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\Firewall\Util;
class VipNetworkField extends TextField
{
public function getValidators()
{
$validators = parent::getValidators();
if ($this->getInternalXMLTagName() == 'subnet_bits' && $this->getParentNode()->subnet->isFieldChanged()) {
// no need to validate on subnet_bits as subnet is changed as well, let's trigger on subnet only
return $validators;
}
$validators[] = new CallbackValidator(["callback" => function ($data) {
$parent = $this->getParentNode();
$subnet_bits = (string)$parent->subnet_bits;
$subnet = (string)$parent->subnet;
$messages = [];
if (!Util::isSubnet($subnet . "/" . $subnet_bits)) {
$messages[] = sprintf(
gettext('Entry "%s/%s" is not a valid network address.'),
$subnet,
$subnet_bits
);
}
return $messages;
}
]);
return $validators;
}
}

View File

@ -0,0 +1,193 @@
<?php
/*
* Copyright (C) 2022 Deciso B.V.
* 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.
*/
namespace OPNsense\Interfaces;
use Phalcon\Messages\Message;
use OPNsense\Base\BaseModel;
use OPNsense\Core\Config;
use OPNsense\Firewall\Util;
class Vip extends BaseModel
{
/**
* {@inheritdoc}
*/
public function performValidation($validateFullModel = false)
{
$messages = parent::performValidation($validateFullModel);
$vips = [];
$carp_vhids = [];
// collect chaned VIP entries
$vip_fields = ['mode', 'subnet', 'subnet_bits', 'password', 'vhid', 'interface'];
foreach ($this->getFlatNodes() as $key => $node) {
$tagName = $node->getInternalXMLTagName();
$parentNode = $node->getParentNode();
if ($validateFullModel || $node->isFieldChanged()) {
if ($parentNode->getInternalXMLTagName() === 'vip' && in_array($tagName, $vip_fields)) {
$parentKey = $parentNode->__reference;
$vips[$parentKey] = $parentNode;
}
}
if ((string)$parentNode->mode == 'carp' && !isset($carp_vhids[(string)$parentNode->vhid])) {
$carp_vhids[(string)$parentNode->vhid] = $parentNode;
}
}
// validate all changed VIPs
foreach ($vips as $key => $node) {
$subnet_bits = (string)$node->subnet_bits;
$subnet = (string)$node->subnet;
if (in_array((string)$node->mode, ['carp', 'ipalias'])) {
if (Util::isSubnet($subnet . "/" . $subnet_bits) && strpos($subnet, ':') === false) {
$sm = 0;
for ($i = 0; $i < $subnet_bits; $i++) {
$sm >>= 1;
$sm |= 0x80000000;
}
$network_addr = long2ip(ip2long($subnet) & $sm);
$broadcast_addr = long2ip((ip2long($subnet) & 0xFFFFFFFF) | $sm);
if ($subnet == $network_addr && $subnet_bits != '32') {
$messages->appendMessage(
new Message(
gettext("You cannot use the network address for this VIP"),
$key . ".subnet"
)
);
} elseif ($subnet == $broadcast_addr && $subnet_bits != '32') {
$messages->appendMessage(
new Message(
gettext("You cannot use the broadcast address for this VIP"),
$key . ".subnet"
)
);
}
}
$configHandle = Config::getInstance()->object();
if (!empty($configHandle->interfaces) && !empty((string)$node->vhid)) {
foreach ($configHandle->interfaces->children() as $ifname => $ifnode) {
if ($ifname === (string)$node->interface && substr($ifnode->if, 0, 2) === 'lo') {
$messages->appendMessage(
new Message(
gettext('For this type of VIP loopback is not allowed.'),
$key . ".interface"
)
);
break;
}
}
}
}
if ((string)$node->mode == 'carp') {
if (empty((string)$node->password)) {
$messages->appendMessage(
new Message(
gettext("You must specify a CARP password that is shared between the two VHID members."),
$key . ".password"
)
);
}
if (empty((string)$node->vhid)) {
$messages->appendMessage(
new Message(
gettext('A VHID must be selected for this CARP VIP.'),
$key . ".vhid"
)
);
} elseif (
isset($carp_vhids[(string)$node->vhid]) &&
$carp_vhids[(string)$node->vhid]->__reference != $node->__reference
) {
$errmsg = gettext(
"VHID %s is already in use on interface %s. Pick a unique number on this interface."
);
$messages->appendMessage(
new Message(
sprintf($errmsg, (string)$node->vhid, (string)$carp_vhids[(string)$node->vhid]->interface),
$key . ".vhid"
)
);
}
} elseif (
(string)$node->mode == 'ipalias' &&
!empty((string)$node->vhid) && (
!isset($carp_vhids[(string)$node->vhid]) ||
(string)$carp_vhids[(string)$node->vhid]->interface != (string)$node->interface
)
) {
$errmsg = gettext("VHID %s must be defined on interface %s as a CARP VIP first.");
$messages->appendMessage(
new Message(
sprintf($errmsg, (string)$node->vhid, (string)$node->interface),
$key . ".vhid"
)
);
}
}
return $messages;
}
/**
* find relevant references to this address which prevent removal or change of this address.
*/
public function whereUsed($address)
{
$relevant_paths = [
'nat.outbound.rule.' => gettext('Address %s referenced by outboud nat rule "%s"'),
'nat.rule.' => gettext('Address %s referenced by port forward "%s"'),
];
$usages = [];
foreach (Config::getInstance()->object()->xpath("//text()[.='{$address}']") as $node) {
$referring_node = $node->xpath("..")[0];
$item_path = [$node->getName(), $referring_node->getName()];
$item_description = "";
$parent_node = $referring_node;
while ($parent_node != null && $parent_node->xpath("../..") != null) {
if (empty($item_description)) {
foreach (["description", "descr", "name"] as $key) {
if (!empty($parent_node->$key)) {
$item_description = (string)$parent_node->$key;
break;
}
}
}
$parent_node = $parent_node->xpath("..")[0];
$item_path[] = $parent_node->getName();
}
$item_path = implode('.', array_reverse($item_path))."\n";
foreach ($relevant_paths as $ref => $msg) {
if (preg_match("/^{$ref}/", $item_path)) {
$usages[] = sprintf($msg, $address, $item_description);
}
}
}
return $usages;
}
}

View File

@ -0,0 +1,77 @@
<model>
<mount>/virtualip</mount>
<version>1.0.0</version>
<description>
Virtual IP configuration
</description>
<items>
<vip type="ArrayField">
<interface type=".\VipInterfaceField">
<Required>Y</Required>
<AllowDynamic>S</AllowDynamic>
</interface>
<mode type="OptionField">
<Required>Y</Required>
<default>ipalias</default>
<OptionValues>
<ipalias>IP Alias</ipalias>
<carp>CARP</carp>
<proxyarp>Proxy ARP</proxyarp>
<!-- madness without function -->
<other>Other</other>
</OptionValues>
</mode>
<subnet type=".\VipNetworkField">
<!--required, but validated in model -->
<Required>N</Required>
<Constraints>
<check001>
<ValidationMessage>Address already assigned.</ValidationMessage>
<type>UniqueConstraint</type>
</check001>
</Constraints>
</subnet>
<subnet_bits type=".\VipNetworkField">
<!--required, but validated in model -->
<Required>N</Required>
</subnet_bits>
<gateway type="NetworkField">
<Required>N</Required>
</gateway>
<noexpand type="BooleanField">
<default>0</default>
<Required>Y</Required>
</noexpand>
<nobind type="BooleanField">
<default>0</default>
<Required>Y</Required>
</nobind>
<password type="TextField">
<Required>N</Required>
</password>
<vhid type="IntegerField">
<Required>N</Required>
<MinimumValue>1</MinimumValue>
<MaximumValue>255</MaximumValue>
<ValidationMessage>Invalid VHID number provided, Acceptable values for vhid are 1 to 255.</ValidationMessage>
</vhid>
<advbase type="IntegerField">
<Required>Y</Required>
<default>1</default>
<MinimumValue>1</MinimumValue>
<MaximumValue>254</MaximumValue>
<ValidationMessage>Invalid advertisement interval, acceptable values are 1 to 255.</ValidationMessage>
</advbase>
<advskew type="IntegerField">
<Required>Y</Required>
<default>0</default>
<MinimumValue>0</MinimumValue>
<MaximumValue>254</MaximumValue>
<ValidationMessage>Invalid skew value, acceptable values are 0 to 255.</ValidationMessage>
</advskew>
<descr type="TextField">
<Required>N</Required>
</descr>
</vip>
</items>
</model>

View File

@ -0,0 +1,129 @@
<script>
$( document ).ready(function() {
$("#grid-vips").UIBootgrid(
{ search:'/api/interfaces/vip_settings/searchItem/',
get:'/api/interfaces/vip_settings/getItem/',
set:'/api/interfaces/vip_settings/setItem/',
add:'/api/interfaces/vip_settings/addItem/',
del:'/api/interfaces/vip_settings/delItem/',
options:{
requestHandler: function(request){
if ( $('#mode_filter').val().length > 0) {
request['mode'] = $('#mode_filter').val();
}
return request;
},
formatters: {
vhid: function (column, row) {
return row.vhid_txt;
}
}
}
}
);
$("#mode_filter").change(function(){
$('#grid-vips').bootgrid('reload');
});
$("#vip\\.mode").change(function(){
$(".mode").closest("tr").hide();
let show_advanced = $("#show_advanced_formDialogDialogVip").hasClass("fa-toggle-on");
$(".mode_"+$(this).val()).each(function(){
if (($(this).hasClass("advanced") && show_advanced) || !$(this).hasClass("advanced")) {
$(this).closest("tr").show();
}
});
// carp button
if ($(this).val() == 'carp') {
$("#vip\\.vhid").css('width', '100px').addClass('btn-group');
$(".carp_btn").show();
} else {
$("#vip\\.vhid").css('width', '');
$(".carp_btn").hide();
}
});
// hook mode change to "show advanced" toggle to show dependant advanced fields
$("#show_advanced_formDialogDialogVip").click(function(e){
$("#vip\\.mode").change();
});
let vhid_btn = $("<button type='button' class='btn carp_btn btn-default btn-group'>").html("{{ lang._('Select an unassigned VHID')}}");
$("#vip\\.vhid").closest("td").prepend(
$("<div class='btn-group'>").append(
$("#vip\\.vhid").detach(),
vhid_btn
)
);
$("#mode_filter_container").detach().prependTo('#grid-vips-header > .row > .actionBar > .actions');
/**
* select an unassigned carp vhid
*/
vhid_btn.click(function(){
ajaxGet("/api/interfaces/vip_settings/get_unused_vhid", {}, function(data){
if (data.vhid !== undefined) {
$("#vip\\.vhid").val(data.vhid);
}
});
});
$("#reconfigureAct").SimpleActionButton();
});
</script>
<div class="tab-content content-box">
<div class="hidden">
<!-- filter per type container -->
<div id="mode_filter_container" class="btn-group">
<select id="mode_filter" data-title="{{ lang._('Filter type') }}" class="selectpicker" multiple="multiple" data-width="200px">
<option value="ipalias">{{ lang._('IP Alias') }}</option>
<option value="carp">{{ lang._('CARP') }}</option>
<option value="proxyarp">{{ lang._('Proxy ARP') }}</option>
<option value="other">{{ lang._('Other') }}</option>
</select>
</div>
</div>
<table id="grid-vips" class="table table-condensed table-hover table-striped" data-editDialog="DialogVip" data-editAlert="VipChangeMessage">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="address" data-type="string">{{ lang._('Address') }}</th>
<th data-column-id="vhid" data-type="string" data-formatter="vhid" >{{ lang._('VHID') }}</th>
<th data-column-id="interface" data-type="string">{{ lang._('Interface') }}</th>
<th data-column-id="mode" data-type="string">{{ lang._('Type') }}</th>
<th data-column-id="descr" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-primary"><span class="fa fa-fw fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-fw fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<div class="col-md-12">
<div id="VipChangeMessage" class="alert alert-info" style="display: none" role="alert">
{{ lang._('After changing settings, please remember to apply them with the button below') }}
</div>
<hr/>
<button class="btn btn-primary" id="reconfigureAct"
data-endpoint='/api/interfaces/vip_settings/reconfigure'
data-label="{{ lang._('Apply') }}"
data-error-title="{{ lang._('Error reconfiguring virtual IPs') }}"
type="button"
></button>
<br/><br/>
</div>
</div>
{{ partial("layout_partials/base_dialog",['fields':formDialogVip,'id':'DialogVip','label':lang._('Edit Virtual IP')])}}

View File

@ -0,0 +1,123 @@
#!/usr/local/bin/php
<?php
/*
* Copyright (C) 2022 Deciso B.V.
* 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.
*/
require_once("config.inc");
require_once("filter.inc");
require_once("interfaces.inc");
require_once("util.inc");
$addresses = [];
$anyproxyarp = false;
foreach (legacy_interfaces_details() as $ifname => $ifcnf) {
foreach (['ipv4', 'ipv6'] as $proto) {
if (!empty($ifcnf[$proto])) {
foreach ($ifcnf[$proto] as $address) {
$addresses[$address['ipaddr']] = [
'subnetbits' => $address['subnetbits'],
'if' => $ifname,
'vhid' => $address['vhid'] ?? '',
'advbase' => '',
'advskew' => ''
];
if (!empty($address['vhid'])) {
foreach ($ifcnf['carp'] as $vhid) {
if ($vhid['vhid'] == $address['vhid']) {
$addresses[$address['ipaddr']]['advbase'] = $vhid['advbase'];
$addresses[$address['ipaddr']]['advskew'] = $vhid['advskew'];
}
}
}
}
}
}
}
// remove deleted vips
foreach (glob("/tmp/delete_vip_*.todo") as $filename) {
$address = trim(file_get_contents($filename));
if (isset($addresses[$address])) {
legacy_interface_deladdress($addresses[$address]['if'], $address, is_ipaddrv6($address) ? 6 : 4);
} else {
// not found, likely proxy arp
$anyproxyarp = true;
}
unlink($filename);
}
// diff model and actual ifconfig
if (!empty($config['virtualip']['vip'])) {
$interfaces = [];
foreach (legacy_config_get_interfaces() as $interfaceKey => $itf) {
if (!empty($itf['if']) && ($itf['type'] ?? '') != 'group') {
$interfaces[$interfaceKey] = $itf['if'];
}
}
foreach ($config['virtualip']['vip'] as $vipent) {
if (!empty($vipent['interface']) && !empty($interfaces[$vipent['interface']])) {
$if = $interfaces[$vipent['interface']];
$subnet = $vipent['subnet'];
$subnet_bits = $vipent['subnet_bits'];
$vhid = $vipent['vhid'] ?? '';
$advbase = !empty($vipent['vhid']) ? $vipent['advbase'] : '';
$advskew = !empty($vipent['vhid']) ? $vipent['advskew'] : '';
if ($vipent['mode'] == 'proxyarp') {
$anyproxyarp = true;
}
if (in_array($vipent['mode'], ['proxyarp', 'other'])) {
if (isset($addresses[$subnet])) {
legacy_interface_deladdress($addresses[$subnet]['if'], $subnet, is_ipaddrv6($subnet) ? 6 : 4);
}
continue;
} elseif (
isset($addresses[$subnet]) &&
$addresses[$subnet]['subnetbits'] == $subnet_bits &&
$addresses[$subnet]['if'] == $if &&
$addresses[$subnet]['vhid'] == $vhid &&
$addresses[$subnet]['advbase'] == $advbase &&
$addresses[$subnet]['advskew'] == $advskew
) {
// configured and found equal
continue;
}
// default configure action depending on type
switch ($vipent['mode']) {
case 'ipalias':
interface_ipalias_configure($vipent);
break;
case 'carp':
interface_carp_configure($vipent);
break;
}
}
}
}
if ($anyproxyarp) {
interface_proxyarp_configure();
}

View File

@ -131,6 +131,11 @@ command: /usr/local/sbin/pluginctl -c loopback
message: Reconfiguring loopbacks
type: script
[vip.configure]
command: /usr/local/opnsense/scripts/interfaces/reconfigure_vips.php
message: Reconfiguring Virtual IPs
type: script
[show.protocol]
command:/usr/bin/netstat -s --libxo json
parameters:

View File

@ -766,7 +766,7 @@ $( document ).ready(function() {
foreach ($config['virtualip']['vip'] as $sn):
if (isset($sn['noexpand']))
continue;
if (in_array($sn['mode'], array("proxyarp", "other")) && $sn['type'] == "network"):
if ($sn['mode'] == "proxyarp"):
$start = ip2long32(gen_subnet($sn['subnet'], $sn['subnet_bits']));
$end = ip2long32(gen_subnet_max($sn['subnet'], $sn['subnet_bits']));
$len = $end - $start;

View File

@ -47,7 +47,7 @@ function formTranslateAddresses() {
if (isset($config['virtualip']['vip'])) {
foreach ($config['virtualip']['vip'] as $sn) {
if (!isset($sn['noexpand'])) {
if (in_array($sn['mode'], array("proxyarp", "other")) && $sn['type'] == "network") {
if ($sn['mode'] == "proxyarp") {
$start = ip2long32(gen_subnet($sn['subnet'], $sn['subnet_bits']));
$end = ip2long32(gen_subnet_max($sn['subnet'], $sn['subnet_bits']));
$len = $end - $start;

View File

@ -1,329 +0,0 @@
<?php
/*
* Copyright (C) 2014-2015 Deciso B.V.
* Copyright (C) 2005 Bill Marquette <bill.marquette@gmail.com>
* Copyright (C) 2003-2005 Manuel Kasper <mk@neon1.net>
* Copyright (C) 2004-2005 Scott Ullrich <sullrich@gmail.com>
* 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.
*/
require_once("guiconfig.inc");
require_once("interfaces.inc");
require_once("filter.inc");
/**
* delete virtual ip
*/
function deleteVIPEntry($id) {
global $config;
$input_errors = array();
$a_vip = &config_read_array('virtualip', 'vip');
/* make sure no inbound NAT mappings reference this entry */
if (isset($config['nat']['rule'])) {
foreach ($config['nat']['rule'] as $rule) {
if(!empty($rule['destination']['address'])) {
if ($rule['destination']['address'] == $a_vip[$id]['subnet']) {
$input_errors[] = gettext("This entry cannot be deleted because it is still referenced by at least one NAT mapping.");
break;
}
}
}
}
if (is_ipaddrv6($a_vip[$id]['subnet'])) {
list (, $if_subnet) = interfaces_primary_address6($a_vip[$id]['interface']);
$subnet = gen_subnetv6($a_vip[$id]['subnet'], $a_vip[$id]['subnet_bits']);
$is_ipv6 = true;
} else {
list (, $if_subnet) = interfaces_primary_address($a_vip[$id]['interface']);
$subnet = gen_subnet($a_vip[$id]['subnet'], $a_vip[$id]['subnet_bits']);
$is_ipv6 = false;
}
$subnet .= "/" . $a_vip[$id]['subnet_bits'];
if (isset($config['gateways']['gateway_item'])) {
foreach($config['gateways']['gateway_item'] as $gateway) {
if ($a_vip[$id]['interface'] != $gateway['interface'])
continue;
if ($is_ipv6 && $gateway['ipprotocol'] == 'inet')
continue;
if (!$is_ipv6 && $gateway['ipprotocol'] == 'inet6')
continue;
if (ip_in_subnet($gateway['gateway'], $if_subnet))
continue;
if (ip_in_subnet($gateway['gateway'], $subnet)) {
$input_errors[] = gettext("This entry cannot be deleted because it is still referenced by at least one Gateway.");
break;
}
}
}
if (count($input_errors) == 0) {
// Special case since every proxyarp vip is handled by the same daemon.
if ($a_vip[$id]['mode'] == "proxyarp") {
$viface = $a_vip[$id]['interface'];
unset($a_vip[$id]);
interface_proxyarp_configure($viface);
} else {
interface_vip_bring_down($a_vip[$id]);
unset($a_vip[$id]);
}
if (count($config['virtualip']['vip']) == 0) {
unset($config['virtualip']['vip']);
}
}
return $input_errors;
}
$a_vip = &config_read_array('virtualip', 'vip');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pconfig = $_POST;
if (isset($pconfig['id']) && isset($a_vip[$pconfig['id']])) {
// id found and valid
$id = $pconfig['id'];
}
if (isset($pconfig['apply'])) {
if (file_exists('/tmp/.firewall_virtual_ip.apply')) {
$toapplylist = unserialize(file_get_contents('/tmp/.firewall_virtual_ip.apply'));
foreach ($toapplylist as $vid => $ovip) {
if (!empty($ovip)) {
interface_vip_bring_down($ovip);
}
if (!empty($a_vip[$vid])) {
switch ($a_vip[$vid]['mode']) {
case "ipalias":
interface_ipalias_configure($a_vip[$vid]);
break;
case "proxyarp":
interface_proxyarp_configure($a_vip[$vid]['interface']);
break;
case "carp":
interface_carp_configure($a_vip[$vid]);
break;
default:
break;
}
}
}
@unlink('/tmp/.firewall_virtual_ip.apply');
}
filter_configure();
$savemsg = get_std_save_message();
clear_subsystem_dirty('vip');
} elseif (isset($pconfig['act']) && $pconfig['act'] == 'del' && isset($id)) {
$input_errors = deleteVIPEntry($id);
if (count($input_errors) == 0) {
write_config();
header(url_safe('Location: /firewall_virtual_ip.php'));
exit;
}
} elseif (isset($pconfig['act']) && $pconfig['act'] == 'del_x' && isset($pconfig['rule']) && count($pconfig['rule']) > 0) {
// delete selected VIPs, sort rule in reverse order to delete the highest item sequences first
foreach (array_reverse($pconfig['rule']) as $ruleId) {
if (isset($a_vip[$ruleId])) {
deleteVIPEntry($ruleId);
}
}
write_config();
header(url_safe('Location: /firewall_virtual_ip.php'));
exit;
} elseif (isset($pconfig['act']) && $pconfig['act'] == 'move' && isset($pconfig['rule']) && count($pconfig['rule']) > 0) {
// move selected rules
if (!isset($id)) {
// if rule not set/found, move to end
$id = count($a_vip);
}
$a_vip = legacy_move_config_list_items($a_vip, $id, $pconfig['rule']);
write_config();
header(url_safe('Location: /firewall_virtual_ip.php'));
exit;
}
}
include("head.inc");
?>
<body>
<script>
$( document ).ready(function() {
// link delete buttons
$(".act_delete").click(function(){
var id = $(this).attr("id").split('_').pop(-1);
// delete single
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("Virtual IP");?>",
message: "<?=gettext("Do you really want to delete this entry?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val(id);
$("#action").val("del");
$("#iform").submit()
}
}]
});
});
$("#del_x").click(function(){
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("Rules");?>",
message: "<?=gettext("Do you really want to delete the selected Virtual IPs?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val("");
$("#action").val("del_x");
$("#iform").submit()
}
}]
});
});
// link move buttons
$(".act_move").click(function(){
var id = $(this).attr("id").split('_').pop(-1);
$("#id").val(id);
$("#action").val("move");
$("#iform").submit();
});
// select All
$("#selectAll").click(function(){
$(".rule_select").prop("checked", $(this).prop("checked"));
});
});
</script>
<?php include("fbegin.inc"); ?>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<?php
if (isset($input_errors) && count($input_errors) > 0)
print_input_errors($input_errors);
else
if (isset($savemsg))
print_info_box($savemsg);
else
if (is_subsystem_dirty('vip'))
print_info_box_apply(gettext("The VIP configuration has been changed.")."<br />".gettext("You must apply the changes in order for them to take effect."));
?>
<section class="col-xs-12">
<div class="content-box tab-content">
<form method="post" name="iform" id="iform">
<input type="hidden" id="id" name="id" value="" />
<input type="hidden" id="action" name="act" value="" />
<table class="table table-striped">
<thead>
<tr>
<td><input type="checkbox" id="selectAll"></td>
<td><?=gettext("Virtual IP address");?></td>
<td><?=gettext("Interface");?></td>
<td><?=gettext("Type");?></td>
<td><?=gettext("Description");?></td>
<td>
<a href="firewall_virtual_ip_edit.php" class="btn btn-primary btn-xs" data-toggle="tooltip" title="<?= html_safe(gettext('Add')) ?>">
<i class="fa fa-plus fa-fw"></i> <?= $button['label'] ?>
</a>
<a type="submit" id="move_<?= count($a_vip) ?>" name="move_<?= count($a_vip) ?>_x" data-toggle="tooltip" title="<?= html_safe(gettext("Move selected virtual IPs to end")) ?>" class="act_move btn btn-default btn-xs">
<i class="fa fa-arrow-left fa-fw"></i>
</a>
<a id="del_x" title="<?= html_safe(gettext('delete selected virtual IPs')) ?>" data-toggle="tooltip" class="btn btn-default btn-xs">
<i class="fa fa-trash fa-fw"></i>
</a>
</td>
</tr>
</thead>
<tbody>
<?php
$interfaces = legacy_config_get_interfaces(array('virtual' => false));
$interfaces['lo0'] = array('descr' => 'Loopback');
$i = 0;
foreach ($a_vip as $vipent):
if(!empty($vipent['subnet']) || !empty($vipent['range']) || !empty($vipent['subnet_bits']) || (isset($vipent['range']['from']) && !empty($vipent['range']['from']))): ?>
<tr ondblclick="document.location='firewall_virtual_ip_edit.php?id=<?=$i;?>';">
<td>
<input class="rule_select" type="checkbox" name="rule[]" value="<?=$i;?>" />
</td>
<td>
<?=($vipent['type'] == "single" || $vipent['type'] == "network") && !empty($vipent['subnet_bits']) ? $vipent['subnet']."/".$vipent['subnet_bits'] : "";?>
<?=$vipent['type'] == "range" ? $vipent['range']['from'] . "-" . $vipent['range']['to'] : "";?>
<?=$vipent['mode'] == "carp" ? " (vhid {$vipent['vhid']} , freq. {$vipent['advbase']} / {$vipent['advskew']})" : "";?>
<?=$vipent['mode'] != "carp" && !empty($vipent['vhid']) ? " (vhid {$vipent['vhid']})" : "";?>
</td>
<td>
<?= htmlspecialchars($interfaces[$vipent['interface']]['descr']) ?>
</td>
<td>
<?=$vipent['mode'] == "proxyarp" ? "Proxy ARP" : "";?>
<?=$vipent['mode'] == "carp" ? "CARP" : "";?>
<?=$vipent['mode'] == "other" ? "Other" : "";?>
<?=$vipent['mode'] == "ipalias" ? "IP Alias" :"";?>
</td>
<td>
<?=htmlspecialchars($vipent['descr']);?>
</td>
<td>
<a id="move_<?=$i;?>" name="move_<?=$i;?>_x" data-toggle="tooltip" title="<?= html_safe(gettext("Move selected virtual IPs before this entry")) ?>" class="act_move btn btn-default btn-xs">
<span class="fa fa-arrow-left fa-fw"></span>
</a>
<a href="firewall_virtual_ip_edit.php?id=<?=$i;?>" data-toggle="tooltip" title="<?= html_safe(gettext('Edit')) ?>" class="btn btn-default btn-xs">
<span class="fa fa-pencil fa-fw"></span>
</a>
<a id="del_<?=$i;?>" title="<?= html_safe(gettext('Delete')) ?>" data-toggle="tooltip" class="act_delete btn btn-default btn-xs">
<span class="fa fa-trash fa-fw"></span>
</a>
<a href="firewall_virtual_ip_edit.php?dup=<?=$i;?>" class="btn btn-default btn-xs" data-toggle="tooltip" title="<?= html_safe(gettext('Clone')) ?>">
<span class="fa fa-clone fa-fw"></span>
</a>
</td>
</tr>
<?php
endif;
$i++;
endforeach;
?>
</tbody>
</table>
</form>
</div>
</section>
</div>
</div>
</section>
<?php include("foot.inc"); ?>

View File

@ -1,517 +0,0 @@
<?php
/*
* Copyright (C) 2014-2022 Deciso B.V.
* Copyright (C) 2005 Bill Marquette <bill.marquette@gmail.com>
* Copyright (C) 2003-2005 Manuel Kasper <mk@neon1.net>
* Copyright (C) 2004-2005 Scott Ullrich <sullrich@gmail.com>
* 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.
*/
require_once("guiconfig.inc");
require_once("interfaces.inc");
/**
* find max vhid
*/
function find_last_used_vhid() {
global $config;
$vhid = 0;
if (isset($config['virtualip']['vip'])) {
foreach($config['virtualip']['vip'] as $vip) {
if(!empty($vip['vhid']) && $vip['vhid'] > $vhid) {
$vhid = $vip['vhid'];
}
}
}
return $vhid;
}
$a_vip = &config_read_array('virtualip', 'vip');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (isset($_GET['dup']) && isset($a_vip[$_GET['dup']])) {
$configId = $_GET['dup'];
$after = $configId;
} elseif (isset($_GET['id']) && isset($a_vip[$_GET['id']])) {
$id = $_GET['id'];
$configId = $id;
}
$pconfig = [];
$form_fields = [
'advbase',
'advskew',
'descr',
'gateway',
'interface',
'mode',
'noexpand',
'password',
'subnet',
'subnet_bits',
'type',
'vhid',
];
// initialize empty form fields
foreach ($form_fields as $fieldname) {
$pconfig[$fieldname] = null;
}
$pconfig['bind'] = 'yes';
if (isset($configId)) {
// 1-on-1 copy of config data
foreach ($form_fields as $fieldname) {
if (isset($a_vip[$configId][$fieldname])) {
$pconfig[$fieldname] = $a_vip[$configId][$fieldname];
}
}
if (!empty($a_vip[$configId]['nobind'])) {
$pconfig['bind'] = null;
}
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = [];
$pconfig = $_POST;
// input record id, if valid
if (isset($pconfig['id']) && isset($a_vip[$pconfig['id']])) {
$id = $pconfig['id'];
}
if (!empty($config['interfaces'][$pconfig['interface']]) && !empty($config['interfaces'][$pconfig['interface']]['if'])) {
$selected_interface = $config['interfaces'][$pconfig['interface']]['if'];
} else {
$selected_interface = [];
}
// perform form validations
$reqdfields = ['mode'];
$reqdfieldsn = [gettext('Type')];
do_input_validation($pconfig, $reqdfields, $reqdfieldsn, $input_errors);
if (isset($id) && $pconfig['mode'] != $a_vip[$id]['mode']) {
$input_errors[] = gettext("Virtual IP mode may not be changed for an existing entry.");
} else {
if (isset($pconfig['subnet'])) {
$pconfig['subnet'] = trim($pconfig['subnet']);
if (!is_ipaddr($pconfig['subnet'])) {
$input_errors[] = gettext("A valid IP address must be specified.");
} else {
$ignore_if = isset($id) ? $a_vip[$id]['interface'] : $pconfig['interface'];
if (is_ipaddr_configured($pconfig['subnet'], $ignore_if)) {
$input_errors[] = gettext("This IP address is being used by another interface or VIP.");
}
}
}
if (!empty($pconfig['gateway']) && !is_ipaddr($pconfig['gateway'])) {
$input_errors[] = gettext("A valid gateway IP address must be specified.");
}
/* ipalias and carp should not use network or broadcast address */
if ($pconfig['mode'] == "ipalias" || $pconfig['mode'] == "carp") {
if (is_ipaddrv4($pconfig['subnet']) && $pconfig['subnet_bits'] != '32' && $pconfig['subnet_bits'] != '31') {
$network_addr = gen_subnet($pconfig['subnet'], $pconfig['subnet_bits']);
$broadcast_addr = gen_subnet_max($pconfig['subnet'], $pconfig['subnet_bits']);
if (isset($network_addr) && $pconfig['subnet'] == $network_addr) {
$input_errors[] = gettext("You cannot use the network address for this VIP");
} else if (isset($broadcast_addr) && $pconfig['subnet'] == $broadcast_addr) {
$input_errors[] = gettext("You cannot use the broadcast address for this VIP");
}
}
}
/* make sure new ip is within the subnet of a valid ip
* on one of our interfaces (wan, lan optX)
*/
if ($pconfig['mode'] == 'carp') {
/* verify against reusage of vhids */
foreach($config['virtualip']['vip'] as $vipId => $vip) {
if (isset($vip['vhid']) && $vip['vhid'] == $pconfig['vhid'] && $vip['mode'] == 'carp' && $vip['interface'] == $pconfig['interface'] && $vipId != $id) {
$input_errors[] = sprintf(gettext("VHID %s is already in use on interface %s. Pick a unique number on this interface."),$pconfig['vhid'], convert_friendly_interface_to_friendly_descr($pconfig['interface']));
}
}
if (empty($pconfig['password'])) {
$input_errors[] = gettext("You must specify a CARP password that is shared between the two VHID members.");
}
if (empty($pconfig['vhid'])) {
$input_errors[] = gettext('A VHID must be selected for this CARP VIP.');
}
if (substr($selected_interface,0, 2) === 'lo') {
$input_errors[] = gettext('For this type of VIP loopback is not allowed.');
}
} else if ($pconfig['mode'] != 'ipalias' && substr($selected_interface,0, 2) === 'lo') {
$input_errors[] = gettext('For this type of VIP loopback is not allowed.');
} elseif ($pconfig['mode'] == 'ipalias' && !empty($pconfig['vhid'])) {
$carpvip_found = false;
foreach($config['virtualip']['vip'] as $vipId => $vip) {
if ($vip['interface'] == $pconfig['interface'] && $vip['vhid'] == $pconfig['vhid'] && $vip['mode'] == 'carp') {
$carpvip_found = true ;
}
}
if (!$carpvip_found) {
$input_errors[] = sprintf(gettext("VHID %s must be defined on interface %s as a CARP VIP first."),$pconfig['vhid'], convert_friendly_interface_to_friendly_descr($pconfig['interface']));
}
}
}
if (count($input_errors) == 0) {
$vipent = [];
// defaults
$vipent['type'] = "single";
$vipent['subnet_bits'] = "32";
// 1-on-1 copy attributes
foreach (['mode', 'interface', 'descr', 'type', 'subnet_bits', 'subnet', 'vhid',
'advskew','advbase','password', 'gateway'] as $fieldname) {
if (isset($pconfig[$fieldname]) && $pconfig[$fieldname] != "") {
$vipent[$fieldname] = $pconfig[$fieldname];
}
}
if (!empty($pconfig['noexpand'])) {
// noexpand, only used for proxyarp
$vipent['noexpand'] = true;
}
if (empty($pconfig['bind']) && ($pconfig['mode'] == 'ipalias' || $pconfig['mode'] == 'carp')) {
// nobind, only used for ipalias/carp
$vipent['nobind'] = true;
}
// virtual ip UI keeps track of its changes in a separate file
// (which is only use on apply in firewall_virtual_ip)
// add or change this administration here.
// Not the nicest thing to do, but we keep it for now.
if (file_exists('/tmp/.firewall_virtual_ip.apply')) {
$toapplylist = unserialize(file_get_contents('/tmp/.firewall_virtual_ip.apply'));
} else {
$toapplylist = [];
}
if (isset($id)) {
// save existing content before changing it
$toapplylist[$id] = $a_vip[$id];
} else {
// new entry, no old data
$toapplylist[count($a_vip)] = [];
}
if (isset($id)) {
/* modify all virtual IP rules with this address */
for ($i = 0; isset($config['nat']['rule'][$i]); $i++) {
if (isset($config['nat']['rule'][$i]['destination']['address']) && $config['nat']['rule'][$i]['destination']['address'] == $a_vip[$id]['subnet']) {
$config['nat']['rule'][$i]['destination']['address'] = $vipent['subnet'];
}
}
}
// update or insert item in config
if (isset($id)) {
$a_vip[$id] = $vipent;
} else {
$a_vip[] = $vipent;
}
write_config();
mark_subsystem_dirty('vip');
file_put_contents('/tmp/.firewall_virtual_ip.apply', serialize($toapplylist));
header(url_safe('Location: /firewall_virtual_ip.php'));
exit;
}
}
legacy_html_escape_form_data($pconfig);
include("head.inc");
?>
<body>
<?php include("fbegin.inc");?>
<script>
$( document ).ready(function() {
$("#mode").change(function(){
//$("#subnet").attr('disabled', true);
$("#type").attr('disabled', true);
$("#gateway").attr('disabled', true);
$("#subnet_bits").attr('disabled', true);
$("#bind").attr('disabled', true);
$("#bindrow").addClass("hidden");
$("#password").attr('disabled', true);
$("#vhid").attr('disabled', true);
$("#advskew").attr('disabled', true);
$("#advbase").attr('disabled', true);
$("#noexpand").attr('disabled', true);
$("#noexpandrow").addClass("hidden");
$("#max_vhid").attr('disabled', true);
switch ($(this).val()) {
case "ipalias":
$("#type").prop("selectedIndex",0);
$("#gateway").attr('disabled', false);
$("#vhid").attr('disabled', false);
$("#subnet_bits").attr('disabled', false);
$("#bind").attr('disabled', false);
$("#bindrow").removeClass("hidden");
$("#typenote").html("<?= html_safe(gettext('Please provide a single IP address.')) ?>");
break;
case "carp":
$("#type").prop("selectedIndex",0);
$("#subnet_bits").attr('disabled', false);
$("#bind").attr('disabled', false);
$("#bindrow").removeClass("hidden");
$("#password").attr('disabled', false);
$("#vhid").attr('disabled', false);
$("#advskew").attr('disabled', false);
$("#advbase").attr('disabled', false);
$("#max_vhid").attr('disabled', false);
$("#typenote").html("<?= html_safe(gettext('This must be the network\'s subnet mask. It does not specify a CIDR range.')) ?>");
break;
case "proxyarp":
$("#type").attr('disabled', false);
$("#subnet_bits").attr('disabled', false);
$("#noexpand").attr('disabled', false);
$("#noexpandrow").removeClass("hidden");
$("#typenote").html("<?= html_safe(gettext('This is a CIDR block of proxy ARP addresses.')) ?>");
break;
case "other":
$("#type").attr('disabled', false);
$("#subnet_bits").attr('disabled', false);
$("#typenote").html("<?= html_safe(gettext('This must be the network\'s subnet mask. It does not specify a CIDR range.')) ?>");
break;
}
// refresh selectpickers
setTimeout(function(){
$('.selectpicker').selectpicker('refresh');
}, 100);
});
$("#max_vhid").click(function(event){
event.preventDefault();
$("#vhid").val($(this).data('vhid'));
$("#vhid").selectpicker('refresh');
});
// toggle initial mode change
$("#mode").change();
// IPv4/IPv6 select
hook_ipv4v6('ipv4v6net', 'network-id');
});
</script>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<?php if (isset($input_errors) && count($input_errors) > 0) print_input_errors($input_errors); ?>
<section class="col-xs-12">
<div class="content-box tab-content">
<form method="post" name="iform" id="iform">
<table class="table table-striped opnsense_standard_table_form">
<thead></thead>
<tbody>
<tr>
<td style="width:22%"><strong><?=gettext("Edit Virtual IP");?></strong></td>
<td style="width:78%; text-align:right">
<small><?=gettext("full help"); ?> </small>
<i class="fa fa-toggle-off text-danger" style="cursor: pointer;" id="show_all_help_page"></i>
</td>
</tr>
<tr>
<td><a id="help_for_mode" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Mode');?></td>
<td>
<select id="mode" name="mode" class="selectpicker" data-width="auto" data-live-search="true">
<option value="ipalias" <?=$pconfig['mode'] == "ipalias" ? "selected=\"selected\"" : ""; ?>><?=gettext("IP Alias");?></option>
<option value="carp" <?=$pconfig['mode'] == "carp" ? "selected=\"selected\"" : ""; ?>><?=gettext("CARP");?></option>
<option value="proxyarp" <?=$pconfig['mode'] == "proxyarp" ? "selected=\"selected\"" : ""; ?>><?=gettext("Proxy ARP");?></option>
<option value="other" <?=$pconfig['mode'] == "other" ? "selected=\"selected\"" : ""; ?>><?=gettext("Other");?></option>
</select>
<div class="hidden" data-for="help_for_mode">
<?=gettext("Proxy ARP and other type Virtual IPs cannot be bound to by anything running on the firewall, such as IPsec, OpenVPN, etc. Use a CARP or IP Alias type address for these cases.");?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Interface");?></td>
<td>
<select name="interface" class="selectpicker" data-width="auto">
<?php
$interfaces = legacy_config_get_interfaces(['virtual' => false]);
$interfaces['lo0'] = ['descr' => 'Loopback'];
foreach ($interfaces as $iface => $ifcfg): ?>
<option value="<?=$iface;?>" <?= $iface == $pconfig['interface'] ? 'selected="selected"' : '' ?>>
<?= htmlspecialchars($ifcfg['descr']) ?>
</option>
<?php
endforeach; ?>
</select>
</td>
</tr>
<tr>
<td><?=gettext("IP Address(es)");?></td>
<td></td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Type");?></td>
<td>
<select name="type" class="selectpicker" data-width="auto" id="type">
<option value="single" <?=(!empty($pconfig['subnet_bits']) && $pconfig['subnet_bits'] == 32) || !isset($pconfig['subnet']) ? "selected=\"selected\"" : "";?>>
<?=gettext("Single address");?>
</option>
<option value="network" <?=empty($pconfig['subnet_bits']) || $pconfig['subnet_bits'] != 32 || isset($pconfig['subnet']) ? "selected=\"selected\"" : "";?>>
<?=gettext("Network");?></option>
</select>
</td>
</tr>
<tr>
<td><a id="help_for_address" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Address");?></td>
<td>
<table style="border:0;">
<tr>
<td style="width:348px">
<input name="subnet" type="text" class="form-control" id="subnet" size="28" value="<?=$pconfig['subnet'];?>" />
</td>
<td>
<select name="subnet_bits" data-network-id="subnet" class="selectpicker ipv4v6net" data-size="10" data-width="auto" id="subnet_bits">
<?php for ($i = 128; $i >= 1; $i--): ?>
<option value="<?=$i;?>" <?= $i == $pconfig['subnet_bits'] ? "selected=\"selected\"" :""; ?>>
<?=$i;?>
</option>
<?php endfor ?>
</select>
</td>
</tr>
</table>
<div class="hidden" data-for="help_for_address">
<i id="typenote"></i>
</div>
</td>
</tr>
<tr id="bindrow">
<td><a id="help_for_bind" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Allow service binding') ?> </td>
<td>
<input id="bind" name="bind" type="checkbox" class="form-control" id="bind" <?= !empty($pconfig['bind']) ? 'checked="checked"' : '' ?> />
<div class="hidden" data-for="help_for_bind">
<?= gettext('Assigning services to the virtual IP\'s interface will automatically include this address. Uncheck to prevent binding to this address instead.');?>
</div>
</tr>
<tr>
<td><a id="help_for_gateway" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Gateway");?></td>
<td>
<input name="gateway" type="text" class="form-control" id="gateway" value="<?=$pconfig['gateway'];?>" />
<div class="hidden" data-for="help_for_gateway">
<?=gettext("For some interface types a gateway is required to configure an IP Alias (ppp/pppoe/tun), leave this field empty for all other interface types.");?>
</div>
</td>
</tr>
<tr id="noexpandrow">
<td><a id="help_for_noexpand" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Expansion");?> </td>
<td>
<input id="noexpand" name="noexpand" type="checkbox" class="form-control" id="noexpand" <?= !empty($pconfig['noexpand']) ? "checked=\"checked\"" : "" ; ?> />
<div class="hidden" data-for="help_for_noexpand">
<?=gettext("Disable expansion of this entry into IPs on NAT lists (e.g. 192.168.1.0/24 expands to 256 entries.");?>
</div>
</tr>
<tr>
<td><a id="help_for_password" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Virtual IP Password");?></td>
<td>
<input type='password' autocomplete='new-password' name='password' id="password" value="<?=$pconfig['password'];?>" />
<div class="hidden" data-for="help_for_password">
<?=gettext("Enter the VHID group password.");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_vhid" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("VHID Group");?></td>
<td>
<select id='vhid' name='vhid' class="selectpicker" data-size="10" data-width="auto">
<option value=""><?= gettext('none') ?></option>
<?php for ($i = 1; $i <= 255; $i++): ?>
<option value="<?=$i;?>" <?= $i == $pconfig['vhid'] ? "selected=\"selected\"" : ""; ?>>
<?=$i;?>
</option>
<?php endfor; ?>
</select>
<button type="button" data-vhid="<?=find_last_used_vhid() + 1;?>" id="max_vhid" class="btn btn-default btn-cs">
<?=gettext("Select an unassigned VHID");?>
</button>
<div class="hidden" data-for="help_for_vhid">
<?=gettext("Enter the VHID group that the machines will share.");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_adv" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Advertising Frequency");?></td>
<td>
<?=gettext("Base");?>:
<select id='advbase' name='advbase' class="selectpicker" data-size="10" data-width="auto">
<?php for ($i = 1; $i <= 254; $i++): ?>
<option value="<?=$i;?>" <?=$i == $pconfig['advbase'] ? "selected=\"selected\"" :""; ?>>
<?=$i;?>
</option>
<?php endfor; ?>
</select>
<?=gettext("Skew");?>:
<select id='advskew' name='advskew' class="selectpicker" data-size="10" data-width="auto">
<?php for ($i = 0; $i <= 254; $i++): ?>
<option value="<?=$i;?>" <?php if ($i == $pconfig['advskew']) echo "selected=\"selected\""; ?>>
<?=$i;?>
</option>
<?php endfor; ?>
</select>
<div class="hidden" data-for="help_for_adv">
<br/>
<?=gettext("The frequency that this machine will advertise. 0 usually means master. Otherwise the lowest combination of both values in the cluster determines the master.");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_descr" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Description");?></td>
<td>
<input name="descr" type="text" class="form-control" id="descr" size="40" value="<?=$pconfig['descr'];?>" />
<div class="hidden" data-for="help_for_adv">
<?=gettext("You may enter a description here for your reference (not parsed).");?>
</div>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input name="Submit" type="submit" class="btn btn-primary" value="<?=html_safe(gettext('Save')); ?>" />
<input type="button" class="btn btn-default" value="<?=html_safe(gettext('Cancel'));?>" onclick="window.location.href='/firewall_virtual_ip.php'" />
<?php if (isset($id) && $a_vip[$id]): ?>
<input name="id" type="hidden" value="<?=$id;?>" />
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</section>
</div>
</div>
</section>
<?php include("foot.inc"); ?>