Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abbility to create custom VLANs to network settings #2907

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
-- SPDX-FileCopyrightText: 2023, Leonardo Mörlein <[email protected]>

local uci = require("simple-uci").cursor()
local ethernet = require 'gluon.ethernet'

local wan = uci:get_all("network", "wan")
local wan6 = uci:get_all("network", "wan6")
local dns_static = uci:get_first("gluon-wan-dnsmasq", "static")


local f = Form(translate("WAN connection"))

local s = f:section(Section)
Expand Down Expand Up @@ -75,24 +75,7 @@ local pretty_ifnames = {
["/lan"] = translate("LAN Interfaces")
}

uci:foreach('gluon', 'interface', function(config)
local section_name = config['.name']
local ifaces = s:option(MultiListValue, section_name, pretty_ifnames[config.name] or config.name)

ifaces.orientation = 'horizontal'
ifaces:value('uplink', 'Uplink')
ifaces:value('mesh', 'Mesh')
ifaces:value('client', 'Client')
ifaces:exclusive('uplink', 'client')
ifaces:exclusive('mesh', 'client')

ifaces.default = config.role

function ifaces:write(data)
uci:set_list("gluon", section_name, "role", data)
end
end)

local vlan_interface_sections = {}

local section
uci:foreach("system", "gpio_switch", function(si)
Expand Down Expand Up @@ -153,5 +136,123 @@ function f:write()
uci:commit('system')
end

local f_vlan = Form(translate("VLAN Configuration"))

local s_vlan = f_vlan:section(Section)

s_vlan:element('model/warning', {
content = translate(
'Extended VLAN configuration is not supported on devices ' ..
'using legacy OpenWRT Switch Config (swconfig). ' ..
'This device needs to be ported to DSA for this feature to work.'
),
hide = ethernet.get_switch_type() ~= 'swconfig',
}, 'dsa_warning')

local action = s_vlan:option(ListValue, "action", translate("Action"))

-- Options for create_vlan_interface

local interface = s_vlan:option(ListValue, "interface", translate("Interface"))
for _, iface in ipairs(ethernet.interfaces()) do
-- TODO: this should not include vlan interfaces
interface:value(iface, iface)
end
interface:depends(action, "create_vlan_interface")

local vlan_id = s_vlan:option(Value, "vlan_id", translate("VLAN ID"))
vlan_id.datatype = "irange(1,4094)"
vlan_id:depends(action, "create_vlan_interface")

local function create_vlan_interface()
local new_iface = interface.data .. '.' .. vlan_id.data
local section_name = 'iface_' .. interface.data .. '_vlan' .. vlan_id.data

uci:section('gluon', 'interface', section_name, {
name = new_iface,
role = {}
})
end

-- TODO: implement by iterating over ethernet_interfaces() and add each individually
-- action:value("expand_wan_interfaces", translate("Expand WAN interfaces"))
-- action:value("contract_wan_interfaces", translate("Contract WAN interfaces"))

-- Options for delete_vlan_interface

local vlan_iface_to_delete = s_vlan:option(ListValue, "vlan_iface_to_delete", translate("VLAN Interface"))
vlan_iface_to_delete:depends(action, "delete_vlan_interface")
function vlan_iface_to_delete:validate()
if self.data == nil then
return true
end

local data = uci:get('gluon', self.data)
return data ~= nil
end

local function delete_vlan_interface()
uci:delete('gluon', vlan_iface_to_delete.data)
-- Clear field otherwise it gets set to something random
vlan_iface_to_delete.data = nil
end

-- Show options

if not ethernet.get_switch_type() ~= 'swconfig' then
action:value("create_vlan_interface", translate("Create VLAN interface config"))
action:value("delete_vlan_interface", translate("Delete VLAN interface config"))
end

local function render_interface_options()
uci:foreach('gluon', 'interface', function(config)
local section_name = config['.name']
local ifaces = s:option(MultiListValue, section_name, pretty_ifnames[config.name] or config.name)

-- TODO: refactor this to detect if this is a vlan by using the contained values instead of special magic?
if section_name:find("vlan") then
vlan_interface_sections[section_name] = config.name
end

ifaces.orientation = 'horizontal'
ifaces:value('uplink', 'Uplink')
ifaces:value('mesh', 'Mesh')
ifaces:value('client', 'Client')
ifaces:exclusive('uplink', 'client')
ifaces:exclusive('mesh', 'client')

ifaces.default = config.role

function ifaces:write(data)
uci:set_list("gluon", section_name, "role", data)
end
end)

-- Options for delete_vlan_interface

for section_name, iface in pairs(vlan_interface_sections) do
vlan_iface_to_delete:value(section_name, iface)
end
end

function f_vlan:write()
if action.data == 'create_vlan_interface' then
create_vlan_interface()
elseif action.data == 'delete_vlan_interface' then
delete_vlan_interface()
end

uci:commit('gluon')

render_interface_options()
end

function f_vlan:parse(http)
if not f_vlan:submitstate(http) then
render_interface_options()
end

return Form.parse(self, http)
end

return f
return f, f_vlan
12 changes: 12 additions & 0 deletions package/gluon-web-network/web2/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Uplink from './uplink.tsx'
import Interfaces from './interfaces.tsx'

window.RegisterModule({
path: '/network/uplink',
component: Uplink,
})

window.RegisterModule({
path: '/network/interfaces',
component: Interfaces,
})
11 changes: 11 additions & 0 deletions package/gluon-web-network/web2/sample/sample-dsa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"path": "/network/interfaces",
"display": "Network Interfaces with dsa",
"info": {
"switch_type": "dsa",
"interfaces": ["wan", "lan1", "lan2", "lan3", "lan4"]
},
"get": {

}
}
11 changes: 11 additions & 0 deletions package/gluon-web-network/web2/sample/sample-swconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"path": "/network/interfaces",
"display": "Network Interfaces with swconfig",
"info": {
"switch_type": "swconfig",
"interfaces": ["eth0", "eth1"]
},
"get": {

}
}
6 changes: 6 additions & 0 deletions package/gluon-web2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/node_modules
/.parcel-cache
/dist
.DS_Store
yarn-debug.log*
yarn-error.log*
14 changes: 14 additions & 0 deletions package/gluon-web2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=gluon-web2

PKG_INSTALL:=1

include ../gluon.mk

define Package/gluon-web2
TITLE:=Gluon Web rewritten with Preact and Parcel
DEPENDS:=+gluon-web
endef

$(eval $(call BuildPackageGluon,gluon-web2))
44 changes: 44 additions & 0 deletions package/gluon-web2/dump-network.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
local interfaces = {}
local uci = require('simple-uci').cursor()
local json = require 'jsonc'
local ethernet = require 'gluon.ethernet'

uci:foreach('gluon', 'interface', function(interface)
table.insert(interfaces, interface)
end)

local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
-- encoding
function enc(data)
return ((data:gsub('.', function(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data%3+1])
end

-- decoding
function dec(data)
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end

print(enc(json.stringify({
phy_interfaces = ethernet.interfaces(),
uci = interfaces
})))
35 changes: 35 additions & 0 deletions package/gluon-web2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "gluon-web2",
"version": "1.0.0",
"description": "Gluon Web rewritten with Preact and Parcel",
"author": "Gluon Contributors",
"license": "MIT",
"dependencies": {
"preact": "^10.15.1"
},
"scripts": {
"start": "parcel serve --target dev",
"build": "parcel build"
},
"targets": {
"default": {
"source": "src/index.tsx",
"distDir": "bundle"
},
"dev": {
"source": "src/index.html"
}
},
"eslintConfig": {
"extends": "ipfs"
},
"devDependencies": {
"@parcel/transformer-sass": "2.9.2",
"@types/jest": "^29.5.2",
"eslint-config-ipfs": "^4.0.3",
"jest": "^29.5.0",
"parcel": "^2.9.2",
"ts-jest": "^29.1.0",
"typescript": "^5.1.3"
}
}
8 changes: 8 additions & 0 deletions package/gluon-web2/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { render, h } from "preact";
import App from "./App";

it("renders without crashing", () => {
const div = document.createElement("div");
render(<App />, div);
render(null, div);
});
5 changes: 5 additions & 0 deletions package/gluon-web2/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { h } from "preact";

const App = () => <h1>Hello world</h1>;

export default App;
21 changes: 21 additions & 0 deletions package/gluon-web2/src/GluonWeb2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Fragment, h } from "preact";
import { useState } from 'preact/hooks';

import Components from "./components"

const GluonWeb2 = ({ id, component, data: initialData }: { id: string, component: string, data: any }) => {
let { data, setData } = useState<any>({ "phy_interfaces": ["wan", "lan4", "lan2", "lan3", "lan1"], "uci": [{ ".name": "iface_wan", ".type": "interface", "name": "\\/wan", "role": ["nothing"], ".anonymous": false, ".index": 1 }, { ".name": "iface_lan", ".type": "interface", "name": "\\/lan", "role": ["uplink"], ".anonymous": false, ".index": 2 }, { ".name": "iface_wan_vlan11", ".type": "interface", "name": "wan.11", "role": ["mesh"], ".anonymous": false, ".index": 5 }, { ".name": "iface_lan1_vlan255", ".type": "interface", "name": "lan1.255", "role": ["mesh"], ".anonymous": false, ".index": 6 }, { ".name": "iface_lan2_vlan255", ".type": "interface", "name": "lan2.255", "role": ["mesh"], ".anonymous": false, ".index": 7 }, { ".name": "iface_lan3_vlan255", ".type": "interface", "name": "lan3.255", "role": ["mesh"], ".anonymous": false, ".index": 8 }, { ".name": "iface_lan4_vlan255", ".type": "interface", "name": "lan4.255", "role": ["mesh"], ".anonymous": false, ".index": 9 }, { ".name": "iface_lan1_vlan3", ".type": "interface", "name": "lan1.3", "role": ["client"], ".anonymous": false, ".index": 10 }, { ".name": "iface_lan2_vlan3", ".type": "interface", "name": "lan2.3", "role": ["client"], ".anonymous": false, ".index": 11 }, { ".name": "iface_lan3_vlan3", ".type": "interface", "name": "lan3.3", "role": ["client"], ".anonymous": false, ".index": 12 }, { ".name": "iface_lan4_vlan3", ".type": "interface", "name": "lan4.3", "role": ["client"], ".anonymous": false, ".index": 13 }] })

data = { "phy_interfaces": ["wan", "lan4", "lan2", "lan3", "lan1"], "uci": [{ ".name": "iface_wan", ".type": "interface", "name": "\\/wan", "role": ["nothing"], ".anonymous": false, ".index": 1 }, { ".name": "iface_lan", ".type": "interface", "name": "\\/lan", "role": ["uplink"], ".anonymous": false, ".index": 2 }, { ".name": "iface_wan_vlan11", ".type": "interface", "name": "wan.11", "role": ["mesh"], ".anonymous": false, ".index": 5 }, { ".name": "iface_lan1_vlan255", ".type": "interface", "name": "lan1.255", "role": ["mesh"], ".anonymous": false, ".index": 6 }, { ".name": "iface_lan2_vlan255", ".type": "interface", "name": "lan2.255", "role": ["mesh"], ".anonymous": false, ".index": 7 }, { ".name": "iface_lan3_vlan255", ".type": "interface", "name": "lan3.255", "role": ["mesh"], ".anonymous": false, ".index": 8 }, { ".name": "iface_lan4_vlan255", ".type": "interface", "name": "lan4.255", "role": ["mesh"], ".anonymous": false, ".index": 9 }, { ".name": "iface_lan1_vlan3", ".type": "interface", "name": "lan1.3", "role": ["client"], ".anonymous": false, ".index": 10 }, { ".name": "iface_lan2_vlan3", ".type": "interface", "name": "lan2.3", "role": ["client"], ".anonymous": false, ".index": 11 }, { ".name": "iface_lan3_vlan3", ".type": "interface", "name": "lan3.3", "role": ["client"], ".anonymous": false, ".index": 12 }, { ".name": "iface_lan4_vlan3", ".type": "interface", "name": "lan4.3", "role": ["client"], ".anonymous": false, ".index": 13 }] }

const Comp = Components[component]

return (
<Fragment>
<input type="text" style="display: none" name={id} value={JSON.stringify(data)} />,
<Comp data={data} setData={setData} />
</Fragment>
)
}

export default GluonWeb2;
4 changes: 4 additions & 0 deletions package/gluon-web2/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import VlanUI from "./vlan-ui"
export default {
"vlan-ui": VlanUI
}
Loading