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('ipv4'),
47 fwtool
.fmt_proto(uci
.get('firewall', s
, 'proto'),
48 uci
.get('firewall', s
, 'icmp_type')) || 'TCP+UDP');
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'));
58 return fmt(_('From %s in %s with source %s and %s'), a
, z
, p
, m
);
60 return fmt(_('From %s in %s with source %s'), a
, z
, p
|| m
);
62 return fmt(_('From %s in %s'), a
, z
);
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'));
70 return fmt(_('Via %s at %s'), a
, p
);
72 return fmt(_('Via %s'), a
);
75 return L
.view
.extend({
76 callHostHints
: rpc
.declare({
78 method
: 'getHostHints',
82 callConntrackHelpers
: rpc
.declare({
84 method
: 'getConntrackHelpers',
85 expect
: { result
: [] }
91 this.callConntrackHelpers()
95 render: function(data
) {
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.'));
103 s
= m
.section(form
.GridSection
, 'redirect', _('Port Forwards'));
108 s
.tab('general', _('General Settings'));
109 s
.tab('advanced', _('Advanced Settings'));
111 s
.filter = function(section_id
) {
112 return (uci
.get('firewall', section_id
, 'target') != 'SNAT');
115 s
.sectiontitle = function(section_id
) {
116 return uci
.get('firewall', section_id
, 'name') || _('Unnamed forward');
119 s
.handleAdd = function(ev
) {
120 var config_name
= this.uciconfig
|| this.map
.config
,
121 section_id
= uci
.add(config_name
, this.sectiontype
);
123 uci
.set(config_name
, section_id
, 'target', 'DNAT');
125 this.addedSection
= section_id
;
126 this.renderMoreOptionsModal(section_id
);
129 o
= s
.taboption('general', form
.Value
, 'name', _('Name'));
130 o
.placeholder
= _('Unnamed forward');
133 o
= s
.option(form
.DummyValue
, '_match', _('Match'));
135 o
.textvalue = function(s
) {
137 forward_proto_txt(s
), E('br'),
138 forward_src_txt(s
), E('br'),
143 o
= s
.option(form
.ListValue
, '_dest', _('Forward to'));
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'));
152 return fmt(_('%s, %s in %s'), a
, p
, z
);
154 return fmt(_('%s in %s'), a
, z
);
157 o
= s
.option(form
.Flag
, 'enabled', _('Enable'));
159 o
.default = o
.enabled
;
162 o
= s
.taboption('general', form
.Value
, 'proto', _('Protocol'));
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');
170 o
.cfgvalue = function(/* ... */) {
171 var v
= this.super('cfgvalue', arguments
);
172 return (v
== 'tcpudp') ? 'tcp udp' : v
;
175 o
= s
.taboption('general', widgets
.ZoneSelect
, 'src', _('Source zone'));
181 o
= s
.taboption('advanced', form
.Value
, 'src_mac', _('Source MAC address'),
182 _('Only match incoming traffic from these MACs.'));
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(
190 hosts
[mac
].name
|| hosts
[mac
].ipv4
|| hosts
[mac
].ipv6
|| '?'
194 o
= s
.taboption('advanced', form
.Value
, 'src_ip', _('Source IP address'),
195 _('Only match incoming traffic from this IP or range.'));
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(
203 hosts
[mac
].name
|| mac
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'));
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');
218 o
= s
.taboption('advanced', form
.Value
, 'src_dip', _('External IP address'),
219 _('Only match incoming traffic directed at the given IP address.'));
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(
227 hosts
[mac
].name
|| mac
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'));
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');
241 o
= s
.taboption('general', widgets
.ZoneSelect
, 'dest', _('Internal zone'));
247 o
= s
.taboption('general', form
.Value
, 'dest_ip', _('Internal IP address'),
248 _('Redirect matched incoming traffic to the specified internal host'));
251 o
.datatype
= 'ipmask4';
252 L
.sortedKeys(hosts
, 'ipv4', 'addr').forEach(function(mac
) {
253 o
.value(hosts
[mac
].ipv4
, '%s (%s)'.format(
255 hosts
[mac
].name
|| mac
259 o
= s
.taboption('general', form
.Value
, 'dest_port', _('Internal port'),
260 _('Redirect matched incoming traffic to the given port on the internal host'));
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');
270 o
= s
.taboption('advanced', form
.Flag
, 'reflection', _('Enable NAT Loopback'));
273 o
.default = o
.enabled
;
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.'));
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);
284 o
= s
.taboption('advanced', form
.Value
, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
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)
293 value
= value
.replace(/^!\s*/, '');
295 for (var i
= 0; i
< ctHelpers
.length
; i
++)
296 if (value
== ctHelpers
[i
].name
)
299 return _('Unknown or not installed conntrack helper "%s"').format(value
);
302 o
= s
.taboption('advanced', form
.Value
, 'mark', _('Match mark'),
303 _('Matches a specific firewall mark or a range of different marks.'));
306 o
.validate = function(section_id
, value
) {
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);
312 if (!m
|| +m
[1] > 0xffffffff || (m
[2] != null && +m
[2] > 0xffffffff))
313 return _('Expecting: %s').format(_('valid firewall mark'));
318 o
= s
.taboption('advanced', form
.Value
, 'limit', _('Limit matching'),
319 _('Limits traffic matching to the specified rate.'));
322 o
.placeholder
= _('unlimited');
323 o
.value('10/second');
324 o
.value('60/minute');
327 o
.validate = function(section_id
, value
) {
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'],
336 for (i
= 0; i
< u
.length
; i
++)
337 if (u
[i
].indexOf(m
[1]) == 0)
340 if (!m
|| i
>= u
.length
)
341 return _('Invalid limit value');
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.'));
351 o
.datatype
= 'uinteger';
352 o
.depends({ limit
: null, '!reverse': true });
354 o
= s
.taboption('advanced', form
.Value
, 'extra', _('Extra arguments'),
355 _('Passes additional arguments to iptables. Use with care!'));