Merge pull request #5174 from TDT-AG/pr/20210702-luci-app-mwan3
luci-app-mwan3: convert to JS
This commit is contained in:
commit
b6ca2947e9
33 changed files with 1283 additions and 1628 deletions
|
@ -6,17 +6,12 @@
|
|||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for the MWAN3 multiwan hotplug script
|
||||
LUCI_DEPENDS:=+luci-compat \
|
||||
+mwan3 \
|
||||
+libuci-lua \
|
||||
+luci-mod-admin-full \
|
||||
+luci-lib-nixio
|
||||
LUCI_TITLE:=LuCI support for the MWAN3 MultiWAN Manager
|
||||
LUCI_DEPENDS:=+mwan3
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_LICENSE:=GPLv2
|
||||
|
||||
PKG_MAINTAINER:=Aedan Renner <chipdankly@gmail.com> \
|
||||
Florian Eckert <fe@dev.tdt.de>
|
||||
PKG_MAINTAINER:=Florian Eckert <fe@dev.tdt.de>
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#mwan3-service-status > .alert-message {
|
||||
display: inline-block;
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
width: 15rem;
|
||||
height: 6rem;
|
||||
vertical-align: middle;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
|
||||
return view.extend({
|
||||
|
||||
render: function () {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mwan3', _('MultiWAN Manager - Globals'));
|
||||
|
||||
s = m.section(form.NamedSection, 'globals', 'globals');
|
||||
|
||||
o = s.option(form.Value, 'mmx_mask', _('Firewall mask'),
|
||||
_('Enter value in hex, starting with <code>0x</code>'));
|
||||
o.datatype = 'hex(4)';
|
||||
o.default = '0x3F00';
|
||||
|
||||
o = s.option(form.Flag, 'logging', _('Logging'),
|
||||
_('Enables global firewall logging'));
|
||||
|
||||
o = s.option(form.ListValue, 'loglevel', _('Loglevel'),
|
||||
_('Firewall loglevel'));
|
||||
o.default = 'notice';
|
||||
o.value('emerg', _('Emergency'));
|
||||
o.value('alert', _('Alert'));
|
||||
o.value('crit', _('Critical'));
|
||||
o.value('error', _('Error'));
|
||||
o.value('warning', _('Warning'));
|
||||
o.value('notice', _('Notice'));
|
||||
o.value('info', _('Info'));
|
||||
o.value('debug', _('Debug'));
|
||||
o.depends('logging', '1');
|
||||
|
||||
o = s.option(form.DynamicList, 'rt_table_lookup',
|
||||
_('Routing table lookup'),
|
||||
_('Also scan this Routing table for connected networks'));
|
||||
o.datatype = 'uinteger';
|
||||
o.value('220', _('Routing table %d').format('220'));
|
||||
|
||||
return m.render();
|
||||
}
|
||||
})
|
|
@ -0,0 +1,276 @@
|
|||
'use strict';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require view';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat('/usr/bin/httping'), {}),
|
||||
L.resolveDefault(fs.stat('/usr/bin/nping'), {}),
|
||||
L.resolveDefault(fs.stat('/usr/bin/arping'), {}),
|
||||
uci.load('network')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (stats) {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mwan3', _('MultiWAN Manager - Interfaces'),
|
||||
_('Mwan3 requires that all interfaces have a unique metric configured in /etc/config/network.') + '<br />' +
|
||||
_('Names must match the interface name found in /etc/config/network.') + '<br />' +
|
||||
_('Names may contain characters A-Z, a-z, 0-9, _ and no spaces-') + '<br />' +
|
||||
_('Interfaces may not share the same name as configured members, policies or rules.'));
|
||||
|
||||
s = m.section(form.GridSection, 'interface');
|
||||
s.addremove = true;
|
||||
s.anonymous = false;
|
||||
s.nodescriptions = true;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enabled'));
|
||||
o.default = false;
|
||||
|
||||
o = s.option(form.ListValue, 'initial_state', _('Initial state'),
|
||||
_('Expect interface state on up event'));
|
||||
o.default = 'online';
|
||||
o.value('online', _('Online'));
|
||||
o.value('offline', _('Offline'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, 'family', _('Internet Protocol'));
|
||||
o.default = 'ipv4';
|
||||
o.value('ipv4', _('IPv4'));
|
||||
o.value('ipv6', _('IPv6'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.DynamicList, 'track_ip', _('Tracking hostname or IP address'),
|
||||
_('This hostname or IP address will be pinged to determine if the link is up or down. Leave blank to assume interface is always online'));
|
||||
o.datatype = 'host';
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, 'track_method', _('Tracking method'));
|
||||
o.default = 'ping';
|
||||
o.value('ping');
|
||||
if (stats[0].type === 'file') {
|
||||
o.value('httping');
|
||||
}
|
||||
if (stats[1].type === 'file') {
|
||||
o.value('nping-tcp');
|
||||
o.value('nping-udp');
|
||||
o.value('nping-icmp');
|
||||
o.value('nping-arp');
|
||||
}
|
||||
if (stats[2].type === 'file') {
|
||||
o.value('arping');
|
||||
}
|
||||
|
||||
o = s.option(form.Flag, 'httping_ssl', _('Enable ssl tracking'),
|
||||
_('Enables https tracking on ssl port 443'));
|
||||
o.depends('track_method', 'httping');
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'reliability', _('Tracking reliability'),
|
||||
_('Acceptable values: 1-100. This many Tracking IP addresses must respond for the link to be deemed up'));
|
||||
o.datatype = 'range(1, 100)';
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.ListValue, 'count', _('Ping count'));
|
||||
o.default = '1';
|
||||
o.value('1');
|
||||
o.value('2');
|
||||
o.value('3');
|
||||
o.value('4');
|
||||
o.value('5');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'size', _('Ping size'));
|
||||
o.default = '56';
|
||||
o.depends('track_method', 'ping');
|
||||
o.value('8');
|
||||
o.value('24');
|
||||
o.value('56');
|
||||
o.value('120');
|
||||
o.value('248');
|
||||
o.value('504');
|
||||
o.value('1016');
|
||||
o.value('1472');
|
||||
o.value('2040');
|
||||
o.datatype = 'range(1, 65507)';
|
||||
o.modalonly = true;
|
||||
|
||||
o =s.option(form.Value, 'max_ttl', _('Max TTL'));
|
||||
o.default = '60';
|
||||
o.depends('track_method', 'ping');
|
||||
o.value('10');
|
||||
o.value('20');
|
||||
o.value('30');
|
||||
o.value('40');
|
||||
o.value('50');
|
||||
o.value('60');
|
||||
o.value('70');
|
||||
o.datatype = 'range(1, 255)';
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Flag, 'check_quality', _('Check link quality'));
|
||||
o.depends('track_method', 'ping');
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'failure_latency', _('Failure latency [ms]'));
|
||||
o.depends('check_quality', '1');
|
||||
o.default = '1000';
|
||||
o.value('25');
|
||||
o.value('50');
|
||||
o.value('75');
|
||||
o.value('100');
|
||||
o.value('150');
|
||||
o.value('200');
|
||||
o.value('250');
|
||||
o.value('300');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'failure_loss', _('Failure packet loss [%]'));
|
||||
o.depends('check_quality', '1');
|
||||
o.default = '40';
|
||||
o.value('2');
|
||||
o.value('5');
|
||||
o.value('10');
|
||||
o.value('20');
|
||||
o.value('25');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'recovery_latency', _('Recovery latency [ms]'));
|
||||
o.depends('check_quality', '1');
|
||||
o.default = '500';
|
||||
o.value('25');
|
||||
o.value('50');
|
||||
o.value('75');
|
||||
o.value('100');
|
||||
o.value('150');
|
||||
o.value('200');
|
||||
o.value('250');
|
||||
o.value('300');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'recovery_loss', _('Recovery packet loss [%]'));
|
||||
o.depends('check_quality', '1');
|
||||
o.default = '10';
|
||||
o.value('2');
|
||||
o.value('5');
|
||||
o.value('10');
|
||||
o.value('20');
|
||||
o.value('25');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, "timeout", _("Ping timeout"));
|
||||
o.default = '4';
|
||||
for (var i = 1; i <= 10; i++)
|
||||
o.value(String(i), N_(i, '%d second', '%d seconds').format(i));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, 'interval', _('Ping interval'));
|
||||
o.default = '10';
|
||||
o.value('1', _('%d second').format('1'));
|
||||
o.value('3', _('%d seconds').format('3'));
|
||||
o.value('5', _('%d seconds').format('5'));
|
||||
o.value('10', _('%d seconds').format('10'));
|
||||
o.value('20', _('%d seconds').format('20'));
|
||||
o.value('30', _('%d seconds').format('30'));
|
||||
o.value('60', _('%d minute').format('1'));
|
||||
o.value('300', _('%d minutes').format('5'));
|
||||
o.value('600', _('%d minutes').format('10'));
|
||||
o.value('900', _('%d minutes').format('15'));
|
||||
o.value('1800', _('%d minutes').format('30'));
|
||||
o.value('3600', _('%d hour').format('1'));
|
||||
|
||||
o = s.option(form.Value, 'failure_interval', _('Failure interval'),
|
||||
_('Ping interval during failure detection'));
|
||||
o.default = '5';
|
||||
o.value('1', _('%d second').format('1'));
|
||||
o.value('3', _('%d seconds').format('3'));
|
||||
o.value('5', _('%d seconds').format('5'));
|
||||
o.value('10', _('%d seconds').format('10'));
|
||||
o.value('20', _('%d seconds').format('20'));
|
||||
o.value('30', _('%d seconds').format('30'));
|
||||
o.value('60', _('%d minute').format('1'));
|
||||
o.value('300', _('%d minutes').format('5'));
|
||||
o.value('600', _('%d minutes').format('10'));
|
||||
o.value('900', _('%d minutes').format('15'));
|
||||
o.value('1800', _('%d minutes').format('30'));
|
||||
o.value('3600', _('%d hour').format('1'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Flag, 'keep_failure_interval', _('Keep failure interval'),
|
||||
_('Keep ping failure interval during failure state'));
|
||||
o.default = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'recovery_interval', _('Recovery interval'),
|
||||
_('Ping interval during failure recovering'));
|
||||
o.default = '5';
|
||||
o.value('1', _('%d second').format('1'));
|
||||
o.value('3', _('%d seconds').format('3'));
|
||||
o.value('5', _('%d seconds').format('5'));
|
||||
o.value('10', _('%d seconds').format('10'));
|
||||
o.value('20', _('%d seconds').format('20'));
|
||||
o.value('30', _('%d seconds').format('30'));
|
||||
o.value('60', _('%d minute').format('1'));
|
||||
o.value('300', _('%d minutes').format('5'));
|
||||
o.value('600', _('%d minutes').format('10'));
|
||||
o.value('900', _('%d minutes').format('15'));
|
||||
o.value('1800', _('%d minutes').format('30'));
|
||||
o.value('3600', _('%d hour').format('1'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, 'down', _('Interface down'),
|
||||
_('Interface will be deemed down after this many failed ping tests'));
|
||||
o.default = '5';
|
||||
o.value('1');
|
||||
o.value('2');
|
||||
o.value('3');
|
||||
o.value('4');
|
||||
o.value('5');
|
||||
o.value('6');
|
||||
o.value('7');
|
||||
o.value('8');
|
||||
o.value('9');
|
||||
o.value('10');
|
||||
|
||||
o = s.option(form.ListValue, 'up', _('Interface up'),
|
||||
_('Downed interface will be deemed up after this many successful ping tests'));
|
||||
o.default = "5";
|
||||
o.value('1');
|
||||
o.value('2');
|
||||
o.value('3');
|
||||
o.value('4');
|
||||
o.value('5');
|
||||
o.value('6');
|
||||
o.value('7');
|
||||
o.value('8');
|
||||
o.value('9');
|
||||
o.value('10');
|
||||
|
||||
o = s.option(form.ListValue, 'flush_conntrack', _('Flush conntrack table'),
|
||||
_('Flush global firewall conntrack table on interface events'));
|
||||
o.value('ifup', _('ifup (netifd)'));
|
||||
o.value('ifdown', _('ifdown (netifd)'));
|
||||
o.value('connected', _('connected (mwan3)'));
|
||||
o.value('disconnected', _('disconnected (mwan3)'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.DummyValue, 'metric', _('Metric'),
|
||||
_('This displays the metric assigned to this interface in /etc/config/network'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function(s) {
|
||||
var metric = uci.get('network', s, 'metric')
|
||||
if (metric)
|
||||
return metric;
|
||||
else
|
||||
return _('No interface metric set!');
|
||||
}
|
||||
|
||||
return m.render();
|
||||
}
|
||||
})
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('mwan3')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mwan3', _('MultiWAN Manager - Members'),
|
||||
_('Members are profiles attaching a metric and weight to an MWAN interface.') + '<br />' +
|
||||
_('Names may contain characters A-Z, a-z, 0-9, _ and no spaces.') + '<br />' +
|
||||
_('Members may not share the same name as configured interfaces, policies or rules.'));
|
||||
|
||||
s = m.section(form.GridSection, 'member');
|
||||
s.addremove = true;
|
||||
s.anonymous = false;
|
||||
s.nodescriptions = true;
|
||||
|
||||
o = s.option(form.ListValue, 'interface', _('Interface'));
|
||||
var options = uci.sections('mwan3', 'interface')
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var value = options[i]['.name'];
|
||||
o.value(value);
|
||||
}
|
||||
|
||||
o = s.option(form.Value, 'metric', _('Metric'),
|
||||
_('Acceptable values: 1-256. Defaults to 1 if not set'));
|
||||
o.datatype = 'range(1, 256)';
|
||||
|
||||
o = s.option(form.Value, 'weight', ('Weight'),
|
||||
_('Acceptable values: 1-1000. Defaults to 1 if not set'));
|
||||
o.datatype = 'range(1, 1000)';
|
||||
|
||||
return m.render();
|
||||
}
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
var isReadonlyView = !L.hasViewPermission() || null;
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return L.resolveDefault(fs.read('/etc/mwan3.user'), '');
|
||||
},
|
||||
|
||||
handleSave: function(ev) {
|
||||
var value = (document.querySelector('textarea').value || '').trim().replace(/\r\n/g, '\n') + '\n';
|
||||
|
||||
return fs.write('/etc/mwan3.user', value).then(function(rc) {
|
||||
document.querySelector('textarea').value = value;
|
||||
ui.addNotification(null, E('p', _('Contents have been saved.')), 'info');
|
||||
}).catch(function(e) {
|
||||
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
|
||||
});
|
||||
},
|
||||
|
||||
render: function(mwan3user) {
|
||||
return E([
|
||||
E('h2', _('MultiWAN Manager - Notify')),
|
||||
E('p', { 'class': 'cbi-section-descr' },
|
||||
_('This section allows you to modify the content of \"/etc/mwan3.user\".') + '<br/>' +
|
||||
_('The file is also preserved during sysupgrade.') + '<br/>' +
|
||||
'<br />' +
|
||||
_('Notes:') + '<br />' +
|
||||
_('This file is interpreted as a shell script.') + '<br />' +
|
||||
_('The first line of the script must be "#!/bin/sh" without quotes.') + '<br />' +
|
||||
_('Lines beginning with # are comments and are not executed.') + '<br />' +
|
||||
_('Put your custom mwan3 action here, they will be executed with each netifd hotplug interface event on interfaces for which mwan3 is enabled.') + '<br />' +
|
||||
'<br />' +
|
||||
_('There are three main environment variables that are passed to this script.') + '<br />' +
|
||||
'<br />' +
|
||||
_('%s: Name of the action that triggered this event').format('$ACTION') + '<br />' +
|
||||
_('* %s: Is called by netifd and mwan3track').format('ifup') + '<br />' +
|
||||
_('* %s: Is called by netifd and mwan3track').format('ifdown') + '<br />' +
|
||||
_('* %s: Is only called by mwan3track if tracking was successful').format('connected') + '<br />' +
|
||||
_('* %s: Is only called by mwan3track if tracking has failed').format('disonnected') + '<br />' +
|
||||
_('%s: Name of the interface which went up or down (e.g. \"wan\" or \"wwan\")').format('$INTERFACE') + '<br />' +
|
||||
_('%s: Name of Physical device which interface went up or down (e.g. \"eth0\" or \"wwan0\")').format('$DEVICE') + '<br />'),
|
||||
E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 10, 'disabled': isReadonlyView }, [ mwan3user != null ? mwan3user : '' ]))
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('mwan3')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mwan3', _('MultiWAN Manager - Policies'),
|
||||
_('Policies are profiles grouping one or more members controlling how Mwan3 distributes traffic.') +
|
||||
_('Member interfaces with lower metrics are used first.') +
|
||||
_('Member interfaces with the same metric will be load-balanced.') +
|
||||
_('Load-balanced member interfaces distribute more traffic out those with higher weights.') +
|
||||
_('Names may contain characters A-Z, a-z, 0-9, _ and no spaces.') +
|
||||
_('Names must be 15 characters or less.') +
|
||||
_('Policies may not share the same name as configured interfaces, members or rules'));
|
||||
|
||||
s = m.section(form.GridSection, 'policy');
|
||||
s.addremove = true;
|
||||
s.anonymous = false;
|
||||
s.nodescriptions = true;
|
||||
|
||||
o = s.option(form.DynamicList, 'use_member', _('Member used'));
|
||||
var options = uci.sections('mwan3', 'member')
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var value = options[i]['.name'];
|
||||
o.value(value);
|
||||
}
|
||||
|
||||
o = s.option(form.ListValue, 'last_resort', _('Last resort'),
|
||||
_('When all policy members are offline use this behavior for matched traffic'));
|
||||
o.default = 'unreachable';
|
||||
o.value('unreachable', _('unreachable (reject)'));
|
||||
o.value('blackhole', _('blackhole (drop)'));
|
||||
o.value('default', _('default (use main routing table)'));
|
||||
|
||||
return m.render();
|
||||
}
|
||||
})
|
|
@ -0,0 +1,107 @@
|
|||
'use strict';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require view';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
fs.exec_direct('/usr/libexec/luci-mwan3', ['ipset', 'dump']),
|
||||
uci.load('mwan3')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (data) {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mwan3', _('MultiWAN Manager - Rules'),
|
||||
_('Rules specify which traffic will use a particular MWAN policy.') + '<br />' +
|
||||
_('Rules are based on IP address, port or protocol.') + '<br />' +
|
||||
_('Rules are matched from top to bottom.') + '<br />' +
|
||||
_('Rules below a matching rule are ignored.') + '<br />' +
|
||||
_('Traffic not matching any rule is routed using the main routing table.') + '<br />' +
|
||||
_('Traffic destined for known (other than default) networks is handled by the main routing table.') + '<br />' +
|
||||
_('Traffic matching a rule, but all WAN interfaces for that policy are down will be blackholed.') + '<br />' +
|
||||
_('Names may contain characters A-Z, a-z, 0-9, _ and no spaces.') + '<br />' +
|
||||
_('Rules may not share the same name as configured interfaces, members or policies.'));
|
||||
|
||||
s = m.section(form.GridSection, 'rule');
|
||||
s.addremove = true;
|
||||
s.anonymous = false;
|
||||
s.nodescriptions = true;
|
||||
|
||||
o = s.option(form.ListValue, 'family', _('Internet Protocol'));
|
||||
o.default = '';
|
||||
o.value('', _('IPv4 and IPv6'));
|
||||
o.value('ipv4', _('IPv4 only'));
|
||||
o.value('ipv6', _('IPv6 only'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'src_ip', _('Source address'),
|
||||
_('Supports CIDR notation (eg \"192.168.100.0/24\") without quotes'));
|
||||
o.datatype = 'ipaddr';
|
||||
|
||||
o = s.option(form.Value, 'src_port', _('Source port'),
|
||||
_('May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes'));
|
||||
o.depends('proto', 'tcp');
|
||||
o.depends('proto', 'udp');
|
||||
|
||||
o = s.option(form.Value, 'dest_ip', _('Destination address'),
|
||||
_('Supports CIDR notation (eg \"192.168.100.0/24\") without quotes'));
|
||||
o.datatype = 'ipaddr';
|
||||
|
||||
o = s.option(form.Value, 'dest_port', _('Destination port'),
|
||||
_('May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes'));
|
||||
o.depends('proto', 'tcp');
|
||||
o.depends('proto', 'udp');
|
||||
|
||||
o = s.option(form.Value, 'proto', _('Protocol'),
|
||||
_('View the content of /etc/protocols for protocol description'));
|
||||
o.default = 'all';
|
||||
o.rmempty = false;
|
||||
o.value('all');
|
||||
o.value('tcp');
|
||||
o.value('udp');
|
||||
o.value('icmp');
|
||||
o.value('esp');
|
||||
|
||||
o = s.option(form.ListValue, 'sticky', _('Sticky'),
|
||||
_('Traffic from the same source IP address that previously matched this rule within the sticky timeout period will use the same WAN interface'));
|
||||
o.default = '0';
|
||||
o.value('1', _('Yes'));
|
||||
o.value('0', _('No'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'timeout', _('Sticky timeout'),
|
||||
_('Seconds. Acceptable values: 1-1000000. Defaults to 600 if not set'));
|
||||
o.datatype = 'range(1, 1000000)';
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Value, 'ipset', _('IPset'),
|
||||
_('Name of IPset rule. Requires IPset rule in /etc/dnsmasq.conf (eg \"ipset=/youtube.com/youtube\")'));
|
||||
o.value('', _('-- Please choose --'));
|
||||
var ipsets = data[0].split(/\n/);
|
||||
for (var i = 0; i < ipsets.length; i++) {
|
||||
if (ipsets[i].length > 0)
|
||||
o.value(ipsets[i]);
|
||||
}
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.Flag, 'logging', _('Logging'),
|
||||
_('Enables firewall rule logging (global mwan3 logging must also be enabled)'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.ListValue, 'use_policy', _('Policy assigned'));
|
||||
var options = uci.sections('mwan3', 'policy')
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var value = options[i]['.name'];
|
||||
o.value(value);
|
||||
}
|
||||
o.value('unreachable', _('unreachable (reject)'));
|
||||
o.value('blackhole', _('blackhole (drop)'));
|
||||
o.value('default', _('default (use main routing table)'));
|
||||
|
||||
return m.render();
|
||||
}
|
||||
})
|
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
'require fs';
|
||||
'require view';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return L.resolveDefault(fs.exec_direct('/usr/sbin/mwan3', [ 'status' ]),'');
|
||||
},
|
||||
|
||||
render: function (report) {
|
||||
return E('div', { 'class': 'cbi-map', 'id': 'map' }, [
|
||||
E('h2', _('MultiWAN Manager - Status')),
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('pre', [ report ])
|
||||
]),
|
||||
])
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
})
|
|
@ -0,0 +1,116 @@
|
|||
'use strict';
|
||||
'require fs';
|
||||
'require uci';
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require view';
|
||||
|
||||
return view.extend({
|
||||
handleCommand: function(exec, args) {
|
||||
var buttons = document.querySelectorAll('.cbi-button');
|
||||
|
||||
for (var i = 0; i < buttons.length; i++)
|
||||
buttons[i].setAttribute('disabled', 'true');
|
||||
|
||||
return fs.exec(exec, args).then(function(res) {
|
||||
var out = document.querySelector('.command-output');
|
||||
out.style.display = '';
|
||||
|
||||
dom.content(out, [ res.stdout || '', res.stderr || '' ]);
|
||||
}).catch(function(err) {
|
||||
ui.addNotification(null, E('p', [ err ]))
|
||||
}).finally(function() {
|
||||
for (var i = 0; i < buttons.length; i++)
|
||||
buttons[i].removeAttribute('disabled');
|
||||
});
|
||||
},
|
||||
|
||||
handleAction: function(ev) {
|
||||
var iface = document.getElementById('iface');
|
||||
var task = document.getElementById('task');
|
||||
|
||||
switch (task.value) {
|
||||
case 'gateway':
|
||||
return this.handleCommand('/usr/libexec/luci-mwan3',
|
||||
[ 'diag', 'gateway', iface.value ]);
|
||||
case 'tracking':
|
||||
return this.handleCommand('/usr/libexec/luci-mwan3',
|
||||
[ 'diag', 'tracking', iface.value ]);
|
||||
case 'rules':
|
||||
return this.handleCommand('/usr/libexec/luci-mwan3',
|
||||
[ 'diag', 'rules', iface.value ]);
|
||||
case 'routes':
|
||||
return this.handleCommand('/usr/libexec/luci-mwan3',
|
||||
[ 'diag', 'routes', iface.value ]);
|
||||
case 'ifup':
|
||||
return this.handleCommand('/usr/sbin/mwan3',
|
||||
[ 'ifup', iface.value]);
|
||||
case 'ifdown':
|
||||
return this.handleCommand('/usr/sbin/mwan3',
|
||||
[ 'ifdown', iface.value]);
|
||||
}
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('mwan3')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
|
||||
var taskSel = [
|
||||
E('option', { 'value': 'gateway' }, [ _('Ping default gateway') ]),
|
||||
E('option', { 'value': 'tracking' }, [ _('Ping tracking IP') ]),
|
||||
E('option', { 'value': 'rules' }, [ _('Check IP rules') ]),
|
||||
E('option', { 'value': 'routes' }, [ _('Check routing table') ]),
|
||||
E('option', { 'value': 'ifup' }, [ _('Hotplug ifup') ]),
|
||||
E('option', { 'value': 'ifdown' }, [ _('Hotplug ifdown') ])
|
||||
];
|
||||
|
||||
var ifaceSel = [E('option', { value: '' }, [_('-- Interface Selection --')])];
|
||||
|
||||
var options = uci.sections('mwan3', 'interface')
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
ifaceSel.push(E('option', { 'value': options[i]['.name'] }, options[i]['.name']));
|
||||
}
|
||||
|
||||
return E('div', { 'class': 'cbi-map', 'id': 'map' }, [
|
||||
E('h2', {}, [ _('MultiWAN Manager - Diagnostics') ]),
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, [ _('Interface') ]),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('select', {'class': 'cbi-input-select', 'id': 'iface'},
|
||||
ifaceSel
|
||||
)
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, [ _('Task') ]),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('select', { 'class': 'cbi-input-select', 'id': 'task' },
|
||||
taskSel
|
||||
)
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
'\xa0',
|
||||
E('pre', { 'class': 'command-output', 'style': 'display:none' }),
|
||||
'\xa0',
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'id': 'execute',
|
||||
'click': ui.createHandlerFn(this, 'handleAction')
|
||||
}, [ _('Execute') ]),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
})
|
|
@ -0,0 +1,104 @@
|
|||
'use strict';
|
||||
'require poll';
|
||||
'require view';
|
||||
'require rpc';
|
||||
|
||||
var callMwan3Status = rpc.declare({
|
||||
object: 'mwan3',
|
||||
method: 'status',
|
||||
expect: { },
|
||||
});
|
||||
|
||||
document.querySelector('head').appendChild(E('link', {
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css',
|
||||
'href': L.resource('view/mwan3/mwan3.css')
|
||||
}));
|
||||
|
||||
function renderMwan3Status(status) {
|
||||
if (!status.interfaces)
|
||||
return '<strong>%h</strong>'.format(_('No MWAN interfaces found'));
|
||||
|
||||
var statusview = '';
|
||||
for ( var iface in status.interfaces) {
|
||||
var state = '';
|
||||
var css = '';
|
||||
var time = '';
|
||||
var tname = '';
|
||||
switch (status.interfaces[iface].status) {
|
||||
case 'online':
|
||||
state = _('Online');
|
||||
css = 'success';
|
||||
time = '%t'.format(status.interfaces[iface].online);
|
||||
tname = _('Uptime');
|
||||
css = 'success';
|
||||
break;
|
||||
case 'offline':
|
||||
state = _('Offline');
|
||||
css = 'danger';
|
||||
time = '%t'.format(status.interfaces[iface].offline);
|
||||
tname = _('Downtime');
|
||||
break;
|
||||
case 'notracking':
|
||||
state = _('No Tracking');
|
||||
if ((status.interfaces[iface].uptime) > 0) {
|
||||
css = 'success';
|
||||
time = '%t'.format(status.interfaces[iface].uptime);
|
||||
tname = _('Uptime');
|
||||
}
|
||||
else {
|
||||
css = 'warning';
|
||||
time = '';
|
||||
tname = '';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
state = _('Disabled');
|
||||
css = 'warning';
|
||||
time = '';
|
||||
tname = '';
|
||||
break;
|
||||
}
|
||||
|
||||
statusview += '<div class="alert-message %h">'.format(css);
|
||||
statusview += '<div><strong>%h: </strong>%h</div>'.format(_('Interface'), iface);
|
||||
statusview += '<div><strong>%h: </strong>%h</div>'.format(_('Status'), state);
|
||||
|
||||
if (time)
|
||||
statusview += '<div><strong>%h: </strong>%h</div>'.format(tname, time);
|
||||
|
||||
statusview += '</div>';
|
||||
}
|
||||
|
||||
return statusview;
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callMwan3Status(),
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (data) {
|
||||
poll.add(function() {
|
||||
return callMwan3Status().then(function(result) {
|
||||
var view = document.getElementById('mwan3-service-status');
|
||||
view.innerHTML = renderMwan3Status(result);
|
||||
});
|
||||
});
|
||||
|
||||
return E('div', { class: 'cbi-map' }, [
|
||||
E('h2', [ _('MultiWAN Manager - Overview') ]),
|
||||
E('div', { class: 'cbi-section' }, [
|
||||
E('div', { 'id': 'mwan3-service-status' }, [
|
||||
E('em', { 'class': 'spinning' }, [ _('Collecting data ...') ])
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
})
|
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
'require fs';
|
||||
'require view';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return L.resolveDefault(fs.exec_direct('/usr/sbin/mwan3', [ 'internal', 'ipv4' ]),'');
|
||||
},
|
||||
|
||||
render: function (report) {
|
||||
return E('div', { 'class': 'cbi-map', 'id': 'map' }, [
|
||||
E('h2', _('MultiWAN Manager - Troubleshooting')),
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('pre', [ report ])
|
||||
]),
|
||||
])
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
})
|
|
@ -0,0 +1,117 @@
|
|||
'use strict';
|
||||
'require baseclass';
|
||||
'require rpc';
|
||||
|
||||
var callMwan3Status = rpc.declare({
|
||||
object: 'mwan3',
|
||||
method: 'status',
|
||||
expect: { },
|
||||
});
|
||||
|
||||
document.querySelector('head').appendChild(E('link', {
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css',
|
||||
'href': L.resource('view/mwan3/mwan3.css')
|
||||
}));
|
||||
|
||||
return baseclass.extend({
|
||||
title: _('MultiWAN Manager'),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callMwan3Status(),
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (result) {
|
||||
if (!result[0].interfaces)
|
||||
return null;
|
||||
|
||||
var container = E('div', { 'id': 'mwan3-service-status' });
|
||||
var iface;
|
||||
for ( iface in result[0].interfaces) {
|
||||
var state = '';
|
||||
var css = '';
|
||||
var time = '';
|
||||
var tname = '';
|
||||
switch (result[0].interfaces[iface].status) {
|
||||
case 'online':
|
||||
state = _('Online');
|
||||
css = 'alert-message success';
|
||||
time = '%t'.format(result[0].interfaces[iface].online);
|
||||
tname = _('Uptime');
|
||||
break;
|
||||
case 'offline':
|
||||
state = _('Offline');
|
||||
css = 'alert-message danger';
|
||||
time = '%t'.format(result[0].interfaces[iface].offline);
|
||||
tname = _('Downtime');
|
||||
break;
|
||||
case 'notracking':
|
||||
state = _('No Tracking');
|
||||
if ((result[0].interfaces[iface].uptime) > 0) {
|
||||
css = 'alert-message success';
|
||||
time = '%t'.format(result[0].interfaces[iface].uptime);
|
||||
tname = _('Uptime');
|
||||
}
|
||||
else {
|
||||
css = 'alert-message warning';
|
||||
time = '';
|
||||
tname = '';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
css = 'alert-message warning';
|
||||
state = _('Disabled');
|
||||
time = '';
|
||||
tname = '';
|
||||
break;
|
||||
}
|
||||
|
||||
if (time !== '' ) {
|
||||
container.appendChild(
|
||||
E('div', { 'class': css }, [
|
||||
E('div', {}, [
|
||||
E('strong', {}, [
|
||||
_('Interface'), ':', ' '
|
||||
]),
|
||||
iface
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('strong', {}, [
|
||||
_('Status'), ':', ' '
|
||||
]),
|
||||
state
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('strong', {}, [
|
||||
tname, ':', ' '
|
||||
]),
|
||||
time
|
||||
])
|
||||
])
|
||||
);
|
||||
}
|
||||
else {
|
||||
container.appendChild(
|
||||
E('div', { 'class': css }, [
|
||||
E('div', {}, [
|
||||
E('strong', {}, [
|
||||
_('Interface'), ':', ' '
|
||||
]),
|
||||
iface
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('strong', {}, [
|
||||
_('Status'), ':', ' '
|
||||
]),
|
||||
state
|
||||
])
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
|
@ -1,320 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
module("luci.controller.mwan3", package.seeall)
|
||||
|
||||
sys = require "luci.sys"
|
||||
ut = require "luci.util"
|
||||
|
||||
ip = "ip -4 "
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/mwan3") then
|
||||
return
|
||||
end
|
||||
|
||||
entry({"admin", "status", "mwan"},
|
||||
alias("admin", "status", "mwan", "overview"),
|
||||
_("Load Balancing"), 600).acl_depends = { "luci-app-mwan3" }
|
||||
|
||||
entry({"admin", "status", "mwan", "overview"},
|
||||
template("mwan/status_interface"))
|
||||
entry({"admin", "status", "mwan", "detail"},
|
||||
template("mwan/status_detail"))
|
||||
entry({"admin", "status", "mwan", "diagnostics"},
|
||||
template("mwan/status_diagnostics"))
|
||||
entry({"admin", "status", "mwan", "troubleshooting"},
|
||||
template("mwan/status_troubleshooting"))
|
||||
entry({"admin", "status", "mwan", "interface_status"},
|
||||
call("mwan_Status"))
|
||||
entry({"admin", "status", "mwan", "detailed_status"},
|
||||
call("detailedStatus"))
|
||||
entry({"admin", "status", "mwan", "diagnostics_display"},
|
||||
call("diagnosticsData"), nil).leaf = true
|
||||
entry({"admin", "status", "mwan", "troubleshooting_display"},
|
||||
call("troubleshootingData"))
|
||||
|
||||
|
||||
entry({"admin", "network", "mwan"},
|
||||
alias("admin", "network", "mwan", "interface"),
|
||||
_("Load Balancing"), 600).acl_depends = { "luci-app-mwan3" }
|
||||
|
||||
entry({"admin", "network", "mwan", "globals"},
|
||||
cbi("mwan/globalsconfig"),
|
||||
_("Globals"), 5).leaf = true
|
||||
entry({"admin", "network", "mwan", "interface"},
|
||||
arcombine(cbi("mwan/interface"), cbi("mwan/interfaceconfig")),
|
||||
_("Interfaces"), 10).leaf = true
|
||||
entry({"admin", "network", "mwan", "member"},
|
||||
arcombine(cbi("mwan/member"), cbi("mwan/memberconfig")),
|
||||
_("Members"), 20).leaf = true
|
||||
entry({"admin", "network", "mwan", "policy"},
|
||||
arcombine(cbi("mwan/policy"), cbi("mwan/policyconfig")),
|
||||
_("Policies"), 30).leaf = true
|
||||
entry({"admin", "network", "mwan", "rule"},
|
||||
arcombine(cbi("mwan/rule"), cbi("mwan/ruleconfig")),
|
||||
_("Rules"), 40).leaf = true
|
||||
entry({"admin", "network", "mwan", "notify"},
|
||||
form("mwan/notify"),
|
||||
_("Notification"), 50).leaf = true
|
||||
end
|
||||
|
||||
function mwan_Status()
|
||||
local status = ut.ubus("mwan3", "status", {})
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
if status ~= nil then
|
||||
luci.http.write_json(status)
|
||||
else
|
||||
luci.http.write_json({})
|
||||
end
|
||||
end
|
||||
|
||||
function detailedStatus()
|
||||
local statusInfo = ut.trim(sys.exec("/usr/sbin/mwan3 status"))
|
||||
luci.http.prepare_content("text/plain")
|
||||
if statusInfo ~= "" then
|
||||
luci.http.write(statusInfo)
|
||||
else
|
||||
luci.http.write("Unable to get status information")
|
||||
end
|
||||
end
|
||||
|
||||
function diagnosticsData(interface, task)
|
||||
function getInterfaceNumber(interface)
|
||||
local number = 0
|
||||
local interfaceNumber
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
uci:foreach("mwan3", "interface",
|
||||
function (section)
|
||||
number = number+1
|
||||
if section[".name"] == interface then
|
||||
interfaceNumber = number
|
||||
end
|
||||
end
|
||||
)
|
||||
return interfaceNumber
|
||||
end
|
||||
|
||||
function diag_command(cmd, device, addr)
|
||||
if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then
|
||||
local util = io.popen(cmd %{ut.shellquote(device), ut.shellquote(addr)})
|
||||
if util then
|
||||
luci.http.write("Command:\n")
|
||||
luci.http.write(cmd %{ut.shellquote(device),
|
||||
ut.shellquote(addr)} .. "\n\n")
|
||||
luci.http.write("Result:\n")
|
||||
while true do
|
||||
local ln = util:read("*l")
|
||||
if not ln then break end
|
||||
luci.http.write(ln)
|
||||
luci.http.write("\n")
|
||||
end
|
||||
util:close()
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
function get_gateway(interface)
|
||||
local gateway = nil
|
||||
local dump = nil
|
||||
|
||||
dump = require("luci.util").ubus("network.interface.%s_4" % interface, "status", {})
|
||||
if not dump then
|
||||
dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
|
||||
end
|
||||
|
||||
if dump and dump.route then
|
||||
local _, route
|
||||
for _, route in ipairs(dump.route) do
|
||||
if dump.route[_].target == "0.0.0.0" then
|
||||
gateway = dump.route[_].nexthop
|
||||
end
|
||||
end
|
||||
end
|
||||
return gateway
|
||||
end
|
||||
|
||||
local mArray = {}
|
||||
local results = ""
|
||||
local number = getInterfaceNumber(interface)
|
||||
|
||||
local uci = require "luci.model.uci".cursor(nil, "/var/state")
|
||||
local nw = require "luci.model.network".init()
|
||||
local i18n = require "luci.i18n"
|
||||
local network = nw:get_network(interface)
|
||||
local device = network and network:get_interface()
|
||||
device = device:name()
|
||||
|
||||
luci.http.prepare_content("text/plain")
|
||||
if device then
|
||||
if task == "ping_gateway" then
|
||||
local gateway = get_gateway(interface)
|
||||
if gateway ~= nil then
|
||||
diag_command("ping -I %s -c 5 -W 1 %s 2>&1", device, gateway)
|
||||
else
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write(i18n.translatef("No gateway for interface %s found.", interface))
|
||||
end
|
||||
elseif task == "ping_trackips" then
|
||||
local trackips = uci:get("mwan3", interface, "track_ip")
|
||||
if #trackips > 0 then
|
||||
for i in pairs(trackips) do
|
||||
diag_command("ping -I %s -c 5 -W 1 %s 2>&1", device, trackips[i])
|
||||
end
|
||||
else
|
||||
luci.http.write(i18n.translatef("No tracking Hosts for interface %s defined.", interface))
|
||||
end
|
||||
elseif task == "check_rules" then
|
||||
local number = getInterfaceNumber(interface)
|
||||
local iif = 1000 + number
|
||||
local fwmark = 2000 + number
|
||||
local iif_rule = sys.exec(string.format("ip rule | grep %d", iif))
|
||||
local fwmark_rule = sys.exec(string.format("ip rule | grep %d", fwmark))
|
||||
if iif_rule ~= "" and fwmark_rule ~= "" then
|
||||
luci.http.write(i18n.translatef("All required IP rules for interface %s found", interface))
|
||||
luci.http.write("\n")
|
||||
luci.http.write(fwmark_rule)
|
||||
luci.http.write(iif_rule)
|
||||
elseif iif_rule == "" and fwmark_rule ~= "" then
|
||||
luci.http.write(i18n.translatef("Only one IP rules for interface %s found", interface))
|
||||
luci.http.write("\n")
|
||||
luci.http.write(fwmark_rule)
|
||||
elseif iif_rule ~= "" and fwmark_rule == "" then
|
||||
luci.http.write(i18n.translatef("Only one IP rules for interface %s found", interface))
|
||||
luci.http.write("\n")
|
||||
luci.http.write(iif_rule)
|
||||
else
|
||||
luci.http.write(i18n.translatef("Missing both IP rules for interface %s", interface))
|
||||
end
|
||||
elseif task == "check_routes" then
|
||||
local number = getInterfaceNumber(interface)
|
||||
local routeTable = sys.exec(string.format("ip route list table %s", number))
|
||||
if routeTable ~= "" then
|
||||
luci.http.write(i18n.translatef("Routing table %s for interface %s found", number, interface))
|
||||
luci.http.write("\n")
|
||||
luci.http.write(routeTable)
|
||||
else
|
||||
luci.http.write(i18n.translatef("Routing table %s for interface %s not found", number, interface))
|
||||
end
|
||||
elseif task == "hotplug_ifup" then
|
||||
os.execute(string.format("/usr/sbin/mwan3 ifup %s", ut.shellquote(interface)))
|
||||
luci.http.write(string.format("Hotplug ifup sent to interface %s", interface))
|
||||
elseif task == "hotplug_ifdown" then
|
||||
os.execute(string.format("/usr/sbin/mwan3 ifdown %s", ut.shellquote(interface)))
|
||||
luci.http.write(string.format("Hotplug ifdown sent to interface %s", interface))
|
||||
else
|
||||
luci.http.write("Unknown task")
|
||||
end
|
||||
else
|
||||
luci.http.write(string.format("Unable to perform diagnostic tests on %s.", interface))
|
||||
luci.http.write("\n")
|
||||
luci.http.write("There is no physical or virtual device associated with this interface.")
|
||||
end
|
||||
end
|
||||
|
||||
function troubleshootingData()
|
||||
local ver = require "luci.version"
|
||||
local dash = "-------------------------------------------------"
|
||||
|
||||
luci.http.prepare_content("text/plain")
|
||||
|
||||
luci.http.write("\n")
|
||||
luci.http.write("\n")
|
||||
luci.http.write("Software-Version")
|
||||
luci.http.write("\n")
|
||||
luci.http.write(dash)
|
||||
luci.http.write("\n")
|
||||
if ver.distversion then
|
||||
luci.http.write(string.format("OpenWrt - %s", ver.distversion))
|
||||
luci.http.write("\n")
|
||||
else
|
||||
luci.http.write("OpenWrt - unknown")
|
||||
luci.http.write("\n")
|
||||
end
|
||||
|
||||
if ver.luciversion then
|
||||
luci.http.write(string.format("LuCI - %s", ver.luciversion))
|
||||
luci.http.write("\n")
|
||||
else
|
||||
luci.http.write("LuCI - unknown")
|
||||
luci.http.write("\n")
|
||||
end
|
||||
|
||||
luci.http.write("\n")
|
||||
luci.http.write("\n")
|
||||
local output = ut.trim(sys.exec("ip a show"))
|
||||
luci.http.write("Output of \"ip a show\"")
|
||||
luci.http.write("\n")
|
||||
luci.http.write(dash)
|
||||
luci.http.write("\n")
|
||||
if output ~= "" then
|
||||
luci.http.write(output)
|
||||
luci.http.write("\n")
|
||||
else
|
||||
luci.http.write("No data found")
|
||||
luci.http.write("\n")
|
||||
end
|
||||
|
||||
luci.http.write("\n")
|
||||
luci.http.write("\n")
|
||||
local output = ut.trim(sys.exec("ip route show"))
|
||||
luci.http.write("Output of \"ip route show\"")
|
||||
luci.http.write("\n")
|
||||
luci.http.write(dash)
|
||||
luci.http.write("\n")
|
||||
if output ~= "" then
|
||||
luci.http.write(output)
|
||||
luci.http.write("\n")
|
||||
else
|
||||
luci.http.write("No data found")
|
||||
luci.http.write("\n")
|
||||
end
|
||||
|
||||
luci.http.write("\n")
|
||||
luci.http.write("\n")
|
||||
local output = ut.trim(sys.exec("ip rule show"))
|
||||
luci.http.write("Output of \"ip rule show\"")
|
||||
luci.http.write("\n")
|
||||
luci.http.write(dash)
|
||||
luci.http.write("\n")
|
||||
if output ~= "" then
|
||||
luci.http.write(output)
|
||||
luci.http.write("\n")
|
||||
else
|
||||
luci.http.write("No data found")
|
||||
luci.http.write("\n")
|
||||
end
|
||||
|
||||
luci.http.write("\n")
|
||||
luci.http.write("\n")
|
||||
luci.http.write("Output of \"ip route list table 1-250\"")
|
||||
luci.http.write("\n")
|
||||
luci.http.write(dash)
|
||||
luci.http.write("\n")
|
||||
for i=1,250 do
|
||||
local output = ut.trim(sys.exec(string.format("ip route list table %d", i)))
|
||||
if output ~= "" then
|
||||
luci.http.write(string.format("Table %s: ", i))
|
||||
luci.http.write(output)
|
||||
luci.http.write("\n")
|
||||
end
|
||||
end
|
||||
|
||||
luci.http.write("\n")
|
||||
luci.http.write("\n")
|
||||
local output = ut.trim(sys.exec("iptables -L -t mangle -v -n"))
|
||||
luci.http.write("Output of \"iptables -L -t mangle -v -n\"")
|
||||
luci.http.write("\n")
|
||||
luci.http.write(dash)
|
||||
luci.http.write("\n")
|
||||
if output ~= "" then
|
||||
luci.http.write(output)
|
||||
luci.http.write("\n")
|
||||
else
|
||||
luci.http.write("No data found")
|
||||
luci.http.write("\n")
|
||||
end
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
-- Copyright 2017 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local net = require "luci.model.network".init()
|
||||
|
||||
local s, m, o
|
||||
|
||||
m = Map("mwan3", translate("MWAN - Globals"))
|
||||
|
||||
s = m:section(NamedSection, "globals", "globals", nil)
|
||||
|
||||
o = s:option(Value, "mmx_mask",
|
||||
translate("Firewall mask"),
|
||||
translate("Enter value in hex, starting with <code>0x</code>"))
|
||||
o.datatype = "hex(4)"
|
||||
o.default = "0x3F00"
|
||||
|
||||
o = s:option(Flag, "logging",
|
||||
translate("Logging"),
|
||||
translate("Enables global firewall logging"))
|
||||
|
||||
o = s:option(ListValue, "loglevel",
|
||||
translate("Loglevel"),
|
||||
translate("Firewall loglevel"))
|
||||
o.default = "notice"
|
||||
o:value("emerg", translate("Emergency"))
|
||||
o:value("alert", translate("Alert"))
|
||||
o:value("crit", translate("Critical"))
|
||||
o:value("error", translate("Error"))
|
||||
o:value("warning", translate("Warning"))
|
||||
o:value("notice", translate("Notice"))
|
||||
o:value("info", translate("Info"))
|
||||
o:value("debug", translate("Debug"))
|
||||
o:depends("logging", "1")
|
||||
|
||||
o = s:option(DynamicList, "rt_table_lookup",
|
||||
translate("Routing table lookup"),
|
||||
translate("Also scan this Routing table for connected networks"))
|
||||
o.datatype = "integer"
|
||||
o:value("220", translatef("Routing table %d", 220))
|
||||
|
||||
return m
|
|
@ -1,241 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
local uci = require "uci"
|
||||
|
||||
local m, mwan_interface, enabled, track_method, reliability, interval
|
||||
local down, up, metric
|
||||
|
||||
function interfaceWarnings(overview, count, iface_max)
|
||||
local warnings = ""
|
||||
if count <= iface_max then
|
||||
warnings = string.format("<strong>%s</strong><br />",
|
||||
translatef("There are currently %d of %d supported interfaces configured", count, iface_max)
|
||||
)
|
||||
else
|
||||
warnings = string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: %d interfaces are configured exceeding the maximum of %d!", count, iface_max)
|
||||
)
|
||||
end
|
||||
|
||||
for i, k in pairs(overview) do
|
||||
if overview[i]["network"] == false then
|
||||
warnings = warnings .. string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: Interface %s are not found in /etc/config/network", i)
|
||||
)
|
||||
end
|
||||
|
||||
if overview[i]["default_route"] == false then
|
||||
warnings = warnings .. string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: Interface %s has no default route in the main routing table", i)
|
||||
)
|
||||
end
|
||||
|
||||
if overview[i]["reliability"] == false then
|
||||
warnings = warnings .. string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: Interface %s has a higher reliability " ..
|
||||
"requirement than tracking hosts (%d)", i, overview[i]["tracking"])
|
||||
)
|
||||
end
|
||||
|
||||
if overview[i]["duplicate_metric"] == true then
|
||||
warnings = warnings .. string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: Interface %s has a duplicate metric %s configured", i, overview[i]["metric"])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return warnings
|
||||
end
|
||||
|
||||
function configCheck()
|
||||
local overview = {}
|
||||
local count = 0
|
||||
local duplicate_metric = {}
|
||||
uci.cursor():foreach("mwan3", "interface",
|
||||
function (section)
|
||||
local uci = uci.cursor(nil, "/var/state")
|
||||
local iface = section[".name"]
|
||||
overview[iface] = {}
|
||||
count = count + 1
|
||||
local network = uci:get("network", iface)
|
||||
overview[iface]["network"] = false
|
||||
if network ~= nil then
|
||||
overview[iface]["network"] = true
|
||||
|
||||
local device = uci:get("network", iface, "ifname")
|
||||
if device ~= nil then
|
||||
overview[iface]["device"] = device
|
||||
end
|
||||
|
||||
local metric = uci:get("network", iface, "metric")
|
||||
if metric ~= nil then
|
||||
overview[iface]["metric"] = metric
|
||||
overview[iface]["duplicate_metric"] = false
|
||||
for _, m in ipairs(duplicate_metric) do
|
||||
if m == metric then
|
||||
overview[iface]["duplicate_metric"] = true
|
||||
end
|
||||
end
|
||||
table.insert(duplicate_metric, metric)
|
||||
end
|
||||
|
||||
local dump = require("luci.util").ubus("network.interface.%s" % iface, "status", {})
|
||||
overview[iface]["default_route"] = false
|
||||
if dump and dump.route then
|
||||
local _, route
|
||||
for _, route in ipairs(dump.route) do
|
||||
if dump.route[_].target == "0.0.0.0" then
|
||||
overview[iface]["default_route"] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local trackingNumber = uci:get("mwan3", iface, "track_ip")
|
||||
overview[iface]["tracking"] = 0
|
||||
if trackingNumber and #trackingNumber > 0 then
|
||||
overview[iface]["tracking"] = #trackingNumber
|
||||
overview[iface]["reliability"] = false
|
||||
local reliabilityNumber = tonumber(uci:get("mwan3", iface, "reliability") or "1")
|
||||
if reliabilityNumber and reliabilityNumber <= #trackingNumber then
|
||||
overview[iface]["reliability"] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- calculate iface_max usage from firewall mmx_mask
|
||||
function bit(p)
|
||||
return 2 ^ (p - 1)
|
||||
end
|
||||
function hasbit(x, p)
|
||||
return x % (p + p) >= p
|
||||
end
|
||||
function setbit(x, p)
|
||||
return hasbit(x, p) and x or x + p
|
||||
end
|
||||
|
||||
local uci = require("uci").cursor(nil, "/var/state")
|
||||
local mmx_mask = uci:get("mwan3", "globals", "mmx_mask") or "0x3F00"
|
||||
local number = tonumber(mmx_mask, 16)
|
||||
local bits = 0
|
||||
local iface_max = 0
|
||||
for i=1,16 do
|
||||
if hasbit(number, bit(i)) then
|
||||
bits = bits + 1
|
||||
iface_max = setbit( iface_max, bit(bits))
|
||||
end
|
||||
end
|
||||
|
||||
-- subtract blackhole, unreachable and default table from iface_max
|
||||
iface_max = iface_max - 3
|
||||
|
||||
return overview, count, iface_max
|
||||
end
|
||||
|
||||
m = Map("mwan3", translate("MWAN - Interfaces"),
|
||||
interfaceWarnings(configCheck()))
|
||||
|
||||
mwan_interface = m:section(TypedSection, "interface", nil,
|
||||
translate("mwan3 requires that all interfaces have a unique metric configured in /etc/config/network<br />" ..
|
||||
"Names must match the interface name found in /etc/config/network<br />" ..
|
||||
"Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
|
||||
"Interfaces may not share the same name as configured members, policies or rules"))
|
||||
mwan_interface.addremove = true
|
||||
mwan_interface.dynamic = false
|
||||
mwan_interface.sectionhead = translate("Interface")
|
||||
mwan_interface.sortable = false
|
||||
mwan_interface.template = "cbi/tblsection"
|
||||
mwan_interface.extedit = dsp.build_url("admin", "network", "mwan", "interface", "%s")
|
||||
function mwan_interface.create(self, section)
|
||||
TypedSection.create(self, section)
|
||||
m.uci:save("mwan3")
|
||||
luci.http.redirect(dsp.build_url("admin", "network", "mwan", "interface", section))
|
||||
end
|
||||
|
||||
enabled = mwan_interface:option(DummyValue, "enabled", translate("Enabled"))
|
||||
enabled.rawhtml = true
|
||||
function enabled.cfgvalue(self, s)
|
||||
if self.map:get(s, "enabled") == "1" then
|
||||
return translate("Yes")
|
||||
else
|
||||
return translate("No")
|
||||
end
|
||||
end
|
||||
|
||||
track_method = mwan_interface:option(DummyValue, "track_method", translate("Tracking method"))
|
||||
track_method.rawhtml = true
|
||||
function track_method.cfgvalue(self, s)
|
||||
local tracked = self.map:get(s, "track_ip")
|
||||
if tracked then
|
||||
return self.map:get(s, "track_method") or "ping"
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
reliability = mwan_interface:option(DummyValue, "reliability", translate("Tracking reliability"))
|
||||
reliability.rawhtml = true
|
||||
function reliability.cfgvalue(self, s)
|
||||
local tracked = self.map:get(s, "track_ip")
|
||||
if tracked then
|
||||
return self.map:get(s, "reliability") or "1"
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
interval = mwan_interface:option(DummyValue, "interval", translate("Ping interval"))
|
||||
interval.rawhtml = true
|
||||
function interval.cfgvalue(self, s)
|
||||
local tracked = self.map:get(s, "track_ip")
|
||||
if tracked then
|
||||
local intervalValue = self.map:get(s, "interval")
|
||||
if intervalValue then
|
||||
return intervalValue .. "s"
|
||||
else
|
||||
return "5s"
|
||||
end
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
down = mwan_interface:option(DummyValue, "down", translate("Interface down"))
|
||||
down.rawhtml = true
|
||||
function down.cfgvalue(self, s)
|
||||
local tracked = self.map:get(s, "track_ip")
|
||||
if tracked then
|
||||
return self.map:get(s, "down") or "3"
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
up = mwan_interface:option(DummyValue, "up", translate("Interface up"))
|
||||
up.rawhtml = true
|
||||
function up.cfgvalue(self, s)
|
||||
local tracked = self.map:get(s, "track_ip")
|
||||
if tracked then
|
||||
return self.map:get(s, "up") or "3"
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
metric = mwan_interface:option(DummyValue, "metric", translate("Metric"))
|
||||
metric.rawhtml = true
|
||||
function metric.cfgvalue(self, s)
|
||||
local uci = uci.cursor(nil, "/var/state")
|
||||
local metric = uci:get("network", s, "metric")
|
||||
if metric then
|
||||
return metric
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,262 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
|
||||
local m, mwan_interface, enabled, initial_state, family, track_ip
|
||||
local track_method, reliability, count, size, max_ttl
|
||||
local check_quality, failure_latency, failure_loss, recovery_latency
|
||||
local recovery_loss, timeout, interval, failure
|
||||
local keep_failure, recovery, down, up, flush, metric
|
||||
local httping_ssl
|
||||
|
||||
arg[1] = arg[1] or ""
|
||||
|
||||
m = Map("mwan3", translatef("MWAN Interface Configuration - %s", arg[1]))
|
||||
m.redirect = dsp.build_url("admin", "network", "mwan", "interface")
|
||||
|
||||
mwan_interface = m:section(NamedSection, arg[1], "interface", "")
|
||||
mwan_interface.addremove = false
|
||||
mwan_interface.dynamic = false
|
||||
|
||||
enabled = mwan_interface:option(Flag, "enabled", translate("Enabled"))
|
||||
enabled.default = false
|
||||
|
||||
initial_state = mwan_interface:option(ListValue, "initial_state", translate("Initial state"),
|
||||
translate("Expect interface state on up event"))
|
||||
initial_state.default = "online"
|
||||
initial_state:value("online", translate("Online"))
|
||||
initial_state:value("offline", translate("Offline"))
|
||||
|
||||
family = mwan_interface:option(ListValue, "family", translate("Internet Protocol"))
|
||||
family.default = "ipv4"
|
||||
family:value("ipv4", translate("IPv4"))
|
||||
family:value("ipv6", translate("IPv6"))
|
||||
|
||||
track_ip = mwan_interface:option(DynamicList, "track_ip", translate("Tracking hostname or IP address"),
|
||||
translate("This hostname or IP address will be pinged to determine if the link is up or down. Leave blank to assume interface is always online"))
|
||||
track_ip.datatype = "host"
|
||||
|
||||
track_method = mwan_interface:option(ListValue, "track_method", translate("Tracking method"))
|
||||
track_method.default = "ping"
|
||||
track_method:value("ping")
|
||||
if os.execute("command -v nping 1>/dev/null") == 0 then
|
||||
track_method:value("nping-tcp")
|
||||
track_method:value("nping-udp")
|
||||
track_method:value("nping-icmp")
|
||||
track_method:value("nping-arp")
|
||||
end
|
||||
|
||||
if os.execute("command -v arping 1>/dev/null") == 0 then
|
||||
track_method:value("arping")
|
||||
end
|
||||
|
||||
if os.execute("command -v httping 1>/dev/null") == 0 then
|
||||
track_method:value("httping")
|
||||
end
|
||||
|
||||
httping_ssl = mwan_interface:option(Flag, "httping_ssl", translate("Enable ssl tracking"),
|
||||
translate("Enables https tracking on ssl port 443"))
|
||||
httping_ssl:depends("track_method", "httping")
|
||||
httping_ssl.rmempty = false
|
||||
httping_ssl.default = httping_ssl.enabled
|
||||
|
||||
reliability = mwan_interface:option(Value, "reliability", translate("Tracking reliability"),
|
||||
translate("Acceptable values: 1-100. This many Tracking IP addresses must respond for the link to be deemed up"))
|
||||
reliability.datatype = "range(1, 100)"
|
||||
reliability.default = "1"
|
||||
|
||||
count = mwan_interface:option(ListValue, "count", translate("Ping count"))
|
||||
count.default = "1"
|
||||
count:value("1")
|
||||
count:value("2")
|
||||
count:value("3")
|
||||
count:value("4")
|
||||
count:value("5")
|
||||
|
||||
size = mwan_interface:option(Value, "size", translate("Ping size"))
|
||||
size.default = "56"
|
||||
size:depends("track_method", "ping")
|
||||
size:value("8")
|
||||
size:value("24")
|
||||
size:value("56")
|
||||
size:value("120")
|
||||
size:value("248")
|
||||
size:value("504")
|
||||
size:value("1016")
|
||||
size:value("1472")
|
||||
size:value("2040")
|
||||
size.datatype = "range(1, 65507)"
|
||||
|
||||
max_ttl = mwan_interface:option(Value, "max_ttl", translate("Max TTL"))
|
||||
max_ttl.default = "60"
|
||||
max_ttl:depends("track_method", "ping")
|
||||
max_ttl:value("10")
|
||||
max_ttl:value("20")
|
||||
max_ttl:value("30")
|
||||
max_ttl:value("40")
|
||||
max_ttl:value("50")
|
||||
max_ttl:value("60")
|
||||
max_ttl:value("70")
|
||||
max_ttl.datatype = "range(1, 255)"
|
||||
|
||||
check_quality = mwan_interface:option(Flag, "check_quality", translate("Check link quality"))
|
||||
check_quality:depends("track_method", "ping")
|
||||
check_quality.default = false
|
||||
|
||||
failure_latency = mwan_interface:option(Value, "failure_latency", translate("Failure latency [ms]"))
|
||||
failure_latency:depends("check_quality", 1)
|
||||
failure_latency.default = "1000"
|
||||
failure_latency:value("25")
|
||||
failure_latency:value("50")
|
||||
failure_latency:value("75")
|
||||
failure_latency:value("100")
|
||||
failure_latency:value("150")
|
||||
failure_latency:value("200")
|
||||
failure_latency:value("250")
|
||||
failure_latency:value("300")
|
||||
|
||||
failure_loss = mwan_interface:option(Value, "failure_loss", translate("Failure packet loss [%]"))
|
||||
failure_loss:depends("check_quality", 1)
|
||||
failure_loss.default = "40"
|
||||
failure_loss:value("2")
|
||||
failure_loss:value("5")
|
||||
failure_loss:value("10")
|
||||
failure_loss:value("20")
|
||||
failure_loss:value("25")
|
||||
|
||||
recovery_latency = mwan_interface:option(Value, "recovery_latency", translate("Recovery latency [ms]"))
|
||||
recovery_latency:depends("check_quality", 1)
|
||||
recovery_latency.default = "500"
|
||||
recovery_latency:value("25")
|
||||
recovery_latency:value("50")
|
||||
recovery_latency:value("75")
|
||||
recovery_latency:value("100")
|
||||
recovery_latency:value("150")
|
||||
recovery_latency:value("200")
|
||||
recovery_latency:value("250")
|
||||
recovery_latency:value("300")
|
||||
|
||||
recovery_loss = mwan_interface:option(Value, "recovery_loss", translate("Recovery packet loss [%]"))
|
||||
recovery_loss:depends("check_quality", 1)
|
||||
recovery_loss.default = "10"
|
||||
recovery_loss:value("2")
|
||||
recovery_loss:value("5")
|
||||
recovery_loss:value("10")
|
||||
recovery_loss:value("20")
|
||||
recovery_loss:value("25")
|
||||
|
||||
timeout = mwan_interface:option(ListValue, "timeout", translate("Ping timeout"))
|
||||
timeout.default = "4"
|
||||
timeout:value("1", translatef("%d second", 1))
|
||||
timeout:value("2", translatef("%d seconds", 2))
|
||||
timeout:value("3", translatef("%d seconds", 3))
|
||||
timeout:value("4", translatef("%d seconds", 4))
|
||||
timeout:value("5", translatef("%d seconds", 5))
|
||||
timeout:value("6", translatef("%d seconds", 6))
|
||||
timeout:value("7", translatef("%d seconds", 7))
|
||||
timeout:value("8", translatef("%d seconds", 8))
|
||||
timeout:value("9", translatef("%d seconds", 9))
|
||||
timeout:value("10", translatef("%d seconds", 10))
|
||||
|
||||
interval = mwan_interface:option(ListValue, "interval", translate("Ping interval"))
|
||||
interval.default = "10"
|
||||
interval:value("1", translatef("%d second", 1))
|
||||
interval:value("3", translatef("%d seconds", 3))
|
||||
interval:value("5", translatef("%d seconds", 5))
|
||||
interval:value("10", translatef("%d seconds", 10))
|
||||
interval:value("20", translatef("%d seconds", 20))
|
||||
interval:value("30", translatef("%d seconds", 30))
|
||||
interval:value("60", translatef("%d minute", 1))
|
||||
interval:value("300", translatef("%d minutes", 5))
|
||||
interval:value("600", translatef("%d minutes", 10))
|
||||
interval:value("900", translatef("%d minutes", 15))
|
||||
interval:value("1800", translatef("%d minutes", 30))
|
||||
interval:value("3600", translatef("%d hour", 1))
|
||||
|
||||
failure = mwan_interface:option(Value, "failure_interval", translate("Failure interval"),
|
||||
translate("Ping interval during failure detection"))
|
||||
failure.default = "5"
|
||||
failure:value("1", translatef("%d second", 1))
|
||||
failure:value("3", translatef("%d seconds", 3))
|
||||
failure:value("5", translatef("%d seconds", 5))
|
||||
failure:value("10", translatef("%d seconds", 10))
|
||||
failure:value("20", translatef("%d seconds", 20))
|
||||
failure:value("30", translatef("%d seconds", 30))
|
||||
failure:value("60", translatef("%d minute", 1))
|
||||
failure:value("300", translatef("%d minutes", 5))
|
||||
failure:value("600", translatef("%d minutes", 10))
|
||||
failure:value("900", translatef("%d minutes", 15))
|
||||
failure:value("1800", translatef("%d minutes", 30))
|
||||
failure:value("3600", translatef("%d hour", 1))
|
||||
|
||||
keep_failure = mwan_interface:option(Flag, "keep_failure_interval", translate("Keep failure interval"),
|
||||
translate("Keep ping failure interval during failure state"))
|
||||
keep_failure.default = keep_failure.disabled
|
||||
|
||||
recovery = mwan_interface:option(Value, "recovery_interval", translate("Recovery interval"),
|
||||
translate("Ping interval during failure recovering"))
|
||||
recovery.default = "5"
|
||||
recovery:value("1", translatef("%d second", 1))
|
||||
recovery:value("3", translatef("%d seconds", 3))
|
||||
recovery:value("5", translatef("%d seconds", 5))
|
||||
recovery:value("10", translatef("%d seconds", 10))
|
||||
recovery:value("20", translatef("%d seconds", 20))
|
||||
recovery:value("30", translatef("%d seconds", 30))
|
||||
recovery:value("60", translatef("%d minute", 1))
|
||||
recovery:value("300", translatef("%d minutes", 5))
|
||||
recovery:value("600", translatef("%d minutes", 10))
|
||||
recovery:value("900", translatef("%d minutes", 15))
|
||||
recovery:value("1800", translatef("%d minutes", 30))
|
||||
recovery:value("3600", translatef("%d hour", 1))
|
||||
|
||||
down = mwan_interface:option(ListValue, "down", translate("Interface down"),
|
||||
translate("Interface will be deemed down after this many failed ping tests"))
|
||||
down.default = "5"
|
||||
down:value("1")
|
||||
down:value("2")
|
||||
down:value("3")
|
||||
down:value("4")
|
||||
down:value("5")
|
||||
down:value("6")
|
||||
down:value("7")
|
||||
down:value("8")
|
||||
down:value("9")
|
||||
down:value("10")
|
||||
|
||||
up = mwan_interface:option(ListValue, "up", translate("Interface up"),
|
||||
translate("Downed interface will be deemed up after this many successful ping tests"))
|
||||
up.default = "5"
|
||||
up:value("1")
|
||||
up:value("2")
|
||||
up:value("3")
|
||||
up:value("4")
|
||||
up:value("5")
|
||||
up:value("6")
|
||||
up:value("7")
|
||||
up:value("8")
|
||||
up:value("9")
|
||||
up:value("10")
|
||||
|
||||
flush = mwan_interface:option(StaticList, "flush_conntrack", translate("Flush conntrack table"),
|
||||
translate("Flush global firewall conntrack table on interface events"))
|
||||
flush:value("ifup", translate("ifup (netifd)"))
|
||||
flush:value("ifdown", translate("ifdown (netifd)"))
|
||||
flush:value("connected", translate("connected (mwan3)"))
|
||||
flush:value("disconnected", translate("disconnected (mwan3)"))
|
||||
|
||||
metric = mwan_interface:option(DummyValue, "metric", translate("Metric"),
|
||||
translate("This displays the metric assigned to this interface in /etc/config/network"))
|
||||
metric.rawhtml = true
|
||||
function metric.cfgvalue(self, s)
|
||||
local uci = require "luci.model.uci".cursor(nil, "/var/state")
|
||||
local metric = uci:get("network", arg[1], "metric")
|
||||
if metric then
|
||||
return metric
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,45 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
|
||||
local m, s, o
|
||||
|
||||
m = Map("mwan3", translate("MWAN - Members"))
|
||||
|
||||
s = m:section(TypedSection, "member", nil,
|
||||
translate("Members are profiles attaching a metric and weight to an MWAN interface<br />" ..
|
||||
"Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
|
||||
"Members may not share the same name as configured interfaces, policies or rules"))
|
||||
s.addremove = true
|
||||
s.dynamic = false
|
||||
s.sectionhead = translate("Member")
|
||||
s.sortable = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.extedit = dsp.build_url("admin", "network", "mwan", "member", "%s")
|
||||
function s.create(self, section)
|
||||
TypedSection.create(self, section)
|
||||
m.uci:save("mwan3")
|
||||
luci.http.redirect(dsp.build_url("admin", "network", "mwan", "member", section))
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "interface", translate("Interface"))
|
||||
o.rawhtml = true
|
||||
function o.cfgvalue(self, s)
|
||||
return self.map:get(s, "interface") or "—"
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "metric", translate("Metric"))
|
||||
o.rawhtml = true
|
||||
function o.cfgvalue(self, s)
|
||||
return self.map:get(s, "metric") or "1"
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "weight", translate("Weight"))
|
||||
o.rawhtml = true
|
||||
function o.cfgvalue(self, s)
|
||||
return self.map:get(s, "weight") or "1"
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,33 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
|
||||
local m, mwan_member, interface, metric, weight
|
||||
|
||||
arg[1] = arg[1] or ""
|
||||
|
||||
m = Map("mwan3", translatef("MWAN Member Configuration - %s", arg[1]))
|
||||
m.redirect = dsp.build_url("admin", "network", "mwan", "member")
|
||||
|
||||
mwan_member = m:section(NamedSection, arg[1], "member", "")
|
||||
mwan_member.addremove = false
|
||||
mwan_member.dynamic = false
|
||||
|
||||
interface = mwan_member:option(Value, "interface", translate("Interface"))
|
||||
m.uci:foreach("mwan3", "interface",
|
||||
function(s)
|
||||
interface:value(s['.name'], s['.name'])
|
||||
end
|
||||
)
|
||||
|
||||
metric = mwan_member:option(Value, "metric", translate("Metric"),
|
||||
translate("Acceptable values: 1-256. Defaults to 1 if not set"))
|
||||
metric.datatype = "range(1, 256)"
|
||||
|
||||
weight = mwan_member:option(Value, "weight", translate("Weight"),
|
||||
translate("Acceptable values: 1-1000. Defaults to 1 if not set"))
|
||||
weight.datatype = "range(1, 1000)"
|
||||
|
||||
return m
|
|
@ -1,46 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local fs = require "nixio.fs"
|
||||
local ut = require "luci.util"
|
||||
local script = "/etc/mwan3.user"
|
||||
|
||||
local m, f, t
|
||||
|
||||
m = SimpleForm("luci", translate("MWAN - Notification"))
|
||||
|
||||
f = m:section(SimpleSection, nil,
|
||||
translate("This section allows you to modify the content of \"/etc/mwan3.user\".<br />" ..
|
||||
"The file is also preserved during sysupgrade.<br />" ..
|
||||
"<br />" ..
|
||||
"Notes:<br />" ..
|
||||
"This file is interpreted as a shell script.<br />" ..
|
||||
"The first line of the script must be "#!/bin/sh" without quotes.<br />" ..
|
||||
"Lines beginning with # are comments and are not executed.<br />" ..
|
||||
"Put your custom mwan3 action here, they will<br />" ..
|
||||
"be executed with each netifd hotplug interface event<br />" ..
|
||||
"on interfaces for which mwan3 is enabled.<br />" ..
|
||||
"<br />" ..
|
||||
"There are three main environment variables that are passed to this script.<br />" ..
|
||||
"<br />" ..
|
||||
"$ACTION <br />" ..
|
||||
"* \"ifup\" Is called by netifd and mwan3track <br />" ..
|
||||
"* \"ifdown\" Is called by netifd and mwan3track <br />" ..
|
||||
"* \"connected\" Is only called by mwan3track if tracking was successful <br />" ..
|
||||
"* \"disconnected\" Is only called by mwan3track if tracking has failed <br />" ..
|
||||
"$INTERFACE Name of the interface which went up or down (e.g. \"wan\" or \"wwan\")<br />" ..
|
||||
"$DEVICE Physical device name which interface went up or down (e.g. \"eth0\" or \"wwan0\")<br />" ..
|
||||
"<br />"))
|
||||
|
||||
t = f:option(TextValue, "lines")
|
||||
t.rmempty = true
|
||||
t.rows = 20
|
||||
function t.cfgvalue()
|
||||
return fs.readfile(script)
|
||||
end
|
||||
function t.write(self, section, data)
|
||||
return fs.writefile(script, ut.trim(data:gsub("\r\n", "\n")) .. "\n")
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,92 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
local uci = require "uci"
|
||||
|
||||
local m, s, o
|
||||
|
||||
function policyCheck()
|
||||
local policy_error = {}
|
||||
|
||||
uci.cursor():foreach("mwan3", "policy",
|
||||
function (section)
|
||||
policy_error[section[".name"]] = false
|
||||
if string.len(section[".name"]) > 15 then
|
||||
policy_error[section[".name"]] = true
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return policy_error
|
||||
end
|
||||
|
||||
function policyError(policy_error)
|
||||
local warnings = ""
|
||||
for i, k in pairs(policy_error) do
|
||||
if policy_error[i] == true then
|
||||
warnings = warnings .. string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: Policy %s has exceeding the maximum name of 15 characters", i)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return warnings
|
||||
end
|
||||
|
||||
m = Map("mwan3", translate("MWAN - Policies"),
|
||||
policyError(policyCheck()))
|
||||
|
||||
s = m:section(TypedSection, "policy", nil,
|
||||
translate("Policies are profiles grouping one or more members controlling how MWAN distributes traffic<br />" ..
|
||||
"Member interfaces with lower metrics are used first<br />" ..
|
||||
"Member interfaces with the same metric will be load-balanced<br />" ..
|
||||
"Load-balanced member interfaces distribute more traffic out those with higher weights<br />" ..
|
||||
"Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
|
||||
"Names must be 15 characters or less<br />" ..
|
||||
"Policies may not share the same name as configured interfaces, members or rules"))
|
||||
s.addremove = true
|
||||
s.dynamic = false
|
||||
s.sectionhead = translate("Policy")
|
||||
s.sortable = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.extedit = dsp.build_url("admin", "network", "mwan", "policy", "%s")
|
||||
function s.create(self, section)
|
||||
if #section > 15 then
|
||||
self.invalid_cts = true
|
||||
else
|
||||
TypedSection.create(self, section)
|
||||
m.uci:save("mwan3")
|
||||
luci.http.redirect(dsp.build_url("admin", "network", "mwan", "policy", section))
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "use_member", translate("Members assigned"))
|
||||
o.rawhtml = true
|
||||
function o.cfgvalue(self, s)
|
||||
local memberConfig, memberList = self.map:get(s, "use_member"), ""
|
||||
if memberConfig then
|
||||
for k,v in pairs(memberConfig) do
|
||||
memberList = memberList .. v .. "<br />"
|
||||
end
|
||||
return memberList
|
||||
else
|
||||
return "—"
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "last_resort", translate("Last resort"))
|
||||
o.rawhtml = true
|
||||
function o.cfgvalue(self, s)
|
||||
local action = self.map:get(s, "last_resort")
|
||||
if action == "blackhole" then
|
||||
return translate("blackhole (drop)")
|
||||
elseif action == "default" then
|
||||
return translate("default (use main routing table)")
|
||||
else
|
||||
return translate("unreachable (reject)")
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,32 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
|
||||
local m, mwan_policy, member, last_resort
|
||||
|
||||
arg[1] = arg[1] or ""
|
||||
|
||||
m = Map("mwan3", translatef("MWAN Policy Configuration - %s", arg[1]))
|
||||
m.redirect = dsp.build_url("admin", "network", "mwan", "policy")
|
||||
|
||||
mwan_policy = m:section(NamedSection, arg[1], "policy", "")
|
||||
mwan_policy.addremove = false
|
||||
mwan_policy.dynamic = false
|
||||
|
||||
member = mwan_policy:option(DynamicList, "use_member", translate("Member used"))
|
||||
m.uci:foreach("mwan3", "member",
|
||||
function(s)
|
||||
member:value(s['.name'], s['.name'])
|
||||
end
|
||||
)
|
||||
|
||||
last_resort = mwan_policy:option(ListValue, "last_resort", translate("Last resort"),
|
||||
translate("When all policy members are offline use this behavior for matched traffic"))
|
||||
last_resort.default = "unreachable"
|
||||
last_resort:value("unreachable", translate("unreachable (reject)"))
|
||||
last_resort:value("blackhole", translate("blackhole (drop)"))
|
||||
last_resort:value("default", translate("default (use main routing table)"))
|
||||
|
||||
return m
|
|
@ -1,109 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
local uci = require "uci"
|
||||
|
||||
local m, mwan_rule, src_ip, src_port, dest_ip, dest_port, proto, use_policy
|
||||
|
||||
function ruleCheck()
|
||||
local rule_error = {}
|
||||
uci.cursor():foreach("mwan3", "rule",
|
||||
function (section)
|
||||
rule_error[section[".name"]] = false
|
||||
local uci = uci.cursor(nil, "/var/state")
|
||||
local sourcePort = uci:get("mwan3", section[".name"], "src_port")
|
||||
local destPort = uci:get("mwan3", section[".name"], "dest_port")
|
||||
if sourcePort ~= nil or destPort ~= nil then
|
||||
local protocol = uci:get("mwan3", section[".name"], "proto")
|
||||
if protocol == nil or protocol == "all" then
|
||||
rule_error[section[".name"]] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
return rule_error
|
||||
end
|
||||
|
||||
function ruleWarn(rule_error)
|
||||
local warnings = ""
|
||||
for i, k in pairs(rule_error) do
|
||||
if rule_error[i] == true then
|
||||
warnings = warnings .. string.format("<strong>%s</strong><br />",
|
||||
translatef("WARNING: Rule %s have a port configured with no or improper protocol specified!", i)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return warnings
|
||||
end
|
||||
|
||||
m = Map("mwan3", translate("MWAN - Rules"),
|
||||
ruleWarn(ruleCheck())
|
||||
)
|
||||
|
||||
mwan_rule = m:section(TypedSection, "rule", nil,
|
||||
translate("Rules specify which traffic will use a particular MWAN policy<br />" ..
|
||||
"Rules are based on IP address, port or protocol<br />" ..
|
||||
"Rules are matched from top to bottom<br />" ..
|
||||
"Rules below a matching rule are ignored<br />" ..
|
||||
"Traffic not matching any rule is routed using the main routing table<br />" ..
|
||||
"Traffic destined for known (other than default) networks is handled by the main routing table<br />" ..
|
||||
"Traffic matching a rule, but all WAN interfaces for that policy are down will be blackholed<br />" ..
|
||||
"Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" ..
|
||||
"Rules may not share the same name as configured interfaces, members or policies"))
|
||||
mwan_rule.addremove = true
|
||||
mwan_rule.anonymous = false
|
||||
mwan_rule.dynamic = false
|
||||
mwan_rule.sectionhead = translate("Rule")
|
||||
mwan_rule.sortable = true
|
||||
mwan_rule.template = "cbi/tblsection"
|
||||
mwan_rule.extedit = dsp.build_url("admin", "network", "mwan", "rule", "%s")
|
||||
function mwan_rule.create(self, section)
|
||||
if #section > 15 then
|
||||
self.invalid_cts = true
|
||||
else
|
||||
TypedSection.create(self, section)
|
||||
m.uci:save("mwan3")
|
||||
luci.http.redirect(dsp.build_url("admin", "network", "mwan", "rule", section))
|
||||
end
|
||||
end
|
||||
|
||||
src_ip = mwan_rule:option(DummyValue, "src_ip", translate("Source address"))
|
||||
src_ip.rawhtml = true
|
||||
function src_ip.cfgvalue(self, s)
|
||||
return self.map:get(s, "src_ip") or "—"
|
||||
end
|
||||
|
||||
src_port = mwan_rule:option(DummyValue, "src_port", translate("Source port"))
|
||||
src_port.rawhtml = true
|
||||
function src_port.cfgvalue(self, s)
|
||||
return self.map:get(s, "src_port") or "—"
|
||||
end
|
||||
|
||||
dest_ip = mwan_rule:option(DummyValue, "dest_ip", translate("Destination address"))
|
||||
dest_ip.rawhtml = true
|
||||
function dest_ip.cfgvalue(self, s)
|
||||
return self.map:get(s, "dest_ip") or "—"
|
||||
end
|
||||
|
||||
dest_port = mwan_rule:option(DummyValue, "dest_port", translate("Destination port"))
|
||||
dest_port.rawhtml = true
|
||||
function dest_port.cfgvalue(self, s)
|
||||
return self.map:get(s, "dest_port") or "—"
|
||||
end
|
||||
|
||||
proto = mwan_rule:option(DummyValue, "proto", translate("Protocol"))
|
||||
proto.rawhtml = true
|
||||
function proto.cfgvalue(self, s)
|
||||
return self.map:get(s, "proto") or "all"
|
||||
end
|
||||
|
||||
use_policy = mwan_rule:option(DummyValue, "use_policy", translate("Policy assigned"))
|
||||
use_policy.rawhtml = true
|
||||
function use_policy.cfgvalue(self, s)
|
||||
return self.map:get(s, "use_policy") or "—"
|
||||
end
|
||||
|
||||
return m
|
|
@ -1,85 +0,0 @@
|
|||
-- Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
-- Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
-- Licensed to the public under the GNU General Public License v2.
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
local util = require("luci.util")
|
||||
|
||||
local m, s, o
|
||||
|
||||
arg[1] = arg[1] or ""
|
||||
|
||||
local ipsets = util.split(util.trim(util.exec("ipset -n -L 2>/dev/null | grep -v mwan3_ | sort")), "\n", nil, true) or {}
|
||||
|
||||
m = Map("mwan3", translatef("MWAN Rule Configuration - %s", arg[1]))
|
||||
m.redirect = dsp.build_url("admin", "network", "mwan", "rule")
|
||||
|
||||
s = m:section(NamedSection, arg[1], "rule", "")
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
o = s:option(ListValue, "family", translate("Internet Protocol"))
|
||||
o.default = ""
|
||||
o:value("", translate("IPv4 and IPv6"))
|
||||
o:value("ipv4", translate("IPv4 only"))
|
||||
o:value("ipv6", translate("IPv6 only"))
|
||||
|
||||
o = s:option(Value, "src_ip", translate("Source address"),
|
||||
translate("Supports CIDR notation (eg \"192.168.100.0/24\") without quotes"))
|
||||
o.datatype = ipaddr
|
||||
|
||||
o = s:option(Value, "src_port", translate("Source port"),
|
||||
translate("May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes"))
|
||||
o:depends("proto", "tcp")
|
||||
o:depends("proto", "udp")
|
||||
|
||||
o = s:option(Value, "dest_ip", translate("Destination address"),
|
||||
translate("Supports CIDR notation (eg \"192.168.100.0/24\") without quotes"))
|
||||
o.datatype = ipaddr
|
||||
|
||||
o = s:option(Value, "dest_port", translate("Destination port"),
|
||||
translate("May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes"))
|
||||
o:depends("proto", "tcp")
|
||||
o:depends("proto", "udp")
|
||||
|
||||
o = s:option(Value, "proto", translate("Protocol"),
|
||||
translate("View the content of /etc/protocols for protocol description"))
|
||||
o.default = "all"
|
||||
o.rmempty = false
|
||||
o:value("all")
|
||||
o:value("tcp")
|
||||
o:value("udp")
|
||||
o:value("icmp")
|
||||
o:value("esp")
|
||||
|
||||
o = s:option(ListValue, "sticky", translate("Sticky"),
|
||||
translate("Traffic from the same source IP address that previously matched this rule within the sticky timeout period will use the same WAN interface"))
|
||||
o.default = "0"
|
||||
o:value("1", translate("Yes"))
|
||||
o:value("0", translate("No"))
|
||||
|
||||
o = s:option(Value, "timeout", translate("Sticky timeout"),
|
||||
translate("Seconds. Acceptable values: 1-1000000. Defaults to 600 if not set"))
|
||||
o.datatype = "range(1, 1000000)"
|
||||
|
||||
o = s:option(Value, "ipset", translate("IPset"),
|
||||
translate("Name of IPset rule. Requires IPset rule in /etc/dnsmasq.conf (eg \"ipset=/youtube.com/youtube\")"))
|
||||
o:value("", translate("-- Please choose --"))
|
||||
for _, z in ipairs(ipsets) do
|
||||
o:value(z)
|
||||
end
|
||||
|
||||
o = s:option(Flag, "logging", translate("Logging"),
|
||||
translate("Enables firewall rule logging (global mwan3 logging must also be enabled)"))
|
||||
|
||||
o = s:option(Value, "use_policy", translate("Policy assigned"))
|
||||
m.uci:foreach("mwan3", "policy",
|
||||
function(s)
|
||||
o:value(s['.name'], s['.name'])
|
||||
end
|
||||
)
|
||||
o:value("unreachable", translate("unreachable (reject)"))
|
||||
o:value("blackhole", translate("blackhole (drop)"))
|
||||
o:value("default", translate("default (use main routing table)"))
|
||||
|
||||
return m
|
|
@ -1,3 +0,0 @@
|
|||
<%if require("luci.sys").init.enabled("mwan3") then%>
|
||||
<%+mwan/overview_status_interface%>
|
||||
<%end%>
|
|
@ -1,114 +0,0 @@
|
|||
<%#
|
||||
Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
Licensed to the public under the GNU General Public License v2.
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
function secondsToString(time) {
|
||||
var seconds = parseInt(time, 10);
|
||||
|
||||
var hrs = Math.floor(seconds / 3600);
|
||||
seconds -= hrs*3600;
|
||||
var mnts = Math.floor(seconds / 60);
|
||||
seconds -= mnts*60;
|
||||
return String.format("%sh:%sm:%ss", hrs, mnts, seconds);
|
||||
}
|
||||
|
||||
XHR.poll(-1, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_status")%>', null,
|
||||
function(x, status)
|
||||
{
|
||||
var statusDiv = document.getElementById('mwan_status_text');
|
||||
if (status.interfaces)
|
||||
{
|
||||
var statusview = '';
|
||||
for ( var iface in status.interfaces)
|
||||
{
|
||||
var state = '';
|
||||
var css = '';
|
||||
var time = '';
|
||||
switch (status.interfaces[iface].status)
|
||||
{
|
||||
case 'online':
|
||||
state = '<%:Online%>';
|
||||
time = String.format(
|
||||
'<div><strong><%:Uptime%>: </strong>%s</div>',
|
||||
secondsToString(status.interfaces[iface].online)
|
||||
);
|
||||
css = 'success';
|
||||
break;
|
||||
case 'offline':
|
||||
state = '<%:Offline%>';
|
||||
time = String.format(
|
||||
'<div><strong><%:Downtime%>: </strong>%s</div>',
|
||||
secondsToString(status.interfaces[iface].offline)
|
||||
);
|
||||
css = 'danger';
|
||||
break;
|
||||
case 'notracking':
|
||||
state = '<%:No Tracking%>';
|
||||
if ((status.interfaces[iface].uptime) > 0) {
|
||||
time = String.format(
|
||||
'<div><strong><%:Uptime%>: </strong>%s</div>',
|
||||
secondsToString(status.interfaces[iface].uptime)
|
||||
);
|
||||
css = 'success';
|
||||
}
|
||||
else {
|
||||
time = '<div> </div>'
|
||||
css = 'warning';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
state = '<%:Disabled%>';
|
||||
time = '<div> </div>'
|
||||
css = 'warning';
|
||||
break;
|
||||
}
|
||||
statusview += String.format(
|
||||
'<div class="alert-message %s">',
|
||||
css
|
||||
);
|
||||
statusview += String.format(
|
||||
'<div><strong><%:Interface%>: </strong>%s</div>',
|
||||
iface
|
||||
);
|
||||
statusview += String.format(
|
||||
'<div><strong><%:Status%>: </strong>%s</div>',
|
||||
state
|
||||
);
|
||||
if (time)
|
||||
{
|
||||
statusview += time;
|
||||
}
|
||||
statusview += '</div>'
|
||||
}
|
||||
statusDiv.innerHTML = statusview;
|
||||
}
|
||||
else
|
||||
{
|
||||
statusDiv.innerHTML = '<strong><%:No MWAN interfaces found%></strong>';
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]></script>
|
||||
|
||||
<style type="text/css">
|
||||
#mwan_status_text > div {
|
||||
display: inline-block;
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
width: 15rem;
|
||||
float: left;
|
||||
line-height: 125%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset id="interface_field" class="cbi-section">
|
||||
<legend><%:MWAN Interfaces%></legend>
|
||||
<div id="mwan_status_text">
|
||||
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
|
||||
<%:Collecting data...%>
|
||||
</div>
|
||||
</fieldset>
|
|
@ -1,39 +0,0 @@
|
|||
<%#
|
||||
Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
Licensed to the public under the GNU General Public License v2.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<ul class="cbi-tabmenu">
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
|
||||
<li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(-1, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "detailed_status")%>', null,
|
||||
function(x)
|
||||
{
|
||||
var output = document.getElementById('diag-rc-output');
|
||||
output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
|
||||
}
|
||||
);
|
||||
//]]></script>
|
||||
|
||||
<div class="cbi-map">
|
||||
<h2 name="content"><%:MWAN Status - Detail%></h2>
|
||||
<%if not require("luci.sys").init.enabled("mwan3") then%>
|
||||
<div><strong><%:INFO: MWAN not running%></strong></div>
|
||||
<%end%>
|
||||
<fieldset class="cbi-section">
|
||||
<span id="diag-rc-output">
|
||||
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
|
||||
<%:Collecting data...%>
|
||||
</span>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<%+footer%>
|
|
@ -1,97 +0,0 @@
|
|||
<%#
|
||||
Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
Licensed to the public under the GNU General Public License v2.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<ul class="cbi-tabmenu">
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
|
||||
<li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
|
||||
</ul>
|
||||
|
||||
<%
|
||||
local uci = require "luci.model.uci"
|
||||
|
||||
local iface = {}
|
||||
|
||||
uci.cursor():foreach("mwan3", "interface",
|
||||
function (section)
|
||||
table.insert(iface, section[".name"])
|
||||
end
|
||||
)
|
||||
%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var stxhr = new XHR();
|
||||
|
||||
function update_status(iface, task)
|
||||
{
|
||||
var output = document.getElementById('diag-rc-output');
|
||||
|
||||
output.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />' +
|
||||
"<%:Waiting for command to complete...%>"
|
||||
;
|
||||
|
||||
output.parentNode.style.display = 'block';
|
||||
output.style.display = 'inline';
|
||||
|
||||
stxhr.post('<%=url('admin/status/mwan')%>/diagnostics_display' + '/' + iface + '/' + task, { token: '<%=token%>' },
|
||||
function(x)
|
||||
{
|
||||
output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
|
||||
}
|
||||
);
|
||||
}
|
||||
//]]></script>
|
||||
|
||||
<form method="post" action="<%=url('admin/network/diagnostics')%>">
|
||||
<div class="cbi-map">
|
||||
<h2 name="content"><%:MWAN Status - Diagnostics%></h2>
|
||||
<%if not require("luci.sys").init.enabled("mwan3") then%>
|
||||
<div><strong><%:INFO: MWAN not running%></strong></div>
|
||||
<%end%>
|
||||
<div class="cbi-section">
|
||||
<div class="cbi-section-node">
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"><%:Interface%></label>
|
||||
<div class="cbi-value-field">
|
||||
<select class="cbi-input-select" name="iface">
|
||||
<% for _, z in ipairs(iface) do -%><option value="<%=z%>"><%=z%></option><%- end %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section-node">
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"><%:Task%></label>
|
||||
<div class="cbi-value-field">
|
||||
<select class="cbi-input-select" name="task">
|
||||
<option value="ping_gateway"><%:Ping default gateway%></option>
|
||||
<option value="ping_trackips"><%:Ping tracking IP%></option>
|
||||
<option value="check_rules"><%:Check IP rules%></option>
|
||||
<option value="check_routes"><%:Check routing table%></option>
|
||||
<option value="hotplug_ifup"><%:Hotplug ifup%></option>
|
||||
<option value="hotplug_ifdown"><%:Hotplug ifdown%></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section-create">
|
||||
<input type="button" value="<%:Execute%>" class="btn cbi-button cbi-button-apply" onclick="update_status(this.form.iface.value, this.form.task.value)"/>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section" style="display:none">
|
||||
<span id="diag-rc-output"></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<%+footer%>
|
|
@ -1,20 +0,0 @@
|
|||
<%#
|
||||
Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
Licensed to the public under the GNU General Public License v2.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<ul class="cbi-tabmenu">
|
||||
<li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<div class="cbi-map">
|
||||
<%+mwan/overview_status_interface%>
|
||||
</div>
|
||||
<%+footer%>
|
|
@ -1,39 +0,0 @@
|
|||
<%#
|
||||
Copyright 2014 Aedan Renner <chipdankly@gmail.com>
|
||||
Copyright 2018 Florian Eckert <fe@dev.tdt.de>
|
||||
Licensed to the public under the GNU General Public License v2.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<ul class="cbi-tabmenu">
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li>
|
||||
<li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li>
|
||||
<li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(15, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "troubleshooting_display")%>', null,
|
||||
function(x)
|
||||
{
|
||||
var output = document.getElementById('diag-rc-output');
|
||||
output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
|
||||
}
|
||||
);
|
||||
//]]></script>
|
||||
|
||||
<div class="cbi-map">
|
||||
<h2 name="content"><%:MWAN Status - Troubleshooting%></h2>
|
||||
<%if not require("luci.sys").init.enabled("mwan3") then%>
|
||||
<div><strong><%:INFO: MWAN not running%></strong></div>
|
||||
<%end%>
|
||||
<fieldset class="cbi-section">
|
||||
<span id="diag-rc-output">
|
||||
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
|
||||
<%:Collecting data...%>
|
||||
</span>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<%+footer%>
|
199
applications/luci-app-mwan3/root/usr/libexec/luci-mwan3
Executable file
199
applications/luci-app-mwan3/root/usr/libexec/luci-mwan3
Executable file
|
@ -0,0 +1,199 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2021 TDT AG <development@tdt.de>
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See https://www.gnu.org/licenses/gpl-2.0.txt for more information.
|
||||
#
|
||||
|
||||
. /lib/functions.sh
|
||||
. /lib/functions/network.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
IIF=1000
|
||||
FWMARK=2000
|
||||
ID=0
|
||||
|
||||
usage() {
|
||||
local status="$1"
|
||||
local msg="$2"
|
||||
if [ -n "$msg" ]; then
|
||||
echo "$msg"
|
||||
echo ""
|
||||
fi
|
||||
echo "Usage: $(basename "$0") <command>"
|
||||
echo "command:"
|
||||
echo " diag: diagnostic commands"
|
||||
echo " ipset: ipset commands"
|
||||
echo ""
|
||||
echo "diag <command> <iface>"
|
||||
echo "command:"
|
||||
echo " gateway <iface>: ping interface gateway"
|
||||
echo " tracking <iface>: ping interface tracking targets"
|
||||
echo " rules <iface>: check interface routing rules"
|
||||
echo " routes <iface>: check interface routing tables"
|
||||
echo ""
|
||||
echo "ipset <command>"
|
||||
echo "command:"
|
||||
echo " dump: show all configured ipset names"
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
diag_gateway() {
|
||||
local iface="$1"
|
||||
|
||||
local gw
|
||||
|
||||
network_get_gateway gw "${iface}"
|
||||
[ -z "$gw" ] && network_get_gateway gw "${iface}_4"
|
||||
|
||||
[ -z "$gw" ] && {
|
||||
echo "No gateway for interface ${iface} found."
|
||||
exit 2
|
||||
}
|
||||
|
||||
mwan3 use "$iface" "ping" "-c" "5" "-W" "1" "$gw"
|
||||
}
|
||||
|
||||
diag_tracking() {
|
||||
local iface="$1"
|
||||
|
||||
checkips() {
|
||||
local ip="$1"
|
||||
local iface="$2"
|
||||
|
||||
mwan3 use "$iface" "ping" "-c" "5" "-W" "1" "$ip"
|
||||
}
|
||||
|
||||
config_load mwan3
|
||||
config_list_foreach "$iface" "track_ip" checkips "$iface"
|
||||
}
|
||||
|
||||
iface_number() {
|
||||
local cfg="$1"
|
||||
local iface="$2"
|
||||
|
||||
let number++
|
||||
|
||||
[ "$cfg" = "$iface" ] && {
|
||||
ID="$number"
|
||||
}
|
||||
}
|
||||
|
||||
diag_rules() {
|
||||
local iface="$1"
|
||||
|
||||
local number=0
|
||||
local iif=0
|
||||
local fwmark=0
|
||||
|
||||
local iif_rule iif_result
|
||||
local fwmark_rule fwmark_result
|
||||
|
||||
config_load mwan3
|
||||
config_foreach iface_number 'interface' "$iface"
|
||||
|
||||
[ "$ID" = "0" ] && {
|
||||
echo "Unable to get mwan3 interface number for \"$iface\"."
|
||||
exit 2
|
||||
}
|
||||
|
||||
let "iif=$IIF+$ID"
|
||||
let "fwmark=$FWMARK+$ID"
|
||||
|
||||
iif_rule="$(ip rule | grep ${iif})"
|
||||
iif_result="$?"
|
||||
|
||||
fwmark_rule="$(ip rule | grep ${fwmark})"
|
||||
fwmark_result="$?"
|
||||
|
||||
if [ "$fwmark_result" = 0 ] && [ "$iif_result" = 0 ]; then
|
||||
echo "All required IP rules for interface \"$iface\" found"
|
||||
echo "$fwmark_rule"
|
||||
echo "$iif_rule"
|
||||
elif [ "$fwmark_result" = 1 ] && [ "$iif_result" = 0 ]; then
|
||||
echo "Only iif IP rule for interface \"$iface\" found"
|
||||
echo "$iif_rule"
|
||||
elif [ "$fwmark_result" = 0 ] && [ "$iif_result" = 1 ]; then
|
||||
echo "Only fwmark IP rule for interface \"$iface\" found"
|
||||
echo "$fwmark_rule"
|
||||
else
|
||||
echo "Missing fwmark and iif IP rule for interface \"$iface\""
|
||||
fi
|
||||
}
|
||||
|
||||
diag_routes() {
|
||||
local iface="$1"
|
||||
|
||||
local table table_result
|
||||
|
||||
config_load mwan3
|
||||
config_foreach iface_number 'interface' "$iface"
|
||||
|
||||
[ "$ID" = "0" ] && {
|
||||
echo "Unable to get mwan3 interface number for \"$iface\"."
|
||||
exit 2
|
||||
}
|
||||
|
||||
table="$(ip route list table $ID)"
|
||||
table_result="$?"
|
||||
|
||||
if [ "$table_result" = 0 ]; then
|
||||
echo "Routing table \"$ID\" for interface \"$iface\" found"
|
||||
echo "$table"
|
||||
else
|
||||
echo "Routing table \"$ID\" for interface \"$iface\" not found"
|
||||
fi
|
||||
}
|
||||
|
||||
diag_cmd() {
|
||||
case "$1" in
|
||||
gateway)
|
||||
diag_gateway "$2"
|
||||
;;
|
||||
tracking)
|
||||
diag_tracking "$2"
|
||||
;;
|
||||
rules)
|
||||
diag_rules "$2"
|
||||
;;
|
||||
routes)
|
||||
diag_routes "$2"
|
||||
;;
|
||||
*)
|
||||
usage "1" "Command not supported"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ipset_dump() {
|
||||
ipset -n -L 2>/dev/null | grep -v mwan3_ | sort -u
|
||||
}
|
||||
|
||||
ipset_cmd() {
|
||||
case "$1" in
|
||||
dump)
|
||||
ipset_dump
|
||||
;;
|
||||
*)
|
||||
usage "1" "Command not supported"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main () {
|
||||
case "$1" in
|
||||
diag)
|
||||
diag_cmd "$2" "$3"
|
||||
;;
|
||||
ipset)
|
||||
ipset_cmd "$2"
|
||||
;;
|
||||
*)
|
||||
usage "1" "Command not supported"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
"admin/status/mwan3": {
|
||||
"title": "MultiWAN Manager",
|
||||
"order": "600",
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mwan3" ]
|
||||
}
|
||||
},
|
||||
"admin/status/mwan3/overview": {
|
||||
"title": "Overview",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/status/overview"
|
||||
}
|
||||
},
|
||||
"admin/status/mwan3/detail": {
|
||||
"title": "Status",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/status/detail"
|
||||
}
|
||||
},
|
||||
"admin/status/mwan3/diagnostics": {
|
||||
"title": "Diagnostics",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/status/diagnostics"
|
||||
}
|
||||
},
|
||||
"admin/status/mwan3/troubleshooting": {
|
||||
"title": "Troubleshooting",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/status/troubleshooting"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/mwan3": {
|
||||
"title": "MultiWAN Manager",
|
||||
"order": "600",
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-mwan3" ]
|
||||
}
|
||||
},
|
||||
"admin/network/mwan3/globals": {
|
||||
"title": "Globals",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/network/globals"
|
||||
}
|
||||
},
|
||||
"admin/network/mwan3/interface": {
|
||||
"title": "Interface",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/network/interface"
|
||||
}
|
||||
},
|
||||
"admin/network/mwan3/member": {
|
||||
"title": "Member",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/network/member"
|
||||
}
|
||||
},
|
||||
"admin/network/mwan3/policy": {
|
||||
"title": "Policy",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/network/policy"
|
||||
}
|
||||
},
|
||||
"admin/network/mwan3/rule": {
|
||||
"title": "Rule",
|
||||
"order": 50,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/network/rule"
|
||||
}
|
||||
},
|
||||
"admin/network/mwan3/notify": {
|
||||
"title": "Notify",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mwan3/network/notify"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,30 @@
|
|||
"luci-app-mwan3": {
|
||||
"description": "Grant UCI access for luci-app-mwan3",
|
||||
"read": {
|
||||
"uci": [ "mwan3" ]
|
||||
"file": {
|
||||
"/etc/mwan3.user": [ "read" ],
|
||||
"/usr/bin/httping": [ "list" ],
|
||||
"/usr/bin/nping": [ "list" ],
|
||||
"/usr/bin/arping": [ "list" ],
|
||||
"/usr/sbin/mwan3 status": [ "exec" ],
|
||||
"/usr/sbin/mwan3 ifup *": [ "exec" ],
|
||||
"/usr/sbin/mwan3 ifdown *": [ "exec" ],
|
||||
"/usr/sbin/mwan3 internal ipv4": [ "exec" ],
|
||||
"/usr/sbin/mwan3 internal ipv6": [ "exec" ],
|
||||
"/usr/libexec/luci-mwan3 diag * *": [ "exec" ],
|
||||
"/usr/libexec/luci-mwan3 ipset *": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"mwan3": [ "status" ]
|
||||
},
|
||||
"uci": [ "mwan3", "network" ]
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/mwan3.user": ["write"],
|
||||
"/usr/sbin/mwan3 ifup *": [ "exec" ],
|
||||
"/usr/sbin/mwan3 ifdown *": [ "exec" ]
|
||||
},
|
||||
"uci": [ "mwan3" ]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue