summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-nft-qos/luasrc
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-nft-qos/luasrc')
-rw-r--r--applications/luci-app-nft-qos/luasrc/controller/nft-qos.lua51
-rw-r--r--applications/luci-app-nft-qos/luasrc/model/cbi/nft-qos/nft-qos.lua229
-rw-r--r--applications/luci-app-nft-qos/luasrc/view/nft-qos/rate.htm167
3 files changed, 447 insertions, 0 deletions
diff --git a/applications/luci-app-nft-qos/luasrc/controller/nft-qos.lua b/applications/luci-app-nft-qos/luasrc/controller/nft-qos.lua
new file mode 100644
index 000000000..42043505d
--- /dev/null
+++ b/applications/luci-app-nft-qos/luasrc/controller/nft-qos.lua
@@ -0,0 +1,51 @@
+-- Copyright 2018 Rosy Song <rosysong@rosinson.com>
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.nft-qos", package.seeall)
+
+function index()
+ if not nixio.fs.access("/etc/config/nft-qos") then
+ return
+ end
+
+ entry({"admin", "status", "realtime", "rate"},
+ template("nft-qos/rate"), _("Rate"), 5).leaf = true
+ entry({"admin", "status", "realtime", "rate_status"},
+ call("action_rate")).leaf = true
+ entry({"admin", "services", "nft-qos"}, cbi("nft-qos/nft-qos"),
+ _("Qos over Nftables"), 60)
+end
+
+function _action_rate(rv, n)
+ local c = io.popen("nft list chain inet nft-qos-monitor " .. n .. " 2>/dev/null")
+ if c then
+ for l in c:lines() do
+ local _, i, p, b = l:match('^%s+ip ([^%s]+) ([^%s]+) counter packets (%d+) bytes (%d+)')
+ if i and p and b then
+ -- handle expression
+ local r = {
+ rule = {
+ family = "inet",
+ table = "nft-qos-monitor",
+ chain = n,
+ handle = 0,
+ expr = {
+ { match = { right = i } },
+ { counter = { packets = p, bytes = b } }
+ }
+ }
+ }
+ rv[#rv + 1] = r
+ end
+ end
+ c:close()
+ end
+end
+
+function action_rate()
+ luci.http.prepare_content("application/json")
+ local data = { nftables = {} }
+ _action_rate(data.nftables, "upload")
+ _action_rate(data.nftables, "download")
+ luci.http.write_json(data)
+end
diff --git a/applications/luci-app-nft-qos/luasrc/model/cbi/nft-qos/nft-qos.lua b/applications/luci-app-nft-qos/luasrc/model/cbi/nft-qos/nft-qos.lua
new file mode 100644
index 000000000..61a6d76a7
--- /dev/null
+++ b/applications/luci-app-nft-qos/luasrc/model/cbi/nft-qos/nft-qos.lua
@@ -0,0 +1,229 @@
+-- Copyright 2018 Rosy Song <rosysong@rosinson.com>
+-- Licensed to the public under the Apache License 2.0.
+
+local uci = require("luci.model.uci").cursor()
+local wa = require("luci.tools.webadmin")
+local fs = require("nixio.fs")
+local ipc = require("luci.ip")
+
+local def_rate_dl = uci:get("nft-qos", "default", "static_rate_dl")
+local def_rate_ul = uci:get("nft-qos", "default", "static_rate_ul")
+local def_unit_dl = uci:get("nft-qos", "default", "static_unit_dl")
+local def_unit_ul = uci:get("nft-qos", "default", "static_unit_ul")
+
+local def_up = uci:get("nft-qos", "default", "dynamic_bw_up")
+local def_down = uci:get("nft-qos", "default", "dynamic_bw_down")
+
+local limit_enable = uci:get("nft-qos", "default", "limit_enable")
+local limit_type = uci:get("nft-qos", "default", "limit_type")
+local enable_priority = uci:get("nft-qos", "default", "priority_enable")
+
+local has_ipv6 = fs.access("/proc/net/ipv6_route")
+
+m = Map("nft-qos", translate("Qos over Nftables"))
+
+--
+-- Taboptions
+--
+s = m:section(TypedSection, "default", translate("NFT-QoS Settings"))
+s.addremove = false
+s.anonymous = true
+
+s:tab("limit", "Limit Rate")
+s:tab("priority", "Traffic Priority")
+
+--
+-- Static
+--
+o = s:taboption("limit", Flag, "limit_enable", translate("Limit Enable"), translate("Enable Limit Rate Feature"))
+o.default = limit_enable or o.enabled
+o.rmempty = false
+
+o = s:taboption("limit", ListValue, "limit_type", translate("Limit Type"), translate("Type of Limit Rate"))
+o.default = limit_static or "static"
+o:depends("limit_enable","1")
+o:value("static", "Static")
+o:value("dynamic", "Dynamic")
+
+o = s:taboption("limit", Value, "static_rate_dl", translate("Default Download Rate"), translate("Default value for download rate"))
+o.datatype = "uinteger"
+o.default = def_rate_dl or '50'
+o:depends("limit_type","static")
+
+o = s:taboption("limit", ListValue, "static_unit_dl", translate("Default Download Unit"), translate("Default unit for download rate"))
+o.default = def_unit_dl or "kbytes"
+o:depends("limit_type","static")
+o:value("bytes", "Bytes/s")
+o:value("kbytes", "KBytes/s")
+o:value("mbytes", "MBytes/s")
+
+o = s:taboption("limit", Value, "static_rate_ul", translate("Default Upload Rate"), translate("Default value for upload rate"))
+o.datatype = "uinteger"
+o.default = def_rate_ul or '50'
+o:depends("limit_type","static")
+
+o = s:taboption("limit", ListValue, "static_unit_ul", translate("Default Upload Unit"), translate("Default unit for upload rate"))
+o.default = def_unit_ul or "kbytes"
+o:depends("limit_type","static")
+o:value("bytes", "Bytes/s")
+o:value("kbytes", "KBytes/s")
+o:value("mbytes", "MBytes/s")
+
+--
+-- Dynamic
+--
+o = s:taboption("limit", Value, "dynamic_bw_down", translate("Download Bandwidth (Mbps)"), translate("Default value for download bandwidth"))
+o.default = def_up or '100'
+o.datatype = "uinteger"
+o:depends("limit_type","dynamic")
+
+o = s:taboption("limit", Value, "dynamic_bw_up", translate("Upload Bandwidth (Mbps)"), translate("Default value for upload bandwidth"))
+o.default = def_down or '100'
+o.datatype = "uinteger"
+o:depends("limit_type","dynamic")
+
+o = s:taboption("limit", Value, "dynamic_cidr", translate("Target Network (IPv4/MASK)"), translate("Network to be apply, e.g. 192.168.1.0/24, 10.2.0.0/16, etc"))
+o.datatype = "cidr4"
+ipc.routes({ family = 4, type = 1 }, function(rt) o.default = rt.dest end)
+o:depends("limit_type","dynamic")
+
+if has_ipv6 then
+ o = s:taboption("limit", Value, "dynamic_cidr6", translate("Target Network6 (IPv6/MASK)"), translate("Network to be apply, e.g. AAAA::BBBB/64, CCCC::1/128, etc"))
+ o.datatype = "cidr6"
+ o:depends("limit_type","dynamic")
+end
+
+o = s:taboption("limit", DynamicList, "limit_whitelist", translate("White List for Limit Rate"))
+o.datatype = "ipaddr"
+o:depends("limit_enable","1")
+
+--
+-- Priority
+--
+o = s:taboption("priority", Flag, "priority_enable", translate("Enable Traffic Priority"), translate("Enable this feature"))
+o.default = enable_priority or o.enabled
+o.rmempty = false
+
+o = s:taboption("priority", ListValue, "priority_netdev", translate("Default Network Interface"), translate("Network Interface for Traffic Shaping, e.g. br-lan, eth0.1, eth0, etc"))
+o:depends("priority_enable", "1")
+wa.cbi_add_networks(o)
+
+--
+-- Static Limit Rate - Download Rate
+--
+if limit_enable == "1" and limit_type == "static" then
+
+x = m:section(TypedSection, "download", translate("Static QoS-Download Rate"))
+x.anonymous = true
+x.addremove = true
+x.template = "cbi/tblsection"
+
+o = x:option(Value, "hostname", translate("Hostname"))
+o.datatype = "hostname"
+o.default = 'undefined'
+
+if has_ipv6 then
+ o = x:option(Value, "ipaddr", translate("IP Address(V4 / V6)"))
+else
+ o = x:option(Value, "ipaddr", translate("IP Address(V4 Only)"))
+end
+o.datatype = "ipaddr"
+if nixio.fs.access("/tmp/dhcp.leases") or nixio.fs.access("/var/dhcp6.leases") then
+ o.titleref = luci.dispatcher.build_url("admin", "status", "overview")
+end
+
+o = x:option(Value, "macaddr", translate("MAC (optional)"))
+o.rmempty = true
+o.datatype = "macaddr"
+
+o = x:option(Value, "rate", translate("Rate"))
+o.default = def_rate_dl or '50'
+o.size = 4
+o.datatype = "uinteger"
+
+o = x:option(ListValue, "unit", translate("Unit"))
+o.default = def_unit_dl or "kbytes"
+o:value("bytes", "Bytes/s")
+o:value("kbytes", "KBytes/s")
+o:value("mbytes", "MBytes/s")
+
+--
+-- Static Limit Rate - Upload Rate
+--
+y = m:section(TypedSection, "upload", translate("Static QoS-Upload Rate"))
+y.anonymous = true
+y.addremove = true
+y.template = "cbi/tblsection"
+
+o = y:option(Value, "hostname", translate("Hostname"))
+o.datatype = "hostname"
+o.default = 'undefined'
+
+if has_ipv6 then
+ o = y:option(Value, "ipaddr", translate("IP Address(V4 / V6)"))
+else
+ o = y:option(Value, "ipaddr", translate("IP Address(V4 Only)"))
+end
+o.datatype = "ipaddr"
+if nixio.fs.access("/tmp/dhcp.leases") or nixio.fs.access("/var/dhcp6.leases") then
+ o.titleref = luci.dispatcher.build_url("admin", "status", "overview")
+end
+
+o = y:option(Value, "macaddr", translate("MAC (optional)"))
+o.rmempty = true
+o.datatype = "macaddr"
+
+o = y:option(Value, "rate", translate("Rate"))
+o.default = def_rate_ul or '50'
+o.size = 4
+o.datatype = "uinteger"
+
+o = y:option(ListValue, "unit", translate("Unit"))
+o.default = def_unit_ul or "kbytes"
+o:value("bytes", "Bytes/s")
+o:value("kbytes", "KBytes/s")
+o:value("mbytes", "MBytes/s")
+
+end
+
+--
+-- Traffic Priority Settings
+--
+if enable_priority == "1" then
+
+s = m:section(TypedSection, "priority", translate("Traffic Priority Settings"))
+s.anonymous = true
+s.addremove = true
+s.template = "cbi/tblsection"
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o.default = "tcp"
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("udplite", "UDP-Lite")
+o:value("sctp", "SCTP")
+o:value("dccp", "DCCP")
+
+o = s:option(ListValue, "priority", translate("Priority"))
+o.default = "1"
+o:value("-400", "1")
+o:value("-300", "2")
+o:value("-225", "3")
+o:value("-200", "4")
+o:value("-150", "5")
+o:value("-100", "6")
+o:value("0", "7")
+o:value("50", "8")
+o:value("100", "9")
+o:value("225", "10")
+o:value("300", "11")
+
+o = s:option(Value, "service", translate("Service"), translate("e.g. https, 23, (separator is comma)"))
+o.default = '?'
+
+o = s:option(Value, "comment", translate("Comment"))
+o.default = '?'
+
+end
+
+return m
diff --git a/applications/luci-app-nft-qos/luasrc/view/nft-qos/rate.htm b/applications/luci-app-nft-qos/luasrc/view/nft-qos/rate.htm
new file mode 100644
index 000000000..5f9cb57d2
--- /dev/null
+++ b/applications/luci-app-nft-qos/luasrc/view/nft-qos/rate.htm
@@ -0,0 +1,167 @@
+<%#
+ Copyright 2018 Rosy Song <rosysong@rosinson.com>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<script type="text/javascript">//<![CDATA[
+ var bwxhr = new XHR();
+
+ var RC = { };
+ var em = 0;
+ var ec = 1;
+
+ var rate_table_dl;
+ var rate_table_ul;
+
+ function init_bytes(rl, ra) {
+ var bytes_pre;
+ var obj = { };
+ obj.chain = rl.chain;
+ obj.ipaddr = rl.expr[em].match.right;
+ obj.bytes = rl.expr[ec].counter.bytes;
+ obj.packets = rl.expr[ec].counter.packets;
+ obj.rate = 0;
+
+ if (RC[obj.chain] && RC[obj.chain][obj.ipaddr])
+ bytes_pre = RC[obj.chain][obj.ipaddr];
+ else
+ bytes_pre = 0;
+
+ obj.rate = (bytes_pre > 0) ? (obj.bytes - bytes_pre) / 3: 0;
+
+ if (!RC[obj.chain])
+ RC[obj.chain] = { };
+ RC[obj.chain][obj.ipaddr] = obj.bytes;
+
+ if (!ra[obj.chain])
+ ra[obj.chain] = [ ];
+ ra[obj.chain].push(obj);
+ } /* function init_bytes(rl, ra) */
+
+ function bytes_label(bytes) {
+ var uby = '<%:kB%>';
+ var kby = (bytes / 1024);
+
+ if (kby > 1024) {
+ uby = '<%:MB%>';
+ kby = (kby / 1024);
+ }
+
+ return String.format("%f %s", kby.toFixed(2), uby);
+ }
+
+ function print_table(tbl, rs, ra) {
+ ra.sort(function(a, b) { return b.rate - a.rate });
+ for (var i = 0; i < ra.length; i++) {
+ rs.push([
+ ra[i].ipaddr,
+ bytes_label(ra[i].rate) + '/s',
+ bytes_label(ra[i].bytes),
+ '%s Pkts.'.format(ra[i].packets),
+ ]);
+ }
+ cbi_update_table(tbl, rs, '<em><%:No information available%></em>');
+ } /* function print_table(tbl, ra) */
+
+ /* wait for SVG */
+ window.setTimeout(
+ function() {
+ if (!RC)
+ {
+ window.setTimeout(arguments.callee, 1000);
+ }
+ else
+ {
+ rate_table_dl = document.getElementById('rate_table_dl');
+ rate_table_ul = document.getElementById('rate_table_ul');
+
+ /* render datasets, start update interval */
+ XHR.poll(3, '<%=build_url("admin/status/realtime/rate_status")%>', null,
+ function(x, json)
+ {
+ var RA = {};
+ var rows_dl = [];
+ var rows_ul = [];
+
+ var rules = json.nftables;
+ for (var i = 0; i < rules.length; i++)
+ {
+ if (!rules[i].rule)
+ continue;
+ if (rules[i].rule.table != 'nft-qos-monitor')
+ continue;
+
+ var rl = rules[i].rule;
+ switch (rl.chain)
+ {
+ case 'download':
+ case 'upload': init_bytes(rl, RA); break;
+ }
+ } /* for (var i = 0; i < rules.length; i++) */
+
+ /* display the result */
+ if (RA.download) {
+ while (rate_table_dl.firstElementChild !== rate_table_dl.lastElementChild)
+ rate_table_dl.removeChild(rate_table_dl.lastElementChild);
+ print_table(rate_table_dl, rows_dl, RA.download);
+ }
+ if (RA.upload) {
+ while (rate_table_ul.firstElementChild !== rate_table_ul.lastElementChild)
+ rate_table_ul.removeChild(rate_table_ul.lastElementChild);
+ print_table(rate_table_ul, rows_ul, RA.upload);
+ }
+
+ } /* function(x, json) */
+ ); /* XHR.poll() */
+
+ XHR.run();
+ }
+ }, 1000
+ );
+//]]></script>
+
+<h2 name="content"><%:Realtime Rate%></h2>
+
+<div class="cbi-map-descr"><%:This page gives an overview over currently download/upload rate.%></div>
+
+<fieldset class="cbi-section" id="cbi-table-table">
+ <legend><%:Realtime Download Rate%></legend>
+ <div class="cbi-section-node">
+ <div class="table" id="rate_table_dl">
+ <div class="tr table-titles">
+ <div class="th col-2 hide-xs"><%:IP Address%></div>
+ <div class="th col-2"><%:Download Rate%></div>
+ <div class="th col-7"><%:Bytes Total%></div>
+ <div class="th col-7"><%:Packets Total%></div>
+ </div>
+ <div class="tr placeholder">
+ <div class="td">
+ <em><%:Collecting data...%></em>
+ </div>
+ </div>
+ </div>
+ </div>
+</fieldset>
+
+<fieldset class="cbi-section" id="cbi-table-table">
+ <legend><%:Realtime Upload Rate%></legend>
+ <div class="cbi-section-node">
+ <div class="table" id="rate_table_ul">
+ <div class="tr table-titles">
+ <div class="th col-2 hide-xs"><%:IP Address%></div>
+ <div class="th col-2"><%:Upload Rate%></div>
+ <div class="th col-7"><%:Bytes Total%></div>
+ <div class="th col-7"><%:Packets Total%></div>
+ </div>
+ <div class="tr placeholder">
+ <div class="td">
+ <em><%:Collecting data...%></em>
+ </div>
+ </div>
+ </div>
+ </div>
+</fieldset>
+
+<%+footer%>