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 | |
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')
4 files changed, 1259 insertions, 0 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 new file mode 100644 index 000000000..909540eaf --- /dev/null +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/tools/firewall.js @@ -0,0 +1,319 @@ +'use strict'; +'require ui'; +'require uci'; +'require form'; +'require network'; +'require firewall'; +'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', + 'ipv6-icmp', 58, 'IPv6-ICMP', + 'ipv6-nonxt', 59, 'IPv6-NoNxt', + 'ipv6-opts', 60, 'IPv6-Opts', + 'rspf', 73, 'RSPF', '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', + '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 toArray(x) { + if (x == null) + return []; + else if (Array.isArray(x)) + return x.map(String); + else if (typeof(x) == 'object') + return [ x ]; + + var s = String(x).trim(); + + if (s == '') + return []; + + return s.split(/\s+/); +} + +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] ]; + + return [ -1, x ]; +} + + +return L.Class.extend({ + fmt_neg: function(x) { + var rv = E([]), + v = (typeof(x) == 'string') ? x.replace(/^ *! */, '') : ''; + + L.dom.append(rv, (v != '' && v != x) ? [ _('not') + ' ', v ] : [ '', x ]); + return rv; + }, + + fmt_mac: function(x) { + var rv = E([]), l = toArray(x); + + if (l.length == 0) + return null; + + L.dom.append(rv, [ _('MAC') + ' ' ]); + + for (var i = 0; i < l.length; i++) { + var n = this.fmt_neg(l[i]); + L.dom.append(rv, (i > 0) ? [ ', ', n ] : n); + } + + if (rv.childNodes.length > 2) + rv.firstChild.data = _('MACs') + ' '; + + return rv; + }, + + fmt_port: function(x, d) { + var rv = E([]), l = toArray(x); + + if (l.length == 0) { + if (d) { + L.dom.append(rv, E('var', {}, d)); + return rv; + } + + return null; + } + + L.dom.append(rv, [ _('port') + ' ' ]); + + for (var i = 0; i < l.length; i++) { + var n = this.fmt_neg(l[i]), + m = n.lastChild.data.match(/^(\d+)\D+(\d+)$/); + + if (i > 0) + L.dom.append(rv, [ ', ' ]); + + if (m) { + rv.firstChild.data = _('ports') + ' '; + L.dom.append(rv, E('var', [ n.firstChild, m[1], '-', m[2] ])); + } + else { + L.dom.append(rv, E('var', {}, n)); + } + } + + if (rv.childNodes.length > 2) + rv.firstChild.data = _('ports') + ' '; + + return rv; + }, + + fmt_ip: function(x, d) { + var rv = E([]), l = toArray(x); + + if (l.length == 0) { + if (d) { + L.dom.append(rv, E('var', {}, d)); + return rv; + } + + return null; + } + + L.dom.append(rv, [ _('IP') + ' ' ]); + + for (var i = 0; i < l.length; i++) { + var n = this.fmt_neg(l[i]), + m = n.lastChild.data.match(/^(\S+)\/(\d+\.\S+)$/); + + if (i > 0) + L.dom.append(rv, [ ', ' ]); + + if (m) + rv.firstChild.data = _('IP range') + ' '; + else if (n.lastChild.data.match(/^[a-zA-Z0-9_]+$/)) + rv.firstChild.data = _('Network') + ' '; + + L.dom.append(rv, E('var', {}, n)); + } + + if (rv.childNodes.length > 2) + rv.firstChild.data = _('IPs') + ' '; + + return rv; + }, + + fmt_zone: function(x, d) { + if (x == '*') + return E('var', _('any zone')); + else if (x != null && x != '') + return E('var', {}, [ x ]); + else if (d != null && d != '') + return E('var', {}, d); + else + return null; + }, + + fmt_icmp_type: function(x) { + var rv = E([]), l = toArray(x); + + if (l.length == 0) + return null; + + L.dom.append(rv, [ _('type') + ' ' ]); + + for (var i = 0; i < l.length; i++) { + var n = this.fmt_neg(l[i]); + + if (i > 0) + L.dom.append(rv, [ ', ' ]); + + L.dom.append(rv, E('var', {}, n)); + } + + if (rv.childNodes.length > 2) + rv.firstChild.data = _('types') + ' '; + + return rv; + }, + + fmt_proto: function(x, icmp_types) { + var rv = E([]), l = toArray(x); + + if (l.length == 0) + return null; + + var t = this.fmt_icmp_type(icmp_types); + + for (var i = 0; i < l.length; i++) { + var n = this.fmt_neg(l[i]), + p = lookupProto(n.lastChild.data); + + if (n.lastChild.data == 'all') + continue; + + if (i > 0) + L.dom.append(rv, [ ', ' ]); + + if (t && (p[0] == 1 || p[0] == 58)) + L.dom.append(rv, [ _('%s%s with %s').format(n.firstChild.data, p[1], ''), t ]); + else + L.dom.append(rv, [ n.firstChild.data, p[1] ]); + } + + return rv; + }, + + fmt_limit: function(limit, burst) { + if (limit == null || limit == '') + return null; + + var m = String(limit).match(/^(\d+)\/(\w+)$/), + u = m[2] || 'second', + l = +(m[1] || limit), + b = +burst; + + if (!isNaN(l)) { + if (u.match(/^s/)) + u = _('second'); + else if (u.match(/^m/)) + u = _('minute'); + else if (u.match(/^h/)) + u = _('hour'); + else if (u.match(/^d/)) + u = _('day'); + + if (!isNaN(b) && b > 0) + return E('<span>' + + _('<var>%d</var> pkts. per <var>%s</var>, burst <var>%d</var> pkts.').format(l, u, b) + + '</span>'); + else + return E('<span>' + + _('<var>%d</var> pkts. per <var>%s</var>').format(l, u) + + '</span>'); + } + }, + + fmt_target: function(x, src, dest) { + if (src == null || src == '') { + if (x == 'ACCEPT') + return _('Accept output'); + else if (x == 'REJECT') + return _('Refuse output'); + else if (x == 'NOTRACK') + return _('Do not track output'); + else /* if (x == 'DROP') */ + return _('Discard output'); + } + else if (dest != null && dest != '') { + if (x == 'ACCEPT') + return _('Accept forward'); + else if (x == 'REJECT') + return _('Refuse forward'); + else if (x == 'NOTRACK') + return _('Do not track forward'); + else /* if (x == 'DROP') */ + return _('Discard forward'); + } + else { + if (x == 'ACCEPT') + return _('Accept input'); + else if (x == 'REJECT' ) + return _('Refuse input'); + else if (x == 'NOTRACK') + return _('Do not track input'); + else /* if (x == 'DROP') */ + return _('Discard input'); + } + } +}); 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 000000000..743d115e8 --- /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 000000000..9fa1aa252 --- /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 000000000..3f1061a10 --- /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(); + } +}); |