diff options
Diffstat (limited to 'applications/luci-app-nft-qos/luasrc')
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%> |