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:"` 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 }