🌴 Sync 2026-04-03 08:49:56

This commit is contained in:
github-actions[bot] committed 2026-04-03 08:49:56 +08:00
1 parent 133898bee4
commit 6b6cae9986
27 files changed
+1571 -1687

No files matched your search

+1 -1
View File
@@ -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)
+7
View File
@@ -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
});
-245
View File
@@ -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
-200
View File
@@ -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
View File
@@ -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": {
+1 -1
View File
@@ -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