Interfaces: Assignments - work in progress for https://github.com/opnsense/core/issues/9945

In order to migrate the interface assignments, we need to think of a way to use the differently named xml nodes for interfaces (wan, lan, ..) into something that closely resembles a standard model implementation.
Since we can't match these nodes in our statically defined model xmls, the main idea is to flush all via an in-memory model with a separate load [construct] and save hook.
This commit is contained in:
Ad Schellevis 2026-05-10 16:10:50 +02:00
parent 49b54ef032
commit 94a084e442
9 changed files with 342 additions and 0 deletions

7
plist
View file

@ -367,6 +367,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/AssignmentController.php
/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
@ -378,6 +379,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/AssignmentController.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
@ -389,6 +391,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/dialogAssignment.xml
/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
@ -810,6 +813,8 @@
/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/Assignment.php
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/Assignment.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
@ -999,6 +1004,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/assignment.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
@ -1266,6 +1272,7 @@
/usr/local/opnsense/scripts/interfaces/ifctl.sh
/usr/local/opnsense/scripts/interfaces/lib/__init__.py
/usr/local/opnsense/scripts/interfaces/list_arp.py
/usr/local/opnsense/scripts/interfaces/list_assign_options.php
/usr/local/opnsense/scripts/interfaces/list_hosts.py
/usr/local/opnsense/scripts/interfaces/list_macdb.py
/usr/local/opnsense/scripts/interfaces/list_ndp.py

View file

@ -0,0 +1,77 @@
<?php
/*
* Copyright (C) 2026 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;
class AssignmentController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'interface';
protected static $internalModelClass = 'OPNsense\Interfaces\Assignment';
public function searchItemAction()
{
return $this->searchBase("interface");
}
public function setItemAction($uuid)
{
return $this->setBase("interface", "interface", $uuid, $overlay);
}
public function addItemAction()
{
return $this->addBase("interface", "interface", $overlay);
}
public function getItemAction($uuid = null)
{
return $this->getBase("interface", "interface", $uuid);
}
public function delItemAction($uuid)
{
return $this->delBase("interface", $uuid);
}
public function reconfigureAction()
{
if ($this->request->isPost()) {
//(new Backend())->configdRun("interface xxx");
return ["status" => "ok"];
} else {
return ["status" => "failed"];
}
}
}

View file

@ -0,0 +1,40 @@
<?php
/*
* Copyright (C) 2026 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 AssignmentController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Interface/assignment');
$this->view->formDialogAssignment = $this->getForm("dialogAssignment");
$this->view->formGridAssignment = $this->getFormGrid("dialogAssignment");
}
}

View file

@ -0,0 +1,20 @@
<form>
<field>
<id>interface.descr</id>
<label>Description</label>
<type>text</type>
<help>You may enter a description here for your reference (not parsed).</help>
</field>
<field>
<id>interface.identifier</id>
<label>Identifier</label>
<type>text</type>
<help>Technical identifier of the interface, used by hasync for example.</help>
</field>
<field>
<id>interface.if</id>
<label>Interface</label>
<type>dropdown</type>
<help>Device name to connect this interface to.</help>
</field>
</form>

View file

@ -0,0 +1,50 @@
<?php
/*
* Copyright (C) 2026 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\BaseModel;
use OPNsense\Core\Config;
class Assignment extends BaseModel
{
/**
* @inheritdoc
*/
public function __construct($lazyload = false)
{
parent::__construct();
foreach (Config::getInstance()->object()->interfaces->children() as $key => $intf) {
$node = $this->interface->add($key);
$node->descr = (string)$intf->descr;
$node->identifier = $key;
$node->if = (string)$intf->if;
}
}
}

View file

@ -0,0 +1,15 @@
<model>
<mount>:memory:</mount>
<version>0.0.1</version>
<description>Interface assignments</description>
<items>
<interface type="ArrayField">
<descr type="DescriptionField"/>
<identifier type="TextField"/>
<if type="JsonKeyValueStoreField">
<ConfigdPopulateAct>interface list assign-opts</ConfigdPopulateAct>
<Required>Y</Required>
</if>
</interface>
</items>
</model>

View file

@ -0,0 +1,45 @@
{#
# Copyright (c) 2026 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() {
$("#{{formGridAssignment['table_id']}}").UIBootgrid(
{ search:'/api/interfaces/assignment/search_item/',
get:'/api/interfaces/assignment/get_item/',
set:'/api/interfaces/assignment/set_item/',
add:'/api/interfaces/assignment/add_item/',
del:'/api/interfaces/assignment/del_item/'
}
);
//$("#reconfigureAct").SimpleActionButton();
});
</script>
<div class="tab-content content-box">
{{ partial('layout_partials/base_bootgrid_table', formGridAssignment)}}
</div>
{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/interfaces/assignment/reconfigure'}) }}
{{ partial('layout_partials/base_dialog',['fields':formDialogAssignment,'id':formGridAssignment['edit_dialog_id'],'label':lang._('Edit Assignment')])}}

View file

@ -0,0 +1,79 @@
#!/usr/local/bin/php
<?php
/*
* Copyright (C) 2026 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("interfaces.inc");
require_once("config.inc");
require_once("util.inc");
/**
* XXX: needs to be refactored at some point, but for now keep it 100% compatible with legacy code.
*/
function list_devices($devices)
{
$interfaces = [
'hardware' => []
];
$excludes = [];
/* add physical network interfaces */
foreach (get_interface_list() as $key => $item) {
$interfaces['hardware'][$key] = $key . ' (' . $item['mac'] . ')';
}
/* add virtual network interfaces */
foreach ($devices as $device) {
if (!empty($device['names'])) {
foreach ($device['names'] as $key => $values) {
if (!empty($values)) {
if (!isset($interfaces[$device['type']])) {
$interfaces[$device['type']] = [];
}
$interfaces[$device['type']][] = $values['descr'];
if (!empty($values['exclude'])) {
$excludes = array_merge($excludes, $values['exclude']);
}
}
}
}
}
/* enforce constraints */
foreach ($excludes as $device) {
if (isset($interfaces[$device])) {
unset($interfaces[$device]);
}
}
return $interfaces;
}
$a_devices = plugins_devices();
echo json_encode(list_devices($a_devices));

View file

@ -66,6 +66,15 @@ type:script_output
cache_ttl:30
message:request arp table
[list.assign-opts]
command:/usr/local/opnsense/scripts/interfaces/list_assign_options.php
parameters:
type:script_output
cache_ttl:30
message:show assignable interfaces
[remove.arp]
command:/usr/sbin/arp -d
parameters:%s 2> /dev/null