mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 05:45:15 +08:00
🌴 Sync 2026-04-03 08:49:56
This commit is contained in:
1 parent
133898bee4
commit
6b6cae9986
27 files changed
+1571
-1687
No files matched your search
@@ -7,7 +7,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=filebrowser
|
||||
PKG_VERSION:=1.2.3-stable
|
||||
PKG_VERSION:=1.2.4-stable
|
||||
PKG_RELEASE=1
|
||||
|
||||
ifeq ($(ARCH),aarch64)
|
||||
|
||||
@@ -10,4 +10,11 @@ PKG_LICENSE:=GPLv3
|
||||
|
||||
#include $(TOPDIR)/feeds/luci/luci.mk
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/luci-app-mptcp/install
|
||||
$(call Package/luci-app-mptcp/install/default,$(1))
|
||||
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.mptcp \
|
||||
$(1)/usr/libexec/rpcd/luci.mptcp
|
||||
endef
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
|
||||
var callMptcpCheckTrace = rpc.declare({
|
||||
object: 'luci.mptcp',
|
||||
method: 'mptcp_check_trace',
|
||||
params: ['iface'],
|
||||
expect: { output: '' }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('network'),
|
||||
uci.load('openmptcprouter')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var ifaces = [];
|
||||
uci.sections('network', 'interface', function(s) {
|
||||
var name = s['.name'];
|
||||
if (name === 'loopback') return;
|
||||
var multipath = s.multipath ||
|
||||
uci.get('openmptcprouter', name, 'multipath') || 'off';
|
||||
if (multipath !== 'off')
|
||||
ifaces.push(name);
|
||||
});
|
||||
|
||||
var select = E('select', { 'class': 'cbi-input-select', 'id': 'mptcp-check-iface' },
|
||||
ifaces.map(function(n) { return E('option', { 'value': n }, [ n ]); })
|
||||
);
|
||||
|
||||
var pre = E('pre', { 'style': 'white-space:pre-wrap' });
|
||||
|
||||
var btn = E('input', {
|
||||
'type': 'button',
|
||||
'value': _('Test'),
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'click': L.bind(this._runCheck, this, select, pre)
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', {}, [ _('MPTCP Support Check') ]),
|
||||
E('div', { 'class': 'cbi-map-descr' },
|
||||
[ _('Check if MPTCP between interface and server is working.') ]),
|
||||
E('fieldset', { 'class': 'cbi-section' }, [
|
||||
E('legend', {}, [ _('Settings') ]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, [ _('Interface') ]),
|
||||
E('div', { 'class': 'cbi-value-field' }, [ select ])
|
||||
]),
|
||||
btn
|
||||
]),
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
pre,
|
||||
E('br'),
|
||||
E('i', {}, [ _('If you get "TCPOptionMPTCPCapable [...] Sender\'s Key" at the end, then MPTCP is supported. If there is a "-TCPOptionMPTCPCapable", then it\'s blocked.') ])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
_runCheck: function(select, pre) {
|
||||
var iface = select.value;
|
||||
if (!iface) return;
|
||||
|
||||
pre.textContent = _('Waiting for command to complete…');
|
||||
|
||||
return callMptcpCheckTrace(iface).then(function(text) {
|
||||
pre.textContent = (text || '').trim() || _('Error');
|
||||
}).catch(function() {
|
||||
pre.textContent = _('Error');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
|
||||
var callMptcpConnections = rpc.declare({
|
||||
object: 'luci.mptcp',
|
||||
method: 'mptcp_connections',
|
||||
expect: { output: '' }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
var pre = E('pre', { 'id': 'mptcp-connections-output', 'style': 'white-space:pre-wrap' },
|
||||
[ _('Waiting for command to complete…') ]);
|
||||
|
||||
poll.add(L.bind(this._update, this, pre), 10);
|
||||
|
||||
this._update(pre);
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [ pre ]);
|
||||
},
|
||||
|
||||
_update: function(pre) {
|
||||
return callMptcpConnections().then(function(text) {
|
||||
pre.textContent = (text || '').trim() || _('No data');
|
||||
}).catch(function() {
|
||||
pre.textContent = _('Error fetching connections data.');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
|
||||
var callMptcpFullmesh = rpc.declare({
|
||||
object: 'luci.mptcp',
|
||||
method: 'mptcp_fullmesh',
|
||||
expect: { output: '' }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
var pre = E('pre', { 'id': 'mptcp-fullmesh-output', 'style': 'white-space:pre-wrap' },
|
||||
[ _('Waiting for command to complete…') ]);
|
||||
|
||||
poll.add(L.bind(this._update, this, pre), 10);
|
||||
|
||||
this._update(pre);
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [ pre ]);
|
||||
},
|
||||
|
||||
_update: function(pre) {
|
||||
return callMptcpFullmesh().then(function(text) {
|
||||
pre.textContent = (text || '').trim() || _('(no output)');
|
||||
}).catch(function() {
|
||||
pre.textContent = _('Error fetching fullmesh data.');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
|
||||
var callMptcpMonitor = rpc.declare({
|
||||
object: 'luci.mptcp',
|
||||
method: 'mptcp_monitor',
|
||||
expect: { output: '' }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
var pre = E('pre', { 'id': 'mptcp-monitor-output', 'style': 'white-space:pre-wrap' },
|
||||
[ _('Waiting for command to complete…') ]);
|
||||
|
||||
poll.add(L.bind(this._update, this, pre), 10);
|
||||
|
||||
/* Run immediately without waiting for the first poll interval */
|
||||
this._update(pre);
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [ pre ]);
|
||||
},
|
||||
|
||||
_update: function(pre) {
|
||||
return callMptcpMonitor().then(function(text) {
|
||||
pre.textContent = (text || '').trim() || _('(no output)');
|
||||
}).catch(function() {
|
||||
pre.textContent = _('Error fetching monitor data.');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,973 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
|
||||
var callGetInterfaces = rpc.declare({
|
||||
object: 'network.interface',
|
||||
method: 'dump',
|
||||
expect: { interface: [] }
|
||||
});
|
||||
|
||||
var callMultipathBandwidth = rpc.declare({
|
||||
object: 'luci.mptcp',
|
||||
method: 'multipath_bandwidth',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
var callIfaceBandwidth = rpc.declare({
|
||||
object: 'luci.mptcp',
|
||||
method: 'interface_bandwidth',
|
||||
params: ['iface'],
|
||||
expect: { data: [] }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
STEP: 5,
|
||||
HEIGHT: 300,
|
||||
WIDTH: 760,
|
||||
|
||||
/* runtime state – reset on every tab switch */
|
||||
_state: null,
|
||||
_smaState: {},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('network'),
|
||||
callGetInterfaces()
|
||||
]);
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Helpers *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_ifaceColor: function(name) {
|
||||
var fixed = {
|
||||
'total': 'OrangeRed',
|
||||
'wan1': 'DeepSkyBlue',
|
||||
'wan2': 'SeaGreen',
|
||||
'wan3': 'PaleGreen',
|
||||
'wan4': 'PowderBlue',
|
||||
'wan5': 'Salmon',
|
||||
'wan6': 'LightGreen',
|
||||
'wan7': 'PaleTurquoise',
|
||||
'wan': 'FireBrick'
|
||||
};
|
||||
/* prefix match: wan1x should still get wan1's colour */
|
||||
var keys = Object.keys(fixed).sort(function(a, b) { return b.length - a.length; });
|
||||
for (var i = 0; i < keys.length; i++)
|
||||
if (name.indexOf(keys[i]) === 0) return fixed[keys[i]];
|
||||
/* deterministic hash fallback */
|
||||
var h = 0;
|
||||
for (var j = 0; j < name.length; j++)
|
||||
h = Math.imul(31, h) + name.charCodeAt(j) | 0;
|
||||
var c = '#';
|
||||
for (var k = 0; k < 3; k++)
|
||||
c += ('00' + ((h >> (k * 8)) & 0xFF).toString(16)).slice(-2);
|
||||
return c;
|
||||
},
|
||||
|
||||
_bwLabel: function(bytes, br) {
|
||||
if (bytes < 0) bytes = 0;
|
||||
var kby = bytes / 1024, uby = _('kB/s');
|
||||
if (kby >= 1024) { uby = _('MB/s'); kby /= 1024; }
|
||||
var kbi = bytes * 8 / 1024, ubi = _('kbit/s');
|
||||
if (kbi >= 1024) { ubi = _('Mbit/s'); kbi /= 1024; }
|
||||
return kbi.toFixed(2) + '\u202f' + ubi +
|
||||
(br ? '<br />' : ' ') +
|
||||
'(' + kby.toFixed(2) + '\u202f' + uby + ')';
|
||||
},
|
||||
|
||||
_sma: function(key, period, val) {
|
||||
if (!this._smaState[key]) this._smaState[key] = [];
|
||||
var buf = this._smaState[key];
|
||||
buf.push(val);
|
||||
if (buf.length > period) buf.shift();
|
||||
var s = 0;
|
||||
for (var i = 0; i < buf.length; i++) s += buf[i];
|
||||
return s / buf.length;
|
||||
},
|
||||
|
||||
_domSafeId: function(s) {
|
||||
return String(s == null ? '' : s).replace(/[^A-Za-z0-9_-]/g, '_');
|
||||
},
|
||||
|
||||
/* Returns [{name, label, device}] for multipath-enabled interfaces */
|
||||
_getMultipathIfaces: function(ifaceDump) {
|
||||
var devByName = {};
|
||||
(ifaceDump || []).forEach(function(ifc) {
|
||||
if (ifc['interface'] && ifc['l3_device'])
|
||||
devByName[ifc['interface']] = ifc['l3_device'];
|
||||
});
|
||||
var ifaces = [];
|
||||
uci.sections('network', 'interface', function(s) {
|
||||
var name = s['.name'];
|
||||
if (!name || name === 'loopback') return;
|
||||
var mp = s.multipath || '';
|
||||
if (mp !== 'on' && mp !== 'master' && mp !== 'backup' && mp !== 'handover') return;
|
||||
var dev = devByName[name] || s.device || s.ifname || name;
|
||||
ifaces.push({ name: name, label: s.label || name, device: dev });
|
||||
});
|
||||
return ifaces;
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* SVG construction *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_createSVG: function(id, height) {
|
||||
var NS = 'http://www.w3.org/2000/svg';
|
||||
var svg = document.createElementNS(NS, 'svg');
|
||||
svg.id = id;
|
||||
svg.setAttribute('width', '100%');
|
||||
svg.setAttribute('height', String(height));
|
||||
svg.setAttribute('viewBox', '0 0 ' + this.WIDTH + ' ' + height);
|
||||
svg.setAttribute('preserveAspectRatio', 'none');
|
||||
svg.style.cssText = 'display:block;border:1px solid #d6e2ee;border-radius:14px;background:linear-gradient(180deg,#fdfefe 0%,#eef5fb 100%);box-shadow:inset 0 1px 0 rgba(255,255,255,.9);box-sizing:border-box;';
|
||||
|
||||
var g = document.createElementNS(NS, 'g');
|
||||
g.id = id + '_g';
|
||||
svg.appendChild(g);
|
||||
|
||||
/* Scale labels at 25 / 50 / 75 % */
|
||||
[25, 50, 75].forEach(function(pct) {
|
||||
var t = document.createElementNS(NS, 'text');
|
||||
t.id = id + '_lbl' + pct;
|
||||
t.setAttribute('x', '4');
|
||||
t.setAttribute('y', String(height - Math.round(height * pct / 100) - 3));
|
||||
t.setAttribute('style', 'fill:#888;font-size:9px;');
|
||||
t.appendChild(document.createTextNode(''));
|
||||
svg.appendChild(t);
|
||||
});
|
||||
|
||||
return svg;
|
||||
},
|
||||
|
||||
_addTimeLines: function(id, width, height, step) {
|
||||
var NS = 'http://www.w3.org/2000/svg';
|
||||
var g = document.getElementById(id + '_g');
|
||||
if (!g) return;
|
||||
[25, 50, 75].forEach(function(pct) {
|
||||
var y = height - Math.round(height * pct / 100);
|
||||
var hline = document.createElementNS(NS, 'line');
|
||||
hline.setAttribute('x1', 0);
|
||||
hline.setAttribute('y1', y);
|
||||
hline.setAttribute('x2', width);
|
||||
hline.setAttribute('y2', y);
|
||||
hline.setAttribute('style', 'stroke:#d9e5ef;stroke-width:0.8');
|
||||
g.insertBefore(hline, g.firstChild);
|
||||
});
|
||||
for (var x = width % (step * 60); x < width; x += step * 60) {
|
||||
var line = document.createElementNS(NS, 'line');
|
||||
line.setAttribute('x1', x); line.setAttribute('y1', 0);
|
||||
line.setAttribute('x2', x); line.setAttribute('y2', height);
|
||||
line.setAttribute('style', 'stroke:#d9e5ef;stroke-width:0.8');
|
||||
g.insertBefore(line, g.firstChild);
|
||||
var txt = document.createElementNS(NS, 'text');
|
||||
txt.setAttribute('x', x + 3);
|
||||
txt.setAttribute('y', 12);
|
||||
txt.setAttribute('style', 'fill:#8ca0b4;font-size:8px;');
|
||||
txt.appendChild(document.createTextNode(Math.round((width - x) / step / 60) + 'm'));
|
||||
g.insertBefore(txt, g.firstChild);
|
||||
}
|
||||
},
|
||||
|
||||
_scaleLbl: function(id, pct, maxBytes) {
|
||||
var el = document.getElementById(id + '_lbl' + pct);
|
||||
if (el) el.textContent = this._bwLabel(maxBytes * pct / 100);
|
||||
},
|
||||
|
||||
_injectStyles: function() {
|
||||
if (document.getElementById('mptcp-bw-styles'))
|
||||
return;
|
||||
|
||||
var style = document.createElement('style');
|
||||
style.id = 'mptcp-bw-styles';
|
||||
style.textContent = [
|
||||
'.mptcp-summary-card{margin-bottom:18px;padding:18px;}',
|
||||
'.mptcp-summary-card__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px;}',
|
||||
'.mptcp-summary-card__title{font-size:17px;font-weight:700;color:#17324e;line-height:1.2;}',
|
||||
'.mptcp-summary-card__meta{margin-top:4px;font-size:11px;color:#688198;}',
|
||||
'.mptcp-summary-card__legend{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;}',
|
||||
'.mptcp-legend-pill{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;background:#fff;border:1px solid #dde7f1;font-size:11px;font-weight:700;color:#29445f;}',
|
||||
'.mptcp-legend-pill__swatch{width:10px;height:10px;border-radius:999px;display:inline-block;}',
|
||||
'.mptcp-summary-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:12px;}',
|
||||
'.mptcp-all-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:18px;align-items:start;}',
|
||||
'.mptcp-wan-card{padding:16px;}',
|
||||
'.mptcp-wan-card__header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:12px;}',
|
||||
'.mptcp-wan-card__title{font-size:15px;font-weight:700;color:#16324f;line-height:1.2;}',
|
||||
'.mptcp-wan-card__meta{margin-top:3px;font-size:11px;color:#5b728a;}',
|
||||
'.mptcp-wan-card__badge{display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;border:1px solid currentColor;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;}',
|
||||
'.mptcp-chart-block + .mptcp-chart-block{margin-top:14px;padding-top:14px;border-top:1px solid #dde7f1;}',
|
||||
'.mptcp-chart-block__title{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;font-size:12px;font-weight:700;color:#29445f;text-transform:uppercase;letter-spacing:.04em;}',
|
||||
'.mptcp-chart-block__stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:10px;}',
|
||||
'.mptcp-stat{background:#fff;border:1px solid #e2ebf3;border-radius:12px;padding:8px 10px;min-width:0;}',
|
||||
'.mptcp-stat__label{display:block;font-size:10px;font-weight:700;color:#6b8298;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;}',
|
||||
'.mptcp-stat__value{display:block;font-size:12px;color:#18344f;line-height:1.35;}',
|
||||
'.mptcp-chart-block__meta{margin-top:8px;font-size:11px;color:#6b8298;text-align:right;}',
|
||||
'.mptcp-empty-state{padding:28px;border:1px dashed #c9d7e5;border-radius:14px;background:#f8fbfd;color:#56708a;text-align:center;}',
|
||||
'#mptcp-tooltip{position:fixed;z-index:9999;pointer-events:none;background:rgba(15,23,42,.88);color:#e8f0f8;border-radius:8px;padding:8px 12px;font-size:12px;line-height:1.7;box-shadow:0 4px 16px rgba(0,0,0,.3);white-space:nowrap;display:none;}'
|
||||
].join('');
|
||||
document.head.appendChild(style);
|
||||
},
|
||||
|
||||
_getTooltip: function() {
|
||||
var tip = document.getElementById('mptcp-tooltip');
|
||||
if (!tip) {
|
||||
tip = document.createElement('div');
|
||||
tip.id = 'mptcp-tooltip';
|
||||
document.body.appendChild(tip);
|
||||
}
|
||||
return tip;
|
||||
},
|
||||
|
||||
_xToIndex: function(svgX, seriesLen) {
|
||||
if (!seriesLen) return -1;
|
||||
var startX = this.WIDTH - (seriesLen - 1) * this.STEP;
|
||||
var i = Math.round((svgX - startX) / this.STEP);
|
||||
return Math.max(0, Math.min(seriesLen - 1, i));
|
||||
},
|
||||
|
||||
_addSVGHover: function(svgId, height, getContent) {
|
||||
var self = this;
|
||||
var svg = document.getElementById(svgId);
|
||||
if (!svg) return;
|
||||
var NS = 'http://www.w3.org/2000/svg';
|
||||
var g = document.getElementById(svgId + '_g');
|
||||
var xh = document.createElementNS(NS, 'line');
|
||||
xh.setAttribute('y1', '0');
|
||||
xh.setAttribute('y2', String(height));
|
||||
xh.setAttribute('style', 'stroke:#8ca0b4;stroke-width:1;stroke-dasharray:3,2;visibility:hidden');
|
||||
if (g) g.appendChild(xh);
|
||||
svg.style.cursor = 'crosshair';
|
||||
svg.addEventListener('mousemove', function(ev) {
|
||||
var rect = svg.getBoundingClientRect();
|
||||
if (!rect.width) return;
|
||||
var svgX = (ev.clientX - rect.left) * self.WIDTH / rect.width;
|
||||
svgX = Math.max(0, Math.min(self.WIDTH, svgX));
|
||||
xh.setAttribute('x1', String(svgX));
|
||||
xh.setAttribute('x2', String(svgX));
|
||||
xh.style.visibility = 'visible';
|
||||
var html = getContent(svgX);
|
||||
var tip = self._getTooltip();
|
||||
if (html) {
|
||||
tip.innerHTML = html;
|
||||
var tx = ev.clientX + 14;
|
||||
var ty = ev.clientY - 10;
|
||||
if (tx + 200 > window.innerWidth) tx = ev.clientX - 214;
|
||||
tip.style.left = tx + 'px';
|
||||
tip.style.top = ty + 'px';
|
||||
tip.style.display = 'block';
|
||||
} else {
|
||||
tip.style.display = 'none';
|
||||
}
|
||||
});
|
||||
svg.addEventListener('mouseleave', function() {
|
||||
xh.style.visibility = 'hidden';
|
||||
var tip = document.getElementById('mptcp-tooltip');
|
||||
if (tip) tip.style.display = 'none';
|
||||
});
|
||||
},
|
||||
|
||||
_linePoints: function(series, width, height, step, scale) {
|
||||
if (!series.length) return '';
|
||||
var startX = width - (series.length - 1) * step;
|
||||
var points = '';
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
var x = startX + i * step;
|
||||
var y = height - Math.floor((series[i] || 0) * scale);
|
||||
points += (i ? ' ' : '') + x + ',' + y;
|
||||
}
|
||||
var lastY = height - Math.floor((series[series.length - 1] || 0) * scale);
|
||||
points += ' ' + width + ',' + lastY;
|
||||
return points;
|
||||
},
|
||||
|
||||
_areaPoints: function(series, width, height, step, scale) {
|
||||
if (!series.length) return '';
|
||||
var startX = width - (series.length - 1) * step;
|
||||
var points = startX + ',' + height;
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
var x = startX + i * step;
|
||||
var y = height - Math.floor((series[i] || 0) * scale);
|
||||
points += ' ' + x + ',' + y;
|
||||
}
|
||||
var lastY = height - Math.floor((series[series.length - 1] || 0) * scale);
|
||||
points += ' ' + width + ',' + lastY;
|
||||
points += ' ' + width + ',' + height + ' ' + startX + ',' + height;
|
||||
return points;
|
||||
},
|
||||
|
||||
_seriesStats: function(series) {
|
||||
var current = 0, peak = 0, sum = 0;
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
var val = series[i] || 0;
|
||||
sum += val;
|
||||
peak = Math.max(peak, val);
|
||||
}
|
||||
if (series.length)
|
||||
current = series[series.length - 1] || 0;
|
||||
return {
|
||||
current: current,
|
||||
average: series.length ? (sum / series.length) : 0,
|
||||
peak: peak
|
||||
};
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Render *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var ifaces = this._getMultipathIfaces(data[1]);
|
||||
var allTabs = [{ name: 'all', label: _('All interfaces'), device: 'all' }].concat(ifaces);
|
||||
this._allIfaces = ifaces;
|
||||
this._injectStyles();
|
||||
|
||||
var root = E('div', { 'id': 'bw-root' }, [
|
||||
E('ul', { 'class': 'cbi-tabmenu', 'id': 'bw-tabmenu' }),
|
||||
E('div', { 'id': 'bw-area' })
|
||||
]);
|
||||
|
||||
var tabmenu = root.querySelector('#bw-tabmenu');
|
||||
allTabs.forEach(function(tab) {
|
||||
var li = E('li', { 'class': tab.name === 'all' ? 'cbi-tab' : 'cbi-tab-disabled' },
|
||||
E('a', { 'href': '#', 'click': function(ev) {
|
||||
ev.preventDefault();
|
||||
self._switchTab(tab, allTabs);
|
||||
}}, tab.label)
|
||||
);
|
||||
li.id = 'bwtab-' + tab.name;
|
||||
tabmenu.appendChild(li);
|
||||
});
|
||||
|
||||
/* Register the single recurring poll; data arrives after SVG is sized */
|
||||
poll.add(L.bind(this._tick, this), 1);
|
||||
|
||||
/* Build initial chart after first layout pass */
|
||||
requestAnimationFrame(function() {
|
||||
self._switchTab(allTabs[0], allTabs);
|
||||
});
|
||||
|
||||
return root;
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Tab switching *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_switchTab: function(tab, allTabs) {
|
||||
var self = this;
|
||||
|
||||
allTabs.forEach(function(t) {
|
||||
var li = document.getElementById('bwtab-' + t.name);
|
||||
if (li) li.className = (t.name === tab.name) ? 'cbi-tab' : 'cbi-tab-disabled';
|
||||
});
|
||||
|
||||
/* Nulling _state makes _tick a no-op until the new state is ready */
|
||||
this._state = null;
|
||||
this._smaState = {};
|
||||
|
||||
var area = document.getElementById('bw-area');
|
||||
if (!area) return;
|
||||
area.innerHTML = '';
|
||||
|
||||
if (tab.name === 'all')
|
||||
this._buildAllCharts(area, this._allIfaces || []);
|
||||
else
|
||||
this._buildIfaceChart(area, tab);
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* "All interfaces" chart construction *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_buildAllCharts: function(area, ifaces) {
|
||||
var self = this;
|
||||
ifaces = ifaces || [];
|
||||
|
||||
if (!ifaces.length) {
|
||||
area.appendChild(E('div', { 'class': 'mptcp-empty-state' },
|
||||
_('No multipath WAN interface is currently available.')));
|
||||
return;
|
||||
}
|
||||
|
||||
var totalInColor = '#2f7df6';
|
||||
var totalOutColor = '#0f9d7a';
|
||||
|
||||
var legendPills = [
|
||||
E('span', { 'class': 'mptcp-legend-pill' }, [
|
||||
E('span', { 'class': 'mptcp-legend-pill__swatch', 'style': 'background:' + totalInColor }),
|
||||
_('Total in')
|
||||
]),
|
||||
E('span', { 'class': 'mptcp-legend-pill' }, [
|
||||
E('span', { 'class': 'mptcp-legend-pill__swatch', 'style': 'background:' + totalOutColor }),
|
||||
_('Total out')
|
||||
])
|
||||
];
|
||||
ifaces.forEach(function(iface) {
|
||||
var c = self._ifaceColor(iface.name);
|
||||
legendPills.push(E('span', { 'class': 'mptcp-legend-pill' }, [
|
||||
E('span', { 'class': 'mptcp-legend-pill__swatch', 'style': 'background:' + c }),
|
||||
iface.label
|
||||
]));
|
||||
});
|
||||
|
||||
var makeSummaryStats = function(prefix) {
|
||||
var cells = [];
|
||||
/* header row */
|
||||
['', _('Current'), _('Average'), _('Peak')].forEach(function(h) {
|
||||
cells.push(E('div', { 'class': 'mptcp-stat', 'style': 'background:#eef3f8;' }, [
|
||||
E('span', { 'class': 'mptcp-stat__label', 'style': 'display:block;text-align:center;' }, h)
|
||||
]));
|
||||
});
|
||||
/* one row per WAN interface */
|
||||
ifaces.forEach(function(iface) {
|
||||
var sid = self._domSafeId(iface.name);
|
||||
var c = self._ifaceColor(iface.name);
|
||||
cells.push(E('div', { 'class': 'mptcp-stat' }, [
|
||||
E('span', { 'class': 'mptcp-stat__value', 'style': 'color:' + c + ';font-weight:700;font-size:11px;' }, iface.label)
|
||||
]));
|
||||
['_cur', '_avg', '_peak'].forEach(function(suf) {
|
||||
cells.push(E('div', { 'class': 'mptcp-stat' }, [
|
||||
E('span', { 'id': prefix + '_' + sid + suf, 'class': 'mptcp-stat__value' }, '0\u202f' + _('kbit/s'))
|
||||
]));
|
||||
});
|
||||
});
|
||||
/* total row */
|
||||
cells.push(E('div', { 'class': 'mptcp-stat', 'style': 'background:#eef3f8;' }, [
|
||||
E('span', { 'class': 'mptcp-stat__value', 'style': 'font-weight:700;font-size:11px;' }, _('Total'))
|
||||
]));
|
||||
['_cur', '_avg', '_peak'].forEach(function(suf) {
|
||||
cells.push(E('div', { 'class': 'mptcp-stat', 'style': 'background:#eef3f8;' }, [
|
||||
E('span', { 'id': prefix + suf, 'class': 'mptcp-stat__value' }, '0\u202f' + _('kbit/s'))
|
||||
]));
|
||||
});
|
||||
return E('div', { 'style': 'display:grid;grid-template-columns:minmax(80px,auto) repeat(3,minmax(0,1fr));gap:6px;margin-top:10px;' }, cells);
|
||||
};
|
||||
|
||||
var combinedHeight = Math.max(190, Math.round(this.HEIGHT * 0.72));
|
||||
var totalCard = E('div', { 'class': 'mptcp-summary-card' }, [
|
||||
E('div', { 'class': 'mptcp-summary-card__header' }, [
|
||||
E('div', {}, [
|
||||
E('div', { 'class': 'mptcp-summary-card__title' }, _('All interfaces – combined view')),
|
||||
E('div', { 'class': 'mptcp-summary-card__meta' }, _('All interfaces overlaid, inbound and outbound separately'))
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-summary-card__legend' }, legendPills)
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-chart-block' }, [
|
||||
E('div', { 'class': 'mptcp-chart-block__title' }, _('Inbound – all interfaces')),
|
||||
self._createSVG('total_dn_svg', combinedHeight),
|
||||
makeSummaryStats('total_dn'),
|
||||
E('div', { 'id': 'total_dn_scale', 'class': 'mptcp-chart-block__meta' }, '-')
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-chart-block' }, [
|
||||
E('div', { 'class': 'mptcp-chart-block__title' }, _('Outbound – all interfaces')),
|
||||
self._createSVG('total_up_svg', combinedHeight),
|
||||
makeSummaryStats('total_up'),
|
||||
E('div', { 'id': 'total_up_scale', 'class': 'mptcp-chart-block__meta' }, '-')
|
||||
])
|
||||
]);
|
||||
area.appendChild(totalCard);
|
||||
|
||||
var cards = {};
|
||||
ifaces.forEach(function(iface) {
|
||||
cards[iface.name] = { color: self._ifaceColor(iface.name), label: iface.label };
|
||||
});
|
||||
|
||||
requestAnimationFrame(function() {
|
||||
var totalWidth = self.WIDTH;
|
||||
var totalHeight = Math.max(190, Math.round(self.HEIGHT * 0.72));
|
||||
var step = self.STEP;
|
||||
var wanted = Math.ceil(totalWidth / step);
|
||||
var NS = 'http://www.w3.org/2000/svg';
|
||||
var minWin = Math.round(wanted / 60);
|
||||
|
||||
self._addTimeLines('total_dn_svg', totalWidth, totalHeight, step);
|
||||
self._addTimeLines('total_up_svg', totalWidth, totalHeight, step);
|
||||
|
||||
var totalDnG = document.getElementById('total_dn_svg_g');
|
||||
var totalUpG = document.getElementById('total_up_svg_g');
|
||||
|
||||
var combinedDn = { width: totalWidth, height: totalHeight, id: 'total_dn_svg', ifaceLines: {} };
|
||||
var combinedUp = { width: totalWidth, height: totalHeight, id: 'total_up_svg', ifaceLines: {} };
|
||||
|
||||
/* Per-interface lines (behind total) */
|
||||
ifaces.forEach(function(iface) {
|
||||
var color = self._ifaceColor(iface.name);
|
||||
|
||||
var dnFill = document.createElementNS(NS, 'polyline');
|
||||
dnFill.setAttribute('style', 'fill:' + color + ';fill-opacity:0.10;stroke:none');
|
||||
if (totalDnG) totalDnG.appendChild(dnFill);
|
||||
var dnLine = document.createElementNS(NS, 'polyline');
|
||||
dnLine.setAttribute('style', 'fill:none;stroke:' + color + ';stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;opacity:0.85');
|
||||
if (totalDnG) totalDnG.appendChild(dnLine);
|
||||
combinedDn.ifaceLines[iface.name] = { fill: dnFill, line: dnLine };
|
||||
|
||||
var upFill = document.createElementNS(NS, 'polyline');
|
||||
upFill.setAttribute('style', 'fill:' + color + ';fill-opacity:0.10;stroke:none');
|
||||
if (totalUpG) totalUpG.appendChild(upFill);
|
||||
var upLine = document.createElementNS(NS, 'polyline');
|
||||
upLine.setAttribute('style', 'fill:none;stroke:' + color + ';stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;opacity:0.85');
|
||||
if (totalUpG) totalUpG.appendChild(upLine);
|
||||
combinedUp.ifaceLines[iface.name] = { fill: upFill, line: upLine };
|
||||
});
|
||||
|
||||
/* Total lines on top */
|
||||
var totalDnFill = document.createElementNS(NS, 'polyline');
|
||||
totalDnFill.setAttribute('style', 'fill:' + totalInColor + ';fill-opacity:0.18;stroke:none');
|
||||
if (totalDnG) totalDnG.appendChild(totalDnFill);
|
||||
var totalDnLine = document.createElementNS(NS, 'polyline');
|
||||
totalDnLine.setAttribute('style', 'fill:none;stroke:' + totalInColor + ';stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round');
|
||||
if (totalDnG) totalDnG.appendChild(totalDnLine);
|
||||
combinedDn.totalFill = totalDnFill;
|
||||
combinedDn.totalLine = totalDnLine;
|
||||
|
||||
var totalUpFill = document.createElementNS(NS, 'polyline');
|
||||
totalUpFill.setAttribute('style', 'fill:' + totalOutColor + ';fill-opacity:0.18;stroke:none');
|
||||
if (totalUpG) totalUpG.appendChild(totalUpFill);
|
||||
var totalUpLine = document.createElementNS(NS, 'polyline');
|
||||
totalUpLine.setAttribute('style', 'fill:none;stroke:' + totalOutColor + ';stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round');
|
||||
if (totalUpG) totalUpG.appendChild(totalUpLine);
|
||||
combinedUp.totalFill = totalUpFill;
|
||||
combinedUp.totalLine = totalUpLine;
|
||||
|
||||
var dnScaleEl = document.getElementById('total_dn_scale');
|
||||
var upScaleEl = document.getElementById('total_up_scale');
|
||||
if (dnScaleEl) dnScaleEl.textContent = '(' + minWin + ' ' + _('minutes window, 1 second interval') + ')';
|
||||
if (upScaleEl) upScaleEl.textContent = '(' + minWin + ' ' + _('minutes window, 1 second interval') + ')';
|
||||
|
||||
self._state = {
|
||||
mode: 'all',
|
||||
step: step,
|
||||
wanted: wanted,
|
||||
stamp: 0,
|
||||
stampIface: {},
|
||||
cards: cards,
|
||||
ifaces: ifaces.map(function(iface) { return iface.name; }),
|
||||
combinedDn: combinedDn,
|
||||
combinedUp: combinedUp,
|
||||
dndata: {},
|
||||
updata: {},
|
||||
totalDn: [],
|
||||
totalUp: []
|
||||
};
|
||||
ifaces.forEach(function(iface) {
|
||||
self._state.dndata[iface.name] = [];
|
||||
self._state.updata[iface.name] = [];
|
||||
self._state.stampIface[iface.name] = 0;
|
||||
});
|
||||
|
||||
/* Hover tooltips */
|
||||
self._addSVGHover('total_dn_svg', totalHeight, function(svgX) {
|
||||
var st = self._state;
|
||||
if (!st || st.mode !== 'all') return null;
|
||||
var lines = [];
|
||||
var ti = self._xToIndex(svgX, st.totalDn.length);
|
||||
if (ti >= 0) lines.push('<span style="color:#5aabff">\u25bc\u00a0' + _('Total') + ':</span> ' + self._bwLabel(st.totalDn[ti] || 0, false));
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var series = (st.dndata && st.dndata[itf]) || [];
|
||||
var j = self._xToIndex(svgX, series.length);
|
||||
if (j < 0) return;
|
||||
var c = st.cards[itf];
|
||||
lines.push('<span style="color:' + (c ? c.color : '#aaa') + '">' + (c ? c.label : itf) + ':</span> ' + self._bwLabel(series[j] || 0, false));
|
||||
});
|
||||
return lines.length ? lines.join('<br>') : null;
|
||||
});
|
||||
self._addSVGHover('total_up_svg', totalHeight, function(svgX) {
|
||||
var st = self._state;
|
||||
if (!st || st.mode !== 'all') return null;
|
||||
var lines = [];
|
||||
var ti = self._xToIndex(svgX, st.totalUp.length);
|
||||
if (ti >= 0) lines.push('<span style="color:#3ecfa8">\u25b2\u00a0' + _('Total') + ':</span> ' + self._bwLabel(st.totalUp[ti] || 0, false));
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var series = (st.updata && st.updata[itf]) || [];
|
||||
var j = self._xToIndex(svgX, series.length);
|
||||
if (j < 0) return;
|
||||
var c = st.cards[itf];
|
||||
lines.push('<span style="color:' + (c ? c.color : '#aaa') + '">' + (c ? c.label : itf) + ':</span> ' + self._bwLabel(series[j] || 0, false));
|
||||
});
|
||||
return lines.length ? lines.join('<br>') : null;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Single-interface chart construction *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_buildIfaceChart: function(area, tab) {
|
||||
var self = this;
|
||||
var color = this._ifaceColor(tab.name);
|
||||
var badgeStyle = 'background:' + color + '18;color:' + color + ';border-color:' + color + '55;';
|
||||
var chartHeight = Math.max(160, Math.round(this.HEIGHT * 0.58));
|
||||
|
||||
var makeStats = function(prefix) {
|
||||
return E('div', { 'class': 'mptcp-chart-block__stats' }, [
|
||||
E('div', { 'class': 'mptcp-stat' }, [
|
||||
E('span', { 'class': 'mptcp-stat__label' }, _('Current')),
|
||||
E('span', { 'id': prefix + '_cur', 'class': 'mptcp-stat__value' }, '0 ' + _('kbit/s'))
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-stat' }, [
|
||||
E('span', { 'class': 'mptcp-stat__label' }, _('Average')),
|
||||
E('span', { 'id': prefix + '_avg', 'class': 'mptcp-stat__value' }, '0 ' + _('kbit/s'))
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-stat' }, [
|
||||
E('span', { 'class': 'mptcp-stat__label' }, _('Peak')),
|
||||
E('span', { 'id': prefix + '_peak', 'class': 'mptcp-stat__value' }, '0 ' + _('kbit/s'))
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
var card = E('div', { 'class': 'mptcp-wan-card' }, [
|
||||
E('div', { 'class': 'mptcp-wan-card__header' }, [
|
||||
E('div', {}, [
|
||||
E('div', { 'class': 'mptcp-wan-card__title' }, tab.label),
|
||||
E('div', { 'class': 'mptcp-wan-card__meta' }, tab.device || '')
|
||||
]),
|
||||
E('span', { 'class': 'mptcp-wan-card__badge', 'style': badgeStyle }, tab.name)
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-chart-block' }, [
|
||||
E('div', { 'class': 'mptcp-chart-block__title' }, _('Inbound traffic')),
|
||||
self._createSVG('bwsvg_dn', chartHeight),
|
||||
makeStats('rx_bw'),
|
||||
E('div', { 'id': 'bwscale_dn', 'class': 'mptcp-chart-block__meta' }, '-')
|
||||
]),
|
||||
E('div', { 'class': 'mptcp-chart-block' }, [
|
||||
E('div', { 'class': 'mptcp-chart-block__title' }, _('Outbound traffic')),
|
||||
self._createSVG('bwsvg_up', chartHeight),
|
||||
makeStats('tx_bw'),
|
||||
E('div', { 'id': 'bwscale_up', 'class': 'mptcp-chart-block__meta' }, '-')
|
||||
])
|
||||
]);
|
||||
area.appendChild(card);
|
||||
|
||||
var NS = 'http://www.w3.org/2000/svg';
|
||||
['dn', 'up'].forEach(function(dir) {
|
||||
var g = document.getElementById('bwsvg_' + dir + '_g');
|
||||
var fill = document.createElementNS(NS, 'polyline');
|
||||
fill.id = 'bwsvg_' + dir + '_fill';
|
||||
fill.setAttribute('style', 'fill:' + color + ';fill-opacity:0.18;stroke:none');
|
||||
if (g) g.appendChild(fill);
|
||||
var line = document.createElementNS(NS, 'polyline');
|
||||
line.id = 'bwsvg_' + dir + '_line';
|
||||
line.setAttribute('style', 'fill:none;stroke:' + color + ';stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round');
|
||||
if (g) g.appendChild(line);
|
||||
});
|
||||
|
||||
requestAnimationFrame(function() {
|
||||
var width = self.WIDTH;
|
||||
var height = chartHeight;
|
||||
var step = self.STEP;
|
||||
var wanted = Math.ceil(width / step);
|
||||
var minWin = Math.round(wanted / 60);
|
||||
|
||||
self._addTimeLines('bwsvg_dn', width, height, step);
|
||||
self._addTimeLines('bwsvg_up', width, height, step);
|
||||
|
||||
var scDn = document.getElementById('bwscale_dn');
|
||||
var scUp = document.getElementById('bwscale_up');
|
||||
if (scDn) scDn.textContent = '(' + minWin + ' ' + _('minutes window, 1 second interval') + ')';
|
||||
if (scUp) scUp.textContent = '(' + minWin + ' ' + _('minutes window, 1 second interval') + ')';
|
||||
|
||||
self._state = {
|
||||
mode: 'iface',
|
||||
device: tab.device,
|
||||
width: width,
|
||||
height: height,
|
||||
step: step,
|
||||
wanted: wanted,
|
||||
stamp: 0,
|
||||
dataRx: [],
|
||||
dataTx: []
|
||||
};
|
||||
|
||||
self._addSVGHover('bwsvg_dn', height, function(svgX) {
|
||||
var st = self._state;
|
||||
if (!st || st.mode !== 'iface') return null;
|
||||
var i = self._xToIndex(svgX, st.dataRx.length);
|
||||
if (i < 0) return null;
|
||||
return '<span style="color:#6699ff">\u25bc\u00a0' + _('In') + ':</span> ' + self._bwLabel(st.dataRx[i] || 0, false);
|
||||
});
|
||||
self._addSVGHover('bwsvg_up', height, function(svgX) {
|
||||
var st = self._state;
|
||||
if (!st || st.mode !== 'iface') return null;
|
||||
var i = self._xToIndex(svgX, st.dataTx.length);
|
||||
if (i < 0) return null;
|
||||
return '<span style="color:#44cc88">\u25b2\u00a0' + _('Out') + ':</span> ' + self._bwLabel(st.dataTx[i] || 0, false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Poll dispatcher *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_tick: function() {
|
||||
if (!this._state) return Promise.resolve();
|
||||
if (this._state.mode === 'all') return this._pollAll();
|
||||
if (this._state.mode === 'iface') return this._pollIface();
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* "All interfaces" poll & render *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_pollAll: function() {
|
||||
var self = this;
|
||||
var st = this._state;
|
||||
|
||||
return callMultipathBandwidth().then(function(raw) {
|
||||
if (!st || st.mode !== 'all') return;
|
||||
if (!raw || typeof raw !== 'object') return;
|
||||
|
||||
/* Accept both direct payload and wrapped { data: ... } payloads */
|
||||
var payload = (raw.data && typeof raw.data === 'object') ? raw.data : raw;
|
||||
|
||||
/* Interface data can arrive as arrays, strings or wrapped arrays */
|
||||
var parsed = {};
|
||||
var keys = Object.keys(payload);
|
||||
var nextStamp = st.stamp;
|
||||
|
||||
keys.forEach(function(itf) {
|
||||
var v = payload[itf];
|
||||
if (Array.isArray(v)) {
|
||||
parsed[itf] = v;
|
||||
}
|
||||
else if (v && Array.isArray(v.data)) {
|
||||
parsed[itf] = v.data;
|
||||
}
|
||||
else if (typeof v === 'string' && v !== '') {
|
||||
try {
|
||||
var j = JSON.parse(v);
|
||||
if (Array.isArray(j))
|
||||
parsed[itf] = j;
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
|
||||
/* Append new data points using per-interface stamps so a slow/short
|
||||
interface is not permanently frozen by a faster one's stamp. */
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var d = parsed[itf];
|
||||
if (!d) return;
|
||||
var prev = st.stampIface[itf] || 0;
|
||||
for (var i = (prev ? 0 : 1); i < d.length; i++) {
|
||||
if (d[i][0] <= prev) continue;
|
||||
if (i > 0) {
|
||||
var dt = d[i][0] - d[i-1][0];
|
||||
if (dt) {
|
||||
st.dndata[itf].push(self._sma('dn_'+itf, 15, Math.max(0, (d[i][1]-d[i-1][1])/dt)));
|
||||
st.updata[itf].push(self._sma('up_'+itf, 15, Math.max(0, (d[i][3]-d[i-1][3])/dt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
st.dndata[itf] = st.dndata[itf].slice(-st.wanted);
|
||||
st.updata[itf] = st.updata[itf].slice(-st.wanted);
|
||||
if (d.length) {
|
||||
st.stampIface[itf] = d[d.length-1][0];
|
||||
nextStamp = Math.max(nextStamp, st.stampIface[itf]);
|
||||
}
|
||||
});
|
||||
|
||||
var totalSeries = parsed.total;
|
||||
if (totalSeries) {
|
||||
var prevTotal = st.stamp;
|
||||
for (var ti = (prevTotal ? 0 : 1); ti < totalSeries.length; ti++) {
|
||||
if (totalSeries[ti][0] <= prevTotal) continue;
|
||||
if (ti > 0) {
|
||||
var tdt = totalSeries[ti][0] - totalSeries[ti - 1][0];
|
||||
if (tdt) {
|
||||
st.totalDn.push(self._sma('dn_total', 15, Math.max(0, (totalSeries[ti][1] - totalSeries[ti - 1][1]) / tdt)));
|
||||
st.totalUp.push(self._sma('up_total', 15, Math.max(0, (totalSeries[ti][3] - totalSeries[ti - 1][3]) / tdt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
st.totalDn = st.totalDn.slice(-st.wanted);
|
||||
st.totalUp = st.totalUp.slice(-st.wanted);
|
||||
nextStamp = Math.max(nextStamp, totalSeries[totalSeries.length - 1][0]);
|
||||
}
|
||||
|
||||
st.stamp = nextStamp;
|
||||
|
||||
if (st.combinedDn && st.combinedUp) {
|
||||
var scDnAll, scUpAll;
|
||||
var cDn = st.combinedDn, cUp = st.combinedUp;
|
||||
|
||||
/* Align all series to globalStamp:
|
||||
1. Right-pad lagging interfaces with their last known value.
|
||||
2. Left-pad shorter histories with 0 so all start at the same x. */
|
||||
var globalStamp = st.stamp;
|
||||
function alignSeries(s, itfStamp) {
|
||||
var lag = (itfStamp > 0) ? Math.max(0, Math.round(globalStamp - itfStamp)) : 0;
|
||||
if (lag === 0) return s;
|
||||
var lastVal = s.length > 0 ? s[s.length - 1] : 0;
|
||||
var out = s.slice();
|
||||
for (var _p = 0; _p < Math.min(lag, st.wanted); _p++) out.push(lastVal);
|
||||
return out;
|
||||
}
|
||||
var alignedDn = {}, alignedUp = {};
|
||||
var refLen = 0;
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var aDn = alignSeries(st.dndata[itf] || [], st.stampIface[itf] || 0);
|
||||
var aUp = alignSeries(st.updata[itf] || [], st.stampIface[itf] || 0);
|
||||
alignedDn[itf] = aDn;
|
||||
alignedUp[itf] = aUp;
|
||||
refLen = Math.max(refLen, aDn.length, aUp.length);
|
||||
});
|
||||
function padLeft(s) {
|
||||
if (s.length >= refLen) return s;
|
||||
var z = new Array(refLen - s.length); for (var _i = 0; _i < z.length; _i++) z[_i] = 0;
|
||||
return z.concat(s);
|
||||
}
|
||||
|
||||
/* Compute total as element-wise sum of per-interface rate series.
|
||||
This avoids the cumulative-counter alignment bug where timestamps
|
||||
from different interfaces interleave, causing negative/zero rates. */
|
||||
var totalDnP = new Array(refLen);
|
||||
var totalUpP = new Array(refLen);
|
||||
for (var _k = 0; _k < refLen; _k++) { totalDnP[_k] = 0; totalUpP[_k] = 0; }
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var aDn = padLeft(alignedDn[itf] || []);
|
||||
var aUp = padLeft(alignedUp[itf] || []);
|
||||
for (var _k = 0; _k < refLen; _k++) {
|
||||
totalDnP[_k] += (aDn[_k] || 0);
|
||||
totalUpP[_k] += (aUp[_k] || 0);
|
||||
}
|
||||
});
|
||||
|
||||
var totalDnStats = self._seriesStats(totalDnP);
|
||||
var totalUpStats = self._seriesStats(totalUpP);
|
||||
var maxDnAll = Math.max(totalDnStats.peak, 1);
|
||||
var maxUpAll = Math.max(totalUpStats.peak, 1);
|
||||
scDnAll = st.combinedDn.height / (maxDnAll * 1.15);
|
||||
scUpAll = st.combinedUp.height / (maxUpAll * 1.15);
|
||||
if (cDn.totalFill) cDn.totalFill.setAttribute('points', self._areaPoints(totalDnP, cDn.width, cDn.height, st.step, scDnAll));
|
||||
if (cDn.totalLine) cDn.totalLine.setAttribute('points', self._linePoints(totalDnP, cDn.width, cDn.height, st.step, scDnAll));
|
||||
if (cUp.totalFill) cUp.totalFill.setAttribute('points', self._areaPoints(totalUpP, cUp.width, cUp.height, st.step, scUpAll));
|
||||
if (cUp.totalLine) cUp.totalLine.setAttribute('points', self._linePoints(totalUpP, cUp.width, cUp.height, st.step, scUpAll));
|
||||
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var dnSeries = padLeft(alignedDn[itf] || []);
|
||||
var upSeries = padLeft(alignedUp[itf] || []);
|
||||
var il = cDn.ifaceLines[itf];
|
||||
var ul = cUp.ifaceLines[itf];
|
||||
if (il) {
|
||||
if (il.fill) il.fill.setAttribute('points', self._areaPoints(dnSeries, cDn.width, cDn.height, st.step, scDnAll));
|
||||
if (il.line) il.line.setAttribute('points', self._linePoints(dnSeries, cDn.width, cDn.height, st.step, scDnAll));
|
||||
}
|
||||
if (ul) {
|
||||
if (ul.fill) ul.fill.setAttribute('points', self._areaPoints(upSeries, cUp.width, cUp.height, st.step, scUpAll));
|
||||
if (ul.line) ul.line.setAttribute('points', self._linePoints(upSeries, cUp.width, cUp.height, st.step, scUpAll));
|
||||
}
|
||||
});
|
||||
|
||||
/* per-interface stats update */
|
||||
st.ifaces.forEach(function(itf) {
|
||||
var sid = self._domSafeId(itf);
|
||||
var dnSer = padLeft(alignedDn[itf] || []);
|
||||
var upSer = padLeft(alignedUp[itf] || []);
|
||||
var dnSt = self._seriesStats(dnSer);
|
||||
var upSt = self._seriesStats(upSer);
|
||||
[
|
||||
{ id: 'total_dn_' + sid + '_cur', v: dnSt.current },
|
||||
{ id: 'total_dn_' + sid + '_avg', v: dnSt.average },
|
||||
{ id: 'total_dn_' + sid + '_peak', v: dnSt.peak },
|
||||
{ id: 'total_up_' + sid + '_cur', v: upSt.current },
|
||||
{ id: 'total_up_' + sid + '_avg', v: upSt.average },
|
||||
{ id: 'total_up_' + sid + '_peak', v: upSt.peak }
|
||||
].forEach(function(entry) {
|
||||
var el = document.getElementById(entry.id);
|
||||
if (el) el.innerHTML = self._bwLabel(entry.v, true);
|
||||
});
|
||||
});
|
||||
|
||||
[25, 50, 75].forEach(function(pct) {
|
||||
self._scaleLbl(cDn.id, pct, maxDnAll * 1.15);
|
||||
self._scaleLbl(cUp.id, pct, maxUpAll * 1.15);
|
||||
});
|
||||
|
||||
[
|
||||
{ id: 'total_dn_cur', v: totalDnStats.current },
|
||||
{ id: 'total_dn_avg', v: totalDnStats.average },
|
||||
{ id: 'total_dn_peak', v: totalDnStats.peak },
|
||||
{ id: 'total_up_cur', v: totalUpStats.current },
|
||||
{ id: 'total_up_avg', v: totalUpStats.average },
|
||||
{ id: 'total_up_peak', v: totalUpStats.peak }
|
||||
].forEach(function(entry) {
|
||||
var el = document.getElementById(entry.id);
|
||||
if (el) el.innerHTML = self._bwLabel(entry.v, true);
|
||||
});
|
||||
}
|
||||
}).catch(function() {});
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Single-interface poll & render *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
_pollIface: function() {
|
||||
var self = this;
|
||||
var st = this._state;
|
||||
|
||||
return callIfaceBandwidth(st.device).then(function(data) {
|
||||
if (!st || st.mode !== 'iface') return;
|
||||
if (!Array.isArray(data) || !data.length) return;
|
||||
|
||||
for (var i = (st.stamp ? 0 : 1); i < data.length; i++) {
|
||||
if (data[i][0] <= st.stamp) continue;
|
||||
if (i > 0) {
|
||||
var dt = data[i][0] - data[i-1][0];
|
||||
if (dt) {
|
||||
st.dataRx.push((data[i][1] - data[i-1][1]) / dt);
|
||||
st.dataTx.push((data[i][3] - data[i-1][3]) / dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
st.dataRx = st.dataRx.slice(-st.wanted);
|
||||
st.dataTx = st.dataTx.slice(-st.wanted);
|
||||
st.stamp = data[data.length-1][0];
|
||||
|
||||
var rxStats = self._seriesStats(st.dataRx);
|
||||
var txStats = self._seriesStats(st.dataTx);
|
||||
var maxRx = Math.max(rxStats.peak, 1);
|
||||
var maxTx = Math.max(txStats.peak, 1);
|
||||
var scRx = st.height / (maxRx * 1.15);
|
||||
var scTx = st.height / (maxTx * 1.15);
|
||||
|
||||
var dnFill = document.getElementById('bwsvg_dn_fill');
|
||||
var dnLine = document.getElementById('bwsvg_dn_line');
|
||||
var upFill = document.getElementById('bwsvg_up_fill');
|
||||
var upLine = document.getElementById('bwsvg_up_line');
|
||||
if (dnFill) dnFill.setAttribute('points', self._areaPoints(st.dataRx, st.width, st.height, st.step, scRx));
|
||||
if (dnLine) dnLine.setAttribute('points', self._linePoints(st.dataRx, st.width, st.height, st.step, scRx));
|
||||
if (upFill) upFill.setAttribute('points', self._areaPoints(st.dataTx, st.width, st.height, st.step, scTx));
|
||||
if (upLine) upLine.setAttribute('points', self._linePoints(st.dataTx, st.width, st.height, st.step, scTx));
|
||||
|
||||
[25, 50, 75].forEach(function(pct) {
|
||||
self._scaleLbl('bwsvg_dn', pct, maxRx * 1.15);
|
||||
self._scaleLbl('bwsvg_up', pct, maxTx * 1.15);
|
||||
});
|
||||
|
||||
[
|
||||
{ id: 'rx_bw_cur', v: rxStats.current },
|
||||
{ id: 'rx_bw_avg', v: rxStats.average },
|
||||
{ id: 'rx_bw_peak', v: rxStats.peak },
|
||||
{ id: 'tx_bw_cur', v: txStats.current },
|
||||
{ id: 'tx_bw_avg', v: txStats.average },
|
||||
{ id: 'tx_bw_peak', v: txStats.peak }
|
||||
].forEach(function(c) {
|
||||
var el = document.getElementById(c.id);
|
||||
if (el) el.innerHTML = self._bwLabel(c.v, true);
|
||||
});
|
||||
}).catch(function() {});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -1,245 +0,0 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
|
||||
-- Copyright 2018-2023 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module("luci.controller.mptcp", package.seeall)
|
||||
|
||||
|
||||
function index()
|
||||
local uname = nixio.uname()
|
||||
entry({"admin", "network", "mptcp"}, alias("admin", "network", "mptcp", "settings"), _("MPTCP"))
|
||||
if uname ~= nil and uname.release:sub(1,1) == "5" then
|
||||
entry({"admin", "network", "mptcp", "settings"}, cbi("mptcp"), _("Settings"),2).leaf = true
|
||||
else
|
||||
entry({"admin", "network", "mptcp", "settings"}, view("mptcp/mptcp"), _("Settings"),2).leaf = true
|
||||
end
|
||||
entry({"admin", "network", "mptcp", "bandwidth"}, template("mptcp/multipath"), _("Bandwidth"), 3).leaf = true
|
||||
entry({"admin", "network", "mptcp", "multipath_bandwidth"}, call("multipath_bandwidth")).leaf = true
|
||||
entry({"admin", "network", "mptcp", "interface_bandwidth"}, call("interface_bandwidth")).leaf = true
|
||||
if uname ~= nil and uname.release:sub(1,1) == "5" then
|
||||
entry({"admin", "network", "mptcp", "mptcp_check"}, template("mptcp/mptcp_check"), _("MPTCP Support Check"), 4).leaf = true
|
||||
end
|
||||
entry({"admin", "network", "mptcp", "mptcp_check_trace"}, post("mptcp_check_trace")).leaf = true
|
||||
entry({"admin", "network", "mptcp", "mptcp_fullmesh"}, template("mptcp/mptcp_fullmesh"), _("MPTCP Fullmesh"), 5).leaf = true
|
||||
entry({"admin", "network", "mptcp", "mptcp_fullmesh_data"}, post("mptcp_fullmesh_data")).leaf = true
|
||||
-- entry({"admin", "network", "mptcp", "mptcp_connections"}, template("mptcp/mptcp_connections"), _("Established connections"), 6).leaf = true
|
||||
entry({"admin", "network", "mptcp", "mptcp_connections_data"}, post("mptcp_connections_data")).leaf = true
|
||||
entry({"admin", "network", "mptcp", "mptcp_monitor"}, template("mptcp/mptcp_monitor"), _("MPTCP monitoring"), 6).leaf = true
|
||||
entry({"admin", "network", "mptcp", "mptcp_monitor_data"}, post("mptcp_monitor_data")).leaf = true
|
||||
end
|
||||
|
||||
function interface_bandwidth(iface)
|
||||
luci.http.prepare_content("application/json")
|
||||
local bwc = io.popen("luci-bwc -i %q 2>/dev/null" % iface)
|
||||
if bwc then
|
||||
luci.http.write("[")
|
||||
while true do
|
||||
local ln = bwc:read("*l")
|
||||
if not ln then break end
|
||||
luci.http.write(ln)
|
||||
end
|
||||
luci.http.write("]")
|
||||
bwc:close()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function string.split(input, delimiter)
|
||||
input = tostring(input)
|
||||
delimiter = tostring(delimiter)
|
||||
if (delimiter=='') then return false end
|
||||
local pos,arr = 0, {}
|
||||
-- for each divider found
|
||||
for st,sp in function() return string.find(input, delimiter, pos, true) end do
|
||||
table.insert(arr, string.sub(input, pos, st - 1))
|
||||
pos = sp + 1
|
||||
end
|
||||
table.insert(arr, string.sub(input, pos))
|
||||
return arr
|
||||
end
|
||||
|
||||
function multipath_bandwidth()
|
||||
local result = { };
|
||||
local uci = luci.model.uci.cursor()
|
||||
local res={ };
|
||||
local str="";
|
||||
local tmpstr="";
|
||||
|
||||
uci:foreach("network", "interface", function(s)
|
||||
local intname = s[".name"]
|
||||
local label = s["label"]
|
||||
local dev = get_device(intname)
|
||||
if dev == "" then
|
||||
dev = get_device(s["device"])
|
||||
if dev == "" then
|
||||
dev = get_device(s["ifname"])
|
||||
end
|
||||
end
|
||||
local multipath = s["multipath"] or ""
|
||||
if dev ~= "lo" and dev ~= "" then
|
||||
if multipath == "" then
|
||||
multipath = uci:get("openmptcprouter", intname, "multipath") or ""
|
||||
end
|
||||
if multipath == "" then
|
||||
multipath = "off"
|
||||
end
|
||||
if multipath == "on" or multipath == "master" or multipath == "backup" or multipath == "handover" then
|
||||
local bwc = luci.sys.exec("luci-bwc -i %q 2>/dev/null" % dev) or ""
|
||||
if bwc ~= nil then
|
||||
--result[dev] = "[" .. string.gsub(bwc, '[\r\n]', '') .. "]"
|
||||
if label ~= nil then
|
||||
result[intname .. " (" .. label .. ")" ] = "[" .. string.gsub(bwc, '[\r\n]', '') .. "]"
|
||||
else
|
||||
result[intname] = "[" .. string.gsub(bwc, '[\r\n]', '') .. "]"
|
||||
end
|
||||
else
|
||||
if label ~= nil then
|
||||
result[intname .. " (" .. label .. ")" ] = "[]"
|
||||
else
|
||||
result[intname] = "[]"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
res["total"]={ };
|
||||
for i=1,60 do
|
||||
res["total"][i]={}
|
||||
for j=1,5 do
|
||||
res["total"][i][j]=0
|
||||
end
|
||||
end
|
||||
|
||||
for key,value in pairs(result) do
|
||||
res[key]={}
|
||||
value=(string.gsub(value, "^%[%[", ""))
|
||||
value=(string.gsub(value, "%]%]", ""))
|
||||
local temp1 = string.split(value, "],")
|
||||
if temp1[2] ~= nil then
|
||||
res[key][1]=temp1[1]
|
||||
for i=2,60 do
|
||||
res[key][i]={}
|
||||
if temp1[i] ~= nil then
|
||||
res[key][i]=(string.gsub(temp1[i], "%[", " "))
|
||||
end
|
||||
end
|
||||
for i=1,60 do
|
||||
res[key][i] = string.split(res[key][i], ",")
|
||||
for j=1,5 do
|
||||
res[key][i][j]= tonumber(res[key][i][j])
|
||||
res["total"][i][j]= tonumber(res["total"][i][j])
|
||||
if j ==1 then
|
||||
if res[key][i][j] ~= nil and res[key][i][j] > 0 then
|
||||
res["total"][i][j] = res[key][i][j]
|
||||
else
|
||||
res["total"][i][j] = 0
|
||||
end
|
||||
else
|
||||
if res[key][i][j] ~= nil and res[key][i][j] > 0 then
|
||||
res["total"][i][j] = res["total"][i][j] + res[key][i][j]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for i=1,60 do
|
||||
for j=1,5 do
|
||||
if "number"== type(res["total"][i][j]) then
|
||||
res["total"][i][j]= tostring(res["total"][i][j])
|
||||
end
|
||||
end
|
||||
end
|
||||
for i=1,60 do
|
||||
if i == 60 then
|
||||
tmpstr = "["..table.concat(res["total"][i], ",")
|
||||
else
|
||||
tmpstr = "["..table.concat(res["total"][i], ",").."],"
|
||||
end
|
||||
str = str..tmpstr
|
||||
end
|
||||
str = "["..str.."]]"
|
||||
result["total"]=str
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(result)
|
||||
end
|
||||
|
||||
function get_device(interface)
|
||||
local dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
|
||||
if dump then
|
||||
return dump['l3_device']
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
function mptcp_check_trace(iface)
|
||||
luci.http.prepare_content("text/plain")
|
||||
local tracebox
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local interface = get_device(iface)
|
||||
local server = uci:get("shadowsocks-libev", "sss0", "server") or ""
|
||||
if server == "" then return end
|
||||
if interface == "" then
|
||||
tracebox = io.popen("tracebox -s /usr/share/tracebox/omr-mptcp-trace.lua " .. server)
|
||||
else
|
||||
tracebox = io.popen("tracebox -s /usr/share/tracebox/omr-mptcp-trace.lua -i " .. interface .. " " .. server)
|
||||
end
|
||||
if tracebox then
|
||||
while true do
|
||||
local ln = tracebox:read("*l")
|
||||
if not ln then break end
|
||||
luci.http.write(ln)
|
||||
luci.http.write("\n")
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function mptcp_fullmesh_data()
|
||||
luci.http.prepare_content("text/plain")
|
||||
local fullmesh
|
||||
fullmesh = io.popen("multipath -f")
|
||||
if fullmesh then
|
||||
while true do
|
||||
local ln = fullmesh:read("*l")
|
||||
if not ln then break end
|
||||
luci.http.write(ln)
|
||||
luci.http.write("\n")
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function mptcp_monitor_data()
|
||||
luci.http.prepare_content("text/plain")
|
||||
local fullmesh
|
||||
fullmesh = io.popen("multipath -m")
|
||||
if fullmesh:read() ~= nil then
|
||||
while true do
|
||||
local ln = fullmesh:read("*l")
|
||||
if not ln then break end
|
||||
luci.http.write(ln)
|
||||
luci.http.write("\n")
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function mptcp_connections_data()
|
||||
luci.http.prepare_content("text/plain")
|
||||
local connections
|
||||
connections = io.popen("multipath -c")
|
||||
if connections:read() ~= nil then
|
||||
while true do
|
||||
local ln = connections:read("*l")
|
||||
if not ln then break end
|
||||
luci.http.write(ln)
|
||||
luci.http.write("\n")
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -1,200 +0,0 @@
|
||||
local net = require "luci.model.network".init()
|
||||
local sys = require "luci.sys"
|
||||
local ifaces = sys.net:devices()
|
||||
local m, s, o
|
||||
local uname = nixio.uname()
|
||||
|
||||
m = Map("network", translate("MPTCP"), translate("Networks MPTCP settings."))
|
||||
|
||||
local unameinfo = nixio.uname() or { }
|
||||
|
||||
s = m:section(TypedSection, "globals")
|
||||
o = s:option(ListValue, "multipath", translate("Multipath TCP"))
|
||||
o:value("enable", translate("enable"))
|
||||
o:value("disable", translate("disable"))
|
||||
o = s:option(ListValue, "mptcp_checksum", translate("Multipath TCP checksum"))
|
||||
o:value(1, translate("enable"))
|
||||
o:value(0, translate("disable"))
|
||||
|
||||
if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then
|
||||
o = s:option(ListValue, "mptcp_debug", translate("Multipath Debug"))
|
||||
o:value(1, translate("enable"))
|
||||
o:value(0, translate("disable"))
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "mptcp_path_manager", translate("Multipath TCP path-manager"), translate("Default is fullmesh"))
|
||||
o:value("default", translate("default"))
|
||||
o:value("fullmesh", "fullmesh")
|
||||
--if tonumber(uname.release:sub(1,4)) <= 5.15 then
|
||||
if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then
|
||||
o:value("ndiffports", "ndiffports")
|
||||
o:value("binder", "binder")
|
||||
if uname.release:sub(1,4) ~= "4.14" then
|
||||
o:value("netlink", translate("Netlink"))
|
||||
end
|
||||
end
|
||||
o = s:option(ListValue, "mptcp_scheduler", translate("Multipath TCP scheduler"))
|
||||
o:value("default", translate("default"))
|
||||
-- if tonumber(uname.release:sub(1,4)) <= 5.15 then
|
||||
if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then
|
||||
o:value("roundrobin", "round-robin")
|
||||
o:value("redundant", "redundant")
|
||||
if uname.release:sub(1,4) ~= "4.14" then
|
||||
o:value("blest", "BLEST")
|
||||
o:value("ecf", "ECF")
|
||||
end
|
||||
end
|
||||
if uname.release:sub(1,3) == "6.6" then
|
||||
for dir in io.popen([[cd /usr/share/bpf/scheduler && ls -1 *.o | sed -e 's/.o//g' -e 's/mptcp_//g']]):lines() do
|
||||
o:value(dir, dir)
|
||||
end
|
||||
-- bpf_burst => same as the default scheduler
|
||||
-- bpf_red => sends all packets redundantly on all available subflows
|
||||
-- bpf_first => always picks the first subflow to send data
|
||||
-- bpf_rr => always picks the next available subflow to send data (round-robin)
|
||||
end
|
||||
|
||||
-- if tonumber(uname.release:sub(1,4)) <= 5.15 then
|
||||
if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then
|
||||
o = s:option(Value, "mptcp_syn_retries", translate("Multipath TCP SYN retries"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
end
|
||||
-- if tonumber(uname.release:sub(1,4)) <= 5.15 then
|
||||
if uname.release:sub(1,4) ~= "5.15" and uname.release:sub(1,1) ~= "6" then
|
||||
o = s:option(ListValue, "mptcp_version", translate("Multipath TCP version"))
|
||||
o:value(0, translate("0"))
|
||||
o:value(1, translate("1"))
|
||||
o.default = 0
|
||||
end
|
||||
o = s:option(ListValue, "congestion", translate("Congestion Control"),translate("Default is bbr"))
|
||||
local availablecong = sys.exec("sysctl -n net.ipv4.tcp_available_congestion_control | xargs -n1 | sort | xargs")
|
||||
for cong in string.gmatch(availablecong, "[^%s]+") do
|
||||
if cong == "bbr" and string.match(availablecong, "bbr1") then
|
||||
o:value(cong, "bbr3")
|
||||
else
|
||||
o:value(cong, cong)
|
||||
end
|
||||
end
|
||||
|
||||
-- if tonumber(uname.release:sub(1,4)) >= 5.15 then
|
||||
if uname.release:sub(1,4) == "5.15" or uname.release:sub(1,1) == "6" then
|
||||
if uname.release:sub(1,1) == "6" then
|
||||
-- Only available since 5.19
|
||||
o = s:option(ListValue, "mptcp_pm_type", translate("Path Manager type"))
|
||||
o:value(0, translate("In-kernel path manager"))
|
||||
o:value(1, translate("Userspace path manager"))
|
||||
o.default = 0
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "mptcp_disable_initial_config", translate("Initial MPTCP configuration"))
|
||||
o:depends("mptcp_pm_type",1)
|
||||
o:value("0", translate("enable"))
|
||||
o:value("1", translate("disable"))
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(ListValue, "mptcp_force_multipath", translate("Force Multipath configuration"))
|
||||
o:depends("mptcp_pm_type",1)
|
||||
o:value("1", translate("enable"))
|
||||
o:value("0", translate("disable"))
|
||||
o.default = "1"
|
||||
|
||||
o = s:option(ListValue, "mptcpd_enable", translate("Enable MPTCPd"))
|
||||
o:depends("mptcp_pm_type",1)
|
||||
o:value("enable", translate("enable"))
|
||||
o:value("disable", translate("disable"))
|
||||
o.default = "disable"
|
||||
|
||||
o = s:option(DynamicList, "mptcpd_path_manager", translate("MPTCPd path managers"))
|
||||
for dir in io.popen([[cd /usr/lib/mptcpd && ls -1 *.so | sed 's/.so//g']]):lines() do
|
||||
o:value(dir, dir)
|
||||
end
|
||||
o:depends("mptcp_pm_type",1)
|
||||
|
||||
o = s:option(DynamicList, "mptcpd_plugins", translate("MPTCPd plugins"))
|
||||
for dir in io.popen([[cd /usr/lib/mptcpd && ls -1 *.so | sed 's/.so//g']]):lines() do
|
||||
o:value(dir, dir)
|
||||
end
|
||||
o:depends("mptcp_pm_type",1)
|
||||
|
||||
o = s:option(DynamicList, "mptcpd_addr_flags", translate("MPTCPd Address annoucement flags"))
|
||||
o:value("subflow","subflow")
|
||||
o:value("signal","signal")
|
||||
o:value("backup","backup")
|
||||
o:value("fullmesh","fullmesh")
|
||||
o:depends("mptcp_pm_type",1)
|
||||
|
||||
o = s:option(DynamicList, "mptcpd_notify_flags", translate("MPTCPd Address notification flags"))
|
||||
o:value("existing","existing")
|
||||
o:value("skip_link_local","skip_link_local")
|
||||
o:value("skip_loopback","skip_loopback")
|
||||
o:depends("mptcp_pm_type",1)
|
||||
|
||||
o = s:option(Value, "mptcp_subflows", translate("Max subflows"),translate("specifies the maximum number of additional subflows allowed for each MPTCP connection"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 3
|
||||
|
||||
o = s:option(Value, "mptcp_stale_loss_cnt", translate("Retranmission intervals"),translate("The number of MPTCP-level retransmission intervals with no traffic and pending outstanding data on a given subflow required to declare it stale. A low stale_loss_cnt value allows for fast active-backup switch-over, an high value maximize links utilization on edge scenarios e.g. lossy link with high BER or peer pausing the data processing."))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 4
|
||||
|
||||
o = s:option(Value, "mptcp_add_addr_accepted", translate("Max add address"),translate("specifies the maximum number of ADD_ADDR (add address) suboptions accepted for each MPTCP connection"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 1
|
||||
|
||||
o = s:option(Value, "mptcp_add_addr_timeout", translate("Control message timeout"),translate("Set the timeout after which an ADD_ADDR (add address) control message will be resent to an MPTCP peer that has not acknowledged a previous ADD_ADDR message."))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 120
|
||||
|
||||
else
|
||||
o = s:option(Value, "mptcp_fullmesh_num_subflows", translate("Fullmesh subflows for each pair of IP addresses"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 1
|
||||
--o:depends("mptcp_path_manager","fullmesh")
|
||||
|
||||
o = s:option(ListValue, "mptcp_fullmesh_create_on_err", translate("Re-create fullmesh subflows after a timeout"))
|
||||
o:value(1, translate("enable"))
|
||||
o:value(0, translate("disable"))
|
||||
--o:depends("mptcp_path_manager","fullmesh")
|
||||
|
||||
o = s:option(Value, "mptcp_ndiffports_num_subflows", translate("ndiffports subflows number"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 1
|
||||
--o:depends("mptcp_path_manager","ndiffports")
|
||||
|
||||
o = s:option(ListValue, "mptcp_rr_cwnd_limited", translate("Fill the congestion window on all subflows for round robin"))
|
||||
o:value("Y", translate("enable"))
|
||||
o:value("N", translate("disable"))
|
||||
o.default = "Y"
|
||||
--o:depends("mptcp_scheduler","roundrobin")
|
||||
|
||||
o = s:option(Value, "mptcp_rr_num_segments", translate("Consecutive segments that should be sent for round robin"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = false
|
||||
o.default = 1
|
||||
--o:depends("mptcp_scheduler","roundrobin")
|
||||
end
|
||||
|
||||
s = m:section(TypedSection, "interface", translate("Interfaces Settings"))
|
||||
function s.filter(self, section)
|
||||
return not section:match("^oip.*") and not section:match("^lo.*") and section ~= "omrvpn" and section ~= "omr6in4"
|
||||
end
|
||||
o = s:option(ListValue, "multipath", translate("Multipath TCP"), translate("One interface must be set as master"))
|
||||
o:value("on", translate("enabled"))
|
||||
o:value("off", translate("disabled"))
|
||||
o:value("master", translate("master"))
|
||||
o:value("backup", translate("backup"))
|
||||
--o:value("handover", translate("handover"))
|
||||
o.default = "off"
|
||||
|
||||
function m.on_after_apply(self,map)
|
||||
sys.call('/etc/init.d/mptcp reload')
|
||||
end
|
||||
|
||||
return m
|
||||
@@ -1,75 +0,0 @@
|
||||
<%+header%>
|
||||
|
||||
<%
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local sys = require "luci.sys"
|
||||
local ifaces = sys.net:devices()
|
||||
local net = require "luci.model.network".init()
|
||||
%>
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.193.28471-ee087a1"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var stxhr = new XHR();
|
||||
|
||||
function update_mptcp(interface)
|
||||
{
|
||||
var trace = document.getElementById('tracebox');
|
||||
|
||||
if (trace)
|
||||
{
|
||||
trace.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' + '<%:Waiting for command to complete...%>'
|
||||
;
|
||||
|
||||
stxhr.post('<%=url('admin/network/mptcp')%>/mptcp_check_trace/' + interface, { token: '<%=token%>' },
|
||||
function(x)
|
||||
{
|
||||
if (x.responseText)
|
||||
{
|
||||
trace.innerHTML = String.format('<pre>%s</pre>', x.responseText );
|
||||
} else {
|
||||
trace.innerHTML = '<pre><%:Error%></pre>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
//]]></script>
|
||||
|
||||
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
|
||||
<form class="inline" method="post" action="<%=url('admin/network/mptcp/mptcp_check_trace')%>">
|
||||
<div class="cbi-map">
|
||||
<h2 name="content"><%:MPTCP Support Check%></h2>
|
||||
<div class="cbi-map-descr"><%:Check if MPTCP between interface and server is working.%></div>
|
||||
<fieldset class="cbi-section" id="networks">
|
||||
<legend><%:Settings%></legend>
|
||||
<div class="cbi-section-descr"></div>
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"><%:Interface%></label>
|
||||
<div class="cbi-value-field">
|
||||
<select class="cbi-input-select" name="interface" size="1">
|
||||
<%
|
||||
for _, iface in ipairs(net:get_networks()) do
|
||||
local ifname = iface:name()
|
||||
local multipath = uci:get("network",ifname,"multipath")
|
||||
if multipath ~= "off" then
|
||||
%>
|
||||
<option value="<%=ifname%>"><%=ifname%></option>
|
||||
<%
|
||||
end
|
||||
end
|
||||
%>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type="button" value="<%:Test%>" class="cbi-button cbi-button-apply" onclick="update_mptcp(this.form.interface.value)" />
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="cbi-section">
|
||||
<span id="tracebox"></span>
|
||||
<br />
|
||||
<i><%:If you get "TCPOptionMPTCPCapable [...] Sender's Key" at the end, then MPTCP is supported. If there is a "-TCPOptionMPTCPCapable", then it's blocked.%></i>
|
||||
</div>
|
||||
<%+footer%>
|
||||
@@ -1,49 +0,0 @@
|
||||
<%+header%>
|
||||
|
||||
<%
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local sys = require "luci.sys"
|
||||
local ifaces = sys.net:devices()
|
||||
local net = require "luci.model.network".init()
|
||||
%>
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.193.28471-ee087a1"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var stxhr = new XHR();
|
||||
|
||||
function update_connections()
|
||||
{
|
||||
var ec = document.getElementById('connections');
|
||||
|
||||
if (ec)
|
||||
{
|
||||
ec.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' + '<%:Waiting for command to complete...%>'
|
||||
;
|
||||
|
||||
stxhr.post('<%=url('admin/network/mptcp')%>/mptcp_connections_data/', { token: '<%=token%>' },
|
||||
function(x)
|
||||
{
|
||||
if (x.responseText)
|
||||
{
|
||||
ec.innerHTML = String.format('<pre>%s</pre>', x.responseText );
|
||||
} else {
|
||||
ec.innerHTML = '<pre><%:No data%></pre>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
setInterval(function () {
|
||||
update_connections();
|
||||
}, 10000);
|
||||
//]]></script>
|
||||
|
||||
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
|
||||
<div class="cbi-section">
|
||||
<span id="connections"></span>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
update_connections();
|
||||
//]]></script>
|
||||
</div>
|
||||
<%+footer%>
|
||||
@@ -1,49 +0,0 @@
|
||||
<%+header%>
|
||||
|
||||
<%
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local sys = require "luci.sys"
|
||||
local ifaces = sys.net:devices()
|
||||
local net = require "luci.model.network".init()
|
||||
%>
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.193.28471-ee087a1"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var stxhr = new XHR();
|
||||
|
||||
function update_fullmesh()
|
||||
{
|
||||
var fm = document.getElementById('fullmesh');
|
||||
|
||||
if (fm)
|
||||
{
|
||||
fm.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' + '<%:Waiting for command to complete...%>'
|
||||
;
|
||||
|
||||
stxhr.post('<%=url('admin/network/mptcp')%>/mptcp_fullmesh_data/', { token: '<%=token%>' },
|
||||
function(x)
|
||||
{
|
||||
if (x.responseText)
|
||||
{
|
||||
fm.innerHTML = String.format('<pre>%s</pre>', x.responseText );
|
||||
} else {
|
||||
fm.innerHTML = '<pre><%:Error%></pre>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
setInterval(function () {
|
||||
update_fullmesh();
|
||||
}, 10000);
|
||||
//]]></script>
|
||||
|
||||
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
|
||||
<div class="cbi-section">
|
||||
<span id="fullmesh"></span>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
update_fullmesh();
|
||||
//]]></script>
|
||||
</div>
|
||||
<%+footer%>
|
||||
@@ -1,42 +0,0 @@
|
||||
<%+header%>
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.193.28471-ee087a1"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var stxhr = new XHR();
|
||||
|
||||
function update_monitor()
|
||||
{
|
||||
var fm = document.getElementById('monitor');
|
||||
|
||||
if (fm)
|
||||
{
|
||||
fm.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' + '<%:Waiting for command to complete...%>'
|
||||
;
|
||||
|
||||
stxhr.post('<%=url('admin/network/mptcp')%>/mptcp_monitor_data/', { token: '<%=token%>' },
|
||||
function(x)
|
||||
{
|
||||
if (x.responseText)
|
||||
{
|
||||
fm.innerHTML = String.format('<pre>%s</pre>', x.responseText );
|
||||
} else {
|
||||
fm.innerHTML = '<pre><%:Error%></pre>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
setInterval(function () {
|
||||
update_monitor();
|
||||
}, 10000);
|
||||
//]]></script>
|
||||
|
||||
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
|
||||
<div class="cbi-section">
|
||||
<span id="monitor"></span>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
update_monitor();
|
||||
//]]></script>
|
||||
</div>
|
||||
<%+footer%>
|
||||
@@ -1,954 +0,0 @@
|
||||
<%
|
||||
-- Copyright 2010 Jo-Philipp Wich (jow@openwrt.org)
|
||||
-- Copyright 2015 OVH (OverTheBox@ovh.net)
|
||||
-- Simon Lelievre (simon.lelievre@corp.ovh.com)
|
||||
-- Sebastien Duponcheel (sebastien.duponcheel@ovh.net)
|
||||
-- Copyright 2018-2020 Ycarus - Yannick Chabanois (ycarus@zugaina.org) for OpenMPTCProuter
|
||||
--
|
||||
-- Part of this file come from OverTheBox for OpenWrt.
|
||||
--
|
||||
-- OverTheBox is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- OverTheBox is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with OverTheBox. If not, see (http://www.gnu.org/licenses/)
|
||||
-%>
|
||||
<%-
|
||||
local ntm = require "luci.model.network".init()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
function get_device(interface)
|
||||
local dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
|
||||
if dump and dump['l3_device'] ~= "" then
|
||||
return dump['l3_device']
|
||||
else
|
||||
return interface
|
||||
end
|
||||
end
|
||||
|
||||
local dev
|
||||
local devices = { "all" }
|
||||
for _, dev in ipairs(ntm:get_networks()) do
|
||||
--if dev ~= "lo" and not ntm:ignore_interface(dev) then
|
||||
local multipath = uci:get("network", dev:name(), "multipath")
|
||||
if multipath == "on" or multipath == "master" or multipath == "backup" or multipath == "handover" then
|
||||
devices[#devices+1] = dev:name()
|
||||
end
|
||||
--end
|
||||
end
|
||||
|
||||
local curifname = luci.http.formvalue("dev") or "all"
|
||||
local bandwidthtotalurl = "admin/network/mptcp/multipath_bandwidth"
|
||||
local bandwidthintfurl = "admin/network/mptcp/interface_bandwidth"
|
||||
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-20.122.32821-5b14298"></script>
|
||||
<script type="text/javascript" src="<%=resource%>/seedrandom.js?v=git-20.122.32821-5b14298"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
function stringToColour(str) {
|
||||
if(str == "total")
|
||||
return "OrangeRed";
|
||||
if(str.substring(0, 4) == "wan")
|
||||
return "FireBrick";
|
||||
if(str.substring(0, 4) == "wan1")
|
||||
return "DeepSkyBlue";
|
||||
if(str.substring(0, 4) == "wan2")
|
||||
return "SeaGreen";
|
||||
if(str.substring(0, 4) == "wan3")
|
||||
return "PaleGreen";
|
||||
if(str.substring(0, 4) == "wan4")
|
||||
return "PowderBlue";
|
||||
if(str.substring(0, 4) == "wan5")
|
||||
return "Salmon";
|
||||
if(str.substring(0, 4) == "wan6")
|
||||
return "LightGreen";
|
||||
if(str.substring(0, 4) == "wan7")
|
||||
return "PaleTurquoise";
|
||||
// Generate a color folowing the name
|
||||
Math.seedrandom(str);
|
||||
var rand = Math.random() * Math.pow(255,3);
|
||||
Math.seedrandom(); // don't leave a non-random seed in the generator
|
||||
for (var i = 0, colour = "#"; i < 3; colour += ("00" + ((rand >> i++ * 8) & 0xFF).toString(16)).slice(-2));
|
||||
return colour;
|
||||
}
|
||||
|
||||
function bandwidth_label(bytes, br)
|
||||
{
|
||||
if (bytes < 0) bytes = 0;
|
||||
var uby = '<%:kB/s%>';
|
||||
var kby = (bytes / 1024);
|
||||
|
||||
if (kby >= 1024)
|
||||
{
|
||||
uby = '<%:MB/s%>';
|
||||
kby = kby / 1024;
|
||||
}
|
||||
|
||||
var ubi = '<%:kbit/s%>';
|
||||
var kbi = (bytes * 8 / 1024);
|
||||
|
||||
if (kbi >= 1024)
|
||||
{
|
||||
ubi = '<%:Mbit/s%>';
|
||||
kbi = kbi / 1024;
|
||||
}
|
||||
|
||||
return String.format("%f %s%s(%f %s)",
|
||||
kbi.toFixed(2), ubi,
|
||||
br ? '<br />' : ' ',
|
||||
kby.toFixed(2), uby
|
||||
);
|
||||
}
|
||||
|
||||
/* Smoother */
|
||||
function simple_moving_averager(name, period) {
|
||||
var nums = {}
|
||||
nums[name] = [];
|
||||
return function(num) {
|
||||
nums[name].push(num);
|
||||
if (nums[name].length > period)
|
||||
nums[name].splice(0,1); // remove the first element of the array
|
||||
var sum = 0;
|
||||
for (var i in nums[name])
|
||||
sum += nums[name][i];
|
||||
var n = period;
|
||||
if (nums[name].length < period)
|
||||
n = nums[name].length;
|
||||
return(sum/n);
|
||||
}
|
||||
}
|
||||
|
||||
<% if curifname == "all" then %>
|
||||
var all = true;
|
||||
var bwxhr = new XHR();
|
||||
|
||||
var Gdn;
|
||||
var Gup;
|
||||
|
||||
var TIME = 0;
|
||||
var RXB = 1;
|
||||
var RXP = 2;
|
||||
var TXB = 3;
|
||||
var TXP = 4;
|
||||
|
||||
var width = 760;
|
||||
var height = 300;
|
||||
var step = 5;
|
||||
|
||||
var data_wanted = Math.floor(width / step);
|
||||
|
||||
var data_fill = 0;
|
||||
var data_stamp = 0;
|
||||
|
||||
var label_25;
|
||||
var label_50;
|
||||
var label_75;
|
||||
|
||||
var label_scale;
|
||||
|
||||
var def;
|
||||
var dntopline;
|
||||
var uptopline;
|
||||
var dnPolygons = {};
|
||||
var dndata = {};
|
||||
var upPolygons = {};
|
||||
var updata = {};
|
||||
|
||||
/* wait for SVG */
|
||||
window.setTimeout(
|
||||
function() {
|
||||
var dnsvg = document.getElementById('dnsvg');
|
||||
var upsvg = document.getElementById('upsvg');
|
||||
|
||||
try {
|
||||
Gdn = dnsvg.getSVGDocument ? dnsvg.getSVGDocument() : dnsvg.contentDocument;
|
||||
Gup = upsvg.getSVGDocument ? upsvg.getSVGDocument() : upsvg.contentDocument;
|
||||
}
|
||||
catch(e) {
|
||||
Gdn = document.embeds['dnsvg'].getSVGDocument();
|
||||
Gup = document.embeds['upsvg'].getSVGDocument();
|
||||
}
|
||||
|
||||
if (!Gdn || !Gup)
|
||||
{
|
||||
window.setTimeout(arguments.callee, 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* find sizes */
|
||||
width = dnsvg.offsetWidth - 2;
|
||||
height = dnsvg.offsetHeight - 2;
|
||||
data_wanted = Math.ceil(width / step);
|
||||
|
||||
/* find svg elements */
|
||||
labeldn_25 = Gdn.getElementById('label_25');
|
||||
labeldn_50 = Gdn.getElementById('label_50');
|
||||
labeldn_75 = Gdn.getElementById('label_75');
|
||||
|
||||
labelup_25 = Gup.getElementById('label_25');
|
||||
labelup_50 = Gup.getElementById('label_50');
|
||||
labelup_75 = Gup.getElementById('label_75');
|
||||
|
||||
labeldn_scale = document.getElementById('dnscale');
|
||||
labelup_scale = document.getElementById('upscale');
|
||||
|
||||
/* plot horizontal time interval lines */
|
||||
for (var i = width % (step * 60); i < width; i += step * 60)
|
||||
{
|
||||
var linedn = Gdn.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||
linedn.setAttribute('x1', i);
|
||||
linedn.setAttribute('y1', 0);
|
||||
linedn.setAttribute('x2', i);
|
||||
linedn.setAttribute('y2', '100%');
|
||||
linedn.setAttribute('style', 'stroke:black;stroke-width:0.3');
|
||||
|
||||
var textdn = Gdn.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
textdn.setAttribute('x', i + 5);
|
||||
textdn.setAttribute('y', 15);
|
||||
textdn.setAttribute('style', 'fill:#999999; font-size:9pt');
|
||||
textdn.appendChild(Gdn.createTextNode(Math.round((width - i) / step / 60) + 'm'));
|
||||
|
||||
labeldn_25.parentNode.appendChild(linedn);
|
||||
labeldn_25.parentNode.appendChild(textdn);
|
||||
|
||||
var lineup = Gup.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||
lineup.setAttribute('x1', i);
|
||||
lineup.setAttribute('y1', 0);
|
||||
lineup.setAttribute('x2', i);
|
||||
lineup.setAttribute('y2', '100%');
|
||||
lineup.setAttribute('style', 'stroke:black;stroke-width:0.1');
|
||||
|
||||
var textup = Gup.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
textup.setAttribute('x', i + 5);
|
||||
textup.setAttribute('y', 15);
|
||||
textup.setAttribute('style', 'fill:#999999; font-size:9pt');
|
||||
textup.appendChild(Gup.createTextNode(Math.round((width - i) / step / 60) + 'm'));
|
||||
|
||||
labelup_25.parentNode.appendChild(lineup);
|
||||
labelup_25.parentNode.appendChild(textup);
|
||||
}
|
||||
|
||||
labeldn_scale.innerHTML = String.format('<%:(%d minutes window, %d seconds interval)%>', data_wanted / 60, 3);
|
||||
labelup_scale.innerHTML = String.format('<%:(%d minutes window, %d seconds interval)%>', data_wanted / 60, 3);
|
||||
|
||||
/* render datasets, start update interval */
|
||||
XHR.poll(3, '<%=build_url(bandwidthtotalurl, all)%>', null,
|
||||
function(x, dataarray)
|
||||
{
|
||||
var data_max_dnl = 0;
|
||||
var data_max_upl = 0;
|
||||
var data_scale_dnl = 0;
|
||||
var data_scale_upl = 0;
|
||||
|
||||
var data_rx_avg = 0;
|
||||
var data_tx_avg = 0;
|
||||
|
||||
var data_rx_peak = 0;
|
||||
var data_tx_peak = 0;
|
||||
|
||||
var data = {};
|
||||
var pre_itf = "";
|
||||
|
||||
for(var itf in dataarray)
|
||||
{
|
||||
// convert json string to json object
|
||||
data[itf] = JSON.parse(dataarray[itf]);
|
||||
// Create lines for each interfaces
|
||||
var color = stringToColour(itf);
|
||||
if(typeof dnPolygons[itf] != "object")
|
||||
{
|
||||
// Create a new polygon to draw the bandwith
|
||||
var dnline = Gdn.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
||||
dnline.setAttributeNS(null, 'id', 'rx_' + itf);
|
||||
if (itf != "total")
|
||||
{
|
||||
dnline.setAttributeNS(null, 'style', 'fill:' + color + ';fill-opacity:0.4;stroke:black;stroke-width:0.1');
|
||||
}
|
||||
else
|
||||
{
|
||||
//dnline.setAttributeNS(null, 'style', 'fill: none;stroke:OrangeRed;stroke-width:1');
|
||||
dnline.setAttributeNS(null, 'style', 'fill: none;stroke:none');
|
||||
}
|
||||
Gdn.getElementById('rx').parentNode.appendChild(dnline);
|
||||
|
||||
dnPolygons[itf] = Gdn.getElementById('rx_' + itf);
|
||||
/* prefill datasets */
|
||||
dndata[itf] = [ ];
|
||||
for (var i = 0; i < data_wanted; i++)
|
||||
{
|
||||
dndata[itf][i] = 0;
|
||||
}
|
||||
|
||||
// Create legend for this connextion
|
||||
var table = document.getElementById('download_stats');
|
||||
if (pre_itf == "total")
|
||||
{
|
||||
var tr = table.insertRow(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tr = table.insertRow();
|
||||
}
|
||||
tr.setAttribute('id', itf + '_download');
|
||||
// Create cells of the table
|
||||
var itflabel = tr.insertCell(0);
|
||||
// Create itf legend
|
||||
var strong = document.createElement('strong')
|
||||
strong.appendChild(document.createTextNode(itf));
|
||||
strong.setAttribute('style', 'border-bottom:2px solid ' + color);
|
||||
itflabel.appendChild(strong);
|
||||
// Create label for stats
|
||||
tr.insertCell(1).appendChild(document.createTextNode('<%:Current:%>'));
|
||||
var itfcur = tr.insertCell(2);
|
||||
itfcur.setAttribute('id', itf + '_download_cur');
|
||||
itfcur.appendChild(document.createTextNode('<%:Current:%> 0 <%:kbit/s%> (0 <%:kB/s%>)'));
|
||||
tr.insertCell(3).appendChild(document.createTextNode('<%:Average:%>'));
|
||||
var itfavg = tr.insertCell(4);
|
||||
itfavg.setAttribute('id', itf + '_download_avg');
|
||||
itfavg.appendChild(document.createTextNode('<%:Average:%> 0 <%:kbit/s%> (0 <%:kB/s%>)'));
|
||||
tr.insertCell(5).appendChild(document.createTextNode('<%:Peak:%>'));
|
||||
var itfpeak = tr.insertCell(6);
|
||||
itfpeak.setAttribute('id', itf + '_download_peak');
|
||||
itfpeak.appendChild(document.createTextNode('<%:Peak:%> 0 <%:kbit/s%> (0 <%:kB/s%>)'));
|
||||
|
||||
}
|
||||
if(typeof upPolygons[itf] != "object")
|
||||
{
|
||||
var upline = Gup.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
||||
upline.setAttributeNS(null, 'id', 'tx_' + itf);
|
||||
if (itf != "total")
|
||||
{
|
||||
upline.setAttributeNS(null, 'style', 'fill:' + color + ';fill-opacity:0.4;stroke:black;stroke-width:0.1');
|
||||
}
|
||||
else
|
||||
{
|
||||
//upline.setAttributeNS(null, 'style', 'fill:none;stroke:OrangeRed;stroke-width:1');
|
||||
upline.setAttributeNS(null, 'style', 'fill:none;stroke:none');
|
||||
}
|
||||
Gup.getElementById('tx').parentNode.appendChild(upline);
|
||||
|
||||
upPolygons[itf] = Gup.getElementById('tx_' + itf);
|
||||
/* prefill datasets */
|
||||
updata[itf] = [ ];
|
||||
for (var i = 0; i < data_wanted; i++)
|
||||
{
|
||||
updata[itf][i] = 0;
|
||||
}
|
||||
|
||||
// Create legend for this connextion
|
||||
var table = document.getElementById('upload_stats');
|
||||
|
||||
if (pre_itf == "total")
|
||||
{
|
||||
var tr = table.insertRow(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var tr = table.insertRow();
|
||||
}
|
||||
tr.setAttribute('id', itf + '_upload');
|
||||
// Create cells of the table
|
||||
var itflabel = tr.insertCell(0);
|
||||
// Create itf legend
|
||||
var strong = document.createElement('strong')
|
||||
strong.appendChild(document.createTextNode(itf));
|
||||
strong.setAttribute('style', 'border-bottom:2px solid ' + color);
|
||||
itflabel.appendChild(strong);
|
||||
// Create label for stats
|
||||
tr.insertCell(1).appendChild(document.createTextNode('<%:Current:%>'));
|
||||
var itfcur = tr.insertCell(2);
|
||||
itfcur.setAttribute('id', itf + '_upload_cur');
|
||||
itfcur.appendChild(document.createTextNode('0 <%:kbit/s%> (0 <%:kB/s%>)'));
|
||||
tr.insertCell(3).appendChild(document.createTextNode('<%:Average:%>'));
|
||||
var itfavg = tr.insertCell(4);
|
||||
itfavg.setAttribute('id', itf + '_upload_avg');
|
||||
itfavg.appendChild(document.createTextNode('0 <%:kbit/s%> (0 <%:kB/s%>)'));
|
||||
tr.insertCell(5).appendChild(document.createTextNode('<%:Peak:%>'));
|
||||
var itfpeak = tr.insertCell(6);
|
||||
itfpeak.setAttribute('id', itf + '_upload_peak');
|
||||
itfpeak.appendChild(document.createTextNode('0 <%:kbit/s%> (0 <%:kB/s%>)'));
|
||||
}
|
||||
if (pre_itf != "total") pre_itf = itf;
|
||||
}
|
||||
|
||||
var dnsma = {};
|
||||
var upsma = {};
|
||||
for (var itf in data)
|
||||
{
|
||||
if(!dnsma[itf])
|
||||
{
|
||||
dnsma[itf] = simple_moving_averager('down_'+itf, 15);
|
||||
}
|
||||
if(!upsma[itf])
|
||||
{
|
||||
upsma[itf] = simple_moving_averager('up_'+itf, 15);
|
||||
}
|
||||
for (var i = data_stamp ? 0 : 1; i < data[itf].length; i++)
|
||||
{
|
||||
/* skip overlapping entries */
|
||||
if (data[itf][i][TIME] <= data_stamp)
|
||||
continue;
|
||||
|
||||
/* normalize difference against time interval */
|
||||
if (i > 0)
|
||||
{
|
||||
var time_delta = data[itf][i][TIME] - data[itf][i-1][TIME];
|
||||
if (time_delta)
|
||||
{
|
||||
dndata[itf].push(dnsma[itf]((data[itf][i][RXB] - data[itf][i-1][RXB]) / time_delta));
|
||||
updata[itf].push(upsma[itf]((data[itf][i][TXB] - data[itf][i-1][TXB]) / time_delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
/* cut off outdated entries */
|
||||
dndata[itf] = dndata[itf].slice(dndata[itf].length - data_wanted, dndata[itf].length);
|
||||
updata[itf] = updata[itf].slice(updata[itf].length - data_wanted, updata[itf].length);
|
||||
}
|
||||
/* remember current timestamp, calculate horizontal scale */
|
||||
for (var itf in data)
|
||||
{
|
||||
for (var i = 0; i < data[itf].length; i++)
|
||||
data_stamp = Math.max(data_stamp, data[itf][data[itf].length-1][TIME]);
|
||||
}
|
||||
|
||||
var el = Gdn.getElementById('rx').parentNode;
|
||||
|
||||
// intialize upline and downline datas for download and upload
|
||||
var uplineDnl = { };
|
||||
var downlineDnl = { };
|
||||
var uplineUpl = { };
|
||||
var downlineUpl = { };
|
||||
// fetch upline and downline datas
|
||||
var toadditf = [ ];
|
||||
for (var itf in data)
|
||||
{
|
||||
//if (itf == "total") continue;
|
||||
uplineDnl[itf] = [ ];
|
||||
downlineDnl[itf]= [ ];
|
||||
uplineUpl[itf] = [ ];
|
||||
downlineUpl[itf]= [ ];
|
||||
// build point sets
|
||||
var data_tx_avg;
|
||||
var data_rx_avg;
|
||||
var data_rx_peak = 0;
|
||||
var data_tx_peak = 0;
|
||||
for (var i = 0; i < dndata[itf].length; i++)
|
||||
{
|
||||
var rx_bot=0;
|
||||
var tx_bot=0;
|
||||
if (itf != "total")
|
||||
{
|
||||
for(var j=0; j < toadditf.length; j++)
|
||||
{
|
||||
if (toadditf[j] == "total") continue;
|
||||
rx_bot += dndata[toadditf[j]][i];
|
||||
tx_bot += updata[toadditf[j]][i];
|
||||
}
|
||||
}
|
||||
// set upline of the interface
|
||||
uplineDnl[itf][i] = dndata[itf][i] + rx_bot;
|
||||
data_rx_peak = Math.max(data_rx_peak, dndata[itf][i]);
|
||||
data_max_dnl = Math.max(data_max_dnl, uplineDnl[itf][i]);
|
||||
uplineUpl[itf][i] = updata[itf][i] + tx_bot;
|
||||
data_tx_peak = Math.max(data_tx_peak, updata[itf][i]);
|
||||
data_max_upl = Math.max(data_max_upl, uplineUpl[itf][i]);
|
||||
if (i > 0)
|
||||
{
|
||||
data_rx_avg = (data_rx_avg + dndata[itf][i]) / 2;
|
||||
data_tx_avg = (data_tx_avg + updata[itf][i]) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
data_rx_avg = dndata[itf][i];
|
||||
data_tx_avg = updata[itf][i];
|
||||
}
|
||||
if(toadditf.length)
|
||||
{
|
||||
if (toadditf[toadditf.length-1] != "total")
|
||||
{
|
||||
downlineDnl[itf][i] = uplineDnl[toadditf[toadditf.length-1]][i];
|
||||
downlineUpl[itf][i] = uplineUpl[toadditf[toadditf.length-1]][i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
toadditf.push(itf);
|
||||
|
||||
// Update stats labels
|
||||
if(document.getElementById(itf + '_download_cur'))
|
||||
{
|
||||
var label = document.getElementById(itf + '_download_cur');
|
||||
label.innerHTML = bandwidth_label(dndata[itf][dndata[itf].length-1], true);
|
||||
}
|
||||
if(document.getElementById(itf + '_upload_cur'))
|
||||
{
|
||||
var label = document.getElementById(itf + '_upload_cur');
|
||||
label.innerHTML = bandwidth_label(updata[itf][updata[itf].length-1], true);
|
||||
}
|
||||
if(label = document.getElementById(itf + '_download_avg'))
|
||||
{
|
||||
label.innerHTML = bandwidth_label(data_rx_avg, true);
|
||||
}
|
||||
if(label = document.getElementById(itf + '_upload_avg'))
|
||||
{
|
||||
label.innerHTML = bandwidth_label(data_tx_avg, true);
|
||||
}
|
||||
if(document.getElementById(itf + '_download_peak'))
|
||||
{
|
||||
var label = document.getElementById(itf + '_download_peak');
|
||||
label.innerHTML = bandwidth_label(data_rx_peak, true);
|
||||
}
|
||||
if(document.getElementById(itf + '_upload_peak'))
|
||||
{
|
||||
var label = document.getElementById(itf + '_upload_peak');
|
||||
label.innerHTML = bandwidth_label(data_tx_peak, true);
|
||||
}
|
||||
}
|
||||
data_scale_upl = height / (data_max_upl * 1.1);
|
||||
data_scale_dnl = height / (data_max_dnl * 1.1);
|
||||
|
||||
for (var itf in uplineDnl)
|
||||
{
|
||||
if (itf == "total") continue;
|
||||
var y_rx = 0;
|
||||
var y_tx = 0;
|
||||
|
||||
/* plot data */
|
||||
var pt_rx = '0,' + height;
|
||||
var pt_tx = '0,' + height;
|
||||
|
||||
// draw polygon
|
||||
for (var i = 0; i < uplineDnl[itf].length; i++)
|
||||
{
|
||||
var x = i * step;
|
||||
|
||||
y_rx = height - Math.floor((uplineDnl[itf][i]) * data_scale_dnl);
|
||||
y_tx = height - Math.floor((uplineUpl[itf][i]) * data_scale_upl);
|
||||
|
||||
pt_rx += ' ' + x + ',' + y_rx;
|
||||
pt_tx += ' ' + x + ',' + y_tx;
|
||||
}
|
||||
// draw last tangant point in upline graph
|
||||
pt_rx += ' ' + width + ',' + y_rx;
|
||||
pt_rx += ' ' + width + ',' + height;
|
||||
|
||||
pt_tx += ' ' + width + ',' + y_tx;
|
||||
pt_tx += ' ' + width + ',' + height;
|
||||
|
||||
for (var i = downlineDnl[itf].length - 1; i >= 0; i--)
|
||||
{
|
||||
var x = i * step;
|
||||
|
||||
y_rx = height - Math.floor((downlineDnl[itf][i] - 10) * data_scale_dnl);
|
||||
y_tx = height - Math.floor((downlineUpl[itf][i] - 10) * data_scale_upl);
|
||||
// recreate tangant point in bottom line graph
|
||||
if(i == downlineDnl[itf].length - 1)
|
||||
{
|
||||
pt_rx += ' ' + width + ',' + height;
|
||||
pt_rx += ' ' + width + ',' + y_rx;
|
||||
|
||||
pt_tx += ' ' + width + ',' + height;
|
||||
pt_tx += ' ' + width + ',' + y_tx;
|
||||
}
|
||||
|
||||
pt_rx += ' ' + x + ',' + y_rx;
|
||||
pt_tx += ' ' + x + ',' + y_tx;
|
||||
}
|
||||
// go back to right bottom corner to correctly fill polyline
|
||||
pt_tx += ' ' + 0 + ',' + height;
|
||||
pt_tx += ' ' + width + ',' + height;
|
||||
pt_rx += ' ' + 0 + ',' + height;
|
||||
pt_rx += ' ' + width + ',' + height;
|
||||
|
||||
// Draw polygon
|
||||
dnPolygons[itf].setAttribute('points', pt_rx);
|
||||
upPolygons[itf].setAttribute('points', pt_tx);
|
||||
|
||||
labeldn_25.firstChild.data = bandwidth_label(1.1 * 0.25 * data_max_dnl);
|
||||
labeldn_50.firstChild.data = bandwidth_label(1.1 * 0.50 * data_max_dnl);
|
||||
labeldn_75.firstChild.data = bandwidth_label(1.1 * 0.75 * data_max_dnl);
|
||||
|
||||
labelup_25.firstChild.data = bandwidth_label(1.1 * 0.25 * data_max_upl);
|
||||
labelup_50.firstChild.data = bandwidth_label(1.1 * 0.50 * data_max_upl);
|
||||
labelup_75.firstChild.data = bandwidth_label(1.1 * 0.75 * data_max_upl);
|
||||
}
|
||||
/* now draw top line */
|
||||
var keys = Object.keys(uplineDnl);
|
||||
if (keys.length)
|
||||
{
|
||||
var itf = keys[keys.length - 1];
|
||||
if (typeof dntopline != "object")
|
||||
{
|
||||
dntopline = Gdn.getElementById('rx');
|
||||
dntopline.setAttributeNS(null, 'style', 'stroke:DimGray;stroke-width:0;stroke-linecap="round";fill:;fill-opacity:0;');
|
||||
/* Move line on top */
|
||||
var father = dntopline.parentNode;
|
||||
father.removeChild(dntopline);
|
||||
father.appendChild(dntopline);
|
||||
}
|
||||
if (typeof uptopline != "object")
|
||||
{
|
||||
uptopline = Gup.getElementById('tx');
|
||||
uptopline.setAttributeNS(null, 'style', 'stroke:DimGray;stroke-width:0;stroke-linecap="round";fill:;fill-opacity:0;');
|
||||
/* Move line on top */
|
||||
var father = uptopline.parentNode;
|
||||
father.removeChild(uptopline);
|
||||
father.appendChild(uptopline);
|
||||
}
|
||||
var y_rx = 0;
|
||||
var y_tx = 0;
|
||||
|
||||
/* plot data */
|
||||
var pt_rx = '0,' + height;
|
||||
var pt_tx = '0,' + height;
|
||||
|
||||
// draw polygon
|
||||
for (var i = 0; i < uplineDnl[itf].length; i++)
|
||||
{
|
||||
var x = i * step;
|
||||
y_rx = height - Math.floor((uplineDnl[itf][i]) * data_scale_dnl);
|
||||
y_tx = height - Math.floor((uplineUpl[itf][i]) * data_scale_upl);
|
||||
pt_rx += ' ' + x + ',' + y_rx;
|
||||
pt_tx += ' ' + x + ',' + y_tx;
|
||||
}
|
||||
// draw last tangant point in upline graph
|
||||
pt_rx += ' ' + width + ',' + y_rx;
|
||||
pt_rx += ' ' + width + ',' + height;
|
||||
|
||||
pt_tx += ' ' + width + ',' + y_tx;
|
||||
pt_tx += ' ' + width + ',' + height;
|
||||
|
||||
dntopline.setAttribute('points', pt_rx);
|
||||
uptopline.setAttribute('points', pt_tx);
|
||||
}
|
||||
}
|
||||
);
|
||||
//XHR.run();
|
||||
}
|
||||
}, 1000
|
||||
<% else %>
|
||||
var bwxhr = new XHR();
|
||||
|
||||
var G;
|
||||
var TIME = 0;
|
||||
var RXB = 1;
|
||||
var RXP = 2;
|
||||
var TXB = 3;
|
||||
var TXP = 4;
|
||||
|
||||
var width = 760;
|
||||
var height = 300;
|
||||
var step = 5;
|
||||
|
||||
var data_wanted = Math.floor(width / step);
|
||||
var data_fill = 0;
|
||||
var data_stamp = 0;
|
||||
|
||||
var data_rx = [ ];
|
||||
var data_tx = [ ];
|
||||
|
||||
var line_rx;
|
||||
var line_tx;
|
||||
|
||||
var label_25;
|
||||
var label_50;
|
||||
var label_75;
|
||||
|
||||
var label_rx_cur;
|
||||
var label_rx_avg;
|
||||
var label_rx_peak;
|
||||
|
||||
var label_tx_cur;
|
||||
var label_tx_avg;
|
||||
var label_tx_peak;
|
||||
|
||||
var label_scale;
|
||||
|
||||
|
||||
function bandwidth_label(bytes, br)
|
||||
{
|
||||
var uby = '<%:kB/s%>';
|
||||
var kby = (bytes / 1024);
|
||||
|
||||
if (kby >= 1024)
|
||||
{
|
||||
uby = '<%:MB/s%>';
|
||||
kby = kby / 1024;
|
||||
}
|
||||
|
||||
var ubi = '<%:kbit/s%>';
|
||||
var kbi = (bytes * 8 / 1024);
|
||||
|
||||
if (kbi >= 1024)
|
||||
{
|
||||
ubi = '<%:Mbit/s%>';
|
||||
kbi = kbi / 1024;
|
||||
}
|
||||
|
||||
return String.format("%f %s%s(%f %s)",
|
||||
kbi.toFixed(2), ubi,
|
||||
br ? '<br />' : ' ',
|
||||
kby.toFixed(2), uby
|
||||
);
|
||||
}
|
||||
|
||||
/* wait for SVG */
|
||||
window.setTimeout(
|
||||
function() {
|
||||
var svg = document.getElementById('bwsvg');
|
||||
|
||||
try {
|
||||
G = svg.getSVGDocument
|
||||
? svg.getSVGDocument() : svg.contentDocument;
|
||||
}
|
||||
catch(e) {
|
||||
G = document.embeds['bwsvg'].getSVGDocument();
|
||||
}
|
||||
|
||||
if (!G)
|
||||
{
|
||||
window.setTimeout(arguments.callee, 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* find sizes */
|
||||
width = svg.offsetWidth - 2;
|
||||
height = svg.offsetHeight - 2;
|
||||
data_wanted = Math.ceil(width / step);
|
||||
|
||||
/* prefill datasets */
|
||||
for (var i = 0; i < data_wanted; i++)
|
||||
{
|
||||
data_rx[i] = 0;
|
||||
data_tx[i] = 0;
|
||||
}
|
||||
|
||||
/* find svg elements */
|
||||
line_rx = G.getElementById('rx');
|
||||
line_tx = G.getElementById('tx');
|
||||
|
||||
label_25 = G.getElementById('label_25');
|
||||
label_50 = G.getElementById('label_50');
|
||||
label_75 = G.getElementById('label_75');
|
||||
|
||||
label_rx_cur = document.getElementById('rx_bw_cur');
|
||||
label_rx_avg = document.getElementById('rx_bw_avg');
|
||||
label_rx_peak = document.getElementById('rx_bw_peak');
|
||||
|
||||
label_tx_cur = document.getElementById('tx_bw_cur');
|
||||
label_tx_avg = document.getElementById('tx_bw_avg');
|
||||
label_tx_peak = document.getElementById('tx_bw_peak');
|
||||
|
||||
label_scale = document.getElementById('scale');
|
||||
|
||||
/* plot horizontal time interval lines */
|
||||
for (var i = width % (step * 60); i < width; i += step * 60)
|
||||
{
|
||||
var line = G.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||
line.setAttribute('x1', i);
|
||||
line.setAttribute('y1', 0);
|
||||
line.setAttribute('x2', i);
|
||||
line.setAttribute('y2', '100%');
|
||||
line.setAttribute('style', 'stroke:black;stroke-width:0.1');
|
||||
|
||||
var text = G.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
text.setAttribute('x', i + 5);
|
||||
text.setAttribute('y', 15);
|
||||
text.setAttribute('style', 'fill:#999999; font-size:9pt');
|
||||
text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm'));
|
||||
|
||||
label_25.parentNode.appendChild(line);
|
||||
label_25.parentNode.appendChild(text);
|
||||
}
|
||||
|
||||
label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3);
|
||||
|
||||
/* render datasets, start update interval */
|
||||
XHR.poll(3, '<%=build_url(bandwidthintfurl, curifname)%>', null,
|
||||
function(x, data)
|
||||
{
|
||||
var data_max = 0;
|
||||
var data_scale = 0;
|
||||
|
||||
var data_rx_avg = 0;
|
||||
var data_tx_avg = 0;
|
||||
|
||||
var data_rx_peak = 0;
|
||||
var data_tx_peak = 0;
|
||||
|
||||
for (var i = data_stamp ? 0 : 1; i < data.length; i++)
|
||||
{
|
||||
/* skip overlapping entries */
|
||||
if (data[i][TIME] <= data_stamp)
|
||||
continue;
|
||||
|
||||
/* normalize difference against time interval */
|
||||
if (i > 0)
|
||||
{
|
||||
var time_delta = data[i][TIME] - data[i-1][TIME];
|
||||
if (time_delta)
|
||||
{
|
||||
data_rx.push((data[i][RXB] - data[i-1][RXB]) / time_delta);
|
||||
data_tx.push((data[i][TXB] - data[i-1][TXB]) / time_delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* cut off outdated entries */
|
||||
data_rx = data_rx.slice(data_rx.length - data_wanted, data_rx.length);
|
||||
data_tx = data_tx.slice(data_tx.length - data_wanted, data_tx.length);
|
||||
|
||||
/* find peak */
|
||||
for (var i = 0; i < data_rx.length; i++)
|
||||
{
|
||||
data_max = Math.max(data_max, data_rx[i]);
|
||||
data_max = Math.max(data_max, data_tx[i]);
|
||||
|
||||
data_rx_peak = Math.max(data_rx_peak, data_rx[i]);
|
||||
data_tx_peak = Math.max(data_tx_peak, data_tx[i]);
|
||||
|
||||
data_rx_avg += data_rx[i];
|
||||
data_tx_avg += data_tx[i];
|
||||
}
|
||||
data_rx_avg = (data_rx_avg / Math.max(data_rx.length, 1));
|
||||
data_tx_avg = (data_tx_avg / Math.max(data_tx.length, 1));
|
||||
|
||||
var size = Math.floor(Math.log2(data_max)),
|
||||
div = Math.pow(2, size - (size % 10)),
|
||||
mult = data_max / div,
|
||||
mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000));
|
||||
|
||||
data_max = data_max + (mult * div) - (data_max % (mult * div));
|
||||
|
||||
/* remember current timestamp, calculate horizontal scale */
|
||||
data_stamp = data[data.length-1][TIME];
|
||||
data_scale = height / data_max;
|
||||
|
||||
|
||||
/* plot data */
|
||||
var pt_rx = '0,' + height;
|
||||
var pt_tx = '0,' + height;
|
||||
|
||||
var y_rx = 0;
|
||||
var y_tx = 0;
|
||||
|
||||
for (var i = 0; i < data_rx.length; i++)
|
||||
{
|
||||
var x = i * step;
|
||||
|
||||
y_rx = height - Math.floor(data_rx[i] * data_scale);
|
||||
y_tx = height - Math.floor(data_tx[i] * data_scale);
|
||||
|
||||
pt_rx += ' ' + x + ',' + y_rx;
|
||||
pt_tx += ' ' + x + ',' + y_tx;
|
||||
}
|
||||
|
||||
pt_rx += ' ' + width + ',' + y_rx + ' ' + width + ',' + height;
|
||||
pt_tx += ' ' + width + ',' + y_tx + ' ' + width + ',' + height;
|
||||
|
||||
|
||||
line_rx.setAttribute('points', pt_rx);
|
||||
line_tx.setAttribute('points', pt_tx);
|
||||
|
||||
label_25.firstChild.data = bandwidth_label(0.25 * data_max);
|
||||
label_50.firstChild.data = bandwidth_label(0.50 * data_max);
|
||||
label_75.firstChild.data = bandwidth_label(0.75 * data_max);
|
||||
|
||||
label_rx_cur.innerHTML = bandwidth_label(data_rx[data_rx.length-1], true);
|
||||
label_tx_cur.innerHTML = bandwidth_label(data_tx[data_tx.length-1], true);
|
||||
|
||||
label_rx_avg.innerHTML = bandwidth_label(data_rx_avg, true);
|
||||
label_tx_avg.innerHTML = bandwidth_label(data_tx_avg, true);
|
||||
|
||||
label_rx_peak.innerHTML = bandwidth_label(data_rx_peak, true);
|
||||
label_tx_peak.innerHTML = bandwidth_label(data_tx_peak, true);
|
||||
}
|
||||
);
|
||||
XHR.run();
|
||||
}
|
||||
}, 2000
|
||||
<% end %>
|
||||
);
|
||||
// Set 1h reload page timer because of suspected SVG graph memmory leak
|
||||
window.setTimeout(function()
|
||||
{
|
||||
if (!activating)
|
||||
location.reload(true)
|
||||
}, 3600000)
|
||||
//]]></script>
|
||||
|
||||
<h2><a id="content" name="content"><%:Realtime Traffic%></a></h2>
|
||||
|
||||
<ul class="cbi-tabmenu">
|
||||
<% for _, dev in ipairs(devices) do
|
||||
local ifname = get_device(dev)
|
||||
local label = uci:get("network",dev,"label")
|
||||
if label ~= nil then
|
||||
%>
|
||||
<li class="cbi-tab<%= ifname == curifname and "" or "-disabled" %>"><a href="?dev=<%=pcdata(get_device(dev))%>"><%=label%></a></li>
|
||||
<% else %>
|
||||
<li class="cbi-tab<%= ifname == curifname and "" or "-disabled" %>"><a href="?dev=<%=pcdata(get_device(dev))%>"><%=pcdata(dev)%></a></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<% if curifname == "all" then %>
|
||||
|
||||
<strong><%:Download:%></strong>
|
||||
<embed id="dnsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/svg/bandwidth.svg" />
|
||||
<div style="text-align:right"><small id="dnscale">-</small></div>
|
||||
<table style="width:100%; table-layout:fixed" cellspacing="5" id="download_stats">
|
||||
</table>
|
||||
|
||||
<strong><%:Upload:%></strong>
|
||||
<embed id="upsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/svg/bandwidth.svg" />
|
||||
<div style="text-align:right"><small id="upscale">-</small></div>
|
||||
<table style="width:100%; table-layout:fixed" cellspacing="5" id="upload_stats">
|
||||
</table>
|
||||
<br />
|
||||
|
||||
<% else %>
|
||||
|
||||
<embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/svg/bandwidth.svg" />
|
||||
<div style="text-align:right"><small id="scale">-</small></div>
|
||||
<br />
|
||||
|
||||
<table style="width:100%; table-layout:fixed" cellspacing="5">
|
||||
<tr>
|
||||
<td style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:Inbound:%></strong></td>
|
||||
<td id="rx_bw_cur">0 <%:kbit/s%><br />(0 <%:kB/s%>)</td>
|
||||
|
||||
<td style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></td>
|
||||
<td id="rx_bw_avg">0 <%:kbit/s%><br />(0 <%:kB/s%>)</td>
|
||||
|
||||
<td style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></td>
|
||||
<td id="rx_bw_peak">0 <%:kbit/s%><br />(0 <%:kB/s%>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:Outbound:%></strong></td>
|
||||
<td id="tx_bw_cur">0 <%:kbit/s%><br />(0 <%:kB/s%>)</td>
|
||||
|
||||
<td style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></td>
|
||||
<td id="tx_bw_avg">0 <%:kbit/s%><br />(0 <%:kB/s%>)</td>
|
||||
|
||||
<td style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></td>
|
||||
<td id="tx_bw_peak">0 <%:kbit/s%><br />(0 <%:kB/s%>)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<% end %>
|
||||
|
||||
|
||||
<%+footer%>
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
-- Copyright 2018-2023 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
--
|
||||
-- rpcd script exposing MPTCP bandwidth and diagnostic data as UBUS/RPC methods,
|
||||
-- replacing the legacy Lua HTTP controller handlers.
|
||||
|
||||
local jsonc = require "luci.jsonc"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- Helpers
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
local function get_device(interface)
|
||||
if not interface or interface == "" then return "" end
|
||||
local dump = require("luci.util").ubus("network.interface." .. interface, "status", {})
|
||||
if dump and dump["l3_device"] then
|
||||
return dump["l3_device"]
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
local function exec_output(cmd)
|
||||
local lines = {}
|
||||
local f = io.popen(cmd)
|
||||
if f then
|
||||
for line in f:lines() do
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
-- Returns an array of [ts, rx_bytes, rx_pkts, tx_bytes, tx_pkts] rows from luci-bwc.
|
||||
-- luci-bwc outputs one "[ ts, rx, rxp, tx, txp ]," entry per line (no outer brackets).
|
||||
-- The trailing comma is determined by file position, not by whether the next entry is
|
||||
-- non-zero: when the circular buffer is partially filled the last valid row still gets
|
||||
-- a trailing comma. Strip it before wrapping with "[...]" to produce valid JSON.
|
||||
local function bwc_rows(dev)
|
||||
local f = io.popen(string.format("luci-bwc -i %q 2>/dev/null", dev))
|
||||
if not f then return {} end
|
||||
local output = f:read("*all")
|
||||
f:close()
|
||||
if not output or output:match("^%s*$") then return {} end
|
||||
output = output:gsub(",%s*$", "")
|
||||
local ok, rows = pcall(jsonc.parse, "[" .. output .. "]")
|
||||
if ok and type(rows) == "table" then return rows end
|
||||
return {}
|
||||
end
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- RPC methods
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
local methods = {
|
||||
|
||||
-- Returns per-interface bandwidth arrays plus a "total" entry.
|
||||
-- Each value is an array of [ts, rx_bytes, rx_pkts, tx_bytes, tx_pkts].
|
||||
multipath_bandwidth = {
|
||||
call = function()
|
||||
local result = {}
|
||||
local iface_rows = {} -- interface -> rows (only non-empty)
|
||||
|
||||
uci:foreach("network", "interface", function(s)
|
||||
local intname = s[".name"]
|
||||
local dev = get_device(intname)
|
||||
if dev == "" then dev = s["device"] or s["ifname"] or "" end
|
||||
|
||||
local multipath = s["multipath"] or ""
|
||||
if dev ~= "lo" and dev ~= "" then
|
||||
if multipath == "" then
|
||||
multipath = uci:get("openmptcprouter", intname, "multipath") or ""
|
||||
end
|
||||
if multipath == "" then multipath = "off" end
|
||||
|
||||
if multipath == "on" or multipath == "master" or
|
||||
multipath == "backup" or multipath == "handover" then
|
||||
local rows = bwc_rows(dev)
|
||||
result[intname] = rows
|
||||
if #rows > 0 then
|
||||
iface_rows[intname] = rows
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Build a correctly aligned cumulative total using last-known-value
|
||||
-- interpolation. The old approach summed raw cumulative counters at
|
||||
-- each timestamp, but different interfaces record data at different
|
||||
-- (interleaved) timestamps. When consecutive total entries came from
|
||||
-- different interfaces, rate = (total[i] - total[i-1]) / dt could be
|
||||
-- negative or zero even under real traffic.
|
||||
--
|
||||
-- Fix: for every timestamp in the union of all interface timestamps,
|
||||
-- use the latest known cumulative bytes from EACH interface (carry the
|
||||
-- last seen value forward). This keeps the total monotonically
|
||||
-- increasing so JS rate computation stays correct.
|
||||
local ts_set = {}
|
||||
local all_ts = {}
|
||||
for _, rows in pairs(iface_rows) do
|
||||
for _, row in ipairs(rows) do
|
||||
local ts = row[1]
|
||||
if not ts_set[ts] then
|
||||
ts_set[ts] = true
|
||||
all_ts[#all_ts + 1] = ts
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(all_ts)
|
||||
|
||||
-- One forward-scan pointer per interface
|
||||
local iface_ptrs = {}
|
||||
for itf, rows in pairs(iface_rows) do
|
||||
iface_ptrs[itf] = { idx = 1, rows = rows }
|
||||
end
|
||||
|
||||
local iface_last = {} -- latest row seen so far per interface
|
||||
local total_rows = {}
|
||||
for _, ts in ipairs(all_ts) do
|
||||
-- Advance each pointer up to (and including) this timestamp
|
||||
for itf, ptr in pairs(iface_ptrs) do
|
||||
while ptr.idx <= #ptr.rows and ptr.rows[ptr.idx][1] <= ts do
|
||||
iface_last[itf] = ptr.rows[ptr.idx]
|
||||
ptr.idx = ptr.idx + 1
|
||||
end
|
||||
end
|
||||
-- Sum latest known cumulative bytes from every interface
|
||||
local total = { ts, 0, 0, 0, 0 }
|
||||
for _, last_row in pairs(iface_last) do
|
||||
for j = 2, 5 do
|
||||
total[j] = total[j] + (last_row[j] or 0)
|
||||
end
|
||||
end
|
||||
total_rows[#total_rows + 1] = total
|
||||
end
|
||||
|
||||
result["total"] = total_rows
|
||||
return result
|
||||
end
|
||||
},
|
||||
|
||||
-- Returns bandwidth rows for a single device as { data: [...] }.
|
||||
interface_bandwidth = {
|
||||
args = { iface = "" },
|
||||
call = function(args)
|
||||
return { data = bwc_rows(args.iface or "") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs "multipath -f" and returns its output.
|
||||
mptcp_fullmesh = {
|
||||
call = function()
|
||||
return { output = exec_output("multipath -f 2>/dev/null") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs "multipath -m" and returns its output.
|
||||
mptcp_monitor = {
|
||||
call = function()
|
||||
return { output = exec_output("multipath -m 2>/dev/null") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs "multipath -c" and returns its output.
|
||||
mptcp_connections = {
|
||||
call = function()
|
||||
return { output = exec_output("multipath -c 2>/dev/null") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs tracebox against the configured VPN server via the given interface.
|
||||
mptcp_check_trace = {
|
||||
args = { iface = "" },
|
||||
call = function(args)
|
||||
local iface = args.iface or ""
|
||||
local interface = get_device(iface)
|
||||
local server = uci:get("shadowsocks-libev", "sss0", "server") or ""
|
||||
if server == "" then return { output = "" } end
|
||||
local cmd
|
||||
if interface == "" then
|
||||
cmd = "tracebox -s /usr/share/tracebox/omr-mptcp-trace.lua " .. server
|
||||
else
|
||||
cmd = "tracebox -s /usr/share/tracebox/omr-mptcp-trace.lua -i " .. interface .. " " .. server
|
||||
end
|
||||
return { output = exec_output(cmd) }
|
||||
end
|
||||
},
|
||||
}
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- rpcd dispatch (same boilerplate used by all rpcd Lua scripts in this repo)
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
local function parseInput()
|
||||
local parse = jsonc.new()
|
||||
local done, err
|
||||
while true do
|
||||
local chunk = io.read(4096)
|
||||
if not chunk then break end
|
||||
if not done and not err then
|
||||
done, err = parse:parse(chunk)
|
||||
end
|
||||
end
|
||||
if not done then
|
||||
print(jsonc.stringify({ error = err or "Incomplete input" }))
|
||||
os.exit(1)
|
||||
end
|
||||
return parse:get()
|
||||
end
|
||||
|
||||
local function validateArgs(func, uargs)
|
||||
local method = methods[func]
|
||||
if not method then
|
||||
print(jsonc.stringify({ error = "Method not found" }))
|
||||
os.exit(1)
|
||||
end
|
||||
if type(uargs) ~= "table" then
|
||||
print(jsonc.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
uargs.ubus_rpc_session = nil
|
||||
local margs = method.args or {}
|
||||
for k, v in pairs(uargs) do
|
||||
if margs[k] == nil or (v ~= nil and type(v) ~= type(margs[k])) then
|
||||
print(jsonc.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
return method
|
||||
end
|
||||
|
||||
if arg[1] == "list" then
|
||||
local rv = {}
|
||||
for name, method in pairs(methods) do rv[name] = method.args or {} end
|
||||
print((jsonc.stringify(rv):gsub(":%[%]", ":{}")))
|
||||
elseif arg[1] == "call" then
|
||||
local args = parseInput()
|
||||
local method = validateArgs(arg[2], args)
|
||||
local result, code = method.call(args)
|
||||
print((jsonc.stringify(result):gsub("^%[%]$", "{}")))
|
||||
os.exit(code or 0)
|
||||
end
|
||||
@@ -1,10 +1,65 @@
|
||||
{
|
||||
"admin/network/mptcp": {
|
||||
"title": "MPTCP",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/network/mptcp/mptcp"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mptcp" ]
|
||||
}
|
||||
},
|
||||
"admin/network/mptcp/mptcp": {
|
||||
"title": "MPTCP",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "cbi",
|
||||
"path": "mptcp"
|
||||
"type": "view",
|
||||
"path": "mptcp/mptcp"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mptcp" ]
|
||||
}
|
||||
},
|
||||
"admin/network/mptcp/bandwidth": {
|
||||
"title": "Bandwidth",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mptcp/multipath"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mptcp" ]
|
||||
}
|
||||
},
|
||||
"admin/network/mptcp/mptcp_connections": {
|
||||
"title": "Established connections",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mptcp/mptcp_connections"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mptcp" ]
|
||||
}
|
||||
},
|
||||
"admin/network/mptcp/mptcp_fullmesh": {
|
||||
"title": "MPTCP Fullmesh",
|
||||
"order": 50,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mptcp/mptcp_fullmesh"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mptcp" ]
|
||||
}
|
||||
},
|
||||
"admin/network/mptcp/mptcp_monitor": {
|
||||
"title": "MPTCP monitoring",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mptcp/mptcp_monitor"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mptcp" ]
|
||||
|
||||
@@ -10,7 +10,16 @@
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "list", "exec" ],
|
||||
"system": [ "board" ]
|
||||
"system": [ "board" ],
|
||||
"network.interface": [ "dump" ],
|
||||
"luci.mptcp": [
|
||||
"multipath_bandwidth",
|
||||
"interface_bandwidth",
|
||||
"mptcp_fullmesh",
|
||||
"mptcp_monitor",
|
||||
"mptcp_connections",
|
||||
"mptcp_check_trace"
|
||||
]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.4.1
|
||||
PKG_VERSION:=26.4.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ if not arg[1] or not m:get(arg[1]) then
|
||||
luci.http.redirect(m.redirect)
|
||||
end
|
||||
|
||||
fs = require "nixio.fs"
|
||||
formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
|
||||
|
||||
local header = Template(appname .. "/node_config/header")
|
||||
header.api = api
|
||||
header.section = arg[1]
|
||||
@@ -61,12 +64,18 @@ o.write = function(self, section, value)
|
||||
m:set(section, self.option, value)
|
||||
end
|
||||
|
||||
local fs = api.fs
|
||||
local types_dir = "/usr/lib/lua/luci/model/cbi/passwall/client/type/"
|
||||
s.val = {}
|
||||
s.val["type"] = m.uci:get(appname, arg[1], "type")
|
||||
s.val["protocol"] = m.uci:get(appname, arg[1], "protocol")
|
||||
|
||||
if luci.http.formvalue("cbi.submit") == "1" then
|
||||
local formvalue_type = luci.http.formvalue(formvalue_key .. "type")
|
||||
if formvalue_type then
|
||||
s.val["type"] = formvalue_type
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "type", translate("Type"))
|
||||
|
||||
if api.is_finded("ipt2socks") then
|
||||
|
||||
@@ -25,7 +25,6 @@ local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
|
||||
local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
|
||||
|
||||
if formvalue_proto then s.val["protocol"] = formvalue_proto end
|
||||
|
||||
@@ -25,7 +25,6 @@ local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
|
||||
local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
|
||||
|
||||
if formvalue_proto then s.val["protocol"] = formvalue_proto end
|
||||
|
||||
@@ -527,7 +527,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
["User-Agent"] = node.user_agent
|
||||
} or nil,
|
||||
quic = node.naive_quic == "1" and true or false,
|
||||
quic_congestion_control = (node.naive_quic == "1" and node.naive_congestion_control) and node.naive_congestion_control or nil
|
||||
quic_congestion_control = (node.naive_quic == "1" and node.naive_congestion_control) and node.naive_congestion_control or nil,
|
||||
tls = tls
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
local api = self.api
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
let node_id = "<%=self.section%>";
|
||||
let url = '<%=api.url("node_config")%>/' + node_id;
|
||||
//<![CDATA[
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
waitForElement('select[name*="<%=api.appname%>"][name*="type"]', function(el) {
|
||||
@@ -13,8 +11,12 @@ local api = self.api
|
||||
if (o_val != el.value) {
|
||||
let save = true;
|
||||
if (save) {
|
||||
//document.getElementsByClassName('cbi-button-save')[0].click();
|
||||
update_node({
|
||||
type: getOption("type").value
|
||||
remarks: getOption("remarks").value,
|
||||
group: getOption("group").value,
|
||||
type: getOption("type").value,
|
||||
//protocol: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -29,11 +31,14 @@ local api = self.api
|
||||
let name = name_split[name_split.length - 1];
|
||||
let save = true;
|
||||
if (save) {
|
||||
//document.getElementsByClassName('cbi-button-save')[0].click();
|
||||
update_node({
|
||||
remarks: getOption("remarks").value,
|
||||
group: getOption("group").value,
|
||||
protocol: getOption(name).value
|
||||
});
|
||||
} else {
|
||||
window.location.href = url + "?select_proto=" + el.value;
|
||||
window.location.href = node_config_url + "?select_proto=" + el.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,9 @@ local api = self.api
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let node_id = "<%=self.section%>";
|
||||
let node_config_url = '<%=api.url("node_config")%>/' + node_id;
|
||||
|
||||
function waitForElement(selector, callback) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) return callback(el);
|
||||
@@ -18,7 +21,7 @@ local api = self.api
|
||||
|
||||
function getOption(opt) {
|
||||
var obj;
|
||||
var id = 'cbid.<%=api.appname%>.<%=self.section%>.' + opt;
|
||||
var id = 'cbid.<%=api.appname%>.' + node_id + '.' + opt;
|
||||
obj = document.getElementsByName(id)[0] || document.getElementById(id);
|
||||
if (obj) {
|
||||
var combobox = document.getElementById('cbi.combobox.' + id);
|
||||
@@ -37,12 +40,12 @@ local api = self.api
|
||||
|
||||
function update_node(data) {
|
||||
XHR.get('<%=api.url("update_node")%>', {
|
||||
id: "<%=self.section%>",
|
||||
id: node_id,
|
||||
data: JSON.stringify(data)
|
||||
},
|
||||
function(x, data) {
|
||||
if (x && x.status == 200 && data.code == 1) {
|
||||
window.location.reload();
|
||||
window.location.href = node_config_url;
|
||||
}
|
||||
else {
|
||||
alert("<%:Error%>");
|
||||
|
||||
@@ -30,6 +30,8 @@ FORCE_INDEX=2
|
||||
USE_SHUNT_TCP=0
|
||||
USE_SHUNT_UDP=0
|
||||
|
||||
FWMARK="0x50535732"
|
||||
|
||||
ipt=$(command -v iptables-legacy || command -v iptables)
|
||||
ip6t=$(command -v ip6tables-legacy || command -v ip6tables)
|
||||
|
||||
@@ -165,15 +167,13 @@ RULE_LAST_INDEX() {
|
||||
|
||||
REDIRECT() {
|
||||
local s="-j REDIRECT"
|
||||
if [ -n "$1" ]; then
|
||||
s="$s --to-ports $1"
|
||||
if [ "$2" = "MARK" ]; then
|
||||
s="-j MARK --set-mark $1"
|
||||
elif [ "$2" = "TPROXY" ]; then
|
||||
local mark="-m mark --mark 1"
|
||||
s="${mark} -j TPROXY --tproxy-mark 1/1 --on-port $1"
|
||||
fi
|
||||
fi
|
||||
[ -n "$1" ] && {
|
||||
local s="$s --to-ports $1"
|
||||
[ "$2" == "TPROXY" ] && {
|
||||
local mark="-m mark --mark ${FWMARK}"
|
||||
s="${mark} -j TPROXY --on-port $1"
|
||||
}
|
||||
}
|
||||
echo "$s"
|
||||
}
|
||||
|
||||
@@ -982,7 +982,7 @@ add_firewall_rule() {
|
||||
$ipt_n -A PSW_OUTPUT $(dst $IPSET_LAN) -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT $(dst $IPSET_VPS) -j RETURN
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ipt_n -A PSW_OUTPUT $(dst $IPSET_WHITE) -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
|
||||
$ipt_n -N PSW_DNS
|
||||
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
|
||||
@@ -993,14 +993,14 @@ add_firewall_rule() {
|
||||
fi
|
||||
|
||||
$ipt_m -N PSW_DIVERT
|
||||
$ipt_m -A PSW_DIVERT -j MARK --set-mark 1
|
||||
$ipt_m -A PSW_DIVERT -j MARK --set-mark ${FWMARK}
|
||||
$ipt_m -A PSW_DIVERT -j ACCEPT
|
||||
|
||||
$ipt_m -N PSW_RULE
|
||||
$ipt_m -A PSW_RULE -j CONNMARK --restore-mark
|
||||
$ipt_m -A PSW_RULE -m mark --mark 1 -j RETURN
|
||||
$ipt_m -A PSW_RULE -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j MARK --set-xmark 1
|
||||
$ipt_m -A PSW_RULE -p udp -m conntrack --ctstate NEW -j MARK --set-xmark 1
|
||||
$ipt_m -A PSW_RULE -m mark --mark ${FWMARK} -j RETURN
|
||||
$ipt_m -A PSW_RULE -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j MARK --set-xmark ${FWMARK}
|
||||
$ipt_m -A PSW_RULE -p udp -m conntrack --ctstate NEW -j MARK --set-xmark ${FWMARK}
|
||||
$ipt_m -A PSW_RULE -j CONNMARK --save-mark
|
||||
|
||||
$ipt_m -N PSW
|
||||
@@ -1019,6 +1019,7 @@ add_firewall_rule() {
|
||||
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW"
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p udp -m socket -j PSW_DIVERT"
|
||||
|
||||
$ipt_m -N PSW_OUTPUT
|
||||
$ipt_m -A PSW_OUTPUT $(dst $IPSET_LAN) -j RETURN
|
||||
@@ -1043,10 +1044,10 @@ add_firewall_rule() {
|
||||
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && $ipt_m -A PSW_OUTPUT $(dst $IPSET_BLOCK) -j DROP
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ipt_m -A PSW_OUTPUT $(dst $IPSET_WHITE) -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
ip rule add fwmark ${FWMARK} lookup 999 priority 999
|
||||
ip route add local 0.0.0.0/0 dev lo table 999
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && {
|
||||
$ip6t_n -N PSW
|
||||
@@ -1058,7 +1059,7 @@ add_firewall_rule() {
|
||||
$ip6t_n -A PSW_OUTPUT $(dst $IPSET_LAN6) -j RETURN
|
||||
$ip6t_n -A PSW_OUTPUT $(dst $IPSET_VPS6) -j RETURN
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ip6t_n -A PSW_OUTPUT $(dst $IPSET_WHITE6) -j RETURN
|
||||
$ip6t_n -A PSW_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
$ip6t_n -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
}
|
||||
|
||||
$ip6t_n -N PSW_DNS
|
||||
@@ -1070,14 +1071,14 @@ add_firewall_rule() {
|
||||
fi
|
||||
|
||||
$ip6t_m -N PSW_DIVERT
|
||||
$ip6t_m -A PSW_DIVERT -j MARK --set-mark 1
|
||||
$ip6t_m -A PSW_DIVERT -j MARK --set-mark ${FWMARK}
|
||||
$ip6t_m -A PSW_DIVERT -j ACCEPT
|
||||
|
||||
$ip6t_m -N PSW_RULE
|
||||
$ip6t_m -A PSW_RULE -j CONNMARK --restore-mark
|
||||
$ip6t_m -A PSW_RULE -m mark --mark 1 -j RETURN
|
||||
$ip6t_m -A PSW_RULE -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j MARK --set-xmark 1
|
||||
$ip6t_m -A PSW_RULE -p udp -m conntrack --ctstate NEW -j MARK --set-xmark 1
|
||||
$ip6t_m -A PSW_RULE -m mark --mark ${FWMARK} -j RETURN
|
||||
$ip6t_m -A PSW_RULE -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j MARK --set-xmark ${FWMARK}
|
||||
$ip6t_m -A PSW_RULE -p udp -m conntrack --ctstate NEW -j MARK --set-xmark ${FWMARK}
|
||||
$ip6t_m -A PSW_RULE -j CONNMARK --save-mark
|
||||
|
||||
$ip6t_m -N PSW
|
||||
@@ -1097,16 +1098,17 @@ add_firewall_rule() {
|
||||
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW"
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p udp -m socket -j PSW_DIVERT"
|
||||
|
||||
$ip6t_m -N PSW_OUTPUT
|
||||
$ip6t_m -A PSW_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
$ip6t_m -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
$ip6t_m -A PSW_OUTPUT $(dst $IPSET_LAN6) -j RETURN
|
||||
$ip6t_m -A PSW_OUTPUT $(dst $IPSET_VPS6) -j RETURN
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && $ip6t_m -A PSW_OUTPUT $(dst $IPSET_BLOCK6) -j DROP
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ip6t_m -A PSW_OUTPUT $(dst $IPSET_WHITE6) -j RETURN
|
||||
|
||||
ip -6 rule add fwmark 1 table 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
ip -6 rule add fwmark ${FWMARK} table 999 priority 999
|
||||
ip -6 route add local ::/0 dev lo table 999
|
||||
|
||||
[ "$TCP_UDP" = "1" ] && [ -z "$UDP_NODE" ] && UDP_NODE=$TCP_NODE
|
||||
|
||||
@@ -1306,10 +1308,10 @@ add_firewall_rule() {
|
||||
fi
|
||||
|
||||
$ipt_m -I OUTPUT $(comment "mangle-OUTPUT-PSW") -o lo -j RETURN
|
||||
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW) -m mark --mark 1 -j RETURN"
|
||||
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW) -m mark --mark ${FWMARK} -j RETURN"
|
||||
|
||||
$ip6t_m -I OUTPUT $(comment "mangle-OUTPUT-PSW") -o lo -j RETURN
|
||||
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW) -m mark --mark 1 -j RETURN"
|
||||
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW) -m mark --mark ${FWMARK} -j RETURN"
|
||||
}
|
||||
|
||||
# 加载ACLS
|
||||
@@ -1322,8 +1324,8 @@ add_firewall_rule() {
|
||||
done
|
||||
}
|
||||
|
||||
$ipt_n -I PREROUTING $(comment "PSW") -m mark --mark 1 -j RETURN
|
||||
$ip6t_n -I PREROUTING $(comment "PSW") -m mark --mark 1 -j RETURN
|
||||
$ipt_n -I PREROUTING $(comment "PSW") -m mark --mark ${FWMARK} -j RETURN
|
||||
$ip6t_n -I PREROUTING $(comment "PSW") -m mark --mark ${FWMARK} -j RETURN
|
||||
|
||||
filter_direct_node_list > /dev/null 2>&1 &
|
||||
|
||||
@@ -1344,11 +1346,11 @@ del_firewall_rule() {
|
||||
done
|
||||
done
|
||||
|
||||
ip rule del fwmark 1 lookup 100 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
|
||||
ip rule del fwmark ${FWMARK} lookup 999 priority 999 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
|
||||
|
||||
ip -6 rule del fwmark 1 table 100 2>/dev/null
|
||||
ip -6 route del local ::/0 dev lo table 100 2>/dev/null
|
||||
ip -6 rule del fwmark ${FWMARK} table 999 priority 999 2>/dev/null
|
||||
ip -6 route del local ::/0 dev lo table 999 2>/dev/null
|
||||
|
||||
destroy_ipset $IPSET_LOCAL
|
||||
destroy_ipset $IPSET_WAN
|
||||
@@ -1417,6 +1419,7 @@ gen_include() {
|
||||
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW")
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT")
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW" "-p udp -m socket -j PSW_DIVERT")
|
||||
|
||||
WAN_IP=\$(get_wan_ips ip4)
|
||||
[ ! -z "\${WAN_IP}" ] && {
|
||||
@@ -1447,6 +1450,7 @@ gen_include() {
|
||||
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW")
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT")
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p udp -m socket -j PSW_DIVERT")
|
||||
|
||||
WAN6_IP=\$(get_wan_ips ip6)
|
||||
[ ! -z "\${WAN6_IP}" ] && {
|
||||
|
||||
@@ -31,6 +31,8 @@ FORCE_INDEX=0
|
||||
USE_SHUNT_TCP=0
|
||||
USE_SHUNT_UDP=0
|
||||
|
||||
FWMARK="0x50535732"
|
||||
|
||||
FWI=$(uci -q get firewall.passwall.path 2>/dev/null)
|
||||
FAKE_IP="198.18.0.0/15"
|
||||
FAKE_IP_6="fc00::/18"
|
||||
@@ -120,17 +122,15 @@ REDIRECT() {
|
||||
local s="counter redirect"
|
||||
[ -n "$1" ] && {
|
||||
local s="$s to :$1"
|
||||
[ "$2" == "MARK" ] && s="counter meta mark set $1"
|
||||
[ "$2" == "TPROXY" ] && {
|
||||
s="counter meta mark 1 tproxy to :$1"
|
||||
s="counter meta mark ${FWMARK} tproxy to :$1"
|
||||
}
|
||||
[ "$2" == "TPROXY4" ] && {
|
||||
s="counter meta mark 1 tproxy ip to :$1"
|
||||
s="counter meta mark ${FWMARK} tproxy ip to :$1"
|
||||
}
|
||||
[ "$2" == "TPROXY6" ] && {
|
||||
s="counter meta mark 1 tproxy ip6 to :$1"
|
||||
s="counter meta mark ${FWMARK} tproxy ip6 to :$1"
|
||||
}
|
||||
|
||||
}
|
||||
echo $s
|
||||
}
|
||||
@@ -1012,7 +1012,8 @@ add_firewall_rule() {
|
||||
|
||||
nft "add chain $NFTABLE_NAME PSW_DIVERT"
|
||||
nft "flush chain $NFTABLE_NAME PSW_DIVERT"
|
||||
nft "add rule $NFTABLE_NAME PSW_DIVERT meta l4proto tcp socket transparent 1 mark set 1 counter accept"
|
||||
nft "add rule $NFTABLE_NAME PSW_DIVERT meta l4proto tcp socket transparent 1 mark set ${FWMARK} counter accept"
|
||||
nft "add rule $NFTABLE_NAME PSW_DIVERT meta l4proto udp socket transparent 1 mark set ${FWMARK} counter accept"
|
||||
|
||||
nft "add chain $NFTABLE_NAME PSW_DNS"
|
||||
nft "flush chain $NFTABLE_NAME PSW_DNS"
|
||||
@@ -1027,11 +1028,11 @@ add_firewall_rule() {
|
||||
# for ipv4 ipv6 tproxy mark
|
||||
nft "add chain $NFTABLE_NAME PSW_RULE"
|
||||
nft "flush chain $NFTABLE_NAME PSW_RULE"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE meta mark set ct mark counter"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE meta mark 1 counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE tcp flags &(fin|syn|rst|ack) == syn meta mark set mark and 0x0 xor 0x1 counter"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE meta l4proto udp ct state new meta mark set mark and 0x0 xor 0x1 counter"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE ct mark set mark counter"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE counter meta mark set ct mark"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE meta mark ${FWMARK} counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE tcp flags & (fin|syn|rst|ack) == syn counter meta mark set ${FWMARK}"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE meta l4proto udp ct state new,related counter meta mark set ${FWMARK}"
|
||||
nft "add rule $NFTABLE_NAME PSW_RULE counter ct mark set mark"
|
||||
|
||||
#ipv4 tproxy mode and udp
|
||||
nft "add chain $NFTABLE_NAME PSW_MANGLE"
|
||||
@@ -1046,7 +1047,7 @@ add_firewall_rule() {
|
||||
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE ip daddr @$NFTSET_BLOCK counter drop"
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE ip daddr @$NFTSET_WHITE counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE meta mark 0xff counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE meta mark 255 counter return"
|
||||
|
||||
# jump chains
|
||||
nft "add rule $NFTABLE_NAME mangle_prerouting counter jump PSW_DIVERT"
|
||||
@@ -1067,7 +1068,7 @@ add_firewall_rule() {
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT ip daddr @$NFTSET_VPS counter return"
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT ip daddr @$NFTSET_BLOCK counter drop"
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT ip daddr @$NFTSET_WHITE counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT meta mark 0xff counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT meta mark 255 counter return"
|
||||
}
|
||||
|
||||
#icmp ipv6-icmp redirect
|
||||
@@ -1098,8 +1099,8 @@ add_firewall_rule() {
|
||||
fi
|
||||
unset WAN_IP wan_ip
|
||||
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
ip rule add fwmark ${FWMARK} lookup 999 priority 999
|
||||
ip route add local 0.0.0.0/0 dev lo table 999
|
||||
|
||||
#ipv6 tproxy mode and udp
|
||||
nft "add chain $NFTABLE_NAME PSW_MANGLE_V6"
|
||||
@@ -1113,7 +1114,7 @@ add_firewall_rule() {
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_VPS6 counter return"
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_BLOCK6 counter drop"
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_WHITE6 counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 meta mark 0xff counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 meta mark 255 counter return"
|
||||
|
||||
[ -n "$IPT_APPEND_DNS" ] && {
|
||||
local local_dns dns_address dns_port
|
||||
@@ -1148,8 +1149,8 @@ add_firewall_rule() {
|
||||
}
|
||||
unset WAN6_IP wan6_ip
|
||||
|
||||
ip -6 rule add fwmark 1 table 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
ip -6 rule add fwmark ${FWMARK} table 999 priority 999
|
||||
ip -6 route add local ::/0 dev lo table 999
|
||||
}
|
||||
|
||||
[ "$TCP_UDP" = "1" ] && [ -z "$UDP_NODE" ] && UDP_NODE=$TCP_NODE
|
||||
@@ -1347,7 +1348,7 @@ add_firewall_rule() {
|
||||
fi
|
||||
|
||||
nft "add rule $NFTABLE_NAME mangle_output oif lo counter return comment \"PSW_OUTPUT_MANGLE\""
|
||||
nft "add rule $NFTABLE_NAME mangle_output meta mark 1 counter return comment \"PSW_OUTPUT_MANGLE\""
|
||||
nft "add rule $NFTABLE_NAME mangle_output meta mark ${FWMARK} counter return comment \"PSW_OUTPUT_MANGLE\""
|
||||
}
|
||||
|
||||
# 加载ACLS
|
||||
@@ -1380,11 +1381,11 @@ del_firewall_rule() {
|
||||
# Need to be removed at the end, otherwise it will show "Resource busy"
|
||||
nft delete chain $NFTABLE_NAME handle $(nft -a list chains | grep -E "PSW_RULE" | awk -F '# handle ' '{print$2}') 2>/dev/null
|
||||
|
||||
ip rule del fwmark 1 lookup 100 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
|
||||
ip rule del fwmark ${FWMARK} lookup 999 priority 999 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
|
||||
|
||||
ip -6 rule del fwmark 1 table 100 2>/dev/null
|
||||
ip -6 route del local ::/0 dev lo table 100 2>/dev/null
|
||||
ip -6 rule del fwmark ${FWMARK} table 999 priority 999 2>/dev/null
|
||||
ip -6 route del local ::/0 dev lo table 999 2>/dev/null
|
||||
|
||||
destroy_nftset $NFTSET_LOCAL
|
||||
destroy_nftset $NFTSET_WAN
|
||||
|
||||
@@ -542,7 +542,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
["User-Agent"] = node.user_agent
|
||||
} or nil,
|
||||
quic = node.naive_quic == "1" and true or false,
|
||||
quic_congestion_control = (node.naive_quic == "1" and node.naive_congestion_control) and node.naive_congestion_control or nil
|
||||
quic_congestion_control = (node.naive_quic == "1" and node.naive_congestion_control) and node.naive_congestion_control or nil,
|
||||
tls = tls
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
Reference in new issue
Block a user