Interfaces: Devices: Bridge - refactor to MVC closes https://github.com/opnsense/core/issues/8353 (#8534)

* Interfaces: Devices: Bridge - refactor to MVC for https://github.com/opnsense/core/issues/8353

* move existing properties to model which overlays existing config path
* add a simple wrapper script for [re]configuration which diffs and applies using the new _interfaces_bridge_configure() implementation

* Update src/opnsense/mvc/app/models/OPNsense/Interfaces/Bridge.xml

Co-authored-by: Franco Fichtner <franco@opnsense.org>

---------

Co-authored-by: Franco Fichtner <franco@opnsense.org>
This commit is contained in:
Ad Schellevis 2025-04-09 13:09:09 +02:00 committed by GitHub
parent 09bd2d96cc
commit 983a0663b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 695 additions and 744 deletions

10
plist
View File

@ -360,6 +360,7 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/IPsec/forms/dialogSPD.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/IPsec/forms/dialogVTI.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/IPsec/forms/settings.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/BridgeSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/GifSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/GreSettingsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/LaggSettingsController.php
@ -369,6 +370,7 @@
/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/BridgeController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/GifController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/GreController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/LaggController.php
@ -378,6 +380,7 @@
/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/dialogBridge.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogGif.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogGre.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogLagg.xml
@ -752,6 +755,9 @@
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Swanctl.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Swanctl.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/ACL/ACL.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Bridge.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Bridge.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/BridgeMemberField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/LaggInterfaceField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/LinkAddressField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/NeighborField.php
@ -915,6 +921,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/IPsec/vti.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/bridge.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/gif.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/gre.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Interface/lagg.volt
@ -1175,6 +1182,7 @@
/usr/local/opnsense/scripts/interfaces/ppp-linkup.sh
/usr/local/opnsense/scripts/interfaces/ppp-rename.sh
/usr/local/opnsense/scripts/interfaces/ppp-uptime.sh
/usr/local/opnsense/scripts/interfaces/reconfigure_bridges.php
/usr/local/opnsense/scripts/interfaces/reconfigure_gifs.php
/usr/local/opnsense/scripts/interfaces/reconfigure_gres.php
/usr/local/opnsense/scripts/interfaces/reconfigure_laggs.php
@ -2404,8 +2412,6 @@
/usr/local/www/index.php
/usr/local/www/interfaces.php
/usr/local/www/interfaces_assign.php
/usr/local/www/interfaces_bridge.php
/usr/local/www/interfaces_bridge_edit.php
/usr/local/www/interfaces_ppps.php
/usr/local/www/interfaces_ppps_edit.php
/usr/local/www/interfaces_wireless.php

View File

@ -0,0 +1,138 @@
<?php
/*
* Copyright (C) 2025 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\Base\ApiMutableModelControllerBase;
use OPNsense\Base\UserException;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
/**
* @package OPNsense\Interfaces
*/
class BridgeSettingsController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'bridge';
protected static $internalModelClass = 'OPNsense\Interfaces\Bridge';
/**
* search bridges
* @return array search results
*/
public function searchItemAction()
{
return $this->searchBase("bridged", null, "descr");
}
/**
* Update bridge with given properties
* @param string $uuid internal id
* @return array save result + validation output
*/
public function setItemAction($uuid)
{
Config::getInstance()->lock();
$node = $this->getModel()->getNodeByReference('bridged.' . $uuid);
$overlay = null;
if (!empty($node)) {
// not allowed to change bridge interface name
$overlay['bridgeif'] = (string)$node->bridgeif;
}
return $this->setBase("bridge", "bridged", $uuid, $overlay);
}
/**
* Add new bridge and set with attributes from post
* @return array save result + validation output
*/
public function addItemAction()
{
Config::getInstance()->lock();
$overlay = [];
$ifnames = [];
foreach ($this->getModel()->bridged->iterateItems() as $node) {
$ifnames[] = (string)$node->bridgeif;
}
for ($i = 0; true; ++$i) {
$gifif = sprintf('bridge%d', $i);
if (!in_array($gifif, $ifnames)) {
$overlay['bridgeif'] = $gifif;
break;
}
}
return $this->addBase("bridge", "bridged", $overlay);
}
/**
* Retrieve bridge settings or return defaults for new one
* @param $uuid item unique id
* @return array bridge content
*/
public function getItemAction($uuid = null)
{
return $this->getBase("bridge", "bridged", $uuid);
}
/**
* Delete bridge by uuid
* @param string $uuid internal id
* @return array save status
*/
public function delItemAction($uuid)
{
Config::getInstance()->lock();
$node = $this->getModel()->getNodeByReference('bridged.' . $uuid);
if ($node != null) {
$cfg = Config::getInstance()->object();
foreach ($cfg->interfaces->children() as $key => $value) {
if ((string)$value->if == (string)$node->bridgeif) {
throw new \OPNsense\Base\UserException(
sprintf(gettext("Cannot delete bridge. Currently in use by [%s] %s"), $key, $value),
gettext("bridge in use")
);
}
}
}
return $this->delBase("bridged", $uuid);
}
/**
* reconfigure bridges
*/
public function reconfigureAction()
{
if ($this->request->isPost()) {
(new Backend())->configdRun("interface bridge configure");
return ["status" => "ok"];
} else {
return ["status" => "failed"];
}
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* Copyright (C) 2025 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 BridgeController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Interface/bridge');
$this->view->formDialogBridge = $this->getForm("dialogBridge");
$this->view->formGridBridge = $this->getFormGrid("dialogBridge");
}
}

View File

@ -0,0 +1,194 @@
<form>
<field>
<id>bridge.bridgeif</id>
<label>Device</label>
<type>info</type>
<grid_view>
<width>12em</width>
</grid_view>
</field>
<field>
<id>bridge.members</id>
<label>Member interfaces</label>
<type>select_multiple</type>
<help>Interfaces participating in the bridge.</help>
</field>
<field>
<id>bridge.descr</id>
<label>Description</label>
<type>text</type>
<help>You may enter a description here for your reference (not parsed).</help>
</field>
<field>
<id>bridge.linklocal</id>
<label>Enable link-local address</label>
<type>checkbox</type>
<help>By default, link-local addresses for bridges are disabled. You can enable them manually using this option. However, when a bridge interface has IPv6 addresses, IPv6 addresses on a member interface will be automatically removed before the interface is added.</help>
<grid_view>
<type>boolean</type>
<formatter>boolean</formatter>
</grid_view>
</field>
<field>
<type>header</type>
<label>Spanning Tree Protocol (RSTP/STP)</label>
<advanced>true</advanced>
</field>
<field>
<id>bridge.enablestp</id>
<label>Enable</label>
<type>checkbox</type>
<help>Enable spanning tree options for this bridge.</help>
<advanced>true</advanced>
<grid_view>
<type>boolean</type>
<formatter>boolean</formatter>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.proto</id>
<label>Protocol</label>
<type>dropdown</type>
<help>Protocol used for spanning tree.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.stp</id>
<label>STP interfaces</label>
<type>select_multiple</type>
<help>Enable Spanning Tree Protocol on interface. The if_bridge(4) driver has support for the IEEE 802.1D Spanning Tree Protocol (STP). STP is used to detect and remove loops in a network topology.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.maxage</id>
<label>Valid time</label>
<type>text</type>
<help>Set the time that a Spanning Tree Protocol configuration is valid. The default is 20 seconds. The minimum is 6 seconds and the maximum is 40 seconds.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.fwdelay</id>
<label>Forward time</label>
<type>text</type>
<help>Set the time that must pass before an interface begins forwarding packets when Spanning Tree is enabled. The default is 15 seconds. The minimum is 4 seconds and the maximum is 30 seconds.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.holdcnt</id>
<label>Hold count</label>
<type>text</type>
<help>Set the transmit hold count for Spanning Tree. This is the number of packets transmitted before being rate limited. The default is 6. The minimum is 1 and the maximum is 10.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<type>header</type>
<label>Advanced</label>
<advanced>true</advanced>
</field>
<field>
<id>bridge.maxaddr</id>
<label>Cache size</label>
<type>text</type>
<help>Set the size of the bridge address cache to size. The default is 2000 entries.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.timeout</id>
<label>Cache entry expire time (s)</label>
<type>text</type>
<help>Set the timeout of address cache entries to this number of seconds. If seconds is zero, then address cache entries will not be expired. The default is 1200 seconds.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.span</id>
<label>Span port</label>
<type>dropdown</type>
<help>Add the interface named by interface as a span port on the bridge. Span ports transmit a copy of every frame received by the bridge. This is most useful for snooping a bridged network passively on another host connected to one of the span ports of the bridge.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.edge</id>
<label>Edge ports</label>
<type>select_multiple</type>
<help>Set interface as an edge port. An edge port connects directly to end stations and cannot create bridging loops in the network; this allows it to transition straight to forwarding.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.autoedge</id>
<label>Auto Edge ports</label>
<type>select_multiple</type>
<help>Allow interface to automatically detect edge status. This is the default for all interfaces added to a bridge. (This will disable the autoedge status of interfaces.)</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.ptp</id>
<label>PTP ports</label>
<type>select_multiple</type>
<help>Set the interface as a point-to-point link. This is required for straight transitions to forwarding and should be enabled on a direct link to another RSTP-capable switch.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.autoptp</id>
<label>Auto PTP ports</label>
<type>select_multiple</type>
<help>Automatically detect the point-to-point status on interface by checking the full duplex link status. This is the default for interfaces added to the bridge. (The interfaces selected here will be removed from default autoedge status.)</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.static</id>
<label>Sticky ports</label>
<type>select_multiple</type>
<help>Mark an interface as a "sticky" interface. Dynamically learned address entries are treated as static once entered into the cache. Sticky entries are never aged out of the cache or replaced, even if the address is seen on a different interface.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
<field>
<id>bridge.private</id>
<label>Private ports</label>
<type>select_multiple</type>
<help>Mark an interface as a "private" interface. A private interface does not forward any traffic to any other port that is also a private interface.</help>
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
</grid_view>
</field>
</form>

View File

@ -299,17 +299,12 @@
</patterns>
</page-interfaces-assignnetworkports>
<page-interfaces-bridge-edit>
<name>Interfaces: Bridge edit</name>
<patterns>
<pattern>interfaces_bridge_edit.php*</pattern>
</patterns>
</page-interfaces-bridge-edit>
<page-interfaces-bridge>
<name>Interfaces: Bridge</name>
<patterns>
<pattern>interfaces_bridge.php*</pattern>
<pattern>ui/interfaces/bridge</pattern>
<pattern>api/interfaces/bridge_settings/*</pattern>
</patterns>
</page-interfaces-bridge>
</page-interfaces-bridge-edit>
<page-interfaces-gif-edit>
<name>Interfaces: GIF</name>
<patterns>

View File

@ -0,0 +1,76 @@
<?php
/*
* Copyright (C) 2025 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 OPNsense\Base\Messages\Message;
use OPNsense\Base\BaseModel;
use OPNsense\Firewall\Util;
class Bridge extends BaseModel
{
/**
* {@inheritdoc}
*/
public function performValidation($validateFullModel = false)
{
$messages = parent::performValidation($validateFullModel);
foreach ($this->bridged->iterateItems() as $bridge) {
if (!$validateFullModel && !$bridge->isFieldChanged()) {
continue;
}
$key = $bridge->__reference;
$members = explode(',', $bridge->members->getCurrentValue());
if (!$bridge->span->isEmpty() && in_array($bridge->span->getCurrentValue(), $members)) {
$messages->appendMessage(
new Message(
gettext("Span interface cannot be part of the bridge."),
$key . ".span"
)
);
}
foreach (['stp', 'edge', 'autoedge', 'ptp', 'autoptp', 'static', 'private'] as $section) {
if ($bridge->$section->isEmpty()) {
continue;
}
foreach (explode(',', $bridge->$section->getCurrentValue()) as $if) {
if (!in_array($if, $members)) {
$messages->appendMessage(
new Message(
gettext("Contains non bridge members."),
$key . "." . $section
)
);
break;
}
}
}
}
return $messages;
}
}

View File

@ -0,0 +1,74 @@
<model>
<mount>/bridges</mount>
<version>1.0.0</version>
<description>Bridge interfaces</description>
<items>
<bridged type="ArrayField">
<bridgeif type="TextField">
<Required>Y</Required>
<Constraints>
<check001>
<ValidationMessage>Bridge already exists!</ValidationMessage>
<type>UniqueConstraint</type>
</check001>
</Constraints>
<Mask>/^bridge[\d]+$/</Mask>
</bridgeif>
<members type=".\BridgeMemberField">
<Required>Y</Required>
<Multiple>Y</Multiple>
</members>
<linklocal type="BooleanField"/>
<enablestp type="BooleanField"/>
<proto type="OptionField">
<Required>Y</Required>
<Default>rstp</Default>
<OptionValues>
<rstp>RSTP</rstp>
<stp>STP</stp>
</OptionValues>
</proto>
<stp type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</stp>
<maxage type="IntegerField">
<MinimumValue>6</MinimumValue>
<MaximumValue>40</MaximumValue>
</maxage>
<fwdelay type="IntegerField">
<MinimumValue>4</MinimumValue>
<MaximumValue>30</MaximumValue>
</fwdelay>
<holdcnt type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>10</MaximumValue>
</holdcnt>
<maxaddr type="IntegerField">
<MinimumValue>1</MinimumValue>
</maxaddr>
<timeout type="IntegerField">
<MinimumValue>0</MinimumValue>
</timeout>
<span type=".\BridgeMemberField"/>
<edge type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</edge>
<autoedge type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</autoedge>
<ptp type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</ptp>
<autoptp type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</autoptp>
<static type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</static>
<private type=".\BridgeMemberField">
<Multiple>Y</Multiple>
</private>
<descr type="DescriptionField"/>
</bridged>
</items>
</model>

View File

@ -0,0 +1,58 @@
<?php
/*
* Copyright (C) 2025 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 BridgeMemberField 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->virtual)) {
continue;
} elseif (!empty((string)$node->if) && str_starts_with('gre', $node->if)) {
continue;
} elseif (!empty((string)$node->if) && str_starts_with('lo', $node->if)) {
continue;
}
$descr = !empty((string)$node->descr) ? (string)$node->descr : strtoupper($ifname);
self::$interfaces[$ifname] = $descr;
}
}
}
$this->internalOptionList = self::$interfaces;
return parent::actionPostLoadingEvent();
}
}

View File

@ -2,9 +2,7 @@
<Interfaces>
<Assignments order="200" url="/interfaces_assign.php" cssClass="fa fa-pencil fa-fw"/>
<Devices order="210" cssClass="fa fa-archive fa-fw">
<Bridge url="/interfaces_bridge.php">
<Edit url="/interfaces_bridge_edit.php*" visibility="hidden"/>
</Bridge>
<Bridge url="/ui/interfaces/bridge"/>
<GIF url="/ui/interfaces/gif"/>
<GRE url="/ui/interfaces/gre"/>
<LAGG url="/ui/interfaces/lagg"/>

View File

@ -0,0 +1,45 @@
{#
# Copyright (c) 2025 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.
#}
<script>
$( document ).ready(function() {
$("#{{formGridBridge['table_id']}}").UIBootgrid(
{ search:'/api/interfaces/bridge_settings/searchItem/',
get:'/api/interfaces/bridge_settings/getItem/',
set:'/api/interfaces/bridge_settings/setItem/',
add:'/api/interfaces/bridge_settings/addItem/',
del:'/api/interfaces/bridge_settings/delItem/'
}
);
$("#reconfigureAct").SimpleActionButton();
});
</script>
<div class="tab-content content-box">
{{ partial('layout_partials/base_bootgrid_table', formGridBridge)}}
</div>
{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/interfaces/bridge_settings/reconfigure'}) }}
{{ partial('layout_partials/base_dialog',['fields':formDialogBridge,'id':formGridBridge['edit_dialog_id'],'label':lang._('Edit Bridge')])}}

View File

@ -0,0 +1,53 @@
#!/usr/local/bin/php
<?php
/*
* Copyright (C) 2025 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 'interfaces.inc';
require_once 'util.inc';
require_once 'system.inc';
$ifconfig_details = legacy_interfaces_details();
$current_bridgeifs = [];
if (isset($config['bridges']['bridged'])) {
foreach ($config['bridges']['bridged'] as $bridge) {
$current_bridgeifs[$bridge['bridgeif']] = $bridge;
}
}
/* delete before update */
foreach (array_keys($ifconfig_details) as $ifname) {
if (str_starts_with($ifname, 'bridge') && !isset($current_bridgeifs[$ifname])) {
legacy_interface_destroy($ifname);
}
}
/* update and create new */
foreach ($current_bridgeifs as $bridge) {
_interfaces_bridge_configure($bridge, $ifconfig_details);
}

View File

@ -163,6 +163,11 @@ command: /usr/local/sbin/pluginctl -c vxlan
message: Reconfiguring VXLAN
type: script
[bridge.configure]
command: /usr/local/opnsense/scripts/interfaces/reconfigure_bridges.php
message: Reconfiguring bridge interfaces
type: script
[vlan.configure]
command: /usr/local/opnsense/scripts/interfaces/reconfigure_vlans.php
message: Reconfiguring vlan

View File

@ -1,164 +0,0 @@
<?php
/*
* Copyright (C) 2014-2015 Deciso B.V.
* Copyright (C) 2008 Ermal Luçi
* 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");
$a_bridges = &config_read_array('bridges', 'bridged') ;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = array();
if (!empty($a_bridges[$_POST['id']])) {
$id = $_POST['id'];
}
if (!empty($_POST['action']) && $_POST['action'] == "del" && isset($id)) {
if (is_interface_assigned($a_bridges[$id]['bridgeif'])) {
$input_errors[] = gettext("This bridge cannot be deleted because it is assigned as an interface.");
} else {
mwexec("/sbin/ifconfig " . escapeshellarg($a_bridges[$id]['bridgeif']) . " destroy");
unset($a_bridges[$id]);
write_config();
header(url_safe('Location: /interfaces_bridge.php'));
exit;
}
}
}
include("head.inc");
legacy_html_escape_form_data($a_bridges);
?>
<body>
<script>
$( document ).ready(function() {
// link delete buttons
$(".act_delete").click(function(event){
event.preventDefault();
var id = $(this).data("id");
// delete single
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("Bridge");?>",
message: "<?=gettext("Do you really want to delete this bridge?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val(id);
$("#action").val("del");
$("#iform").submit()
}
}]
});
});
});
</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); ?>
<section class="col-xs-12">
<div class="tab-content content-box col-xs-12">
<form method="post" name="iform" id="iform">
<input type="hidden" id="action" name="action" value="">
<input type="hidden" id="id" name="id" value="">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th><?=gettext("Interface");?></th>
<th><?=gettext("Members");?></th>
<th><?=gettext("Description");?></th>
<th><?=gettext("Link-local");?></th>
<th class="text-nowrap">
<a href="interfaces_bridge_edit.php" class="btn btn-primary btn-xs" data-toggle="tooltip" title="<?= html_safe(gettext('Add')) ?>">
<i class="fa fa-plus fa-fw"></i>
</a>
</th>
</tr>
</thead>
<tbody>
<?php
$i = 0;
$ifdescrs = [];
foreach (legacy_config_get_interfaces(['virtual' => false]) as $intf => $intfdata) {
if (substr($intfdata['if'], 0, 3) != 'gre' && substr($intfdata['if'], 0, 2) != 'lo') {
$ifdescrs[$intf] = $intfdata['descr'];
}
}
foreach ($a_bridges as $bridge): ?>
<tr>
<td><?= $bridge['bridgeif'] ?></td>
<td>
<?php
$members = explode(',', $bridge['members'] ?? '');
foreach ($members as $key => $member) {
if (!isset($ifdescrs[$member])) {
unset($members[$key]);
} else {
$members[$key] = $ifdescrs[$member];
}
}
echo implode(', ', $members); ?>
</td>
<td><?=$bridge['descr'];?></td>
<td><?= !empty($bridge['linklocal']) ? gettext('On') : gettext('Off') ?></td>
<td class="text-nowrap">
<a href="interfaces_bridge_edit.php?id=<?=$i;?>" class="btn btn-xs btn-default" data-toggle="tooltip" title="<?= html_safe(gettext('Edit')) ?>">
<i class="fa fa-pencil fa-fw"></i>
</a>
<button title="<?= html_safe(gettext('Delete')) ?>" data-toggle="tooltip" data-id="<?=$i;?>" class="btn btn-default btn-xs act_delete" type="submit">
<i class="fa fa-trash fa-fw"></i>
</button>
</td>
</tr>
<?php
$i++;
endforeach; ?>
</tbody>
</table>
</div>
</form>
</div>
</section>
</div>
</div>
</section>
<?php
include 'foot.inc';

View File

@ -1,567 +0,0 @@
<?php
/*
* Copyright (C) 2014-2015 Deciso B.V.
* Copyright (C) 2008 Ermal Luçi
* 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("system.inc");
require_once("interfaces.inc");
require_once("filter.inc");
$a_bridges = &config_read_array('bridges', 'bridged');
// interface list
$ifacelist = [];
foreach (legacy_config_get_interfaces(['virtual' => false]) as $intf => $intfdata) {
if (substr($intfdata['if'], 0, 3) != 'gre' && substr($intfdata['if'], 0, 2) != 'lo') {
$ifacelist[$intf] = $intfdata['descr'];
}
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// read form data
if (isset($_GET['id']) && !empty($a_bridges[$_GET['id']])) {
$id = $_GET['id'];
}
// copy fields 1-on-1
$copy_fields = ['descr', 'bridgeif', 'maxaddr', 'timeout', 'maxage','fwdelay', 'proto', 'holdcnt', 'span'];
foreach ($copy_fields as $fieldname) {
if (isset($a_bridges[$id][$fieldname])) {
$pconfig[$fieldname] = $a_bridges[$id][$fieldname];
} else {
$pconfig[$fieldname] = null;
}
}
// bool fields
$pconfig['enablestp'] = !empty($a_bridges[$id]['enablestp']);
$pconfig['linklocal'] = !empty($a_bridges[$id]['linklocal']);
// simple array fields
$array_fields = ['members', 'stp', 'edge', 'autoedge', 'ptp', 'autoptp', 'static', 'private'];
foreach ($array_fields as $fieldname) {
if (!empty($a_bridges[$id][$fieldname])) {
$pconfig[$fieldname] = explode(',', $a_bridges[$id][$fieldname]);
} else {
$pconfig[$fieldname] = [];
}
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
// save / validate formdata
if (isset($_POST['id']) && !empty($a_bridges[$_POST['id']])) {
$id = $_POST['id'];
}
$input_errors = [];
$pconfig = $_POST;
$not_between_int = function($val, $min, $max) {
return !is_numeric($val) || $val < $min || $val > $max;
};
if (!empty($pconfig['maxage']) && $not_between_int($pconfig['maxage'], 6, 40)) {
$input_errors[] = gettext("Maxage needs to be an integer between 6 and 40.");
}
if ($pconfig['maxaddr'] != "" && $not_between_int($pconfig['maxaddr'], 0, PHP_INT_MAX)) {
$input_errors[] = gettext("Maxaddr needs to be an integer.");
}
if ($pconfig['timeout'] != "" && $not_between_int($pconfig['timeout'], 0, PHP_INT_MAX)) {
$input_errors[] = gettext("Timeout needs to be an integer.");
}
if (!empty($pconfig['fwdelay']) && $not_between_int($pconfig['fwdelay'], 4, 30)) {
$input_errors[] = gettext("Forward Delay needs to be an integer between 4 and 30.");
}
if (!empty($pconfig['holdcnt']) && $not_between_int($pconfig['holdcnt'], 1, 10)) {
$input_errors[] = gettext("Transmit Hold Count for STP needs to be an integer between 1 and 10.");
}
$members = !empty($pconfig['members']) ? $pconfig['members'] : [];
if (!empty($members)) {
foreach($members as $ifmembers) {
if (empty($config['interfaces'][$ifmembers])) {
$input_errors[] = gettext("A member interface passed does not exist in configuration");
}
if (!empty($config['interfaces'][$ifmembers]['wireless']['mode']) && $config['interfaces'][$ifmembers]['wireless']['mode'] != "hostap") {
$input_errors[] = gettext("Bridging a wireless interface is only possible in hostap mode.");
}
if ($pconfig['span'] != "none" && $pconfig['span'] == $ifmembers) {
$input_errors[] = gettext("Span interface cannot be part of the bridge. Remove the span interface from bridge members to continue.");
}
}
}
foreach (['stp', 'edge', 'autoedge', 'ptp', 'autoptp', 'static', 'private'] as $section) {
if (!empty($pconfig[$section])) {
foreach ($pconfig[$section] as $if) {
if (!in_array($if, $members)) {
$ifname = !empty($ifacelist[$if]) ? $ifacelist[$if] : $if;
$input_errors[] = gettext(sprintf("Option %s contains non bridge member interface %s", $section, $ifname));
}
}
}
}
if (count($input_errors) == 0) {
$bridge = [];
// booleans
foreach (['enablestp', 'linklocal'] as $fieldname) {
if (!empty($pconfig[$fieldname])) {
$bridge[$fieldname] = true;
}
}
// 1 on 1 copy
$copy_fields = ['descr', 'maxaddr', 'timeout', 'bridgeif', 'maxage','fwdelay', 'proto', 'holdcnt'];
foreach ($copy_fields as $fieldname) {
if (isset($pconfig[$fieldname]) && $pconfig[$fieldname] != '') {
$bridge[$fieldname] = $pconfig[$fieldname];
} else {
$bridge[$fieldname] = null;
}
}
if ($pconfig['span'] != 'none') {
$bridge['span'] = $pconfig['span'];
}
// simple array fields
$array_fields = ['members', 'stp', 'edge', 'autoedge', 'ptp', 'autoptp', 'static', 'private'];
foreach ($array_fields as $fieldname) {
if(!empty($pconfig[$fieldname])) {
$bridge[$fieldname] = implode(',', $pconfig[$fieldname]);
}
}
if (empty($bridge['bridgeif'])) {
$bridge['bridgeif'] = legacy_interface_create('bridge'); /* XXX find another strategy */
}
if (empty($bridge['bridgeif']) || strpos($bridge['bridgeif'], 'bridge') !== 0) {
$input_errors[] = gettext("Error occurred creating interface, please retry.");
} else {
if (isset($id)) {
$a_bridges[$id] = $bridge;
} else {
$a_bridges[] = $bridge;
}
write_config();
interfaces_bridge_configure($bridge['bridgeif']);
ifgroup_setup();
interfaces_restart_by_device(false, [$bridge['bridgeif']]);
header(url_safe('Location: /interfaces_bridge.php'));
exit;
}
}
}
legacy_html_escape_form_data($pconfig);
include("head.inc");
?>
<body>
<script>
$(document).ready(function() {
// advanced options
$("#show_advanced").click(function(){
$(".act_show_advanced").show();
$("#show_advanced_opt").hide();
});
});
</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); ?>
<section class="col-xs-12">
<form method="post" name="iform" id="iform">
<div class="tab-content content-box col-xs-12 __mb">
<div class="table-responsive">
<table class="table table-striped opnsense_standard_table_form">
<thead>
<tr>
<td style="width:22%"><strong><?=gettext("Bridge configuration");?></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>
&nbsp;
</td>
</tr>
</thead>
<tbody>
<tr>
<td><a id="help_for_members" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Member interfaces"); ?></td>
<td>
<select name="members[]" multiple="multiple" class="selectpicker" data-size="5" data-live-search="true">
<?php
$bridge_interfaces = [];
foreach ($a_bridges as $idx => $bridge_item) {
if (!isset($id) || $idx != $id) {
$bridge_interfaces = array_merge(explode(',', $bridge_item['members'] ?? ''), $bridge_interfaces);
}
}
foreach ($ifacelist as $ifn => $ifinfo):
if (!in_array($ifn, $bridge_interfaces)):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['members']) && in_array($ifn, $pconfig['members']) ? 'selected="selected"' : "";?>>
<?=$ifinfo;?>
</option>
<?php
endif;
endforeach;?>
</select>
<div class="hidden" data-for="help_for_members">
<?=gettext("Interfaces participating in the bridge."); ?>
</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 type="text" name="descr" value="<?=$pconfig['descr'];?>" />
<div class="hidden" data-for="help_for_descr">
<?=gettext("You may enter a description here for your reference (not parsed).");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_linklocal" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Link-local address') ?></td>
<td>
<input type="checkbox" name="linklocal" <?= !empty($pconfig['linklocal']) ? 'checked="checked"' : '' ?> />
<?= gettext('Enable link-local address') ?>
<div class="hidden" data-for="help_for_linklocal">
<?= gettext('By default, link-local addresses for bridges are disabled. You can enable them manually using this option. ' .
'However, when a bridge interface has IPv6 addresses, IPv6 addresses on a member interface will be automatically ' .
'removed before the interface is added.') ?>
</div>
</td>
</tr>
<tr id="show_advanced_opt">
<td></td>
<td>
<input type="button" id="show_advanced" class="btn btn-xs btn-default" value="<?= html_safe(gettext('Show advanced options')) ?>" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Advanced / RSTP/STP -->
<div class="tab-content content-box col-xs-12 __mb act_show_advanced" style="display:none">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<td colspan="2"><strong><?=gettext("Spanning Tree Protocol");?> (<?=gettext("RSTP/STP"); ?>)</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td style="width:22%"><a id="help_for_enablestp" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Enable");?></td>
<td style="width:78%">
<input type="checkbox" name="enablestp" <?= !empty($pconfig['enablestp']) ? 'checked="checked"' : "";?> />
<div class="hidden" data-for="help_for_enablestp">
<?=gettext("Enable spanning tree options for this bridge."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_proto" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Protocol"); ?></td>
<td>
<select name="proto" id="proto" class="selectpicker">
<option value="rstp" <?=$pconfig['proto'] == "rstp" ? "selected=\"selected\"" : "";?> >
<?=gettext("RSTP");?>
</option>
<option value="stp" <?=$pconfig['proto'] == "stp" ? "selected=\"selected\"" : "";?> >
<?=gettext("STP");?>
</option>
</select>
<div class="hidden" data-for="help_for_proto">
<?=gettext("Protocol used for spanning tree."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_stp" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("STP interfaces"); ?></td>
<td>
<select name="stp[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?= $ifn ?>" <?= !empty($pconfig['stp']) && in_array($ifn, $pconfig['stp']) ? 'selected="selected"' : '' ?> >
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_stp" >
<?=gettext("Enable Spanning Tree Protocol on interface. The if_bridge(4) " .
"driver has support for the IEEE 802.1D Spanning Tree Protocol " .
"(STP). STP is used to detect and remove loops in a " .
"network topology."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_maxage" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Valid time"); ?> (<?=gettext("seconds"); ?>)</td>
<td>
<input name="maxage" type="text" value="<?=$pconfig['maxage'];?>" />
<div class="hidden" data-for="help_for_maxage">
<?=gettext("Set the time that a Spanning Tree Protocol configuration is " .
"valid. The default is 20 seconds. The minimum is 6 seconds and " .
"the maximum is 40 seconds."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_fwdelay" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Forward time"); ?> (<?=gettext("seconds"); ?>)</td>
<td>
<input name="fwdelay" type="text" value="<?=$pconfig['fwdelay'];?>" />
<div class="hidden" data-for="help_for_fwdelay">
<?=gettext("Set the time that must pass before an interface begins forwarding " .
"packets when Spanning Tree is enabled. The default is 15 seconds. The minimum is 4 seconds and the maximum is 30 seconds."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_holdcnt" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Hold count"); ?></td>
<td>
<input name="holdcnt" type="text" value="<?=$pconfig['holdcnt'];?>" />
<div class="hidden" data-for="help_for_holdcnt">
<?=gettext("Set the transmit hold count for Spanning Tree. This is the number " .
"of packets transmitted before being rate limited. The " .
"default is 6. The minimum is 1 and the maximum is 10."); ?>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Advanced options-->
<div class="tab-content content-box col-xs-12 __mb act_show_advanced" style="display:none">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<td colspan="2"><strong><?=gettext("Advanced options");?></strong></td>
</tr>
</thead>
<tbody>
<tr>
<td style="width:22%"><a id="help_for_maxaddr" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Cache size"); ?> (<?=gettext("entries"); ?>)</td>
<td style="width:78%">
<input name="maxaddr" type="text" value="<?=$pconfig['maxaddr'];?>" />
<div class="hidden" data-for="help_for_maxaddr">
<?=gettext("Set the size of the bridge address cache to size. The default is 2000 entries."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_timeout" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Cache entry expire time"); ?> (<?=gettext("seconds"); ?>)</td>
<td>
<input name="timeout" type="text" value="<?=$pconfig['timeout'];?>" />
<div class="hidden" data-for="help_for_timeout">
<?=gettext("Set the timeout of address cache entries to this number of seconds. If " .
"seconds is zero, then address cache entries will not be expired. " .
"The default is 1200 seconds."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_span" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Span port"); ?></td>
<td>
<select name="span" class="selectpicker" data-live-search="true">
<option value="none"><?=gettext("None"); ?></option>
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=$ifn == $pconfig['span'] ? "selected=\"selected\"" : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_span">
<?=gettext("Add the interface named by interface as a span port on the " .
"bridge. Span ports transmit a copy of every frame received by " .
"the bridge. This is most useful for snooping a bridged network " .
"passively on another host connected to one of the span ports of " .
"the bridge."); ?><br/>
<span class="text-warning"><strong><?=gettext("Note:"); ?><br /></strong></span>
<?=gettext("The span interface cannot be part of the bridge member interfaces."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_edge" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Edge ports"); ?></td>
<td>
<select name="edge[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['edge']) && in_array($ifn, $pconfig['edge']) ? "selected=\"selected\"" : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_edge">
<?=gettext("Set interface as an edge port. An edge port connects directly to " .
"end stations and cannot create bridging loops in the network; this " .
"allows it to transition straight to forwarding."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_autoedge" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Auto Edge ports"); ?></td>
<td>
<select name="autoedge[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['autoedge']) && in_array($ifn, $pconfig['autoedge']) ? "selected=\"selected\"" : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_autoedge">
<?=gettext("Allow interface to automatically detect edge status. This is the " .
"default for all interfaces added to a bridge."); ?><br/>
<span class="text-warning"><strong><?=gettext("Note:"); ?><br /></strong></span>
<?=gettext("This will disable the autoedge status of interfaces."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_ptp" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("PTP ports"); ?></td>
<td>
<select name="ptp[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['ptp']) && in_array($ifn, $pconfig['ptp']) ? 'selected="selected"' : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_ptp">
<?=gettext("Set the interface as a point-to-point link. This is required for " .
"straight transitions to forwarding and should be enabled on a " .
"direct link to another RSTP-capable switch."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_autoptp" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Auto PTP ports"); ?></td>
<td>
<select name="autoptp[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['autoptp']) && in_array($ifn, $pconfig['autoptp']) ? 'selected="selected"' : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_autoptp">
<?=gettext("Automatically detect the point-to-point status on interface by " .
"checking the full duplex link status. This is the default for " .
"interfaces added to the bridge."); ?><br/>
<span class="text-warning"><strong><?=gettext("Note:"); ?><br /></strong></span>
<?=gettext("The interfaces selected here will be removed from default autoedge status."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_static" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Sticky ports"); ?></td>
<td>
<select name="static[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['static']) && in_array($ifn, $pconfig['static']) ? "selected=\"selected\"" : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_static">
<?=gettext('Mark an interface as a "sticky" interface. Dynamically learned ' .
"address entries are treated as static once entered into the cache. " .
"Sticky entries are never aged out of the cache or " .
"replaced, even if the address is seen on a different interface."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_private" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Private ports"); ?></td>
<td>
<select name="private[]" class="selectpicker" multiple="multiple" size="3" data-live-search="true">
<?php
foreach ($ifacelist as $ifn => $ifdescr):?>
<option value="<?=$ifn;?>" <?=!empty($pconfig['private']) && in_array($ifn, $pconfig['private']) ? "selected=\"selected\"" : "";?>>
<?=$ifdescr;?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_private">
<?=gettext('Mark an interface as a "private" interface. A private interface does not forward any traffic to any other port that is also ' .
"a private interface."); ?>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Advanced / RSTP/STP -->
<div class="tab-content content-box col-xs-12 __mb">
<div class="table-responsive">
<table class="table table-striped">
<tbody>
<tr>
<td style="width:22%">&nbsp;</td>
<td style="width:78%">
<input type="hidden" name="bridgeif" value="<?=$pconfig['bridgeif']; ?>" />
<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='/interfaces_bridge.php'" />
<?php if (isset($id)): ?>
<input name="id" type="hidden" value="<?=htmlspecialchars($id);?>" />
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</section>
</div>
</div>
</section>
<?php
include 'foot.inc';