summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-firewall/htdocs/luci-static/resources
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-firewall/htdocs/luci-static/resources')
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js81
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js2
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js98
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/ipsets.js219
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js84
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/snats.js130
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js157
7 files changed, 647 insertions, 124 deletions
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js b/applications/luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js
index 32998c2ff8..f9d7dc611c 100644
--- a/applications/luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js
@@ -6,6 +6,7 @@
'require form';
'require network';
'require firewall';
+'require validation';
'require tools.prng as random';
var protocols = [
@@ -392,12 +393,25 @@ return baseclass.extend({
},
transformHostHints: function(family, hosts) {
- var choice_values = [], choice_labels = {};
+ 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(hosts, 'ipv4', 'addr').forEach(function(mac) {
- var val = hosts[mac].ipv4,
- txt = hosts[mac].name || mac;
+ 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]), ')' ]);
@@ -405,9 +419,9 @@ return baseclass.extend({
}
if (!family || family == 'ipv6') {
- L.sortedKeys(hosts, 'ipv6', 'addr').forEach(function(mac) {
- var val = hosts[mac].ipv6,
- txt = hosts[mac].name || mac;
+ 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]), ')' ]);
@@ -425,11 +439,27 @@ return baseclass.extend({
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 ? form.DynamicList : form.Value, name, label, description);
+ var o = s.taboption(tab, multiple ? this.CBIDynamicMultiValueList : form.Value, name, label, description);
+ var fw4 = L.hasSystemFeature('firewall4');
o.modalonly = true;
- o.datatype = 'list(neg(ipmask))';
+ 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) {
@@ -449,18 +479,20 @@ return baseclass.extend({
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 = 'ip4addr("nomask")';
+ 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) || !Array.isArray(ip4addrs) || devices[dev].flags.loopback)
+ if (!L.isObject(devices[dev].flags) || devices[dev].flags.loopback)
return;
- for (var i = 0; i < ip4addrs.length; i++) {
+ for (var i = 0; Array.isArray(ip4addrs) && i < ip4addrs.length; i++) {
if (!L.isObject(ip4addrs[i]) || !ip4addrs[i].address)
continue;
@@ -468,13 +500,21 @@ return baseclass.extend({
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, form.DynamicList, name, label, description);
+ var o = s.taboption(tab, this.CBIDynamicMultiValueList, name, label, description);
o.modalonly = true;
o.datatype = 'list(macaddr)';
@@ -482,7 +522,10 @@ return baseclass.extend({
L.sortedKeys(hosts).forEach(function(mac) {
o.value(mac, E([], [ mac, ' (', E('strong', {}, [
- hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?'
+ hosts[mac].name ||
+ L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0] ||
+ L.toArray(hosts[mac].ip6addrs || hosts[mac].ipv6)[0] ||
+ '?'
]), ')' ]));
});
@@ -522,6 +565,9 @@ return baseclass.extend({
}
}, this));
+ if (cfgvalue == '*' || cfgvalue == 'any' || cfgvalue == 'all')
+ cfgvalue = 'all';
+
return cfgvalue;
},
@@ -556,8 +602,7 @@ return baseclass.extend({
});
widget.createChoiceElement = function(sb, value) {
- var m = value.match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/),
- p = lookupProto(lookupProto(m ? +m[1] : value)[0]);
+ var p = lookupProto(value);
return ui.Dropdown.prototype.createChoiceElement.call(this, sb, p[2], p[1]);
};
@@ -567,9 +612,11 @@ return baseclass.extend({
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] : value;
+ return (p[0] > -1) ? p[2] : p[1];
});
+ values.sort();
+
return ui.Dropdown.prototype.createItems.call(this, sb, values.join(' '));
};
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
index ca870552a6..1997a720c6 100644
--- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
@@ -24,7 +24,7 @@ return view.extend({
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': 10 }, [ fwuser != null ? fwuser : '' ]))
+ E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 25 }, [ fwuser != null ? fwuser : '' ]))
]);
},
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js
index 82703c320e..34ab804257 100644
--- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js
@@ -9,6 +9,8 @@
'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) {
@@ -20,7 +22,7 @@ function rule_proto_txt(s, ctHelpers) {
};
});
- m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
+ var m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
var h = m ? {
val: m[0].toUpperCase(),
inv: m[1],
@@ -35,7 +37,9 @@ function rule_proto_txt(s, ctHelpers) {
mask: m[3] ? '0x%02X'.format(+m[3]) : null
} : null;
- return fwtool.fmt(_('Incoming IPv4%{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 &quot;%{helper.name}&quot;">%{helper.val}</var>:<var data-tooltip="%{helper.name}">%{helper.val}</var>}}'), {
+ 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 &quot;%{helper.name}&quot;">%{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
@@ -46,7 +50,7 @@ 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': 'background-color:' + fwmodel.getColorForName((z && z != '*') ? z : null) }, [(z == '*') ? E('em', _('any zone')) : (z || E('em', _('this device')))]),
+ 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'))
@@ -55,7 +59,7 @@ function rule_src_txt(s, hosts) {
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': 'background-color:' + fwmodel.getColorForName(null) }, [E('em', _('this device'))]),
+ 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'))
});
@@ -79,12 +83,30 @@ 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('em', _('this device')))]),
+ 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',
@@ -125,6 +147,7 @@ return view.extend({
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.'));
@@ -133,6 +156,7 @@ return view.extend({
s.addremove = true;
s.anonymous = true;
s.sortable = true;
+ s.cloneable = true;
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
@@ -149,9 +173,10 @@ return view.extend({
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');
- this.addedSection = section_id;
+ m.addedSection = section_id;
this.renderMoreOptionsModal(section_id);
};
@@ -159,6 +184,32 @@ return view.extend({
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) {
@@ -193,15 +244,23 @@ return view.extend({
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.'), 'ipv4', hosts);
+ _('Only match incoming traffic from this IP or range.'), !fw4?'ipv4':'', hosts);
o.rmempty = true;
- o.datatype = 'neg(ipmask4)';
+ 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'));
@@ -214,7 +273,7 @@ return view.extend({
o = fwtool.addLocalIPOption(s, 'advanced', 'src_dip', _('External IP address'),
_('Only match incoming traffic directed at the given IP address.'), devs);
- o.datatype = 'neg(ipmask4)';
+ o.datatype = !fw4?'neg(ipmask4("true"))':'neg(ipmask("true"))';
o.rmempty = true;
o = s.taboption('general', form.Value, 'src_dport', _('External port'),
@@ -229,12 +288,11 @@ return view.extend({
o.modalonly = true;
o.rmempty = true;
o.nocreate = true;
- o.default = 'lan';
o = fwtool.addIPOption(s, 'general', 'dest_ip', _('Internal IP address'),
- _('Redirect matched incoming traffic to the specified internal host'), 'ipv4', hosts);
+ _('Redirect matched incoming traffic to the specified internal host'), !fw4?'ipv4':'', hosts);
o.rmempty = true;
- o.datatype = 'ipmask4';
+ 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'));
@@ -259,6 +317,12 @@ return view.extend({
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');
@@ -281,10 +345,12 @@ return view.extend({
fwtool.addLimitOption(s);
fwtool.addLimitBurstOption(s);
- o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
- _('Passes additional arguments to iptables. Use with care!'));
- o.modalonly = true;
- o.rmempty = true;
+ 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();
}
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/ipsets.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/ipsets.js
new file mode 100644
index 0000000000..f7813a85a4
--- /dev/null
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/ipsets.js
@@ -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) {
+ var 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();
+ }
+});
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js
index bacbbd7044..25c7568a9a 100644
--- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js
@@ -22,7 +22,7 @@ function rule_proto_txt(s, ctHelpers) {
};
});
- m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
+ var m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
var h = m ? {
val: m[0].toUpperCase(),
inv: m[1],
@@ -62,7 +62,7 @@ function rule_src_txt(s, hosts) {
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': 'background-color:' + fwmodel.getColorForName((z && z != '*') ? z : null) }, [(z == '*') ? E('em', _('any zone')) : (z || E('em', _('this device')))]),
+ 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')),
@@ -75,7 +75,7 @@ function rule_dest_txt(s) {
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': 'background-color:' + fwmodel.getColorForName((z && z != '*') ? z : null) }, [(z == '*') ? E('em', _('any zone')) : (z || E('em', _('this device')))]),
+ 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
@@ -171,12 +171,13 @@ return view.extend({
m, s, o;
m = new form.Map('firewall', _('Firewall - Traffic Rules'),
- _('Traffic rules define policies for packets traveling between different zones, for example to reject traffic between certain hosts or to open WAN ports on the router.'));
+ _('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'));
@@ -193,13 +194,8 @@ return view.extend({
s.handleAdd = function(ev) {
var config_name = this.uciconfig || this.map.config,
section_id = uci.add(config_name, this.sectiontype),
- opt1, opt2;
-
- for (var i = 0; i < this.children.length; i++)
- if (this.children[i].option == 'src')
- opt1 = this.children[i];
- else if (this.children[i].option == 'dest')
- opt2 = this.children[i];
+ opt1 = this.getOption('src'),
+ opt2 = this.getOption('dest');
opt1.default = 'wan';
opt2.default = 'lan';
@@ -236,7 +232,19 @@ return view.extend({
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;
@@ -287,27 +295,52 @@ return view.extend({
o.multiple = true;
o.custom = true;
o.cast = 'table';
- o.placeholder = _('any');
- o.value('', 'any');
+ 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');
@@ -316,7 +349,9 @@ return view.extend({
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');
@@ -328,6 +363,9 @@ return view.extend({
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 });
@@ -337,6 +375,14 @@ return view.extend({
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);
@@ -420,9 +466,11 @@ return view.extend({
fwtool.addLimitOption(s);
fwtool.addLimitBurstOption(s);
- o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
- _('Passes additional arguments to iptables. Use with care!'));
- o.modalonly = true;
+ 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;
@@ -451,11 +499,11 @@ return view.extend({
for (var i = 1; i <= 31; i++)
o.value(i);
- o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh.mm.ss)'));
+ 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 = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh:mm:ss)'));
o.modalonly = true;
o.datatype = 'timehhmmss';
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/snats.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/snats.js
index 859dba3e88..a700475e49 100644
--- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/snats.js
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/snats.js
@@ -9,6 +9,10 @@
'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) {
@@ -19,7 +23,7 @@ function rule_proto_txt(s) {
};
});
- 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 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],
@@ -27,7 +31,9 @@ function rule_proto_txt(s) {
mask: m[3] ? '0x%02X'.format(+m[3]) : null
} : null;
- return fwtool.fmt(_('Forwarded IPv4%{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>}'), {
+ 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
});
@@ -37,7 +43,7 @@ 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': 'background-color:' + fwmodel.getColorForName(null) }, [E('em', _('any zone'))]),
+ 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'))
});
@@ -47,7 +53,7 @@ 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': 'background-color:' + fwmodel.getColorForName((z && z != '*') ? z : null) }, [(z == '*') ? E('em', _('any zone')) : (z || E('em', _('this device')))]),
+ 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')
@@ -91,6 +97,44 @@ function rule_target_txt(s) {
}
}
+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',
@@ -123,6 +167,7 @@ return view.extend({
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.'));
@@ -131,6 +176,7 @@ return view.extend({
s.addremove = true;
s.anonymous = true;
s.sortable = true;
+ s.cloneable = true;
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
@@ -166,6 +212,33 @@ return view.extend({
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';
@@ -178,9 +251,12 @@ return view.extend({
o.default = 'lan';
o = fwtool.addIPOption(s, 'general', 'src_ip', _('Source address'),
- _('Match forwarded traffic from this IP or range.'), 'ipv4', hosts);
+ _('Match forwarded traffic from this IP or range.'), !fw4?'ipv4':'', hosts);
o.rmempty = true;
- o.datatype = 'neg(ipmask4)';
+ 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.'));
@@ -192,9 +268,12 @@ return view.extend({
o.depends({ proto: 'udp', '!contains': true });
o = fwtool.addIPOption(s, 'general', 'dest_ip', _('Destination address'),
- _('Match forwarded traffic directed at the given IP address.'), 'ipv4', hosts);
+ _('Match forwarded traffic directed at the given IP address.'), !fw4?'ipv4':'', hosts);
o.rmempty = true;
- o.datatype = 'neg(ipmask4)';
+ 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.'));
@@ -211,20 +290,22 @@ return view.extend({
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 port = this.map.lookupOption('snat_port', section_id),
- a = this.formvalue(section_id),
- p = port ? port[0].formvalue(section_id) : null;
+ 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 true;
+ return !fw4?true:validate_opt_family(this, section_id, 'snat_ip');
};
o = s.taboption('general', form.Value, 'snat_port', _('Rewrite port'),
@@ -236,6 +317,17 @@ return view.extend({
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;
@@ -246,10 +338,12 @@ return view.extend({
fwtool.addLimitOption(s);
fwtool.addLimitBurstOption(s);
- o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
- _('Passes additional arguments to iptables. Use with care!'));
- o.modalonly = true;
- o.rmempty = true;
+ 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;
@@ -278,11 +372,11 @@ return view.extend({
for (var i = 1; i <= 31; i++)
o.value(i);
- o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh.mm.ss)'));
+ 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 = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh:mm:ss)'));
o.modalonly = true;
o.datatype = 'timehhmmss';
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
index b24521e1ee..bf2d6cf3c7 100644
--- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
+++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
@@ -33,6 +33,7 @@ return view.extend({
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.'));
@@ -73,21 +74,27 @@ return view.extend({
if (L.hasSystemFeature('offloading')) {
s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'),
- _('Experimental feature. Not fully compatible with QoS/SQM.'));
+ _('Not fully compatible with QoS/SQM.'));
s.anonymous = true;
s.addremove = false;
- o = s.option(form.Flag, 'flow_offloading',
- _('Software flow offloading'),
- _('Software based offloading for routing/NAT'));
- o.optional = true;
-
- o = s.option(form.Flag, 'flow_offloading_hw',
- _('Hardware flow offloading'),
- _('Requires hardware NAT support. Implemented at least for mt7621'));
- o.optional = true;
- o.depends('flow_offloading', '1');
+ 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);
+ };
}
@@ -95,6 +102,7 @@ return view.extend({
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() {
@@ -142,7 +150,7 @@ return view.extend({
var p = [
s.taboption('general', form.ListValue, 'input', _('Input')),
s.taboption('general', form.ListValue, 'output', _('Output')),
- s.taboption('general', form.ListValue, 'forward', _('Forward'))
+ s.taboption('general', form.ListValue, 'forward', _('Intra zone forward'))
];
for (var i = 0; i < p.length; i++) {
@@ -156,8 +164,18 @@ return view.extend({
p[1].default = fwDefaults.getOutput();
p[2].default = fwDefaults.getForward();
- o = s.taboption('general', form.Flag, 'masq', _('Masquerading'));
+ 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;
@@ -170,25 +188,32 @@ return view.extend({
};
o.write = function(section_id, formvalue) {
var name = uci.get('firewall', section_id, 'name'),
- cfgvalue = this.cfgvalue(section_id);
+ cfgvalue = this.cfgvalue(section_id),
+ oldNetworks = L.toArray(cfgvalue),
+ newNetworks = L.toArray(formvalue);
- if (typeof(cfgvalue) == 'string' && Array.isArray(formvalue) && (cfgvalue == formvalue.join(' ')))
+ oldNetworks.sort();
+ newNetworks.sort();
+
+ if (oldNetworks.join(' ') == newNetworks.join(' '))
return;
var tasks = [ firewall.getZone(name) ];
if (Array.isArray(formvalue))
- for (var i = 0; i < formvalue.length; i++) {
- var netname = formvalue[i];
- tasks.push(network.getNetwork(netname).then(function(net) {
+ 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])
+ 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());
+ }
});
};
@@ -209,10 +234,24 @@ return view.extend({
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)';
+ 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'));
@@ -220,16 +259,24 @@ return view.extend({
o.modalonly = true;
o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
- o.depends('family', '');
- o.depends('family', 'ipv4');
- o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
+ 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'));
- o.depends('family', '');
- o.depends('family', 'ipv4');
- o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
+ 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;
@@ -244,7 +291,7 @@ return view.extend({
o.depends('auto_helper', '0');
o.modalonly = true;
for (var i = 0; i < ctHelpers.length; i++)
- o.value(ctHelpers[i].name, '<span class="hide-close">%s (%s)</span><span class="hide-open">%s</span>'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase(), ctHelpers[i].name.toUpperCase()));
+ 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;
@@ -254,32 +301,34 @@ return view.extend({
o.placeholder = '10/minute';
o.modalonly = true;
- 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);
- };
+ 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;