ec0ee369ea7c3c9f55cd7d8be290d258ae043f83
9 'require tools.prng as random';
13 'hopopt', 0, 'HOPOPT',
17 'ipencap', 4, 'IP-ENCAP',
25 'xns-idp', 22, 'XNS-IDP',
27 'iso-tp4', 29, 'ISO-TP4',
31 'idpr-cmtp', 38, 'IDPR-CMTP',
33 'ipv6-route', 43, 'IPv6-Route',
34 'ipv6-frag', 44, 'IPv6-Frag',
38 'esp', 50, 'IPSEC-ESP',
41 'icmpv6', 58, 'IPv6-ICMP',
42 'ipv6-icmp', 58, 'IPv6-ICMP',
43 'ipv6-nonxt', 59, 'IPv6-NoNxt',
44 'ipv6-opts', 60, 'IPv6-Opts',
49 'ospf', 89, 'OSPFIGP',
52 'etherip', 97, 'ETHERIP',
55 'ipcomp', 108, 'IPCOMP',
61 'mh', 135, 'Mobility-Header',
62 'ipv6-mh', 135, 'Mobility-Header',
63 'mobility-header', 135, 'Mobility-Header',
64 'udplite', 136, 'UDPLite',
65 'mpls-in-ip', 137, 'MPLS-in-IP',
66 'manet', 138, 'MANET',
68 'shim6', 140, 'Shim6',
73 function lookupProto(x
) {
74 if (x
== null || x
=== '')
77 var s
= String(x
).toLowerCase();
79 for (var i
= 0; i
< protocols
.length
; i
+= 3)
80 if (s
== protocols
[i
] || s
== protocols
[i
+1])
81 return [ protocols
[i
+1], protocols
[i
+2], protocols
[i
] ];
86 return baseclass
.extend({
87 fmt: function(fmtstr
, args
, values
) {
97 var get = function(args
, key
) {
98 var names
= key
.trim().split(/\./),
102 for (var i
= 0; i
< names
.length
; i
++) {
103 if (!L
.isObject(obj
))
110 if (typeof(obj
) == 'function')
111 return obj
.call(ctx
);
116 var isset = function(val
) {
117 if (L
.isObject(val
) && !dom
.elem(val
)) {
119 if (val
.hasOwnProperty(k
))
124 else if (Array
.isArray(val
)) {
125 return (val
.length
> 0);
128 return (val
!== null && val
!== undefined && val
!== '' && val
!== false);
132 var parse = function(tokens
, text
) {
133 if (dom
.elem(text
)) {
134 tokens
.push('<span data-fmt-placeholder="%d"></span>'.format(values
.length
));
138 tokens
.push(String(text
).replace(/\\(.)/g, '$1'));
142 for (var i
= 0, last
= 0; i
<= fmtstr
.length
; i
++) {
143 if (fmtstr
.charAt(i
) == '%' && fmtstr
.charAt(i
+ 1) == '{') {
145 parse(tokens
, fmtstr
.substring(last
, i
));
147 var j
= i
+ 1, nest
= 0;
151 for (var off
= j
+ 1, esc
= false; j
<= fmtstr
.length
; j
++) {
152 var ch
= fmtstr
.charAt(j
);
157 else if (ch
== '\\') {
160 else if (ch
== '{') {
163 else if (ch
== '}') {
165 subexpr
.push(fmtstr
.substring(off
, j
));
169 else if (ch
== '?' || ch
== ':' || ch
== '#') {
171 subexpr
.push(fmtstr
.substring(off
, j
));
178 var varname
= subexpr
[0].trim(),
179 op1
= (subexpr
[1] != null) ? subexpr
[1] : '?',
180 if_set
= (subexpr
[2] != null && subexpr
[2] != '') ? subexpr
[2] : '%{' + varname
+ '}',
181 op2
= (subexpr
[3] != null) ? subexpr
[3] : ':',
182 if_unset
= (subexpr
[4] != null) ? subexpr
[4] : '';
184 /* Invalid expression */
185 if (nest
!= 0 || subexpr
.length
> 5 || varname
== '') {
190 else if (op1
== '#' && subexpr
.length
== 3) {
191 var items
= L
.toArray(get(args
, varname
));
193 for (var k
= 0; k
< items
.length
; k
++) {
194 tokens
.push
.apply(tokens
, this.fmt(if_set
, Object
.assign({}, args
, {
197 last
: (k
+ 1) == items
.length
,
203 /* ternary expression */
204 else if (op1
== '?' && op2
== ':' && (subexpr
.length
== 1 || subexpr
.length
== 3 || subexpr
.length
== 5)) {
205 var val
= get(args
, varname
);
207 if (subexpr
.length
== 1)
208 parse(tokens
, isset(val
) ? val
: '');
210 tokens
.push
.apply(tokens
, this.fmt(if_set
, args
, values
));
212 tokens
.push
.apply(tokens
, this.fmt(if_unset
, args
, values
));
215 /* unrecognized command */
223 else if (i
>= fmtstr
.length
) {
225 parse(tokens
, fmtstr
.substring(last
, i
));
230 var node
= E('span', {}, tokens
.join('')),
231 repl
= node
.querySelectorAll('span[data-fmt-placeholder]');
233 for (var i
= 0; i
< repl
.length
; i
++)
234 repl
[i
].parentNode
.replaceChild(values
[repl
[i
].getAttribute('data-fmt-placeholder')], repl
[i
]);
243 map_invert: function(v
, fn
) {
244 return L
.toArray(v
).map(function(v
) {
247 if (fn
!= null && typeof(v
[fn
]) == 'function')
252 inv
: v
.charAt(0) == '!',
253 val
: v
.replace(/^!\s*/, '')
258 lookupProto
: lookupProto
,
260 addDSCPOption: function(s
, is_target
) {
261 var o
= s
.taboption(is_target
? 'general' : 'advanced', form
.Value
, is_target
? 'set_dscp' : 'dscp',
262 is_target
? _('DSCP mark') : _('Match DSCP'),
263 is_target
? _('Apply the given DSCP class or value to established connections.') : _('Matches traffic carrying the specified DSCP marking.'));
266 o
.rmempty
= !is_target
;
267 o
.placeholder
= _('any');
270 o
.depends('target', 'DSCP');
294 o
.validate = function(section_id
, value
) {
296 return is_target
? _('DSCP mark required') : true;
299 value
= String(value
).replace(/^!\s*/, '');
301 var m
= value
.match(/^(?:CS[0-7]|BE|AF[1234][123]|EF|(0x[0-9a-f]{1,2}|[0-9]{1,2}))$/);
303 if (!m
|| (m
[1] != null && +m
[1] > 0x3f))
304 return _('Invalid DSCP mark');
312 addMarkOption: function(s
, is_target
) {
313 var o
= s
.taboption(is_target
? 'general' : 'advanced', form
.Value
,
314 (is_target
> 1) ? 'set_xmark' : (is_target
? 'set_mark' : 'mark'),
315 (is_target
> 1) ? _('XOR mark') : (is_target
? _('Set mark') : _('Match mark')),
316 (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.') :
317 (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.') :
318 _('Matches a specific firewall mark or a range of different marks.')));
324 o
.depends('target', 'MARK_XOR');
326 o
.depends('target', 'MARK_SET');
328 o
.validate = function(section_id
, value
) {
330 return is_target
? _('Valid firewall mark required') : true;
333 value
= String(value
).replace(/^!\s*/, '');
335 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);
337 if (!m
|| +m
[1] > 0xffffffff || (m
[2] != null && +m
[2] > 0xffffffff))
338 return _('Expecting: %s').format(_('valid firewall mark'));
346 addLimitOption: function(s
) {
347 var o
= s
.taboption('advanced', form
.Value
, 'limit',
349 _('Limits traffic matching to the specified rate.'));
353 o
.placeholder
= _('unlimited');
354 o
.value('10/second');
355 o
.value('60/minute');
358 o
.validate = function(section_id
, value
) {
362 var m
= String(value
).toLowerCase().match(/^(?:0x[0-9a-f]{1,8}|[0-9]{1,10})\/([a-z]+)$/),
363 u
= ['second', 'minute', 'hour', 'day'],
367 for (i
= 0; i
< u
.length
; i
++)
368 if (u
[i
].indexOf(m
[1]) == 0)
371 if (!m
|| i
>= u
.length
)
372 return _('Invalid limit value');
380 addLimitBurstOption: function(s
) {
381 var o
= s
.taboption('advanced', form
.Value
, 'limit_burst',
383 _('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.'));
388 o
.datatype
= 'uinteger';
389 o
.depends({ limit
: null, '!reverse': true });
394 transformHostHints: function(family
, hosts
) {
395 var choice_values
= [],
400 for (var mac
in hosts
) {
401 L
.toArray(hosts
[mac
].ipaddrs
|| hosts
[mac
].ipv4
).forEach(function(ip
) {
405 L
.toArray(hosts
[mac
].ip6addrs
|| hosts
[mac
].ipv6
).forEach(function(ip
) {
410 if (!family
|| family
== 'ipv4') {
411 L
.sortedKeys(ipaddrs
, null, 'addr').forEach(function(ip
) {
413 txt
= hosts
[ipaddrs
[ip
]].name
|| ipaddrs
[ip
];
415 choice_values
.push(val
);
416 choice_labels
[val
] = E([], [ val
, ' (', E('strong', {}, [txt
]), ')' ]);
420 if (!family
|| family
== 'ipv6') {
421 L
.sortedKeys(ip6addrs
, null, 'addr').forEach(function(ip
) {
423 txt
= hosts
[ip6addrs
[ip
]].name
|| ip6addrs
[ip
];
425 choice_values
.push(val
);
426 choice_labels
[val
] = E([], [ val
, ' (', E('strong', {}, [txt
]), ')' ]);
430 return [choice_values
, choice_labels
];
433 updateHostHints: function(map
, section_id
, option
, family
, hosts
) {
434 var opt
= map
.lookupOption(option
, section_id
)[0].getUIElement(section_id
),
435 choices
= this.transformHostHints(family
, hosts
);
438 opt
.addChoices(choices
[0], choices
[1]);
441 CBIDynamicMultiValueList
: form
.DynamicList
.extend({
442 renderWidget: function(/* ... */) {
443 var dl
= form
.DynamicList
.prototype.renderWidget
.apply(this, arguments
),
444 inst
= dom
.findClassInstance(dl
);
446 inst
.addItem = function(dl
, value
, text
, flash
) {
447 var values
= L
.toArray(value
);
448 for (var i
= 0; i
< values
.length
; i
++)
449 ui
.DynamicList
.prototype.addItem
.call(this, dl
, values
[i
], null, true);
456 addIPOption: function(s
, tab
, name
, label
, description
, family
, hosts
, multiple
) {
457 var o
= s
.taboption(tab
, multiple
? this.CBIDynamicMultiValueList
: form
.Value
, name
, label
, description
);
460 o
.datatype
= 'list(neg(ipmask("true")))';
461 o
.placeholder
= multiple
? _('-- add IP --') : _('any');
463 if (family
!= null) {
464 var choices
= this.transformHostHints(family
, hosts
);
466 for (var i
= 0; i
< choices
[0].length
; i
++)
467 o
.value(choices
[0][i
], choices
[1][choices
[0][i
]]);
470 /* force combobox rendering */
471 o
.transformChoices = function() {
472 return this.super('transformChoices', []) || {};
478 addLocalIPOption: function(s
, tab
, name
, label
, description
, devices
) {
479 var o
= s
.taboption(tab
, form
.Value
, name
, label
, description
);
480 var fw4
= L
.hasSystemFeature('firewall4');
483 o
.datatype
= !fw4
?'ip4addr("nomask")':'ipaddr("nomask")';
484 o
.placeholder
= _('any');
486 L
.sortedKeys(devices
, 'name').forEach(function(dev
) {
487 var ip4addrs
= devices
[dev
].ipaddrs
;
488 var ip6addrs
= devices
[dev
].ip6addrs
;
490 if (!L
.isObject(devices
[dev
].flags
) || devices
[dev
].flags
.loopback
)
493 for (var i
= 0; Array
.isArray(ip4addrs
) && i
< ip4addrs
.length
; i
++) {
494 if (!L
.isObject(ip4addrs
[i
]) || !ip4addrs
[i
].address
)
497 o
.value(ip4addrs
[i
].address
, E([], [
498 ip4addrs
[i
].address
, ' (', E('strong', {}, [dev
]), ')'
501 for (var i
= 0; fw4
&& Array
.isArray(ip6addrs
) && i
< ip6addrs
.length
; i
++) {
502 if (!L
.isObject(ip6addrs
[i
]) || !ip6addrs
[i
].address
)
505 o
.value(ip6addrs
[i
].address
, E([], [
506 ip6addrs
[i
].address
, ' (', E('strong', {}, [dev
]), ')'
514 addMACOption: function(s
, tab
, name
, label
, description
, hosts
) {
515 var o
= s
.taboption(tab
, this.CBIDynamicMultiValueList
, name
, label
, description
);
518 o
.datatype
= 'list(macaddr)';
519 o
.placeholder
= _('-- add MAC --');
521 L
.sortedKeys(hosts
).forEach(function(mac
) {
522 o
.value(mac
, E([], [ mac
, ' (', E('strong', {}, [
524 L
.toArray(hosts
[mac
].ipaddrs
|| hosts
[mac
].ipv4
)[0] ||
525 L
.toArray(hosts
[mac
].ip6addrs
|| hosts
[mac
].ipv6
)[0] ||
533 CBIProtocolSelect
: form
.MultiValue
.extend({
534 __name__
: 'CBI.ProtocolSelect',
536 addChoice: function(value
, label
) {
537 if (!Array
.isArray(this.keylist
) || this.keylist
.indexOf(value
) == -1)
538 this.value(value
, label
);
541 load: function(section_id
) {
542 var cfgvalue
= L
.toArray(this.super('load', [section_id
]) || this.default).sort();
544 ['all', 'tcp', 'udp', 'icmp'].concat(cfgvalue
).forEach(L
.bind(function(value
) {
549 this.addChoice('all', _('Any'));
553 this.addChoice('tcp', 'TCP');
554 this.addChoice('udp', 'UDP');
558 var m
= value
.match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/),
559 p
= lookupProto(m
? +m
[1] : value
);
561 this.addChoice(p
[2], p
[1]);
566 if (cfgvalue
== '*' || cfgvalue
== 'any' || cfgvalue
== 'all')
572 renderWidget: function(section_id
, option_index
, cfgvalue
) {
573 var value
= (cfgvalue
!= null) ? cfgvalue
: this.default,
574 choices
= this.transformChoices();
576 var widget
= new ui
.Dropdown(L
.toArray(value
), choices
, {
577 id
: this.cbid(section_id
),
584 disabled
: (this.readonly
!= null) ? this.readonly
: this.map
.readonly
,
585 validate: function(value
) {
586 var v
= L
.toArray(value
);
588 for (var i
= 0; i
< v
.length
; i
++) {
592 var m
= v
[i
].match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/);
594 if (m
? (+m
[1] > 255) : (lookupProto(v
[i
])[0] == -1))
595 return _('Unrecognized protocol');
602 widget
.createChoiceElement = function(sb
, value
) {
603 var p
= lookupProto(value
);
605 return ui
.Dropdown
.prototype.createChoiceElement
.call(this, sb
, p
[2], p
[1]);
608 widget
.createItems = function(sb
, value
) {
609 var values
= L
.toArray(value
).map(function(value
) {
610 var m
= value
.match(/^(0x[0-9a-f]{1,2}|[0-9]{1,3})$/),
611 p
= lookupProto(m
? +m
[1] : value
);
613 return (p
[0] > -1) ? p
[2] : p
[1];
618 return ui
.Dropdown
.prototype.createItems
.call(this, sb
, values
.join(' '));
621 widget
.toggleItem = function(sb
, li
) {
622 var value
= li
.getAttribute('data-value'),
623 toggleFn
= ui
.Dropdown
.prototype.toggleItem
;
625 toggleFn
.call(this, sb
, li
);
627 if (value
== 'all') {
628 var items
= li
.parentNode
.querySelectorAll('li[data-value]');
630 for (var j
= 0; j
< items
.length
; j
++)
632 toggleFn
.call(this, sb
, items
[j
], false);
635 toggleFn
.call(this, sb
, li
.parentNode
.querySelector('li[data-value="all"]'), false);
639 return widget
.render();
643 checkLegacySNAT: function() {
644 var redirects
= uci
.sections('firewall', 'redirect');
646 for (var i
= 0; i
< redirects
.length
; i
++)
647 if ((redirects
[i
]['target'] || '').toLowerCase() == 'snat')
653 handleMigration: function(ev
) {
654 var redirects
= uci
.sections('firewall', 'redirect'),
660 reflection_src
: null,
662 src_dport
: 'snat_port',
666 for (var i
= 0; i
< redirects
.length
; i
++) {
667 if ((redirects
[i
]['target'] || '').toLowerCase() != 'snat')
670 var sid
= uci
.add('firewall', 'nat');
672 for (var opt
in redirects
[i
]) {
673 if (opt
.charAt(0) == '.')
676 if (mapping
[opt
] === null)
679 uci
.set('firewall', sid
, mapping
[opt
] || opt
, redirects
[i
][opt
]);
682 uci
.remove('firewall', redirects
[i
]['.name']);
686 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
687 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
690 renderMigration: function() {
691 ui
.showModal(_('Firewall configuration migration'), [
692 E('p', _('The existing firewall configuration needs to be changed for LuCI to function properly.')),
693 E('p', _('Upon pressing "Continue", "redirect" sections with target "SNAT" will be converted to "nat" sections and the firewall will be restarted to apply the updated configuration.')),
694 E('div', { 'class': 'right' },
696 'class': 'btn cbi-button-action important',
697 'click': ui
.createHandlerFn(this, 'handleMigration')