luci-app-firewall: support 'helper' and 'set_helper' parameters for rules
[oweals/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / rules.js
1 'use strict';
2 'require ui';
3 'require rpc';
4 'require uci';
5 'require form';
6 'require tools.firewall as fwtool';
7 'require tools.widgets as widgets';
8
9 function fmt(fmt /*, ...*/) {
10         var repl = [], wrap = false;
11
12         for (var i = 1; i < arguments.length; i++) {
13                 if (L.dom.elem(arguments[i])) {
14                         switch (arguments[i].nodeType) {
15                         case 1:
16                                 repl.push(arguments[i].outerHTML);
17                                 wrap = true;
18                                 break;
19
20                         case 3:
21                                 repl.push(arguments[i].data);
22                                 break;
23
24                         case 11:
25                                 var span = E('span');
26                                 span.appendChild(arguments[i]);
27                                 repl.push(span.innerHTML);
28                                 wrap = true;
29                                 break;
30
31                         default:
32                                 repl.push('');
33                         }
34                 }
35                 else {
36                         repl.push(arguments[i]);
37                 }
38         }
39
40         var rv = fmt.format.apply(fmt, repl);
41         return wrap ? E('span', rv) : rv;
42 }
43
44 function forward_proto_txt(s) {
45         return fmt('%s-%s',
46                 fwtool.fmt_family(uci.get('firewall', s, 'family')),
47                 fwtool.fmt_proto(uci.get('firewall', s, 'proto'),
48                                  uci.get('firewall', s, 'icmp_type')) || 'TCP+UDP');
49 }
50
51 function rule_src_txt(s) {
52         var z = fwtool.fmt_zone(uci.get('firewall', s, 'src')),
53             p = fwtool.fmt_port(uci.get('firewall', s, 'src_port')),
54             m = fwtool.fmt_mac(uci.get('firewall', s, 'src_mac'));
55
56         // Forward/Input
57         if (z) {
58                 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any host'));
59                 if (p && m)
60                         return fmt(_('From %s in %s with source %s and %s'), a, z, p, m);
61                 else if (p || m)
62                         return fmt(_('From %s in %s with source %s'), a, z, p || m);
63                 else
64                         return fmt(_('From %s in %s'), a, z);
65         }
66
67         // Output
68         else {
69                 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any router IP'));
70                 if (p && m)
71                         return fmt(_('From %s on <var>this device</var> with source %s and %s'), a, p, m);
72                 else if (p || m)
73                         return fmt(_('From %s on <var>this device</var> with source %s'), a, p || m);
74                 else
75                         return fmt(_('From %s on <var>this device</var>'), a);
76         }
77 }
78
79 function rule_dest_txt(s) {
80         var z = fwtool.fmt_zone(uci.get('firewall', s, 'dest')),
81             p = fwtool.fmt_port(uci.get('firewall', s, 'dest_port'));
82
83     // Forward
84         if (z) {
85                 var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any host'));
86                 if (p)
87                         return fmt(_('To %s, %s in %s'), a, p, z);
88                 else
89                         return fmt(_('To %s in %s'), a, z);
90         }
91
92         // Input
93         else {
94                 var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any router IP'));
95                 if (p)
96                         return fmt(_('To %s at %s on <var>this device</var>'), a, p);
97                 else
98                         return fmt(_('To %s on <var>this device</var>'), a);
99         }
100 }
101
102 function rule_target_txt(s) {
103         var t = fwtool.fmt_target(uci.get('firewall', s, 'target'), uci.get('firewall', s, 'src'), uci.get('firewall', s, 'dest')),
104             l = fwtool.fmt_limit(uci.get('firewall', s, 'limit'), uci.get('firewall', s, 'limit_burst'));
105
106         if (l)
107                 return fmt(_('<var>%s</var> and limit to %s'), t, l);
108         else
109                 return fmt('<var>%s</var>', t);
110 }
111
112 function update_ip_hints(map, section_id, family, hosts) {
113         var elem_src_ip = map.lookupOption('src_ip', section_id)[0].getUIElement(section_id),
114             elem_dst_ip = map.lookupOption('dest_ip', section_id)[0].getUIElement(section_id),
115             choice_values = [], choice_labels = {};
116
117         elem_src_ip.clearChoices();
118         elem_dst_ip.clearChoices();
119
120         if (!family || family == 'ipv4') {
121                 L.sortedKeys(hosts, 'ipv4', 'addr').forEach(function(mac) {
122                         var val = hosts[mac].ipv4,
123                             txt = '%s (<strong>%s</strong>)'.format(val, hosts[mac].name || mac);
124
125                         choice_values.push(val);
126                         choice_labels[val] = txt;
127                 });
128         }
129
130         if (!family || family == 'ipv6') {
131                 L.sortedKeys(hosts, 'ipv6', 'addr').forEach(function(mac) {
132                         var val = hosts[mac].ipv6,
133                             txt = '%s (<strong>%s</strong>)'.format(val, hosts[mac].name || mac);
134
135                         choice_values.push(val);
136                         choice_labels[val] = txt;
137                 });
138         }
139
140         elem_src_ip.addChoices(choice_values, choice_labels);
141         elem_dst_ip.addChoices(choice_values, choice_labels);
142 }
143
144 return L.view.extend({
145         callHostHints: rpc.declare({
146                 object: 'luci-rpc',
147                 method: 'getHostHints',
148                 expect: { '': {} }
149         }),
150
151         callConntrackHelpers: rpc.declare({
152                 object: 'luci',
153                 method: 'getConntrackHelpers',
154                 expect: { result: [] }
155         }),
156
157         load: function() {
158                 return Promise.all([
159                         this.callHostHints(),
160                         this.callConntrackHelpers()
161                 ]);
162         },
163
164         render: function(data) {
165                 var hosts = data[0],
166                     ctHelpers = data[1],
167                     m, s, o;
168
169                 m = new form.Map('firewall', _('Firewall - Traffic Rules'),
170                         _('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.'));
171
172                 s = m.section(form.GridSection, 'rule', _('Traffic Rules'));
173                 s.addremove = true;
174                 s.anonymous = true;
175                 s.sortable  = true;
176
177                 s.tab('general', _('General Settings'));
178                 s.tab('advanced', _('Advanced Settings'));
179                 s.tab('timed', _('Time Restrictions'));
180
181                 s.filter = function(section_id) {
182                         return (uci.get('firewall', section_id, 'target') != 'SNAT');
183                 };
184
185                 s.sectiontitle = function(section_id) {
186                         return uci.get('firewall', section_id, 'name') || _('Unnamed rule');
187                 };
188
189                 s.handleAdd = function(ev) {
190                         var config_name = this.uciconfig || this.map.config,
191                             section_id = uci.add(config_name, this.sectiontype),
192                             opt1, opt2;
193
194                         for (var i = 0; i < this.children.length; i++)
195                                 if (this.children[i].option == 'src')
196                                         opt1 = this.children[i];
197                                 else if (this.children[i].option == 'dest')
198                                         opt2 = this.children[i];
199
200                         opt1.default = 'wan';
201                         opt2.default = 'lan';
202
203                         this.addedSection = section_id;
204                         this.renderMoreOptionsModal(section_id);
205
206                         delete opt1.default;
207                         delete opt2.default;
208                 };
209
210                 o = s.taboption('general', form.Value, 'name', _('Name'));
211                 o.placeholder = _('Unnamed rule');
212                 o.modalonly = true;
213
214                 o = s.option(form.DummyValue, '_match', _('Match'));
215                 o.modalonly = false;
216                 o.textvalue = function(s) {
217                         return E('small', [
218                                 forward_proto_txt(s), E('br'),
219                                 rule_src_txt(s), E('br'),
220                                 rule_dest_txt(s)
221                         ]);
222                 };
223
224                 o = s.option(form.ListValue, '_target', _('Action'));
225                 o.modalonly = false;
226                 o.textvalue = function(s) {
227                         return rule_target_txt(s);
228                 };
229
230                 o = s.option(form.Flag, 'enabled', _('Enable'));
231                 o.modalonly = false;
232                 o.default = o.enabled;
233                 o.editable = true;
234
235
236                 o = s.taboption('advanced', form.ListValue, 'direction', _('Match device'));
237                 o.modalonly = true;
238                 o.value('', _('unspecified'));
239                 o.value('in', _('Inbound device'));
240                 o.value('out', _('Outbound device'));
241                 o.cfgvalue = function(section_id) {
242                         var val = uci.get('firewall', section_id, 'direction');
243                         switch (val) {
244                                 case 'in':
245                                 case 'ingress':
246                                         return 'in';
247
248                                 case 'out':
249                                 case 'egress':
250                                         return 'out';
251                         }
252
253                         return null;
254                 };
255
256                 o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Device name'),
257                         _('Specifies whether to tie this traffic rule to a specific inbound or outbound network device.'));
258                 o.modalonly = true;
259                 o.noaliases = true;
260                 o.rmempty = false;
261                 o.depends('direction', 'in');
262                 o.depends('direction', 'out');
263
264                 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
265                 o.modalonly = true;
266                 o.rmempty = true;
267                 o.value('', _('IPv4 and IPv6'));
268                 o.value('ipv4', _('IPv4 only'));
269                 o.value('ipv6', _('IPv6 only'));
270                 o.validate = function(section_id, value) {
271                         update_ip_hints(this.map, section_id, value, hosts);
272                         return true;
273                 };
274
275                 o = s.taboption('general', form.Value, 'proto', _('Protocol'));
276                 o.modalonly = true;
277                 o.default = 'tcp udp';
278                 o.value('all', _('Any'));
279                 o.value('tcp udp', 'TCP+UDP');
280                 o.value('tcp', 'TCP');
281                 o.value('udp', 'UDP');
282                 o.value('icmp', 'ICMP');
283                 o.cfgvalue = function(/* ... */) {
284                         var v = this.super('cfgvalue', arguments);
285                         return (v == 'tcpudp') ? 'tcp udp' : v;
286                 };
287
288                 o = s.taboption('advanced', form.MultiValue, 'icmp_type', _('Match ICMP type'));
289                 o.modalonly = true;
290                 o.multiple = true;
291                 o.custom = true;
292                 o.cast = 'table';
293                 o.placeholder = _('any');
294                 o.value('', 'any');
295                 o.value('address-mask-reply');
296                 o.value('address-mask-request');
297                 o.value('communication-prohibited');
298                 o.value('destination-unreachable');
299                 o.value('echo-reply');
300                 o.value('echo-request');
301                 o.value('fragmentation-needed');
302                 o.value('host-precedence-violation');
303                 o.value('host-prohibited');
304                 o.value('host-redirect');
305                 o.value('host-unknown');
306                 o.value('host-unreachable');
307                 o.value('ip-header-bad');
308                 o.value('neighbour-advertisement');
309                 o.value('neighbour-solicitation');
310                 o.value('network-prohibited');
311                 o.value('network-redirect');
312                 o.value('network-unknown');
313                 o.value('network-unreachable');
314                 o.value('parameter-problem');
315                 o.value('port-unreachable');
316                 o.value('precedence-cutoff');
317                 o.value('protocol-unreachable');
318                 o.value('redirect');
319                 o.value('required-option-missing');
320                 o.value('router-advertisement');
321                 o.value('router-solicitation');
322                 o.value('source-quench');
323                 o.value('source-route-failed');
324                 o.value('time-exceeded');
325                 o.value('timestamp-reply');
326                 o.value('timestamp-request');
327                 o.value('TOS-host-redirect');
328                 o.value('TOS-host-unreachable');
329                 o.value('TOS-network-redirect');
330                 o.value('TOS-network-unreachable');
331                 o.value('ttl-zero-during-reassembly');
332                 o.value('ttl-zero-during-transit');
333                 o.depends('proto', 'icmp');
334
335                 o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
336                 o.modalonly = true;
337                 o.nocreate = true;
338                 o.allowany = true;
339                 o.allowlocal = 'src';
340
341                 o = s.taboption('advanced', form.Value, 'src_mac', _('Source MAC address'));
342                 o.modalonly = true;
343                 o.datatype = 'list(macaddr)';
344                 o.placeholder = _('any');
345                 L.sortedKeys(hosts).forEach(function(mac) {
346                         o.value(mac, '%s (%s)'.format(
347                                 mac,
348                                 hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?'
349                         ));
350                 });
351
352                 o = s.taboption('general', form.Value, 'src_ip', _('Source address'));
353                 o.modalonly = true;
354                 o.datatype = 'list(neg(ipmask))';
355                 o.placeholder = _('any');
356                 o.transformChoices = function() { return {} }; /* force combobox rendering */
357
358                 o = s.taboption('general', form.Value, 'src_port', _('Source port'));
359                 o.modalonly = true;
360                 o.datatype = 'list(neg(portrange))';
361                 o.placeholder = _('any');
362                 o.depends('proto', 'tcp');
363                 o.depends('proto', 'udp');
364                 o.depends('proto', 'tcp udp');
365                 o.depends('proto', 'tcpudp');
366
367                 o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Destination zone'));
368                 o.modalonly = true;
369                 o.nocreate = true;
370                 o.allowany = true;
371                 o.allowlocal = true;
372
373                 o = s.taboption('general', form.Value, 'dest_ip', _('Destination address'));
374                 o.modalonly = true;
375                 o.datatype = 'list(neg(ipmask))';
376                 o.placeholder = _('any');
377                 o.transformChoices = function() { return {} }; /* force combobox rendering */
378
379                 o = s.taboption('general', form.Value, 'dest_port', _('Destination port'));
380                 o.modalonly = true;
381                 o.datatype = 'list(neg(portrange))';
382                 o.placeholder = _('any');
383                 o.depends('proto', 'tcp');
384                 o.depends('proto', 'udp');
385                 o.depends('proto', 'tcp udp');
386                 o.depends('proto', 'tcpudp');
387
388                 o = s.taboption('general', form.ListValue, 'target', _('Action'));
389                 o.modalonly = true;
390                 o.default = 'ACCEPT';
391                 o.value('DROP', _('drop'));
392                 o.value('ACCEPT', _('accept'));
393                 o.value('REJECT', _('reject'));
394                 o.value('NOTRACK', _("don't track"));
395                 o.value('HELPER', _('assign conntrack helper'));
396
397                 o = s.taboption('general', form.ListValue, 'set_helper', _('Tracking helper'), _('Assign the specified connection tracking helper to matched traffic.'));
398                 o.modalonly = true;
399                 o.placeholder = _('any');
400                 o.depends('target', 'HELPER');
401                 for (var i = 0; i < ctHelpers.length; i++)
402                         o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
403
404                 o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
405                 o.modalonly = true;
406                 o.placeholder = _('any');
407                 for (var i = 0; i < ctHelpers.length; i++)
408                         o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
409                 o.validate = function(section_id, value) {
410                         if (value == '' || value == null)
411                                 return true;
412
413                         value = value.replace(/^!\s*/, '');
414
415                         for (var i = 0; i < ctHelpers.length; i++)
416                                 if (value == ctHelpers[i].name)
417                                         return true;
418
419                         return _('Unknown or not installed conntrack helper "%s"').format(value);
420                 };
421
422                 o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
423                         _('Passes additional arguments to iptables. Use with care!'));
424                 o.modalonly = true;
425
426                 o = s.taboption('timed', form.MultiValue, 'weekdays', _('Week Days'));
427                 o.modalonly = true;
428                 o.multiple = true;
429                 o.display = 5;
430                 o.placeholder = _('Any day');
431                 o.value('Sun', _('Sunday'));
432                 o.value('Mon', _('Monday'));
433                 o.value('Tue', _('Tuesday'));
434                 o.value('Wed', _('Wednesday'));
435                 o.value('Thu', _('Thursday'));
436                 o.value('Fri', _('Friday'));
437                 o.value('Sat', _('Saturday'));
438                 o.write = function(section_id, value) {
439                         return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
440                 };
441
442                 o = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days'));
443                 o.modalonly = true;
444                 o.multiple = true;
445                 o.display_size = 15;
446                 o.placeholder = _('Any day');
447                 o.write = function(section_id, value) {
448                         return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
449                 };
450                 for (var i = 1; i <= 31; i++)
451                         o.value(i);
452
453                 o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh.mm.ss)'));
454                 o.modalonly = true;
455                 o.datatype = 'timehhmmss';
456
457                 o = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh.mm.ss)'));
458                 o.modalonly = true;
459                 o.datatype = 'timehhmmss';
460
461                 o = s.taboption('timed', form.Value, 'start_date', _('Start Date (yyyy-mm-dd)'));
462                 o.modalonly = true;
463                 o.datatype = 'dateyyyymmdd';
464
465                 o = s.taboption('timed', form.Value, 'stop_date', _('Stop Date (yyyy-mm-dd)'));
466                 o.modalonly = true;
467                 o.datatype = 'dateyyyymmdd';
468
469                 o = s.taboption('timed', form.Flag, 'utc_time', _('Time in UTC'));
470                 o.modalonly = true;
471                 o.default = o.disabled;
472
473                 return m.render();
474         }
475 });