summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-mwan3/luasrc
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-mwan3/luasrc')
-rw-r--r--applications/luci-app-mwan3/luasrc/controller/mwan3.lua314
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua31
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua240
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua230
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua44
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua31
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua45
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua87
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua30
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua104
-rw-r--r--applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua63
-rw-r--r--applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm3
-rw-r--r--applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm75
-rw-r--r--applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm39
-rw-r--r--applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm90
-rw-r--r--applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm20
-rw-r--r--applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm39
17 files changed, 1485 insertions, 0 deletions
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..2d46953e55
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/controller/mwan3.lua
@@ -0,0 +1,314 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.mwan3", package.seeall)
+
+sys = require "luci.sys"
+ut = require "luci.util"
+
+ip = "ip -4 "
+
+function index()
+ if not nixio.fs.access("/etc/config/mwan3") then
+ return
+ end
+
+ entry({"admin", "status", "mwan"},
+ alias("admin", "status", "mwan", "overview"),
+ _("Load Balancing"), 600)
+
+ entry({"admin", "status", "mwan", "overview"},
+ template("mwan/status_interface"))
+ entry({"admin", "status", "mwan", "detail"},
+ template("mwan/status_detail"))
+ entry({"admin", "status", "mwan", "diagnostics"},
+ template("mwan/status_diagnostics"))
+ entry({"admin", "status", "mwan", "troubleshooting"},
+ template("mwan/status_troubleshooting"))
+ entry({"admin", "status", "mwan", "interface_status"},
+ call("mwan_Status"))
+ entry({"admin", "status", "mwan", "detailed_status"},
+ call("detailedStatus"))
+ entry({"admin", "status", "mwan", "diagnostics_display"},
+ call("diagnosticsData"), nil).leaf = true
+ entry({"admin", "status", "mwan", "troubleshooting_display"},
+ call("troubleshootingData"))
+
+
+ entry({"admin", "network", "mwan"},
+ alias("admin", "network", "mwan", "interface"),
+ _("Load Balancing"), 600)
+
+ entry({"admin", "network", "mwan", "globals"},
+ cbi("mwan/globalsconfig"),
+ _("Globals"), 5).leaf = true
+ entry({"admin", "network", "mwan", "interface"},
+ arcombine(cbi("mwan/interface"), cbi("mwan/interfaceconfig")),
+ _("Interfaces"), 10).leaf = true
+ entry({"admin", "network", "mwan", "member"},
+ arcombine(cbi("mwan/member"), cbi("mwan/memberconfig")),
+ _("Members"), 20).leaf = true
+ entry({"admin", "network", "mwan", "policy"},
+ arcombine(cbi("mwan/policy"), cbi("mwan/policyconfig")),
+ _("Policies"), 30).leaf = true
+ entry({"admin", "network", "mwan", "rule"},
+ arcombine(cbi("mwan/rule"), cbi("mwan/ruleconfig")),
+ _("Rules"), 40).leaf = true
+ entry({"admin", "network", "mwan", "notify"},
+ form("mwan/notify"),
+ _("Notification"), 50).leaf = true
+end
+
+function mwan_Status()
+ local status = ut.ubus("mwan3", "status", {})
+
+ luci.http.prepare_content("application/json")
+ if status ~= nil then
+ luci.http.write_json(status)
+ else
+ luci.http.write_json({})
+ end
+end
+
+function detailedStatus()
+ local statusInfo = ut.trim(sys.exec("/usr/sbin/mwan3 status"))
+ luci.http.prepare_content("text/plain")
+ if statusInfo ~= "" then
+ luci.http.write(statusInfo)
+ else
+ luci.http.write("Unable to get status information")
+ end
+end
+
+function diagnosticsData(interface, task)
+ function getInterfaceNumber(interface)
+ local number = 0
+ local interfaceNumber
+ local uci = require "luci.model.uci".cursor()
+ uci:foreach("mwan3", "interface",
+ function (section)
+ number = number+1
+ if section[".name"] == interface then
+ interfaceNumber = number
+ end
+ end
+ )
+ return interfaceNumber
+ end
+
+ function diag_command(cmd, device, addr)
+ if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then
+ local util = io.popen(cmd %{ut.shellquote(device), ut.shellquote(addr)})
+ if util then
+ while true do
+ local ln = util:read("*l")
+ if not ln then break end
+ luci.http.write(ln)
+ luci.http.write("\n")
+ end
+ util:close()
+ end
+ return
+ end
+ end
+
+ function get_gateway(inteface)
+ local gateway = nil
+ local dump = nil
+
+ dump = require("luci.util").ubus("network.interface.%s_4" % interface, "status", {})
+ if not dump then
+ dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
+ end
+
+ if dump and dump.route then
+ local _, route
+ for _, route in ipairs(dump.route) do
+ if dump.route[_].target == "0.0.0.0" then
+ gateway = dump.route[_].nexthop
+ end
+ end
+ end
+ return gateway
+ end
+
+ local mArray = {}
+ local results = ""
+ local number = getInterfaceNumber(interface)
+
+ local uci = require "luci.model.uci".cursor(nil, "/var/state")
+ local nw = require "luci.model.network".init()
+ local network = nw:get_network(interface)
+ local device = network and network:ifname()
+
+ luci.http.prepare_content("text/plain")
+ if device then
+ if task == "ping_gateway" then
+ local gateway = get_gateway(interface)
+ if gateway ~= nil then
+ diag_command("ping -I %s -c 5 -W 1 %s 2>&1", device, gateway)
+ else
+ luci.http.prepare_content("text/plain")
+ luci.http.write(string.format("No gateway for interface %s found.", interface))
+ end
+ elseif task == "ping_trackips" then
+ local trackips = uci:get("mwan3", interface, "track_ip")
+ if #trackips > 0 then
+ for i in pairs(trackips) do
+ diag_command("ping -I %s -c 5 -W 1 %s 2>&1", device, trackips[i])
+ end
+ else
+ luci.http.write(string.format("No tracking Hosts for interface %s defined.", interface))
+ end
+ elseif task == "check_rules" then
+ local number = getInterfaceNumber(interface)
+ local iif = 1000 + number
+ local fwmark = 2000 + number
+ local iif_rule = sys.exec(string.format("ip rule | grep %d", iif))
+ local fwmark_rule = sys.exec(string.format("ip rule | grep %d", fwmark))
+ if iif_rule ~= "" and fwmark_rule ~= "" then
+ luci.http.write(string.format("All required IP rules for interface %s found", interface))
+ luci.http.write("\n")
+ luci.http.write(fwmark_rule)
+ luci.http.write(iif_rule)
+ elseif iif_rule == "" and fwmark_rule ~= "" then
+ luci.http.write(string.format("Only one IP rules for interface %s found", interface))
+ luci.http.write("\n")
+ luci.http.write(fwmark_rule)
+ elseif iif_rule ~= "" and fwmark_rule == "" then
+ luci.http.write(string.format("Only one IP rules for interface %s found", interface))
+ luci.http.write("\n")
+ luci.http.write(iif_rule)
+ else
+ luci.http.write(string.format("Missing both IP rules for interface %s", interface))
+ end
+ elseif task == "check_routes" then
+ local number = getInterfaceNumber(interface)
+ local routeTable = sys.exec(string.format("ip route list table %s", number))
+ if routeTable ~= "" then
+ luci.http.write(string.format("Routing table %s for interface %s found", number, interface))
+ luci.http.write("\n")
+ luci.http.write(routeTable)
+ else
+ luci.http.write(string.format("Routing table %s for interface %s not found", number, interface))
+ end
+ elseif task == "hotplug_ifup" then
+ os.execute(string.format("/usr/sbin/mwan3 ifup %s", ut.shellquote(interface)))
+ luci.http.write(string.format("Hotplug ifup sent to interface %s", interface))
+ elseif task == "hotplug_ifdown" then
+ os.execute(string.format("/usr/sbin/mwan3 ifdown %s", ut.shellquote(interface)))
+ luci.http.write(string.format("Hotplug ifdown sent to interface %s", interface))
+ else
+ luci.http.write("Unknown task")
+ end
+ else
+ luci.http.write(string.format("Unable to perform diagnostic tests on %s.", interface))
+ luci.http.write("\n")
+ luci.http.write("There is no physical or virtual device associated with this interface.")
+ end
+end
+
+function troubleshootingData()
+ local ver = require "luci.version"
+ local dash = "-------------------------------------------------"
+
+ luci.http.prepare_content("text/plain")
+
+ luci.http.write("\n")
+ luci.http.write("\n")
+ luci.http.write("Software-Version")
+ luci.http.write("\n")
+ luci.http.write(dash)
+ luci.http.write("\n")
+ if ver.distversion then
+ luci.http.write(string.format("OpenWrt - %s", ver.distversion))
+ luci.http.write("\n")
+ else
+ luci.http.write("OpenWrt - unknown")
+ luci.http.write("\n")
+ end
+
+ if ver.luciversion then
+ luci.http.write(string.format("LuCI - %s", ver.luciversion))
+ luci.http.write("\n")
+ else
+ luci.http.write("LuCI - unknown")
+ luci.http.write("\n")
+ end
+
+ luci.http.write("\n")
+ luci.http.write("\n")
+ local output = ut.trim(sys.exec("ip a show"))
+ luci.http.write("Output of \"ip a show\"")
+ luci.http.write("\n")
+ luci.http.write(dash)
+ luci.http.write("\n")
+ if output ~= "" then
+ luci.http.write(output)
+ luci.http.write("\n")
+ else
+ luci.http.write("No data found")
+ luci.http.write("\n")
+ end
+
+ luci.http.write("\n")
+ luci.http.write("\n")
+ local output = ut.trim(sys.exec("ip route show"))
+ luci.http.write("Output of \"ip route show\"")
+ luci.http.write("\n")
+ luci.http.write(dash)
+ luci.http.write("\n")
+ if output ~= "" then
+ luci.http.write(output)
+ luci.http.write("\n")
+ else
+ luci.http.write("No data found")
+ luci.http.write("\n")
+ end
+
+ luci.http.write("\n")
+ luci.http.write("\n")
+ local output = ut.trim(sys.exec("ip rule show"))
+ luci.http.write("Output of \"ip rule show\"")
+ luci.http.write("\n")
+ luci.http.write(dash)
+ luci.http.write("\n")
+ if output ~= "" then
+ luci.http.write(output)
+ luci.http.write("\n")
+ else
+ luci.http.write("No data found")
+ luci.http.write("\n")
+ end
+
+ luci.http.write("\n")
+ luci.http.write("\n")
+ luci.http.write("Output of \"ip route list table 1-250\"")
+ luci.http.write("\n")
+ luci.http.write(dash)
+ luci.http.write("\n")
+ for i=1,250 do
+ local output = ut.trim(sys.exec(string.format("ip route list table %d", i)))
+ if output ~= "" then
+ luci.http.write(string.format("Table %s: ", i))
+ luci.http.write(output)
+ luci.http.write("\n")
+ end
+ end
+
+ luci.http.write("\n")
+ luci.http.write("\n")
+ local output = ut.trim(sys.exec("iptables -L -t mangle -v -n"))
+ luci.http.write("Output of \"iptables -L -t mangle -v -n\"")
+ luci.http.write("\n")
+ luci.http.write(dash)
+ luci.http.write("\n")
+ if output ~= "" then
+ luci.http.write(output)
+ luci.http.write("\n")
+ else
+ luci.http.write("No data found")
+ luci.http.write("\n")
+ end
+end
diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua
new file mode 100644
index 0000000000..d8f90e1e25
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua
@@ -0,0 +1,31 @@
+-- Copyright 2017 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+local net = require "luci.model.network".init()
+
+
+m = Map("mwan3", translate("MWAN - Globals"))
+
+s = m:section(NamedSection, "globals", "globals", nil)
+n = s:option(ListValue, "local_source",
+ translate("Local source interface"),
+ translate("Use the IP address of this interface as source IP " ..
+ "address for traffic initiated by the router itself"))
+n:value("none")
+n.default = "none"
+for _, net in ipairs(net:get_networks()) do
+ if net:name() ~= "loopback" then
+ n:value(net:name())
+ end
+end
+n.rmempty = false
+
+mask = s:option(
+ Value,
+ "mmx_mask",
+ translate("Firewall mask"),
+ translate("Enter value in hex, starting with <code>0x</code>"))
+mask.datatype = "hex(4)"
+mask.default = "0xff00"
+
+return m
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..162b388d17
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua
@@ -0,0 +1,240 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+uci = require "uci"
+
+
+function interfaceWarnings(overview, count, iface_max)
+ local warnings = ""
+ if count <= iface_max then
+ warnings = string.format("<strong>%s</strong><br />",
+ translatef("There are currently %d of %d supported interfaces configured", count, iface_max)
+ )
+ else
+ warnings = string.format("<strong>%s</strong><br />",
+ translatef("WARNING: %d interfaces are configured exceeding the maximum of %d!", count, iface_max)
+ )
+ end
+
+ for i, k in pairs(overview) do
+ if overview[i]["network"] == false then
+ warnings = warnings .. string.format("<strong>%s</strong><br />",
+ translatef("WARNING: Interface %s are not found in /etc/config/network", i)
+ )
+ end
+
+ if overview[i]["default_route"] == false then
+ warnings = warnings .. string.format("<strong>%s</strong><br />",
+ translatef("WARNING: Interface %s has no default route in the main routing table", i)
+ )
+ end
+
+ if overview[i]["reliability"] == false then
+ warnings = warnings .. string.format("<strong>%s</strong><br />",
+ translatef("WARNING: Interface %s has a higher reliability " ..
+ "requirement than tracking hosts (%d)", i, overview[i]["tracking"])
+ )
+ end
+
+ if overview[i]["duplicate_metric"] == true then
+ warnings = warnings .. string.format("<strong>%s</strong><br />",
+ translatef("WARNING: Interface %s has a duplicate metric %s configured", i, overview[i]["metric"])
+ )
+ end
+ end
+
+ return warnings
+end
+
+function configCheck()
+ local overview = {}
+ local count = 0
+ local duplicate_metric = {}
+ uci.cursor():foreach("mwan3", "interface",
+ function (section)
+ local uci = uci.cursor(nil, "/var/state")
+ local iface = section[".name"]
+ overview[iface] = {}
+ count = count + 1
+ local network = uci:get("network", iface)
+ overview[iface]["network"] = false
+ if network ~= nil then
+ overview[iface]["network"] = true
+
+ local device = uci:get("network", iface, "ifname")
+ if device ~= nil then
+ overview[iface]["device"] = device
+ end
+
+ local metric = uci:get("network", iface, "metric")
+ if metric ~= nil then
+ overview[iface]["metric"] = metric
+ overview[iface]["duplicate_metric"] = false
+ for _, m in ipairs(duplicate_metric) do
+ if m == metric then
+ overview[iface]["duplicate_metric"] = true
+ end
+ end
+ table.insert(duplicate_metric, metric)
+ end
+
+ local dump = require("luci.util").ubus("network.interface.%s" % iface, "status", {})
+ overview[iface]["default_route"] = false
+ if dump and dump.route then
+ local _, route
+ for _, route in ipairs(dump.route) do
+ if dump.route[_].target == "0.0.0.0" then
+ overview[iface]["default_route"] = true
+ end
+ end
+ end
+ end
+
+ local trackingNumber = uci:get("mwan3", iface, "track_ip")
+ overview[iface]["tracking"] = 0
+ if trackingNumber and #trackingNumber > 0 then
+ overview[iface]["tracking"] = #trackingNumber
+ overview[iface]["reliability"] = false
+ local reliabilityNumber = tonumber(uci:get("mwan3", iface, "reliability"))
+ if reliabilityNumber and reliabilityNumber <= #trackingNumber then
+ overview[iface]["reliability"] = true
+ end
+ end
+ end
+ )
+
+ -- calculate iface_max usage from firewall mmx_mask
+ function bit(p)
+ return 2 ^ (p - 1)
+ end
+ function hasbit(x, p)
+ return x % (p + p) >= p
+ end
+ function setbit(x, p)
+ return hasbit(x, p) and x or x + p
+ end
+
+ local uci = require("uci").cursor(nil, "/var/state")
+ local mmx_mask = uci:get("mwan3", "globals", "mmx_mask") or "0x3F00"
+ local number = tonumber(mmx_mask, 16)
+ local bits = 0
+ local iface_max = 0
+ for i=1,16 do
+ if hasbit(number, bit(i)) then
+ bits = bits + 1
+ iface_max = setbit( iface_max, bit(bits))
+ end
+ end
+
+ -- subtract blackhole, unreachable and default table from iface_max
+ iface_max = iface_max - 3
+
+ return overview, count, iface_max
+end
+
+m5 = Map("mwan3", translate("MWAN - Interfaces"),
+ interfaceWarnings(configCheck()))
+
+mwan_interface = m5:section(TypedSection, "interface", nil,
+ translate("MWAN supports up to 252 physical and/or logical interfaces<br />" ..
+ "MWAN requires that all interfaces have a unique metric configured in /etc/config/network<br />" ..
+ "Names must match the interface name found in /etc/config/network<br />" ..
+ "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
+ "Interfaces may not share the same name as configured members, policies or rules"))
+mwan_interface.addremove = true
+mwan_interface.dynamic = false
+mwan_interface.sectionhead = translate("Interface")
+mwan_interface.sortable = false
+mwan_interface.template = "cbi/tblsection"
+mwan_interface.extedit = dsp.build_url("admin", "network", "mwan", "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", "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 translate("Yes")
+ else
+ return translate("No")
+ end
+end
+
+track_method = mwan_interface:option(DummyValue, "track_method", translate("Tracking method"))
+track_method.rawhtml = true
+function track_method.cfgvalue(self, s)
+ local tracked = self.map:get(s, "track_ip")
+ if tracked then
+ return self.map:get(s, "track_method") or "ping"
+ else
+ return "&#8212;"
+ end
+end
+
+reliability = mwan_interface:option(DummyValue, "reliability", translate("Tracking reliability"))
+reliability.rawhtml = true
+function reliability.cfgvalue(self, s)
+ local tracked = self.map:get(s, "track_ip")
+ if tracked then
+ return self.map:get(s, "reliability") or "1"
+ else
+ return "&#8212;"
+ end
+end
+
+interval = mwan_interface:option(DummyValue, "interval", translate("Ping interval"))
+interval.rawhtml = true
+function interval.cfgvalue(self, s)
+ local tracked = self.map:get(s, "track_ip")
+ if tracked then
+ local intervalValue = self.map:get(s, "interval")
+ if intervalValue then
+ return intervalValue .. "s"
+ else
+ return "5s"
+ end
+ else
+ return "&#8212;"
+ end
+end
+
+down = mwan_interface:option(DummyValue, "down", translate("Interface down"))
+down.rawhtml = true
+function down.cfgvalue(self, s)
+ local tracked = self.map:get(s, "track_ip")
+ if tracked then
+ return self.map:get(s, "down") or "3"
+ else
+ return "&#8212;"
+ end
+end
+
+up = mwan_interface:option(DummyValue, "up", translate("Interface up"))
+up.rawhtml = true
+function up.cfgvalue(self, s)
+ local tracked = self.map:get(s, "track_ip")
+ if tracked then
+ return self.map:get(s, "up") or "3"
+ else
+ return "&#8212;"
+ end
+end
+
+metric = mwan_interface:option(DummyValue, "metric", translate("Metric"))
+metric.rawhtml = true
+function metric.cfgvalue(self, s)
+ local uci = uci.cursor(nil, "/var/state")
+ local metric = uci:get("network", s, "metric")
+ if metric then
+ return metric
+ else
+ return "&#8212;"
+ 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..a6570e341c
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua
@@ -0,0 +1,230 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+arg[1] = arg[1] or ""
+
+
+m5 = Map("mwan3", translatef("MWAN Interface Configuration - %s", arg[1]))
+ m5.redirect = dsp.build_url("admin", "network", "mwan", "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"))
+
+initial_state = mwan_interface:option(ListValue, "initial_state", translate("Initial state"),
+ translate("Expect interface state on up event"))
+initial_state.default = "online"
+initial_state:value("online", translate("Online"))
+initial_state:value("offline", translate("Offline"))
+
+family = mwan_interface:option(ListValue, "family", translate("Internet Protocol"))
+family.default = "ipv4"
+family:value("ipv4", translate("IPv4"))
+family:value("ipv6", translate("IPv6"))
+
+track_ip = mwan_interface:option(DynamicList, "track_ip", translate("Tracking hostname or IP address"),
+ translate("This hostname or IP address will be pinged to determine if the link is up or down. Leave blank to assume interface is always online"))
+track_ip.datatype = "host"
+
+track_method = mwan_interface:option(ListValue, "track_method", translate("Tracking method"))
+track_method.default = "ping"
+track_method:value("ping")
+track_method:value("arping")
+track_method:value("httping")
+
+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")
+
+size = mwan_interface:option(Value, "size", translate("Ping size"))
+size.default = "56"
+size:depends("track_method", "ping")
+size:value("8")
+size:value("24")
+size:value("56")
+size:value("120")
+size:value("248")
+size:value("504")
+size:value("1016")
+size:value("1472")
+size:value("2040")
+size.datatype = "range(1, 65507)"
+size.rmempty = false
+size.optional = false
+
+check_quality = mwan_interface:option(Flag, "check_quality", translate("Check link quality"))
+check_quality:depends("track_method", "ping")
+check_quality.default = false
+
+failure_latency = mwan_interface:option(Value, "failure_latency", translate("Max packet latency [ms]"))
+failure_latency:depends("check_quality", 1)
+failure_latency.default = "1000"
+failure_latency:value("25")
+failure_latency:value("50")
+failure_latency:value("75")
+failure_latency:value("100")
+failure_latency:value("150")
+failure_latency:value("200")
+failure_latency:value("250")
+failure_latency:value("300")
+
+failure_loss = mwan_interface:option(Value, "failure_loss", translate("Max packet loss [%]"))
+failure_loss:depends("check_quality", 1)
+failure_loss.default = "20"
+failure_loss:value("2")
+failure_loss:value("5")
+failure_loss:value("10")
+failure_loss:value("20")
+failure_loss:value("25")
+
+recovery_latency = mwan_interface:option(Value, "recovery_latency", translate("Min packet latency [ms]"))
+recovery_latency:depends("check_quality", 1)
+recovery_latency.default = "500"
+recovery_latency:value("25")
+recovery_latency:value("50")
+recovery_latency:value("75")
+recovery_latency:value("100")
+recovery_latency:value("150")
+recovery_latency:value("200")
+recovery_latency:value("250")
+recovery_latency:value("300")
+
+recovery_loss = mwan_interface:option(Value, "recovery_loss", translate("Min packet loss [%]"))
+recovery_loss:depends("check_quality", 1)
+recovery_loss.default = "5"
+recovery_loss:value("2")
+recovery_loss:value("5")
+recovery_loss:value("10")
+recovery_loss:value("20")
+recovery_loss:value("25")
+
+timeout = mwan_interface:option(ListValue, "timeout", translate("Ping timeout"))
+timeout.default = "2"
+timeout:value("1", translatef("%d second", 1))
+timeout:value("2", translatef("%d seconds", 2))
+timeout:value("3", translatef("%d seconds", 3))
+timeout:value("4", translatef("%d seconds", 4))
+timeout:value("5", translatef("%d seconds", 5))
+timeout:value("6", translatef("%d seconds", 6))
+timeout:value("7", translatef("%d seconds", 7))
+timeout:value("8", translatef("%d seconds", 8))
+timeout:value("9", translatef("%d seconds", 9))
+timeout:value("10", translatef("%d seconds", 10))
+
+interval = mwan_interface:option(ListValue, "interval", translate("Ping interval"))
+interval.default = "5"
+interval:value("1", translatef("%d second", 1))
+interval:value("3", translatef("%d seconds", 3))
+interval:value("5", translatef("%d seconds", 5))
+interval:value("10", translatef("%d seconds", 10))
+interval:value("20", translatef("%d seconds", 20))
+interval:value("30", translatef("%d seconds", 30))
+interval:value("60", translatef("%d minute", 1))
+interval:value("300", translatef("%d minutes", 5))
+interval:value("600", translatef("%d minutes", 10))
+interval:value("900", translatef("%d minutes", 15))
+interval:value("1800", translatef("%d minutes", 30))
+interval:value("3600", translatef("%d hour", 1))
+
+failure = mwan_interface:option(Value, "failure_interval", translate("Failure interval"),
+ translate("Ping interval during failure detection"))
+failure.default = "5"
+failure:value("1", translatef("%d second", 1))
+failure:value("3", translatef("%d seconds", 3))
+failure:value("5", translatef("%d seconds", 5))
+failure:value("10", translatef("%d seconds", 10))
+failure:value("20", translatef("%d seconds", 20))
+failure:value("30", translatef("%d seconds", 30))
+failure:value("60", translatef("%d minute", 1))
+failure:value("300", translatef("%d minutes", 5))
+failure:value("600", translatef("%d minutes", 10))
+failure:value("900", translatef("%d minutes", 15))
+failure:value("1800", translatef("%d minutes", 30))
+failure:value("3600", translatef("%d hour", 1))
+
+keep_failure = mwan_interface:option(Flag, "keep_failure_interval", translate("Keep failure interval"),
+ translate("Keep ping failure interval during failure state"))
+keep_failure.default = keep_failure.disabled
+
+recovery = mwan_interface:option(Value, "recovery_interval", translate("Recovery interval"),
+ translate("Ping interval during failure recovering"))
+recovery.default = "5"
+recovery:value("1", translatef("%d second", 1))
+recovery:value("3", translatef("%d seconds", 3))
+recovery:value("5", translatef("%d seconds", 5))
+recovery:value("10", translatef("%d seconds", 10))
+recovery:value("20", translatef("%d seconds", 20))
+recovery:value("30", translatef("%d seconds", 30))
+recovery:value("60", translatef("%d minute", 1))
+recovery:value("300", translatef("%d minutes", 5))
+recovery:value("600", translatef("%d minutes", 10))
+recovery:value("900", translatef("%d minutes", 15))
+recovery:value("1800", translatef("%d minutes", 30))
+recovery:value("3600", translatef("%d hour", 1))
+
+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")
+
+flush = mwan_interface:option(ListValue, "flush_conntrack", translate("Flush conntrack table"),
+ translate("Flush global firewall conntrack table on interface events"))
+flush.default = "never"
+flush:value("ifup", translate("ifup"))
+flush:value("ifdown", translate("ifdown"))
+flush:value("never", translate("never"))
+flush:value("always", translate("always"))
+
+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)
+ local uci = require "luci.model.uci".cursor(nil, "/var/state")
+ local metric = uci:get("network", arg[1], "metric")
+ if metric then
+ return metric
+ else
+ return "&#8212;"
+ 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..9b4ab102d5
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua
@@ -0,0 +1,44 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+
+
+m5 = Map("mwan3", translate("MWAN - Members"))
+
+mwan_member = m5:section(TypedSection, "member", nil,
+ translate("Members are profiles attaching a metric and weight to an MWAN interface<br />" ..
+ "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
+ "Members may not share the same name as configured interfaces, policies or rules"))
+mwan_member.addremove = true
+mwan_member.dynamic = false
+mwan_member.sectionhead = translate("Member")
+mwan_member.sortable = true
+mwan_member.template = "cbi/tblsection"
+mwan_member.extedit = dsp.build_url("admin", "network", "mwan", "member", "%s")
+function mwan_member.create(self, section)
+ TypedSection.create(self, section)
+ m5.uci:save("mwan3")
+ luci.http.redirect(dsp.build_url("admin", "network", "mwan", "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 "&#8212;"
+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..27d9a3e858
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua
@@ -0,0 +1,31 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+arg[1] = arg[1] or ""
+
+
+m5 = Map("mwan3", translatef("MWAN Member Configuration - %s", arg[1]))
+m5.redirect = dsp.build_url("admin", "network", "mwan", "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"))
+m5.uci:foreach("mwan3", "interface",
+ function(s)
+ interface:value(s['.name'], s['.name'])
+ end
+)
+
+metric = mwan_member:option(Value, "metric", translate("Metric"),
+ translate("Acceptable values: 1-256. Defaults to 1 if not set"))
+metric.datatype = "range(1, 256)"
+
+weight = mwan_member:option(Value, "weight", translate("Weight"),
+ translate("Acceptable values: 1-1000. Defaults to 1 if not set"))
+weight.datatype = "range(1, 1000)"
+
+return m5
diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua
new file mode 100644
index 0000000000..4c6e21003e
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua
@@ -0,0 +1,45 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+local fs = require "nixio.fs"
+local ut = require "luci.util"
+script = "/etc/mwan3.user"
+
+
+m5 = SimpleForm("luci", translate("MWAN - Notification"))
+
+f = m5:section(SimpleSection, nil,
+ translate("This section allows you to modify the content of \"/etc/mwan3.user\".<br />" ..
+ "The file is also preserved during sysupgrade.<br />" ..
+ "<br />" ..
+ "Notes:<br />" ..
+ "This file is interpreted as a shell script.<br />" ..
+ "The first line of the script must be &#34;#!/bin/sh&#34; without quotes.<br />" ..
+ "Lines beginning with # are comments and are not executed.<br />" ..
+ "Put your custom mwan3 action here, they will<br />" ..
+ "be executed with each netifd hotplug interface event<br />" ..
+ "on interfaces for which mwan3 is enabled.<br />" ..
+ "<br />" ..
+ "There are three main environment variables that are passed to this script.<br />" ..
+ "<br />" ..
+ "$ACTION <br />" ..
+ "* \"ifup\" Is called by netifd and mwan3track <br />" ..
+ "* \"ifdown\" Is called by netifd and mwan3track <br />" ..
+ "* \"connected\" Is only called by mwan3track if tracking was successful <br />" ..
+ "* \"disconnected\" Is only called by mwan3track if tracking has failed <br />" ..
+ "$INTERFACE Name of the interface which went up or down (e.g. \"wan\" or \"wwan\")<br />" ..
+ "$DEVICE Physical device name which interface went up or down (e.g. \"eth0\" or \"wwan0\")<br />" ..
+ "<br />"))
+
+t = f:option(TextValue, "lines")
+t.rmempty = true
+t.rows = 20
+function t.cfgvalue()
+ return fs.readfile(script)
+end
+function t.write(self, section, data)
+ 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/policy.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua
new file mode 100644
index 0000000000..4543260f6f
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua
@@ -0,0 +1,87 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+uci = require "uci"
+
+
+function policyCheck()
+ local policy_error = {}
+
+ uci.cursor():foreach("mwan3", "policy",
+ function (section)
+ policy_error[section[".name"]] = false
+ if string.len(section[".name"]) > 15 then
+ policy_error[section[".name"]] = true
+ end
+ end
+ )
+
+ return policy_error
+end
+
+function policyError(policy_error)
+ local warnings = ""
+ for i, k in pairs(policy_error) do
+ if policy_error[i] == true then
+ warnings = warnings .. string.format("<strong>%s</strong><br />",
+ translatef("WARNING: Policy %s has exceeding the maximum name of 15 characters", i)
+ )
+ end
+ end
+
+ return warnings
+end
+
+m5 = Map("mwan3", translate("MWAN - Policies"),
+ policyError(policyCheck()))
+
+mwan_policy = m5:section(TypedSection, "policy", nil,
+ translate("Policies are profiles grouping one or more members controlling how MWAN distributes traffic<br />" ..
+ "Member interfaces with lower metrics are used first<br />" ..
+ "Member interfaces with the same metric will be load-balanced<br />" ..
+ "Load-balanced member interfaces distribute more traffic out those with higher weights<br />" ..
+ "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
+ "Names must be 15 characters or less<br />" ..
+ "Policies may not share the same name as configured interfaces, members or rules"))
+mwan_policy.addremove = true
+mwan_policy.dynamic = false
+mwan_policy.sectionhead = translate("Policy")
+mwan_policy.sortable = true
+mwan_policy.template = "cbi/tblsection"
+mwan_policy.extedit = dsp.build_url("admin", "network", "mwan", "policy", "%s")
+function mwan_policy.create(self, section)
+ TypedSection.create(self, section)
+ m5.uci:save("mwan3")
+ luci.http.redirect(dsp.build_url("admin", "network", "mwan", "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 .. "<br />"
+ end
+ return memberList
+ else
+ return "&#8212;"
+ 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 translate("blackhole (drop)")
+ elseif action == "default" then
+ return translate("default (use main routing table)")
+ else
+ return translate("unreachable (reject)")
+ 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..d1a063d093
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua
@@ -0,0 +1,30 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+arg[1] = arg[1] or ""
+
+
+m5 = Map("mwan3", translatef("MWAN Policy Configuration - %s", arg[1]))
+m5.redirect = dsp.build_url("admin", "network", "mwan", "policy")
+
+mwan_policy = m5:section(NamedSection, arg[1], "policy", "")
+mwan_policy.addremove = false
+mwan_policy.dynamic = false
+
+member = mwan_policy:option(DynamicList, "use_member", translate("Member used"))
+m5.uci:foreach("mwan3", "member",
+ function(s)
+ member:value(s['.name'], s['.name'])
+ end
+)
+
+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)"))
+
+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..f0b94bd0bb
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua
@@ -0,0 +1,104 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+uci = require "uci"
+
+
+function ruleCheck()
+ local rule_error = {}
+ uci.cursor():foreach("mwan3", "rule",
+ function (section)
+ rule_error[section[".name"]] = false
+ local uci = uci.cursor(nil, "/var/state")
+ local sourcePort = uci:get("mwan3", section[".name"], "src_port")
+ local destPort = uci:get("mwan3", section[".name"], "dest_port")
+ if sourcePort ~= nil or destPort ~= nil then
+ local protocol = uci:get("mwan3", section[".name"], "proto")
+ if protocol == nil or protocol == "all" then
+ rule_error[section[".name"]] = true
+ end
+ end
+ end
+ )
+ return rule_error
+end
+
+function ruleWarn(rule_error)
+ local warnings = ""
+ for i, k in pairs(rule_error) do
+ if rule_error[i] == true then
+ warnings = warnings .. string.format("<strong>%s</strong><br />",
+ translatef("WARNING: Rule %s have a port configured with no or improper protocol specified!", i)
+ )
+ end
+ end
+
+ return warnings
+end
+
+m5 = Map("mwan3", translate("MWAN - Rules"),
+ ruleWarn(ruleCheck())
+ )
+
+mwan_rule = m5:section(TypedSection, "rule", nil,
+ translate("Rules specify which traffic will use a particular MWAN policy<br />" ..
+ "Rules are based on IP address, port or protocol<br />" ..
+ "Rules are matched from top to bottom<br />" ..
+ "Rules below a matching rule are ignored<br />" ..
+ "Traffic not matching any rule is routed using the main routing table<br />" ..
+ "Traffic destined for known (other than default) networks is handled by the main routing table<br />" ..
+ "Traffic matching a rule, but all WAN interfaces for that policy are down will be blackholed<br />" ..
+ "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
+ "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 = translate("Rule")
+mwan_rule.sortable = true
+mwan_rule.template = "cbi/tblsection"
+mwan_rule.extedit = dsp.build_url("admin", "network", "mwan", "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", "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 "&#8212;"
+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 "&#8212;"
+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 "&#8212;"
+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 "&#8212;"
+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
+
+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 "&#8212;"
+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..84adfcf910
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua
@@ -0,0 +1,63 @@
+-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+-- Licensed to the public under the GNU General Public License v2.
+
+dsp = require "luci.dispatcher"
+arg[1] = arg[1] or ""
+
+
+m5 = Map("mwan3", translatef("MWAN Rule Configuration - %s", arg[1]))
+m5.redirect = dsp.build_url("admin", "network", "mwan", "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 content of /etc/protocols for protocol description"))
+proto.default = "all"
+proto.rmempty = false
+proto:value("all")
+proto:value("tcp")
+proto:value("udp")
+proto:value("icmp")
+proto:value("esp")
+
+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\")"))
+
+policy = mwan_rule:option(Value, "use_policy", translate("Policy assigned"))
+m5.uci:foreach("mwan3", "policy",
+ function(s)
+ policy:value(s['.name'], s['.name'])
+ end
+)
+policy:value("unreachable", translate("unreachable (reject)"))
+policy:value("blackhole", translate("blackhole (drop)"))
+policy:value("default", translate("default (use main routing table)"))
+
+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..e4b3c06999
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm
@@ -0,0 +1,3 @@
+<%if require("luci.sys").init.enabled("mwan3") then%>
+<%+mwan/overview_status_interface%>
+<%end%>
diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm b/applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm
new file mode 100644
index 0000000000..49d120c1ae
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm
@@ -0,0 +1,75 @@
+<%#
+ Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+ Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+ Licensed to the public under the GNU General Public License v2.
+-%>
+
+<script type="text/javascript">//<![CDATA[
+XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_status")%>', null,
+ function(x, status)
+ {
+ var statusDiv = document.getElementById('mwan_status_text');
+ if (status.interfaces)
+ {
+ var statusview = '';
+ for ( var iface in status.interfaces)
+ {
+ var state = '';
+ var css = ''
+ switch (status.interfaces[iface].status)
+ {
+ case 'online':
+ state = '<%:Online%>';
+ css = 'success';
+ break;
+ case 'offline':
+ state = '<%:Offline%>';
+ css = 'danger';
+ break;
+ default:
+ state = '<%:Disabled%>';
+ css = 'warning';
+ break;
+ }
+ statusview += String.format(
+ '<div class="alert-message %s">',
+ css
+ );
+ statusview += String.format(
+ '<div><strong>Interface: </strong>%s</div>',
+ iface
+ );
+ statusview += String.format(
+ '<div><strong>Status: </strong>%s</div>',
+ state
+ );
+ statusview += '</div>'
+ }
+ statusDiv.innerHTML = statusview;
+ }
+ else
+ {
+ statusDiv.innerHTML = '<strong><%:No MWAN interfaces found%></strong>';
+ }
+ }
+ );
+//]]></script>
+
+<style type="text/css">
+ #mwan_status_text > div {
+ display: inline-block;
+ margin: 1rem;
+ padding: 1rem;
+ width: 10rem;
+ float: left;
+ line-height: 125%;
+ }
+</style>
+
+<fieldset id="interface_field" class="cbi-section">
+ <legend><%:MWAN Interfaces%></legend>
+ <div id="mwan_status_text">
+ <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
+ <%:Collecting data...%>
+ </div>
+</fieldset>
diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm
new file mode 100644
index 0000000000..12700c4a36
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm
@@ -0,0 +1,39 @@
+<%#
+ Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+ Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+ Licensed to the public under the GNU General Public License v2.
+-%>
+
+<%+header%>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
+ <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
+</ul>
+
+<script type="text/javascript">//<![CDATA[
+ XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "detailed_status")%>', null,
+ function(x)
+ {
+ var output = document.getElementById('diag-rc-output');
+ output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
+ }
+ );
+//]]></script>
+
+<div class="cbi-map">
+ <h2 name="content"><%:MWAN Status - Detail%></h2>
+ <%if not require("luci.sys").init.enabled("mwan3") then%>
+ <div><strong><%:INFO: MWAN not running%></strong></div>
+ <%end%>
+ <fieldset class="cbi-section">
+ <span id="diag-rc-output">
+ <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
+ <%:Collecting data...%>
+ </span>
+ </fieldset>
+</div>
+
+<%+footer%>
diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm
new file mode 100644
index 0000000000..454adcc1cd
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm
@@ -0,0 +1,90 @@
+<%#
+ Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+ Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+ Licensed to the public under the GNU General Public License v2.
+-%>
+
+<%+header%>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
+ <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
+</ul>
+
+<%
+ local uci = require "luci.model.uci"
+
+ local iface = {}
+
+ uci.cursor():foreach("mwan3", "interface",
+ function (section)
+ table.insert(iface, section[".name"])
+ end
+ )
+%>
+
+<script type="text/javascript">//<![CDATA[
+ var stxhr = new XHR();
+
+ function update_status(iface, task)
+ {
+ var output = document.getElementById('diag-rc-output');
+
+ output.innerHTML =
+ '<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />' +
+ "<%:Waiting for command to complete...%>"
+ ;
+
+ output.parentNode.style.display = 'block';
+ output.style.display = 'inline';
+
+ stxhr.post('<%=url('admin/status/mwan')%>/diagnostics_display' + '/' + iface + '/' + task, { token: '<%=token%>' },
+ function(x)
+ {
+ output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
+ }
+ );
+ }
+//]]></script>
+
+<form method="post" action="<%=url('admin/network/diagnostics')%>">
+ <div class="cbi-map">
+ <h2 name="content"><%:MWAN Status - Diagnostics%></h2>
+ <%if not require("luci.sys").init.enabled("mwan3") then%>
+ <div><strong><%:INFO: MWAN not running%></strong></div>
+ <%end%>
+ <fieldset class="cbi-section">
+ <br />
+
+ <div style="width:30%; float:left">
+ <label class="cbi-value-title"><%:Interface%></label>
+ <select name="iface" style="width:auto">
+ <% for _, z in ipairs(iface) do -%><option value="<%=z%>"><%=z%></option><%- end %>
+ </select>
+ </div>
+
+ <div style="width:30%; float:left">
+ <label class="cbi-value-title"><%:Task%></label>
+ <select name="task" style="width:auto">
+ <option value="ping_gateway"><%:Ping default gateway%></option>
+ <option value="ping_trackips"><%:Ping tracking IP%></option>
+ <option value="check_rules"><%:Check IP rules%></option>
+ <option value="check_routes"><%:Check routing table%></option>
+ <option value="hotplug_ifup"><%:Hotplug ifup%></option>
+ <option value="hotplug_ifdown"><%:Hotplug ifdown%></option>
+ </select>
+ </div>
+
+ <div style="width:30%; float:left">
+ <input type="button" value="<%:Execute%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.iface.value, this.form.task.value)"/>
+ </div>
+ </fieldset>
+ </div>
+ <fieldset class="cbi-section" style="display:none">
+ <span id="diag-rc-output"></span>
+ </fieldset>
+</form>
+
+<%+footer%>
diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm
new file mode 100644
index 0000000000..962cde521d
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm
@@ -0,0 +1,20 @@
+<%#
+ Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+ Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+ Licensed to the public under the GNU General Public License v2.
+-%>
+
+<%+header%>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
+</ul>
+
+
+<div class="cbi-map">
+ <%+mwan/overview_status_interface%>
+</div>
+<%+footer%>
diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm
new file mode 100644
index 0000000000..a20516bd2a
--- /dev/null
+++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm
@@ -0,0 +1,39 @@
+<%#
+ Copyright 2014 Aedan Renner <chipdankly@gmail.com>
+ Copyright 2018 Florian Eckert <fe@dev.tdt.de>
+ Licensed to the public under the GNU General Public License v2.
+-%>
+
+<%+header%>
+
+<ul class="cbi-tabmenu">
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
+ <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
+ <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
+</ul>
+
+<script type="text/javascript">//<![CDATA[
+ XHR.poll(15, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "troubleshooting_display")%>', null,
+ function(x)
+ {
+ var output = document.getElementById('diag-rc-output');
+ output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
+ }
+ );
+//]]></script>
+
+<div class="cbi-map">
+ <h2 name="content"><%:MWAN Status - Troubleshooting%></h2>
+ <%if not require("luci.sys").init.enabled("mwan3") then%>
+ <div><strong><%:INFO: MWAN not running%></strong></div>
+ <%end%>
+ <fieldset class="cbi-section">
+ <span id="diag-rc-output">
+ <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
+ <%:Collecting data...%>
+ </span>
+ </fieldset>
+</div>
+
+<%+footer%>