diff --git a/applications/luci-app-rtbwmon/Makefile b/applications/luci-app-rtbwmon/Makefile new file mode 100644 index 0000000..33cdf40 --- /dev/null +++ b/applications/luci-app-rtbwmon/Makefile @@ -0,0 +1,15 @@ + + +include $(TOPDIR)/rules.mk + +PKG_VERSION:=1.0.0-1 +PKG_RELEASE:= +PKG_MAINTAINER:=jjm2473 + +LUCI_TITLE:=LuCI realtime client bandwidth monitor +LUCI_PKGARCH:=all +LUCI_DEPENDS:=+iptables + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/applications/luci-app-rtbwmon/README.md b/applications/luci-app-rtbwmon/README.md new file mode 100644 index 0000000..b0bce7b --- /dev/null +++ b/applications/luci-app-rtbwmon/README.md @@ -0,0 +1 @@ +LuCI realtime traffic monitor, inspired by luci-app-wrtbwmon \ No newline at end of file diff --git a/applications/luci-app-rtbwmon/htdocs/luci-static/rtbwmon/rtbwmon.js b/applications/luci-app-rtbwmon/htdocs/luci-static/rtbwmon/rtbwmon.js new file mode 100644 index 0000000..d2c188a --- /dev/null +++ b/applications/luci-app-rtbwmon/htdocs/luci-static/rtbwmon/rtbwmon.js @@ -0,0 +1,526 @@ + +(function () { + const numberCol = 3; + const wrt = { + // variables for auto-update, interval is in seconds + scheduleTimeout: undefined, + interval: 5, + // option on whether to show per host sub-totals + perHostTotals: false, + paused: false, + headers: [], + // variables for sorting + sortData: { + column: numberCol, + elId: 'thDlb', + dir: 'desc', + }, + filter: '', + ifaceFilter: '', + cache: {}, + }; + + let oldDate, oldValues, oldValuesSeconds; + const basePath = "/cgi-bin/luci/admin/status/rtbwmon" + //---------------------- + // HELPER FUNCTIONS + //---------------------- + + /** + * Human readable text for size + * @param size + * @returns {string} + */ + const getSize = function(size, suffix) { + let prefix = [' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']; + let precision, base = 1000, pos = 0; + while (size > base) { + size /= base; + pos++; + } + if (pos > 2) precision = 1000; else precision = 1; + return (Math.round(size * precision) / precision) + ' ' + prefix[pos] + suffix; + }; + + /** + * Human readable text for date + * @param date + * @returns {string} + */ + const dateToString = function(date) { + return date.toString().substring(0, 24); + }; + + /** + * Gets the string representation of the date received from BE + * @param value + * @returns {*} + */ + const getDateString = function(value) { + let tmp = value.split('_'), + str = tmp[0].split('-').reverse().join('-') + 'T' + tmp[1]; + return dateToString(new Date(str)); + }; + + /** + * Create a `tr` element with content + * @param content + * @returns {string} + */ + const createTR = function(content) { + let res = document.createElement('tr'); + res.classList.add("tr"); + res.replaceChildren(...content) + return res; + }; + + /** + * Create a `td` element with content and options + * @param content + * @param opts + * @returns {string} + */ + const createTD = function(content, opts) { + opts = opts || {}; + let res = document.createElement('td'); + if (opts.right) { + res.align="right"; + } + if (opts.dataTitle) { + res.setAttribute("data-title", opts.dataTitle); + } + res.classList.add("td"); + if (opts.title) { + res.title=opts.title; + res.classList.add("more_info"); + } + res.innerHTML = content; + return res; + }; + + const createTH = function(content, opts) { + opts = opts || {}; + let res = document.createElement('th'); + if (opts.right) { + res.align = "right"; + } + if (opts.id) { + res.id = opts.id; + } + res.classList.add("th"); + res.innerHTML = content; + return res; + }; + + /** + * Returns true if obj is instance of Array + * @param obj + * @returns {boolean} + */ + const isArray = function(obj) { + return obj instanceof Array; + }; + + //---------------------- + // END HELPER FUNCTIONS + //---------------------- + + // UI + // TABLE + const rowToTr = function(row) { + let iptitle = undefined; + if (wrt.perHostTotals && row[numberCol+5].length>1) { + iptitle = row[numberCol+5].join('\n'); + } + // create displayData + let displayData = [ + createTD(row[0] + '
' + row[7], {title: iptitle, dataTitle: wrt.headers[0].title}), + createTD(row[1], {dataTitle: wrt.headers[1].title}), + createTD(getSize(row[numberCol], 'Bps'), {right: true, dataTitle: wrt.headers[2].title}), + createTD(getSize(row[numberCol+1], 'pps'), {right: true, dataTitle: wrt.headers[3].title}), + createTD(getSize(row[numberCol+2], 'Bps'), {right: true, dataTitle: wrt.headers[4].title}), + createTD(getSize(row[numberCol+3], 'pps'), {right: true, dataTitle: wrt.headers[5].title}), + ]; + + // display row data + return createTR(displayData); + }; + + const filterData = function(data) { + if (wrt.filter == '') { + return data; + } + let value = wrt.filter; + return data.filter(row=> + (row[numberCol+4] && row[numberCol+4].toLowerCase().indexOf(value.toLowerCase()) > -1) || (row[0].indexOf(value) > -1) || (row[1].toLowerCase().indexOf(value.toLowerCase()) > -1) || + (wrt.perHostTotals && row[numberCol+5].length>1 && row[numberCol+5].some(ip=>ip.indexOf(value) > -1)) + ) + }; + + const filterIface = function(data) { + if (wrt.ifaceFilter == '') { + return data; + } + let value = wrt.ifaceFilter; + return data.filter(row=>value==row[2]); + }; + + /** + * Calculates per host sub-totals and adds them in the data input + * @param data The data input + */ + const aggregateHostTotals = function(data) { + if (!wrt.perHostTotals) return data; + + let m = data.reduce((m, row)=>{ + let mac = row[1]; + let ary = m[mac]; + if (ary) { + ary.push(row); + } else { + m[mac] = [row]; + } + return m; + }, {}); + let merged = []; + for (let mac in m) { + if (m.hasOwnProperty(mac)) { + let rows = m[mac]; + rows.sort(sortingFunction); + let mrow = rows[0].slice(); // clone + mrow.push([mrow[0]]); // ip s + rows.slice(1).reduce((m, row)=>{ + if (!m[numberCol+4] && row[numberCol+4]) { + m[numberCol+4] = row[numberCol+4]; // hostname + } + m[m.length-1].push(row[0]); + for (let i=0; i<4; ++i) { + m[numberCol+i] += row[numberCol+i]; + } + return m; + }, mrow); + merged.push(mrow); + } + } + return merged; + }; + + /** + * Sorting function used to sort the `data`. Uses the global sort settings + * @param x first item to compare + * @param y second item to compare + * @returns {number} 1 for desc, -1 for asc, 0 for equal + */ + const sortingFunction = function(x, y) { + // get data from global variable + let sortColumn = wrt.sortData.column, sortDirection = wrt.sortData.dir; + let a = x[sortColumn]; + let b = y[sortColumn]; + if (a === b) { + return 0; + } else if (sortDirection === 'desc') { + return a < b ? 1 : -1; + } else { + return a > b ? 1 : -1; + } + }; + + /** + * Renders the table body + * @param data + * @param totals + */ + const renderTableData = function(data) { + if (!isArray(data)) data=[]; + // sort data + data = filterData(aggregateHostTotals(filterIface(data))) + data.sort(sortingFunction); + + // display data + let table = document.getElementById('clients'); + table.replaceChildren(...data.map(rowToTr)); + }; + + // HEADER + const updateHeader = function() { + // set sorting arrows + let th = document.getElementById('theader').firstElementChild; + while(th) { + th.firstElementChild.innerHTML = " "; + th = th.nextElementSibling; + } + let el = document.getElementById(wrt.sortData.elId); + if (el) { + el.firstElementChild.innerHTML = (wrt.sortData.dir === 'desc' ? '▼' : '▲'); + } + }; + + /** + * Sets the relevant global sort variables and re-renders the table to apply the new sorting + * @param elId + * @param column + */ + const setSortColumn = function(elId, column) { + if (column === wrt.sortData.column) { + // same column clicked, switch direction + wrt.sortData.dir = wrt.sortData.dir === 'desc' ? 'asc' : 'desc'; + } else { + // change sort column + wrt.sortData.column = column; + // reset sort direction + wrt.sortData.dir = 'desc'; + } + wrt.sortData.elId = elId; + updateHeader(); + + // render table data from cache + renderTableData(wrt.cache.data); + }; + + /** + * Registers the table events handlers for sorting when clicking the column headers + */ + const registerTableEventHandlers = function() { + // note these ordinals are into the data array, not the table output + document.getElementById('thIp').addEventListener('click', function () { + setSortColumn(this.id, 0); // ip + }); + document.getElementById('thMac').addEventListener('click', function () { + setSortColumn(this.id, 1); // mac + }); + document.getElementById('thDlb').addEventListener('click', function () { + setSortColumn(this.id, numberCol); // dl speed + }); + document.getElementById('thDlp').addEventListener('click', function () { + setSortColumn(this.id, numberCol+1); // dl pps + }); + document.getElementById('thUpb').addEventListener('click', function () { + setSortColumn(this.id, numberCol+2); // ul speed + }); + document.getElementById('thUpp').addEventListener('click', function () { + setSortColumn(this.id, numberCol+3); // ul pps + }); + }; + + const initHeader = function() { + // set sorting arrows + let theader = document.getElementById('theader'); + theader.replaceChildren(...wrt.headers.map(h=>createTH(h.title, h)).map(th=>{ + th.appendChild(document.createElement("span")); + return th; + })); + }; + + // TOOLBAR + /** + * Registers DOM event listeners for user interaction + */ + const addEventListeners = function() { + document.getElementById('perHostTotals').addEventListener('change', function () { + wrt.perHostTotals = this.checked; + renderTableData(wrt.cache.data); + }); + document.getElementById('pause_checkbox').addEventListener('change', function () { + wrt.paused = this.checked; + }); + document.getElementById('iface_select').addEventListener('change', function () { + wrt.ifaceFilter = this.value; + renderTableData(wrt.cache.data); + }); + const submitFilter = function(value) { + if (wrt.filter != value) { + wrt.filter = value; + renderTableData(wrt.cache.data); + } + }; + let filterInput = document.getElementById('filter_input'); + filterInput.addEventListener('keypress', function(event){ + if (event.key === 'Enter') + submitFilter(this.value); + }); + filterInput.addEventListener('blur', function(){ + submitFilter(this.value); + }); + }; + + // model + /** + * Handle the error that happened during the call to the BE + */ + const handleError = function() { + // TODO handle errors + // let message = 'Something went wrong...'; + }; + + /** + * Handle the new `values` that were received from the BE + * @param values + * @returns {string} + */ + const handleValues = function(values) { + if (!isArray(values)) return; + + // find data and totals + let data = parseValues(values); + + // store them in cache for quicker re-rendering + wrt.cache.data = data; + + renderTableData(data); + }; + + /** + * Parses the values and returns a data array, where each element in the data array is an array with two elements, + * and a totals array, that holds aggregated values for each column. + * The first element of each row in the data array, is the HTML output of the row as a `tr` element + * and the second is the actual data: + * [ result, data ] + * @param values The `values` array + * @returns {Array} + */ + const parseValues = function(values) { + return values.map(parseValueRow).filter(a=>a!=null); + }; + + /** + * Parse each row in the `values` array and return an array with two elements. + * The first element is the HTML output of the row as a `tr` element and the second is the actual data + * [ result, data ] + * @param data A row from the `values` array + * @returns {[ string, [] ]} + */ + const parseValueRow = function(data) { + // check if data is array + if (!isArray(data)) return null; + + // find download and upload speeds + let dlSpeed = 0, upSpeed = 0; + let dlPs = 0, upPs = 0; + let seconds = oldValuesSeconds; + if (typeof(seconds) !== 'undefined') { + // find old data + let oldData; + for (let i = 0; i < oldValues.length; i++) { + let cur = oldValues[i]; + // compare mac addresses and ip addresses + if (oldValues[i][0] === data[0] && oldValues[i][1] === data[1]) { + oldData = cur; + break; + } + } + if (typeof(oldData) === 'undefined') { + // new ip + oldData = [0,0,0,0,0,0,0,0,0,0,0,0,0]; + } + upPs = Math.max(0, data[numberCol] - oldData[numberCol]) / seconds; + upSpeed = Math.max(0, data[numberCol+1] - oldData[numberCol+1]) / seconds; + dlPs = Math.max(0, data[numberCol+2] - oldData[numberCol+2]) / seconds; + dlSpeed = Math.max(0, data[numberCol+3] - oldData[numberCol+3]) / seconds; + } + + // create rowData [ip, mac, iface, dlSpeed, dlPs, upSpeed, upPs, hostname] + let rowData = [data[0], data[1], data[2], dlSpeed, dlPs, upSpeed, upPs, data[numberCol+4]]; + + return rowData; + }; + + const httpGet = function(url, cb, onerror) { + let ajax = new XMLHttpRequest(); + ajax.onreadystatechange = function () { + // noinspection EqualityComparisonWithCoercionJS + if (this.readyState === XMLHttpRequest.DONE) { + cb(this.status, this.responseText); + } + }; + ajax.open('GET', url, true); + try { + ajax.send(); + } catch (err) { + onerror && onerror(err) + } + }; + + /** + * Fetches and handles the updated `values` from the BE + */ + const receiveData = function() { + if (wrt.paused) { + reschedule(); + return + } + httpGet(basePath + '/data?t='+parseInt(new Date().getTime()/1000), function (status, responseText) { + if (status == 200) { + if (!wrt.paused) { + let v = responseText.trimEnd().split('\n') + .filter(line=>line).map(line=>{ + let a = line.split(','); + for (let i=0;i<4;++i) { + a[numberCol+i] = parseInt(a[numberCol+i]) + } + return a; + }); + let now = new Date().getTime(); + oldValuesSeconds = undefined; + if (typeof(oldValues) !== 'undefined') { + let seconds = (now - oldDate) / 1000; + if (seconds < 600) { + oldValuesSeconds = seconds; + } + } + handleValues(v); + // set old values + oldValues = v; + // set old date + oldDate = now; + } + reschedule(); + } + }); + }; + + //---------------------- + // AUTO-UPDATE + //---------------------- + + /** + * Start auto-update schedule + */ + const reschedule = function() { + let seconds = wrt.interval || 60; + wrt.scheduleTimeout = window.setTimeout(receiveData, seconds * 1000); + }; + + //---------------------- + // END AUTO-UPDATE + //---------------------- + + window.rtbwmon_init = function(headers){ + wrt.headers = headers; + initHeader(); + updateHeader(); + // register events + addEventListeners(); + // register table events + registerTableEventHandlers(); + // Main entry point + httpGet(basePath + '/ifaces?t='+parseInt(new Date().getTime()/1000), function (status, responseText) { + receiveData(); + let iface_select = document.getElementById('iface_select'); + let selected = iface_select.value; + let ifaces = responseText.trimEnd().split('\n').filter(line=>line).map(iface=>{ + let option = document.createElement('option'); + option.value = iface; + option.innerHTML = iface; + if (selected == iface) { + option.selected = true; + } + return option; + }); + let first = iface_select.firstElementChild; + iface_select.replaceChildren(first, ...ifaces); + }, function(err) { + alert(err); + }); + }; + +})(); diff --git a/applications/luci-app-rtbwmon/luasrc/controller/rtbwmon.lua b/applications/luci-app-rtbwmon/luasrc/controller/rtbwmon.lua new file mode 100644 index 0000000..bda932b --- /dev/null +++ b/applications/luci-app-rtbwmon/luasrc/controller/rtbwmon.lua @@ -0,0 +1,17 @@ +module("luci.controller.rtbwmon", package.seeall) + +function index() + entry({"admin", "status", "rtbwmon"}, template("rtbwmon/rtbwmon"), _("Realtime Bandwidth"), 90) + entry({"admin", "status", "rtbwmon", "data"}, call("data")) + entry({"admin", "status", "rtbwmon", "ifaces"}, call("ifaces")) +end + +function data() + luci.http.prepare_content("text/csv") + luci.http.write(luci.sys.exec("/usr/libexec/rtbwmon.sh update")) +end + +function ifaces() + luci.http.prepare_content("text/csv") + luci.http.write(luci.sys.exec("/usr/libexec/rtbwmon.sh ifaces")) +end diff --git a/applications/luci-app-rtbwmon/luasrc/view/rtbwmon/rtbwmon.htm b/applications/luci-app-rtbwmon/luasrc/view/rtbwmon/rtbwmon.htm new file mode 100644 index 0000000..9794a87 --- /dev/null +++ b/applications/luci-app-rtbwmon/luasrc/view/rtbwmon/rtbwmon.htm @@ -0,0 +1,49 @@ +<%+header%> +
+

<%:Realtime Bandwidth%>

+
<%:Display the network speed of the client, and only count the external traffic%>
+
+ + + + + + +
+
+ + + + + + + +
+ +
+
+ + + +<%+footer%> diff --git a/applications/luci-app-rtbwmon/po/zh-cn/rtbwmon.po b/applications/luci-app-rtbwmon/po/zh-cn/rtbwmon.po new file mode 100644 index 0000000..235d33b --- /dev/null +++ b/applications/luci-app-rtbwmon/po/zh-cn/rtbwmon.po @@ -0,0 +1,41 @@ +msgid "Realtime Bandwidth" +msgstr "实时流量" + +msgid "Display the network speed of the client, and only count the external traffic" +msgstr "显示客户端网速,只统计外连流量" + +msgid "Pause refresh" +msgstr "暂停刷新" + +msgid "Only display clients of specific network interface" +msgstr "只显示特定网络接口的客户端" + +msgid "Interface..." +msgstr "接口..." + +msgid "Merge by MAC address" +msgstr "按MAC地址合并" + +msgid "Filter..." +msgstr "过滤..." + +msgid "Filter the data according to the hostname, IP, MAC" +msgstr "按主机名、IP、MAC过滤数据" + +msgid "Client IP" +msgstr "客户端 IP" + +msgid "Client MAC" +msgstr "客户端 MAC" + +msgid "Download speed" +msgstr "下载速度" + +msgid "Download packets" +msgstr "下载包" + +msgid "Upload speed" +msgstr "上传速度" + +msgid "Upload packets" +msgstr "上传包" diff --git a/applications/luci-app-rtbwmon/root/etc/init.d/rtbwmon b/applications/luci-app-rtbwmon/root/etc/init.d/rtbwmon new file mode 100755 index 0000000..4d31dee --- /dev/null +++ b/applications/luci-app-rtbwmon/root/etc/init.d/rtbwmon @@ -0,0 +1,13 @@ +#!/bin/sh /etc/rc.common + +USE_PROCD=1 + +boot() { + return 0 +} + +start_service() { + procd_open_instance + procd_set_param command /usr/libexec/rtbwmon.sh gc + procd_close_instance +} diff --git a/applications/luci-app-rtbwmon/root/usr/libexec/rtbwmon.sh b/applications/luci-app-rtbwmon/root/usr/libexec/rtbwmon.sh new file mode 100755 index 0000000..ca172cc --- /dev/null +++ b/applications/luci-app-rtbwmon/root/usr/libexec/rtbwmon.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +lookup() { + local MAC=$1 + local IP=$2 + local USERSFILE + local USER + for USERSFILE in /tmp/dhcp.leases /tmp/hosts /tmp/dnsmasq.conf /etc/dnsmasq.conf /etc/hosts; do + [ -e "$USERSFILE" ] || continue + case $USERSFILE in + /tmp/dhcp.leases) + USER=$(grep -i "$MAC" $USERSFILE | cut -f4 -s -d' ') + ;; + /etc/hosts) + USER=$(grep "^$IP " $USERSFILE | cut -f2 -s -d' ') + ;; + /tmp/hosts) + USER=$(grep -rhm1 "^$IP " $USERSFILE | head -1 | cut -f2 -s -d' ') + ;; + *) + USER=$(grep -i "$MAC" "$USERSFILE" | cut -f2 -s -d,) + ;; + esac + [ "$USER" = "*" ] && USER= + [ -n "$USER" ] && break + done + [ -z "$USER" ] && return 1 + echo $USER +} + +get_wan_iface() { + tail -n +2 /proc/net/route | sed -n -e 's/^\([^\t]\+\)\t00000000\t[^\t]\+\t[^\t]\+\t[^\t]\+\t[^\t]\+\t[^\t]\+\t00000000\t.*$/\1/p' +} + +get_arp_excluded() { + tail -n +2 /proc/net/arp | grep -v " ${1//\./\\\.}\$" | sed -n -e 's/^\([^ ]\+\) \+0x[^ ]\+ \+0x2 \+\([^ ]\+\) .* \([^ ]\+\)$/\1\t\2\t\3/p' +} + +merge() { + local arpfile="$1" + local countfile="$2" + local outfile="$3" + local pkts bytes src dest ip mac iface up down + while read pkts bytes src dest; do + if [[ "$dest" = '0.0.0.0/0' ]]; then + eval "local up_${src//[.:]/_}=\"$pkts,$bytes\"" + else + eval "local down_${dest//[.:]/_}=\"$pkts,$bytes\"" + fi + done < "$countfile" + while read ip mac iface; do + eval "up=\$up_${ip//[.:]/_}" + eval "down=\$down_${ip//[.:]/_}" + printf "%s,%s,%s,%s,%s,%s\n" "$ip" "$mac" "$iface" "${up:-0,0}" "${down:-0,0}" "`lookup $mac $ip`" + done < "$arpfile" > "$outfile" +} + +do_clean() { + iptables -t mangle -D FORWARD -j RTBWMON_IFACE 2>/dev/null + iptables -t mangle -F RTBWMON_IFACE 2>/dev/null + iptables -t mangle -F RTBWMON_IP 2>/dev/null + iptables -t mangle -X RTBWMON_IFACE 2>/dev/null + iptables -t mangle -X RTBWMON_IP 2>/dev/null + rm -f /var/run/rtbwmon.tmp.* /var/run/rtbwmon.csv +} + +do_update() { + local ip + local INTERFACE="$1" + + find /var/run/rtbwmon.csv -mmin +30 2>/dev/null | grep -q . && do_clean + + # init iptable + iptables -t mangle -C FORWARD -j RTBWMON_IFACE 2>/dev/null || { + iptables -t mangle -N RTBWMON_IFACE 2>/dev/null + iptables -t mangle -N RTBWMON_IP 2>/dev/null + iptables -t mangle -I FORWARD -j RTBWMON_IFACE + # iptables -t mangle -I FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j RTBWMON_IFACE + } + + # if interface changed, clean chain + iptables -t mangle -C RTBWMON_IFACE -o "$INTERFACE" -j RTBWMON_IP 2>/dev/null || { + iptables -t mangle -F RTBWMON_IP + iptables -t mangle -F RTBWMON_IFACE + # iptables -t mangle -A RTBWMON_IFACE -m addrtype --dst-type LOCAL -j RETURN + iptables -t mangle -A RTBWMON_IFACE -i "$INTERFACE" -j RTBWMON_IP + iptables -t mangle -A RTBWMON_IFACE -o "$INTERFACE" -j RTBWMON_IP + } + + # schedule cleaning task + /etc/init.d/rtbwmon start + + # save system state + iptables -t mangle -nvxL RTBWMON_IP | tail -n +3 | grep -Fv 'Zeroing chain' | sed -e 's/ \+/\t/g' | cut -f2,3,9,10 >/var/run/rtbwmon.tmp.count + get_arp_excluded "$INTERFACE" >/var/run/rtbwmon.tmp.arp + + # get ip + cut -f3 /var/run/rtbwmon.tmp.count | grep -Fv '0.0.0.0/0' >/var/run/rtbwmon.tmp.oips + cut -f1 /var/run/rtbwmon.tmp.arp >/var/run/rtbwmon.tmp.nips + + # delete offline ip + grep -Fvf /var/run/rtbwmon.tmp.nips /var/run/rtbwmon.tmp.oips | while read ip; do + iptables -t mangle -D RTBWMON_IP -s "$ip" -j RETURN + iptables -t mangle -D RTBWMON_IP -d "$ip" -j RETURN + done + + # add new ip + grep -Fvf /var/run/rtbwmon.tmp.oips /var/run/rtbwmon.tmp.nips | while read ip; do + iptables -t mangle -A RTBWMON_IP -s "$ip" -j RETURN + iptables -t mangle -A RTBWMON_IP -d "$ip" -j RETURN + done + + merge /var/run/rtbwmon.tmp.arp /var/run/rtbwmon.tmp.count /var/run/rtbwmon.csv + + rm -f /var/run/rtbwmon.tmp.* + + return 0 +} + +update() { + local WAN_INTERFACE=`get_wan_iface` + + exec 1000>/var/run/rtbwmon.lock + flock -n 1000 2>/dev/null || { + flock 1000 2>/dev/null + [ -f /var/run/rtbwmon.csv ] && { + cat /var/run/rtbwmon.csv + flock -u 1000 2>/dev/null + return 1 + } + } + + if [ -z "$WAN_INTERFACE" ]; then + do_clean + > /var/run/rtbwmon.csv + else + do_update "$WAN_INTERFACE" 2>/dev/null + cat /var/run/rtbwmon.csv + fi + flock -u 1000 2>/dev/null + return 0 +} + +clean() { + exec 1000>/var/run/rtbwmon.lock + flock 1000 + do_clean + flock -u 1000 +} + +run_gc() { + local pid + exec 1001>/var/run/rtbwmon_gc.lock + flock -n 1001 2>/dev/null || return 0 + while :; do + sleep 360 /dev/null 2>&1 1000>/dev/null 1001>/dev/null & + pid=$! + trap "kill $pid;trap TERM;kill -TERM $$" TERM + wait $pid + trap TERM + if ! find /var/run/rtbwmon.csv -mmin -5 2>/dev/null | grep -q .; then + break + fi + done + clean + flock -u 1001 + return 0 +} + +show_ifaces() { + local WAN_INTERFACE=`get_wan_iface` + [ -z "$WAN_INTERFACE" ] && return 1 + ip addr show scope global up | grep '^ \+inet ' | sed -n -e 's/^.* \([^ ]\+\)$/\1/p' | grep -Fv "$WAN_INTERFACE" | sort -u +} + +case $1 in +"clean") + clean + ;; +"update") + update + ;; +"ifaces") + show_ifaces + ;; +"gc") + run_gc + ;; +*) + echo \ + "Usage: $0 {update|clean|ifaces} +Actions: + update update and get + clean clean iptables and temp files + ifaces show up interfaces +" + ;; +esac