luci-app-olsr: handle empty result for non-status tables
[oweals/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / zones.js
1 'use strict';
2 'require rpc';
3 'require uci';
4 'require form';
5 'require network';
6 'require firewall';
7 'require tools.widgets as widgets';
8
9 return L.view.extend({
10         callOffloadSupport: rpc.declare({
11                 object: 'luci',
12                 method: 'offload_support',
13                 expect: { offload_support: false }
14         }),
15
16         callConntrackHelpers: rpc.declare({
17                 object: 'luci',
18                 method: 'conntrack_helpers',
19                 expect: { helpers: [] }
20         }),
21
22         load: function() {
23                 return Promise.all([
24                         this.callOffloadSupport(),
25                         this.callConntrackHelpers()
26                 ]);
27         },
28
29         render: function(data) {
30                 var hasOffloading = data[0],
31                     ctHelpers = data[1],
32                     m, s, o, inp, out;
33
34                 m = new form.Map('firewall', _('Firewall - Zone Settings'),
35                         _('The firewall creates zones over your network interfaces to control network traffic flow.'));
36
37                 s = m.section(form.TypedSection, 'defaults', _('General Settings'));
38                 s.anonymous = true;
39                 s.addremove = false;
40
41                 o = s.option(form.Flag, 'syn_flood', _('Enable SYN-flood protection'));
42                 o = s.option(form.Flag, 'drop_invalid', _('Drop invalid packets'));
43
44                 var p = [
45                         s.option(form.ListValue, 'input', _('Input')),
46                         s.option(form.ListValue, 'output', _('Output')),
47                         s.option(form.ListValue, 'forward', _('Forward'))
48                 ];
49
50                 for (var i = 0; i < p.length; i++) {
51                         p[i].value('REJECT', _('reject'));
52                         p[i].value('DROP', _('drop'));
53                         p[i].value('ACCEPT', _('accept'));
54                 }
55
56                 /* Netfilter flow offload support */
57
58                 if (hasOffloading) {
59                         s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'),
60                                 _('Experimental feature. Not fully compatible with QoS/SQM.'));
61
62                         s.anonymous = true;
63                         s.addremove = false;
64
65                         o = s.option(form.Flag, 'flow_offloading',
66                                 _('Software flow offloading'),
67                                 _('Software based offloading for routing/NAT'));
68                         o.optional = true;
69
70                         o = s.option(form.Flag, 'flow_offloading_hw',
71                                 _('Hardware flow offloading'),
72                                 _('Requires hardware NAT support. Implemented at least for mt7621'));
73                         o.optional = true;
74                         o.depends('flow_offloading', '1');
75                 }
76
77
78                 s = m.section(form.GridSection, 'zone', _('Zones'));
79                 s.addremove = true;
80                 s.anonymous = true;
81                 s.sortable  = true;
82
83                 s.tab('general', _('General Settings'));
84                 s.tab('advanced', _('Advanced Settings'));
85                 s.tab('conntrack', _('Conntrack Settings'));
86                 s.tab('extra', _('Extra iptables arguments'));
87
88                 o = s.taboption('general', form.DummyValue, '_generalinfo');
89                 o.rawhtml = true;
90                 o.modalonly = true;
91                 o.cfgvalue = function(section_id) {
92                         var name = uci.get('firewall', section_id, 'name');
93
94                         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.')
95                                 .replace(/%s/g, name).replace(/%q/g, '"' + name + '"');
96                 };
97
98                 o = s.taboption('general', form.Value, 'name', _('Name'));
99                 o.placeholder = _('Unnamed zone');
100                 o.modalonly = true;
101                 o.datatype = 'and(uciname,maxlength(11))';
102                 o.write = function(section_id, formvalue) {
103                         var cfgvalue = this.cfgvalue(section_id);
104
105                         if (cfgvalue != formvalue)
106                                 return firewall.renameZone(cfgvalue, formvalue);
107                 };
108
109                 o = s.option(widgets.ZoneForwards, '_info', _('Zone ⇒ Forwardings'));
110                 o.editable = true;
111                 o.modalonly = false;
112                 o.cfgvalue = function(section_id) {
113                         return uci.get('firewall', section_id, 'name');
114                 };
115
116                 var p = [
117                         s.taboption('general', form.ListValue, 'input', _('Input')),
118                         s.taboption('general', form.ListValue, 'output', _('Output')),
119                         s.taboption('general', form.ListValue, 'forward', _('Forward'))
120                 ];
121
122                 for (var i = 0; i < p.length; i++) {
123                         p[i].value('REJECT', _('reject'));
124                         p[i].value('DROP', _('drop'));
125                         p[i].value('ACCEPT', _('accept'));
126                         p[i].editable = true;
127                 }
128
129                 o = s.taboption('general', form.Flag, 'masq', _('Masquerading'));
130                 o.editable = true;
131
132                 o = s.taboption('general', form.Flag, 'mtu_fix', _('MSS clamping'));
133                 o.modalonly = true;
134
135                 o = s.taboption('general', widgets.NetworkSelect, 'network', _('Covered networks'));
136                 o.modalonly = true;
137                 o.multiple = true;
138                 o.write = function(section_id, formvalue) {
139                         var name = uci.get('firewall', section_id, 'name'),
140                             cfgvalue = this.cfgvalue(section_id);
141
142                         if (typeof(cfgvalue) == 'string' && Array.isArray(formvalue) && (cfgvalue == formvalue.join(' ')))
143                                 return;
144
145                         var tasks = [ firewall.getZone(name) ];
146
147                         if (Array.isArray(formvalue))
148                                 for (var i = 0; i < formvalue.length; i++) {
149                                         var netname = formvalue[i];
150                                         tasks.push(network.getNetwork(netname).then(function(net) {
151                                                 return net || network.addNetwork(netname, { 'proto': 'none' });
152                                         }));
153                                 }
154
155                         return Promise.all(tasks).then(function(zone_networks) {
156                                 if (zone_networks[0])
157                                         for (var i = 1; i < zone_networks.length; i++)
158                                                 zone_networks[0].addNetwork(zone_networks[i].getName());
159                         });
160                 };
161                 o.remove = function(section_id) {
162                         return uci.set('firewall', section_id, 'network', ' ');
163                 };
164
165                 o = s.taboption('advanced', form.DummyValue, '_advancedinfo');
166                 o.rawhtml = true;
167                 o.modalonly = true;
168                 o.cfgvalue = function(section_id) {
169                         var name = uci.get('firewall', section_id, 'name');
170
171                         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.')
172                                 .format(name);
173                 };
174
175                 o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Covered devices'), _('Use this option to classify zone traffic by raw, non-<em>uci</em> managed network devices.'));
176                 o.modalonly = true;
177                 o.multiple = true;
178
179                 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.'));
180                 o.datatype = 'neg(cidr)';
181                 o.modalonly = true;
182                 o.multiple = true;
183
184                 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
185                 o.value('', _('IPv4 and IPv6'));
186                 o.value('ipv4', _('IPv4 only'));
187                 o.value('ipv6', _('IPv6 only'));
188                 o.modalonly = true;
189
190                 o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
191                 o.depends('family', '');
192                 o.depends('family', 'ipv4');
193                 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
194                 o.placeholder = '0.0.0.0/0';
195                 o.modalonly = true;
196
197                 o = s.taboption('advanced', form.DynamicList, 'masq_dest', _('Restrict Masquerading to given destination subnets'));
198                 o.depends('family', '');
199                 o.depends('family', 'ipv4');
200                 o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
201                 o.placeholder = '0.0.0.0/0';
202                 o.modalonly = true;
203
204                 o = s.taboption('conntrack', form.Flag, 'conntrack', _('Force connection tracking'), _('Prevent the installation of <em>NOTRACK</em> rules which would bypass connection tracking.'));
205                 o.modalonly = true;
206
207                 o = s.taboption('conntrack', form.Flag, 'masq_allow_invalid', _('Allow "invalid" traffic'), _('Do not install extra rules to reject forwarded traffic with conntrack state <em>invalid</em>. This may be required for complex asymmetric route setups.'));
208                 o.modalonly = true;
209
210                 o = s.taboption('conntrack', form.Flag, 'auto_helper', _('Automatic helper assignment'), _('Automatically assign conntrack helpers based on traffic protocol and port'));
211                 o.default = o.enabled;
212                 o.modalonly = true;
213
214                 o = s.taboption('conntrack', form.MultiValue, 'helper', _('Conntrack helpers'), _('Explicitly choses allowed connection tracking helpers for zone traffic'));
215                 o.depends('auto_helper', '0');
216                 o.modalonly = true;
217                 for (var i = 0; i < ctHelpers.length; i++)
218                         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()));
219
220                 o = s.taboption('advanced', form.Flag, 'log', _('Enable logging on this zone'));
221                 o.modalonly = true;
222
223                 o = s.taboption('advanced', form.Value, 'log_limit', _('Limit log messages'));
224                 o.depends('log', '1');
225                 o.placeholder = '10/minute';
226                 o.modalonly = true;
227
228                 o = s.taboption('extra', form.DummyValue, '_extrainfo');
229                 o.rawhtml = true;
230                 o.modalonly = true;
231                 o.cfgvalue = function(section_id) {
232                         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.');
233                 };
234
235                 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.'));
236                 o.modalonly = true;
237                 o.cfgvalue = function(section_id) {
238                         return uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
239                 };
240                 o.write = function(section_id, value) {
241                         uci.unset('firewall', section_id, 'extra');
242                         uci.set('firewall', section_id, 'extra_src', value);
243                 };
244
245                 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.'));
246                 o.modalonly = true;
247                 o.cfgvalue = function(section_id) {
248                         return uci.get('firewall', section_id, 'extra_dest') || uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
249                 };
250                 o.write = function(section_id, value) {
251                         uci.unset('firewall', section_id, 'extra');
252                         uci.set('firewall', section_id, 'extra_dest', value);
253                 };
254
255                 o = s.taboption('general', form.DummyValue, '_forwardinfo');
256                 o.rawhtml = true;
257                 o.modalonly = true;
258                 o.cfgvalue = function(section_id) {
259                         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.')
260                                 .format(uci.get('firewall', section_id, 'name'));
261                 };
262
263                 out = o = s.taboption('general', widgets.ZoneSelect, 'out', _('Allow forward to <em>destination zones</em>:'));
264                 o.nocreate = true;
265                 o.multiple = true;
266                 o.modalonly = true;
267                 o.filter = function(section_id, value) {
268                         return (uci.get('firewall', section_id, 'name') != value);
269                 };
270                 o.cfgvalue = function(section_id) {
271                         var out = (this.option == 'out'),
272                             zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
273                             fwds = zone.getForwardingsBy(out ? 'src' : 'dest'),
274                             value = [];
275
276                         for (var i = 0; i < fwds.length; i++)
277                                 value.push(out ? fwds[i].getDestination() : fwds[i].getSource());
278
279                         return value;
280                 };
281                 o.write = o.remove = function(section_id, formvalue) {
282                         var out = (this.option == 'out'),
283                             zone = this.lookupZone(uci.get('firewall', section_id, 'name')),
284                             fwds = zone.getForwardingsBy(out ? 'src' : 'dest');
285
286                         if (formvalue == null)
287                                 formvalue = [];
288
289                         if (Array.isArray(formvalue)) {
290                                 for (var i = 0; i < fwds.length; i++) {
291                                         var cmp = out ? fwds[i].getDestination() : fwds[i].getSource();
292                                         if (!formvalue.filter(function(d) { return d == cmp }).length)
293                                                 zone.deleteForwarding(fwds[i]);
294                                 }
295
296                                 for (var i = 0; i < formvalue.length; i++)
297                                         if (out)
298                                                 zone.addForwardingTo(formvalue[i]);
299                                         else
300                                                 zone.addForwardingFrom(formvalue[i]);
301                         }
302                 };
303
304                 inp = o = s.taboption('general', widgets.ZoneSelect, 'in', _('Allow forward from <em>source zones</em>:'));
305                 o.nocreate = true;
306                 o.multiple = true;
307                 o.modalonly = true;
308                 o.write = o.remove = out.write;
309                 o.filter = out.filter;
310                 o.cfgvalue = out.cfgvalue;
311
312                 return m.render();
313         }
314 });