luci-app-banip: sync with banIP-1.8.0-1 master
authorDirk Brenken <dev@brenken.org>
Mon, 12 Jan 2026 22:04:39 +0000 (23:04 +0100)
committerDirk Brenken <dev@brenken.org>
Mon, 12 Jan 2026 22:04:58 +0000 (23:04 +0100)
Signed-off-by: Dirk Brenken <dev@brenken.org>
applications/luci-app-banip/Makefile
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/allowlist.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/blocklist.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/feeds.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/logtemplate.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/overview.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/setreport.js
applications/luci-app-banip/root/usr/share/rpcd/acl.d/luci-app-banip.json

index ce20f7740e09953972849e4bb632a62e086043fb..bc6381faa516cc1ab5d0849ff5f5f9e34c98d3e0 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2018-2025 Dirk Brenken (dev@brenken.org)
+# Copyright 2018-2026 Dirk Brenken (dev@brenken.org)
 # This is free software, licensed under the Apache License, Version 2.0
 
 include $(TOPDIR)/rules.mk
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
 LUCI_TITLE:=LuCI support for banIP
 LUCI_DEPENDS:=+luci-base +banip
 
-PKG_VERSION:=1.6.0
+PKG_VERSION:=1.8.0
 PKG_RELEASE:=1
 PKG_LICENSE:=Apache-2.0
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
index 43f62cd8db606cffd156372706941752958bf631..678e3c2389039c8c81dbfc0027b52b2821486ee1 100644 (file)
@@ -13,7 +13,7 @@ const resetScroll = () => {
 return view.extend({
        load: function () {
                return L.resolveDefault(fs.stat(localFile), "")
-               .then(function (stat) {
+                       .then(function (stat) {
                        if (!stat) {
                                return fs.write(localFile, "");
                        }
@@ -24,7 +24,7 @@ return view.extend({
                });
        },
        render: function (allowlist) {
-               if (allowlist[0].size >= 100000) {
+               if (allowlist[0] && allowlist[0].size >= 100000) {
                        resetScroll();
                        ui.addNotification(null, E('p', _('The allowlist is too big, unable to save modifications.')), 'error');
                }
index a676bc8cc56d56e19119cb03ee872d499ba53e6d..ad5c3fd39e3e90ef0ec2769534f0d0317613f527 100644 (file)
@@ -13,7 +13,7 @@ const resetScroll = () => {
 return view.extend({
        load: function () {
                return L.resolveDefault(fs.stat(localFile), "")
-               .then(function (stat) {
+                       .then(function (stat) {
                        if (!stat) {
                                return fs.write(localFile, "");
                        }
@@ -24,19 +24,19 @@ return view.extend({
                });
        },
        render: function (blocklist) {
-               if (blocklist[0].size >= 100000) {
+               if (blocklist[0] && blocklist[0].size >= 100000) {
                        resetScroll();
                        ui.addNotification(null, E('p', _('The blocklist is too big, unable to save modifications.')), 'error');
                }
                return E('div', { 'class': 'cbi-section cbi-section-descr' }, [
                        E('p', _('This is the local banIP blocklist that will prevent certain MAC-, IP-addresses or domain names.<br /> \
                                <em><b>Please note:</b></em> add only exactly one MAC/IPv4/IPv6 address or domain name per line. Ranges in CIDR notation and MAC/IP-bindings are allowed.')),
-                               E('textarea', {
-                                       'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em',
-                                       'spellcheck': 'false',
-                                       'wrap': 'off',
-                                       'rows': 25
-                               }, [blocklist[1] != null ? blocklist[1] : ''])
+                       E('textarea', {
+                               'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em',
+                               'spellcheck': 'false',
+                               'wrap': 'off',
+                               'rows': 25
+                       }, [blocklist[1] != null ? blocklist[1] : ''])
                ]);
        },
        handleSave: function (ev) {
index 4ac74f4710bb6b206f43df405b8a10a74ab88cdc..25ad62693c286c0d2fb0d13b61317343266dab8a 100644 (file)
@@ -119,59 +119,50 @@ function handleEdit(ev) {
                        return ui.addNotification(null, E('p', _('Invalid input values, unable to save modifications.')), 'error');
                }
        }
-       let sumSubElements = [], exportJson;
+       /*
+               gather all input data
+       */
+       let sumSubElements = [];
        const nodeKeys = document.querySelectorAll('[id^="widget.cbid.json"][id$="name"]');
-       for (let i = 0; i < nodeKeys.length; i++) {
-               let subElements = {};
-               const elements = document.querySelectorAll('[id^="widget.cbid.json.' + nodeKeys[i].id.split('.')[3] + '\."], \
-                       [id^="cbid.json.' + nodeKeys[i].id.split('.')[3] + '\.rule_4"], \
-                       [id^="cbid.json.' + nodeKeys[i].id.split('.')[3] + '\.rule_6"]');
-               for (const element of elements) {
-                       let key;
-                       const value = element.value || "";
-                       const parts = element.id.split('.');
-                       if (parts.length === 5) {
-                               key = element.id.split('.')[4];
-                       } else if (parts.length === 4) {
-                               key = element.id.split('.')[3];
-                       }
-                       if (!key || value === "") {
-                               continue;
-                       }
-                       switch (key) {
-                               case 'url_4':
-                                       subElements.url_4 = value;
-                                       break;
-                               case 'rule_4':
-                                       subElements.rule_4 = value;
-                                       break;
-                               case 'url_6':
-                                       subElements.url_6 = value;
-                                       break;
-                               case 'rule_6':
-                                       subElements.rule_6 = value;
-                                       break;
-                               case 'chain':
-                                       subElements.chain = value;
-                                       break;
-                               case 'descr':
-                                       subElements.descr = value;
-                                       break;
-                               case 'flag':
-                                       subElements.flag = value;
-                                       break;
+       for (const keyNode of nodeKeys) {
+               const keyValue = keyNode.value?.trim();
+               if (!keyValue) continue;
+               const idParts = keyNode.id.split(".");
+               const ruleId = idParts[3];
+               if (!ruleId) continue;
+               const selector =
+                       `[id^="widget.cbid.json.${ruleId}."], ` +
+                       `[id^="cbid.json.${ruleId}.rule"]`;
+               const elements = document.querySelectorAll(selector);
+               const sub = {};
+               for (const el of elements) {
+                       const parts = el.id.split(".");
+                       const key = parts[parts.length - 1];
+                       const value = el.value?.trim();
+                       if (!value) continue;
+                       if (["url_4", "url_6", "rule", "chain", "descr", "flag"].includes(key)) {
+                               sub[key] = value;
                        }
                }
-               if (nodeKeys[i].value !== "" && subElements.descr !== "") {
-                       sumSubElements.push(nodeKeys[i].value, subElements);
+               if (sub.descr) {
+                       sumSubElements.push(keyValue, sub);
                }
        }
-       if (sumSubElements.length > 0) {
-               exportJson = JSON.stringify(sumSubElements).replace(/^\[/, '{\n').replace(/\}]$/, '\n\t}\n}\n').replace(/,{"/g, ':{\n\t"').replace(/"},"/g, '"\n\t},\n"').replace(/","/g, '",\n\t"');
+       /*
+               construct json object
+       */
+       let exportObj = {};
+       for (let i = 0; i < sumSubElements.length; i += 2) {
+               const key = sumSubElements[i];
+               const value = sumSubElements[i + 1];
+               exportObj[key] = value;
        }
-       return fs.write('/etc/banip/banip.custom.feeds', exportJson).then(function () {
-               location.reload();
-       });
+       const exportJson = JSON.stringify(exportObj, null, 4);
+       /*
+               save to file and reload
+       */
+       return fs.write('/etc/banip/banip.custom.feeds', exportJson)
+               .then(() => location.reload());
 }
 
 return view.extend({
@@ -186,7 +177,7 @@ return view.extend({
        },
 
        render: function (data) {
-               let m, s, o, feed, url_4, url_6, rule_4, rule_6, chain, descr, flag;
+               let m, s, o, feed, url_4, url_6, rule, chain, descr, flag;
 
                m = new form.JSONMap(data, null, _('With this editor you can upload your local custom feed file or fill up an initial one (a 1:1 copy of the version shipped with the package). \
                        The file is located at \'/etc/banip/banip.custom.feeds\'. \
@@ -194,9 +185,8 @@ return view.extend({
                for (let i = 0; i < Object.keys(m.data.data).length; i++) {
                        feed = Object.keys(m.data.data)[i];
                        url_4 = m.data.data[feed].url_4;
-                       rule_4 = m.data.data[feed].rule_4;
                        url_6 = m.data.data[feed].url_6;
-                       rule_6 = m.data.data[feed].rule_6;
+                       rule = m.data.data[feed].rule;
                        chain = m.data.data[feed].chain;
                        descr = m.data.data[feed].descr;
                        flag = m.data.data[feed].flag;
@@ -229,20 +219,6 @@ return view.extend({
                                return true;
                        }
 
-                       o = s.option(form.Value, 'rule_4', _('Rulev4'));
-                       o.value('/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)[[:space:]]/{printf \"%s,\\n\",$1}', _('<IPv4><SPACE>'));
-                       o.value('/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)$/{printf \"%s,\\n\",$1}', _('<IPv4><END>'));
-                       o.value('/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)[[:space:]]/{printf \"%s/%s,\\n\",$1,$3}', _('<IPv4><SPACE>, concatinated'));
-                       o.value('/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)$/{if(!seen[$1]++)printf \"%s,\\n\",$1}', _('<IPv4><END>, nodups'));
-                       o.value('/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)[-[:space:]]?/{printf \"%s,\\n\",$1}', _('<IPv4>[<SPACE>|<HYPHEN>]'));
-                       o.value('/127\\./{next}/(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)[[:space:]]/{printf \"%s,\\n\",$2}', _('<DATE><IPv4><SPACE>'));
-                       o.value('BEGIN{FS=\",\"}/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)/{printf \"%s,\\n\",$1}', _('<IPv4>, csv'));
-                       o.value('BEGIN{IGNORECASE=1}/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]NET)/{printf \"%s,\\n\",$1}', _('<IPv4><SPACE>NET'));
-                       o.value('BEGIN{IGNORECASE=1}/^127\\./{next}/^(([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]YOUR)/{printf \"%s,\\n\",$1}', _('<IPv4><SPACE>YOUR'));
-                       o.value('BEGIN{FS=\";\"}/content:\"127\\./{next}/(content:\"([1-9][0-9]{0,2}\\.){1}([0-9]{1,3}\\.){2}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\")/{printf \"%s,\\n\",substr($10,11,length($10)-11)}', _('<IPv4>, substring'));
-                       o.optional = true;
-                       o.rmempty = true;
-
                        o = s.option(form.Value, 'url_6', _('URLv6'));
                        o.validate = function (section_id, value) {
                                if (!value) {
@@ -254,10 +230,11 @@ return view.extend({
                                return true;
                        }
 
-                       o = s.option(form.Value, 'rule_6', _('Rulev6'));
-                       o.value('/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)[[:space:]]/{printf \"%s,\\n\",$1}', _('<IPv6><SPACE>'));
-                       o.value('/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)$/{printf \"%s,\\n\",$1}', _('<IPv6><END>'));
-                       o.value('BEGIN{FS=\",\"}/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)/{printf \"%s,\\n\",$1}', _('<IPv6>, csv'));
+                       o = s.option(form.Value, 'rule', _('Rule'));
+                       o.value('feed 1', _('<IP-Address>'));
+                       o.value('feed 1 ,', _('<IP-Address><CSV-Seperator>'));
+                       o.value('feed 13', _('<IP-Address> <Netmask>'));
+                       o.value('suricata 1', _('<Suricata Syntax>'));
                        o.optional = true;
                        o.rmempty = true;
 
index 10c552721f3fee1f2134633f4305b07070cd33b6..c26aa23bbdff91d61c2cd09f7c91d17a58e070f6 100644 (file)
@@ -18,25 +18,24 @@ function Logview(logtag, name) {
                                        const logEl = document.getElementById('logfile');
                                        if (!logEl) return;
 
-                                       const entries = res?.log ?? [];
-                                       if (entries.length > 0) {
-                                               const filtered = entries
-                                                       .filter(entry => !logtag || entry.msg.includes(logtag))
-                                                       .map(entry => {
-                                                               const d = new Date(entry.time);
-                                                               const date = d.toLocaleDateString([], {
-                                                                       year: 'numeric',
-                                                                       month: '2-digit',
-                                                                       day: '2-digit'
-                                                               });
-                                                               const time = d.toLocaleTimeString([], {
-                                                                       hour: '2-digit',
-                                                                       minute: '2-digit',
-                                                                       second: '2-digit',
-                                                                       hour12: false
-                                                               });
-                                                               return `[${date}-${time}] ${entry.msg}`;
-                                                       });
+                                       const filtered = (res?.log ?? [])
+                                       .filter(entry => !logtag || entry.msg.includes(logtag))
+                                       .map(entry => {
+                                               const d = new Date(entry.time);
+                                               const date = d.toLocaleDateString([], {
+                                                       year: 'numeric',
+                                                       month: '2-digit',
+                                                       day: '2-digit'
+                                               });
+                                               const time = d.toLocaleTimeString([], {
+                                                       hour: '2-digit',
+                                                       minute: '2-digit',
+                                                       second: '2-digit',
+                                                       hour12: false
+                                               });
+                                               return `[${date}-${time}] ${entry.msg}`;
+                                       });
+                                       if (filtered.length > 0) {
                                                logEl.value = filtered.join('\n');
                                        } else {
                                                logEl.value = _('No %s related logs yet!').format(name);
index 642f82ee6df636101a7cdf393c2a0d4eb2c186d8..fe529b47677ad6273a40fcc83e96aff28c27a758 100644 (file)
@@ -372,6 +372,10 @@ return view.extend({
                o.optional = true;
                o.rmempty = true;
 
+               o = s.taboption('adv_chain', form.Flag, 'ban_bcp38', _('Enable BCP38'), _('Block packets with spoofed source IP addresses in all supported chains.'));
+               o.optional = true;
+               o.rmempty = true;
+
                o = s.taboption('adv_chain', form.ListValue, 'ban_icmplimit', _('ICMP-Threshold'), _('ICMP-Threshold in packets per second to prevent WAN-DoS attacks. To disable this safeguard set it to \'0\'.'));
                o.value('0');
                o.value('25');
@@ -435,8 +439,7 @@ return view.extend({
                o.value('1');
                o.value('3');
                o.value('5');
-               o.value('10');
-               o.default = '5';
+               o.default = '3';
                o.placeholder = _('-- default --');
                o.create = true;
                o.optional = true;
index 1148c73a30135a95e284fb6ce3de9a30d04aaa1b..8768cbab8ea0c4ee3b9c610e6b6d285f096ad047 100644 (file)
@@ -272,6 +272,10 @@ return view.extend({
                                        E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked invalid tcp packets')),
                                        E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_tcpinvalid || '-')
                                ]),
+                               E('div', { 'class': 'cbi-value' }, [
+                                       E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked bcp38 packets')),
+                                       E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_bcp38 || '-')
+                               ]),
                                E('hr'),
                                E('div', { 'class': 'cbi-value' }, [
                                        E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('auto-added IPs to allowlist')),
index b74902cdf0aefffd561aaa4df113ff1262e88c53..e43ac0111fbd11e10bfea6101cfc24742b4f4fcc 100644 (file)
@@ -2,13 +2,13 @@
        "luci-app-banip": {
                "description": "Grant access to LuCI app banIP",
                "write": {
+                       "uci": [ "banip" ],
                        "file": {
                                "/etc/banip/*": [ "read", "write" ],
                                "/etc/banip/banip.allowlist": [ "write" ],
                                "/etc/banip/banip.blocklist": [ "write" ],
                                "/etc/banip/banip.custom.feeds": [ "read", "write" ]
-                       },
-                       "uci": [ "banip" ]
+                       }
                },
                "read": {
                        "cgi-io": [ "exec" ],