From: danrl Date: Fri, 17 Feb 2017 10:22:01 +0000 (+0100) Subject: luci-app-mwan3: initial commit X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=a25c71f55697d13c0a12da104bde7fee57d00f2c;p=project%2Fluci.git luci-app-mwan3: initial commit Moved over here from the packages repository. Signed-off-by: Dan Luedtke --- diff --git a/applications/luci-app-mwan3/Makefile b/applications/luci-app-mwan3/Makefile new file mode 100644 index 0000000000..758ec90a04 --- /dev/null +++ b/applications/luci-app-mwan3/Makefile @@ -0,0 +1,18 @@ +# +# Copyright (C) 2017 Dan Luedtke +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI support for the MWAN3 multiwan hotplug script +LUCI_DEPENDS:=+mwan3 +luci-mod-admin-full +luci-app-firewall +luci-lib-nixio +LUCI_PKGARCH:=all +PKG_LICENSE:=GPLv2 + +PKG_MAINTAINER:=Aedan Renner + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/applications/luci-app-mwan3/luasrc/controller/mwan3.lua b/applications/luci-app-mwan3/luasrc/controller/mwan3.lua new file mode 100644 index 0000000000..3d5a23dd03 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/controller/mwan3.lua @@ -0,0 +1,331 @@ +module("luci.controller.mwan3", package.seeall) + +sys = require "luci.sys" +ut = require "luci.util" + +ip = "/usr/bin/ip -4 " + +function index() + if not nixio.fs.access("/etc/config/mwan3") then + return + end + + entry({"admin", "network", "mwan"}, + alias("admin", "network", "mwan", "overview"), + _("Load Balancing"), 600) + + entry({"admin", "network", "mwan", "overview"}, + alias("admin", "network", "mwan", "overview", "overview_interface"), + _("Overview"), 10) + entry({"admin", "network", "mwan", "overview", "overview_interface"}, + template("mwan/overview_interface")) + entry({"admin", "network", "mwan", "overview", "interface_status"}, + call("interfaceStatus")) + entry({"admin", "network", "mwan", "overview", "overview_detailed"}, + template("mwan/overview_detailed")) + entry({"admin", "network", "mwan", "overview", "detailed_status"}, + call("detailedStatus")) + + entry({"admin", "network", "mwan", "configuration"}, + alias("admin", "network", "mwan", "configuration", "interface"), + _("Configuration"), 20) + entry({"admin", "network", "mwan", "configuration", "interface"}, + arcombine(cbi("mwan/interface"), cbi("mwan/interfaceconfig")), + _("Interfaces"), 10).leaf = true + entry({"admin", "network", "mwan", "configuration", "member"}, + arcombine(cbi("mwan/member"), cbi("mwan/memberconfig")), + _("Members"), 20).leaf = true + entry({"admin", "network", "mwan", "configuration", "policy"}, + arcombine(cbi("mwan/policy"), cbi("mwan/policyconfig")), + _("Policies"), 30).leaf = true + entry({"admin", "network", "mwan", "configuration", "rule"}, + arcombine(cbi("mwan/rule"), cbi("mwan/ruleconfig")), + _("Rules"), 40).leaf = true + + entry({"admin", "network", "mwan", "advanced"}, + alias("admin", "network", "mwan", "advanced", "hotplugscript"), + _("Advanced"), 100) + entry({"admin", "network", "mwan", "advanced", "hotplugscript"}, + form("mwan/advanced_hotplugscript")) + entry({"admin", "network", "mwan", "advanced", "mwanconfig"}, + form("mwan/advanced_mwanconfig")) + entry({"admin", "network", "mwan", "advanced", "networkconfig"}, + form("mwan/advanced_networkconfig")) + entry({"admin", "network", "mwan", "advanced", "wirelessconfig"}, + form("mwan/advanced_wirelessconfig")) + entry({"admin", "network", "mwan", "advanced", "diagnostics"}, + template("mwan/advanced_diagnostics")) + entry({"admin", "network", "mwan", "advanced", "diagnostics_display"}, + call("diagnosticsData"), nil).leaf = true + entry({"admin", "network", "mwan", "advanced", "troubleshooting"}, + template("mwan/advanced_troubleshooting")) + entry({"admin", "network", "mwan", "advanced", "troubleshooting_display"}, + call("troubleshootingData")) +end + +function getInterfaceStatus(ruleNumber, interfaceName) + if ut.trim(sys.exec("uci -p /var/state get mwan3." .. interfaceName .. ".enabled")) == "1" then + if ut.trim(sys.exec(ip .. "route list table " .. ruleNumber)) ~= "" then + if ut.trim(sys.exec("uci -p /var/state get mwan3." .. interfaceName .. ".track_ip")) ~= "" then + return "online" + else + return "notMonitored" + end + else + return "offline" + end + else + return "notEnabled" + end +end + +function getInterfaceName() + local ruleNumber, status = 0, "" + uci.cursor():foreach("mwan3", "interface", + function (section) + ruleNumber = ruleNumber+1 + status = status .. section[".name"] .. "[" .. getInterfaceStatus(ruleNumber, section[".name"]) .. "]" + end + ) + return status +end + +function interfaceStatus() + local ntm = require "luci.model.network".init() + + local mArray = {} + + -- overview status + local statusString = getInterfaceName() + if statusString ~= "" then + mArray.wans = {} + wansid = {} + + for wanName, interfaceState in string.gfind(statusString, "([^%[]+)%[([^%]]+)%]") do + local wanInterfaceName = ut.trim(sys.exec("uci -p /var/state get network." .. wanName .. ".ifname")) + if wanInterfaceName == "" then + wanInterfaceName = "X" + end + local wanDeviceLink = ntm:get_interface(wanInterfaceName) + wanDeviceLink = wanDeviceLink and wanDeviceLink:get_network() + wanDeviceLink = wanDeviceLink and wanDeviceLink:adminlink() or "#" + wansid[wanName] = #mArray.wans + 1 + mArray.wans[wansid[wanName]] = { name = wanName, link = wanDeviceLink, ifname = wanInterfaceName, status = interfaceState } + end + end + + -- overview status log + local mwanLog = ut.trim(sys.exec("logread | grep mwan3 | tail -n 50 | sed 'x;1!H;$!d;x'")) + if mwanLog ~= "" then + mArray.mwanlog = { mwanLog } + end + + luci.http.prepare_content("application/json") + luci.http.write_json(mArray) +end + +function detailedStatus() + local mArray = {} + + -- detailed mwan status + local detailStatusInfo = ut.trim(sys.exec("/usr/sbin/mwan3 status")) + if detailStatusInfo ~= "" then + mArray.mwandetail = { detailStatusInfo } + end + + luci.http.prepare_content("application/json") + luci.http.write_json(mArray) +end + +function diagnosticsData(interface, tool, task) + function getInterfaceNumber() + local number = 0 + uci.cursor():foreach("mwan3", "interface", + function (section) + number = number+1 + if section[".name"] == interface then + interfaceNumber = number + end + end + ) + end + + local mArray = {} + + local results = "" + if tool == "service" then + os.execute("/usr/sbin/mwan3 " .. task) + if task == "restart" then + results = "MWAN3 restarted" + elseif task == "stop" then + results = "MWAN3 stopped" + else + results = "MWAN3 started" + end + else + local interfaceDevice = ut.trim(sys.exec("uci -p /var/state get network." .. interface .. ".ifname")) + if interfaceDevice ~= "" then + if tool == "ping" then + local gateway = ut.trim(sys.exec("route -n | awk '{if ($8 == \"" .. interfaceDevice .. "\" && $1 == \"0.0.0.0\" && $3 == \"0.0.0.0\") print $2}'")) + if gateway ~= "" then + if task == "gateway" then + local pingCommand = "ping -c 3 -W 2 -I " .. interfaceDevice .. " " .. gateway + results = pingCommand .. "\n\n" .. sys.exec(pingCommand) + else + local tracked = ut.trim(sys.exec("uci -p /var/state get mwan3." .. interface .. ".track_ip")) + if tracked ~= "" then + for z in tracked:gmatch("[^ ]+") do + local pingCommand = "ping -c 3 -W 2 -I " .. interfaceDevice .. " " .. z + results = results .. pingCommand .. "\n\n" .. sys.exec(pingCommand) .. "\n\n" + end + else + results = "No tracking IP addresses configured on " .. interface + end + end + else + results = "No default gateway for " .. interface .. " found. Default route does not exist or is configured incorrectly" + end + elseif tool == "rulechk" then + getInterfaceNumber() + local rule1 = sys.exec(ip .. "rule | grep $(echo $((" .. interfaceNumber .. " + 1000)))") + local rule2 = sys.exec(ip .. "rule | grep $(echo $((" .. interfaceNumber .. " + 2000)))") + if rule1 ~= "" and rule2 ~= "" then + results = "All required interface IP rules found:\n\n" .. rule1 .. rule2 + elseif rule1 ~= "" or rule2 ~= "" then + results = "Missing 1 of the 2 required interface IP rules\n\n\nRules found:\n\n" .. rule1 .. rule2 + else + results = "Missing both of the required interface IP rules" + end + elseif tool == "routechk" then + getInterfaceNumber() + local routeTable = sys.exec(ip .. "route list table " .. interfaceNumber) + if routeTable ~= "" then + results = "Interface routing table " .. interfaceNumber .. " was found:\n\n" .. routeTable + else + results = "Missing required interface routing table " .. interfaceNumber + end + elseif tool == "hotplug" then + if task == "ifup" then + os.execute("/usr/sbin/mwan3 ifup " .. interface) + results = "Hotplug ifup sent to interface " .. interface .. "..." + else + os.execute("/usr/sbin/mwan3 ifdown " .. interface) + results = "Hotplug ifdown sent to interface " .. interface .. "..." + end + end + else + results = "Unable to perform diagnostic tests on " .. interface .. ". There is no physical or virtual device associated with this interface" + end + end + if results ~= "" then + results = ut.trim(results) + mArray.diagnostics = { results } + end + + luci.http.prepare_content("application/json") + luci.http.write_json(mArray) +end + +function troubleshootingData() + local ver = require "luci.version" + + local mArray = {} + + -- software versions + local wrtRelease = ut.trim(ver.distversion) + if wrtRelease ~= "" then + wrtRelease = "OpenWrt - " .. wrtRelease + else + wrtRelease = "OpenWrt - unknown" + end + local luciRelease = ut.trim(ver.luciversion) + if luciRelease ~= "" then + luciRelease = "\nLuCI - " .. luciRelease + else + luciRelease = "\nLuCI - unknown" + end + local mwanVersion = ut.trim(sys.exec("opkg info mwan3 | grep Version | awk '{print $2}'")) + if mwanVersion ~= "" then + mwanVersion = "\n\nmwan3 - " .. mwanVersion + else + mwanVersion = "\n\nmwan3 - unknown" + end + local mwanLuciVersion = ut.trim(sys.exec("opkg info luci-app-mwan3 | grep Version | awk '{print $2}'")) + if mwanLuciVersion ~= "" then + mwanLuciVersion = "\nmwan3-luci - " .. mwanLuciVersion + else + mwanLuciVersion = "\nmwan3-luci - unknown" + end + mArray.versions = { wrtRelease .. luciRelease .. mwanVersion .. mwanLuciVersion } + + -- mwan config + local mwanConfig = ut.trim(sys.exec("cat /etc/config/mwan3")) + if mwanConfig == "" then + mwanConfig = "No data found" + end + mArray.mwanconfig = { mwanConfig } + + -- network config + local networkConfig = ut.trim(sys.exec("cat /etc/config/network | sed -e 's/.*username.*/ USERNAME HIDDEN/' -e 's/.*password.*/ PASSWORD HIDDEN/'")) + if networkConfig == "" then + networkConfig = "No data found" + end + mArray.netconfig = { networkConfig } + + -- wireless config + local wirelessConfig = ut.trim(sys.exec("cat /etc/config/wireless | sed -e 's/.*username.*/ USERNAME HIDDEN/' -e 's/.*password.*/ PASSWORD HIDDEN/' -e 's/.*key.*/ KEY HIDDEN/'")) + if wirelessConfig == "" then + wirelessConfig = "No data found" + end + mArray.wificonfig = { wirelessConfig } + + -- ifconfig + local ifconfig = ut.trim(sys.exec("ifconfig")) + if ifconfig == "" then + ifconfig = "No data found" + end + mArray.ifconfig = { ifconfig } + + -- route -n + local routeShow = ut.trim(sys.exec("route -n")) + if routeShow == "" then + routeShow = "No data found" + end + mArray.routeshow = { routeShow } + + -- ip rule show + local ipRuleShow = ut.trim(sys.exec(ip .. "rule show")) + if ipRuleShow == "" then + ipRuleShow = "No data found" + end + mArray.iprule = { ipRuleShow } + + -- ip route list table 1-250 + local routeList, routeString = ut.trim(sys.exec(ip .. "rule | sed 's/://g' | awk '$1>=2001 && $1<=2250' | awk '{print $NF}'")), "" + if routeList ~= "" then + for line in routeList:gmatch("[^\r\n]+") do + routeString = routeString .. line .. "\n" .. sys.exec(ip .. "route list table " .. line) + end + routeString = ut.trim(routeString) + else + routeString = "No data found" + end + mArray.routelist = { routeString } + + -- default firewall output policy + local firewallOut = ut.trim(sys.exec("uci -p /var/state get firewall.@defaults[0].output")) + if firewallOut == "" then + firewallOut = "No data found" + end + mArray.firewallout = { firewallOut } + + -- iptables + local iptables = ut.trim(sys.exec("iptables -L -t mangle -v -n")) + if iptables == "" then + iptables = "No data found" + end + mArray.iptables = { iptables } + + luci.http.prepare_content("application/json") + luci.http.write_json(mArray) +end diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_hotplugscript.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_hotplugscript.lua new file mode 100644 index 0000000000..0e7b8b11d0 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_hotplugscript.lua @@ -0,0 +1,55 @@ +-- ------ hotplug script configuration ------ -- + +fs = require "nixio.fs" +sys = require "luci.sys" +ut = require "luci.util" + +script = "/etc/hotplug.d/iface/16-mwancustom" +scriptBackup = "/etc/hotplug.d/iface/16-mwancustombak" + +if luci.http.formvalue("cbid.luci.1._restorebak") then -- restore button has been clicked + luci.http.redirect(luci.dispatcher.build_url("admin/network/mwan/advanced/hotplugscript") .. "?restore=yes") +elseif luci.http.formvalue("restore") == "yes" then -- restore script from backup + os.execute("cp -f " .. scriptBackup .. " " .. script) +end + + +m5 = SimpleForm("luci", nil) + m5:append(Template("mwan/advanced_hotplugscript")) -- highlight current tab + +f = m5:section(SimpleSection, nil, + translate("This section allows you to modify the contents of /etc/hotplug.d/iface/16-mwancustom
" .. + "This is useful for running system commands and/or scripts based on interface ifup or ifdown hotplug events

" .. + "Notes:
" .. + "The first line of the script must be "#!/bin/sh" without quotes
" .. + "Lines beginning with # are comments and are not executed

" .. + "Available variables:
" .. + "$ACTION is the hotplug event (ifup, ifdown)
" .. + "$INTERFACE is the interface name (wan1, wan2, etc.)
" .. + "$DEVICE is the device name attached to the interface (eth0.1, eth1, etc.)")) + + +restore = f:option(Button, "_restorebak", translate("Restore default hotplug script")) + restore.inputtitle = translate("Restore...") + restore.inputstyle = "apply" + +t = f:option(TextValue, "lines") + t.rmempty = true + t.rows = 20 + + function t.cfgvalue() + local hps = fs.readfile(script) + if not hps or hps == "" then -- if script does not exist or is blank restore from backup + sys.call("cp -f " .. scriptBackup .. " " .. script) + return fs.readfile(script) + else + return hps + end + end + + function t.write(self, section, data) -- format and write new data to script + return fs.writefile(script, ut.trim(data:gsub("\r\n", "\n")) .. "\n") + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_mwanconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_mwanconfig.lua new file mode 100644 index 0000000000..e0a99e8366 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_mwanconfig.lua @@ -0,0 +1,32 @@ +-- ------ mwan configuration ------ -- + +ut = require "luci.util" + +mwanConfig = "/etc/config/mwan3" + + +m5 = SimpleForm("luci", nil) + m5:append(Template("mwan/advanced_mwanconfig")) -- highlight current tab + + +f = m5:section(SimpleSection, nil, + translate("This section allows you to modify the contents of /etc/config/mwan3")) + +t = f:option(TextValue, "lines") + t.rmempty = true + t.rows = 20 + + function t.cfgvalue() + return nixio.fs.readfile(mwanConfig) or "" + end + + function t.write(self, section, data) -- format and write new data to script + return nixio.fs.writefile(mwanConfig, "\n" .. ut.trim(data:gsub("\r\n", "\n")) .. "\n") + end + + function f.handle(self, state, data) + return true + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_networkconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_networkconfig.lua new file mode 100644 index 0000000000..b93d89751b --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_networkconfig.lua @@ -0,0 +1,32 @@ +-- ------ network configuration ------ -- + +ut = require "luci.util" + +networkConfig = "/etc/config/network" + + +m5 = SimpleForm("networkconf", nil) + m5:append(Template("mwan/advanced_networkconfig")) -- highlight current tab + + +f = m5:section(SimpleSection, nil, + translate("This section allows you to modify the contents of /etc/config/network")) + +t = f:option(TextValue, "lines") + t.rmempty = true + t.rows = 20 + + function t.cfgvalue() + return nixio.fs.readfile(networkConfig) or "" + end + + function t.write(self, section, data) -- format and write new data to script + return nixio.fs.writefile(networkConfig, "\n" .. ut.trim(data:gsub("\r\n", "\n")) .. "\n") + end + + function f.handle(self, state, data) + return true + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_wirelessconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_wirelessconfig.lua new file mode 100644 index 0000000000..95e9f7c7e5 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/advanced_wirelessconfig.lua @@ -0,0 +1,32 @@ +-- ------ wireless configuration ------ -- + +ut = require "luci.util" + +wirelessConfig = "/etc/config/wireless" + + +m5 = SimpleForm("wirelessconf", nil) + m5:append(Template("mwan/advanced_wirelessconfig")) -- highlight current tab + + +f = m5:section(SimpleSection, nil, + translate("This section allows you to modify the contents of /etc/config/wireless")) + +t = f:option(TextValue, "lines") + t.rmempty = true + t.rows = 20 + + function t.cfgvalue() + return nixio.fs.readfile(wirelessConfig) or "" + end + + function t.write(self, section, data) -- format and write new data to script + return nixio.fs.writefile(wirelessConfig, "\n" .. ut.trim(data:gsub("\r\n", "\n")) .. "\n") + end + + function f.handle(self, state, data) + return true + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua new file mode 100644 index 0000000000..a8e68a01be --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua @@ -0,0 +1,266 @@ +-- ------ extra functions ------ -- + +function interfaceCheck() -- find issues with too many interfaces, reliability and metric + uci.cursor():foreach("mwan3", "interface", + function (section) + local interfaceName = section[".name"] + interfaceNumber = interfaceNumber+1 -- count number of mwan interfaces configured + -- create list of metrics for none and duplicate checking + local metricValue = ut.trim(sys.exec("uci -p /var/state get network." .. interfaceName .. ".metric")) + if metricValue == "" then + errorFound = 1 + errorNoMetricList = errorNoMetricList .. interfaceName .. " " + else + metricList = metricList .. interfaceName .. " " .. metricValue .. "\n" + end + -- check if any interfaces have a higher reliability requirement than tracking IPs configured + local trackingNumber = tonumber(ut.trim(sys.exec("echo $(uci -p /var/state get mwan3." .. interfaceName .. ".track_ip) | wc -w"))) + if trackingNumber > 0 then + local reliabilityNumber = tonumber(ut.trim(sys.exec("uci -p /var/state get mwan3." .. interfaceName .. ".reliability"))) + if reliabilityNumber and reliabilityNumber > trackingNumber then + errorFound = 1 + errorReliabilityList = errorReliabilityList .. interfaceName .. " " + end + end + -- check if any interfaces are not properly configured in /etc/config/network or have no default route in main routing table + if ut.trim(sys.exec("uci -p /var/state get network." .. interfaceName)) == "interface" then + local interfaceDevice = ut.trim(sys.exec("uci -p /var/state get network." .. interfaceName .. ".ifname")) + if interfaceDevice == "uci: Entry not found" or interfaceDevice == "" then + errorFound = 1 + errorNetConfigList = errorNetConfigList .. interfaceName .. " " + errorRouteList = errorRouteList .. interfaceName .. " " + else + local routeCheck = ut.trim(sys.exec("route -n | awk '{if ($8 == \"" .. interfaceDevice .. "\" && $1 == \"0.0.0.0\" && $3 == \"0.0.0.0\") print $1}'")) + if routeCheck == "" then + errorFound = 1 + errorRouteList = errorRouteList .. interfaceName .. " " + end + end + else + errorFound = 1 + errorNetConfigList = errorNetConfigList .. interfaceName .. " " + errorRouteList = errorRouteList .. interfaceName .. " " + end + end + ) + -- check if any interfaces have duplicate metrics + local metricDuplicateNumbers = sys.exec("echo '" .. metricList .. "' | awk '{print $2}' | uniq -d") + if metricDuplicateNumbers ~= "" then + errorFound = 1 + local metricDuplicates = "" + for line in metricDuplicateNumbers:gmatch("[^\r\n]+") do + metricDuplicates = sys.exec("echo '" .. metricList .. "' | grep '" .. line .. "' | awk '{print $1}'") + errorDuplicateMetricList = errorDuplicateMetricList .. metricDuplicates + end + errorDuplicateMetricList = sys.exec("echo '" .. errorDuplicateMetricList .. "' | tr '\n' ' '") + end +end + +function interfaceWarnings() -- display status and warning messages at the top of the page + local warnings = "" + if interfaceNumber <= 250 then + warnings = "There are currently " .. interfaceNumber .. " of 250 supported interfaces configured" + else + warnings = "WARNING: " .. interfaceNumber .. " interfaces are configured exceeding the maximum of 250!" + end + if errorReliabilityList ~= " " then + warnings = warnings .. "

WARNING: some interfaces have a higher reliability requirement than there are tracking IP addresses!" + end + if errorRouteList ~= " " then + warnings = warnings .. "

WARNING: some interfaces have no default route in the main routing table!" + end + if errorNetConfigList ~= " " then + warnings = warnings .. "

WARNING: some interfaces are configured incorrectly or not at all in /etc/config/network!" + end + if errorNoMetricList ~= " " then + warnings = warnings .. "

WARNING: some interfaces have no metric configured in /etc/config/network!" + end + if errorDuplicateMetricList ~= " " then + warnings = warnings .. "

WARNING: some interfaces have duplicate metrics configured in /etc/config/network!" + end + return warnings +end + +-- ------ interface configuration ------ -- + +dsp = require "luci.dispatcher" +sys = require "luci.sys" +ut = require "luci.util" + +interfaceNumber = 0 +metricList = "" +errorFound = 0 +errorDuplicateMetricList = " " +errorNetConfigList = " " +errorNoMetricList = " " +errorReliabilityList = " " +errorRouteList = " " +interfaceCheck() + + +m5 = Map("mwan3", translate("MWAN Interface Configuration"), + translate(interfaceWarnings())) + m5:append(Template("mwan/config_css")) + + +mwan_interface = m5:section(TypedSection, "interface", translate("Interfaces"), + translate("MWAN supports up to 250 physical and/or logical interfaces
" .. + "MWAN requires that all interfaces have a unique metric configured in /etc/config/network
" .. + "Names must match the interface name found in /etc/config/network (see advanced tab)
" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces
" .. + "Interfaces may not share the same name as configured members, policies or rules")) + mwan_interface.addremove = true + mwan_interface.dynamic = false + mwan_interface.sectionhead = "Interface" + mwan_interface.sortable = true + mwan_interface.template = "cbi/tblsection" + mwan_interface.extedit = dsp.build_url("admin", "network", "mwan", "configuration", "interface", "%s") + function mwan_interface.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(dsp.build_url("admin", "network", "mwan", "configuration", "interface", section)) + end + + +enabled = mwan_interface:option(DummyValue, "enabled", translate("Enabled")) + enabled.rawhtml = true + function enabled.cfgvalue(self, s) + if self.map:get(s, "enabled") == "1" then + return "Yes" + else + return "No" + end + end + +track_ip = mwan_interface:option(DummyValue, "track_ip", translate("Tracking IP")) + track_ip.rawhtml = true + function track_ip.cfgvalue(self, s) + tracked = self.map:get(s, "track_ip") + if tracked then + local ipList = "" + for k,v in pairs(tracked) do + ipList = ipList .. v .. "
" + end + return ipList + else + return "—" + end + end + +reliability = mwan_interface:option(DummyValue, "reliability", translate("Tracking reliability")) + reliability.rawhtml = true + function reliability.cfgvalue(self, s) + if tracked then + return self.map:get(s, "reliability") or "—" + else + return "—" + end + end + +count = mwan_interface:option(DummyValue, "count", translate("Ping count")) + count.rawhtml = true + function count.cfgvalue(self, s) + if tracked then + return self.map:get(s, "count") or "—" + else + return "—" + end + end + +timeout = mwan_interface:option(DummyValue, "timeout", translate("Ping timeout")) + timeout.rawhtml = true + function timeout.cfgvalue(self, s) + if tracked then + local timeoutValue = self.map:get(s, "timeout") + if timeoutValue then + return timeoutValue .. "s" + else + return "—" + end + else + return "—" + end + end + +interval = mwan_interface:option(DummyValue, "interval", translate("Ping interval")) + interval.rawhtml = true + function interval.cfgvalue(self, s) + if tracked then + local intervalValue = self.map:get(s, "interval") + if intervalValue then + return intervalValue .. "s" + else + return "—" + end + else + return "—" + end + end + +down = mwan_interface:option(DummyValue, "down", translate("Interface down")) + down.rawhtml = true + function down.cfgvalue(self, s) + if tracked then + return self.map:get(s, "down") or "—" + else + return "—" + end + end + +up = mwan_interface:option(DummyValue, "up", translate("Interface up")) + up.rawhtml = true + function up.cfgvalue(self, s) + if tracked then + return self.map:get(s, "up") or "—" + else + return "—" + end + end + +metric = mwan_interface:option(DummyValue, "metric", translate("Metric")) + metric.rawhtml = true + function metric.cfgvalue(self, s) + local metricValue = sys.exec("uci -p /var/state get network." .. s .. ".metric") + if metricValue ~= "" then + return metricValue + else + return "—" + end + end + +errors = mwan_interface:option(DummyValue, "errors", translate("Errors")) + errors.rawhtml = true + function errors.cfgvalue(self, s) + if errorFound == 1 then + local mouseOver, lineBreak = "", "" + if string.find(errorReliabilityList, " " .. s .. " ") then + mouseOver = "Higher reliability requirement than there are tracking IP addresses" + lineBreak = " " + end + if string.find(errorRouteList, " " .. s .. " ") then + mouseOver = mouseOver .. lineBreak .. "No default route in the main routing table" + lineBreak = " " + end + if string.find(errorNetConfigList, " " .. s .. " ") then + mouseOver = mouseOver .. lineBreak .. "Configured incorrectly or not at all in /etc/config/network" + lineBreak = " " + end + if string.find(errorNoMetricList, " " .. s .. " ") then + mouseOver = mouseOver .. lineBreak .. "No metric configured in /etc/config/network" + lineBreak = " " + end + if string.find(errorDuplicateMetricList, " " .. s .. " ") then + mouseOver = mouseOver .. lineBreak .. "Duplicate metric configured in /etc/config/network" + end + if mouseOver == "" then + return "" + else + return "\"error\"" + end + else + return "" + end + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua new file mode 100644 index 0000000000..387ff01a8d --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua @@ -0,0 +1,190 @@ +-- ------ extra functions ------ -- + +function interfaceCheck() + metricValue = ut.trim(sys.exec("uci -p /var/state get network." .. arg[1] .. ".metric")) + if metricValue == "" then -- no metric + errorNoMetric = 1 + else -- if metric exists create list of interface metrics to compare against for duplicates + uci.cursor():foreach("mwan3", "interface", + function (section) + local metricValue = ut.trim(sys.exec("uci -p /var/state get network." .. section[".name"] .. ".metric")) + metricList = metricList .. section[".name"] .. " " .. metricValue .. "\n" + end + ) + -- compare metric against list + local metricDuplicateNumbers, metricDuplicates = sys.exec("echo '" .. metricList .. "' | awk '{print $2}' | uniq -d"), "" + for line in metricDuplicateNumbers:gmatch("[^\r\n]+") do + metricDuplicates = sys.exec("echo '" .. metricList .. "' | grep '" .. line .. "' | awk '{print $1}'") + errorDuplicateMetricList = errorDuplicateMetricList .. metricDuplicates + end + if sys.exec("echo '" .. errorDuplicateMetricList .. "' | grep -w " .. arg[1]) ~= "" then + errorDuplicateMetric = 1 + end + end + -- check if this interface has a higher reliability requirement than track IPs configured + local trackingNumber = tonumber(ut.trim(sys.exec("echo $(uci -p /var/state get mwan3." .. arg[1] .. ".track_ip) | wc -w"))) + if trackingNumber > 0 then + local reliabilityNumber = tonumber(ut.trim(sys.exec("uci -p /var/state get mwan3." .. arg[1] .. ".reliability"))) + if reliabilityNumber and reliabilityNumber > trackingNumber then + errorReliability = 1 + end + end + -- check if any interfaces are not properly configured in /etc/config/network or have no default route in main routing table + if ut.trim(sys.exec("uci -p /var/state get network." .. arg[1])) == "interface" then + local interfaceDevice = ut.trim(sys.exec("uci -p /var/state get network." .. arg[1] .. ".ifname")) + if interfaceDevice == "uci: Entry not found" or interfaceDevice == "" then + errorNetConfig = 1 + errorRoute = 1 + else + local routeCheck = ut.trim(sys.exec("route -n | awk '{if ($8 == \"" .. interfaceDevice .. "\" && $1 == \"0.0.0.0\" && $3 == \"0.0.0.0\") print $1}'")) + if routeCheck == "" then + errorRoute = 1 + end + end + else + errorNetConfig = 1 + errorRoute = 1 + end +end + +function interfaceWarnings() -- display warning messages at the top of the page + local warns, lineBreak = "", "" + if errorReliability == 1 then + warns = "WARNING: this interface has a higher reliability requirement than there are tracking IP addresses!" + lineBreak = "

" + end + if errorRoute == 1 then + warns = warns .. lineBreak .. "WARNING: this interface has no default route in the main routing table!" + lineBreak = "

" + end + if errorNetConfig == 1 then + warns = warns .. lineBreak .. "WARNING: this interface is configured incorrectly or not at all in /etc/config/network!" + lineBreak = "

" + end + if errorNoMetric == 1 then + warns = warns .. lineBreak .. "WARNING: this interface has no metric configured in /etc/config/network!" + elseif errorDuplicateMetric == 1 then + warns = warns .. lineBreak .. "WARNING: this and other interfaces have duplicate metrics configured in /etc/config/network!" + end + return warns +end + +-- ------ interface configuration ------ -- + +dsp = require "luci.dispatcher" +sys = require "luci.sys" +ut = require "luci.util" +arg[1] = arg[1] or "" + +metricValue = "" +metricList = "" +errorDuplicateMetricList = "" +errorNoMetric = 0 +errorDuplicateMetric = 0 +errorRoute = 0 +errorNetConfig = 0 +errorReliability = 0 +interfaceCheck() + + +m5 = Map("mwan3", translate("MWAN Interface Configuration - " .. arg[1]), + translate(interfaceWarnings())) + m5.redirect = dsp.build_url("admin", "network", "mwan", "configuration", "interface") + + +mwan_interface = m5:section(NamedSection, arg[1], "interface", "") + mwan_interface.addremove = false + mwan_interface.dynamic = false + + +enabled = mwan_interface:option(ListValue, "enabled", translate("Enabled")) + enabled.default = "1" + enabled:value("1", translate("Yes")) + enabled:value("0", translate("No")) + +track_ip = mwan_interface:option(DynamicList, "track_ip", translate("Tracking IP"), + translate("This IP address will be pinged to dermine if the link is up or down. Leave blank to assume interface is always online")) + track_ip.datatype = "ipaddr" + +reliability = mwan_interface:option(Value, "reliability", translate("Tracking reliability"), + translate("Acceptable values: 1-100. This many Tracking IP addresses must respond for the link to be deemed up")) + reliability.datatype = "range(1, 100)" + reliability.default = "1" + +count = mwan_interface:option(ListValue, "count", translate("Ping count")) + count.default = "1" + count:value("1") + count:value("2") + count:value("3") + count:value("4") + count:value("5") + +timeout = mwan_interface:option(ListValue, "timeout", translate("Ping timeout")) + timeout.default = "2" + timeout:value("1", translate("1 second")) + timeout:value("2", translate("2 seconds")) + timeout:value("3", translate("3 seconds")) + timeout:value("4", translate("4 seconds")) + timeout:value("5", translate("5 seconds")) + timeout:value("6", translate("6 seconds")) + timeout:value("7", translate("7 seconds")) + timeout:value("8", translate("8 seconds")) + timeout:value("9", translate("9 seconds")) + timeout:value("10", translate("10 seconds")) + +interval = mwan_interface:option(ListValue, "interval", translate("Ping interval")) + interval.default = "5" + interval:value("1", translate("1 second")) + interval:value("3", translate("3 seconds")) + interval:value("5", translate("5 seconds")) + interval:value("10", translate("10 seconds")) + interval:value("20", translate("20 seconds")) + interval:value("30", translate("30 seconds")) + interval:value("60", translate("1 minute")) + interval:value("300", translate("5 minutes")) + interval:value("600", translate("10 minutes")) + interval:value("900", translate("15 minutes")) + interval:value("1800", translate("30 minutes")) + interval:value("3600", translate("1 hour")) + +down = mwan_interface:option(ListValue, "down", translate("Interface down"), + translate("Interface will be deemed down after this many failed ping tests")) + down.default = "3" + down:value("1") + down:value("2") + down:value("3") + down:value("4") + down:value("5") + down:value("6") + down:value("7") + down:value("8") + down:value("9") + down:value("10") + +up = mwan_interface:option(ListValue, "up", translate("Interface up"), + translate("Downed interface will be deemed up after this many successful ping tests")) + up.default = "3" + up:value("1") + up:value("2") + up:value("3") + up:value("4") + up:value("5") + up:value("6") + up:value("7") + up:value("8") + up:value("9") + up:value("10") + +metric = mwan_interface:option(DummyValue, "metric", translate("Metric"), + translate("This displays the metric assigned to this interface in /etc/config/network")) + metric.rawhtml = true + function metric.cfgvalue(self, s) + if errorNoMetric == 0 then + return metricValue + else + return "—" + end + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua new file mode 100644 index 0000000000..3bccbd942f --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua @@ -0,0 +1,46 @@ +-- ------ member configuration ------ -- + +ds = require "luci.dispatcher" + + +m5 = Map("mwan3", translate("MWAN Member Configuration")) + m5:append(Template("mwan/config_css")) + + +mwan_member = m5:section(TypedSection, "member", translate("Members"), + translate("Members are profiles attaching a metric and weight to an MWAN interface
" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces
" .. + "Members may not share the same name as configured interfaces, policies or rules")) + mwan_member.addremove = true + mwan_member.dynamic = false + mwan_member.sectionhead = "Member" + mwan_member.sortable = true + mwan_member.template = "cbi/tblsection" + mwan_member.extedit = ds.build_url("admin", "network", "mwan", "configuration", "member", "%s") + function mwan_member.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(ds.build_url("admin", "network", "mwan", "configuration", "member", section)) + end + + +interface = mwan_member:option(DummyValue, "interface", translate("Interface")) + interface.rawhtml = true + function interface.cfgvalue(self, s) + return self.map:get(s, "interface") or "—" + end + +metric = mwan_member:option(DummyValue, "metric", translate("Metric")) + metric.rawhtml = true + function metric.cfgvalue(self, s) + return self.map:get(s, "metric") or "1" + end + +weight = mwan_member:option(DummyValue, "weight", translate("Weight")) + weight.rawhtml = true + function weight.cfgvalue(self, s) + return self.map:get(s, "weight") or "1" + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua new file mode 100644 index 0000000000..181d22e06f --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua @@ -0,0 +1,47 @@ +-- ------ extra functions ------ -- + +function cbi_add_interface(field) + uci.cursor():foreach("mwan3", "interface", + function (section) + field:value(section[".name"]) + end + ) +end + +-- ------ member configuration ------ -- + +dsp = require "luci.dispatcher" +arg[1] = arg[1] or "" + + +m5 = Map("mwan3", translate("MWAN Member Configuration - ") .. arg[1]) + m5.redirect = dsp.build_url("admin", "network", "mwan", "configuration", "member") + + +mwan_member = m5:section(NamedSection, arg[1], "member", "") + mwan_member.addremove = false + mwan_member.dynamic = false + + +interface = mwan_member:option(Value, "interface", translate("Interface")) + cbi_add_interface(interface) + +metric = mwan_member:option(Value, "metric", translate("Metric"), + translate("Acceptable values: 1-1000. Defaults to 1 if not set")) + metric.datatype = "range(1, 1000)" + +weight = mwan_member:option(Value, "weight", translate("Weight"), + translate("Acceptable values: 1-1000. Defaults to 1 if not set")) + weight.datatype = "range(1, 1000)" + + +-- ------ currently configured interfaces ------ -- + +mwan_interface = m5:section(TypedSection, "interface", translate("Currently Configured Interfaces")) + mwan_interface.addremove = false + mwan_interface.dynamic = false + mwan_interface.sortable = false + mwan_interface.template = "cbi/tblsection" + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua new file mode 100644 index 0000000000..e141d696a9 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua @@ -0,0 +1,95 @@ +-- ------ extra functions ------ -- + +function policyCheck() -- check to see if any policy names exceed the maximum of 15 characters + uci.cursor():foreach("mwan3", "policy", + function (section) + if string.len(section[".name"]) > 15 then + nameTooLong = 1 + err_name_list = err_name_list .. section[".name"] .. " " + end + end + ) +end + +function policyWarn() -- display status and warning messages at the top of the page + if nameTooLong == 1 then + return "WARNING: Some policies have names exceeding the maximum of 15 characters!" + else + return "" + end +end + +-- ------ policy configuration ------ -- + +ds = require "luci.dispatcher" +sys = require "luci.sys" + +nameTooLong = 0 +err_name_list = " " +policyCheck() + + +m5 = Map("mwan3", translate("MWAN Policy Configuration"), + translate(policyWarn())) + m5:append(Template("mwan/config_css")) + + +mwan_policy = m5:section(TypedSection, "policy", translate("Policies"), + translate("Policies are profiles grouping one or more members controlling how MWAN distributes traffic
" .. + "Member interfaces with lower metrics are used first. Interfaces with the same metric load-balance
" .. + "Load-balanced member interfaces distribute more traffic out those with higher weights
" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces. Names must be 15 characters or less
" .. + "Policies may not share the same name as configured interfaces, members or rules")) + mwan_policy.addremove = true + mwan_policy.dynamic = false + mwan_policy.sectionhead = "Policy" + mwan_policy.sortable = true + mwan_policy.template = "cbi/tblsection" + mwan_policy.extedit = ds.build_url("admin", "network", "mwan", "configuration", "policy", "%s") + function mwan_policy.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(ds.build_url("admin", "network", "mwan", "configuration", "policy", section)) + end + + +use_member = mwan_policy:option(DummyValue, "use_member", translate("Members assigned")) + use_member.rawhtml = true + function use_member.cfgvalue(self, s) + local memberConfig, memberList = self.map:get(s, "use_member"), "" + if memberConfig then + for k,v in pairs(memberConfig) do + memberList = memberList .. v .. "
" + end + return memberList + else + return "—" + end + + end + +last_resort = mwan_policy:option(DummyValue, "last_resort", translate("Last resort")) + last_resort.rawhtml = true + function last_resort.cfgvalue(self, s) + local action = self.map:get(s, "last_resort") + if action == "blackhole" then + return "blackhole (drop)" + elseif action == "default" then + return "default (use main routing table)" + else + return "unreachable (reject)" + end + end + +errors = mwan_policy:option(DummyValue, "errors", translate("Errors")) + errors.rawhtml = true + function errors.cfgvalue(self, s) + if not string.find(err_name_list, " " .. s .. " ") then + return "" + else + return "\"error\"" + end + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua new file mode 100644 index 0000000000..f48a104c67 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua @@ -0,0 +1,65 @@ +-- ------ extra functions ------ -- + +function policyCheck() -- check to see if this policy's name exceed the maximum of 15 characters + policyNameLength = string.len(arg[1]) + if policyNameLength > 15 then + nameTooLong = 1 + end +end + +function policyWarn() -- display status and warning messages at the top of the page + if nameTooLong == 1 then + return "WARNING: this policy's name is " .. policyNameLength .. " characters exceeding the maximum of 15!" + else + return "" + end +end + +function cbiAddMember(field) + uci.cursor():foreach("mwan3", "member", + function (section) + field:value(section[".name"]) + end + ) +end + +-- ------ policy configuration ------ -- + +dsp = require "luci.dispatcher" +arg[1] = arg[1] or "" + +nameTooLong = 0 +policyCheck() + + +m5 = Map("mwan3", translate("MWAN Policy Configuration - " .. arg[1]), + translate(policyWarn())) + m5.redirect = dsp.build_url("admin", "network", "mwan", "configuration", "policy") + + +mwan_policy = m5:section(NamedSection, arg[1], "policy", "") + mwan_policy.addremove = false + mwan_policy.dynamic = false + + +use_member = mwan_policy:option(DynamicList, "use_member", translate("Member used")) + cbiAddMember(use_member) + +last_resort = mwan_policy:option(ListValue, "last_resort", translate("Last resort"), + translate("When all policy members are offline use this behavior for matched traffic")) + last_resort.default = "unreachable" + last_resort:value("unreachable", translate("unreachable (reject)")) + last_resort:value("blackhole", translate("blackhole (drop)")) + last_resort:value("default", translate("default (use main routing table)")) + + +-- ------ currently configured members ------ -- + +mwan_member = m5:section(TypedSection, "member", translate("Currently Configured Members")) + mwan_member.addremove = false + mwan_member.dynamic = false + mwan_member.sortable = false + mwan_member.template = "cbi/tblsection" + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua new file mode 100644 index 0000000000..a22e01054c --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua @@ -0,0 +1,141 @@ +-- ------ extra functions ------ -- + +function ruleCheck() -- determine if rules needs a proper protocol configured + uci.cursor():foreach("mwan3", "rule", + function (section) + local sourcePort = ut.trim(sys.exec("uci -p /var/state get mwan3." .. section[".name"] .. ".src_port")) + local destPort = ut.trim(sys.exec("uci -p /var/state get mwan3." .. section[".name"] .. ".dest_port")) + if sourcePort ~= "" or destPort ~= "" then -- ports configured + local protocol = ut.trim(sys.exec("uci -p /var/state get mwan3." .. section[".name"] .. ".proto")) + if protocol == "" or protocol == "all" then -- no or improper protocol + error_protocol_list = error_protocol_list .. section[".name"] .. " " + end + end + end + ) +end + +function ruleWarn() -- display warning messages at the top of the page + if error_protocol_list ~= " " then + return "WARNING: some rules have a port configured with no or improper protocol specified! Please configure a specific protocol!" + else + return "" + end +end + +-- ------ rule configuration ------ -- + +dsp = require "luci.dispatcher" +sys = require "luci.sys" +ut = require "luci.util" + +error_protocol_list = " " +ruleCheck() + + +m5 = Map("mwan3", translate("MWAN Rule Configuration"), + translate(ruleWarn())) + m5:append(Template("mwan/config_css")) + + +mwan_rule = m5:section(TypedSection, "rule", translate("Traffic Rules"), + translate("Rules specify which traffic will use a particular MWAN policy based on IP address, port or protocol
" .. + "Rules are matched from top to bottom. Rules below a matching rule are ignored. Traffic not matching any rule is routed using the main routing table
" .. + "Traffic destined for known (other than default) networks is handled by the main routing table. Traffic matching a rule, but all WAN interfaces for that policy are down will be blackholed
" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces
" .. + "Rules may not share the same name as configured interfaces, members or policies")) + mwan_rule.addremove = true + mwan_rule.anonymous = false + mwan_rule.dynamic = false + mwan_rule.sectionhead = "Rule" + mwan_rule.sortable = true + mwan_rule.template = "cbi/tblsection" + mwan_rule.extedit = dsp.build_url("admin", "network", "mwan", "configuration", "rule", "%s") + function mwan_rule.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(dsp.build_url("admin", "network", "mwan", "configuration", "rule", section)) + end + + +src_ip = mwan_rule:option(DummyValue, "src_ip", translate("Source address")) + src_ip.rawhtml = true + function src_ip.cfgvalue(self, s) + return self.map:get(s, "src_ip") or "—" + end + +src_port = mwan_rule:option(DummyValue, "src_port", translate("Source port")) + src_port.rawhtml = true + function src_port.cfgvalue(self, s) + return self.map:get(s, "src_port") or "—" + end + +dest_ip = mwan_rule:option(DummyValue, "dest_ip", translate("Destination address")) + dest_ip.rawhtml = true + function dest_ip.cfgvalue(self, s) + return self.map:get(s, "dest_ip") or "—" + end + +dest_port = mwan_rule:option(DummyValue, "dest_port", translate("Destination port")) + dest_port.rawhtml = true + function dest_port.cfgvalue(self, s) + return self.map:get(s, "dest_port") or "—" + end + +proto = mwan_rule:option(DummyValue, "proto", translate("Protocol")) + proto.rawhtml = true + function proto.cfgvalue(self, s) + return self.map:get(s, "proto") or "all" + end + +sticky = mwan_rule:option(DummyValue, "sticky", translate("Sticky")) + sticky.rawhtml = true + function sticky.cfgvalue(self, s) + if self.map:get(s, "sticky") == "1" then + stickied = 1 + return "Yes" + else + stickied = nil + return "No" + end + end + +timeout = mwan_rule:option(DummyValue, "timeout", translate("Sticky timeout")) + timeout.rawhtml = true + function timeout.cfgvalue(self, s) + if stickied then + local timeoutValue = self.map:get(s, "timeout") + if timeoutValue then + return timeoutValue .. "s" + else + return "600s" + end + else + return "—" + end + end + +ipset = mwan_rule:option(DummyValue, "ipset", translate("IPset")) + ipset.rawhtml = true + function ipset.cfgvalue(self, s) + return self.map:get(s, "ipset") or "—" + end + +use_policy = mwan_rule:option(DummyValue, "use_policy", translate("Policy assigned")) + use_policy.rawhtml = true + function use_policy.cfgvalue(self, s) + return self.map:get(s, "use_policy") or "—" + end + +errors = mwan_rule:option(DummyValue, "errors", translate("Errors")) + errors.rawhtml = true + function errors.cfgvalue(self, s) + if not string.find(error_protocol_list, " " .. s .. " ") then + return "" + else + return "\"error\"" + end + end + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua new file mode 100644 index 0000000000..f7fb341e1f --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua @@ -0,0 +1,113 @@ +-- ------ extra functions ------ -- + +function ruleCheck() -- determine if rule needs a protocol specified + local sourcePort = ut.trim(sys.exec("uci -p /var/state get mwan3." .. arg[1] .. ".src_port")) + local destPort = ut.trim(sys.exec("uci -p /var/state get mwan3." .. arg[1] .. ".dest_port")) + if sourcePort ~= "" or destPort ~= "" then -- ports configured + local protocol = ut.trim(sys.exec("uci -p /var/state get mwan3." .. arg[1] .. ".proto")) + if protocol == "" or protocol == "all" then -- no or improper protocol + error_protocol = 1 + end + end +end + +function ruleWarn() -- display warning message at the top of the page + if error_protocol == 1 then + return "WARNING: this rule is incorrectly configured with no or improper protocol specified! Please configure a specific protocol!" + else + return "" + end +end + +function cbiAddPolicy(field) + uci.cursor():foreach("mwan3", "policy", + function (section) + field:value(section[".name"]) + end + ) +end + +function cbiAddProtocol(field) + local protocols = ut.trim(sys.exec("cat /etc/protocols | grep ' # ' | awk '{print $1}' | grep -vw -e 'ip' -e 'tcp' -e 'udp' -e 'icmp' -e 'esp' | grep -v 'ipv6' | sort | tr '\n' ' '")) + for p in string.gmatch(protocols, "%S+") do + field:value(p) + end +end + +-- ------ rule configuration ------ -- + +dsp = require "luci.dispatcher" +sys = require "luci.sys" +ut = require "luci.util" +arg[1] = arg[1] or "" + +error_protocol = 0 +ruleCheck() + + +m5 = Map("mwan3", translate("MWAN Rule Configuration - ") .. arg[1], + translate(ruleWarn())) + m5.redirect = dsp.build_url("admin", "network", "mwan", "configuration", "rule") + + +mwan_rule = m5:section(NamedSection, arg[1], "rule", "") + mwan_rule.addremove = false + mwan_rule.dynamic = false + + +src_ip = mwan_rule:option(Value, "src_ip", translate("Source address"), + translate("Supports CIDR notation (eg \"192.168.100.0/24\") without quotes")) + src_ip.datatype = ipaddr + +src_port = mwan_rule:option(Value, "src_port", translate("Source port"), + translate("May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes")) + +dest_ip = mwan_rule:option(Value, "dest_ip", translate("Destination address"), + translate("Supports CIDR notation (eg \"192.168.100.0/24\") without quotes")) + dest_ip.datatype = ipaddr + +dest_port = mwan_rule:option(Value, "dest_port", translate("Destination port"), + translate("May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes")) + +proto = mwan_rule:option(Value, "proto", translate("Protocol"), + translate("View the contents of /etc/protocols for protocol descriptions")) + proto.default = "all" + proto.rmempty = false + proto:value("all") + proto:value("ip") + proto:value("tcp") + proto:value("udp") + proto:value("icmp") + proto:value("esp") + cbiAddProtocol(proto) + +sticky = mwan_rule:option(ListValue, "sticky", translate("Sticky"), + translate("Traffic from the same source IP address that previously matched this rule within the sticky timeout period will use the same WAN interface")) + sticky.default = "0" + sticky:value("1", translate("Yes")) + sticky:value("0", translate("No")) + +timeout = mwan_rule:option(Value, "timeout", translate("Sticky timeout"), + translate("Seconds. Acceptable values: 1-1000000. Defaults to 600 if not set")) + timeout.datatype = "range(1, 1000000)" + +ipset = mwan_rule:option(Value, "ipset", translate("IPset"), + translate("Name of IPset rule. Requires IPset rule in /etc/dnsmasq.conf (eg \"ipset=/youtube.com/youtube\")")) + +use_policy = mwan_rule:option(Value, "use_policy", translate("Policy assigned")) + cbiAddPolicy(use_policy) + use_policy:value("unreachable", translate("unreachable (reject)")) + use_policy:value("blackhole", translate("blackhole (drop)")) + use_policy:value("default", translate("default (use main routing table)")) + + +-- ------ currently configured policies ------ -- + +mwan_policy = m5:section(TypedSection, "policy", translate("Currently Configured Policies")) + mwan_policy.addremove = false + mwan_policy.dynamic = false + mwan_policy.sortable = false + mwan_policy.template = "cbi/tblsection" + + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm b/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm new file mode 100644 index 0000000000..53b997af90 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm @@ -0,0 +1 @@ +<%+mwan/openwrt_overview_status%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/advanced_diagnostics.htm b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_diagnostics.htm new file mode 100644 index 0000000000..14d404bc7c --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_diagnostics.htm @@ -0,0 +1,129 @@ +<%+header%> + + + +<% + local uci = require "luci.model.uci" + + interfaceNames = "" + uci.cursor():foreach("mwan3", "interface", + function (section) + interfaceNames = interfaceNames .. section[".name"] .. " " + end + ) +%> + + + + +
+
+ <%:MWAN Interface Diagnostics%> + +
+ + + + + + +
+
+
+ <%:MWAN Service Control%> +
+ + + +
+
+ +
+ + + +<%+footer%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/advanced_hotplugscript.htm b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_hotplugscript.htm new file mode 100644 index 0000000000..4c2a0dc208 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_hotplugscript.htm @@ -0,0 +1,24 @@ + + + diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/advanced_mwanconfig.htm b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_mwanconfig.htm new file mode 100644 index 0000000000..fba3fa6940 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_mwanconfig.htm @@ -0,0 +1,24 @@ + + + diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/advanced_networkconfig.htm b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_networkconfig.htm new file mode 100644 index 0000000000..cf90112078 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_networkconfig.htm @@ -0,0 +1,24 @@ + + + diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/advanced_troubleshooting.htm b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_troubleshooting.htm new file mode 100644 index 0000000000..0a12496899 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_troubleshooting.htm @@ -0,0 +1,74 @@ +<%+header%> + + + + + + +
+
+ <%:Troubleshooting Data%> +
<%:Loading%> Collecting data...
+
+
+ + + +<%+footer%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/advanced_wirelessconfig.htm b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_wirelessconfig.htm new file mode 100644 index 0000000000..5077674185 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/advanced_wirelessconfig.htm @@ -0,0 +1,24 @@ + + + diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/config_css.htm b/applications/luci-app-mwan3/luasrc/view/mwan/config_css.htm new file mode 100644 index 0000000000..99da4875b0 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/config_css.htm @@ -0,0 +1,34 @@ + diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/openwrt_overview_status.htm b/applications/luci-app-mwan3/luasrc/view/mwan/openwrt_overview_status.htm new file mode 100644 index 0000000000..9329b92735 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/openwrt_overview_status.htm @@ -0,0 +1,83 @@ + + +
+ <%:MWAN Interface Live Status%> +
<%:Loading%> Collecting data...
+
+ + diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/overview_detailed.htm b/applications/luci-app-mwan3/luasrc/view/mwan/overview_detailed.htm new file mode 100644 index 0000000000..b80b9f3acf --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/overview_detailed.htm @@ -0,0 +1,51 @@ +<%+header%> + + + + + + +
+
+ <%:MWAN Detailed Status%> +
<%:Loading%> Collecting data...
+
+
+ + + +<%+footer%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/overview_interface.htm b/applications/luci-app-mwan3/luasrc/view/mwan/overview_interface.htm new file mode 100644 index 0000000000..472c7ce7fc --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/overview_interface.htm @@ -0,0 +1,122 @@ +<%+header%> + + + + + + +
+
+ <%:MWAN Interface Live Status%> +
<%:Loading%> Collecting data...
+
+
+ <%:MWAN Interface Systemlog%> +
<%:Loading%> Collecting data...
+
+
+ + + +<%+footer%> diff --git a/applications/luci-app-mwan3/root/etc/hotplug.d/iface/16-mwancustombak b/applications/luci-app-mwan3/root/etc/hotplug.d/iface/16-mwancustombak new file mode 100755 index 0000000000..6e2875e3de --- /dev/null +++ b/applications/luci-app-mwan3/root/etc/hotplug.d/iface/16-mwancustombak @@ -0,0 +1,38 @@ +#!/bin/sh + +# to enable this script uncomment the case loop at the bottom +# to report mwan status on interface hotplug ifup/ifdown events modify the lines in the send_alert function + +send_alert() +{ + # variable "$1" stores the MWAN status information + # insert your code here to send the contents of "$1" + echo "$1" +} + +gather_event_info() +{ + # create event information message + local EVENT_INFO="Interface [ "$INTERFACE" ($DEVICE) ] on router [ "$(uci -p /var/state get system.@system[0].hostname)" ] has triggered a hotplug [ "$ACTION" ] event on "$(date +"%a %b %d %Y %T %Z")"" + + # get current interface, policy and rule status + local CURRENT_STATUS="$(/usr/sbin/mwan3 status)" + + # get last 50 MWAN systemlog messages + local MWAN_LOG="$(echo -e "Last 50 MWAN systemlog entries. Newest entries sorted at the top:\n$(logread | grep mwan3 | tail -n 50 | sed 'x;1!H;$!d;x')")" + + # pass event info to send_alert function + send_alert "$(echo -e "$EVENT_INFO\n\n$CURRENT_STATUS\n\n$MWAN_LOG")" +} + +#case "$ACTION" in +# ifup) +# gather_event_info +# ;; +# +# ifdown) +# gather_event_info +# ;; +#esac + +exit 0 diff --git a/applications/luci-app-mwan3/root/etc/uci-defaults/60_luci-mwan3 b/applications/luci-app-mwan3/root/etc/uci-defaults/60_luci-mwan3 new file mode 100755 index 0000000000..ff9a229edd --- /dev/null +++ b/applications/luci-app-mwan3/root/etc/uci-defaults/60_luci-mwan3 @@ -0,0 +1,14 @@ +#!/bin/sh + +# replace existing mwan ucitrack entry +uci -q batch <<-EOF >/dev/null + del ucitrack.@mwan3[-1] + add ucitrack mwan3 + set ucitrack.@mwan3[-1].exec="/usr/sbin/mwan3 restart" + commit ucitrack +EOF + +# remove LuCI cache +rm -rf /tmp/luci-indexcache /tmp/luci-modulecache + +exit 0