luci-app-firewall: update rule ip hints based on address family
[project/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 load: function() {
152 return this.callHostHints().catch(function(e) {
153 console.debug('load fail', e);
154 });
155 },
156
157 render: function(hosts) {
158 var m, s, o;
159
160 m = new form.Map('firewall', _('Firewall - Traffic Rules'),
161 _('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.'));
162
163 s = m.section(form.GridSection, 'rule', _('Traffic Rules'));
164 s.addremove = true;
165 s.anonymous = true;
166 s.sortable = true;
167
168 s.tab('general', _('General Settings'));
169 s.tab('advanced', _('Advanced Settings'));
170 s.tab('timed', _('Time Restrictions'));
171
172 s.filter = function(section_id) {
173 return (uci.get('firewall', section_id, 'target') != 'SNAT');
174 };
175
176 s.sectiontitle = function(section_id) {
177 return uci.get('firewall', section_id, 'name') || _('Unnamed rule');
178 };
179
180 s.handleAdd = function(ev) {
181 var config_name = this.uciconfig || this.map.config,
182 section_id = uci.add(config_name, this.sectiontype),
183 opt1, opt2;
184
185 for (var i = 0; i < this.children.length; i++)
186 if (this.children[i].option == 'src')
187 opt1 = this.children[i];
188 else if (this.children[i].option == 'dest')
189 opt2 = this.children[i];
190
191 opt1.default = 'wan';
192 opt2.default = 'lan';
193
194 this.addedSection = section_id;
195 this.renderMoreOptionsModal(section_id);
196
197 delete opt1.default;
198 delete opt2.default;
199 };
200
201 o = s.taboption('general', form.Value, 'name', _('Name'));
202 o.placeholder = _('Unnamed rule');
203 o.modalonly = true;
204
205 o = s.option(form.DummyValue, '_match', _('Match'));
206 o.modalonly = false;
207 o.textvalue = function(s) {
208 return E('small', [
209 forward_proto_txt(s), E('br'),
210 rule_src_txt(s), E('br'),
211 rule_dest_txt(s)
212 ]);
213 };
214
215 o = s.option(form.ListValue, '_target', _('Action'));
216 o.modalonly = false;
217 o.textvalue = function(s) {
218 return rule_target_txt(s);
219 };
220
221 o = s.option(form.Flag, 'enabled', _('Enable'));
222 o.modalonly = false;
223 o.default = o.enabled;
224 o.editable = true;
225
226 //ft.opt_enabled(s, Button);
227 //ft.opt_name(s, Value, _('Name'));
228
229
230 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
231 o.modalonly = true;
232 o.rmempty = true;
233 o.value('', _('IPv4 and IPv6'));
234 o.value('ipv4', _('IPv4 only'));
235 o.value('ipv6', _('IPv6 only'));
236 o.validate = function(section_id, value) {
237 update_ip_hints(this.map, section_id, value, hosts);
238 return true;
239 };
240
241 o = s.taboption('general', form.Value, 'proto', _('Protocol'));
242 o.modalonly = true;
243 o.default = 'tcp udp';
244 o.value('all', _('Any'));
245 o.value('tcp udp', 'TCP+UDP');
246 o.value('tcp', 'TCP');
247 o.value('udp', 'UDP');
248 o.value('icmp', 'ICMP');
249 o.cfgvalue = function(/* ... */) {
250 var v = this.super('cfgvalue', arguments);
251 return (v == 'tcpudp') ? 'tcp udp' : v;
252 };
253
254 o = s.taboption('advanced', form.MultiValue, 'icmp_type', _('Match ICMP type'));
255 o.modalonly = true;
256 o.multiple = true;
257 o.custom = true;
258 o.cast = 'table';
259 o.placeholder = _('any');
260 o.value('', 'any');
261 o.value('address-mask-reply');
262 o.value('address-mask-request');
263 o.value('communication-prohibited');
264 o.value('destination-unreachable');
265 o.value('echo-reply');
266 o.value('echo-request');
267 o.value('fragmentation-needed');
268 o.value('host-precedence-violation');
269 o.value('host-prohibited');
270 o.value('host-redirect');
271 o.value('host-unknown');
272 o.value('host-unreachable');
273 o.value('ip-header-bad');
274 o.value('neighbour-advertisement');
275 o.value('neighbour-solicitation');
276 o.value('network-prohibited');
277 o.value('network-redirect');
278 o.value('network-unknown');
279 o.value('network-unreachable');
280 o.value('parameter-problem');
281 o.value('port-unreachable');
282 o.value('precedence-cutoff');
283 o.value('protocol-unreachable');
284 o.value('redirect');
285 o.value('required-option-missing');
286 o.value('router-advertisement');
287 o.value('router-solicitation');
288 o.value('source-quench');
289 o.value('source-route-failed');
290 o.value('time-exceeded');
291 o.value('timestamp-reply');
292 o.value('timestamp-request');
293 o.value('TOS-host-redirect');
294 o.value('TOS-host-unreachable');
295 o.value('TOS-network-redirect');
296 o.value('TOS-network-unreachable');
297 o.value('ttl-zero-during-reassembly');
298 o.value('ttl-zero-during-transit');
299 o.depends('proto', 'icmp');
300
301 o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
302 o.modalonly = true;
303 o.nocreate = true;
304 o.allowany = true;
305 o.allowlocal = 'src';
306
307 o = s.taboption('advanced', form.Value, 'src_mac', _('Source MAC address'));
308 o.modalonly = true;
309 o.datatype = 'list(macaddr)';
310 o.placeholder = _('any');
311 L.sortedKeys(hosts).forEach(function(mac) {
312 o.value(mac, '%s (%s)'.format(
313 mac,
314 hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?'
315 ));
316 });
317
318 o = s.taboption('general', form.Value, 'src_ip', _('Source address'));
319 o.modalonly = true;
320 o.datatype = 'list(neg(ipmask))';
321 o.placeholder = _('any');
322 o.transformChoices = function() { return {} }; /* force combobox rendering */
323
324 o = s.taboption('general', form.Value, 'src_port', _('Source port'));
325 o.modalonly = true;
326 o.datatype = 'list(neg(portrange))';
327 o.placeholder = _('any');
328 o.depends('proto', 'tcp');
329 o.depends('proto', 'udp');
330 o.depends('proto', 'tcp udp');
331 o.depends('proto', 'tcpudp');
332
333 o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Destination zone'));
334 o.modalonly = true;
335 o.nocreate = true;
336 o.allowany = true;
337 o.allowlocal = true;
338
339 o = s.taboption('general', form.Value, 'dest_ip', _('Destination address'));
340 o.modalonly = true;
341 o.datatype = 'list(neg(ipmask))';
342 o.placeholder = _('any');
343 o.transformChoices = function() { return {} }; /* force combobox rendering */
344
345 o = s.taboption('general', form.Value, 'dest_port', _('Destination port'));
346 o.modalonly = true;
347 o.datatype = 'list(neg(portrange))';
348 o.placeholder = _('any');
349 o.depends('proto', 'tcp');
350 o.depends('proto', 'udp');
351 o.depends('proto', 'tcp udp');
352 o.depends('proto', 'tcpudp');
353
354 o = s.taboption('general', form.ListValue, 'target', _('Action'));
355 o.modalonly = true;
356 o.default = 'ACCEPT';
357 o.value('DROP', _('drop'));
358 o.value('ACCEPT', _('accept'));
359 o.value('REJECT', _('reject'));
360 o.value('NOTRACK', _("don't track"));
361
362 o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
363 _('Passes additional arguments to iptables. Use with care!'));
364 o.modalonly = true;
365
366 o = s.taboption('timed', form.MultiValue, 'weekdays', _('Week Days'));
367 o.modalonly = true;
368 o.multiple = true;
369 o.display = 5;
370 o.placeholder = _('Any day');
371 o.value('Sun', _('Sunday'));
372 o.value('Mon', _('Monday'));
373 o.value('Tue', _('Tuesday'));
374 o.value('Wed', _('Wednesday'));
375 o.value('Thu', _('Thursday'));
376 o.value('Fri', _('Friday'));
377 o.value('Sat', _('Saturday'));
378 o.write = function(section_id, value) {
379 return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
380 };
381
382 o = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days'));
383 o.modalonly = true;
384 o.multiple = true;
385 o.display_size = 15;
386 o.placeholder = _('Any day');
387 o.write = function(section_id, value) {
388 return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
389 };
390 for (var i = 1; i <= 31; i++)
391 o.value(i);
392
393 o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh.mm.ss)'));
394 o.modalonly = true;
395 o.datatype = 'timehhmmss';
396
397 o = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh.mm.ss)'));
398 o.modalonly = true;
399 o.datatype = 'timehhmmss';
400
401 o = s.taboption('timed', form.Value, 'start_date', _('Start Date (yyyy-mm-dd)'));
402 o.modalonly = true;
403 o.datatype = 'dateyyyymmdd';
404
405 o = s.taboption('timed', form.Value, 'stop_date', _('Stop Date (yyyy-mm-dd)'));
406 o.modalonly = true;
407 o.datatype = 'dateyyyymmdd';
408
409 o = s.taboption('timed', form.Flag, 'utc_time', _('Time in UTC'));
410 o.modalonly = true;
411 o.default = o.disabled;
412
413 return m.render();
414 }
415 });