feat(api, web): enhance MikroTik integration and IP hit tracking
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m54s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Updated the `evofw-firewall.sh` script to improve the handling of NFT sets, ensuring compatibility with kernel limitations on counters and enhancing logging for better diagnostics.
- Introduced a new presence mode for MikroTik, allowing for real-time tracking of IP hits with updated last seen timestamps and packet counts.
- Enhanced the API to support the new presence mode, updating the database interactions to reflect the changes in how IP hits are recorded.
- Updated the agent detail view to display sync windows for MikroTik, providing clearer insights into blocked IPs and their activity.
- Improved documentation to reflect the new features and changes in the MikroTik handling process, ensuring clarity for users and developers.

These changes significantly enhance the monitoring capabilities and user experience for agents, particularly those using MikroTik devices.
This commit is contained in:
Denozordec
2026-08-07 14:46:06 +07:00
parent 4ee78032c4
commit ee3429b747
8 changed files with 237 additions and 54 deletions
+38 -13
View File
@@ -118,18 +118,34 @@ nft_add_chunk() {
}
}
# Ensure inet set exists with interval + counter (recreate if missing counter).
# Ensure inet set exists. Prefer per-element counters; many kernels reject
# `counter` on interval sets — fall back to plain interval (no per-IP hits).
# Caller must delete referencing chains before recreating a set.
ensure_nft_set() {
local table=$1 name=$2 setname=$3
local def
def=$(nft -a list set "$table" "$name" "$setname" 2>/dev/null || true)
def=$(nft list set "$table" "$name" "$setname" 2>/dev/null || true)
if [[ -n "$def" ]] && [[ "$def" == *"counter"* ]]; then
return 0
fi
if [[ -n "$def" ]]; then
# Upgrade path: drop old set without counters (chain must already be gone).
nft delete set "$table" "$name" "$setname" 2>>"$LOG_FILE" || true
fi
nft add set "$table" "$name" "$setname" '{ type ipv4_addr; flags interval; counter; }' 2>>"$LOG_FILE"
if nft add set "$table" "$name" "$setname" '{ type ipv4_addr; flags interval; counter; }' 2>>"$LOG_FILE"; then
return 0
fi
# Set may still exist if delete failed — try plain create only if missing.
if nft list set "$table" "$name" "$setname" >/dev/null 2>&1; then
log "nft: keep existing set $setname (no per-element counter)"
return 0
fi
if nft add set "$table" "$name" "$setname" '{ type ipv4_addr; flags interval; }' 2>>"$LOG_FILE"; then
log "nft: set $setname without counter (interval+counter unsupported)"
return 0
fi
log "nft: failed to create set $setname"
return 1
}
collect_nft_stats() {
@@ -214,10 +230,13 @@ apply_nft() {
for p in "${ALLOW[@]+"${ALLOW[@]}"}"; do [[ "$p" == *:* ]] && continue; allow_v4+=("$p"); done
nft list table "$table" "$name" >/dev/null 2>&1 || nft add table "$table" "$name"
# Drop chain first so sets can be deleted/recreated (upgrade to counters).
# Stats were already captured by the caller before apply_nft.
nft delete chain "$table" "$name" input 2>/dev/null || true
ensure_nft_set "$table" "$name" deny_v4
ensure_nft_set "$table" "$name" allow_v4
nft flush set "$table" "$name" deny_v4
nft flush set "$table" "$name" allow_v4
nft flush set "$table" "$name" deny_v4 2>>"$LOG_FILE" || true
nft flush set "$table" "$name" allow_v4 2>>"$LOG_FILE" || true
local batch=() chunk=64
for p in "${deny_v4[@]}"; do
@@ -232,7 +251,6 @@ apply_nft() {
done
((${#batch[@]})) && nft_add_chunk "$table" "$name" allow_v4 "${batch[@]}"
nft delete chain "$table" "$name" input 2>/dev/null || true
# Unified chain: deny → allow → default_action
if [[ "$DEFAULT_ACTION" == "drop" ]]; then
nft add chain "$table" "$name" input '{ type filter hook input priority 0; policy drop; }'
@@ -255,16 +273,23 @@ apply_nft() {
ensure_ipset_counters() {
local name=$1
if ! ipset list "$name" >/dev/null 2>&1; then
ipset create "$name" hash:net family inet counters
return
if ipset create "$name" hash:net family inet counters 2>>"$LOG_FILE"; then
return 0
fi
ipset create "$name" hash:net family inet 2>>"$LOG_FILE" || {
log "ipset: failed to create $name"
return 1
}
return 0
fi
# Recreate once if set has no packet counters (Header lacks "counters").
local header
header=$(ipset list "$name" 2>/dev/null | head -n 5 || true)
if [[ "$header" != *"counters"* && "$header" != *"packet"* ]]; then
ipset destroy "$name" 2>>"$LOG_FILE" || true
ipset create "$name" hash:net family inet counters
if [[ "$header" == *"counters"* ]]; then
return 0
fi
# Cannot safely destroy while iptables may reference the set — leave as-is.
log "ipset: $name has no counters (leave existing; per-IP hits unavailable)"
}
apply_ipset() {
@@ -324,11 +349,11 @@ if [[ -f "$HASH_FILE" && "$(tr -d '\r\n' <"$HASH_FILE")" == "$HASH" && -n "$HASH
if [[ "$KERNEL_METHOD" == "nft" ]]; then
APPLIED=$(nft list set inet evofw deny_v4 2>/dev/null | grep -cE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' || true)
local_allow=$(nft list set inet evofw allow_v4 2>/dev/null | grep -cE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' || true)
APPLIED=$((APPLIED + local_allow))
APPLIED=$((${APPLIED:-0} + ${local_allow:-0}))
elif [[ "$KERNEL_METHOD" == "ipset" ]]; then
APPLIED=$(ipset list evofw_deny_v4 2>/dev/null | awk '/^[0-9]/{c++} END{print c+0}')
local_allow=$(ipset list evofw_allow_v4 2>/dev/null | awk '/^[0-9]/{c++} END{print c+0}')
APPLIED=$((APPLIED + local_allow))
APPLIED=$((${APPLIED:-0} + ${local_allow:-0}))
fi
send_report
exit 0