luci-app-firewall: support 'helper' and 'reflection_src' parameters for redirects
[oweals/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, 'extra', _('Extra arguments'),
303                         _('Passes additional arguments to iptables. Use with care!'));
304                 o.modalonly = true;
305                 o.rmempty = true;
306
307                 return m.render();
308         }
309 });