luci-app-firewall: support 'limit' and 'limit_burst' options
[oweals/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / forwards.js
index 63af69f8a939609c38c360300c6ae3ee252ad39e..f8b9e19e121418c48a1d2449003bb97f7c91622e 100644 (file)
@@ -43,7 +43,7 @@ function fmt(fmt /*, ...*/) {
 
 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');
 }
@@ -74,18 +74,27 @@ function forward_via_txt(s) {
 
 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'),
@@ -263,6 +272,85 @@ return L.view.extend({
                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;