mirror of
https://github.com/Ysurac/openmptcprouter-feeds.git
synced 2025-02-12 10:31:51 +00:00
Add luci-app-firewall
This commit is contained in:
parent
ed5e82507c
commit
f700f2ef26
54 changed files with 69624 additions and 0 deletions
17
luci-app-firewall/Makefile
Normal file
17
luci-app-firewall/Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Firewall and port forwarding application
|
||||
LUCI_DEPENDS:=+luci-base +uci-firewall
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
703
luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js
Normal file
703
luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js
Normal file
|
@ -0,0 +1,703 @@
|
|||
'use strict';
|
||||
'require baseclass';
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require network';
|
||||
'require firewall';
|
||||
'require validation';
|
||||
'require tools.prng as random';
|
||||
|
||||
var protocols = [
|
||||
'ip', 0, 'IP',
|
||||
'hopopt', 0, 'HOPOPT',
|
||||
'icmp', 1, 'ICMP',
|
||||
'igmp', 2, 'IGMP',
|
||||
'ggp', 3 , 'GGP',
|
||||
'ipencap', 4, 'IP-ENCAP',
|
||||
'st', 5, 'ST',
|
||||
'tcp', 6, 'TCP',
|
||||
'egp', 8, 'EGP',
|
||||
'igp', 9, 'IGP',
|
||||
'pup', 12, 'PUP',
|
||||
'udp', 17, 'UDP',
|
||||
'hmp', 20, 'HMP',
|
||||
'xns-idp', 22, 'XNS-IDP',
|
||||
'rdp', 27, 'RDP',
|
||||
'iso-tp4', 29, 'ISO-TP4',
|
||||
'dccp', 33, 'DCCP',
|
||||
'xtp', 36, 'XTP',
|
||||
'ddp', 37, 'DDP',
|
||||
'idpr-cmtp', 38, 'IDPR-CMTP',
|
||||
'ipv6', 41, 'IPv6',
|
||||
'ipv6-route', 43, 'IPv6-Route',
|
||||
'ipv6-frag', 44, 'IPv6-Frag',
|
||||
'idrp', 45, 'IDRP',
|
||||
'rsvp', 46, 'RSVP',
|
||||
'gre', 47, 'GRE',
|
||||
'esp', 50, 'IPSEC-ESP',
|
||||
'ah', 51, 'IPSEC-AH',
|
||||
'skip', 57, 'SKIP',
|
||||
'icmpv6', 58, 'IPv6-ICMP',
|
||||
'ipv6-icmp', 58, 'IPv6-ICMP',
|
||||
'ipv6-nonxt', 59, 'IPv6-NoNxt',
|
||||
'ipv6-opts', 60, 'IPv6-Opts',
|
||||
'rspf', 73, 'RSPF',
|
||||
'rspf', 73, 'CPHB',
|
||||
'vmtp', 81, 'VMTP',
|
||||
'eigrp', 88, 'EIGRP',
|
||||
'ospf', 89, 'OSPFIGP',
|
||||
'ax.25', 93, 'AX.25',
|
||||
'ipip', 94, 'IPIP',
|
||||
'etherip', 97, 'ETHERIP',
|
||||
'encap', 98, 'ENCAP',
|
||||
'pim', 103, 'PIM',
|
||||
'ipcomp', 108, 'IPCOMP',
|
||||
'vrrp', 112, 'VRRP',
|
||||
'l2tp', 115, 'L2TP',
|
||||
'isis', 124, 'ISIS',
|
||||
'sctp', 132, 'SCTP',
|
||||
'fc', 133, 'FC',
|
||||
'mh', 135, 'Mobility-Header',
|
||||
'ipv6-mh', 135, 'Mobility-Header',
|
||||
'mobility-header', 135, 'Mobility-Header',
|
||||
'udplite', 136, 'UDPLite',
|
||||
'mpls-in-ip', 137, 'MPLS-in-IP',
|
||||
'manet', 138, 'MANET',
|
||||
'hip', 139, 'HIP',
|
||||
'shim6', 140, 'Shim6',
|
||||
'wesp', 141, 'WESP',
|
||||
'rohc', 142, 'ROHC',
|
||||
];
|
||||
|
||||
function lookupProto(x) {
|
||||
if (x == null || x === '')
|
||||
return null;
|
||||
|
||||
var s = String(x).toLowerCase();
|
||||
|
||||
for (var i = 0; i < protocols.length; i += 3)
|
||||
if (s == protocols[i] || s == protocols[i+1])
|
||||
return [ protocols[i+1], protocols[i+2], protocols[i] ];
|
||||
|
||||
return [ -1, x, x ];
|
||||
}
|
||||
|
||||
return baseclass.extend({
|
||||
fmt: function(fmtstr, args, values) {
|
||||
var repl = [],
|
||||
wrap = false,
|
||||
tokens = [];
|
||||
|
||||
if (values == null) {
|
||||
values = [];
|
||||
wrap = true;
|
||||
}
|
||||
|
||||
var get = function(args, key) {
|
||||
var names = key.trim().split(/\./),
|
||||
obj = args,
|
||||
ctx = obj;
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
if (!L.isObject(obj))
|
||||
return null;
|
||||
|
||||
ctx = obj;
|
||||
obj = obj[names[i]];
|
||||
}
|
||||
|
||||
if (typeof(obj) == 'function')
|
||||
return obj.call(ctx);
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
var isset = function(val) {
|
||||
if (L.isObject(val) && !dom.elem(val)) {
|
||||
for (var k in val)
|
||||
if (val.hasOwnProperty(k))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (Array.isArray(val)) {
|
||||
return (val.length > 0);
|
||||
}
|
||||
else {
|
||||
return (val !== null && val !== undefined && val !== '' && val !== false);
|
||||
}
|
||||
};
|
||||
|
||||
var parse = function(tokens, text) {
|
||||
if (dom.elem(text)) {
|
||||
tokens.push('<span data-fmt-placeholder="%d"></span>'.format(values.length));
|
||||
values.push(text);
|
||||
}
|
||||
else {
|
||||
tokens.push(String(text).replace(/\\(.)/g, '$1'));
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0, last = 0; i <= fmtstr.length; i++) {
|
||||
if (fmtstr.charAt(i) == '%' && fmtstr.charAt(i + 1) == '{') {
|
||||
if (i > last)
|
||||
parse(tokens, fmtstr.substring(last, i));
|
||||
|
||||
var j = i + 1, nest = 0;
|
||||
|
||||
var subexpr = [];
|
||||
|
||||
for (var off = j + 1, esc = false; j <= fmtstr.length; j++) {
|
||||
var ch = fmtstr.charAt(j);
|
||||
|
||||
if (esc) {
|
||||
esc = false;
|
||||
}
|
||||
else if (ch == '\\') {
|
||||
esc = true;
|
||||
}
|
||||
else if (ch == '{') {
|
||||
nest++;
|
||||
}
|
||||
else if (ch == '}') {
|
||||
if (--nest == 0) {
|
||||
subexpr.push(fmtstr.substring(off, j));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (ch == '?' || ch == ':' || ch == '#') {
|
||||
if (nest == 1) {
|
||||
subexpr.push(fmtstr.substring(off, j));
|
||||
subexpr.push(ch);
|
||||
off = j + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var varname = subexpr[0].trim(),
|
||||
op1 = (subexpr[1] != null) ? subexpr[1] : '?',
|
||||
if_set = (subexpr[2] != null && subexpr[2] != '') ? subexpr[2] : '%{' + varname + '}',
|
||||
op2 = (subexpr[3] != null) ? subexpr[3] : ':',
|
||||
if_unset = (subexpr[4] != null) ? subexpr[4] : '';
|
||||
|
||||
/* Invalid expression */
|
||||
if (nest != 0 || subexpr.length > 5 || varname == '') {
|
||||
return fmtstr;
|
||||
}
|
||||
|
||||
/* enumeration */
|
||||
else if (op1 == '#' && subexpr.length == 3) {
|
||||
var items = L.toArray(get(args, varname));
|
||||
|
||||
for (var k = 0; k < items.length; k++) {
|
||||
tokens.push.apply(tokens, this.fmt(if_set, Object.assign({}, args, {
|
||||
first: k == 0,
|
||||
next: k > 0,
|
||||
last: (k + 1) == items.length,
|
||||
item: items[k]
|
||||
}), values));
|
||||
}
|
||||
}
|
||||
|
||||
/* ternary expression */
|
||||
else if (op1 == '?' && op2 == ':' && (subexpr.length == 1 || subexpr.length == 3 || subexpr.length == 5)) {
|
||||
var val = get(args, varname);
|
||||
|
||||
if (subexpr.length == 1)
|
||||
parse(tokens, isset(val) ? val : '');
|
||||
else if (isset(val))
|
||||
tokens.push.apply(tokens, this.fmt(if_set, args, values));
|
||||
else
|
||||
tokens.push.apply(tokens, this.fmt(if_unset, args, values));
|
||||
}
|
||||
|
||||
/* unrecognized command */
|
||||
else {
|
||||
return fmtstr;
|
||||
}
|
||||
|
||||
last = j + 1;
|
||||
i = j;
|
||||
}
|
||||
else if (i >= fmtstr.length) {
|
||||
if (i > last)
|
||||
parse(tokens, fmtstr.substring(last, i));
|
||||
}
|
||||
}
|
||||
|
||||
if (wrap) {
|
||||
var node = E('span', {}, tokens.join('')),
|
||||
repl = node.querySelectorAll('span[data-fmt-placeholder]');
|
||||
|
||||
for (var i = 0; i < repl.length; i++)
|
||||
repl[i].parentNode.replaceChild(values[repl[i].getAttribute('data-fmt-placeholder')], repl[i]);
|
||||
|
||||
return node;
|
||||
}
|
||||
else {
|
||||
return tokens;
|
||||
}
|
||||
},
|
||||
|
||||
map_invert: function(v, fn) {
|
||||
return L.toArray(v).map(function(v) {
|
||||
v = String(v);
|
||||
|
||||
if (fn != null && typeof(v[fn]) == 'function')
|
||||
v = v[fn].call(v);
|
||||
|
||||
return {
|
||||
ival: v,
|
||||
inv: v.charAt(0) == '!',
|
||||
val: v.replace(/^!\s*/, '')
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
lookupProto: lookupProto,
|
||||
|
||||
addDSCPOption: function(s, is_target) {
|
||||
var o = s.taboption(is_target ? 'general' : 'advanced', form.Value, is_target ? 'set_dscp' : 'dscp',
|
||||
is_target ? _('DSCP mark') : _('Match DSCP'),
|
||||
is_target ? _('Apply the given DSCP class or value to established connections.') : _('Matches traffic carrying the specified DSCP marking.'));
|
||||
|
||||
o.modalonly = true;
|
||||
o.rmempty = !is_target;
|
||||
o.placeholder = _('any');
|
||||
|
||||
if (is_target)
|
||||
o.depends('target', 'DSCP');
|
||||
|
||||
o.value('CS0');
|
||||
o.value('CS1');
|
||||
o.value('CS2');
|
||||
o.value('CS3');
|
||||
o.value('CS4');
|
||||
o.value('CS5');
|
||||
o.value('CS6');
|
||||
o.value('CS7');
|
||||
o.value('BE');
|
||||
o.value('AF11');
|
||||
o.value('AF12');
|
||||
o.value('AF13');
|
||||
o.value('AF21');
|
||||
o.value('AF22');
|
||||
o.value('AF23');
|
||||
o.value('AF31');
|
||||
o.value('AF32');
|
||||
o.value('AF33');
|
||||
o.value('AF41');
|
||||
o.value('AF42');
|
||||
o.value('AF43');
|
||||
o.value('EF');
|
||||
o.validate = function(section_id, value) {
|
||||
if (value == '')
|
||||
return is_target ? _('DSCP mark required') : true;
|
||||
|
||||
if (!is_target)
|
||||
value = String(value).replace(/^!\s*/, '');
|
||||
|
||||
var m = value.match(/^(?:CS[0-7]|BE|AF[1234][123]|EF|(0x[0-9a-f]{1,2}|[0-9]{1,2}))$/);
|
||||
|
||||
if (!m || (m[1] != null && +m[1] > 0x3f))
|
||||
return _('Invalid DSCP mark');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
addMarkOption: function(s, is_target) {
|
||||
var o = s.taboption(is_target ? 'general' : 'advanced', form.Value,
|
||||
(is_target > 1) ? 'set_xmark' : (is_target ? 'set_mark' : 'mark'),
|
||||
(is_target > 1) ? _('XOR mark') : (is_target ? _('Set mark') : _('Match mark')),
|
||||
(is_target > 1) ? _('Apply a bitwise XOR of the given value and the existing mark value on established connections. Format is value[/mask]. If a mask is specified then those bits set in the mask are zeroed out.') :
|
||||
(is_target ? _('Set the given mark value on established connections. Format is value[/mask]. If a mask is specified then only those bits set in the mask are modified.') :
|
||||
_('Matches a specific firewall mark or a range of different marks.')));
|
||||
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
if (is_target > 1)
|
||||
o.depends('target', 'MARK_XOR');
|
||||
else if (is_target)
|
||||
o.depends('target', 'MARK_SET');
|
||||
|
||||
o.validate = function(section_id, value) {
|
||||
if (value == '')
|
||||
return is_target ? _('Valid firewall mark required') : true;
|
||||
|
||||
if (!is_target)
|
||||
value = String(value).replace(/^!\s*/, '');
|
||||
|
||||
var m = value.match(/^(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
|
||||
|
||||
if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
|
||||
return _('Expecting: %s').format(_('valid firewall mark'));
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
addLimitOption: function(s) {
|
||||
var o = s.taboption('advanced', form.Value, 'limit',
|
||||
_('Limit matching'),
|
||||
_('Limits traffic matching to the specified rate.'));
|
||||
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.placeholder = _('unlimited');
|
||||
o.value('10/second');
|
||||
o.value('60/minute');
|
||||
o.value('3/hour');
|
||||
o.value('500/day');
|
||||
o.validate = function(section_id, value) {
|
||||
if (value == '')
|
||||
return true;
|
||||
|
||||
var m = String(value).toLowerCase().match(/^(?:0x[0-9a-f]{1,8}|[0-9]{1,10})\/([a-z]+)$/),
|
||||
u = ['second', 'minute', 'hour', 'day'],
|
||||
i = 0;
|
||||
|
||||
if (m)
|
||||
for (i = 0; i < u.length; i++)
|
||||
if (u[i].indexOf(m[1]) == 0)
|
||||
break;
|
||||
|
||||
if (!m || i >= u.length)
|
||||
return _('Invalid limit value');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
addLimitBurstOption: function(s) {
|
||||
var o = s.taboption('advanced', form.Value, 'limit_burst',
|
||||
_('Limit burst'),
|
||||
_('Maximum initial number of packets to match: this number gets recharged by one every time the limit specified above is not reached, up to this number.'));
|
||||
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.placeholder = '5';
|
||||
o.datatype = 'uinteger';
|
||||
o.depends({ limit: null, '!reverse': true });
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
transformHostHints: function(family, hosts) {
|
||||
var choice_values = [],
|
||||
choice_labels = {},
|
||||
ip6addrs = {},
|
||||
ipaddrs = {};
|
||||
|
||||
for (var mac in hosts) {
|
||||
L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4).forEach(function(ip) {
|
||||
ipaddrs[ip] = mac;
|
||||
});
|
||||
|
||||
L.toArray(hosts[mac].ip6addrs || hosts[mac].ipv6).forEach(function(ip) {
|
||||
ip6addrs[ip] = mac;
|
||||
});
|
||||
}
|
||||
|
||||
if (!family || family == 'ipv4') {
|
||||
L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ip) {
|
||||
var val = ip,
|
||||
txt = hosts[ipaddrs[ip]].name || ipaddrs[ip];
|
||||
|
||||
choice_values.push(val);
|
||||
choice_labels[val] = E([], [ val, ' (', E('strong', {}, [txt]), ')' ]);
|
||||
});
|
||||
}
|
||||
|
||||
if (!family || family == 'ipv6') {
|
||||
L.sortedKeys(ip6addrs, null, 'addr').forEach(function(ip) {
|
||||
var val = ip,
|
||||
txt = hosts[ip6addrs[ip]].name || ip6addrs[ip];
|
||||
|
||||
choice_values.push(val);
|
||||
choice_labels[val] = E([], [ val, ' (', E('strong', {}, [txt]), ')' ]);
|
||||
});
|
||||
}
|
||||
|
||||
return [choice_values, choice_labels];
|
||||
},
|
||||
|
||||
updateHostHints: function(map, section_id, option, family, hosts) {
|
||||
var opt = map.lookupOption(option, section_id)[0].getUIElement(section_id),
|
||||
choices = this.transformHostHints(family, hosts);
|
||||
|
||||
opt.clearChoices();
|
||||
opt.addChoices(choices[0], choices[1]);
|
||||
},
|
||||
|
||||
CBIDynamicMultiValueList: form.DynamicList.extend({
|
||||
renderWidget: function(/* ... */) {
|
||||
var dl = form.DynamicList.prototype.renderWidget.apply(this, arguments),
|
||||
inst = dom.findClassInstance(dl);
|
||||
|
||||
inst.addItem = function(dl, value, text, flash) {
|
||||
var values = L.toArray(value);
|
||||
for (var i = 0; i < values.length; i++)
|
||||
ui.DynamicList.prototype.addItem.call(this, dl, values[i], null, true);
|
||||
};
|
||||
|
||||
return dl;
|
||||
}
|
||||
}),
|
||||
|
||||
addIPOption: function(s, tab, name, label, description, family, hosts, multiple) {
|
||||
var o = s.taboption(tab, multiple ? this.CBIDynamicMultiValueList : form.Value, name, label, description);
|
||||
var fw4 = L.hasSystemFeature('firewall4');
|
||||
|
||||
o.modalonly = true;
|
||||
o.datatype = (fw4 && validation.types.iprange) ? 'list(neg(or(ipmask("true"),iprange)))' : 'list(neg(ipmask("true")))';
|
||||
o.placeholder = multiple ? _('-- add IP --') : _('any');
|
||||
|
||||
if (family != null) {
|
||||
var choices = this.transformHostHints(family, hosts);
|
||||
|
||||
for (var i = 0; i < choices[0].length; i++)
|
||||
o.value(choices[0][i], choices[1][choices[0][i]]);
|
||||
}
|
||||
|
||||
/* force combobox rendering */
|
||||
o.transformChoices = function() {
|
||||
return this.super('transformChoices', []) || {};
|
||||
};
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
addLocalIPOption: function(s, tab, name, label, description, devices) {
|
||||
var o = s.taboption(tab, form.Value, name, label, description);
|
||||
var fw4 = L.hasSystemFeature('firewall4');
|
||||
|
||||
o.modalonly = true;
|
||||
o.datatype = !fw4?'ip4addr("nomask")':'ipaddr("nomask")';
|
||||
o.placeholder = _('any');
|
||||
|
||||
L.sortedKeys(devices, 'name').forEach(function(dev) {
|
||||
var ip4addrs = devices[dev].ipaddrs;
|
||||
var ip6addrs = devices[dev].ip6addrs;
|
||||
|
||||
if (!L.isObject(devices[dev].flags) || devices[dev].flags.loopback)
|
||||
return;
|
||||
|
||||
for (var i = 0; Array.isArray(ip4addrs) && i < ip4addrs.length; i++) {
|
||||
if (!L.isObject(ip4addrs[i]) || !ip4addrs[i].address)
|
||||
continue;
|
||||
|
||||
o.value(ip4addrs[i].address, E([], [
|
||||
ip4addrs[i].address, ' (', E('strong', {}, [dev]), ')'
|
||||
]));
|
||||
}
|
||||
for (var i = 0; fw4 && Array.isArray(ip6addrs) && i < ip6addrs.length; i++) {
|
||||
if (!L.isObject(ip6addrs[i]) || !ip6addrs[i].address)
|
||||
continue;
|
||||
|
||||
o.value(ip6addrs[i].address, E([], [
|
||||
ip6addrs[i].address, ' (', E('strong', {}, [dev]), ')'
|
||||
]));
|
||||
}
|
||||
});
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
addMACOption: function(s, tab, name, label, description, hosts) {
|
||||
var o = s.taboption(tab, this.CBIDynamicMultiValueList, name, label, description);
|
||||
|
||||
o.modalonly = true;
|
||||
o.datatype = 'list(macaddr)';
|
||||
o.placeholder = _('-- add MAC --');
|
||||
|
||||
L.sortedKeys(hosts).forEach(function(mac) {
|
||||
o.value(mac, E([], [ mac, ' (', E('strong', {}, [
|
||||
hosts[mac].name ||
|
||||
L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0] ||
|
||||
L.toArray(hosts[mac].ip6addrs || hosts[mac].ipv6)[0] ||
|
||||
'?'
|
||||
]), ')' ]));
|
||||
});
|
||||
|
||||
return o;
|
||||
},
|
||||
|
||||
CBIProtocolSelect: form.MultiValue.extend({
|
||||
__name__: 'CBI.ProtocolSelect',
|
||||
|
||||
addChoice: function(value, label) {
|
||||
if (!Array.isArray(this.keylist) || this.keylist.indexOf(value) == -1)
|
||||
this.value(value, label);
|
||||
},
|
||||
|
||||
load: function(section_id) {
|
||||
var cfgvalue = L.toArray(this.super('load', [section_id]) || this.default).sort();
|
||||
|
||||
['all', 'tcp', 'udp', 'icmp'].concat(cfgvalue).forEach(L.bind(function(value) {
|
||||
switch (value) {
|
||||
case 'all':
|
||||
case 'any':
|
||||
case '*':
|
||||
this.addChoice('all', _('Any'));
|
||||
break;
|
||||
|
||||
case 'tcpudp':
|
||||
this.addChoice('tcp', 'TCP');
|
||||
this.addChoice('udp', 'UDP');
|
||||
break;
|
||||
|
||||
default:
|
||||
var m = value.match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/),
|
||||
p = lookupProto(m ? +m[1] : value);
|
||||
|
||||
this.addChoice(p[2], p[1]);
|
||||
break;
|
||||
}
|
||||
}, this));
|
||||
|
||||
if (cfgvalue == '*' || cfgvalue == 'any' || cfgvalue == 'all')
|
||||
cfgvalue = 'all';
|
||||
|
||||
return cfgvalue;
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var value = (cfgvalue != null) ? cfgvalue : this.default,
|
||||
choices = this.transformChoices();
|
||||
|
||||
var widget = new ui.Dropdown(L.toArray(value), choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: this.keylist,
|
||||
multiple: true,
|
||||
optional: false,
|
||||
display_items: 10,
|
||||
dropdown_items: -1,
|
||||
create: true,
|
||||
disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
|
||||
validate: function(value) {
|
||||
var v = L.toArray(value);
|
||||
|
||||
for (var i = 0; i < v.length; i++) {
|
||||
if (v[i] == 'all')
|
||||
continue;
|
||||
|
||||
var m = v[i].match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/);
|
||||
|
||||
if (m ? (+m[1] > 255) : (lookupProto(v[i])[0] == -1))
|
||||
return _('Unrecognized protocol');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
widget.createChoiceElement = function(sb, value) {
|
||||
var p = lookupProto(value);
|
||||
|
||||
return ui.Dropdown.prototype.createChoiceElement.call(this, sb, p[2], p[1]);
|
||||
};
|
||||
|
||||
widget.createItems = function(sb, value) {
|
||||
var values = L.toArray(value).map(function(value) {
|
||||
var m = value.match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/),
|
||||
p = lookupProto(m ? +m[1] : value);
|
||||
|
||||
return (p[0] > -1) ? p[2] : p[1];
|
||||
});
|
||||
|
||||
values.sort();
|
||||
|
||||
return ui.Dropdown.prototype.createItems.call(this, sb, values.join(' '));
|
||||
};
|
||||
|
||||
widget.toggleItem = function(sb, li) {
|
||||
var value = li.getAttribute('data-value'),
|
||||
toggleFn = ui.Dropdown.prototype.toggleItem;
|
||||
|
||||
toggleFn.call(this, sb, li);
|
||||
|
||||
if (value == 'all') {
|
||||
var items = li.parentNode.querySelectorAll('li[data-value]');
|
||||
|
||||
for (var j = 0; j < items.length; j++)
|
||||
if (items[j] !== li)
|
||||
toggleFn.call(this, sb, items[j], false);
|
||||
}
|
||||
else {
|
||||
toggleFn.call(this, sb, li.parentNode.querySelector('li[data-value="all"]'), false);
|
||||
}
|
||||
};
|
||||
|
||||
return widget.render();
|
||||
}
|
||||
}),
|
||||
|
||||
checkLegacySNAT: function() {
|
||||
var redirects = uci.sections('firewall', 'redirect');
|
||||
|
||||
for (var i = 0; i < redirects.length; i++)
|
||||
if ((redirects[i]['target'] || '').toLowerCase() == 'snat')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
handleMigration: function(ev) {
|
||||
var redirects = uci.sections('firewall', 'redirect'),
|
||||
tasks = [];
|
||||
|
||||
var mapping = {
|
||||
dest: 'src',
|
||||
reflection: null,
|
||||
reflection_src: null,
|
||||
src_dip: 'snat_ip',
|
||||
src_dport: 'snat_port',
|
||||
src: null
|
||||
};
|
||||
|
||||
for (var i = 0; i < redirects.length; i++) {
|
||||
if ((redirects[i]['target'] || '').toLowerCase() != 'snat')
|
||||
continue;
|
||||
|
||||
var sid = uci.add('firewall', 'nat');
|
||||
|
||||
for (var opt in redirects[i]) {
|
||||
if (opt.charAt(0) == '.')
|
||||
continue;
|
||||
|
||||
if (mapping[opt] === null)
|
||||
continue;
|
||||
|
||||
uci.set('firewall', sid, mapping[opt] || opt, redirects[i][opt]);
|
||||
}
|
||||
|
||||
uci.remove('firewall', redirects[i]['.name']);
|
||||
}
|
||||
|
||||
return uci.save()
|
||||
.then(L.bind(ui.changes.init, ui.changes))
|
||||
.then(L.bind(ui.changes.apply, ui.changes));
|
||||
},
|
||||
|
||||
renderMigration: function() {
|
||||
ui.showModal(_('Firewall configuration migration'), [
|
||||
E('p', _('The existing firewall configuration needs to be changed for LuCI to function properly.')),
|
||||
E('p', _('Upon pressing "Continue", "redirect" sections with target "SNAT" will be converted to "nat" sections and the firewall will be restarted to apply the updated configuration.')),
|
||||
E('div', { 'class': 'right' },
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action important',
|
||||
'click': ui.createHandlerFn(this, 'handleMigration')
|
||||
}, _('Continue')))
|
||||
]);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return L.resolveDefault(fs.read('/etc/firewall.user'), '');
|
||||
},
|
||||
|
||||
handleSave: function(ev) {
|
||||
var value = (document.querySelector('textarea').value || '').trim().replace(/\r\n/g, '\n') + '\n';
|
||||
|
||||
return fs.write('/etc/firewall.user', value).then(function(rc) {
|
||||
document.querySelector('textarea').value = value;
|
||||
ui.addNotification(null, E('p', _('Contents have been saved.')), 'info');
|
||||
fs.exec('/etc/init.d/firewall', ['restart']);
|
||||
}).catch(function(e) {
|
||||
ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e.message)));
|
||||
});
|
||||
},
|
||||
|
||||
render: function(fwuser) {
|
||||
return E([
|
||||
E('h2', _('Firewall - Custom Rules')),
|
||||
E('p', {}, _('Custom rules allow you to execute arbitrary iptables commands which are not otherwise covered by the firewall framework. The commands are executed after each firewall restart, right after the default ruleset has been loaded.')),
|
||||
E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 25 }, [ fwuser != null ? fwuser : '' ]))
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
|
@ -0,0 +1,357 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require firewall as fwmodel';
|
||||
'require tools.firewall as fwtool';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
function rule_proto_txt(s, ctHelpers) {
|
||||
var family = (uci.get('firewall', s, 'family') || '').toLowerCase().replace(/^(?:all|\*)$/, 'any');
|
||||
var dip = uci.get('firewall', s, 'dest_ip') || '';
|
||||
var proto = L.toArray(uci.get('firewall', s, 'proto')).filter(function(p) {
|
||||
return (p != '*' && p != 'any' && p != 'all');
|
||||
}).map(function(p) {
|
||||
var pr = fwtool.lookupProto(p);
|
||||
return {
|
||||
num: pr[0],
|
||||
name: pr[1],
|
||||
types: (pr[0] == 1 || pr[0] == 58) ? L.toArray(uci.get('firewall', s, 'icmp_type')) : null
|
||||
};
|
||||
});
|
||||
|
||||
var m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
|
||||
var h = m ? {
|
||||
val: m[0].toUpperCase(),
|
||||
inv: m[1],
|
||||
name: (ctHelpers.filter(function(ctH) { return ctH.name.toLowerCase() == m[2].toLowerCase() })[0] || {}).description
|
||||
} : null;
|
||||
|
||||
m = String(uci.get('firewall', s, 'mark')).match(/^(!\s*)?(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
|
||||
var f = m ? {
|
||||
val: m[0].toUpperCase().replace(/X/g, 'x'),
|
||||
inv: m[1],
|
||||
num: '0x%02X'.format(+m[2]),
|
||||
mask: m[3] ? '0x%02X'.format(+m[3]) : null
|
||||
} : null;
|
||||
|
||||
return fwtool.fmt(_('Incoming %{ipv6?%{ipv4?<var>IPv4</var> and <var>IPv6</var>:<var>IPv6</var>}:<var>IPv4</var>}%{proto?, protocol %{proto#%{next?, }%{item.types?<var class="cbi-tooltip-container">%{item.name}<span class="cbi-tooltip">ICMP with types %{item.types#%{next?, }<var>%{item}</var>}</span></var>:<var>%{item.name}</var>}}}%{mark?, mark <var%{mark.inv? data-tooltip="Match fwmarks except %{mark.num}%{mark.mask? with mask %{mark.mask}}.":%{mark.mask? data-tooltip="Mask fwmark value with %{mark.mask} before compare."}}>%{mark.val}</var>}%{helper?, helper %{helper.inv?<var data-tooltip="Match any helper except "%{helper.name}"">%{helper.val}</var>:<var data-tooltip="%{helper.name}">%{helper.val}</var>}}'), {
|
||||
ipv4: ((!family && dip.indexOf(':') == -1) || family == 'any' || (!family && !dip) || family == 'ipv4'),
|
||||
ipv6: ((!family && dip.indexOf(':') != -1) || family == 'any' || family == 'ipv6'),
|
||||
proto: proto,
|
||||
helper: h,
|
||||
mark: f
|
||||
});
|
||||
}
|
||||
|
||||
function rule_src_txt(s, hosts) {
|
||||
var z = uci.get('firewall', s, 'src');
|
||||
|
||||
return fwtool.fmt(_('From %{src}%{src_ip?, IP %{src_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{src_port?, port %{src_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}%{src_mac?, MAC %{src_mac#%{next?, }<var%{item.inv? data-tooltip="Match MACs except %{item.val}%{item.hint.name? a.k.a. %{item.hint.name}}.":%{item.hint.name? data-tooltip="%{item.hint.name}"}}>%{item.ival}</var>}}'), {
|
||||
src: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(z) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
|
||||
src_ip: fwtool.map_invert(uci.get('firewall', s, 'src_ip'), 'toLowerCase'),
|
||||
src_mac: fwtool.map_invert(uci.get('firewall', s, 'src_mac'), 'toUpperCase').map(function(v) { return Object.assign(v, { hint: hosts[v.val] }) }),
|
||||
src_port: fwtool.map_invert(uci.get('firewall', s, 'src_port'))
|
||||
});
|
||||
}
|
||||
|
||||
function rule_dest_txt(s) {
|
||||
return fwtool.fmt(_('To %{dest}%{dest_ip?, IP %{dest_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{dest_port?, port %{dest_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}'), {
|
||||
dest: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(null) }, [E('em', _('this device'))]),
|
||||
dest_ip: fwtool.map_invert(uci.get('firewall', s, 'src_dip'), 'toLowerCase'),
|
||||
dest_port: fwtool.map_invert(uci.get('firewall', s, 'src_dport'))
|
||||
});
|
||||
}
|
||||
|
||||
function rule_limit_txt(s) {
|
||||
var m = String(uci.get('firewall', s, 'limit')).match(/^(\d+)\/([smhd])\w*$/i),
|
||||
l = m ? {
|
||||
num: +m[1],
|
||||
unit: ({ s: _('second'), m: _('minute'), h: _('hour'), d: _('day') })[m[2]],
|
||||
burst: uci.get('firewall', s, 'limit_burst')
|
||||
} : null;
|
||||
|
||||
if (!l)
|
||||
return '';
|
||||
|
||||
return fwtool.fmt(_('Limit matching to <var>%{limit.num}</var> packets per <var>%{limit.unit}</var>%{limit.burst? burst <var>%{limit.burst}</var>}'), { limit: l });
|
||||
}
|
||||
|
||||
function rule_target_txt(s) {
|
||||
var z = uci.get('firewall', s, 'dest');
|
||||
|
||||
return fwtool.fmt(_('<var data-tooltip="DNAT">Forward</var> to %{dest}%{dest_ip? IP <var>%{dest_ip}</var>}%{dest_port? port <var>%{dest_port}</var>}'), {
|
||||
dest: E('span', { 'class': 'zonebadge', 'style': 'background-color:' + fwmodel.getColorForName((z && z != '*') ? z : null) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
|
||||
dest_ip: (uci.get('firewall', s, 'dest_ip') || '').toLowerCase(),
|
||||
dest_port: uci.get('firewall', s, 'dest_port')
|
||||
});
|
||||
}
|
||||
|
||||
function validate_opt_family(m, section_id, opt) {
|
||||
var dopt = m.section.getOption('dest_ip'),
|
||||
fmopt = m.section.getOption('family');
|
||||
|
||||
if (!dopt.isValid(section_id) && opt != 'dest_ip')
|
||||
return true;
|
||||
if (!fmopt.isValid(section_id) && opt != 'family')
|
||||
return true;
|
||||
|
||||
var dip = dopt.formvalue(section_id) || '',
|
||||
fm = fmopt.formvalue(section_id) || '';
|
||||
|
||||
if (fm == '' || (fm == 'any' && dip == '') || (fm == 'ipv6' && (dip.indexOf(':') != -1 || dip == '')) || (fm == 'ipv4' && dip.indexOf(':') == -1))
|
||||
return true;
|
||||
|
||||
return _('Address family, Internal IP address must match');
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
callHostHints: rpc.declare({
|
||||
object: 'luci-rpc',
|
||||
method: 'getHostHints',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
callConntrackHelpers: rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'getConntrackHelpers',
|
||||
expect: { result: [] }
|
||||
}),
|
||||
|
||||
callNetworkDevices: rpc.declare({
|
||||
object: 'luci-rpc',
|
||||
method: 'getNetworkDevices',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
this.callHostHints(),
|
||||
this.callConntrackHelpers(),
|
||||
this.callNetworkDevices(),
|
||||
uci.load('firewall')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
if (fwtool.checkLegacySNAT())
|
||||
return fwtool.renderMigration();
|
||||
else
|
||||
return this.renderForwards(data);
|
||||
},
|
||||
|
||||
renderForwards: function(data) {
|
||||
var hosts = data[0],
|
||||
ctHelpers = data[1],
|
||||
devs = data[2],
|
||||
m, s, o;
|
||||
var fw4 = L.hasSystemFeature('firewall4');
|
||||
|
||||
m = new form.Map('firewall', _('Firewall - Port Forwards'),
|
||||
_('Port forwarding allows remote computers on the Internet to connect to a specific computer or service within the private LAN.'));
|
||||
|
||||
s = m.section(form.GridSection, 'redirect', _('Port Forwards'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.sortable = true;
|
||||
s.cloneable = true;
|
||||
|
||||
s.tab('general', _('General Settings'));
|
||||
s.tab('advanced', _('Advanced Settings'));
|
||||
|
||||
s.filter = function(section_id) {
|
||||
return (uci.get('firewall', section_id, 'target') != 'SNAT');
|
||||
};
|
||||
|
||||
s.sectiontitle = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'name') || _('Unnamed forward');
|
||||
};
|
||||
|
||||
s.handleAdd = function(ev) {
|
||||
var config_name = this.uciconfig || this.map.config,
|
||||
section_id = uci.add(config_name, this.sectiontype);
|
||||
|
||||
uci.set(config_name, section_id, 'dest', 'lan');
|
||||
uci.set(config_name, section_id, 'target', 'DNAT');
|
||||
|
||||
m.addedSection = section_id;
|
||||
this.renderMoreOptionsModal(section_id);
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'name', _('Name'));
|
||||
o.placeholder = _('Unnamed forward');
|
||||
o.modalonly = true;
|
||||
|
||||
if (fw4) {
|
||||
o = s.taboption('general', form.ListValue, 'family', _('Restrict to address family'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.value('any', _('IPv4 and IPv6'));
|
||||
o.value('ipv4', _('IPv4 only'));
|
||||
o.value('ipv6', _('IPv6 only'));
|
||||
o.value('', _('automatic')); // infer from zone or used IP addresses
|
||||
o.cfgvalue = function(section_id) {
|
||||
var val = this.map.data.get(this.map.config, section_id, 'family');
|
||||
|
||||
if (!val)
|
||||
return '';
|
||||
else if (val == 'any' || val == 'all' || val == '*')
|
||||
return 'any';
|
||||
else if (val == 'inet' || String(val).indexOf('4') != -1)
|
||||
return 'ipv4';
|
||||
else if (String(val).indexOf('6') != -1)
|
||||
return 'ipv6';
|
||||
};
|
||||
o.validate = function(section_id, value) {
|
||||
fwtool.updateHostHints(this.map, section_id, 'dest_ip', value, hosts);
|
||||
return !fw4?true:validate_opt_family(this, section_id, 'family');
|
||||
};
|
||||
}
|
||||
|
||||
o = s.option(form.DummyValue, '_match', _('Match'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(s) {
|
||||
return E('small', [
|
||||
rule_proto_txt(s, ctHelpers), E('br'),
|
||||
rule_src_txt(s, hosts), E('br'),
|
||||
rule_dest_txt(s), E('br'),
|
||||
rule_limit_txt(s)
|
||||
]);
|
||||
};
|
||||
|
||||
o = s.option(form.ListValue, '_dest', _('Action'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(s) {
|
||||
return E('small', [
|
||||
rule_target_txt(s)
|
||||
]);
|
||||
};
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.modalonly = false;
|
||||
o.default = o.enabled;
|
||||
o.editable = true;
|
||||
|
||||
o = s.taboption('general', fwtool.CBIProtocolSelect, 'proto', _('Protocol'));
|
||||
o.modalonly = true;
|
||||
o.default = 'tcp udp';
|
||||
|
||||
o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.nocreate = true;
|
||||
o.default = 'wan';
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'ipset', _('Use ipset'));
|
||||
uci.sections('firewall', 'ipset', function(s) {
|
||||
if (typeof(s.name) == 'string')
|
||||
o.value(s.name, s.comment ? '%s (%s)'.format(s.name, s.comment) : s.name);
|
||||
});
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = fwtool.addMACOption(s, 'advanced', 'src_mac', _('Source MAC address'),
|
||||
_('Only match incoming traffic from these MACs.'), hosts);
|
||||
o.rmempty = true;
|
||||
o.datatype = 'list(neg(macaddr))';
|
||||
|
||||
o = fwtool.addIPOption(s, 'advanced', 'src_ip', _('Source IP address'),
|
||||
_('Only match incoming traffic from this IP or range.'), !fw4?'ipv4':'', hosts);
|
||||
o.rmempty = true;
|
||||
o.datatype = !fw4?'neg(ipmask4("true"))':'neg(ipmask("true"))';
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'src_port', _('Source port'),
|
||||
_('Only match incoming traffic originating from the given source port or port range on the client host'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.datatype = 'neg(portrange)';
|
||||
o.placeholder = _('any');
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = fwtool.addLocalIPOption(s, 'advanced', 'src_dip', _('External IP address'),
|
||||
_('Only match incoming traffic directed at the given IP address.'), devs);
|
||||
o.datatype = !fw4?'neg(ipmask4("true"))':'neg(ipmask("true"))';
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('general', form.Value, 'src_dport', _('External port'),
|
||||
_('Match incoming traffic directed at the given destination port or port range on this host'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.datatype = 'neg(portrange)';
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Internal zone'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.nocreate = true;
|
||||
|
||||
o = fwtool.addIPOption(s, 'general', 'dest_ip', _('Internal IP address'),
|
||||
_('Redirect matched incoming traffic to the specified internal host'), !fw4?'ipv4':'', hosts);
|
||||
o.rmempty = true;
|
||||
o.datatype = !fw4?'ipmask4':'ipmask';
|
||||
|
||||
o = s.taboption('general', form.Value, 'dest_port', _('Internal port'),
|
||||
_('Redirect matched incoming traffic to the given port on the internal host'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.placeholder = _('any');
|
||||
o.datatype = 'portrange';
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = s.taboption('advanced', form.Flag, 'reflection', _('Enable NAT Loopback'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.default = o.enabled;
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'reflection_src', _('Loopback source IP'), _('Specifies whether to use the external or the internal IP address for reflected traffic.'));
|
||||
o.modalonly = true;
|
||||
o.depends('reflection', '1');
|
||||
o.value('internal', _('Use internal IP address'));
|
||||
o.value('external', _('Use external IP address'));
|
||||
o.write = function(section_id, value) {
|
||||
uci.set('firewall', section_id, 'reflection_src', (value != 'internal') ? value : null);
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', widgets.ZoneSelect, 'reflection_zone', _('Reflection zones'), _('Zones from which reflection rules shall be created. If unset, only the destination zone is used.'));
|
||||
o.nocreate = true;
|
||||
o.multiple = true;
|
||||
o.modalonly = true;
|
||||
o.depends('reflection', '1');
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = _('any');
|
||||
for (var i = 0; i < ctHelpers.length; i++)
|
||||
o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
|
||||
o.validate = function(section_id, value) {
|
||||
if (value == '' || value == null)
|
||||
return true;
|
||||
|
||||
value = value.replace(/^!\s*/, '');
|
||||
|
||||
for (var i = 0; i < ctHelpers.length; i++)
|
||||
if (value == ctHelpers[i].name)
|
||||
return true;
|
||||
|
||||
return _('Unknown or not installed conntrack helper "%s"').format(value);
|
||||
};
|
||||
|
||||
fwtool.addMarkOption(s, false);
|
||||
fwtool.addLimitOption(s);
|
||||
fwtool.addLimitBurstOption(s);
|
||||
|
||||
if (!L.hasSystemFeature('firewall4')) {
|
||||
o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
|
||||
_('Passes additional arguments to iptables. Use with care!'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
}
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,219 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require firewall';
|
||||
'require tools.firewall as fwtool';
|
||||
|
||||
|
||||
return view.extend({
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('firewall')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('firewall', _('Firewall - IP sets'),
|
||||
_('firewall4 supports referencing and creating IP sets to simplify matching of large address lists without the need to create one rule per item to match. Port ranges in ipsets are unsupported by firewall4.<br />'));
|
||||
|
||||
var have_fw4 = L.hasSystemFeature('firewall4');
|
||||
|
||||
if (have_fw4) {
|
||||
s = m.section(form.NamedSection, 'fwver', 'fwver', '', _('Your device runs firewall4.'));
|
||||
} else {
|
||||
s = m.section(form.NamedSection, 'fwver', 'fwver', '', _('Your device does not run firewall4.'));
|
||||
}
|
||||
|
||||
|
||||
s = m.section(form.GridSection, 'ipset', _('IP Sets'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.sortable = true;
|
||||
s.cloneable = true;
|
||||
s.nodescriptions = true;
|
||||
|
||||
|
||||
/* refer to: https://ipset.netfilter.org/ipset.man.html */
|
||||
if (have_fw4) {
|
||||
o = s.option(form.Value, 'name', _('Name'));
|
||||
o.optional = false;
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
if (!/^[a-zA-Z_.][a-zA-Z0-9\/_.-]*$/.test(value))
|
||||
return _('Invalid set name');
|
||||
|
||||
return true;
|
||||
};
|
||||
} else {
|
||||
o = s.option(form.Value, 'name', _('Name'));
|
||||
o.depends({ external: '' });
|
||||
/* Default: (none) if external is unset
|
||||
value of external if external is set */
|
||||
}
|
||||
o.placeholder = _('Unnamed set');
|
||||
|
||||
|
||||
/* comment requires https://git.openwrt.org/?p=project/firewall4.git;a=commitdiff;h=39e8c70957c795bf0c12f04299170ae86c6efdf8 */
|
||||
o = s.option(form.Value, 'comment', _('Comment'));
|
||||
o.placeholder = _('Comment');
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
|
||||
o = s.option(form.ListValue, 'family', _('Family'));
|
||||
o.value('ipv4', _('IPv4'));
|
||||
o.value('ipv6', _('IPv6'));
|
||||
o.default = _('ipv4');
|
||||
|
||||
|
||||
/* Direction src, dst; (Data)Types: ip, port, mac, net or set
|
||||
Tuples: direction_datatype e.g. src_port, dest_net */
|
||||
o = s.option(form.DynamicList, 'match', _('Packet Field Match'),
|
||||
_('Packet fields to match upon.<br />' +
|
||||
'Syntax: <em>direction_datatype</em>. e.g.: <code>src_port, dest_net</code>.<br />' +
|
||||
'Directions: <code>src, dst</code>. Datatypes: <code>ip, port, mac, net, set</code>.<br />' +
|
||||
'Direction prefixes are optional.<br />' +
|
||||
'*Note: datatype <code>set</code> is unsupported in fw4.'));
|
||||
o.value('ip', _('ip: IP addr'));
|
||||
o.value('port', _('port: Port'));
|
||||
o.value('mac', _('mac: MAC addr'));
|
||||
o.value('net', _('net: (sub)net'));
|
||||
if (!have_fw4)
|
||||
o.value('set', _('set: ipset*'));
|
||||
o.value('src_ip', _('src_ip: Source IP'));
|
||||
o.value('src_port', _('src_port: Source Port'));
|
||||
o.value('src_mac', _('src_mac: Source MAC addr'));
|
||||
o.value('src_net', _('src_net: Source (sub)net'));
|
||||
if (!have_fw4)
|
||||
o.value('src_set', _('src_Set: Source ipset*')); // fw4 unsupported
|
||||
o.value('dest_ip', _('dest_ip: Destination IP'));
|
||||
o.value('dest_port', _('dest_port: Destination Port'));
|
||||
o.value('dest_mac', _('dest_mac: Destination MAC addr'));
|
||||
o.value('dest_net', _('dest_net: Destination (sub)net'));
|
||||
if (!have_fw4)
|
||||
o.value('dest_set', _('dest_set: Destination ipset*')); // fw4 unsupported
|
||||
o.optional = false;
|
||||
o.rmempty = false;
|
||||
|
||||
|
||||
// TODO: if/when firewall5 arrives, this 'else' check must change.
|
||||
if (have_fw4) {
|
||||
|
||||
//we have fw4
|
||||
o = s.option(form.DynamicList, 'entry', _('IPs/Networks/MACs'),
|
||||
_('macaddr|ip[/cidr]<br />'));
|
||||
o.datatype = 'or(ipaddr,macaddr)';
|
||||
o.rmempty = true;
|
||||
|
||||
|
||||
o = s.option(form.Value, 'maxelem', _('Max Entries'),
|
||||
_('up to 65536 entries.'));
|
||||
o.datatype = 'port'; //covers 16 bit size
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
} else {
|
||||
// this else section is intended to handle firewall3
|
||||
|
||||
o = s.option(form.Value, 'external', _('Refer To External Set'));
|
||||
/* Todo: loop to fill o.values with all other ipset names except itself */
|
||||
o.rmempty = true;
|
||||
o.optional = true;
|
||||
|
||||
|
||||
/* 'storage' depends on fw3. It must be removed for fw4 */
|
||||
//aka 'method' in netfilter terminology.
|
||||
o = s.option(form.ListValue, 'storage', _('Storage Method'));
|
||||
o.value('bitmap', _('bitmap')); //ipv4 only
|
||||
o.value('hash', _('hash'));
|
||||
o.value('list', _('list'));
|
||||
o.validate = function(section_id, value) {
|
||||
var family = this.section.formvalue(section_id, 'family');
|
||||
if (value.match(/bitmap/) && !family.match(/ipv4/))
|
||||
return _('bitmap is ipv4 only');
|
||||
return true;
|
||||
}
|
||||
|
||||
/* this iprange differs from netfilters range fromip-toip|ip/cidr:
|
||||
uci enforces a datatype = cidr in order to be able to enter
|
||||
an IP for all storage/data types. */
|
||||
o = s.option(form.Value, 'iprange', _('IP (range)'),
|
||||
_('ip[/cidr]<br />'+
|
||||
'For use with Match datatypes: <code>*_ip</code>.'));
|
||||
o.datatype = 'ipaddr';
|
||||
o.depends({family: 'ipv4', storage: 'bitmap', match: /_ip|_mac/ });
|
||||
o.depends({storage: 'hash', match: /_ip/ });
|
||||
|
||||
|
||||
o = s.option(form.DynamicList, 'entry', _('IPs/Networks'),
|
||||
_('ip[/cidr]<br />'));
|
||||
o.datatype = 'or(ipaddr,macaddr)';
|
||||
o.depends({storage: 'hash', match: /_ip|_net|_mac/ });
|
||||
|
||||
|
||||
o = s.option(form.Value, 'portrange', _('Port range'),
|
||||
_('fromport-toport'));
|
||||
o.datatype = 'neg(portrange)';
|
||||
o.depends({family: 'ipv4', storage: 'bitmap', match: /_port/ });
|
||||
o.depends({family: 'ipv4', storage: 'hash', match: /_port/ });
|
||||
o.depends({family: 'ipv6', storage: 'hash', match: /_port/ });
|
||||
|
||||
|
||||
o = s.option(form.Value, 'netmask', _('Netmask'));
|
||||
o.datatype = 'or(ip4prefix,ip6prefix)';
|
||||
o.depends({family: 'ipv4', storage: 'bitmap', match: /_ip/ });
|
||||
o.depends({storage: 'hash', match: /_ip/});
|
||||
|
||||
|
||||
o = s.option(form.Value, 'maxelem', _('Max Length'),
|
||||
_('up to 65536 entries.'));
|
||||
o.datatype = 'port'; //covers 16 bit size
|
||||
o.depends('storage', 'hash');
|
||||
o.depends('storage', 'list');
|
||||
o.modalonly = true;
|
||||
|
||||
|
||||
o = s.option(form.Value, 'hashsize', _('Initial Hash Size'));
|
||||
o.depends('storage', 'hash');
|
||||
o.placeholder = _('1024');
|
||||
o.modalonly = true;
|
||||
|
||||
}
|
||||
|
||||
o = s.option(form.FileUpload, 'loadfile', _('Include File'),
|
||||
_('Path to file of CIDRs, subnets, host IPs, etc.<br />'));
|
||||
o.root_directory = '/etc/luci-uploads';
|
||||
o.enable_delete = true;
|
||||
o.enable_upload = true;
|
||||
o.datatype = 'file';
|
||||
o.rmempty = true;
|
||||
|
||||
|
||||
o = s.option(form.Value, 'timeout', _('Timeout'),
|
||||
_('Unit: seconds. Default <code>0</code> means the entry is added permanently to the set.<br />' +
|
||||
'Max: 2147483 seconds.'));
|
||||
o.placeholder = _('0');
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
|
||||
o = s.option(form.Flag, 'counters', _('Counters'),
|
||||
_('Enables packet and byte count tracking for the set.'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.default = false;
|
||||
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enabled'));
|
||||
o.default = true;
|
||||
o.editable = true;
|
||||
o.modalonly = false;
|
||||
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,524 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require firewall as fwmodel';
|
||||
'require tools.firewall as fwtool';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
function rule_proto_txt(s, ctHelpers) {
|
||||
var f = (uci.get('firewall', s, 'family') || '').toLowerCase().replace(/^(?:any|\*)$/, '');
|
||||
|
||||
var proto = L.toArray(uci.get('firewall', s, 'proto')).filter(function(p) {
|
||||
return (p != '*' && p != 'any' && p != 'all');
|
||||
}).map(function(p) {
|
||||
var pr = fwtool.lookupProto(p);
|
||||
return {
|
||||
num: pr[0],
|
||||
name: pr[1],
|
||||
types: (pr[0] == 1 || pr[0] == 58) ? L.toArray(uci.get('firewall', s, 'icmp_type')) : null
|
||||
};
|
||||
});
|
||||
|
||||
var m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
|
||||
var h = m ? {
|
||||
val: m[0].toUpperCase(),
|
||||
inv: m[1],
|
||||
name: (ctHelpers.filter(function(ctH) { return ctH.name.toLowerCase() == m[2].toLowerCase() })[0] || {}).description
|
||||
} : null;
|
||||
|
||||
m = String(uci.get('firewall', s, 'mark')).match(/^(!\s*)?(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
|
||||
var w = m ? {
|
||||
val: m[0].toUpperCase().replace(/X/g, 'x'),
|
||||
inv: m[1],
|
||||
num: '0x%02X'.format(+m[2]),
|
||||
mask: m[3] ? '0x%02X'.format(+m[3]) : null
|
||||
} : null;
|
||||
|
||||
m = String(uci.get('firewall', s, 'dscp')).match(/^(!\s*)?(?:(CS[0-7]|BE|AF[1234][123]|EF)|(0x[0-9a-f]{1,2}|[0-9]{1,2}))$/);
|
||||
var d = m ? {
|
||||
val: m[0],
|
||||
inv: m[1],
|
||||
name: m[2],
|
||||
num: m[3] ? '0x%02X'.format(+m[3]) : null
|
||||
} : null;
|
||||
|
||||
return fwtool.fmt(_('%{src?%{dest?Forwarded:Incoming}:Outgoing} %{ipv6?%{ipv4?<var>IPv4</var> and <var>IPv6</var>:<var>IPv6</var>}:<var>IPv4</var>}%{proto?, protocol %{proto#%{next?, }%{item.types?<var class="cbi-tooltip-container">%{item.name}<span class="cbi-tooltip">ICMP with types %{item.types#%{next?, }<var>%{item}</var>}</span></var>:<var>%{item.name}</var>}}}%{mark?, mark <var%{mark.inv? data-tooltip="Match fwmarks except %{mark.num}%{mark.mask? with mask %{mark.mask}}.":%{mark.mask? data-tooltip="Mask fwmark value with %{mark.mask} before compare."}}>%{mark.val}</var>}%{dscp?, DSCP %{dscp.inv?<var data-tooltip="Match DSCP classifications except %{dscp.num?:%{dscp.name}}">%{dscp.val}</var>:<var>%{dscp.val}</var>}}%{helper?, helper %{helper.inv?<var data-tooltip="Match any helper except "%{helper.name}"">%{helper.val}</var>:<var data-tooltip="%{helper.name}">%{helper.val}</var>}}'), {
|
||||
ipv4: (!f || f == 'ipv4'),
|
||||
ipv6: (!f || f == 'ipv6'),
|
||||
src: uci.get('firewall', s, 'src'),
|
||||
dest: uci.get('firewall', s, 'dest'),
|
||||
proto: proto,
|
||||
helper: h,
|
||||
mark: w,
|
||||
dscp: d
|
||||
});
|
||||
}
|
||||
|
||||
function rule_src_txt(s, hosts) {
|
||||
var z = uci.get('firewall', s, 'src'),
|
||||
d = (uci.get('firewall', s, 'direction') == 'in') ? uci.get('firewall', s, 'device') : null;
|
||||
|
||||
return fwtool.fmt(_('From %{src}%{src_device?, interface <var>%{src_device}</var>}%{src_ip?, IP %{src_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{src_port?, port %{src_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}%{src_mac?, MAC %{src_mac#%{next?, }<var%{item.inv? data-tooltip="Match MACs except %{item.val}%{item.hint.name? a.k.a. %{item.hint.name}}.":%{item.hint.name? data-tooltip="%{item.hint.name}"}}>%{item.ival}</var>}}'), {
|
||||
src: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(z) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
|
||||
src_ip: fwtool.map_invert(uci.get('firewall', s, 'src_ip'), 'toLowerCase'),
|
||||
src_mac: fwtool.map_invert(uci.get('firewall', s, 'src_mac'), 'toUpperCase').map(function(v) { return Object.assign(v, { hint: hosts[v.val] }) }),
|
||||
src_port: fwtool.map_invert(uci.get('firewall', s, 'src_port')),
|
||||
src_device: d
|
||||
});
|
||||
}
|
||||
|
||||
function rule_dest_txt(s) {
|
||||
var z = uci.get('firewall', s, 'dest'),
|
||||
d = (uci.get('firewall', s, 'direction') == 'out') ? uci.get('firewall', s, 'device') : null;
|
||||
|
||||
return fwtool.fmt(_('To %{dest}%{dest_device?, interface <var>%{dest_device}</var>}%{dest_ip?, IP %{dest_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{dest_port?, port %{dest_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}'), {
|
||||
dest: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(z) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
|
||||
dest_ip: fwtool.map_invert(uci.get('firewall', s, 'dest_ip'), 'toLowerCase'),
|
||||
dest_port: fwtool.map_invert(uci.get('firewall', s, 'dest_port')),
|
||||
dest_device: d
|
||||
});
|
||||
}
|
||||
|
||||
function rule_limit_txt(s) {
|
||||
var m = String(uci.get('firewall', s, 'limit')).match(/^(\d+)\/([smhd])\w*$/i),
|
||||
l = m ? {
|
||||
num: +m[1],
|
||||
unit: ({ s: _('second'), m: _('minute'), h: _('hour'), d: _('day') })[m[2]],
|
||||
burst: uci.get('firewall', s, 'limit_burst')
|
||||
} : null;
|
||||
|
||||
if (!l)
|
||||
return '';
|
||||
|
||||
return fwtool.fmt(_('Limit matching to <var>%{limit.num}</var> packets per <var>%{limit.unit}</var>%{limit.burst? burst <var>%{limit.burst}</var>}'), { limit: l });
|
||||
}
|
||||
|
||||
function rule_target_txt(s, ctHelpers) {
|
||||
var t = uci.get('firewall', s, 'target'),
|
||||
h = (uci.get('firewall', s, 'set_helper') || '').toUpperCase(),
|
||||
s = {
|
||||
target: t,
|
||||
src: uci.get('firewall', s, 'src'),
|
||||
dest: uci.get('firewall', s, 'dest'),
|
||||
set_helper: h,
|
||||
set_mark: uci.get('firewall', s, 'set_mark'),
|
||||
set_xmark: uci.get('firewall', s, 'set_xmark'),
|
||||
set_dscp: uci.get('firewall', s, 'set_dscp'),
|
||||
helper_name: (ctHelpers.filter(function(ctH) { return ctH.name.toUpperCase() == h })[0] || {}).description
|
||||
};
|
||||
|
||||
switch (t) {
|
||||
case 'DROP':
|
||||
return fwtool.fmt(_('<var data-tooltip="DROP">Drop</var> %{src?%{dest?forward:input}:output}'), s);
|
||||
|
||||
case 'ACCEPT':
|
||||
return fwtool.fmt(_('<var data-tooltip="ACCEPT">Accept</var> %{src?%{dest?forward:input}:output}'), s);
|
||||
|
||||
case 'REJECT':
|
||||
return fwtool.fmt(_('<var data-tooltip="REJECT">Reject</var> %{src?%{dest?forward:input}:output}'), s);
|
||||
|
||||
case 'NOTRACK':
|
||||
return fwtool.fmt(_('<var data-tooltip="NOTRACK">Do not track</var> %{src?%{dest?forward:input}:output}'), s);
|
||||
|
||||
case 'HELPER':
|
||||
return fwtool.fmt(_('<var data-tooltip="HELPER">Assign conntrack</var> helper <var%{helper_name? data-tooltip="%{helper_name}"}>%{set_helper}</var>'), s);
|
||||
|
||||
case 'MARK':
|
||||
return fwtool.fmt(_('<var data-tooltip="MARK">%{set_mark?Assign:XOR}</var> firewall mark <var>%{set_mark?:%{set_xmark}}</var>'), s);
|
||||
|
||||
case 'DSCP':
|
||||
return fwtool.fmt(_('<var data-tooltip="DSCP">Assign DSCP</var> classification <var>%{set_dscp}</var>'), s);
|
||||
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
callHostHints: rpc.declare({
|
||||
object: 'luci-rpc',
|
||||
method: 'getHostHints',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
callConntrackHelpers: rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'getConntrackHelpers',
|
||||
expect: { result: [] }
|
||||
}),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
this.callHostHints(),
|
||||
this.callConntrackHelpers(),
|
||||
uci.load('firewall')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
if (fwtool.checkLegacySNAT())
|
||||
return fwtool.renderMigration();
|
||||
else
|
||||
return this.renderRules(data);
|
||||
},
|
||||
|
||||
renderRules: function(data) {
|
||||
var hosts = data[0],
|
||||
ctHelpers = data[1],
|
||||
m, s, o;
|
||||
|
||||
m = new form.Map('firewall', _('Firewall - Traffic Rules'),
|
||||
_('Traffic rules define policies for packets travelling between different zones, for example to reject traffic between certain hosts or to open WAN ports on the router.'));
|
||||
|
||||
s = m.section(form.GridSection, 'rule', _('Traffic Rules'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.sortable = true;
|
||||
s.cloneable = true;
|
||||
|
||||
s.tab('general', _('General Settings'));
|
||||
s.tab('advanced', _('Advanced Settings'));
|
||||
s.tab('timed', _('Time Restrictions'));
|
||||
|
||||
s.filter = function(section_id) {
|
||||
return (uci.get('firewall', section_id, 'target') != 'SNAT');
|
||||
};
|
||||
|
||||
s.sectiontitle = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'name') || _('Unnamed rule');
|
||||
};
|
||||
|
||||
s.handleAdd = function(ev) {
|
||||
var config_name = this.uciconfig || this.map.config,
|
||||
section_id = uci.add(config_name, this.sectiontype),
|
||||
opt1 = this.getOption('src'),
|
||||
opt2 = this.getOption('dest');
|
||||
|
||||
opt1.default = 'wan';
|
||||
opt2.default = 'lan';
|
||||
|
||||
this.addedSection = section_id;
|
||||
this.renderMoreOptionsModal(section_id);
|
||||
|
||||
delete opt1.default;
|
||||
delete opt2.default;
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'name', _('Name'));
|
||||
o.placeholder = _('Unnamed rule');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_match', _('Match'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(s) {
|
||||
return E('small', [
|
||||
rule_proto_txt(s, ctHelpers), E('br'),
|
||||
rule_src_txt(s, hosts), E('br'),
|
||||
rule_dest_txt(s), E('br'),
|
||||
rule_limit_txt(s)
|
||||
]);
|
||||
};
|
||||
|
||||
o = s.option(form.ListValue, '_target', _('Action'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(s) {
|
||||
return rule_target_txt(s, ctHelpers);
|
||||
};
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.modalonly = false;
|
||||
o.default = o.enabled;
|
||||
o.editable = true;
|
||||
o.tooltip = function(section_id) {
|
||||
var weekdays = uci.get('firewall', section_id, 'weekdays');
|
||||
var monthdays = uci.get('firewall', section_id, 'monthdays');
|
||||
var start_time = uci.get('firewall', section_id, 'start_time');
|
||||
var stop_time = uci.get('firewall', section_id, 'stop_time');
|
||||
var start_date = uci.get('firewall', section_id, 'start_date');
|
||||
var stop_date = uci.get('firewall', section_id, 'stop_date');
|
||||
|
||||
if (weekdays || monthdays || start_time || stop_time || start_date || stop_date )
|
||||
return _('Time restrictions are enabled for this rule');
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'direction', _('Match device'));
|
||||
o.modalonly = true;
|
||||
o.value('', _('unspecified'));
|
||||
o.value('in', _('Inbound device'));
|
||||
o.value('out', _('Outbound device'));
|
||||
o.cfgvalue = function(section_id) {
|
||||
var val = uci.get('firewall', section_id, 'direction');
|
||||
switch (val) {
|
||||
case 'in':
|
||||
case 'ingress':
|
||||
return 'in';
|
||||
|
||||
case 'out':
|
||||
case 'egress':
|
||||
return 'out';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Device name'),
|
||||
_('Specifies whether to tie this traffic rule to a specific inbound or outbound network device.'));
|
||||
o.modalonly = true;
|
||||
o.noaliases = true;
|
||||
o.rmempty = false;
|
||||
o.depends('direction', 'in');
|
||||
o.depends('direction', 'out');
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.value('', _('IPv4 and IPv6'));
|
||||
o.value('ipv4', _('IPv4 only'));
|
||||
o.value('ipv6', _('IPv6 only'));
|
||||
o.validate = function(section_id, value) {
|
||||
fwtool.updateHostHints(this.map, section_id, 'src_ip', value, hosts);
|
||||
fwtool.updateHostHints(this.map, section_id, 'dest_ip', value, hosts);
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('general', fwtool.CBIProtocolSelect, 'proto', _('Protocol'));
|
||||
o.modalonly = true;
|
||||
o.default = 'tcp udp';
|
||||
|
||||
o = s.taboption('advanced', form.MultiValue, 'icmp_type', _('Match ICMP type'));
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
o.custom = true;
|
||||
o.cast = 'table';
|
||||
o.placeholder = _('any/all');
|
||||
o.value('address-mask-reply');
|
||||
o.value('address-mask-request');
|
||||
o.value('address-unreachable'); /* icmpv6 1:3 */
|
||||
o.value('bad-header'); /* icmpv6 4:0 */
|
||||
o.value('certification-path-solicitation-message'); /* icmpv6 148 */
|
||||
o.value('certification-path-advertisement-message'); /* icmpv6 149 */
|
||||
o.value('communication-prohibited');
|
||||
o.value('destination-unreachable');
|
||||
o.value('duplicate-address-request'); /* icmpv6 157 */
|
||||
o.value('duplicate-address-confirmation'); /* icmpv6 158 */
|
||||
o.value('echo-reply');
|
||||
o.value('echo-request');
|
||||
o.value('extended-echo-request'); /* icmpv6 160 */
|
||||
o.value('extended-echo-reply'); /* icmpv6 161 */
|
||||
o.value('fmipv6-message'); /* icmpv6 154 */
|
||||
o.value('fragmentation-needed');
|
||||
o.value('home-agent-address-discovery-reply-message'); /* icmpv6 145 */
|
||||
o.value('home-agent-address-discovery-request-message'); /* icmpv6 144 */
|
||||
o.value('host-precedence-violation');
|
||||
o.value('host-prohibited');
|
||||
o.value('host-redirect');
|
||||
o.value('host-unknown');
|
||||
o.value('host-unreachable');
|
||||
o.value('ilnpv6-locator-update-message'); /* icmpv6 156 */
|
||||
o.value('inverse-neighbour-discovery-advertisement-message'); /* icmpv6 142 */
|
||||
o.value('inverse-neighbour-discovery-solicitation-message'); /* icmpv6 141 */
|
||||
o.value('ip-header-bad');
|
||||
o.value('mobile-prefix-advertisement'); /* icmpv6 147 */
|
||||
o.value('mobile-prefix-solicitation'); /* icmpv6 146 */
|
||||
o.value('mpl-control-message'); /* icmpv6 159 */
|
||||
o.value('multicast-listener-query'); /* icmpv6 130 */
|
||||
o.value('multicast-listener-report'); /* icmpv6 131 */
|
||||
o.value('multicast-listener-done'); /* icmpv6 132 */
|
||||
o.value('multicast-router-advertisement'); /* icmpv6 151 */
|
||||
o.value('multicast-router-solicitation'); /* icmpv6 152 */
|
||||
o.value('multicast-router-termination'); /* icmpv6 153 */
|
||||
o.value('neighbour-advertisement');
|
||||
o.value('neighbour-solicitation');
|
||||
o.value('network-prohibited');
|
||||
o.value('network-redirect');
|
||||
o.value('network-unknown');
|
||||
o.value('network-unreachable');
|
||||
o.value('no-route'); /* icmpv6 1:0 */
|
||||
o.value('node-info-query'); /* icmpv6 139 */
|
||||
o.value('node-info-response'); /* icmpv6 140 */
|
||||
o.value('packet-too-big');
|
||||
o.value('parameter-problem');
|
||||
o.value('port-unreachable');
|
||||
o.value('precedence-cutoff');
|
||||
o.value('protocol-unreachable');
|
||||
o.value('redirect');
|
||||
o.value('required-option-missing');
|
||||
o.value('router-advertisement');
|
||||
o.value('router-renumbering'); /* icmpv6 138 */
|
||||
o.value('router-solicitation');
|
||||
o.value('rpl-control-message'); /* icmpv6 155 */
|
||||
o.value('source-quench');
|
||||
o.value('source-route-failed');
|
||||
o.value('time-exceeded');
|
||||
o.value('timestamp-reply');
|
||||
o.value('timestamp-request');
|
||||
o.value('TOS-host-redirect');
|
||||
o.value('TOS-host-unreachable');
|
||||
o.value('TOS-network-redirect');
|
||||
o.value('TOS-network-unreachable');
|
||||
o.value('ttl-zero-during-reassembly');
|
||||
o.value('ttl-zero-during-transit');
|
||||
o.value('v2-multicast-listener-report'); /* icmpv6 143 */
|
||||
o.value('unknown-header-type'); /* icmpv6 4:1 */
|
||||
o.value('unknown-option'); /* icmpv6 4:2 */
|
||||
o.depends({ proto: 'icmp', '!contains': true });
|
||||
o.depends({ proto: 'icmpv6', '!contains': true });
|
||||
|
||||
o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
|
||||
o.modalonly = true;
|
||||
o.nocreate = true;
|
||||
o.allowany = true;
|
||||
o.allowlocal = 'src';
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'ipset', _('Use ipset'));
|
||||
uci.sections('firewall', 'ipset', function(s) {
|
||||
if (typeof(s.name) == 'string')
|
||||
o.value(s.name, s.comment ? '%s (%s)'.format(s.name, s.comment) : s.name);
|
||||
});
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
fwtool.addMACOption(s, 'advanced', 'src_mac', _('Source MAC address'), null, hosts);
|
||||
fwtool.addIPOption(s, 'general', 'src_ip', _('Source address'), null, '', hosts, true);
|
||||
|
||||
o = s.taboption('general', form.Value, 'src_port', _('Source port'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'list(neg(portrange))';
|
||||
o.placeholder = _('any');
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Destination zone'));
|
||||
o.modalonly = true;
|
||||
o.nocreate = true;
|
||||
o.allowany = true;
|
||||
o.allowlocal = true;
|
||||
|
||||
fwtool.addIPOption(s, 'general', 'dest_ip', _('Destination address'), null, '', hosts, true);
|
||||
|
||||
o = s.taboption('general', form.Value, 'dest_port', _('Destination port'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'list(neg(portrange))';
|
||||
o.placeholder = _('any');
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'target', _('Action'));
|
||||
o.modalonly = true;
|
||||
o.default = 'ACCEPT';
|
||||
o.value('DROP', _('drop'));
|
||||
o.value('ACCEPT', _('accept'));
|
||||
o.value('REJECT', _('reject'));
|
||||
o.value('NOTRACK', _("don't track"));
|
||||
o.value('HELPER', _('assign conntrack helper'));
|
||||
o.value('MARK_SET', _('apply firewall mark'));
|
||||
o.value('MARK_XOR', _('XOR firewall mark'));
|
||||
o.value('DSCP', _('DSCP classification'));
|
||||
o.cfgvalue = function(section_id) {
|
||||
var t = uci.get('firewall', section_id, 'target'),
|
||||
m = uci.get('firewall', section_id, 'set_mark');
|
||||
|
||||
if (t == 'MARK')
|
||||
return m ? 'MARK_SET' : 'MARK_XOR';
|
||||
|
||||
return t;
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
return this.super('write', [section_id, (value == 'MARK_SET' || value == 'MARK_XOR') ? 'MARK' : value]);
|
||||
};
|
||||
|
||||
fwtool.addMarkOption(s, 1);
|
||||
fwtool.addMarkOption(s, 2);
|
||||
fwtool.addDSCPOption(s, true);
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'set_helper', _('Tracking helper'), _('Assign the specified connection tracking helper to matched traffic.'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = _('any');
|
||||
o.depends('target', 'HELPER');
|
||||
for (var i = 0; i < ctHelpers.length; i++)
|
||||
o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = _('any');
|
||||
for (var i = 0; i < ctHelpers.length; i++)
|
||||
o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
|
||||
o.validate = function(section_id, value) {
|
||||
if (value == '' || value == null)
|
||||
return true;
|
||||
|
||||
value = value.replace(/^!\s*/, '');
|
||||
|
||||
for (var i = 0; i < ctHelpers.length; i++)
|
||||
if (value == ctHelpers[i].name)
|
||||
return true;
|
||||
|
||||
return _('Unknown or not installed conntrack helper "%s"').format(value);
|
||||
};
|
||||
|
||||
fwtool.addMarkOption(s, false);
|
||||
fwtool.addDSCPOption(s, false);
|
||||
fwtool.addLimitOption(s);
|
||||
fwtool.addLimitBurstOption(s);
|
||||
|
||||
if (!L.hasSystemFeature('firewall4')) {
|
||||
o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
|
||||
_('Passes additional arguments to iptables. Use with care!'));
|
||||
o.modalonly = true;
|
||||
}
|
||||
|
||||
o = s.taboption('timed', form.MultiValue, 'weekdays', _('Week Days'));
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
o.display = 5;
|
||||
o.placeholder = _('Any day');
|
||||
o.value('Sun', _('Sunday'));
|
||||
o.value('Mon', _('Monday'));
|
||||
o.value('Tue', _('Tuesday'));
|
||||
o.value('Wed', _('Wednesday'));
|
||||
o.value('Thu', _('Thursday'));
|
||||
o.value('Fri', _('Friday'));
|
||||
o.value('Sat', _('Saturday'));
|
||||
o.write = function(section_id, value) {
|
||||
return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
|
||||
};
|
||||
|
||||
o = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days'));
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
o.display_size = 15;
|
||||
o.placeholder = _('Any day');
|
||||
o.write = function(section_id, value) {
|
||||
return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
|
||||
};
|
||||
for (var i = 1; i <= 31; i++)
|
||||
o.value(i);
|
||||
|
||||
o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh:mm:ss)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'timehhmmss';
|
||||
|
||||
o = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh:mm:ss)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'timehhmmss';
|
||||
|
||||
o = s.taboption('timed', form.Value, 'start_date', _('Start Date (yyyy-mm-dd)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'dateyyyymmdd';
|
||||
|
||||
o = s.taboption('timed', form.Value, 'stop_date', _('Stop Date (yyyy-mm-dd)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'dateyyyymmdd';
|
||||
|
||||
o = s.taboption('timed', form.Flag, 'utc_time', _('Time in UTC'));
|
||||
o.modalonly = true;
|
||||
o.default = o.disabled;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,397 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require firewall as fwmodel';
|
||||
'require tools.firewall as fwtool';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
function rule_proto_txt(s) {
|
||||
var family = (uci.get('firewall', s, 'family') || '').toLowerCase().replace(/^(?:all|\*)$/, 'any');
|
||||
var sip = uci.get('firewall', s, 'src_ip') || '';
|
||||
var dip = uci.get('firewall', s, 'dest_ip') || '';
|
||||
var rwip = uci.get('firewall', s, 'snat_ip') || '';
|
||||
var proto = L.toArray(uci.get('firewall', s, 'proto')).filter(function(p) {
|
||||
return (p != '*' && p != 'any' && p != 'all');
|
||||
}).map(function(p) {
|
||||
var pr = fwtool.lookupProto(p);
|
||||
return {
|
||||
num: pr[0],
|
||||
name: pr[1]
|
||||
};
|
||||
});
|
||||
|
||||
var m = String(uci.get('firewall', s, 'mark')).match(/^(!\s*)?(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
|
||||
var f = m ? {
|
||||
val: m[0].toUpperCase().replace(/X/g, 'x'),
|
||||
inv: m[1],
|
||||
num: '0x%02X'.format(+m[2]),
|
||||
mask: m[3] ? '0x%02X'.format(+m[3]) : null
|
||||
} : null;
|
||||
|
||||
return fwtool.fmt(_('Forwarded %{ipv6?%{ipv4?<var>IPv4</var> and <var>IPv6</var>:<var>IPv6</var>}:<var>IPv4</var>}%{proto?, protocol %{proto#%{next?, }<var>%{item.name}</var>}}%{mark?, mark <var%{mark.inv? data-tooltip="Match fwmarks except %{mark.num}%{mark.mask? with mask %{mark.mask}}.":%{mark.mask? data-tooltip="Mask fwmark value with %{mark.mask} before compare."}}>%{mark.val}</var>}'), {
|
||||
ipv4: (family == 'ipv4' || family == 'any' || (!family && sip.indexOf(':') == -1 && dip.indexOf(':') == -1 && rwip.indexOf(':') == -1)),
|
||||
ipv6: (family == 'ipv6' || family == 'any' || (!family && (sip.indexOf(':') != -1 || dip.indexOf(':') != -1 || rwip.indexOf(':') != -1))),
|
||||
proto: proto,
|
||||
mark: f
|
||||
});
|
||||
}
|
||||
|
||||
function rule_src_txt(s, hosts) {
|
||||
var z = uci.get('firewall', s, 'src');
|
||||
|
||||
return fwtool.fmt(_('From %{src}%{src_device?, interface <var>%{src_device}</var>}%{src_ip?, IP %{src_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{src_port?, port %{src_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}'), {
|
||||
src: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(null) }, [E('em', _('any zone'))]),
|
||||
src_ip: fwtool.map_invert(uci.get('firewall', s, 'src_ip'), 'toLowerCase'),
|
||||
src_port: fwtool.map_invert(uci.get('firewall', s, 'src_port'))
|
||||
});
|
||||
}
|
||||
|
||||
function rule_dest_txt(s) {
|
||||
var z = uci.get('firewall', s, 'src');
|
||||
|
||||
return fwtool.fmt(_('To %{dest}%{dest_device?, via interface <var>%{dest_device}</var>}%{dest_ip?, IP %{dest_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{dest_port?, port %{dest_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}'), {
|
||||
dest: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(z) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
|
||||
dest_ip: fwtool.map_invert(uci.get('firewall', s, 'dest_ip'), 'toLowerCase'),
|
||||
dest_port: fwtool.map_invert(uci.get('firewall', s, 'dest_port')),
|
||||
dest_device: uci.get('firewall', s, 'device')
|
||||
});
|
||||
}
|
||||
|
||||
function rule_limit_txt(s) {
|
||||
var m = String(uci.get('firewall', s, 'limit')).match(/^(\d+)\/([smhd])\w*$/i),
|
||||
l = m ? {
|
||||
num: +m[1],
|
||||
unit: ({ s: _('second'), m: _('minute'), h: _('hour'), d: _('day') })[m[2]],
|
||||
burst: uci.get('firewall', s, 'limit_burst')
|
||||
} : null;
|
||||
|
||||
if (!l)
|
||||
return '';
|
||||
|
||||
return fwtool.fmt(_('Limit matching to <var>%{limit.num}</var> packets per <var>%{limit.unit}</var>%{limit.burst? burst <var>%{limit.burst}</var>}'), { limit: l });
|
||||
}
|
||||
|
||||
function rule_target_txt(s) {
|
||||
var t = uci.get('firewall', s, 'target'),
|
||||
s = {
|
||||
target: t,
|
||||
snat_ip: uci.get('firewall', s, 'snat_ip'),
|
||||
snat_port: uci.get('firewall', s, 'snat_port')
|
||||
};
|
||||
|
||||
switch (t) {
|
||||
case 'SNAT':
|
||||
return fwtool.fmt(_('<var data-tooltip="SNAT">Statically rewrite</var> to source %{snat_ip?IP <var>%{snat_ip}</var>} %{snat_port?port <var>%{snat_port}</var>}'), s);
|
||||
|
||||
case 'MASQUERADE':
|
||||
return fwtool.fmt(_('<var data-tooltip="MASQUERADE">Automatically rewrite</var> source IP'));
|
||||
|
||||
case 'ACCEPT':
|
||||
return fwtool.fmt(_('<var data-tooltip="ACCEPT">Prevent source rewrite</var>'));
|
||||
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
function validate_opt_family(m, section_id, opt) {
|
||||
var sopt = m.section.getOption('src_ip'),
|
||||
dopt = m.section.getOption('dest_ip'),
|
||||
rwopt = m.section.getOption('snat_ip'),
|
||||
fmopt = m.section.getOption('family'),
|
||||
tgopt = m.section.getOption('target');
|
||||
|
||||
if (!sopt.isValid(section_id) && opt != 'src_ip')
|
||||
return true;
|
||||
if (!dopt.isValid(section_id) && opt != 'dest_ip')
|
||||
return true;
|
||||
if (!rwopt.isValid(section_id) && opt != 'snat_ip')
|
||||
return true;
|
||||
if (!fmopt.isValid(section_id) && opt != 'family')
|
||||
return true;
|
||||
if (!tgopt.isValid(section_id) && opt != 'target')
|
||||
return true;
|
||||
|
||||
var sip = sopt.formvalue(section_id) || '',
|
||||
dip = dopt.formvalue(section_id) || '',
|
||||
rwip = rwopt.formvalue(section_id) || '',
|
||||
fm = fmopt.formvalue(section_id) || '',
|
||||
tg = tgopt.formvalue(section_id);
|
||||
|
||||
if (fm == 'ipv6' && (sip.indexOf(':') != -1 || sip == '') && (dip.indexOf(':') != -1 || dip == '') && ((rwip.indexOf(':') != -1 && tg == 'SNAT') || rwip == ''))
|
||||
return true;
|
||||
if (fm == 'ipv4' && (sip.indexOf(':') == -1) && (dip.indexOf(':') == -1) && ((rwip.indexOf(':') == -1 && tg == 'SNAT') || rwip == ''))
|
||||
return true;
|
||||
if (fm == '' || fm == 'any') {
|
||||
if ((sip.indexOf(':') != -1 || sip == '') && (dip.indexOf(':') != -1 || dip == '') && ((rwip.indexOf(':') != -1 && tg == 'SNAT') || rwip == ''))
|
||||
return true;
|
||||
if ((sip.indexOf(':') == -1) && (dip.indexOf(':') == -1) && ((rwip.indexOf(':') == -1 && tg == 'SNAT') || rwip == ''))
|
||||
return true;
|
||||
}
|
||||
|
||||
return _('Address family, source address, destination address, rewrite IP address must match');
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
callHostHints: rpc.declare({
|
||||
object: 'luci-rpc',
|
||||
method: 'getHostHints',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
callNetworkDevices: rpc.declare({
|
||||
object: 'luci-rpc',
|
||||
method: 'getNetworkDevices',
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
this.callHostHints(),
|
||||
this.callNetworkDevices(),
|
||||
uci.load('firewall')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
if (fwtool.checkLegacySNAT())
|
||||
return fwtool.renderMigration();
|
||||
else
|
||||
return this.renderNats(data);
|
||||
},
|
||||
|
||||
renderNats: function(data) {
|
||||
var hosts = data[0],
|
||||
devs = data[1],
|
||||
m, s, o;
|
||||
var fw4 = L.hasSystemFeature('firewall4');
|
||||
|
||||
m = new form.Map('firewall', _('Firewall - NAT Rules'),
|
||||
_('NAT rules allow fine grained control over the source IP to use for outbound or forwarded traffic.'));
|
||||
|
||||
s = m.section(form.GridSection, 'nat', _('NAT Rules'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.sortable = true;
|
||||
s.cloneable = true;
|
||||
|
||||
s.tab('general', _('General Settings'));
|
||||
s.tab('advanced', _('Advanced Settings'));
|
||||
s.tab('timed', _('Time Restrictions'));
|
||||
|
||||
s.sectiontitle = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'name') || _('Unnamed NAT');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'name', _('Name'));
|
||||
o.placeholder = _('Unnamed NAT');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_match', _('Match'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(s) {
|
||||
return E('small', [
|
||||
rule_proto_txt(s), E('br'),
|
||||
rule_src_txt(s, hosts), E('br'),
|
||||
rule_dest_txt(s), E('br'),
|
||||
rule_limit_txt(s)
|
||||
]);
|
||||
};
|
||||
|
||||
o = s.option(form.ListValue, '_target', _('Action'));
|
||||
o.modalonly = false;
|
||||
o.textvalue = function(s) {
|
||||
return rule_target_txt(s);
|
||||
};
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.modalonly = false;
|
||||
o.default = o.enabled;
|
||||
o.editable = true;
|
||||
|
||||
if (fw4) {
|
||||
o = s.taboption('general', form.ListValue, 'family', _('Restrict to address family'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.value('any', _('IPv4 and IPv6'));
|
||||
o.value('ipv4', _('IPv4 only'));
|
||||
o.value('ipv6', _('IPv6 only'));
|
||||
o.value('', _('automatic')); // infer from zone or used IP addresses
|
||||
o.cfgvalue = function(section_id) {
|
||||
var val = this.map.data.get(this.map.config, section_id, 'family');
|
||||
|
||||
if (!val)
|
||||
return '';
|
||||
else if (val == 'any' || val == 'all' || val == '*')
|
||||
return 'any';
|
||||
else if (val == 'inet' || String(val).indexOf('4') != -1)
|
||||
return 'ipv4';
|
||||
else if (String(val).indexOf('6') != -1)
|
||||
return 'ipv6';
|
||||
};
|
||||
o.validate = function(section_id, value) {
|
||||
fwtool.updateHostHints(this.map, section_id, 'src_ip', value, hosts);
|
||||
fwtool.updateHostHints(this.map, section_id, 'dest_ip', value, hosts);
|
||||
return !fw4?true:validate_opt_family(this, section_id, 'family');
|
||||
};
|
||||
}
|
||||
|
||||
o = s.taboption('general', fwtool.CBIProtocolSelect, 'proto', _('Protocol'));
|
||||
o.modalonly = true;
|
||||
o.default = 'all';
|
||||
|
||||
o = s.taboption('general', widgets.ZoneSelect, 'src', _('Outbound zone'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.nocreate = true;
|
||||
o.allowany = true;
|
||||
o.default = 'lan';
|
||||
|
||||
o = fwtool.addIPOption(s, 'general', 'src_ip', _('Source address'),
|
||||
_('Match forwarded traffic from this IP or range.'), !fw4?'ipv4':'', hosts);
|
||||
o.rmempty = true;
|
||||
o.datatype = !fw4?'neg(ipmask4("true"))':'neg(ipmask("true"))';
|
||||
o.validate = function(section_id, value) {
|
||||
return !fw4?true:validate_opt_family(this, section_id, 'src_ip');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'src_port', _('Source port'),
|
||||
_('Match forwarded traffic originating from the given source port or port range.'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.datatype = 'neg(portrange)';
|
||||
o.placeholder = _('any');
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = fwtool.addIPOption(s, 'general', 'dest_ip', _('Destination address'),
|
||||
_('Match forwarded traffic directed at the given IP address.'), !fw4?'ipv4':'', hosts);
|
||||
o.rmempty = true;
|
||||
o.datatype = !fw4?'neg(ipmask4("true"))':'neg(ipmask("true"))';
|
||||
o.validate = function(section_id, value) {
|
||||
return !fw4?true:validate_opt_family(this, section_id, 'dest_ip');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'dest_port', _('Destination port'),
|
||||
_('Match forwarded traffic directed at the given destination port or port range.'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.placeholder = _('any');
|
||||
o.datatype = 'neg(portrange)';
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'target', _('Action'));
|
||||
o.modalonly = true;
|
||||
o.default = 'SNAT';
|
||||
o.value('SNAT', _('SNAT - Rewrite to specific source IP or port'));
|
||||
o.value('MASQUERADE', _('MASQUERADE - Automatically rewrite to outbound interface IP'));
|
||||
o.value('ACCEPT', _('ACCEPT - Disable address rewriting'));
|
||||
o.validate = function(section_id, value) {
|
||||
return !fw4?true:validate_opt_family(this, section_id, 'target');
|
||||
};
|
||||
|
||||
o = fwtool.addLocalIPOption(s, 'general', 'snat_ip', _('Rewrite IP address'),
|
||||
_('Rewrite matched traffic to the specified source IP address.'), devs);
|
||||
o.placeholder = null;
|
||||
o.depends('target', 'SNAT');
|
||||
o.validate = function(section_id, value) {
|
||||
var a = this.formvalue(section_id),
|
||||
p = this.section.formvalue(section_id, 'snat_port');
|
||||
|
||||
if ((a == null || a == '') && (p == null || p == '') && value == '')
|
||||
return _('A rewrite IP must be specified!');
|
||||
|
||||
return !fw4?true:validate_opt_family(this, section_id, 'snat_ip');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'snat_port', _('Rewrite port'),
|
||||
_('Rewrite matched traffic to the specified source port or port range.'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
o.placeholder = _('do not rewrite');
|
||||
o.datatype = 'portrange';
|
||||
o.depends({ proto: 'tcp', '!contains': true });
|
||||
o.depends({ proto: 'udp', '!contains': true });
|
||||
|
||||
var have_fw4 = L.hasSystemFeature('firewall4')
|
||||
if (!have_fw4) {
|
||||
o = s.taboption('advanced', form.Value, 'ipset', _('Use ipset'));
|
||||
uci.sections('firewall', 'ipset', function(s) {
|
||||
if (typeof(s.name) == 'string')
|
||||
o.value(s.name, s.comment ? '%s (%s)'.format(s.name, s.comment) : s.name);
|
||||
});
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
}
|
||||
|
||||
o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Outbound device'),
|
||||
_('Matches forwarded traffic using the specified outbound network device.'));
|
||||
o.noaliases = true;
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
|
||||
fwtool.addMarkOption(s, false);
|
||||
fwtool.addLimitOption(s);
|
||||
fwtool.addLimitBurstOption(s);
|
||||
|
||||
if (!have_fw4) {
|
||||
o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
|
||||
_('Passes additional arguments to iptables. Use with care!'));
|
||||
o.modalonly = true;
|
||||
o.rmempty = true;
|
||||
}
|
||||
|
||||
o = s.taboption('timed', form.MultiValue, 'weekdays', _('Week Days'));
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
o.display = 5;
|
||||
o.placeholder = _('Any day');
|
||||
o.value('Sun', _('Sunday'));
|
||||
o.value('Mon', _('Monday'));
|
||||
o.value('Tue', _('Tuesday'));
|
||||
o.value('Wed', _('Wednesday'));
|
||||
o.value('Thu', _('Thursday'));
|
||||
o.value('Fri', _('Friday'));
|
||||
o.value('Sat', _('Saturday'));
|
||||
o.write = function(section_id, value) {
|
||||
return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
|
||||
};
|
||||
|
||||
o = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days'));
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
o.display_size = 15;
|
||||
o.placeholder = _('Any day');
|
||||
o.write = function(section_id, value) {
|
||||
return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
|
||||
};
|
||||
for (var i = 1; i <= 31; i++)
|
||||
o.value(i);
|
||||
|
||||
o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh:mm:ss)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'timehhmmss';
|
||||
|
||||
o = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh:mm:ss)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'timehhmmss';
|
||||
|
||||
o = s.taboption('timed', form.Value, 'start_date', _('Start Date (yyyy-mm-dd)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'dateyyyymmdd';
|
||||
|
||||
o = s.taboption('timed', form.Value, 'stop_date', _('Stop Date (yyyy-mm-dd)'));
|
||||
o.modalonly = true;
|
||||
o.datatype = 'dateyyyymmdd';
|
||||
|
||||
o = s.taboption('timed', form.Flag, 'utc_time', _('Time in UTC'));
|
||||
o.modalonly = true;
|
||||
o.default = o.disabled;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,395 @@
|
|||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require form';
|
||||
'require network';
|
||||
'require firewall';
|
||||
'require tools.firewall as fwtool';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
return view.extend({
|
||||
callConntrackHelpers: rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'getConntrackHelpers',
|
||||
expect: { result: [] }
|
||||
}),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
this.callConntrackHelpers(),
|
||||
firewall.getDefaults()
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
if (fwtool.checkLegacySNAT())
|
||||
return fwtool.renderMigration();
|
||||
else
|
||||
return this.renderZones(data);
|
||||
},
|
||||
|
||||
renderZones: function(data) {
|
||||
var ctHelpers = data[0],
|
||||
fwDefaults = data[1],
|
||||
m, s, o, inp, out;
|
||||
var fw4 = L.hasSystemFeature('firewall4');
|
||||
|
||||
m = new form.Map('firewall', _('Firewall - Zone Settings'),
|
||||
_('The firewall creates zones over your network interfaces to control network traffic flow.'));
|
||||
|
||||
s = m.section(form.TypedSection, 'defaults', _('General Settings'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'synflood_protect', _('Enable SYN-flood protection'));
|
||||
o.cfgvalue = function(section_id) {
|
||||
var val = uci.get('firewall', section_id, 'synflood_protect');
|
||||
return (val != null) ? val : uci.get('firewall', section_id, 'syn_flood');
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
uci.unset('firewall', section_id, 'syn_flood');
|
||||
uci.set('firewall', section_id, 'synflood_protect', value);
|
||||
};
|
||||
o.remove = function(section_id) {
|
||||
uci.unset('firewall', section_id, 'syn_flood');
|
||||
uci.unset('firewall', section_id, 'synflood_protect');
|
||||
};
|
||||
|
||||
o = s.option(form.Flag, 'drop_invalid', _('Drop invalid packets'));
|
||||
|
||||
var p = [
|
||||
s.option(form.ListValue, 'input', _('Input')),
|
||||
s.option(form.ListValue, 'output', _('Output')),
|
||||
s.option(form.ListValue, 'forward', _('Forward'))
|
||||
];
|
||||
|
||||
for (var i = 0; i < p.length; i++) {
|
||||
p[i].value('REJECT', _('reject'));
|
||||
p[i].value('DROP', _('drop'));
|
||||
p[i].value('ACCEPT', _('accept'));
|
||||
}
|
||||
|
||||
/* Netfilter flow offload support */
|
||||
|
||||
if (L.hasSystemFeature('offloading')) {
|
||||
s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'),
|
||||
_('Not fully compatible with QoS/SQM.'));
|
||||
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.RichListValue, "offloading_type", _("Flow offloading type"));
|
||||
o.value('0', _("None"));
|
||||
o.value('1', _("Software flow offloading"), _('Software based offloading for routing/NAT.'));
|
||||
o.value('2', _("Hardware flow offloading"), _('Hardware based offloading for routing with/without NAT.') + ' ' + _(' Requires hardware NAT support.'));
|
||||
o.optional = false;
|
||||
o.load = function (section_id) {
|
||||
var flow_offloading = uci.get('firewall', section_id, 'flow_offloading');
|
||||
var flow_offloading_hw = uci.get('firewall', section_id, 'flow_offloading_hw');
|
||||
return (flow_offloading === '1')
|
||||
? (flow_offloading_hw === '1' ? '2' : '1')
|
||||
: '0';
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
uci.set('firewall', section_id, 'flow_offloading', value === '0' ? null : '1');
|
||||
uci.set('firewall', section_id, 'flow_offloading_hw', value === '2' ? '1' : null);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
s = m.section(form.GridSection, 'zone', _('Zones'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
s.sortable = true;
|
||||
s.nodescriptions = true;
|
||||
|
||||
s.handleRemove = function(section_id, ev) {
|
||||
return firewall.deleteZone(section_id).then(L.bind(function() {
|
||||
return this.super('handleRemove', [section_id, ev]);
|
||||
}, this));
|
||||
};
|
||||
|
||||
s.tab('general', _('General Settings'));
|
||||
s.tab('advanced', _('Advanced Settings'));
|
||||
s.tab('conntrack', _('Conntrack Settings'));
|
||||
s.tab('extra', _('Extra iptables arguments'));
|
||||
|
||||
o = s.taboption('general', form.DummyValue, '_generalinfo');
|
||||
o.rawhtml = true;
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
var name = uci.get('firewall', section_id, 'name');
|
||||
if (name == null)
|
||||
name = _("this new zone");
|
||||
return _('This section defines common properties of %q. The <em>input</em> and <em>output</em> options set the default policies for traffic entering and leaving this zone while the <em>forward</em> option describes the policy for forwarded traffic between different networks within the zone. <em>Covered networks</em> specifies which available networks are members of this zone.')
|
||||
.replace(/%s/g, name).replace(/%q/g, '"' + name + '"');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Value, 'name', _('Name'));
|
||||
o.placeholder = _('Unnamed zone');
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.datatype = 'and(uciname,maxlength(11))';
|
||||
o.write = function(section_id, formvalue) {
|
||||
var cfgvalue = this.cfgvalue(section_id);
|
||||
|
||||
if (cfgvalue == null || cfgvalue == '')
|
||||
return uci.set('firewall', section_id, 'name', formvalue);
|
||||
else if (cfgvalue != formvalue)
|
||||
return firewall.renameZone(cfgvalue, formvalue);
|
||||
};
|
||||
|
||||
o = s.option(widgets.ZoneForwards, '_info', _('Zone ⇒ Forwardings'));
|
||||
o.editable = true;
|
||||
o.modalonly = false;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'name');
|
||||
};
|
||||
|
||||
var p = [
|
||||
s.taboption('general', form.ListValue, 'input', _('Input')),
|
||||
s.taboption('general', form.ListValue, 'output', _('Output')),
|
||||
s.taboption('general', form.ListValue, 'forward', _('Intra zone forward'))
|
||||
];
|
||||
|
||||
for (var i = 0; i < p.length; i++) {
|
||||
p[i].value('REJECT', _('reject'));
|
||||
p[i].value('DROP', _('drop'));
|
||||
p[i].value('ACCEPT', _('accept'));
|
||||
p[i].editable = true;
|
||||
}
|
||||
|
||||
p[0].default = fwDefaults.getInput();
|
||||
p[1].default = fwDefaults.getOutput();
|
||||
p[2].default = fwDefaults.getForward();
|
||||
|
||||
o = s.taboption('general', form.Flag, 'masq', _('Masquerading'),
|
||||
_('Enable network address and port translation IPv4 (NAT4 or NAPT4) for outbound traffic on this zone. This is typically enabled on the <em>wan</em> zone.'));
|
||||
o.editable = true;
|
||||
o.tooltip = function(section_id) {
|
||||
var family = uci.get('firewall', section_id, 'family')
|
||||
var masq_src = uci.get('firewall', section_id, 'masq_src')
|
||||
var masq_dest = uci.get('firewall', section_id, 'masq_dest')
|
||||
if ((!family || family.indexOf('6') == -1) && (masq_src || masq_dest))
|
||||
return _('Limited masquerading enabled');
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.Flag, 'mtu_fix', _('MSS clamping'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('general', widgets.NetworkSelect, 'network', _('Covered networks'));
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'network');
|
||||
};
|
||||
o.write = function(section_id, formvalue) {
|
||||
var name = uci.get('firewall', section_id, 'name'),
|
||||
cfgvalue = this.cfgvalue(section_id),
|
||||
oldNetworks = L.toArray(cfgvalue),
|
||||
newNetworks = L.toArray(formvalue);
|
||||
|
||||
oldNetworks.sort();
|
||||
newNetworks.sort();
|
||||
|
||||
if (oldNetworks.join(' ') == newNetworks.join(' '))
|
||||
return;
|
||||
|
||||
var tasks = [ firewall.getZone(name) ];
|
||||
|
||||
if (Array.isArray(formvalue))
|
||||
for (var i = 0; i < newNetworks.length; i++) {
|
||||
var netname = newNetworks[i];
|
||||
tasks.push(network.getNetwork(netname).then(L.bind(function(netname, net) {
|
||||
return net || network.addNetwork(netname, { 'proto': 'none' });
|
||||
}, this, netname)));
|
||||
}
|
||||
|
||||
return Promise.all(tasks).then(function(zone_networks) {
|
||||
if (zone_networks[0]) {
|
||||
zone_networks[0].clearNetworks();
|
||||
for (var i = 1; i < zone_networks.length; i++)
|
||||
zone_networks[0].addNetwork(zone_networks[i].getName());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', form.DummyValue, '_advancedinfo');
|
||||
o.rawhtml = true;
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
var name = uci.get('firewall', section_id, 'name');
|
||||
if (name == null)
|
||||
name = _("this new zone");
|
||||
return _('The options below control the forwarding policies between this zone (%s) and other zones. <em>Destination zones</em> cover forwarded traffic <strong>originating from %q</strong>. <em>Source zones</em> match forwarded traffic from other zones <strong>targeted at %q</strong>. The forwarding rule is <em>unidirectional</em>, e.g. a forward from lan to wan does <em>not</em> imply a permission to forward from wan to lan as well.')
|
||||
.format(name);
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Covered devices'), _('Use this option to classify zone traffic by raw, non-<em>uci</em> managed network devices.'));
|
||||
o.modalonly = true;
|
||||
o.noaliases = true;
|
||||
o.multiple = true;
|
||||
|
||||
o = s.taboption('advanced', form.DynamicList, 'subnet', _('Covered subnets'), _('Use this option to classify zone traffic by source or destination subnet instead of networks or devices.'));
|
||||
o.datatype = 'neg(cidr("true"))';
|
||||
o.modalonly = true;
|
||||
o.multiple = true;
|
||||
|
||||
if (fw4) {
|
||||
o = s.taboption('advanced', form.Flag, 'masq6', _('IPv6 Masquerading'),
|
||||
_('Enable network address and port translation IPv6 (NAT6 or NAPT6) for outbound traffic on this zone.'));
|
||||
o.modalonly = true;
|
||||
o.tooltip = function(section_id) {
|
||||
var family = uci.get('firewall', section_id, 'family')
|
||||
var masq_src = uci.get('firewall', section_id, 'masq_src')
|
||||
var masq_dest = uci.get('firewall', section_id, 'masq_dest')
|
||||
if ((!family || family.indexOf('6') >= 0) && (masq_src || masq_dest))
|
||||
return _('Limited masquerading enabled');
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
|
||||
o.value('', _('IPv4 and IPv6'));
|
||||
o.value('ipv4', _('IPv4 only'));
|
||||
o.value('ipv6', _('IPv6 only'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
|
||||
if (fw4) {
|
||||
o.datatype = 'list(neg(or(uciname,hostname,ipmask)))';
|
||||
} else {
|
||||
o.depends('family', '');
|
||||
o.depends('family', 'ipv4');
|
||||
o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
|
||||
}
|
||||
o.placeholder = '0.0.0.0/0';
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.DynamicList, 'masq_dest', _('Restrict Masquerading to given destination subnets'));
|
||||
if (fw4) {
|
||||
o.datatype = 'list(neg(or(uciname,hostname,ipmask)))';
|
||||
} else {
|
||||
o.depends('family', '');
|
||||
o.depends('family', 'ipv4');
|
||||
o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
|
||||
}
|
||||
o.placeholder = '0.0.0.0/0';
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('conntrack', form.Flag, 'masq_allow_invalid', _('Allow "invalid" traffic'), _('Do not install extra rules to reject forwarded traffic with conntrack state <em>invalid</em>. This may be required for complex asymmetric route setups.'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('conntrack', form.Flag, 'auto_helper', _('Automatic helper assignment'), _('Automatically assign conntrack helpers based on traffic protocol and port'));
|
||||
o.default = o.enabled;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('conntrack', form.MultiValue, 'helper', _('Conntrack helpers'), _('Explicitly choses allowed connection tracking helpers for zone traffic'));
|
||||
o.depends('auto_helper', '0');
|
||||
o.modalonly = true;
|
||||
for (var i = 0; i < ctHelpers.length; i++)
|
||||
o.value(ctHelpers[i].name, E('<span><span class="hide-close">%s (%s)</span><span class="hide-open">%s</span></span>'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase(), ctHelpers[i].name.toUpperCase())));
|
||||
|
||||
o = s.taboption('advanced', form.Flag, 'log', _('Enable logging on this zone'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'log_limit', _('Limit log messages'));
|
||||
o.depends('log', '1');
|
||||
o.placeholder = '10/minute';
|
||||
o.modalonly = true;
|
||||
|
||||
if (!L.hasSystemFeature('firewall4')) {
|
||||
o = s.taboption('extra', form.DummyValue, '_extrainfo');
|
||||
o.rawhtml = true;
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return _('Passing raw iptables arguments to source and destination traffic classification rules allows to match packets based on other criteria than interfaces or subnets. These options should be used with extreme care as invalid values could render the firewall ruleset broken, completely exposing all services.');
|
||||
};
|
||||
|
||||
o = s.taboption('extra', form.Value, 'extra_src', _('Extra source arguments'), _('Additional raw <em>iptables</em> arguments to classify zone source traffic, e.g. <code>-p tcp --sport 443</code> to only match inbound HTTPS traffic.'));
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
uci.unset('firewall', section_id, 'extra');
|
||||
uci.set('firewall', section_id, 'extra_src', value);
|
||||
};
|
||||
|
||||
o = s.taboption('extra', form.Value, 'extra_dest', _('Extra destination arguments'), _('Additional raw <em>iptables</em> arguments to classify zone destination traffic, e.g. <code>-p tcp --dport 443</code> to only match outbound HTTPS traffic.'));
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('firewall', section_id, 'extra_dest') || uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
uci.unset('firewall', section_id, 'extra');
|
||||
uci.set('firewall', section_id, 'extra_dest', value);
|
||||
};
|
||||
}
|
||||
|
||||
o = s.taboption('general', form.DummyValue, '_forwardinfo');
|
||||
o.rawhtml = true;
|
||||
o.modalonly = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
var name = uci.get('firewall', section_id, 'name');
|
||||
if (name == null)
|
||||
name = _("this new zone");
|
||||
return _('The options below control the forwarding policies between this zone (%s) and other zones. <em>Destination zones</em> cover forwarded traffic <strong>originating from %q</strong>. <em>Source zones</em> match forwarded traffic from other zones <strong>targeted at %q</strong>. The forwarding rule is <em>unidirectional</em>, e.g. a forward from lan to wan does <em>not</em> imply a permission to forward from wan to lan as well.')
|
||||
.format(name);
|
||||
};
|
||||
|
||||
out = o = s.taboption('general', widgets.ZoneSelect, 'out', _('Allow forward to <em>destination zones</em>:'));
|
||||
o.nocreate = true;
|
||||
o.multiple = true;
|
||||
o.modalonly = true;
|
||||
o.filter = function(section_id, value) {
|
||||
return (uci.get('firewall', section_id, 'name') != value);
|
||||
};
|
||||
o.cfgvalue = function(section_id) {
|
||||
var out = (this.option == 'out'),
|
||||
zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
|
||||
fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [],
|
||||
value = [];
|
||||
|
||||
for (var i = 0; i < fwds.length; i++)
|
||||
value.push(out ? fwds[i].getDestination() : fwds[i].getSource());
|
||||
|
||||
return value;
|
||||
};
|
||||
o.write = o.remove = function(section_id, formvalue) {
|
||||
var out = (this.option == 'out'),
|
||||
zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
|
||||
fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [];
|
||||
|
||||
if (formvalue == null)
|
||||
formvalue = [];
|
||||
|
||||
if (Array.isArray(formvalue)) {
|
||||
for (var i = 0; i < fwds.length; i++) {
|
||||
var cmp = out ? fwds[i].getDestination() : fwds[i].getSource();
|
||||
if (!formvalue.filter(function(d) { return d == cmp }).length)
|
||||
zone.deleteForwarding(fwds[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < formvalue.length; i++)
|
||||
if (out)
|
||||
zone.addForwardingTo(formvalue[i]);
|
||||
else
|
||||
zone.addForwardingFrom(formvalue[i]);
|
||||
}
|
||||
};
|
||||
|
||||
inp = o = s.taboption('general', widgets.ZoneSelect, 'in', _('Allow forward from <em>source zones</em>:'));
|
||||
o.nocreate = true;
|
||||
o.multiple = true;
|
||||
o.modalonly = true;
|
||||
o.write = o.remove = out.write;
|
||||
o.filter = out.filter;
|
||||
o.cfgvalue = out.cfgvalue;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
1589
luci-app-firewall/po/ar/firewall.po
Normal file
1589
luci-app-firewall/po/ar/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1445
luci-app-firewall/po/bg/firewall.po
Normal file
1445
luci-app-firewall/po/bg/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1431
luci-app-firewall/po/bn_BD/firewall.po
Normal file
1431
luci-app-firewall/po/bn_BD/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1461
luci-app-firewall/po/ca/firewall.po
Normal file
1461
luci-app-firewall/po/ca/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1488
luci-app-firewall/po/cs/firewall.po
Normal file
1488
luci-app-firewall/po/cs/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1651
luci-app-firewall/po/da/firewall.po
Normal file
1651
luci-app-firewall/po/da/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1679
luci-app-firewall/po/de/firewall.po
Normal file
1679
luci-app-firewall/po/de/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1444
luci-app-firewall/po/el/firewall.po
Normal file
1444
luci-app-firewall/po/el/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1677
luci-app-firewall/po/es/firewall.po
Normal file
1677
luci-app-firewall/po/es/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1633
luci-app-firewall/po/fa/firewall.po
Normal file
1633
luci-app-firewall/po/fa/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1610
luci-app-firewall/po/fi/firewall.po
Normal file
1610
luci-app-firewall/po/fi/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1676
luci-app-firewall/po/fr/firewall.po
Normal file
1676
luci-app-firewall/po/fr/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1623
luci-app-firewall/po/ga/firewall.po
Normal file
1623
luci-app-firewall/po/ga/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1429
luci-app-firewall/po/he/firewall.po
Normal file
1429
luci-app-firewall/po/he/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1431
luci-app-firewall/po/hi/firewall.po
Normal file
1431
luci-app-firewall/po/hi/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1642
luci-app-firewall/po/hu/firewall.po
Normal file
1642
luci-app-firewall/po/hu/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1474
luci-app-firewall/po/id/firewall.po
Normal file
1474
luci-app-firewall/po/id/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1632
luci-app-firewall/po/it/firewall.po
Normal file
1632
luci-app-firewall/po/it/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1611
luci-app-firewall/po/ja/firewall.po
Normal file
1611
luci-app-firewall/po/ja/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1460
luci-app-firewall/po/ka/firewall.po
Normal file
1460
luci-app-firewall/po/ka/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1452
luci-app-firewall/po/ko/firewall.po
Normal file
1452
luci-app-firewall/po/ko/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1625
luci-app-firewall/po/lt/firewall.po
Normal file
1625
luci-app-firewall/po/lt/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1434
luci-app-firewall/po/mr/firewall.po
Normal file
1434
luci-app-firewall/po/mr/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1429
luci-app-firewall/po/ms/firewall.po
Normal file
1429
luci-app-firewall/po/ms/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1456
luci-app-firewall/po/nb_NO/firewall.po
Normal file
1456
luci-app-firewall/po/nb_NO/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1629
luci-app-firewall/po/nl/firewall.po
Normal file
1629
luci-app-firewall/po/nl/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1663
luci-app-firewall/po/pl/firewall.po
Normal file
1663
luci-app-firewall/po/pl/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1680
luci-app-firewall/po/pt/firewall.po
Normal file
1680
luci-app-firewall/po/pt/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1675
luci-app-firewall/po/pt_BR/firewall.po
Normal file
1675
luci-app-firewall/po/pt_BR/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1673
luci-app-firewall/po/ro/firewall.po
Normal file
1673
luci-app-firewall/po/ro/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1678
luci-app-firewall/po/ru/firewall.po
Normal file
1678
luci-app-firewall/po/ru/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1431
luci-app-firewall/po/si/firewall.po
Normal file
1431
luci-app-firewall/po/si/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1472
luci-app-firewall/po/sk/firewall.po
Normal file
1472
luci-app-firewall/po/sk/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1431
luci-app-firewall/po/sr/firewall.po
Normal file
1431
luci-app-firewall/po/sr/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1487
luci-app-firewall/po/sv/firewall.po
Normal file
1487
luci-app-firewall/po/sv/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1418
luci-app-firewall/po/templates/firewall.pot
Normal file
1418
luci-app-firewall/po/templates/firewall.pot
Normal file
File diff suppressed because it is too large
Load diff
1661
luci-app-firewall/po/tr/firewall.po
Normal file
1661
luci-app-firewall/po/tr/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1631
luci-app-firewall/po/uk/firewall.po
Normal file
1631
luci-app-firewall/po/uk/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1430
luci-app-firewall/po/ur/firewall.po
Normal file
1430
luci-app-firewall/po/ur/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1594
luci-app-firewall/po/vi/firewall.po
Normal file
1594
luci-app-firewall/po/vi/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1430
luci-app-firewall/po/yua/firewall.po
Normal file
1430
luci-app-firewall/po/yua/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1583
luci-app-firewall/po/zh_Hans/firewall.po
Normal file
1583
luci-app-firewall/po/zh_Hans/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
1826
luci-app-firewall/po/zh_Hant/firewall.po
Normal file
1826
luci-app-firewall/po/zh_Hant/firewall.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"admin/network/firewall": {
|
||||
"title": "Firewall",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/network/firewall/zones"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-firewall" ],
|
||||
"fs": { "/sbin/fw3": "executable" },
|
||||
"uci": { "firewall": true }
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/firewall/zones": {
|
||||
"title": "General Settings",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "firewall/zones"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/firewall/forwards": {
|
||||
"title": "Port Forwards",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "firewall/forwards"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/firewall/rules": {
|
||||
"title": "Traffic Rules",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "firewall/rules"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/firewall/snats": {
|
||||
"title": "NAT Rules",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "firewall/snats"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/firewall/ipsets": {
|
||||
"title": "IP Sets",
|
||||
"order": 45,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "firewall/ipsets"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/firewall/custom": {
|
||||
"title": "Custom Rules",
|
||||
"order": 50,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "firewall/custom"
|
||||
},
|
||||
"depends": {
|
||||
"fs": { "/usr/share/fw3/helpers.conf": "file" }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"luci-app-firewall": {
|
||||
"description": "Grant access to firewall configuration",
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/firewall.user": [ "read" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "read" ],
|
||||
"luci": [ "getConntrackHelpers" ]
|
||||
},
|
||||
"uci": [ "firewall" ]
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/firewall.user": [ "write" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "write" ]
|
||||
},
|
||||
"uci": [ "firewall" ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"config": "firewall",
|
||||
"init": "firewall",
|
||||
"affects": [
|
||||
"luci-splash",
|
||||
"qos",
|
||||
"miniupnpd"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue