luci-app-firewall: support 'limit' and 'limit_burst' options
[project/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / forwards.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('ipv4'),
47 fwtool.fmt_proto(uci.get('firewall', s, 'proto'),
48 uci.get('firewall', s, 'icmp_type')) || 'TCP+UDP');
49 }
50
51 function forward_src_txt(s) {
52 var z = fwtool.fmt_zone(uci.get('firewall', s, 'src'), _('any zone')),
53 a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any host')),
54 p = fwtool.fmt_port(uci.get('firewall', s, 'src_port')),
55 m = fwtool.fmt_mac(uci.get('firewall', s, 'src_mac'));
56
57 if (p && m)
58 return fmt(_('From %s in %s with source %s and %s'), a, z, p, m);
59 else if (p || m)
60 return fmt(_('From %s in %s with source %s'), a, z, p || m);
61 else
62 return fmt(_('From %s in %s'), a, z);
63 }
64
65 function forward_via_txt(s) {
66 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_dip'), _('any router IP')),
67 p = fwtool.fmt_port(uci.get('firewall', s, 'src_dport'));
68
69 if (p)
70 return fmt(_('Via %s at %s'), a, p);
71 else
72 return fmt(_('Via %s'), a);
73 }
74
75 return L.view.extend({
76 callHostHints: rpc.declare({
77 object: 'luci-rpc',
78 method: 'getHostHints',
79 expect: { '': {} }
80 }),
81
82 callConntrackHelpers: rpc.declare({
83 object: 'luci',
84 method: 'getConntrackHelpers',
85 expect: { result: [] }
86 }),
87
88 load: function() {
89 return Promise.all([
90 this.callHostHints(),
91 this.callConntrackHelpers()
92 ]);
93 },
94
95 render: function(data) {
96 var hosts = data[0],
97 ctHelpers = data[1],
98 m, s, o;
99
100 m = new form.Map('firewall', _('Firewall - Port Forwards'),
101 _('Port forwarding allows remote computers on the Internet to connect to a specific computer or service within the private LAN.'));
102
103 s = m.section(form.GridSection, 'redirect', _('Port Forwards'));
104 s.addremove = true;
105 s.anonymous = true;
106 s.sortable = true;
107
108 s.tab('general', _('General Settings'));
109 s.tab('advanced', _('Advanced Settings'));
110
111 s.filter = function(section_id) {
112 return (uci.get('firewall', section_id, 'target') != 'SNAT');
113 };
114
115 s.sectiontitle = function(section_id) {
116 return uci.get('firewall', section_id, 'name') || _('Unnamed forward');
117 };
118
119 s.handleAdd = function(ev) {
120 var config_name = this.uciconfig || this.map.config,
121 section_id = uci.add(config_name, this.sectiontype);
122
123 uci.set(config_name, section_id, 'target', 'DNAT');
124
125 this.addedSection = section_id;
126 this.renderMoreOptionsModal(section_id);
127 };
128
129 o = s.taboption('general', form.Value, 'name', _('Name'));
130 o.placeholder = _('Unnamed forward');
131 o.modalonly = true;
132
133 o = s.option(form.DummyValue, '_match', _('Match'));
134 o.modalonly = false;
135 o.textvalue = function(s) {
136 return E('small', [
137 forward_proto_txt(s), E('br'),
138 forward_src_txt(s), E('br'),
139 forward_via_txt(s)
140 ]);
141 };
142
143 o = s.option(form.ListValue, '_dest', _('Forward to'));
144 o.modalonly = false;
145 o.textvalue = function(s) {
146 var z = fwtool.fmt_zone(uci.get('firewall', s, 'dest'), _('any zone')),
147 a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any host')),
148 p = fwtool.fmt_port(uci.get('firewall', s, 'dest_port')) ||
149 fwtool.fmt_port(uci.get('firewall', s, 'src_dport'));
150
151 if (p)
152 return fmt(_('%s, %s in %s'), a, p, z);
153 else
154 return fmt(_('%s in %s'), a, z);
155 };
156
157 o = s.option(form.Flag, 'enabled', _('Enable'));
158 o.modalonly = false;
159 o.default = o.enabled;
160 o.editable = true;
161
162 o = s.taboption('general', form.Value, 'proto', _('Protocol'));
163 o.modalonly = true;
164 o.default = 'tcp udp';
165 o.value('tcp udp', 'TCP+UDP');
166 o.value('tcp', 'TCP');
167 o.value('udp', 'UDP');
168 o.value('icmp', 'ICMP');
169
170 o.cfgvalue = function(/* ... */) {
171 var v = this.super('cfgvalue', arguments);
172 return (v == 'tcpudp') ? 'tcp udp' : v;
173 };
174
175 o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
176 o.modalonly = true;
177 o.rmempty = false;
178 o.nocreate = true;
179 o.default = 'wan';
180
181 o = s.taboption('advanced', form.Value, 'src_mac', _('Source MAC address'),
182 _('Only match incoming traffic from these MACs.'));
183 o.modalonly = true;
184 o.rmempty = true;
185 o.datatype = 'neg(macaddr)';
186 o.placeholder = E('em', _('any'));
187 L.sortedKeys(hosts).forEach(function(mac) {
188 o.value(mac, '%s (%s)'.format(
189 mac,
190 hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?'
191 ));
192 });
193
194 o = s.taboption('advanced', form.Value, 'src_ip', _('Source IP address'),
195 _('Only match incoming traffic from this IP or range.'));
196 o.modalonly = true;
197 o.rmempty = true;
198 o.datatype = 'neg(ipmask4)';
199 o.placeholder = E('em', _('any'));
200 L.sortedKeys(hosts, 'ipv4', 'addr').forEach(function(mac) {
201 o.value(hosts[mac].ipv4, '%s (%s)'.format(
202 hosts[mac].ipv4,
203 hosts[mac].name || mac
204 ));
205 });
206
207 o = s.taboption('advanced', form.Value, 'src_port', _('Source port'),
208 _('Only match incoming traffic originating from the given source port or port range on the client host'));
209 o.modalonly = true;
210 o.rmempty = true;
211 o.datatype = 'neg(portrange)';
212 o.placeholder = _('any');
213 o.depends('proto', 'tcp');
214 o.depends('proto', 'udp');
215 o.depends('proto', 'tcp udp');
216 o.depends('proto', 'tcpudp');
217
218 o = s.taboption('advanced', form.Value, 'src_dip', _('External IP address'),
219 _('Only match incoming traffic directed at the given IP address.'));
220 o.modalonly = true;
221 o.rmempty = true;
222 o.datatype = 'neg(ipmask4)';
223 o.placeholder = E('em', _('any'));
224 L.sortedKeys(hosts, 'ipv4', 'addr').forEach(function(mac) {
225 o.value(hosts[mac].ipv4, '%s (%s)'.format(
226 hosts[mac].ipv4,
227 hosts[mac].name || mac
228 ));
229 });
230
231 o = s.taboption('general', form.Value, 'src_dport', _('External port'),
232 _('Match incoming traffic directed at the given destination port or port range on this host'));
233 o.modalonly = true;
234 o.rmempty = false;
235 o.datatype = 'neg(portrange)';
236 o.depends('proto', 'tcp');
237 o.depends('proto', 'udp');
238 o.depends('proto', 'tcp udp');
239 o.depends('proto', 'tcpudp');
240
241 o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Internal zone'));
242 o.modalonly = true;
243 o.rmempty = true;
244 o.nocreate = true;
245 o.default = 'lan';
246
247 o = s.taboption('general', form.Value, 'dest_ip', _('Internal IP address'),
248 _('Redirect matched incoming traffic to the specified internal host'));
249 o.modalonly = true;
250 o.rmempty = true;
251 o.datatype = 'ipmask4';
252 L.sortedKeys(hosts, 'ipv4', 'addr').forEach(function(mac) {
253 o.value(hosts[mac].ipv4, '%s (%s)'.format(
254 hosts[mac].ipv4,
255 hosts[mac].name || mac
256 ));
257 });
258
259 o = s.taboption('general', form.Value, 'dest_port', _('Internal port'),
260 _('Redirect matched incoming traffic to the given port on the internal host'));
261 o.modalonly = true;
262 o.rmempty = true;
263 o.placeholder = _('any');
264 o.datatype = 'portrange';
265 o.depends('proto', 'tcp');
266 o.depends('proto', 'udp');
267 o.depends('proto', 'tcp udp');
268 o.depends('proto', 'tcpudp');
269
270 o = s.taboption('advanced', form.Flag, 'reflection', _('Enable NAT Loopback'));
271 o.modalonly = true;
272 o.rmempty = true;
273 o.default = o.enabled;
274
275 o = s.taboption('advanced', form.ListValue, 'reflection_src', _('Loopback source IP'), _('Specifies whether to use the external or the internal IP address for reflected traffic.'));
276 o.modalonly = true;
277 o.depends('reflection', '1');
278 o.value('internal', _('Use internal IP address'));
279 o.value('external', _('Use external IP address'));
280 o.write = function(section_id, value) {
281 uci.set('firewall', section_id, 'reflection_src', (value != 'internal') ? value : null);
282 };
283
284 o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
285 o.modalonly = true;
286 o.placeholder = _('any');
287 for (var i = 0; i < ctHelpers.length; i++)
288 o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
289 o.validate = function(section_id, value) {
290 if (value == '' || value == null)
291 return true;
292
293 value = value.replace(/^!\s*/, '');
294
295 for (var i = 0; i < ctHelpers.length; i++)
296 if (value == ctHelpers[i].name)
297 return true;
298
299 return _('Unknown or not installed conntrack helper "%s"').format(value);
300 };
301
302 o = s.taboption('advanced', form.Value, 'mark', _('Match mark'),
303 _('Matches a specific firewall mark or a range of different marks.'));
304 o.modalonly = true;
305 o.rmempty = true;
306 o.validate = function(section_id, value) {
307 if (value == '')
308 return true;
309
310 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);
311
312 if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
313 return _('Expecting: %s').format(_('valid firewall mark'));
314
315 return true;
316 };
317
318 o = s.taboption('advanced', form.Value, 'limit', _('Limit matching'),
319 _('Limits traffic matching to the specified rate.'));
320 o.modalonly = true;
321 o.rmempty = true;
322 o.placeholder = _('unlimited');
323 o.value('10/second');
324 o.value('60/minute');
325 o.value('3/hour');
326 o.value('500/day');
327 o.validate = function(section_id, value) {
328 if (value == '')
329 return true;
330
331 var m = String(value).toLowerCase().match(/^(?:0x[0-9a-f]{1,8}|[0-9]{1,10})\/([a-z]+)$/),
332 u = ['second', 'minute', 'hour', 'day'],
333 i = 0;
334
335 if (m)
336 for (i = 0; i < u.length; i++)
337 if (u[i].indexOf(m[1]) == 0)
338 break;
339
340 if (!m || i >= u.length)
341 return _('Invalid limit value');
342
343 return true;
344 };
345
346 o = s.taboption('advanced', form.Value, 'limit_burst', _('Limit burst'),
347 _('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.'));
348 o.modalonly = true;
349 o.rmempty = true;
350 o.placeholder = '5';
351 o.datatype = 'uinteger';
352 o.depends({ limit: null, '!reverse': true });
353
354 o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
355 _('Passes additional arguments to iptables. Use with care!'));
356 o.modalonly = true;
357 o.rmempty = true;
358
359 return m.render();
360 }
361 });