diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-06-13 15:23:26 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-07-07 15:36:26 +0200 |
commit | 9c16090780b7430ffe88c770732604f6eb6cfda6 (patch) | |
tree | 92be7e7eaf1995b08995ee817670e5f5e03265e6 /applications/luci-app-firewall/htdocs/luci-static/resources/view | |
parent | a13dba8071ce828ef75a30357f2a49bac6071c9a (diff) |
luci-app-firewall: switch to client side CBI views
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'applications/luci-app-firewall/htdocs/luci-static/resources/view')
3 files changed, 940 insertions, 0 deletions
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 new file mode 100644 index 0000000000..743d115e88 --- /dev/null +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js @@ -0,0 +1,290 @@ +'use strict'; +'require ui'; +'require rpc'; +'require uci'; +'require form'; +'require tools.firewall as fwtool'; +'require tools.widgets as widgets'; + +function skeys(obj, key, mode) { + if (obj == null || typeof(obj) != 'object') + return []; + + return Object.keys(obj).map(function(e) { + var v = (key != null) ? obj[e][key] : e; + + switch (mode) { + case 'addr': + v = (v != null) ? v.replace(/(?:^|[.:])([0-9a-fA-F]{1,4})/g, + function(m0, m1) { return ('000' + m1.toLowerCase()).substr(-4) }) : null; + break; + + case 'num': + v = (v != null) ? +v : null; + break; + } + + return [ e, v ]; + }).filter(function(e) { + return (e[1] != null); + }).sort(function(a, b) { + return (a[1] > b[1]); + }).map(function(e) { + return e[0]; + }); +} + +function fmt(fmt /*, ...*/) { + var repl = [], wrap = false; + + for (var i = 1; i < arguments.length; i++) { + if (L.dom.elem(arguments[i])) { + switch (arguments[i].nodeType) { + case 1: + repl.push(arguments[i].outerHTML); + wrap = true; + break; + + case 3: + repl.push(arguments[i].data); + break; + + case 11: + var span = E('span'); + span.appendChild(arguments[i]); + repl.push(span.innerHTML); + wrap = true; + break; + + default: + repl.push(''); + } + } + else { + repl.push(arguments[i]); + } + } + + var rv = fmt.format.apply(fmt, repl); + return wrap ? E('span', rv) : rv; +} + +function forward_proto_txt(s) { + return fmt('%s-%s', _('IPv4'), + fwtool.fmt_proto(uci.get('firewall', s, 'proto'), + uci.get('firewall', s, 'icmp_type')) || 'TCP+UDP'); +} + +function forward_src_txt(s) { + var z = fwtool.fmt_zone(uci.get('firewall', s, 'src'), _('any zone')), + a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any host')), + p = fwtool.fmt_port(uci.get('firewall', s, 'src_port')), + m = fwtool.fmt_mac(uci.get('firewall', s, 'src_mac')); + + if (p && m) + return fmt(_('From %s in %s with source %s and %s'), a, z, p, m); + else if (p || m) + return fmt(_('From %s in %s with source %s'), a, z, p || m); + else + return fmt(_('From %s in %s'), a, z); +} + +function forward_via_txt(s) { + var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_dip'), _('any router IP')), + p = fwtool.fmt_port(uci.get('firewall', s, 'src_dport')); + + if (p) + return fmt(_('Via %s at %s'), a, p); + else + return fmt(_('Via %s'), a); +} + +return L.view.extend({ + callHostHints: rpc.declare({ + object: 'luci', + method: 'host_hints' + }), + + load: function() { + return Promise.all([ + this.callHostHints() + ]); + }, + + render: function(data) { + var hosts = data[0], + m, s, o; + + 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.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'); + }; + + o = s.taboption('general', form.Value, 'name', _('Name')); + o.placeholder = _('Unnamed forward'); + o.modalonly = true; + + o = s.option(form.DummyValue, '_match', _('Match')); + o.modalonly = false; + o.textvalue = function(s) { + return E('small', [ + forward_proto_txt(s), E('br'), + forward_src_txt(s), E('br'), + forward_via_txt(s) + ]); + }; + + o = s.option(form.ListValue, '_dest', _('Forward to')); + o.modalonly = false; + o.textvalue = function(s) { + var z = fwtool.fmt_zone(uci.get('firewall', s, 'dest'), _('any zone')), + a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any host')), + p = fwtool.fmt_port(uci.get('firewall', s, 'dest_port')) || + fwtool.fmt_port(uci.get('firewall', s, 'src_dport')); + + if (p) + return fmt(_('%s, %s in %s'), a, p, z); + else + return fmt(_('%s in %s'), a, z); + }; + + o = s.option(form.Flag, 'enabled', _('Enable')); + o.modalonly = false; + o.default = o.enabled; + o.editable = true; + + o = s.taboption('general', form.Value, 'proto', _('Protocol')); + o.modalonly = true; + o.default = 'tcp udp'; + o.value('tcp udp', 'TCP+UDP'); + o.value('tcp', 'TCP'); + o.value('udp', 'UDP'); + o.value('icmp', 'ICMP'); + + o.cfgvalue = function(/* ... */) { + var v = this.super('cfgvalue', arguments); + return (v == 'tcpudp') ? 'tcp udp' : v; + }; + + 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, 'src_mac', _('Source MAC address'), + _('Only match incoming traffic from these MACs.')); + o.modalonly = true; + o.rmempty = true; + o.datatype = 'neg(macaddr)'; + o.placeholder = E('em', _('any')); + skeys(hosts).forEach(function(mac) { + o.value(mac, '%s (%s)'.format( + mac, + hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?' + )); + }); + + o = s.taboption('advanced', form.Value, 'src_ip', _('Source IP address'), + _('Only match incoming traffic from this IP or range.')); + o.modalonly = true; + o.rmempty = true; + o.datatype = 'neg(ipmask4)'; + o.placeholder = E('em', _('any')); + skeys(hosts, 'ipv4', 'addr').forEach(function(mac) { + o.value(hosts[mac].ipv4, '%s (%s)'.format( + hosts[mac].ipv4, + hosts[mac].name || mac + )); + }); + + 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'); + o.depends('proto', 'udp'); + o.depends('proto', 'tcp udp'); + o.depends('proto', 'tcpudp'); + + o = s.taboption('advanced', form.Value, 'src_dip', _('External IP address'), + _('Only match incoming traffic directed at the given IP address.')); + o.modalonly = true; + o.rmempty = true; + o.datatype = 'neg(ipmask4)'; + o.placeholder = E('em', _('any')); + skeys(hosts, 'ipv4', 'addr').forEach(function(mac) { + o.value(hosts[mac].ipv4, '%s (%s)'.format( + hosts[mac].ipv4, + hosts[mac].name || mac + )); + }); + + 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'); + o.depends('proto', 'udp'); + o.depends('proto', 'tcp udp'); + o.depends('proto', 'tcpudp'); + + o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Internal zone')); + o.modalonly = true; + o.rmempty = true; + o.nocreate = true; + o.default = 'lan'; + + o = s.taboption('general', form.Value, 'dest_ip', _('Internal IP address'), + _('Redirect matched incoming traffic to the specified internal host')); + o.modalonly = true; + o.rmempty = true; + o.datatype = 'ipmask4'; + skeys(hosts, 'ipv4', 'addr').forEach(function(mac) { + o.value(hosts[mac].ipv4, '%s (%s)'.format( + hosts[mac].ipv4, + hosts[mac].name || mac + )); + }); + + 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'); + o.depends('proto', 'udp'); + o.depends('proto', 'tcp udp'); + o.depends('proto', 'tcpudp'); + + 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.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/rules.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js new file mode 100644 index 0000000000..9fa1aa252d --- /dev/null +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js @@ -0,0 +1,401 @@ +'use strict'; +'require ui'; +'require rpc'; +'require uci'; +'require form'; +'require tools.firewall as fwtool'; +'require tools.widgets as widgets'; + +function skeys(obj, key, mode) { + if (obj == null || typeof(obj) != 'object') + return []; + + return Object.keys(obj).map(function(e) { + var v = (key != null) ? obj[e][key] : e; + + switch (mode) { + case 'addr': + v = (v != null) ? v.replace(/(?:^|[.:])([0-9a-fA-F]{1,4})/g, + function(m0, m1) { return ('000' + m1.toLowerCase()).substr(-4) }) : null; + break; + + case 'num': + v = (v != null) ? +v : null; + break; + } + + return [ e, v ]; + }).filter(function(e) { + return (e[1] != null); + }).sort(function(a, b) { + return (a[1] > b[1]); + }).map(function(e) { + return e[0]; + }); +} + +function fmt(fmt /*, ...*/) { + var repl = [], wrap = false; + + for (var i = 1; i < arguments.length; i++) { + if (L.dom.elem(arguments[i])) { + switch (arguments[i].nodeType) { + case 1: + repl.push(arguments[i].outerHTML); + wrap = true; + break; + + case 3: + repl.push(arguments[i].data); + break; + + case 11: + var span = E('span'); + span.appendChild(arguments[i]); + repl.push(span.innerHTML); + wrap = true; + break; + + default: + repl.push(''); + } + } + else { + repl.push(arguments[i]); + } + } + + var rv = fmt.format.apply(fmt, repl); + return wrap ? E('span', rv) : rv; +} + +function forward_proto_txt(s) { + return fmt('%s-%s', _('IPv4'), + fwtool.fmt_proto(uci.get('firewall', s, 'proto'), + uci.get('firewall', s, 'icmp_type')) || 'TCP+UDP'); +} + +function rule_src_txt(s) { + var z = fwtool.fmt_zone(uci.get('firewall', s, 'src')), + p = fwtool.fmt_port(uci.get('firewall', s, 'src_port')), + m = fwtool.fmt_mac(uci.get('firewall', s, 'src_mac')); + + // Forward/Input + if (z) { + var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any host')); + if (p && m) + return fmt(_('From %s in %s with source %s and %s'), a, z, p, m); + else if (p || m) + return fmt(_('From %s in %s with source %s'), a, z, p || m); + else + return fmt(_('From %s in %s'), a, z); + } + + // Output + else { + var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any router IP')); + if (p && m) + return fmt(_('From %s on <var>this device</var> with source %s and %s'), a, p, m); + else if (p || m) + return fmt(_('From %s on <var>this device</var> with source %s'), a, p || m); + else + return fmt(_('From %s on <var>this device</var>'), a); + } +} + +function rule_dest_txt(s) { + var z = fwtool.fmt_zone(uci.get('firewall', s, 'dest')), + p = fwtool.fmt_port(uci.get('firewall', s, 'dest_port')); + + // Forward + if (z) { + var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any host')); + if (p) + return fmt(_('To %s, %s in %s'), a, p, z); + else + return fmt(_('To %s in %s'), a, z); + } + + // Input + else { + var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any router IP')); + if (p) + return fmt(_('To %s at %s on <var>this device</var>'), a, p); + else + return fmt(_('To %s on <var>this device</var>'), a); + } +} + +function rule_target_txt(s) { + var t = fwtool.fmt_target(uci.get('firewall', s, 'target'), uci.get('firewall', s, 'src'), uci.get('firewall', s, 'dest')), + l = fwtool.fmt_limit(uci.get('firewall', s, 'limit'), uci.get('firewall', s, 'limit_burst')); + + if (l) + return fmt(_('<var>%s</var> and limit to %s'), t, l); + else + return fmt('<var>%s</var>', t); +} + +return L.view.extend({ + callHostHints: rpc.declare({ + object: 'luci', + method: 'host_hints' + }), + + load: function() { + return this.callHostHints().catch(function(e) { + console.debug('load fail', e); + }); + }, + + render: function(hosts) { + var 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.')); + + s = m.section(form.GridSection, 'rule', _('Traffic Rules')); + s.addremove = true; + s.anonymous = true; + s.sortable = 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'); + }; + + 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', [ + forward_proto_txt(s), E('br'), + rule_src_txt(s), E('br'), + rule_dest_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; + + //ft.opt_enabled(s, Button); + //ft.opt_name(s, Value, _('Name')); + + + 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 = s.taboption('general', form.Value, 'proto', _('Protocol')); + o.modalonly = true; + o.default = 'tcp udp'; + o.value('all', _('Any')); + o.value('tcp udp', 'TCP+UDP'); + o.value('tcp', 'TCP'); + o.value('udp', 'UDP'); + o.value('icmp', 'ICMP'); + o.cfgvalue = function(/* ... */) { + var v = this.super('cfgvalue', arguments); + return (v == 'tcpudp') ? 'tcp udp' : v; + }; + + 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'); + o.value('', 'any'); + o.value('echo-reply'); + o.value('destination-unreachable'); + o.value('network-unreachable'); + o.value('host-unreachable'); + o.value('protocol-unreachable'); + o.value('port-unreachable'); + o.value('fragmentation-needed'); + o.value('source-route-failed'); + o.value('network-unknown'); + o.value('host-unknown'); + o.value('network-prohibited'); + o.value('host-prohibited'); + o.value('TOS-network-unreachable'); + o.value('TOS-host-unreachable'); + o.value('communication-prohibited'); + o.value('host-precedence-violation'); + o.value('precedence-cutoff'); + o.value('source-quench'); + o.value('redirect'); + o.value('network-redirect'); + o.value('host-redirect'); + o.value('TOS-network-redirect'); + o.value('TOS-host-redirect'); + o.value('echo-request'); + o.value('router-advertisement'); + o.value('router-solicitation'); + o.value('time-exceeded'); + o.value('ttl-zero-during-transit'); + o.value('ttl-zero-during-reassembly'); + o.value('parameter-problem'); + o.value('ip-header-bad'); + o.value('required-option-missing'); + o.value('timestamp-request'); + o.value('timestamp-reply'); + o.value('address-mask-request'); + o.value('address-mask-reply'); + o.depends('proto', 'icmp'); + + o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone')); + o.modalonly = true; + o.nocreate = true; + o.allowany = true; + o.allowlocal = 'src'; + o.default = 'wan'; + + o = s.taboption('advanced', form.Value, 'src_mac', _('Source MAC address')); + o.modalonly = true; + o.datatype = 'list(macaddr)'; + o.placeholder = _('any'); + skeys(hosts).forEach(function(mac) { + o.value(mac, '%s (%s)'.format( + mac, + hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?' + )); + }); + + o = s.taboption('general', form.Value, 'src_ip', _('Source address')); + o.modalonly = true; + o.datatype = 'list(neg(ipmask))'; + o.placeholder = _('any'); + skeys(hosts, 'ipv4', 'addr').forEach(function(mac) { + o.value(hosts[mac].ipv4, '%s (%s)'.format( + hosts[mac].ipv4, + hosts[mac].name || mac + )); + }); + + 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'); + o.depends('proto', 'udp'); + o.depends('proto', 'tcp udp'); + o.depends('proto', 'tcpudp'); + + o = s.taboption('general', widgets.ZoneSelect, 'dest_local', _('Output zone')); + o.modalonly = true; + o.nocreate = true; + o.allowany = true; + o.alias = 'dest'; + o.default = 'wan'; + o.depends('src', ''); + + o = s.taboption('general', widgets.ZoneSelect, 'dest_remote', _('Destination zone')); + o.modalonly = true; + o.nocreate = true; + o.allowany = true; + o.allowlocal = true; + o.alias = 'dest'; + o.default = 'lan'; + o.depends({'src': '', '!reverse': true}); + + o = s.taboption('general', form.Value, 'dest_ip', _('Destination address')); + o.modalonly = true; + o.datatype = 'list(neg(ipmask))'; + o.placeholder = _('any'); + skeys(hosts, 'ipv4', 'addr').forEach(function(mac) { + o.value(hosts[mac].ipv4, '%s (%s)'.format( + hosts[mac].ipv4, + hosts[mac].name || mac + )); + }); + + 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'); + o.depends('proto', 'udp'); + o.depends('proto', 'tcp udp'); + o.depends('proto', 'tcpudp'); + + 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 = 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 = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days')); + o.modalonly = true; + o.multiple = true; + o.display_size = 15; + o.placeholder = _('Any day'); + 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().catch(function(e) { + console.debug('render fail') + }); + + } +}); 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 new file mode 100644 index 0000000000..3f1061a10a --- /dev/null +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js @@ -0,0 +1,249 @@ +'use strict'; +'require rpc'; +'require uci'; +'require form'; +'require network'; +'require firewall'; +'require tools.widgets as widgets'; + +return L.view.extend({ + callOffloadSupport: rpc.declare({ + object: 'luci', + method: 'offload_support', + expect: { offload_support: false } + }), + + load: function() { + return this.callOffloadSupport(); + }, + + render: function(hasOffloading) { + var m, s, o, inp, out; + + 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, 'syn_flood', _('Enable SYN-flood protection')); + 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 (hasOffloading) { + s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'), + _('Experimental feature. 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'); + } + + + s = m.section(form.GridSection, 'zone', _('Zones')); + s.addremove = true; + s.anonymous = true; + s.sortable = true; + + s.tab('general', _('General Settings')); + s.tab('advanced', _('Advanced Settings')); + + 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'); + + 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.datatype = 'and(uciname,maxlength(11))'; + o.write = function(section_id, formvalue) { + var cfgvalue = this.cfgvalue(section_id); + + 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', _('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; + } + + o = s.taboption('general', form.Flag, 'masq', _('Masquerading')); + o.editable = true; + + 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.write = function(section_id, formvalue) { + var name = uci.get('firewall', section_id, 'name'), + cfgvalue = this.cfgvalue(section_id); + + if (typeof(cfgvalue) == 'string' && Array.isArray(formvalue) && (cfgvalue == formvalue.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) { + return net || network.addNetwork(netname, { 'proto': 'none' }); + })); + } + + return Promise.all(tasks).then(function(zone_networks) { + if (zone_networks[0]) + 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'); + + 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', 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')); + 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)))'; + o.placeholder = '0.0.0.0/0'; + o.modalonly = true; + + o = s.taboption('advanced', form.Flag, 'conntrack', _('Force connection tracking')); + o.modalonly = true; + + 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; + + o = s.taboption('general', form.DummyValue, '_forwardinfo'); + o.rawhtml = true; + o.modalonly = true; + o.cfgvalue = function(section_id) { + 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(uci.get('firewall', section_id, '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.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.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(); + } +}); |