/* * Copyright (c) 2025 Genexis B.V. All rights reserved. * * This Software and its content are protected by the Dutch Copyright Act * ('Auteurswet'). All and any copying and distribution of the software * and its content without authorization by Genexis B.V. is * prohibited. The prohibition includes every form of reproduction and * distribution. * */ import { getUciByType } from '../uci.js'; import { getBridgePortType, getTPIDFromDeviceType } from './common.js'; // find the port from bridge-vlan; returns [vlanId, isTagged, isPvid] or null function findPortFromBridgeVlan(bridgeVlans, portName) { if (!bridgeVlans) return null; for (const bridgeVlan of bridgeVlans) { const port = bridgeVlan.ports?.find(x => x.split(':')[0] === portName); if (port) { const flags = port.includes(':') ? port.split(':')[1] : ''; return [bridgeVlan.vlan, flags.includes('t'), flags.includes('*')]; } } return null; } function isVLANSubInterface(portName, ethPorts) { const names = portName.split('.'); if (names.length > 1) { const baseIfname = names.slice(0, -1).join('.'); if (ethPorts.find(x => x.ifname === baseIfname)) { return true; } } return false; } function createProviderBridge(dev, type, ethIndex, bridges, providerBridges, cVLANBridgeIndex) { const briIndex = bridges.length + 1; let sVLANBridgeIndex = bridges.findIndex((x) => x['VLAN.']?.[0]?.VLANID === dev.vid && x['Port.']?.[0]?.LowerLayers === `Device.Ethernet.Interface.${ethIndex}`); if (sVLANBridgeIndex < 0) { // no management port needed for provider bridge bridges.push({ Name: dev.name, Alias: `cpe-${dev.name}`, Standard: '802.1Q-2011', Enable: 1, 'Port.': [{ Enable: 1, Name: dev.name, Alias: `cpe-${dev.name}`, TPID: 34984, PVID: 1, Type: 'ProviderNetworkPort', LowerLayers: `Device.Ethernet.Interface.${ethIndex}`, }], 'VLAN.': [{ Enable: 1, VLANID: dev.vid, }], 'VLANPort.': [{ Enable: 1, VLAN: `Device.Bridging.Bridge.${briIndex}.VLAN.1`, Port: `Device.Bridging.Bridge.${briIndex}.Port.1`, Untagged: 1, }], _key: dev['.name'], }); sVLANBridgeIndex = bridges.length; } else { sVLANBridgeIndex = sVLANBridgeIndex + 1; const pvBridge = providerBridges.find(x => x.SVLANcomponent === `Device.Bridging.Bridge.${sVLANBridgeIndex}`); if (pvBridge) { pvBridge.CVLANcomponents = pvBridge.CVLANcomponents + `,Device.Bridging.Bridge.${cVLANBridgeIndex}`; return; } } providerBridges.push({ Alias: `cpe-${dev.name}`, Enable: 1, Type: type, SVLANcomponent: `Device.Bridging.Bridge.${sVLANBridgeIndex}`, CVLANcomponents: `Device.Bridging.Bridge.${cVLANBridgeIndex}`, _key: dev['.name'], }); } function addRegularEthernetPort(ethDevice, portIndex, briPorts) { const tpid = getTPIDFromDeviceType(ethDevice.type, ethDevice.tpid); briPorts.push({ Enable: 1, Name: ethDevice['ifname'], Alias: `cpe-${ethDevice['.name']}`, TPID: tpid, PVID: 1, Type: 'VLANUnawarePort', LowerLayers: `Device.Ethernet.Interface.${portIndex + 1}`, _key: ethDevice['.name'], }); } function handleVlanDevice(bridgeIndex, device, ethPorts, devices, bridges, briPorts, briVLAN, briVLANPort, providerBridges) { let qinqDev; let ethIndex = ethPorts.findIndex(x => device.ifname === x.ifname); if (device.type === '8021ad') { if (ethIndex < 0) { _log_error('base ethernet device not found', device.ifname); return; } createProviderBridge(device, 'S-VLAN', ethIndex + 1, bridges, providerBridges, bridgeIndex); return; } if (ethIndex < 0) { qinqDev = devices.find(x => x.name === device.ifname); if (!qinqDev || !qinqDev.ifname) { _log_error('device ifname not found', device.ifname); return; } if (qinqDev.type !== '8021ad' || device.type !== '8021q') { _log_error('invalid qinq device type', qinqDev['.name'], device['.name']); return; } device.ifname = qinqDev.ifname; ethIndex = ethPorts.findIndex(x => device.ifname === x.ifname); if (ethIndex < 0) { _log_error('base ethernet device not found', device.ifname); return; } } if (device.type !== '8021q') { _log_error('unsupported device type', device['.name'], device.type); return; } let vlanIndex = briVLAN.findIndex(x => Number(x.VLANID) === Number(device.vid)); if (vlanIndex < 0) { briVLAN.push({ Enable: 1, VLANID: Number(device.vid) }); vlanIndex = briVLAN.length; } else { vlanIndex += 1; } const portType = qinqDev ? 'CustomerEdgePort' : getBridgePortType(device.type); const tpid = getTPIDFromDeviceType(device.type, device.tpid); briPorts.push({ Enable: 1, Name: device['ifname'], Alias: `cpe-${device['.name']}`, TPID: tpid, PVID: device.pvid ? Number(device.vid): 1, Type: portType, LowerLayers: `Device.Ethernet.Interface.${ethIndex + 1}`, _key: device['.name'], }); briVLANPort.push({ Enable: 1, VLAN: `Device.Bridging.Bridge.${bridgeIndex}.VLAN.${vlanIndex}`, Port: `Device.Bridging.Bridge.${bridgeIndex}.Port.${briPorts.length}`, Untagged: device.untagged ? 1 : 0, _key: device['.name'], }); if (qinqDev && qinqDev.vid) { createProviderBridge(qinqDev, 'PE', ethIndex + 1, bridges, providerBridges, bridgeIndex); } } function importBridge(dev, devices, bridges, bridgeVlans, providerBridges) { const briPorts = []; const briVLAN = []; const briVLANPort = []; // create the management port first briPorts.push({ Alias: `cpe-${dev.name}`, Enable: 1, Name: dev.name, ManagementPort: 1, PVID: 1, TPID: 37120, Type: 'CustomerVLANPort', }); bridges.push({ Name: dev.name, Alias: `cpe-${dev.name}`, Enable: 1, 'Port.': briPorts, 'VLAN.': briVLAN, 'VLANPort.': briVLANPort, _key: dev['.name'], }); const ethPorts = devices.filter(x => x.type === undefined && x.eee !== undefined); for (const portName of (dev.ports || [])) { let device; const portIndex = ethPorts.findIndex(x => x.ifname === portName); const briVLANInfo = findPortFromBridgeVlan(bridgeVlans, portName); if (portIndex >= 0 && !briVLANInfo) { // Regular ethernet port const ethDevice = ethPorts[portIndex]; addRegularEthernetPort(ethDevice, portIndex, briPorts); continue; } if (briVLANInfo && portIndex >= 0) { // bridge-vlan device device = {['.name']: portName, ifname: portName, type: '8021q', name: portName, vid: Number(briVLANInfo[0]), untagged: !briVLANInfo[1], pvid: briVLANInfo[2]}; } else { // vlan device device = devices.find(x => x.name === portName); if (!device) { // check if it is a valid sub-interface if (isVLANSubInterface(portName, ethPorts)) { const ifname = portName.split('.').slice(0, -1).join('.'); const vid = portName.split('.').pop(); device = {['.name']: portName, ifname: ifname, type: '8021q', name: portName, vid: Number(vid)}; } else { _log_error('device not found', portName); return; } } } if (!device.ifname || !device.vid) { _log_error('ifname or vid not found', device['.name']); return; } handleVlanDevice(bridges.length, device, ethPorts, devices, bridges, briPorts, briVLAN, briVLANPort, providerBridges); } if (briPorts.length > 1) { const indexes = Array.from({ length: briPorts.length - 1 }, (v, i) => i + 2); briPorts[0].LowerLayers = indexes.map(i => `Device.Bridging.Bridge.${bridges.length}.Port.${i}`).join(','); } } export function importDeviceBridgingBridge() { const bridges = []; const providerBridges = []; const devices = getUciByType('network', 'device'); const bridgeVlans = getUciByType('network', 'bridge-vlan'); devices?.forEach(dev => { if (dev.type === 'bridge') { const bridgeVlan = bridgeVlans?.filter(x => x.device === dev.name); importBridge(dev, devices, bridges, bridgeVlan, providerBridges); } }); if (providerBridges.length > 0) { _dm_update('Device.Bridging.ProviderBridge.', providerBridges); } return bridges; }