function forward_proto_txt(s) {
return fmt('%s-%s',
- fwtool.fmt_family(uci.get('firewall', s, 'family')),
+ fwtool.fmt_family('ipv4'),
fwtool.fmt_proto(uci.get('firewall', s, 'proto'),
uci.get('firewall', s, 'icmp_type')) || 'TCP+UDP');
}
return L.view.extend({
callHostHints: rpc.declare({
+ object: 'luci-rpc',
+ method: 'getHostHints',
+ expect: { '': {} }
+ }),
+
+ callConntrackHelpers: rpc.declare({
object: 'luci',
- method: 'host_hints'
+ method: 'getConntrackHelpers',
+ expect: { result: [] }
}),
load: function() {
return Promise.all([
- this.callHostHints()
+ this.callHostHints(),
+ this.callConntrackHelpers()
]);
},
render: function(data) {
var hosts = data[0],
+ ctHelpers = data[1],
m, s, o;
m = new form.Map('firewall', _('Firewall - Port Forwards'),
o.rmempty = true;
o.default = o.enabled;
+ 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.'));
+ o.modalonly = true;
+ o.depends('reflection', '1');
+ o.value('internal', _('Use internal IP address'));
+ o.value('external', _('Use external IP address'));
+ o.write = function(section_id, value) {
+ uci.set('firewall', section_id, 'reflection_src', (value != 'internal') ? value : null);
+ };
+
+ o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
+ o.modalonly = true;
+ o.placeholder = _('any');
+ for (var i = 0; i < ctHelpers.length; i++)
+ o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
+ o.validate = function(section_id, value) {
+ if (value == '' || value == null)
+ return true;
+
+ value = value.replace(/^!\s*/, '');
+
+ for (var i = 0; i < ctHelpers.length; i++)
+ if (value == ctHelpers[i].name)
+ return true;
+
+ return _('Unknown or not installed conntrack helper "%s"').format(value);
+ };
+
+ o = s.taboption('advanced', form.Value, 'mark', _('Match mark'),
+ _('Matches a specific firewall mark or a range of different marks.'));
+ o.modalonly = true;
+ o.rmempty = true;
+ o.validate = function(section_id, value) {
+ if (value == '')
+ return true;
+
+ 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);
+
+ if (!m || +m[1] > 0xffffffff || (m[2] != null && +m[2] > 0xffffffff))
+ return _('Expecting: %s').format(_('valid firewall mark'));
+
+ return true;
+ };
+
+ o = s.taboption('advanced', form.Value, 'limit', _('Limit matching'),
+ _('Limits traffic matching to the specified rate.'));
+ o.modalonly = true;
+ o.rmempty = true;
+ o.placeholder = _('unlimited');
+ o.value('10/second');
+ o.value('60/minute');
+ o.value('3/hour');
+ o.value('500/day');
+ o.validate = function(section_id, value) {
+ if (value == '')
+ return true;
+
+ var m = String(value).toLowerCase().match(/^(?:0x[0-9a-f]{1,8}|[0-9]{1,10})\/([a-z]+)$/),
+ u = ['second', 'minute', 'hour', 'day'],
+ i = 0;
+
+ if (m)
+ for (i = 0; i < u.length; i++)
+ if (u[i].indexOf(m[1]) == 0)
+ break;
+
+ if (!m || i >= u.length)
+ return _('Invalid limit value');
+
+ return true;
+ };
+
+ o = s.taboption('advanced', form.Value, 'limit_burst', _('Limit burst'),
+ _('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.'));
+ o.modalonly = true;
+ o.rmempty = true;
+ o.placeholder = '5';
+ o.datatype = 'uinteger';
+ o.depends({ limit: null, '!reverse': true });
+
o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
_('Passes additional arguments to iptables. Use with care!'));
o.modalonly = true;