- ruleset.nft.tpl: operator-rules bekommen `counter <action> comment "egid:<id>"`
→ nft zählt Packets + Bytes per Regel ab dem letzten Ruleset-Apply
- handlers/firewall_counters.go: GET /firewall/counters parst
`sudo nft list table inet edgeguard` per Regex, liefert [{rule_id,packets,bytes}]
- handlers/firewall_counters_test.go: unit-tests für parseNFTCounters
- Firewall/Rules.tsx: neue "Hits"-Spalte (Packet-Count, Tooltip mit Bytes),
10s-Polling via TanStack Query ['fw','counters']
- i18n de+en: fw.rule.hits
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response"
|
|
)
|
|
|
|
// ruleCounter is one operator-defined firewall rule's live packet/byte
|
|
// counter. The nft ruleset template marks each rule with
|
|
// `counter … comment "egid:<id>"` so we can correlate the counter
|
|
// values back to DB rule IDs.
|
|
type ruleCounter struct {
|
|
RuleID int64 `json:"rule_id"`
|
|
Packets int64 `json:"packets"`
|
|
Bytes int64 `json:"bytes"`
|
|
}
|
|
|
|
var (
|
|
reEgid = regexp.MustCompile(`comment "egid:(\d+)"`)
|
|
reCounter = regexp.MustCompile(`\bcounter packets (\d+) bytes (\d+)\b`)
|
|
)
|
|
|
|
// Counters reads live hit-counters from the running nft ruleset.
|
|
// Runs `sudo -n /usr/sbin/nft list table inet edgeguard` (already
|
|
// allowed by the postinst sudoers rule) and parses each rule line
|
|
// for the `counter packets N bytes M … comment "egid:X"` pattern
|
|
// that the ruleset template emits.
|
|
func (h *FirewallHandler) Counters(c *gin.Context) {
|
|
out, err := exec.Command("sudo", "-n", "/usr/sbin/nft", "list", "table", "inet", "edgeguard").CombinedOutput()
|
|
if err != nil {
|
|
response.OK(c, gin.H{
|
|
"counters": []ruleCounter{},
|
|
"error": strings.TrimSpace(string(out)),
|
|
})
|
|
return
|
|
}
|
|
response.OK(c, gin.H{"counters": parseNFTCounters(string(out))})
|
|
}
|
|
|
|
func parseNFTCounters(output string) []ruleCounter {
|
|
var out []ruleCounter
|
|
for _, line := range strings.Split(output, "\n") {
|
|
mID := reEgid.FindStringSubmatch(line)
|
|
mCnt := reCounter.FindStringSubmatch(line)
|
|
if mID == nil || mCnt == nil {
|
|
continue
|
|
}
|
|
id, err1 := strconv.ParseInt(mID[1], 10, 64)
|
|
pkts, err2 := strconv.ParseInt(mCnt[1], 10, 64)
|
|
byts, err3 := strconv.ParseInt(mCnt[2], 10, 64)
|
|
if err1 != nil || err2 != nil || err3 != nil {
|
|
continue
|
|
}
|
|
out = append(out, ruleCounter{RuleID: id, Packets: pkts, Bytes: byts})
|
|
}
|
|
return out
|
|
}
|