Files
ctrld/cmd/cli/pf_state.go
T

83 lines
2.3 KiB
Go

package cli
import (
"bufio"
"io"
"maps"
"slices"
"strings"
)
// parsePFAnchorNames returns the anchor names that the running ruleset refers to,
// unique and sorted. It takes both pfctl show commands because each one holds half
// of the list: "pfctl -sr" the scrub and filter anchors, "pfctl -sn" the
// translation anchors.
func parsePFAnchorNames(rules, nat io.Reader) []string {
unique := make(map[string]struct{})
// A read that stopped early gives a shorter list, and a shorter list reads
// as an anchor that went away.
if !collectPFAnchorNames(rules, unique) || !collectPFAnchorNames(nat, unique) {
return nil
}
return slices.Sorted(maps.Keys(unique))
}
// collectPFAnchorNames reports whether it read the whole output.
func collectPFAnchorNames(r io.Reader, into map[string]struct{}) bool {
if r == nil {
return true
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
name := pfAnchorNameOf(scanner.Text())
if name == "" {
continue
}
into[name] = struct{}{}
}
return scanner.Err() == nil
}
// pfAnchorNameOf returns the anchor name of one pfctl rule line. The rule verb is
// anchor, nat-anchor, rdr-anchor, scrub-anchor or dummynet-anchor, and the quoted
// name follows it. Every other line, including the merged ALTQ warnings, gives "".
func pfAnchorNameOf(line string) string {
verb, rest, ok := strings.Cut(strings.TrimSpace(line), " ")
if !ok || !strings.HasSuffix(verb, "anchor") {
return ""
}
_, quoted, ok := strings.Cut(rest, `"`)
if !ok {
return ""
}
name, _, ok := strings.Cut(quoted, `"`)
if !ok {
return ""
}
return name
}
// parsePFStatus reads "pfctl -si" output and reports whether pf runs, with the
// uptime text that pfctl prints beside it.
func parsePFStatus(r io.Reader) (enabled bool, since string) {
if r == nil {
return false, ""
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
status, ok := strings.CutPrefix(strings.TrimSpace(scanner.Text()), "Status:")
if !ok {
continue
}
// pfctl pads the status into a column and prints the debug level next to it.
status, _, _ = strings.Cut(status, "Debug:")
state, uptime, _ := strings.Cut(strings.TrimSpace(status), " for ")
if state != "Enabled" {
return false, ""
}
return true, strings.TrimSpace(uptime)
}
// A read that stopped early may have stopped before the status line.
return false, ""
}