diff --git a/src/etc/inc/interfaces.inc b/src/etc/inc/interfaces.inc index 2b8cb4f23b6..b39a978b857 100644 --- a/src/etc/inc/interfaces.inc +++ b/src/etc/inc/interfaces.inc @@ -182,7 +182,16 @@ function interfaces_vlan_configure($verbose = false) echo 'Configuring VLAN interfaces...'; flush(); } - + // Handle QinQ dependencies by sorting list of vlans to create (first all vlans so we can stack QinQ on top) + usort($config['vlans']['vlan'], function ($a, $b) { + $aqinq = strpos($a['vlanif'], 'vlan') !== false ? 0 : 1 ; + $bqinq = strpos($b['vlanif'], 'vlan') !== false ? 0 : 1 ; + if ($aqinq === $bqinq) { + return $a['vlanif'] <=> $b['vlanif']; + } else { + return $aqinq <=> $bqinq; + } + }); foreach ($config['vlans']['vlan'] as $vlan) { interface_vlan_configure($vlan); } @@ -196,12 +205,11 @@ function interface_vlan_configure($vlan) { interfaces_bring_up($vlan['if']); /* XXX overreach? */ - /* XXX avoid destroy/create */ + /* destroy is a safety precaution, when confguring via api or gui this function should only be called on new vlans */ legacy_interface_destroy($vlan['vlanif']); legacy_interface_create('vlan', $vlan['vlanif']); - $pcp = isset($vlan['pcp']) ? $vlan['pcp'] : 0; - legacy_vlan_tag($vlan['vlanif'], $vlan['if'], $vlan['tag'], $pcp); + legacy_vlan_tag($vlan['vlanif'], $vlan['if'], $vlan['tag'], $vlan['pcp']); interfaces_bring_up($vlan['vlanif']); } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VlanSettingsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VlanSettingsController.php index fa5366846f0..7fa571d3905 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VlanSettingsController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Interfaces/Api/VlanSettingsController.php @@ -38,10 +38,28 @@ class VlanSettingsController extends ApiMutableModelControllerBase protected static $internalModelName = 'vlan'; protected static $internalModelClass = 'OPNsense\Interfaces\Vlan'; - private function generateVlanIfName() + private function generateVlanIfName($current=null) { $tmp = $this->request->getPost("vlan"); - return "{$tmp['if']}_vlan{$tmp['tag']}"; + $prefix = (strpos($tmp['if'], 'vlan') === false ? "vlan" : "qinq"); + if ($current != null && (string)$current->vlanif == "{$tmp['if']}_vlan{$tmp['tag']}") { + // keep legacy naming + return "{$tmp['if']}_vlan{$tmp['tag']}"; + } elseif ($current != null && strpos((string)$current->vlanif, '_vlan') === false && + strpos((string)$current->vlanif, $prefix) === 0 + ) { + // new naming convention and same type, name stays the same + return (string)$current->vlanif; + } else { + // autonumber new + $ifid = 0; + foreach ($this->getModel()->vlan->iterateItems() as $node) { + if (strpos((string)$node->vlanif . "_", $prefix) === 0) { + $ifid = max($ifid, (int)explode("_", (string)$node->vlanif)[1]); + } + } + return $prefix . "_" . ($ifid+1); + } } private function interfaceAssigned($if) @@ -66,8 +84,23 @@ public function setItemAction($uuid) { $node = $this->getModel()->getNodeByReference('vlan.' . $uuid); $old_vlanif = $node != null ? (string)$node->vlanif : null; - $new_vlanif = $this->generateVlanIfName(); - if ($old_vlanif != null && $old_vlanif != $new_vlanif && $this->interfaceAssigned($old_vlanif)) { + $new_vlanif = $this->generateVlanIfName($node); + $children = 0; + foreach ($this->getModel()->vlan->iterateItems() as $node) { + if ((string)$node->if == $old_vlanif) { + $children++; + } + } + if ($old_vlanif != null && $old_vlanif != $new_vlanif && $children > 0) { + $result = [ + "result" => "failed", + "validations" => [ + "vlan.vlanif" => gettext("This VLAN cannot be deleted because it is used in QinQ interfaces.") + ] + ]; + } elseif ($old_vlanif != null && $old_vlanif != $new_vlanif && $this->interfaceAssigned($old_vlanif)) { + // Reassignment is only an issue when naming changes. These additional validations only apply + // for legacy interface nameing (e.g. _vlan_) and type changes vlan verses qinq. $tmp = $this->request->getPost("vlan"); if ($tmp['tag'] != (string)$node->tag) { $result = [ @@ -110,7 +143,15 @@ public function delItemAction($uuid) { $node = $this->getModel()->getNodeByReference('vlan.' . $uuid); $old_vlanif = $node != null ? (string)$node->vlanif : null; - if ($old_vlanif != null && $this->interfaceAssigned($old_vlanif)) { + $children = 0; + foreach ($this->getModel()->vlan->iterateItems() as $node) { + if ((string)$node->if == $old_vlanif) { + $children++; + } + } + if ($children > 0) { + throw new UserException(gettext("This VLAN cannot be deleted because it is used in QinQ interfaces.")); + } elseif ($old_vlanif != null && $this->interfaceAssigned($old_vlanif)) { throw new UserException(gettext("This VLAN cannot be deleted because it is assigned as an interface.")); } else { $result = $this->delBase("vlan", $uuid); diff --git a/src/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VlanInterfaceField.php b/src/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VlanInterfaceField.php index 54f766ecbea..18c7e9b2311 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VlanInterfaceField.php +++ b/src/opnsense/mvc/app/models/OPNsense/Interfaces/FieldTypes/VlanInterfaceField.php @@ -52,9 +52,7 @@ protected function actionPostLoadingEvent() $ifconfig = json_decode((new Backend())->configdRun('interface list ifconfig'), true); if (!empty($ifconfig)) { foreach ($ifconfig as $ifname => $details) { - // XXX: skip same interface types as legacy, may need to revise later - if ( - strpos($ifname, "_vlan") > 1 || strpos($ifname, "lo") === 0 || strpos($ifname, "enc") === 0 || + if (strpos($ifname, "qinq") === 0 || strpos($ifname, "lo") === 0 || strpos($ifname, "enc") === 0 || strpos($ifname, "pflog") === 0 || strpos($ifname, "pfsync") === 0 || strpos($ifname, "bridge") === 0 || strpos($ifname, "gre") === 0 || strpos($ifname, "gif") === 0 || strpos($ifname, "ipsec") === 0