From c6be45a2be1692575da32d57eca402960c9b1c71 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 9 Dec 2025 10:00:42 +0100 Subject: [PATCH 01/37] added sink for core flows --- packages/netifyd/files/etc/netifyd/netify-proc-core.json | 4 ++++ packages/netifyd/files/etc/netifyd/netify-sink-socket.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/netifyd/files/etc/netifyd/netify-proc-core.json b/packages/netifyd/files/etc/netifyd/netify-proc-core.json index 362a1a59b..8ef1fbd15 100644 --- a/packages/netifyd/files/etc/netifyd/netify-proc-core.json +++ b/packages/netifyd/files/etc/netifyd/netify-proc-core.json @@ -6,6 +6,10 @@ "legacy": { "enable": true, "types": [ "legacy-socket" ] + }, + "flows": { + "enable": true, + "types": [ "stream-flows" ] } } } diff --git a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json index 031b89256..9f2c102f1 100644 --- a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json +++ b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json @@ -3,6 +3,10 @@ "legacy": { "enable": true, "bind_address": "unix://${path_state_volatile}/netifyd.sock" + }, + "flows": { + "enable": true, + "bind_address": "unix://${path_state_volatile}/flows.sock" } } } \ No newline at end of file From d2aeb4da344383b7477da93228be3989b4fe9587 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 21 Jan 2026 14:15:10 +0100 Subject: [PATCH 02/37] now ui compiles directly on openwrt --- packages/ns-ui/Makefile | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index ebf3bc8cf..2c7324fce 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,29 +7,31 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=2.14.0 +PKG_VERSION:=flows PKG_RELEASE:=1 -PKG_SOURCE:=ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://nethsecurity.ams3.digitaloceanspaces.com/ui-dist/ +PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz +PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_VERSION) +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_VERSION)? PKG_HASH:=skip PKG_MAINTAINER:=Giacomo Sanchietti PKG_LICENSE:=GPL-3.0-only -include $(INCLUDE_DIR)/package.mk +PKG_BUILD_DEPENDS:=node/host +PKG_BUILD_PARALLEL:=1 -HOST_BUILD_PARALLEL:=1 +include $(INCLUDE_DIR)/package.mk define Package/ns-ui SECTION:=base CATEGORY:=NethSecurity TITLE:=NethSecurity UI - URL:=https://github.com/NethServer/nethsecurity-controller/ + URL:=https://github.com/NethServer/nethsecurity-ui/ DEPENDS:=+nginx-ssl PKGARCH:=all endef - + define Package/ns-ui/description NethSecurity web user interface endef @@ -42,15 +44,8 @@ define Package/ns-ui/conffiles /www-ns/branding.js endef -# custom prepare step to avoid that 'dist' -# directory get filled with multiple versions of the UI -define Build/Prepare - rm -rvf $(PKG_BUILD_DIR)/../dist/* || true - $(PKG_UNPACK) -endef - -# this is required, otherwise compile will fail define Build/Compile + (cd $(PKG_BUILD_DIR) && npm ci && npm run build) endef define Package/ns-ui/postinst @@ -71,9 +66,7 @@ define Package/ns-ui/install $(INSTALL_BIN) ./files/ns-ui $(1)/usr/sbin $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-ui.init $(1)/etc/init.d/ns-ui - $(INSTALL_DIR) $(1)/etc/uci-defaults - $(INSTALL_BIN) ./files/ns-ui.uci-defaults $(1)/etc/uci-defaults - $(CP) $(PKG_BUILD_DIR)/../dist/* $(1)/www-ns + $(CP) $(PKG_BUILD_DIR)/dist/* $(1)/www-ns endef $(eval $(call BuildPackage,ns-ui)) From d510306a74cc30cc0ae305dc3e5dcc02450aeee1 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 22 Jan 2026 16:32:21 +0100 Subject: [PATCH 03/37] added ns-monitoring package --- packages/ns-monitoring/Makefile | 47 ++++++++++++++++++++++ packages/ns-monitoring/files/ns-flows.init | 23 +++++++++++ 2 files changed, 70 insertions(+) create mode 100644 packages/ns-monitoring/Makefile create mode 100644 packages/ns-monitoring/files/ns-flows.init diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile new file mode 100644 index 000000000..e408c991d --- /dev/null +++ b/packages/ns-monitoring/Makefile @@ -0,0 +1,47 @@ +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=ns-monitoring +# renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring +PKG_VERSION:=main +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_VERSION)? +PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_VERSION) +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) + +PKG_HASH:=skip +PKG_MAINTAINER:=Tommaso Bailetti +PKG_LICENSE:=MIT + +PKG_BUILD_DEPENDS:=golang/host +PKG_BUILD_PARALLEL:=1 +PKG_BUILD_FLAGS:=no-mips16 + +GO_PKG:=github.com/nethserver/nethsecurity-monitoring + +include $(INCLUDE_DIR)/package.mk +include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk + +define Package/ns-monitoring + SECTION:=base + CATEGORY:=NethServer + TITLE:=Nethsecurity Monitoring + URL:=https://github.com/nethserver/nethsecurity-monitoring + DEPENDS:=$(GO_ARCH_DEPENDS) +endef + +define Package/ns-monitoring/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/nethsecurity-monitoring $(1)/usr/sbin/ns-flows + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows +endef + +$(eval $(call GoBinPackage,ns-monitoring)) +$(eval $(call BuildPackage,ns-monitoring)) diff --git a/packages/ns-monitoring/files/ns-flows.init b/packages/ns-monitoring/files/ns-flows.init new file mode 100644 index 000000000..330928100 --- /dev/null +++ b/packages/ns-monitoring/files/ns-flows.init @@ -0,0 +1,23 @@ +#!/bin/sh /etc/rc.common + +# +# Copyright (C) 2026 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +USE_PROCD=1 +START=99 + +start_service() { + procd_open_instance + procd_set_param command "/usr/sbin/ns-flows" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +reload_service() +{ + stop + start +} From 6d0a3ee91b105728f8084f167d6ed69da3b4bde1 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 22 Jan 2026 16:42:26 +0100 Subject: [PATCH 04/37] forgot config --- config/ns-monitoring.conf | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/ns-monitoring.conf diff --git a/config/ns-monitoring.conf b/config/ns-monitoring.conf new file mode 100644 index 000000000..739b2e600 --- /dev/null +++ b/config/ns-monitoring.conf @@ -0,0 +1 @@ +CONFIG_PACKAGE_ns-monitoring=y From 702ed2e4a2c70e7558026f225ff3e64c2fadd57c Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 23 Jan 2026 09:14:11 +0100 Subject: [PATCH 05/37] added restart and enable --- packages/ns-monitoring/Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index e408c991d..58c5dbd13 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -43,5 +43,16 @@ define Package/ns-monitoring/install $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows endef +define Package/ns-monitoring/postinst +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + /etc/init.d/ns-flows restart + if /etc/init.d/ns-flows enabled; then + /etc/init.d/ns-flows enable + fi +fi +exit 0 +endef + $(eval $(call GoBinPackage,ns-monitoring)) $(eval $(call BuildPackage,ns-monitoring)) From 8cc17beca3862b0652def2f08de127d1290b9ce8 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 26 Jan 2026 10:42:40 +0100 Subject: [PATCH 06/37] added respawn --- packages/ns-monitoring/files/ns-flows.init | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ns-monitoring/files/ns-flows.init b/packages/ns-monitoring/files/ns-flows.init index 330928100..6ca32a4f2 100644 --- a/packages/ns-monitoring/files/ns-flows.init +++ b/packages/ns-monitoring/files/ns-flows.init @@ -13,6 +13,7 @@ start_service() { procd_set_param command "/usr/sbin/ns-flows" procd_set_param stdout 1 procd_set_param stderr 1 + procd_set_param respawn 3600 5 0 procd_close_instance } From cbd67dcf2cf9d3455ef1d006104be302a050aaf8 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 26 Jan 2026 10:53:23 +0100 Subject: [PATCH 07/37] forgot flows api --- packages/ns-api/files/ns.flows | 37 +++++++++++++++++++++++++++++ packages/ns-api/files/ns.flows.json | 13 ++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/ns-api/files/ns.flows create mode 100644 packages/ns-api/files/ns.flows.json diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows new file mode 100644 index 000000000..2398b9596 --- /dev/null +++ b/packages/ns-api/files/ns.flows @@ -0,0 +1,37 @@ +#!/usr/bin/python3 + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +import sys +import json + +from nethsec.utils import generic_error + +INPUT_PATH = "/var/run/netifyd/flows.json" + + +def main() -> None: + match sys.argv[1]: + case 'list': + print(json.dumps({ + 'list': {} + })) + case 'call': + match sys.argv[2]: + case 'list': + with open(INPUT_PATH, 'r') as f: + flows = json.load(f) + print(json.dumps({ + 'data': flows + })) + case _: + print(json.dumps(generic_error("Unknown method"))) + case _: + print(json.dumps(generic_error("Unknown command"))) + + +if __name__ == "__main__": + main() diff --git a/packages/ns-api/files/ns.flows.json b/packages/ns-api/files/ns.flows.json new file mode 100644 index 000000000..c40549bc4 --- /dev/null +++ b/packages/ns-api/files/ns.flows.json @@ -0,0 +1,13 @@ +{ + "flows": { + "description": "Read flow statistics from netifyd", + "write": {}, + "read": { + "ubus": { + "ns.flows": [ + "*" + ] + } + } + } +} From fc22f781cf0ebfb9e203c4666fe2c382546aca28 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 09:36:39 +0100 Subject: [PATCH 08/37] updated version --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 2c7324fce..1a6d80a6a 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=flows +PKG_VERSION:=7c1bf449 PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz From bdb77625b88fdb68e0187903dd2e0a4266bb6101 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 09:58:17 +0100 Subject: [PATCH 09/37] version bump --- packages/ns-api/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index ebb590704..dc9e891a0 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -164,6 +164,8 @@ define Package/ns-api/install $(INSTALL_DATA) ./files/ns.wizard.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_BIN) ./files/ns.ha $(1)/usr/libexec/rpcd/ $(INSTALL_DATA) ./files/ns.ha.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_BIN) ./files/ns.flows $(1)/usr/libexec/rpcd/ + $(INSTALL_DATA) ./files/ns.flows.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_DIR) $(1)/lib/upgrade/keep.d $(INSTALL_CONF) files/msmtp.keep $(1)/lib/upgrade/keep.d/msmtp $(INSTALL_CONF) files/nat-helpers.keep $(1)/lib/upgrade/keep.d/nat-helpers From f3979c0ab720a3a93c8064c14d5cbba82ab4dcf5 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 10:44:29 +0100 Subject: [PATCH 10/37] fixed data --- packages/ns-api/files/ns.flows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 2398b9596..ad91df6f4 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -25,7 +25,7 @@ def main() -> None: with open(INPUT_PATH, 'r') as f: flows = json.load(f) print(json.dumps({ - 'data': flows + 'list': flows })) case _: print(json.dumps(generic_error("Unknown method"))) From b20b89c83447a7d232a7d1f22f6778706d1e3a4f Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 12:23:26 +0100 Subject: [PATCH 11/37] version --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 1a6d80a6a..cf9c7a971 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=7c1bf449 +PKG_VERSION:=751a887f6d5bbfe5a8bc4bb7b24b47e1853287c7 PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz From e92a4ebc4fdc92081e969d01532c3aa08c5a2e2b Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 3 Feb 2026 09:56:57 +0100 Subject: [PATCH 12/37] monitoring version push --- packages/ns-monitoring/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index 58c5dbd13..df5277964 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=main +PKG_VERSION:=a9978744f63568e2ea10e225bae901cb7271c7e5 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz From 03a7f4bee4acdcababf4240e2208fb2251c57395 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 12 Feb 2026 08:55:40 +0100 Subject: [PATCH 13/37] flows configuration --- packages/ns-api/files/ns.flows | 69 ++++++++++++++++++- packages/ns-monitoring/Makefile | 8 +++ .../files/monitoring.uci-defaults | 15 ++++ packages/ns-monitoring/files/ns-flows.conf | 3 + packages/ns-monitoring/files/ns-flows.init | 19 +++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 packages/ns-monitoring/files/monitoring.uci-defaults create mode 100644 packages/ns-monitoring/files/ns-flows.conf diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index ad91df6f4..00f20b283 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -7,17 +7,75 @@ import sys import json +import re +import subprocess -from nethsec.utils import generic_error +from euci import EUci +from nethsec.utils import generic_error, validation_error INPUT_PATH = "/var/run/netifyd/flows.json" +def get_configuration(): + """Get ns-flows daemon configuration and status.""" + u = EUci() + + # Read configuration from UCI + config = { + 'enabled': u.get('ns-flows', 'daemon', 'enabled', default=False, dtype=bool), + 'expired_persistence': u.get('ns-flows', 'daemon', 'expired_persistence', default='60s') + } + + # Check service status + try: + result = subprocess.run( + ["service", "ns-flows", "running"], + capture_output=True, + check=False + ) + status = result.returncode == 0 + except Exception: + status = False + + return { + 'configuration': config, + 'status': status + } + + +def set_configuration(data): + """Set ns-flows daemon configuration.""" + u = EUci() + + if 'enabled' not in data: + return validation_error('enabled', 'required') + if not isinstance(data['enabled'], bool): + return validation_error('enabled', 'invalid') + if 'expired_persistence' not in data: + return validation_error('expired_persistence', 'required') + + # Validate golang duration format (e.g., "300ms", "1.5h", "2h45m") + duration_pattern = r'^([0-9]+(\.?[0-9]+)?(ns|us|µs|ms|s|m|h))+$' + if not re.match(duration_pattern, data['expired_persistence']): + return validation_error('expired_persistence', 'invalid') + + u.set('ns-flows', 'daemon', 'enabled', data['enabled']) + u.set('ns-flows', 'daemon', 'expired_persistence', data['expired_persistence']) + + u.save('ns-flows') + + return { + "message": "success" + } + + def main() -> None: match sys.argv[1]: case 'list': print(json.dumps({ - 'list': {} + 'list': {}, + 'get-configuration': {}, + 'set-configuration': {"enabled": True, "expired_persistence": "60s"} })) case 'call': match sys.argv[2]: @@ -27,6 +85,13 @@ def main() -> None: print(json.dumps({ 'list': flows })) + case 'get-configuration': + result = get_configuration() + print(json.dumps(result)) + case 'set-configuration': + data = json.load(sys.stdin) + result = set_configuration(data) + print(json.dumps(result)) case _: print(json.dumps(generic_error("Unknown method"))) case _: diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index df5277964..7cd56aa9c 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -36,9 +36,17 @@ define Package/ns-monitoring DEPENDS:=$(GO_ARCH_DEPENDS) endef +define Package/ns-monitoring/conffiles +/etc/config/monitoring +endef + define Package/ns-monitoring/install $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/nethsecurity-monitoring $(1)/usr/sbin/ns-flows + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/ns-flows.conf $(1)/etc/config/ns-flows + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) ./files/monitoring.uci-defaults $(1)/etc/uci-defaults/99-monitoring $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows endef diff --git a/packages/ns-monitoring/files/monitoring.uci-defaults b/packages/ns-monitoring/files/monitoring.uci-defaults new file mode 100644 index 000000000..9dfc42a50 --- /dev/null +++ b/packages/ns-monitoring/files/monitoring.uci-defaults @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +if ! uci -q get ns-flows.daemon > /dev/null; then + uci -q import ns-flows << EOI +config config 'daemon' + option enabled '0' + option log_level 'info' + option expired_persistence '60s' +EOI +fi diff --git a/packages/ns-monitoring/files/ns-flows.conf b/packages/ns-monitoring/files/ns-flows.conf new file mode 100644 index 000000000..b1f64c91b --- /dev/null +++ b/packages/ns-monitoring/files/ns-flows.conf @@ -0,0 +1,3 @@ +config config 'daemon' + option enabled '1' + option log_level 'info' diff --git a/packages/ns-monitoring/files/ns-flows.init b/packages/ns-monitoring/files/ns-flows.init index 6ca32a4f2..7d926590f 100644 --- a/packages/ns-monitoring/files/ns-flows.init +++ b/packages/ns-monitoring/files/ns-flows.init @@ -9,14 +9,33 @@ USE_PROCD=1 START=99 start_service() { + config_load ns-flows + + local enabled + config_get_bool enabled daemon enabled 0 + [ "$enabled" -eq 0 ] && return 0 + + local log_level expired_persistence + config_get log_level daemon log_level "info" + config_get expired_persistence daemon expired_persistence "60s" + procd_open_instance procd_set_param command "/usr/sbin/ns-flows" + procd_append_param command "-log-level" + procd_append_param command "$log_level" + procd_append_param command "-expired-persistence" + procd_append_param command "$expired_persistence" procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param respawn 3600 5 0 procd_close_instance } +service_triggers() +{ + procd_add_reload_trigger "ns-flows" +} + reload_service() { stop From 1ffa4f76d9594d805a22587a996ee51a2eee8b82 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 18 Feb 2026 16:03:18 +0100 Subject: [PATCH 14/37] adapted to new behaviour --- packages/ns-api/files/ns.flows | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 00f20b283..d7ec93136 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -10,6 +10,8 @@ import json import re import subprocess +import requests + from euci import EUci from nethsec.utils import generic_error, validation_error @@ -72,30 +74,35 @@ def set_configuration(data): def main() -> None: match sys.argv[1]: case 'list': - print(json.dumps({ - 'list': {}, + result = { + 'list': { + 'params': {} + }, 'get-configuration': {}, 'set-configuration': {"enabled": True, "expired_persistence": "60s"} - })) + } case 'call': match sys.argv[2]: case 'list': - with open(INPUT_PATH, 'r') as f: - flows = json.load(f) - print(json.dumps({ - 'list': flows - })) + data = json.load(sys.stdin) + if 'params' in data and not isinstance(data['params'], dict): + result = validation_error('params', 'invalid') + else: + try: + response = requests.get('http://127.0.0.1:8080/flows', params=data.get('params', {})) + result = response.json() + except requests.ConnectionError: + result = {} case 'get-configuration': result = get_configuration() - print(json.dumps(result)) case 'set-configuration': data = json.load(sys.stdin) result = set_configuration(data) - print(json.dumps(result)) case _: - print(json.dumps(generic_error("Unknown method"))) + result = generic_error("Unknown method") case _: - print(json.dumps(generic_error("Unknown command"))) + result = generic_error("Unknown command") + print(json.dumps(result)) if __name__ == "__main__": From cf0dde7ab774b08b5e231debc0bf0e81c19ae48c Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 23 Feb 2026 11:42:33 +0100 Subject: [PATCH 15/37] added sorting and more --- packages/ns-api/files/ns.flows | 255 +++++++++++++++++++++++++++++++-- 1 file changed, 245 insertions(+), 10 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index d7ec93136..f72a97a27 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -45,6 +45,180 @@ def get_configuration(): } +def sort_flows(flows, sort_by, desc): + """Sort flows by the given key. + + Flows that lack the relevant rate/timestamp fields are always placed at the + bottom of the result, regardless of the ``desc`` direction. + """ + + def has_sort_value(flow_item): + flow = flow_item.get('flow', {}) + if sort_by in ('download', 'upload'): + return flow.get('local_rate') is not None and flow.get('other_rate') is not None + elif sort_by == 'last_seen_at': + return flow.get('last_seen_at') is not None + elif sort_by == 'duration': + return flow.get('last_seen_at') is not None and flow.get('first_seen_at') is not None + return True + + def get_sort_key(flow_item): + flow = flow_item.get('flow', {}) + if sort_by == 'download': + if flow.get('local_origin', True): + return flow.get('other_rate', 0) or 0 + return flow.get('local_rate', 0) or 0 + elif sort_by == 'upload': + if flow.get('local_origin', True): + return flow.get('local_rate', 0) or 0 + return flow.get('other_rate', 0) or 0 + elif sort_by == 'last_seen_at': + return flow.get('last_seen_at', 0) or 0 + elif sort_by == 'duration': + return (flow.get('last_seen_at', 0) or 0) - (flow.get('first_seen_at', 0) or 0) + return 0 + + with_value = [f for f in flows if has_sort_value(f)] + without_value = [f for f in flows if not has_sort_value(f)] + + return sorted(with_value, key=get_sort_key, reverse=desc) + without_value + + +def get_source(flow_item): + """Get the source IP based on flow direction. + + Uses local_origin to determine which IP is the source: + - If local_origin is True, source is local_ip + - Otherwise, source is other_ip + """ + flow = flow_item.get('flow', {}) + if flow.get('local_origin', True): + return flow.get('local_ip', '') + return flow.get('other_ip', '') + + +def get_destination(flow_item): + """Get the destination IP based on flow direction. + + Uses local_origin to determine which IP is the destination: + - If local_origin is True, destination is other_ip + - Otherwise, destination is local_ip + """ + flow = flow_item.get('flow', {}) + if flow.get('local_origin', True): + return flow.get('other_ip', '') + return flow.get('local_ip', '') + + +def collect_unique_values(flows): + """Collect unique values for all filterable fields from the complete flow list. + + Returns a dict with sorted lists of unique values: + - applications: unique detected_application_name values + - protocols: unique detected_protocol_name values + - sources: unique source IPs (direction-aware) + - destinations: unique destination IPs (direction-aware) + """ + applications = set() + protocols = set() + sources = set() + destinations = set() + + for flow_item in flows: + flow = flow_item.get('flow', {}) + + # Collect applications + app = flow.get('detected_application_name') + if app: + applications.add(app) + + # Collect protocols + proto = flow.get('detected_protocol_name') + if proto: + protocols.add(proto) + + # Collect source/destination IPs + src = get_source(flow_item) + if src: + sources.add(src) + + dst = get_destination(flow_item) + if dst: + destinations.add(dst) + + return { + 'applications': sorted(list(applications)), + 'protocols': sorted(list(protocols)), + 'sources': sorted(list(sources)), + 'destinations': sorted(list(destinations)) + } + + +def filter_flows(flows, q=None, application=None, protocol=None, source=None, destination=None): + """Filter flows based on multiple criteria. + + Args: + flows: List of flow items + q: General text search across all fields (case-insensitive substring) + application: List of application names to filter by (case-insensitive exact match) + protocol: List of protocol names to filter by (case-insensitive exact match) + source: List of source IPs to filter by (case-insensitive exact match) + destination: List of destination IPs to filter by (case-insensitive exact match) + + Returns: + Filtered list of flows (multiple values within same filter use OR logic, different filters use AND) + """ + result = [] + + for flow_item in flows: + flow = flow_item.get('flow', {}) + + # Apply specific field filters (exact match, case-insensitive) + # Multiple values within a filter use OR logic + if application: + app = (flow.get('detected_application_name') or '').lower() + if not any(app == a.lower() for a in application): + continue + + if protocol: + proto = (flow.get('detected_protocol_name') or '').lower() + if not any(proto == p.lower() for p in protocol): + continue + + if source: + src = get_source(flow_item).lower() + if not any(src == s.lower() for s in source): + continue + + if destination: + dst = get_destination(flow_item).lower() + if not any(dst == d.lower() for d in destination): + continue + + # Apply general text search (substring match, case-insensitive) + if q: + q_lower = q.lower() + search_fields = [ + flow.get('detected_application_name', ''), + flow.get('detected_protocol_name', ''), + get_source(flow_item), + get_destination(flow_item), + flow.get('host_server_name', ''), + flow.get('dns_host_name', ''), + flow.get('local_mac', ''), + flow.get('other_mac', ''), + flow_item.get('interface', ''), + str(flow.get('local_port', '')), + str(flow.get('other_port', '')) + ] + if not any(q_lower in str(field).lower() for field in search_fields): + continue + + result.append(flow_item) + + return result + + def set_configuration(data): """Set ns-flows daemon configuration.""" u = EUci() @@ -76,7 +250,17 @@ def main() -> None: case 'list': result = { 'list': { - 'params': {} + 'params': { + 'per_page': 10, + 'page': 1, + 'sort_by': 'download', + 'desc': True, + 'q': '', + 'application': '', + 'protocol': '', + 'source': '', + 'destination': '' + } }, 'get-configuration': {}, 'set-configuration': {"enabled": True, "expired_persistence": "60s"} @@ -84,15 +268,66 @@ def main() -> None: case 'call': match sys.argv[2]: case 'list': - data = json.load(sys.stdin) - if 'params' in data and not isinstance(data['params'], dict): - result = validation_error('params', 'invalid') - else: - try: - response = requests.get('http://127.0.0.1:8080/flows', params=data.get('params', {})) - result = response.json() - except requests.ConnectionError: - result = {} + try: + response = requests.get('http://127.0.0.1:8080/flows') + if "flows" not in response.json(): + result = generic_error("Invalid response from ns-flows daemon") + else: + data = json.load(sys.stdin) + flows = response.json()['flows'] + per_page = data.get('per_page', 10) + page = data.get('page', 1) + + sort_by = data.get('sort_by', 'download') + if sort_by not in ['download', 'upload', 'last_seen_at', 'duration']: + result = validation_error('sort_by', 'invalid') + else: + desc = data.get('desc', True) + + # Collect unique filter values from unfiltered flows + filter_values = collect_unique_values(flows) + + # Apply filters (normalize to lists) + def to_list(value): + if value is None: + return None + if isinstance(value, list): + return value if value else None + if isinstance(value, str): + return [value] if value else None + return None + + q = data.get('q') + application = to_list(data.get('application')) + protocol = to_list(data.get('protocol')) + source = to_list(data.get('source')) + destination = to_list(data.get('destination')) + + filtered_flows = filter_flows( + flows, + q=q, + application=application, + protocol=protocol, + source=source, + destination=destination + ) + + # Sort filtered flows + sorted_flows = sort_flows(filtered_flows, sort_by, desc) + + # Paginate after sorting + paginated = sorted_flows[(page - 1) * per_page: page * per_page] + + result = { + 'flows': paginated, + 'total': len(filtered_flows), + 'per_page': per_page, + 'current_page': page, + 'last_page': (len(sorted_flows) + per_page - 1) // per_page, + 'filters': filter_values + } + except requests.ConnectionError: + result = {} case 'get-configuration': result = get_configuration() case 'set-configuration': From 53c0455c8269749352da709952b41d7c82e3eb73 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 23 Feb 2026 14:59:50 +0100 Subject: [PATCH 16/37] fix: last page min, flows if error --- packages/ns-api/files/ns.flows | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index f72a97a27..e87f3b2af 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -318,16 +318,32 @@ def main() -> None: # Paginate after sorting paginated = sorted_flows[(page - 1) * per_page: page * per_page] + # Calculate last_page, ensuring minimum of 1 when no data + last_page = max(1, (len(sorted_flows) + per_page - 1) // per_page) + result = { 'flows': paginated, 'total': len(filtered_flows), 'per_page': per_page, 'current_page': page, - 'last_page': (len(sorted_flows) + per_page - 1) // per_page, + 'last_page': last_page, 'filters': filter_values } except requests.ConnectionError: - result = {} + # flows daemon is not running or starting up + result = { + 'flows': [], + 'total': 0, + 'per_page': 10, + 'current_page': 1, + 'last_page': 1, + 'filters': { + 'applications': [], + 'protocols': [], + 'sources': [], + 'destinations': [] + } + } case 'get-configuration': result = get_configuration() case 'set-configuration': From 358586c5c81fc868d8085c95adb4553ad4c5ad41 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 23 Feb 2026 15:06:41 +0100 Subject: [PATCH 17/37] reverting errors --- packages/ns-api/files/ns.flows | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index e87f3b2af..e5d9676da 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -330,20 +330,7 @@ def main() -> None: 'filters': filter_values } except requests.ConnectionError: - # flows daemon is not running or starting up - result = { - 'flows': [], - 'total': 0, - 'per_page': 10, - 'current_page': 1, - 'last_page': 1, - 'filters': { - 'applications': [], - 'protocols': [], - 'sources': [], - 'destinations': [] - } - } + result = generic_error("Unable to connect to ns-flows daemon") case 'get-configuration': result = get_configuration() case 'set-configuration': From ef241f9f3a0bc85967a2fae4524e6b668f2b8019 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 24 Feb 2026 08:42:05 +0100 Subject: [PATCH 18/37] refactor: moved flows plugins into ns-monitoring, new versions --- .../netifyd/files/etc/netifyd/netify-proc-core.json | 4 ---- .../files/etc/netifyd/netify-sink-socket.json | 4 ---- packages/ns-api/Makefile | 4 ++-- packages/ns-monitoring/Makefile | 12 +++++++++--- .../files/netifyd/netify-proc-core-flows.json | 12 ++++++++++++ .../files/netifyd/netify-sink-socket-flows.json | 8 ++++++++ .../files/netifyd/plugins.d/10-netify-flows.conf | 13 +++++++++++++ packages/ns-ui/Makefile | 7 ++++--- 8 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json create mode 100644 packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json create mode 100644 packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf diff --git a/packages/netifyd/files/etc/netifyd/netify-proc-core.json b/packages/netifyd/files/etc/netifyd/netify-proc-core.json index 8ef1fbd15..362a1a59b 100644 --- a/packages/netifyd/files/etc/netifyd/netify-proc-core.json +++ b/packages/netifyd/files/etc/netifyd/netify-proc-core.json @@ -6,10 +6,6 @@ "legacy": { "enable": true, "types": [ "legacy-socket" ] - }, - "flows": { - "enable": true, - "types": [ "stream-flows" ] } } } diff --git a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json index 9f2c102f1..031b89256 100644 --- a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json +++ b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json @@ -3,10 +3,6 @@ "legacy": { "enable": true, "bind_address": "unix://${path_state_volatile}/netifyd.sock" - }, - "flows": { - "enable": true, - "bind_address": "unix://${path_state_volatile}/flows.sock" } } } \ No newline at end of file diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index dc9e891a0..afbe9e53a 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-api -PKG_VERSION:=3.5.1 -PKG_RELEASE:=2 +PKG_VERSION:=3.5.1-beta +PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index 7cd56aa9c..c712133e3 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -7,12 +7,13 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=a9978744f63568e2ea10e225bae901cb7271c7e5 +PKG_VERSION:=0.0.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_VERSION)? -PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_VERSION) +PKG_SOURCE_VERSION:=badb4039bf3fd5f216154777d8e72413185e8f86 +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_SOURCE_VERSION)? +PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_SOURCE_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) PKG_HASH:=skip @@ -49,12 +50,17 @@ define Package/ns-monitoring/install $(INSTALL_BIN) ./files/monitoring.uci-defaults $(1)/etc/uci-defaults/99-monitoring $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows + $(INSTALL_DIR) $(1)/etc/netifyd + $(INSTALL_CONF) ./files/netifyd/plugins.d/10-netify-flows.conf $(1)/etc/netifyd/plugins.d/10-netify-flows.conf + $(INSTALL_CONF) ./files/netifyd/netify-sink-socket-flows.json $(1)/etc/netifyd/netify-sink-socket-flows.json + $(INSTALL_CONF) ./files/netifyd/netify-proc-core-flows.json $(1)/etc/netifyd/netify-proc-core-flows.json endef define Package/ns-monitoring/postinst #!/bin/sh if [ -z "$${IPKG_INSTROOT}" ]; then /etc/init.d/ns-flows restart + /etc/init.d/netifyd reload if /etc/init.d/ns-flows enabled; then /etc/init.d/ns-flows enable fi diff --git a/packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json b/packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json new file mode 100644 index 000000000..d20af69e5 --- /dev/null +++ b/packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json @@ -0,0 +1,12 @@ +{ + "format": "json", + "compressor": "none", + "sinks": { + "sink-socket-flows": { + "flows": { + "enable": true, + "types": [ "stream-flows" ] + } + } + } +} \ No newline at end of file diff --git a/packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json b/packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json new file mode 100644 index 000000000..a456a5bad --- /dev/null +++ b/packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json @@ -0,0 +1,8 @@ +{ + "channels": { + "flows": { + "enable": true, + "bind_address": "unix://${path_state_volatile}/flows.sock" + } + } +} \ No newline at end of file diff --git a/packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf b/packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf new file mode 100644 index 000000000..b974796fe --- /dev/null +++ b/packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf @@ -0,0 +1,13 @@ +# Flows analysis for ns-monitoring + +[proc-core-flows] +enable = yes +plugin_library = ${path_plugin_libdir}/libnetify-proc-core.so.0.0.0 +conf_filename = ${path_state_persistent}/netify-proc-core-flows.json + +[sink-socket-flows] +enable = yes +plugin_library = ${path_plugin_libdir}/libnetify-sink-socket.so.0.0.0 +conf_filename = ${path_state_persistent}/netify-sink-socket-flows.json + +# vim: set ft=dosini : diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index cf9c7a971..3ede0e418 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,12 +7,13 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=751a887f6d5bbfe5a8bc4bb7b24b47e1853287c7 +PKG_VERSION:=2.13.1-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_VERSION) -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_VERSION)? +PKG_SOURCE_VERSION:=588b27e5fff8946f15f225bcb0aaea1fbc0072b2 +PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip PKG_MAINTAINER:=Giacomo Sanchietti From 4d18411d87e61398a9e59a8f07bea342571e4258 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 25 Feb 2026 10:05:09 +0100 Subject: [PATCH 19/37] fixed missing directory --- packages/ns-monitoring/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index c712133e3..fd9edfe1b 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -51,6 +51,7 @@ define Package/ns-monitoring/install $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows $(INSTALL_DIR) $(1)/etc/netifyd + $(INSTALL_DIR) $(1)/etc/netifyd/plugins.d $(INSTALL_CONF) ./files/netifyd/plugins.d/10-netify-flows.conf $(1)/etc/netifyd/plugins.d/10-netify-flows.conf $(INSTALL_CONF) ./files/netifyd/netify-sink-socket-flows.json $(1)/etc/netifyd/netify-sink-socket-flows.json $(INSTALL_CONF) ./files/netifyd/netify-proc-core-flows.json $(1)/etc/netifyd/netify-proc-core-flows.json From 21f673d0d6447ff2d416ce7104e48181658ad668 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 25 Feb 2026 11:47:59 +0100 Subject: [PATCH 20/37] updated ui --- packages/ns-ui/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 3ede0e418..7426ace23 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,11 +7,11 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=2.13.1-beta +PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=588b27e5fff8946f15f225bcb0aaea1fbc0072b2 +PKG_SOURCE_VERSION:=45c5fa2d94cf85f0cf205fd82cf7d22cf050453f PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From b9a075c25952fb0f940b50ec129bf8f65f0c2c81 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 25 Feb 2026 16:22:26 +0100 Subject: [PATCH 21/37] added badge generation from backend --- packages/ns-api/files/ns.flows | 104 ++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 21 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index e5d9676da..b1ff6cf95 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -112,49 +112,57 @@ def get_destination(flow_item): def collect_unique_values(flows): """Collect unique values for all filterable fields from the complete flow list. - + Returns a dict with sorted lists of unique values: - applications: unique detected_application_name values - protocols: unique detected_protocol_name values - sources: unique source IPs (direction-aware) - destinations: unique destination IPs (direction-aware) + - tags: unique tag values """ applications = set() protocols = set() sources = set() destinations = set() + tags = set() for flow_item in flows: flow = flow_item.get('flow', {}) - + # Collect applications app = flow.get('detected_application_name') if app: applications.add(app) - + # Collect protocols proto = flow.get('detected_protocol_name') if proto: protocols.add(proto) - + # Collect source/destination IPs src = get_source(flow_item) if src: sources.add(src) - + dst = get_destination(flow_item) if dst: destinations.add(dst) - + + # Collect tags + flow_tags = flow_item.get('tags', []) + if flow_tags: + tags.update(flow_tags) + return { 'applications': sorted(list(applications)), 'protocols': sorted(list(protocols)), 'sources': sorted(list(sources)), - 'destinations': sorted(list(destinations)) + 'destinations': sorted(list(destinations)), + 'tags': sorted(list(tags)) } -def filter_flows(flows, q=None, application=None, protocol=None, source=None, destination=None): +def filter_flows(flows, q=None, application=None, protocol=None, source=None, destination=None, tag=None): """Filter flows based on multiple criteria. Args: @@ -164,37 +172,43 @@ def filter_flows(flows, q=None, application=None, protocol=None, source=None, de protocol: List of protocol names to filter by (case-insensitive exact match) source: List of source IPs to filter by (case-insensitive exact match) destination: List of destination IPs to filter by (case-insensitive exact match) + tag: List of tags to filter by (case-insensitive exact match) Returns: Filtered list of flows (multiple values within same filter use OR logic, different filters use AND) """ result = [] - + for flow_item in flows: flow = flow_item.get('flow', {}) - + # Apply specific field filters (exact match, case-insensitive) # Multiple values within a filter use OR logic if application: app = (flow.get('detected_application_name') or '').lower() if not any(app == a.lower() for a in application): continue - + if protocol: proto = (flow.get('detected_protocol_name') or '').lower() if not any(proto == p.lower() for p in protocol): continue - + if source: src = get_source(flow_item).lower() if not any(src == s.lower() for s in source): continue - + if destination: dst = get_destination(flow_item).lower() if not any(dst == d.lower() for d in destination): continue - + + if tag: + flow_tags = [t.lower() for t in flow_item.get('tags', [])] + if not any(t.lower() in flow_tags for t in tag): + continue + # Apply general text search (substring match, case-insensitive) if q: q_lower = q.lower() @@ -213,9 +227,9 @@ def filter_flows(flows, q=None, application=None, protocol=None, source=None, de ] if not any(q_lower in str(field).lower() for field in search_fields): continue - + result.append(flow_item) - + return result @@ -245,6 +259,46 @@ def set_configuration(data): } +def compute_tags(flow_item): + """Compute tags for a flow based on its type and direction. + + Returns a list of zero or more tags from: 'remote', 'outgoing', 'internal', 'broadcast', 'scanning'. + Rules: + - 'remote': local_origin==False AND other_type=='remote' + - 'outgoing': local_origin==True AND other_type=='remote' + - 'internal': other_type=='local' + - 'broadcast': other_type=='broadcast' + - 'scanning': top-level type=='flow' + """ + tags = [] + top_level_type = flow_item.get('type') + flow = flow_item.get('flow', {}) + + local_origin = flow.get('local_origin', True) + other_type = flow.get('other_type', '') + + # Check for remote/outgoing (mutually exclusive based on local_origin) + if other_type == 'remote': + if not local_origin: + tags.append('remote') + else: + tags.append('outgoing') + + # Check for internal + if other_type == 'local': + tags.append('internal') + + # Check for broadcast + if other_type == 'broadcast': + tags.append('broadcast') + + # Check for scanning + if top_level_type == 'flow': + tags.append('scanning') + + return tags + + def main() -> None: match sys.argv[1]: case 'list': @@ -257,9 +311,10 @@ def main() -> None: 'desc': True, 'q': '', 'application': '', - 'protocol': '', - 'source': '', - 'destination': '' + 'protocol': '', + 'source': '', + 'destination': '', + 'tag': '' } }, 'get-configuration': {}, @@ -275,6 +330,11 @@ def main() -> None: else: data = json.load(sys.stdin) flows = response.json()['flows'] + + # Augment each flow with computed tags + for f in flows: + f['tags'] = compute_tags(f) + per_page = data.get('per_page', 10) page = data.get('page', 1) @@ -302,14 +362,16 @@ def main() -> None: protocol = to_list(data.get('protocol')) source = to_list(data.get('source')) destination = to_list(data.get('destination')) - + tag = to_list(data.get('tag')) + filtered_flows = filter_flows( flows, q=q, application=application, protocol=protocol, source=source, - destination=destination + destination=destination, + tag=tag ) # Sort filtered flows From d52825088ddfaa5702abb6a03fd5b42caf89f8bc Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 27 Feb 2026 08:55:48 +0100 Subject: [PATCH 22/37] added additional metadata to flows --- packages/ns-api/files/ns.dpi | 36 +++++- packages/ns-api/files/ns.flows | 47 +++++++- packages/ns-dpi/Makefile | 4 + .../99-dpi-data-update-cron.uci-defaults | 21 ++++ packages/ns-dpi/files/dpi-data-update.init | 27 +++++ packages/ns-dpi/files/dpi-data-update.py | 110 ++++++++++++++++++ 6 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults create mode 100644 packages/ns-dpi/files/dpi-data-update.init create mode 100644 packages/ns-dpi/files/dpi-data-update.py diff --git a/packages/ns-api/files/ns.dpi b/packages/ns-api/files/ns.dpi index 04cab3a59..84c3dadc5 100755 --- a/packages/ns-api/files/ns.dpi +++ b/packages/ns-api/files/ns.dpi @@ -56,13 +56,45 @@ if cmd == 'list': 'list-popular': { 'limit': 32, 'page': 32 - } + }, + 'list-application-categories': {}, + 'list-application-catalog': {}, + 'list-protocol-categories': {}, + 'list-protocol-catalog': {} })) elif cmd == 'call': action = sys.argv[2] e_uci = EUci() try: - if action == 'list-applications': + if action == 'list-application-categories': + try: + with open('/etc/netifyd/netify-application-categories.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-application-catalog': + try: + with open('/etc/netifyd/netify-application-catalog.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-protocol-categories': + try: + with open('/etc/netifyd/netify-protocol-categories.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-protocol-catalog': + try: + with open('/etc/netifyd/netify-protocol-catalog.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-applications': data = json.JSONDecoder().decode(sys.stdin.read()) result = dpi.list_applications(data.get('search', None), data.get('limit', None), data.get('page', 1)) print(json.dumps({'values': result})) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index b1ff6cf95..5c8f4e75c 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -299,6 +299,34 @@ def compute_tags(flow_item): return tags +def compute_application_name(flow_item, applications_catalog): + """Compute application name for a flow based on detected_application_name and applications catalog. + + If detected_application_name is present, return it. + Otherwise, try to infer application from the catalog based on detected_protocol_name and ports. + If no match is found, return 'unknown'. + """ + for app in applications_catalog: + if app.get('id') == flow_item.get('flow').get('detected_application'): + return app.get('label') + + return flow_item.get('flow').get('detected_application_name') + + +def compute_protocol_name(flow_item, protocols_catalog): + """Compute protocol name for a flow based on detected_protocol_name and protocols catalog. + + If detected_protocol_name is present, return it. + Otherwise, try to infer protocol from the catalog based on local_port/other_port and protocol categories. + If no match is found, return 'unknown'. + """ + for proto in protocols_catalog: + if proto.get('id') == flow_item.get('flow').get('detected_protocol'): + return proto.get('label') + + return flow_item.get('flow').get('detected_protocol_name') + + def main() -> None: match sys.argv[1]: case 'list': @@ -331,9 +359,26 @@ def main() -> None: data = json.load(sys.stdin) flows = response.json()['flows'] - # Augment each flow with computed tags + applications_catalog = [] + # load applications catalog if the file exists + try: + with open('/etc/netifyd/netify-application-catalog.json', 'r') as f: + applications_catalog = json.load(f) + except Exception: + pass + protocols_catalog = [] + # load protocols catalog if the file exists + try: + with open('/etc/netifyd/netify-protocol-catalog.json', 'r') as f: + protocols_catalog = json.load(f) + except Exception: + pass + + # Augment each flow with additional data for f in flows: f['tags'] = compute_tags(f) + f['application_label'] = compute_application_name(f, applications_catalog) + f['protocol_label'] = compute_protocol_name(f, protocols_catalog) per_page = data.get('per_page', 10) page = data.get('page', 1) diff --git a/packages/ns-dpi/Makefile b/packages/ns-dpi/Makefile index 538381a1a..7b039bb63 100644 --- a/packages/ns-dpi/Makefile +++ b/packages/ns-dpi/Makefile @@ -38,6 +38,7 @@ define Package/ns-dpi/postinst if [ -z "$${IPKG_INSTROOT}" ]; then /etc/init.d/cron restart /etc/init.d/dpi-license-update start + /etc/init.d/dpi-data-update start fi exit 0 endef @@ -68,13 +69,16 @@ define Package/ns-dpi/install $(LN) /etc/connlabel.conf $(1)/etc/xtables/connlabel.conf $(INSTALL_BIN) ./files/dpi.init $(1)/etc/init.d/dpi $(INSTALL_BIN) ./files/dpi-license-update.init $(1)/etc/init.d/dpi-license-update + $(INSTALL_BIN) ./files/dpi-data-update.init $(1)/etc/init.d/dpi-data-update $(INSTALL_BIN) ./files/dpi $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-nft $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-config $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-update $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-license-update.py $(1)/usr/sbin/dpi-license-update + $(INSTALL_BIN) ./files/dpi-data-update.py $(1)/usr/sbin/dpi-data-update $(INSTALL_CONF) ./files/20_dpi $(1)/etc/uci-defaults $(INSTALL_BIN) ./files/99-dpi-license-update-cron.uci-defaults $(1)/etc/uci-defaults/99-dpi-license-update-cron + $(INSTALL_BIN) ./files/99-dpi-data-update-cron.uci-defaults $(1)/etc/uci-defaults/99-dpi-data-update-cron $(INSTALL_DIR) $(1)/usr/share/ns-plug/hooks/register $(INSTALL_BIN) ./files/70dpi $(1)/usr/share/ns-plug/hooks/unregister/ $(LN) /usr/sbin/dpi-update $(1)/usr/share/ns-plug/hooks/register/80dpi-update diff --git a/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults b/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults new file mode 100644 index 000000000..de54a6401 --- /dev/null +++ b/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults @@ -0,0 +1,21 @@ +#!/bin/sh + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +# +# DPI Data Update: Add cron job if missing +# + +if ! grep -q '/etc/init.d/dpi-data-update' /etc/crontabs/root; then + echo '0 0 * * * sleep $(( RANDOM % 300 )); /etc/init.d/dpi-data-update start' >> /etc/crontabs/root +fi + +# Ensure dpi-data-update service is enabled at boot +if ! /etc/init.d/dpi-data-update enabled; then + /etc/init.d/dpi-data-update enable + /etc/init.d/dpi-data-update start +fi + diff --git a/packages/ns-dpi/files/dpi-data-update.init b/packages/ns-dpi/files/dpi-data-update.init new file mode 100644 index 000000000..5a4e2354b --- /dev/null +++ b/packages/ns-dpi/files/dpi-data-update.init @@ -0,0 +1,27 @@ +#!/bin/sh /etc/rc.common + +# +# Copyright (C) 2026 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +USE_PROCD=1 +START=51 + +start_service() { + procd_open_instance + procd_set_param command "/usr/sbin/dpi-data-update" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +reload_service() +{ + start +} + +service_triggers() { + procd_add_reload_trigger "fstab" "dpi" +} + diff --git a/packages/ns-dpi/files/dpi-data-update.py b/packages/ns-dpi/files/dpi-data-update.py new file mode 100644 index 000000000..57ed56a95 --- /dev/null +++ b/packages/ns-dpi/files/dpi-data-update.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +from urllib3.util import Retry +from requests import Session +from requests.adapters import HTTPAdapter +import os.path +import json +import logging +from os import environ + + +DATA_SERVER_ENDPOINT = "http://10.0.1.216:8080" +APPLICATIONS_CATEGORIES_ENDPOINT = "/api/netifyd/applications/categories" +APPLICATIONS_CATALOG_ENDPOINT = "/api/netifyd/applications/catalog" +PROTOCOLS_CATEGORIES_ENDPOINT = "/api/netifyd/protocols/categories" +PROTOCOLS_CATALOG_ENDPOINT = "/api/netifyd/protocols/catalog" +DATA_DISK_LOCATION = "/etc/netifyd" +APPLICATIONS_CATEGORIES_NAME = "netify-application-categories.json" +APPLICATIONS_CATALOG_NAME = "netify-application-catalog.json" +PROTOCOLS_CATEGORIES_NAME = "netify-protocol-categories.json" +PROTOCOLS_CATALOG_NAME = "netify-protocol-catalog.json" + + +def save_data(filename: str, content: str) -> None: + filepath = os.path.join(DATA_DISK_LOCATION, filename) + + if os.path.exists(filepath): + logging.debug(f"File {filename} exists, checking if update is needed") + try: + with open(filepath, "r") as f: + if f.read() == content: + logging.debug(f"{filename} is up to date, no action needed") + return + except Exception as e: + logging.warning(f"Failed to read existing {filename}: {e}") + + # save the new data + try: + if not os.path.exists(DATA_DISK_LOCATION): + os.makedirs(DATA_DISK_LOCATION) + with open(filepath, "w") as f: + f.write(content) + logging.info(f"{filename} updated") + except Exception as e: + logging.error(f"Failed to write {filename}: {e}") + raise + + +def download_data(endpoint: str, filename: str) -> None: + s = Session() + retries = Retry( + total=20, backoff_factor=0.1, status_forcelist=range(500, 600), backoff_max=30 + ) + s.mount(DATA_SERVER_ENDPOINT, HTTPAdapter(max_retries=retries)) + s.headers.update({"Accept": "application/json"}) + + try: + logging.info(f"Downloading from {DATA_SERVER_ENDPOINT}{endpoint}") + response = s.get(DATA_SERVER_ENDPOINT + endpoint, timeout=5) + response.raise_for_status() + content = response.text + + # Validate that content is valid JSON + json.loads(content) + + save_data(filename, content) + except Exception as e: + logging.error(f"Failed to download {filename} from {endpoint}: {e}") + raise + + +if __name__ == "__main__": + logger = logging.getLogger() + logger.setLevel(environ.get("LOGLEVEL", "INFO").upper()) + handler = logging.StreamHandler() + logger.addHandler(handler) + + errors = [] + + try: + download_data(APPLICATIONS_CATEGORIES_ENDPOINT, APPLICATIONS_CATEGORIES_NAME) + except Exception as e: + logger.warning(f"Failed to download applications categories: {e}") + errors.append(str(e)) + + try: + download_data(APPLICATIONS_CATALOG_ENDPOINT, APPLICATIONS_CATALOG_NAME) + except Exception as e: + logger.warning(f"Failed to download applications catalog: {e}") + errors.append(str(e)) + + try: + download_data(PROTOCOLS_CATEGORIES_ENDPOINT, PROTOCOLS_CATEGORIES_NAME) + except Exception as e: + logger.warning(f"Failed to download protocols categories: {e}") + errors.append(str(e)) + + try: + download_data(PROTOCOLS_CATALOG_ENDPOINT, PROTOCOLS_CATALOG_NAME) + except Exception as e: + logger.warning(f"Failed to download protocols catalog: {e}") + errors.append(str(e)) + + if errors: + logger.warning(f"Completed with {len(errors)} error(s)") From 4449b7b0873b11f76c75577edc69f69dd3df5d78 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 11:55:47 +0100 Subject: [PATCH 23/37] updated detection, it will be client side --- packages/ns-api/files/ns.flows | 82 ++++++++++------------------------ 1 file changed, 23 insertions(+), 59 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 5c8f4e75c..6fedd916d 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -114,14 +114,14 @@ def collect_unique_values(flows): """Collect unique values for all filterable fields from the complete flow list. Returns a dict with sorted lists of unique values: - - applications: unique detected_application_name values - - protocols: unique detected_protocol_name values + - applications: unique detected_application values as dicts with 'id' and 'name' + - protocols: unique detected_protocol values as dicts with 'id' and 'name' - sources: unique source IPs (direction-aware) - destinations: unique destination IPs (direction-aware) - tags: unique tag values """ - applications = set() - protocols = set() + applications = {} + protocols = {} sources = set() destinations = set() tags = set() @@ -129,15 +129,17 @@ def collect_unique_values(flows): for flow_item in flows: flow = flow_item.get('flow', {}) - # Collect applications - app = flow.get('detected_application_name') - if app: - applications.add(app) + # Collect applications with both id and name + app_id = flow.get('detected_application') + app_name = flow.get('detected_application_name') + if app_id is not None and app_name: + applications[app_id] = app_name - # Collect protocols - proto = flow.get('detected_protocol_name') - if proto: - protocols.add(proto) + # Collect protocols with both id and name + proto_id = flow.get('detected_protocol') + proto_name = flow.get('detected_protocol_name') + if proto_id is not None and proto_name: + protocols[proto_id] = proto_name # Collect source/destination IPs src = get_source(flow_item) @@ -153,9 +155,16 @@ def collect_unique_values(flows): if flow_tags: tags.update(flow_tags) + # Convert applications and protocols to sorted lists of dicts + applications_list = [{'id': app_id, 'name': name} for app_id, name in applications.items()] + applications_list.sort(key=lambda x: x['name'].lower()) + + protocols_list = [{'id': proto_id, 'name': name} for proto_id, name in protocols.items()] + protocols_list.sort(key=lambda x: x['name'].lower()) + return { - 'applications': sorted(list(applications)), - 'protocols': sorted(list(protocols)), + 'applications': applications_list, + 'protocols': protocols_list, 'sources': sorted(list(sources)), 'destinations': sorted(list(destinations)), 'tags': sorted(list(tags)) @@ -299,34 +308,6 @@ def compute_tags(flow_item): return tags -def compute_application_name(flow_item, applications_catalog): - """Compute application name for a flow based on detected_application_name and applications catalog. - - If detected_application_name is present, return it. - Otherwise, try to infer application from the catalog based on detected_protocol_name and ports. - If no match is found, return 'unknown'. - """ - for app in applications_catalog: - if app.get('id') == flow_item.get('flow').get('detected_application'): - return app.get('label') - - return flow_item.get('flow').get('detected_application_name') - - -def compute_protocol_name(flow_item, protocols_catalog): - """Compute protocol name for a flow based on detected_protocol_name and protocols catalog. - - If detected_protocol_name is present, return it. - Otherwise, try to infer protocol from the catalog based on local_port/other_port and protocol categories. - If no match is found, return 'unknown'. - """ - for proto in protocols_catalog: - if proto.get('id') == flow_item.get('flow').get('detected_protocol'): - return proto.get('label') - - return flow_item.get('flow').get('detected_protocol_name') - - def main() -> None: match sys.argv[1]: case 'list': @@ -359,26 +340,9 @@ def main() -> None: data = json.load(sys.stdin) flows = response.json()['flows'] - applications_catalog = [] - # load applications catalog if the file exists - try: - with open('/etc/netifyd/netify-application-catalog.json', 'r') as f: - applications_catalog = json.load(f) - except Exception: - pass - protocols_catalog = [] - # load protocols catalog if the file exists - try: - with open('/etc/netifyd/netify-protocol-catalog.json', 'r') as f: - protocols_catalog = json.load(f) - except Exception: - pass - # Augment each flow with additional data for f in flows: f['tags'] = compute_tags(f) - f['application_label'] = compute_application_name(f, applications_catalog) - f['protocol_label'] = compute_protocol_name(f, protocols_catalog) per_page = data.get('per_page', 10) page = data.get('page', 1) From d054dc193453451981300d8f55e9e55e7ad15fcb Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 15:04:40 +0100 Subject: [PATCH 24/37] updated ui --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 7426ace23..cbb356dff 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=45c5fa2d94cf85f0cf205fd82cf7d22cf050453f +PKG_SOURCE_VERSION:=2e397bf6fd93272041c6cfd1da060b1f3731ae4a PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From 04aea068d5ef6238b983b519d123bbca538c4a22 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 16:20:35 +0100 Subject: [PATCH 25/37] using upstream --- packages/ns-dpi/files/dpi-data-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-dpi/files/dpi-data-update.py b/packages/ns-dpi/files/dpi-data-update.py index 57ed56a95..170dd6538 100644 --- a/packages/ns-dpi/files/dpi-data-update.py +++ b/packages/ns-dpi/files/dpi-data-update.py @@ -14,7 +14,7 @@ from os import environ -DATA_SERVER_ENDPOINT = "http://10.0.1.216:8080" +DATA_SERVER_ENDPOINT = "https://distfeed.nethesis.it" APPLICATIONS_CATEGORIES_ENDPOINT = "/api/netifyd/applications/categories" APPLICATIONS_CATALOG_ENDPOINT = "/api/netifyd/applications/catalog" PROTOCOLS_CATEGORIES_ENDPOINT = "/api/netifyd/protocols/categories" From 83dbde7de75c750509ca80b98354cf8fbca3c182 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 16:24:48 +0100 Subject: [PATCH 26/37] bump --- packages/ns-monitoring/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index fd9edfe1b..7f91dce4d 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=0.0.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=badb4039bf3fd5f216154777d8e72413185e8f86 +PKG_SOURCE_VERSION:=f5bc545bb2fcb7a4f417dd82235509e96c92f891 PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_SOURCE_VERSION)? PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_SOURCE_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) From 73dcecf4f6d1635c3b728bce0e66664a387589d3 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 4 Mar 2026 10:34:28 +0100 Subject: [PATCH 27/37] ui bump --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index cbb356dff..82c550ff5 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=2e397bf6fd93272041c6cfd1da060b1f3731ae4a +PKG_SOURCE_VERSION:=5dd98de26cf11a93b7241dc1e5add11d1b0d4fa8 PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From 7090d03cec2657e53b91530913257cc6f7f976e8 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 5 Mar 2026 10:52:02 +0100 Subject: [PATCH 28/37] bump --- packages/ns-monitoring/Makefile | 2 +- packages/ns-ui/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index 7f91dce4d..d43bfb175 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=0.0.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=f5bc545bb2fcb7a4f417dd82235509e96c92f891 +PKG_SOURCE_VERSION:=8b296e83e412f0443158bfae754133f11a0e70d9 PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_SOURCE_VERSION)? PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_SOURCE_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 82c550ff5..92b5d8400 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=5dd98de26cf11a93b7241dc1e5add11d1b0d4fa8 +PKG_SOURCE_VERSION:=f3bf922a8ad9536c24517ea290ba2d4d0620056a PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From feb93231d3fc37b61f58c65d571969f834348d8c Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 5 Mar 2026 12:15:45 +0100 Subject: [PATCH 29/37] fixing build --- packages/ns-monitoring/Makefile | 9 ++++----- packages/ns-ui/Makefile | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index d43bfb175..ee2c7de01 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -7,13 +7,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=0.0.1 +PKG_VERSION:=8b296e83e412f0443158bfae754133f11a0e70d9 PKG_RELEASE:=1 -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=8b296e83e412f0443158bfae754133f11a0e70d9 -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_SOURCE_VERSION)? -PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_SOURCE_VERSION) +PKG_SOURCE:=nethsecurity-monitoring-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_VERSION)? +PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) PKG_HASH:=skip diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 92b5d8400..d4fda3e4f 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -10,8 +10,8 @@ PKG_NAME:=ns-ui PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 -PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz PKG_SOURCE_VERSION:=f3bf922a8ad9536c24517ea290ba2d4d0620056a +PKG_SOURCE:=nethsecurity-ui-$(PKG_SOURCE_VERSION).tar.gz PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From a52efaecb3285e86b798c20a194adad75f235e49 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 5 Mar 2026 17:44:13 +0100 Subject: [PATCH 30/37] adding blocked tag to flows --- packages/ns-api/files/ns.flows | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 6fedd916d..538840e34 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -13,6 +13,7 @@ import subprocess import requests from euci import EUci +from nethsec import conntrack from nethsec.utils import generic_error, validation_error INPUT_PATH = "/var/run/netifyd/flows.json" @@ -268,16 +269,17 @@ def set_configuration(data): } -def compute_tags(flow_item): +def compute_tags(flow_item, blocked_ids=None): """Compute tags for a flow based on its type and direction. - Returns a list of zero or more tags from: 'remote', 'outgoing', 'internal', 'broadcast', 'scanning'. + Returns a list of zero or more tags from: 'remote', 'outgoing', 'internal', 'broadcast', 'scanning', 'blocked'. Rules: - 'remote': local_origin==False AND other_type=='remote' - 'outgoing': local_origin==True AND other_type=='remote' - 'internal': other_type=='local' - 'broadcast': other_type=='broadcast' - 'scanning': top-level type=='flow' + - 'blocked': flow's conntrack id matches a conntrack entry with the 'netify-blocked' label """ tags = [] top_level_type = flow_item.get('type') @@ -305,6 +307,12 @@ def compute_tags(flow_item): if top_level_type == 'flow': tags.append('scanning') + # Check for blocked: match against conntrack entries labelled 'netify-blocked' + if blocked_ids: + ct_id = flow_item.get('flow', {}).get('conntrack', {}).get('id') + if ct_id is not None and str(ct_id) in blocked_ids: + tags.append('blocked') + return tags @@ -340,9 +348,17 @@ def main() -> None: data = json.load(sys.stdin) flows = response.json()['flows'] + # Build set of conntrack IDs that carry the 'netify-blocked' label + blocked_ids = set() + try: + for ct in conntrack.list_connections(labels=['netify-blocked']): + blocked_ids.add(ct['id']) + except Exception: + pass + # Augment each flow with additional data for f in flows: - f['tags'] = compute_tags(f) + f['tags'] = compute_tags(f, blocked_ids) per_page = data.get('per_page', 10) page = data.get('page', 1) From f981277b3bd0407647169b5801300a13661dd808 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 5 Mar 2026 17:51:45 +0100 Subject: [PATCH 31/37] ui bump --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index d4fda3e4f..977e3cecd 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -10,7 +10,7 @@ PKG_NAME:=ns-ui PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 -PKG_SOURCE_VERSION:=f3bf922a8ad9536c24517ea290ba2d4d0620056a +PKG_SOURCE_VERSION:=ae675b2c5b5f0b689f6bf6904e6199692b9cc312 PKG_SOURCE:=nethsecurity-ui-$(PKG_SOURCE_VERSION).tar.gz PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? From 56829787ae4adc0a5d0ec74f6d1a02e4ee280ce0 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 5 Mar 2026 17:58:29 +0100 Subject: [PATCH 32/37] finalized flows --- packages/ns-monitoring/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index ee2c7de01..f869b881c 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -1,5 +1,5 @@ # -# Copyright (C) 2025 Nethesis S.r.l. +# Copyright (C) 2026 Nethesis S.r.l. # SPDX-License-Identifier: GPL-2.0-only # @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=8b296e83e412f0443158bfae754133f11a0e70d9 +PKG_VERSION:=59b46b9878033f67c2cd22e394304304cac859d5 PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-monitoring-$(PKG_VERSION).tar.gz From 57e283dadd74bd0759982e9165acf57c71acefc0 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 6 Mar 2026 11:44:26 +0100 Subject: [PATCH 33/37] final release and package adjustments --- packages/ns-monitoring/Makefile | 10 +++------- .../ns-monitoring/files/monitoring.uci-defaults | 15 --------------- packages/ns-monitoring/files/ns-flows.conf | 1 + 3 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 packages/ns-monitoring/files/monitoring.uci-defaults diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index f869b881c..f16a7256e 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=59b46b9878033f67c2cd22e394304304cac859d5 +PKG_VERSION:=1.0.0 PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-monitoring-$(PKG_VERSION).tar.gz @@ -45,8 +45,6 @@ define Package/ns-monitoring/install $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/nethsecurity-monitoring $(1)/usr/sbin/ns-flows $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./files/ns-flows.conf $(1)/etc/config/ns-flows - $(INSTALL_DIR) $(1)/etc/uci-defaults - $(INSTALL_BIN) ./files/monitoring.uci-defaults $(1)/etc/uci-defaults/99-monitoring $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows $(INSTALL_DIR) $(1)/etc/netifyd @@ -59,11 +57,9 @@ endef define Package/ns-monitoring/postinst #!/bin/sh if [ -z "$${IPKG_INSTROOT}" ]; then + /etc/init.d/ns-flows enable /etc/init.d/ns-flows restart - /etc/init.d/netifyd reload - if /etc/init.d/ns-flows enabled; then - /etc/init.d/ns-flows enable - fi + /etc/init.d/netifyd restart fi exit 0 endef diff --git a/packages/ns-monitoring/files/monitoring.uci-defaults b/packages/ns-monitoring/files/monitoring.uci-defaults deleted file mode 100644 index 9dfc42a50..000000000 --- a/packages/ns-monitoring/files/monitoring.uci-defaults +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -# -# Copyright (C) 2025 Nethesis S.r.l. -# SPDX-License-Identifier: GPL-2.0-only -# - -if ! uci -q get ns-flows.daemon > /dev/null; then - uci -q import ns-flows << EOI -config config 'daemon' - option enabled '0' - option log_level 'info' - option expired_persistence '60s' -EOI -fi diff --git a/packages/ns-monitoring/files/ns-flows.conf b/packages/ns-monitoring/files/ns-flows.conf index b1f64c91b..efc01d68c 100644 --- a/packages/ns-monitoring/files/ns-flows.conf +++ b/packages/ns-monitoring/files/ns-flows.conf @@ -1,3 +1,4 @@ config config 'daemon' option enabled '1' option log_level 'info' + option expired_persistence '60s' From 7eca690791f5f31ffdc3e603dc7f500afd918a02 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 6 Mar 2026 12:46:35 +0100 Subject: [PATCH 34/37] using official release --- packages/ns-ui/Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 977e3cecd..8a2707f35 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,13 +7,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=2.14.0-beta +PKG_VERSION:=2.15.0 PKG_RELEASE:=1 -PKG_SOURCE_VERSION:=ae675b2c5b5f0b689f6bf6904e6199692b9cc312 -PKG_SOURCE:=nethsecurity-ui-$(PKG_SOURCE_VERSION).tar.gz -PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? +PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz +PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_VERSION) +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_VERSION)? PKG_HASH:=skip PKG_MAINTAINER:=Giacomo Sanchietti From 5669b580c06ad7ede92a741b614138bf198aabd5 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 6 Mar 2026 12:56:15 +0100 Subject: [PATCH 35/37] final touches --- packages/ns-api/files/ns.flows | 14 ++++++++------ packages/ns-dpi/files/dpi-data-update.init | 2 +- packages/ns-monitoring/Makefile | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 538840e34..d4fa95f9f 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -16,8 +16,6 @@ from euci import EUci from nethsec import conntrack from nethsec.utils import generic_error, validation_error -INPUT_PATH = "/var/run/netifyd/flows.json" - def get_configuration(): """Get ns-flows daemon configuration and status.""" @@ -341,12 +339,14 @@ def main() -> None: match sys.argv[2]: case 'list': try: - response = requests.get('http://127.0.0.1:8080/flows') - if "flows" not in response.json(): + _resp = requests.get('http://127.0.0.1:8080/flows') + _resp.raise_for_status() + response = _resp.json() + if "flows" not in response: result = generic_error("Invalid response from ns-flows daemon") else: data = json.load(sys.stdin) - flows = response.json()['flows'] + flows = response['flows'] # Build set of conntrack IDs that carry the 'netify-blocked' label blocked_ids = set() @@ -416,8 +416,10 @@ def main() -> None: 'last_page': last_page, 'filters': filter_values } - except requests.ConnectionError: + except requests.exceptions.RequestException: result = generic_error("Unable to connect to ns-flows daemon") + except json.JSONDecodeError: + result = generic_error("Invalid response from ns-flows daemon") case 'get-configuration': result = get_configuration() case 'set-configuration': diff --git a/packages/ns-dpi/files/dpi-data-update.init b/packages/ns-dpi/files/dpi-data-update.init index 5a4e2354b..6157f6d77 100644 --- a/packages/ns-dpi/files/dpi-data-update.init +++ b/packages/ns-dpi/files/dpi-data-update.init @@ -22,6 +22,6 @@ reload_service() } service_triggers() { - procd_add_reload_trigger "fstab" "dpi" + procd_add_reload_trigger "dpi" "ns-flows" } diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index f16a7256e..4a46e596c 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -37,7 +37,7 @@ define Package/ns-monitoring endef define Package/ns-monitoring/conffiles -/etc/config/monitoring +/etc/config/ns-flows endef define Package/ns-monitoring/install From 18753ab2d2524318b62bdcfa67e9bbeade81bb37 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 6 Mar 2026 14:04:39 +0100 Subject: [PATCH 36/37] build fix --- packages/ns-monitoring/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index 4a46e596c..849872484 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -10,8 +10,8 @@ PKG_NAME:=ns-monitoring PKG_VERSION:=1.0.0 PKG_RELEASE:=1 -PKG_SOURCE:=nethsecurity-monitoring-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_VERSION)? +PKG_SOURCE:=nethsecurity-monitoring-v$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) From cb3b58e978cb7b0db03edd4703b8f9f3ac43d0c9 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 6 Mar 2026 15:25:05 +0100 Subject: [PATCH 37/37] adjustments --- packages/ns-api/Makefile | 4 ++-- packages/ns-api/files/ns.flows | 2 +- packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults | 2 +- packages/ns-dpi/files/dpi-data-update.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index afbe9e53a..dc9e891a0 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-api -PKG_VERSION:=3.5.1-beta -PKG_RELEASE:=1 +PKG_VERSION:=3.5.1 +PKG_RELEASE:=2 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index d4fa95f9f..9b82c0e1c 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -1,7 +1,7 @@ #!/usr/bin/python3 # -# Copyright (C) 2025 Nethesis S.r.l. +# Copyright (C) 2026 Nethesis S.r.l. # SPDX-License-Identifier: GPL-2.0-only # diff --git a/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults b/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults index de54a6401..cc8d8cc07 100644 --- a/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults +++ b/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (C) 2025 Nethesis S.r.l. +# Copyright (C) 2026 Nethesis S.r.l. # SPDX-License-Identifier: GPL-2.0-only # diff --git a/packages/ns-dpi/files/dpi-data-update.py b/packages/ns-dpi/files/dpi-data-update.py index 170dd6538..148e0f3a0 100644 --- a/packages/ns-dpi/files/dpi-data-update.py +++ b/packages/ns-dpi/files/dpi-data-update.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # -# Copyright (C) 2025 Nethesis S.r.l. +# Copyright (C) 2026 Nethesis S.r.l. # SPDX-License-Identifier: GPL-2.0-only #