138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
|
|
fw "github.com/grmrgecko/firewall"
|
|
)
|
|
|
|
// This file holds the read-only/info subcommands: status (backend + caps),
|
|
// zone (resolve an interface's zone), and reload (force a backend reload).
|
|
|
|
// StatusCmd reports the detected backend and its capabilities. It is the
|
|
// default thing to run when orienting on an unfamiliar host.
|
|
type StatusCmd struct{}
|
|
|
|
// Run reports the detected backend and its capabilities.
|
|
func (c *StatusCmd) Run(g *Globals) error {
|
|
mgr, cleanup, err := g.manager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
|
|
caps := mgr.Capabilities()
|
|
type statusOut struct {
|
|
Backend string `json:"backend"`
|
|
Prefix string `json:"prefix"`
|
|
Capabilities fw.Capabilities `json:"capabilities"`
|
|
}
|
|
out := statusOut{
|
|
Backend: mgr.Type(),
|
|
Prefix: g.Prefix,
|
|
Capabilities: caps,
|
|
}
|
|
return g.emit(out, func() error {
|
|
fmt.Printf("backend: %s\n", out.Backend)
|
|
fmt.Printf("prefix: %s\n", out.Prefix)
|
|
fmt.Println()
|
|
fmt.Println("capabilities:")
|
|
printCapabilities(os.Stdout, caps)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// printCapabilities lists the capability booleans in a stable, readable order.
|
|
// It walks the struct via reflection so a new bool field in fw.Capabilities is
|
|
// surfaced automatically without a parallel slice here. Non-bool fields are
|
|
// skipped so a future non-bool capability cannot panic the reflection walk.
|
|
func printCapabilities(w io.Writer, caps fw.Capabilities) {
|
|
v := reflect.ValueOf(caps)
|
|
t := v.Type()
|
|
type field struct {
|
|
name string
|
|
ok bool
|
|
}
|
|
fields := make([]field, 0, t.NumField())
|
|
for i := 0; i < t.NumField(); i++ {
|
|
if v.Field(i).Kind() != reflect.Bool {
|
|
continue
|
|
}
|
|
fields = append(fields, field{
|
|
name: t.Field(i).Name,
|
|
ok: v.Field(i).Bool(),
|
|
})
|
|
}
|
|
sort.Slice(fields, func(i, j int) bool { return fields[i].name < fields[j].name })
|
|
maxLen := 0
|
|
for _, f := range fields {
|
|
if len(f.name) > maxLen {
|
|
maxLen = len(f.name)
|
|
}
|
|
}
|
|
for _, f := range fields {
|
|
mark := "-"
|
|
if f.ok {
|
|
mark = "yes"
|
|
}
|
|
_, _ = fmt.Fprintf(w, " %-*s %s\n", maxLen, f.name, mark)
|
|
}
|
|
}
|
|
|
|
// ZoneCmd resolves the zone an interface belongs to. On backends without zones
|
|
// (iptables, nftables, pf, WFP) the returned zone is empty and that is the
|
|
// correct answer — it just means the backend has no zone abstraction.
|
|
type ZoneCmd struct {
|
|
Interface string `arg:"" name:"interface" help:"Network interface to resolve (e.g. eth0)."`
|
|
}
|
|
|
|
// Run resolves and prints the zone the interface belongs to.
|
|
func (c *ZoneCmd) Run(g *Globals) error {
|
|
mgr, cleanup, err := g.manager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
|
|
zone, err := mgr.GetZone(context.Background(), c.Interface)
|
|
if err != nil {
|
|
return fmt.Errorf("resolving zone for %q: %w", c.Interface, err)
|
|
}
|
|
type zoneOut struct {
|
|
Interface string `json:"interface"`
|
|
Zone string `json:"zone"`
|
|
}
|
|
out := zoneOut{Interface: c.Interface, Zone: zone}
|
|
return g.emit(out, func() error {
|
|
// A zoneless backend returns an empty zone; show a human placeholder here
|
|
// only, so the JSON form keeps the honest empty string.
|
|
display := out.Zone
|
|
if display == "" {
|
|
display = "(none)"
|
|
}
|
|
fmt.Printf("%s -> %s\n", out.Interface, display)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ReloadCmd forces a backend reload, activating any staged rules. Useful after
|
|
// a series of --no-reload mutations, or to pick up changes made out of band.
|
|
type ReloadCmd struct{}
|
|
|
|
// Run forces a backend reload, activating any staged rules.
|
|
func (c *ReloadCmd) Run(g *Globals) error {
|
|
mgr, cleanup, err := g.manager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
if err := mgr.Reload(context.Background()); err != nil {
|
|
return fmt.Errorf("reload: %w", err)
|
|
}
|
|
return g.emitStatus("reloaded")
|
|
}
|