6 'require tools.firewall as fwtool';
7 'require tools.widgets as widgets';
9 function fmt(fmt /*, ...*/) {
10 var repl = [], wrap = false;
12 for (var i = 1; i < arguments.length; i++) {
13 if (L.dom.elem(arguments[i])) {
14 switch (arguments[i].nodeType) {
16 repl.push(arguments[i].outerHTML);
21 repl.push(arguments[i].data);
26 span.appendChild(arguments[i]);
27 repl.push(span.innerHTML);
36 repl.push(arguments[i]);
40 var rv = fmt.format.apply(fmt, repl);
41 return wrap ? E('span', rv) : rv;
44 function forward_proto_txt(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');
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'));
58 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any host'));
60 return fmt(_('From %s in %s with source %s and %s'), a, z, p, m);
62 return fmt(_('From %s in %s with source %s'), a, z, p || m);
64 return fmt(_('From %s in %s'), a, z);
69 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any router IP'));
71 return fmt(_('From %s on <var>this device</var> with source %s and %s'), a, p, m);
73 return fmt(_('From %s on <var>this device</var> with source %s'), a, p || m);
75 return fmt(_('From %s on <var>this device</var>'), a);
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'));
85 var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any host'));
87 return fmt(_('To %s, %s in %s'), a, p, z);
89 return fmt(_('To %s in %s'), a, z);
94 var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any router IP'));
96 return fmt(_('To %s at %s on <var>this device</var>'), a, p);
98 return fmt(_('To %s on <var>this device</var>'), a);
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'));
107 return fmt(_('<var>%s</var> and limit to %s'), t, l);
109 return fmt('<var>%s</var>', t);
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 = {};
117 elem_src_ip.clearChoices();
118 elem_dst_ip.clearChoices();
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);
125 choice_values.push(val);
126 choice_labels[val] = txt;
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);
135 choice_values.push(val);
136 choice_labels[val] = txt;
140 elem_src_ip.addChoices(choice_values, choice_labels);
141 elem_dst_ip.addChoices(choice_values, choice_labels);
144 return L.view.extend({
145 callHostHints: rpc.declare({
147 method: 'getHostHints',
151 callConntrackHelpers: rpc.declare({
153 method: 'getConntrackHelpers',
154 expect: { result: [] }
159 this.callHostHints(),
160 this.callConntrackHelpers()
164 render: function(data) {
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.'));
172 s = m.section(form.GridSection, 'rule', _('Traffic Rules'));
177 s.tab('general', _('General Settings'));
178 s.tab('advanced', _('Advanced Settings'));
179 s.tab('timed', _('Time Restrictions'));
181 s.filter = function(section_id) {
182 return (uci.get('firewall', section_id, 'target') != 'SNAT');
185 s.sectiontitle = function(section_id) {
186 return uci.get('firewall', section_id, 'name') || _('Unnamed rule');
189 s.handleAdd = function(ev) {
190 var config_name = this.uciconfig || this.map.config,
191 section_id = uci.add(config_name, this.sectiontype),
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];
200 opt1.default = 'wan';
201 opt2.default = 'lan';
203 this.addedSection = section_id;
204 this.renderMoreOptionsModal(section_id);
210 o = s.taboption('general', form.Value, 'name', _('Name'));
211 o.placeholder = _('Unnamed rule');
214 o = s.option(form.DummyValue, '_match', _('Match'));
216 o.textvalue = function(s) {
218 forward_proto_txt(s), E('br'),
219 rule_src_txt(s), E('br'),
224 o = s.option(form.ListValue, '_target', _('Action'));
226 o.textvalue = function(s) {
227 return rule_target_txt(s);
230 o = s.option(form.Flag, 'enabled', _('Enable'));
232 o.default = o.enabled;
236 o = s.taboption('advanced', form.ListValue, 'direction', _('Match device'));
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');
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.'));
261 o.depends('direction', 'in');
262 o.depends('direction', 'out');
264 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
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);
275 o = s.taboption('general', form.Value, 'proto', _('Protocol'));
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;
288 o = s.taboption('advanced', form.MultiValue, 'icmp_type', _('Match ICMP type'));
293 o.placeholder = _('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');
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');
335 o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
339 o.allowlocal = 'src';
341 o = s.taboption('advanced', form.Value, 'src_mac', _('Source MAC address'));
343 o.datatype = 'list(macaddr)';
344 o.placeholder = _('any');
345 L.sortedKeys(hosts).forEach(function(mac) {
346 o.value(mac, '%s (%s)'.format(
348 hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?'
352 o = s.taboption('general', form.Value, 'src_ip', _('Source address'));
354 o.datatype = 'list(neg(ipmask))';
355 o.placeholder = _('any');
356 o.transformChoices = function() { return {} }; /* force combobox rendering */
358 o = s.taboption('general', form.Value, 'src_port', _('Source port'));
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');
367 o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Destination zone'));
373 o = s.taboption('general', form.Value, 'dest_ip', _('Destination address'));
375 o.datatype = 'list(neg(ipmask))';
376 o.placeholder = _('any');
377 o.transformChoices = function() { return {} }; /* force combobox rendering */
379 o = s.taboption('general', form.Value, 'dest_port', _('Destination port'));
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');
388 o = s.taboption('general', form.ListValue, 'target', _('Action'));
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 o.value('MARK_SET', _('apply firewall mark'));
397 o.value('MARK_XOR', _('XOR firewall mark'));
398 o.cfgvalue = function(section_id) {
399 var t = uci.get('firewall', section_id, 'target'),
400 m = uci.get('firewall', section_id, 'set_mark');
403 return m ? 'MARK_SET' : 'MARK_XOR';
407 o.write = function(section_id, value) {
408 return this.super('write', [section_id, (value == 'MARK_SET' || value == 'MARK_XOR') ? 'MARK' : value]);
411 o = s.taboption('general', form.Value, 'set_mark', _('Set mark'), _('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.'));
414 o.depends('target', 'MARK_SET');
415 o.validate = function(section_id, value) {
419 var m = String(value).match(/^(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
421 if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
422 return _('Expecting: %s').format(_('valid firewall mark'));
427 o = s.taboption('general', form.Value, 'set_xmark', _('XOR mark'), _('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.'));
430 o.depends('target', 'MARK_XOR');
431 o.validate = function(section_id, value) {
435 var m = String(value).match(/^(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
437 if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
438 return _('Expecting: %s').format(_('valid firewall mark'));
443 o = s.taboption('general', form.ListValue, 'set_helper', _('Tracking helper'), _('Assign the specified connection tracking helper to matched traffic.'));
445 o.placeholder = _('any');
446 o.depends('target', 'HELPER');
447 for (var i = 0; i < ctHelpers.length; i++)
448 o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
450 o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
452 o.placeholder = _('any');
453 for (var i = 0; i < ctHelpers.length; i++)
454 o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
455 o.validate = function(section_id, value) {
456 if (value == '' || value == null)
459 value = value.replace(/^!\s*/, '');
461 for (var i = 0; i < ctHelpers.length; i++)
462 if (value == ctHelpers[i].name)
465 return _('Unknown or not installed conntrack helper "%s"').format(value);
468 o = s.taboption('advanced', form.Value, 'mark', _('Match mark'),
469 _('Matches a specific firewall mark or a range of different marks.'));
472 o.validate = function(section_id, value) {
476 var m = String(value).match(/^(?:!\s*)?(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
478 if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
479 return _('Expecting: %s').format(_('valid firewall mark'));
484 o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
485 _('Passes additional arguments to iptables. Use with care!'));
488 o = s.taboption('timed', form.MultiValue, 'weekdays', _('Week Days'));
492 o.placeholder = _('Any day');
493 o.value('Sun', _('Sunday'));
494 o.value('Mon', _('Monday'));
495 o.value('Tue', _('Tuesday'));
496 o.value('Wed', _('Wednesday'));
497 o.value('Thu', _('Thursday'));
498 o.value('Fri', _('Friday'));
499 o.value('Sat', _('Saturday'));
500 o.write = function(section_id, value) {
501 return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
504 o = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days'));
508 o.placeholder = _('Any day');
509 o.write = function(section_id, value) {
510 return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
512 for (var i = 1; i <= 31; i++)
515 o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh.mm.ss)'));
517 o.datatype = 'timehhmmss';
519 o = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh.mm.ss)'));
521 o.datatype = 'timehhmmss';
523 o = s.taboption('timed', form.Value, 'start_date', _('Start Date (yyyy-mm-dd)'));
525 o.datatype = 'dateyyyymmdd';
527 o = s.taboption('timed', form.Value, 'stop_date', _('Stop Date (yyyy-mm-dd)'));
529 o.datatype = 'dateyyyymmdd';
531 o = s.taboption('timed', form.Flag, 'utc_time', _('Time in UTC'));
533 o.default = o.disabled;