e983035b3d0a04c951dcd565859f689883d2b680
[project/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / tools / firewall.js
1 'use strict';
2 'require ui';
3 'require uci';
4 'require form';
5 'require network';
6 'require firewall';
7 'require tools.prng as random';
8
9 var protocols = [
10 'ip', 0, 'IP',
11 'hopopt', 0, 'HOPOPT',
12 'icmp', 1, 'ICMP',
13 'igmp', 2, 'IGMP',
14 'ggp', 3 , 'GGP',
15 'ipencap', 4, 'IP-ENCAP',
16 'st', 5, 'ST',
17 'tcp', 6, 'TCP',
18 'egp', 8, 'EGP',
19 'igp', 9, 'IGP',
20 'pup', 12, 'PUP',
21 'udp', 17, 'UDP',
22 'hmp', 20, 'HMP',
23 'xns-idp', 22, 'XNS-IDP',
24 'rdp', 27, 'RDP',
25 'iso-tp4', 29, 'ISO-TP4',
26 'dccp', 33, 'DCCP',
27 'xtp', 36, 'XTP',
28 'ddp', 37, 'DDP',
29 'idpr-cmtp', 38, 'IDPR-CMTP',
30 'ipv6', 41, 'IPv6',
31 'ipv6-route', 43, 'IPv6-Route',
32 'ipv6-frag', 44, 'IPv6-Frag',
33 'idrp', 45, 'IDRP',
34 'rsvp', 46, 'RSVP',
35 'gre', 47, 'GRE',
36 'esp', 50, 'IPSEC-ESP',
37 'ah', 51, 'IPSEC-AH',
38 'skip', 57, 'SKIP',
39 'ipv6-icmp', 58, 'IPv6-ICMP',
40 'ipv6-nonxt', 59, 'IPv6-NoNxt',
41 'ipv6-opts', 60, 'IPv6-Opts',
42 'rspf', 73, 'RSPF', 'CPHB',
43 'vmtp', 81, 'VMTP',
44 'eigrp', 88, 'EIGRP',
45 'ospf', 89, 'OSPFIGP',
46 'ax.25', 93, 'AX.25',
47 'ipip', 94, 'IPIP',
48 'etherip', 97, 'ETHERIP',
49 'encap', 98, 'ENCAP',
50 'pim', 103, 'PIM',
51 'ipcomp', 108, 'IPCOMP',
52 'vrrp', 112, 'VRRP',
53 'l2tp', 115, 'L2TP',
54 'isis', 124, 'ISIS',
55 'sctp', 132, 'SCTP',
56 'fc', 133, 'FC',
57 'mobility-header', 135, 'Mobility-Header',
58 'udplite', 136, 'UDPLite',
59 'mpls-in-ip', 137, 'MPLS-in-IP',
60 'manet', 138, 'MANET',
61 'hip', 139, 'HIP',
62 'shim6', 140, 'Shim6',
63 'wesp', 141, 'WESP',
64 'rohc', 142, 'ROHC',
65 ];
66
67 function lookupProto(x) {
68 if (x == null || x == '')
69 return null;
70
71 var s = String(x).toLowerCase();
72
73 for (var i = 0; i < protocols.length; i += 3)
74 if (s == protocols[i] || s == protocols[i+1])
75 return [ protocols[i+1], protocols[i+2] ];
76
77 return [ -1, x ];
78 }
79
80
81 return L.Class.extend({
82 fmt_neg: function(x) {
83 var rv = E([]),
84 v = (typeof(x) == 'string') ? x.replace(/^ *! */, '') : '';
85
86 L.dom.append(rv, (v != '' && v != x) ? [ _('not') + ' ', v ] : [ '', x ]);
87 return rv;
88 },
89
90 fmt_mac: function(x) {
91 var rv = E([]), l = L.toArray(x);
92
93 if (l.length == 0)
94 return null;
95
96 L.dom.append(rv, [ _('MAC') + ' ' ]);
97
98 for (var i = 0; i < l.length; i++) {
99 var n = this.fmt_neg(l[i]);
100 L.dom.append(rv, (i > 0) ? [ ', ', n ] : n);
101 }
102
103 if (rv.childNodes.length > 2)
104 rv.firstChild.data = _('MACs') + ' ';
105
106 return rv;
107 },
108
109 fmt_port: function(x, d) {
110 var rv = E([]), l = L.toArray(x);
111
112 if (l.length == 0) {
113 if (d) {
114 L.dom.append(rv, E('var', {}, d));
115 return rv;
116 }
117
118 return null;
119 }
120
121 L.dom.append(rv, [ _('port') + ' ' ]);
122
123 for (var i = 0; i < l.length; i++) {
124 var n = this.fmt_neg(l[i]),
125 m = n.lastChild.data.match(/^(\d+)\D+(\d+)$/);
126
127 if (i > 0)
128 L.dom.append(rv, [ ', ' ]);
129
130 if (m) {
131 rv.firstChild.data = _('ports') + ' ';
132 L.dom.append(rv, E('var', [ n.firstChild, m[1], '-', m[2] ]));
133 }
134 else {
135 L.dom.append(rv, E('var', {}, n));
136 }
137 }
138
139 if (rv.childNodes.length > 2)
140 rv.firstChild.data = _('ports') + ' ';
141
142 return rv;
143 },
144
145 fmt_ip: function(x, d) {
146 var rv = E([]), l = L.toArray(x);
147
148 if (l.length == 0) {
149 if (d) {
150 L.dom.append(rv, E('var', {}, d));
151 return rv;
152 }
153
154 return null;
155 }
156
157 L.dom.append(rv, [ _('IP') + ' ' ]);
158
159 for (var i = 0; i < l.length; i++) {
160 var n = this.fmt_neg(l[i]),
161 m = n.lastChild.data.match(/^(\S+)\/(\d+\.\S+)$/);
162
163 if (i > 0)
164 L.dom.append(rv, [ ', ' ]);
165
166 if (m)
167 rv.firstChild.data = _('IP range') + ' ';
168 else if (n.lastChild.data.match(/^[a-zA-Z0-9_]+$/))
169 rv.firstChild.data = _('Network') + ' ';
170
171 L.dom.append(rv, E('var', {}, n));
172 }
173
174 if (rv.childNodes.length > 2)
175 rv.firstChild.data = _('IPs') + ' ';
176
177 return rv;
178 },
179
180 fmt_zone: function(x, d) {
181 if (x == '*')
182 return E('var', _('any zone'));
183 else if (x != null && x != '')
184 return E('var', {}, [ x ]);
185 else if (d != null && d != '')
186 return E('var', {}, d);
187 else
188 return null;
189 },
190
191 fmt_icmp_type: function(x) {
192 var rv = E([]), l = L.toArray(x);
193
194 if (l.length == 0)
195 return null;
196
197 L.dom.append(rv, [ _('type') + ' ' ]);
198
199 for (var i = 0; i < l.length; i++) {
200 var n = this.fmt_neg(l[i]);
201
202 if (i > 0)
203 L.dom.append(rv, [ ', ' ]);
204
205 L.dom.append(rv, E('var', {}, n));
206 }
207
208 if (rv.childNodes.length > 2)
209 rv.firstChild.data = _('types') + ' ';
210
211 return rv;
212 },
213
214 fmt_family: function(family) {
215 if (family == 'ipv4')
216 return _('IPv4');
217 else if (family == 'ipv6')
218 return _('IPv6');
219 else
220 return _('IPv4 and IPv6');
221 },
222
223 fmt_proto: function(x, icmp_types) {
224 var rv = E([]), l = L.toArray(x);
225
226 if (l.length == 0)
227 return null;
228
229 var t = this.fmt_icmp_type(icmp_types);
230
231 for (var i = 0; i < l.length; i++) {
232 var n = this.fmt_neg(l[i]),
233 p = lookupProto(n.lastChild.data);
234
235 if (n.lastChild.data == 'all')
236 continue;
237
238 if (i > 0)
239 L.dom.append(rv, [ ', ' ]);
240
241 if (t && (p[0] == 1 || p[0] == 58))
242 L.dom.append(rv, [ _('%s%s with %s').format(n.firstChild.data, p[1], ''), t ]);
243 else
244 L.dom.append(rv, [ n.firstChild.data, p[1] ]);
245 }
246
247 return rv;
248 },
249
250 fmt_limit: function(limit, burst) {
251 if (limit == null || limit == '')
252 return null;
253
254 var m = String(limit).match(/^(\d+)\/(\w+)$/),
255 u = m[2] || 'second',
256 l = +(m[1] || limit),
257 b = +burst;
258
259 if (!isNaN(l)) {
260 if (u.match(/^s/))
261 u = _('second');
262 else if (u.match(/^m/))
263 u = _('minute');
264 else if (u.match(/^h/))
265 u = _('hour');
266 else if (u.match(/^d/))
267 u = _('day');
268
269 if (!isNaN(b) && b > 0)
270 return E('<span>' +
271 _('<var>%d</var> pkts. per <var>%s</var>, burst <var>%d</var> pkts.').format(l, u, b) +
272 '</span>');
273 else
274 return E('<span>' +
275 _('<var>%d</var> pkts. per <var>%s</var>').format(l, u) +
276 '</span>');
277 }
278 },
279
280 fmt_target: function(x, src, dest) {
281 if (src == null || src == '') {
282 if (x == 'ACCEPT')
283 return _('Accept output');
284 else if (x == 'REJECT')
285 return _('Refuse output');
286 else if (x == 'NOTRACK')
287 return _('Do not track output');
288 else /* if (x == 'DROP') */
289 return _('Discard output');
290 }
291 else if (dest != null && dest != '') {
292 if (x == 'ACCEPT')
293 return _('Accept forward');
294 else if (x == 'REJECT')
295 return _('Refuse forward');
296 else if (x == 'NOTRACK')
297 return _('Do not track forward');
298 else /* if (x == 'DROP') */
299 return _('Discard forward');
300 }
301 else {
302 if (x == 'ACCEPT')
303 return _('Accept input');
304 else if (x == 'REJECT' )
305 return _('Refuse input');
306 else if (x == 'NOTRACK')
307 return _('Do not track input');
308 else /* if (x == 'DROP') */
309 return _('Discard input');
310 }
311 },
312
313 addDSCPOption: function(s, is_target) {
314 var o = s.taboption(is_target ? 'general' : 'advanced', form.Value, is_target ? 'set_dscp' : 'dscp',
315 is_target ? _('DSCP mark') : _('Match DSCP'),
316 is_target ? _('Apply the given DSCP class or value to established connections.') : _('Matches traffic carrying the specified DSCP marking.'));
317
318 o.modalonly = true;
319 o.rmempty = !is_target;
320 o.placeholder = _('any');
321
322 if (is_target)
323 o.depends('target', 'DSCP');
324
325 o.value('CS0');
326 o.value('CS1');
327 o.value('CS2');
328 o.value('CS3');
329 o.value('CS4');
330 o.value('CS5');
331 o.value('CS6');
332 o.value('CS7');
333 o.value('BE');
334 o.value('AF11');
335 o.value('AF12');
336 o.value('AF13');
337 o.value('AF21');
338 o.value('AF22');
339 o.value('AF23');
340 o.value('AF31');
341 o.value('AF32');
342 o.value('AF33');
343 o.value('AF41');
344 o.value('AF42');
345 o.value('AF43');
346 o.value('EF');
347 o.validate = function(section_id, value) {
348 if (value == '')
349 return is_target ? _('DSCP mark required') : true;
350
351 if (!is_target)
352 value = String(value).replace(/^!\s*/, '');
353
354 var m = value.match(/^(?:CS[0-7]|BE|AF[1234][123]|EF|(0x[0-9a-f]{1,2}|[0-9]{1,2}))$/);
355
356 if (!m || (m[1] != null && +m[1] > 0x3f))
357 return _('Invalid DSCP mark');
358
359 return true;
360 };
361
362 return o;
363 },
364
365 addMarkOption: function(s, is_target) {
366 var o = s.taboption(is_target ? 'general' : 'advanced', form.Value,
367 (is_target > 1) ? 'set_xmark' : (is_target ? 'set_mark' : 'mark'),
368 (is_target > 1) ? _('XOR mark') : (is_target ? _('Set mark') : _('Match mark')),
369 (is_target > 1) ? _('Apply a bitwise XOR of the given value and the existing mark value on established connections. Format is value[/mask]. If a mask is specified then those bits set in the mask are zeroed out.') :
370 (is_target ? _('Set the given mark value on established connections. Format is value[/mask]. If a mask is specified then only those bits set in the mask are modified.') :
371 _('Matches a specific firewall mark or a range of different marks.')));
372
373 o.modalonly = true;
374 o.rmempty = true;
375
376 if (is_target > 1)
377 o.depends('target', 'MARK_XOR');
378 else if (is_target)
379 o.depends('target', 'MARK_SET');
380
381 o.validate = function(section_id, value) {
382 if (value == '')
383 return is_target ? _('Valid firewall mark required') : true;
384
385 if (!is_target)
386 value = String(value).replace(/^!\s*/, '');
387
388 var m = value.match(/^(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
389
390 if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
391 return _('Expecting: %s').format(_('valid firewall mark'));
392
393 return true;
394 };
395
396 return o;
397 },
398
399 addLimitOption: function(s) {
400 var o = s.taboption('advanced', form.Value, 'limit',
401 _('Limit matching'),
402 _('Limits traffic matching to the specified rate.'));
403
404 o.modalonly = true;
405 o.rmempty = true;
406 o.placeholder = _('unlimited');
407 o.value('10/second');
408 o.value('60/minute');
409 o.value('3/hour');
410 o.value('500/day');
411 o.validate = function(section_id, value) {
412 if (value == '')
413 return true;
414
415 var m = String(value).toLowerCase().match(/^(?:0x[0-9a-f]{1,8}|[0-9]{1,10})\/([a-z]+)$/),
416 u = ['second', 'minute', 'hour', 'day'],
417 i = 0;
418
419 if (m)
420 for (i = 0; i < u.length; i++)
421 if (u[i].indexOf(m[1]) == 0)
422 break;
423
424 if (!m || i >= u.length)
425 return _('Invalid limit value');
426
427 return true;
428 };
429
430 return o;
431 },
432
433 addLimitBurstOption: function(s) {
434 var o = s.taboption('advanced', form.Value, 'limit_burst',
435 _('Limit burst'),
436 _('Maximum initial number of packets to match: this number gets recharged by one every time the limit specified above is not reached, up to this number.'));
437
438 o.modalonly = true;
439 o.rmempty = true;
440 o.placeholder = '5';
441 o.datatype = 'uinteger';
442 o.depends({ limit: null, '!reverse': true });
443
444 return o;
445 }
446 });