diff --git a/cmd/netconfigd/netconfigd.go b/cmd/netconfigd/netconfigd.go index dc0fce3..43e9ccd 100644 --- a/cmd/netconfigd/netconfigd.go +++ b/cmd/netconfigd/netconfigd.go @@ -3,10 +3,19 @@ package main import ( "flag" + "net/http" "os" "os/signal" "syscall" + "github.com/gokrazy/gokrazy" + "github.com/google/nftables" + "github.com/google/nftables/expr" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "router7/internal/multilisten" "router7/internal/netconfig" "router7/internal/teelogger" ) @@ -17,7 +26,86 @@ var ( linger = flag.Bool("linger", true, "linger around after applying the configuration (until killed)") ) +func init() { + var c nftables.Conn + for _, metric := range []struct { + name string + labels prometheus.Labels + table *nftables.Table + chain *nftables.Chain + }{ + { + name: "filter_forward", + labels: prometheus.Labels{"family": "ipv4"}, + table: &nftables.Table{Family: nftables.TableFamilyIPv4, Name: "filter"}, + chain: &nftables.Chain{Name: "forward"}, + }, + { + name: "filter_forward", + labels: prometheus.Labels{"family": "ipv6"}, + table: &nftables.Table{Family: nftables.TableFamilyIPv6, Name: "filter"}, + chain: &nftables.Chain{Name: "forward"}, + }, + } { + metric := metric // copy + promauto.NewCounterFunc( + prometheus.CounterOpts{ + Subsystem: "nftables", + Name: metric.name + "_packets", + Help: "packet count", + ConstLabels: metric.labels, + }, + func() float64 { + rules, err := c.GetRule(metric.table, metric.chain) + if err != nil || + len(rules) != 1 || + len(rules[0].Exprs) != 1 { + return 0 + } + if ce, ok := rules[0].Exprs[0].(*expr.Counter); ok { + return float64(ce.Packets) + } + return 0 + }) + promauto.NewCounterFunc( + prometheus.CounterOpts{ + Subsystem: "nftables", + Name: metric.name + "_bytes", + Help: "bytes count", + ConstLabels: metric.labels, + }, + func() float64 { + rules, err := c.GetRule(metric.table, metric.chain) + if err != nil || + len(rules) != 1 || + len(rules[0].Exprs) != 1 { + return 0 + } + if ce, ok := rules[0].Exprs[0].(*expr.Counter); ok { + return float64(ce.Bytes) + } + return 0 + }) + } +} + +func updateListeners() error { + hosts, err := gokrazy.PrivateInterfaceAddrs() + if err != nil { + return err + } + if net1, err := multilisten.IPv6Net1("/perm"); err == nil { + hosts = append(hosts, net1) + } + + return multilisten.ListenAndServe(hosts, "8066", http.DefaultServeMux) +} + func logic() error { + if *linger { + http.Handle("/metrics", promhttp.Handler()) + updateListeners() + } ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGUSR1) for { @@ -36,6 +124,9 @@ func logic() error { break } <-ch + if err := updateListeners(); err != nil { + log.Printf("updateListeners: %v", err) + } } return nil } diff --git a/integrationnetconfig_test.go b/integrationnetconfig_test.go index 845464d..83baa27 100644 --- a/integrationnetconfig_test.go +++ b/integrationnetconfig_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/nftables/expr" ) const goldenInterfaces = ` @@ -112,6 +113,8 @@ func TestNetconfig(t *testing.T) { t.Fatal(err) } + netconfig.DefaultCounter = expr.Counter{Packets: 23, Bytes: 42} + if err := netconfig.Apply(tmp, filepath.Join(tmp, "root")); err != nil { t.Fatalf("netconfig.Apply: %v", err) } @@ -227,6 +230,18 @@ func TestNetconfig(t *testing.T) { ` oifname "uplink0" masquerade`, ` }`, `}`, + `table ip filter {`, + ` chain forward {`, + ` type filter hook forward priority 0; policy accept;`, + ` counter packets 23 bytes 42`, + ` }`, + `}`, + `table ip6 filter {`, + ` chain forward {`, + ` type filter hook forward priority 0; policy accept;`, + ` counter packets 23 bytes 42`, + ` }`, + `}`, } opts := []cmp.Option{ cmp.Transformer("formatting", func(line string) string { diff --git a/internal/netconfig/netconfig.go b/internal/netconfig/netconfig.go index afba99e..03f7ae5 100644 --- a/internal/netconfig/netconfig.go +++ b/internal/netconfig/netconfig.go @@ -454,6 +454,28 @@ func applyPortForwardings(dir string, c *nftables.Conn, nat *nftables.Table, pre return nil } +// DefaultCounter is overridden while testing +var DefaultCounter expr.Counter + +func getCounter(c *nftables.Conn, table *nftables.Table, chain *nftables.Chain) expr.Counter { + rules, err := c.GetRule(table, chain) + if err != nil { + return DefaultCounter + } + if got, want := len(rules), 1; got != want { + log.Printf("could not carry counter values: unexpected number of rules in table %v, chain %v: got %d, want %d", table.Name, chain.Name, got, want) + return DefaultCounter + } + if got, want := len(rules[0].Exprs), 1; got != want { + log.Printf("could not carry counter values: unexpected number of exprs in rule 0 in table %v, chain %v: got %d, want %d", table.Name, chain.Name, got, want) + return DefaultCounter + } + if ce, ok := rules[0].Exprs[0].(*expr.Counter); ok { + return *ce + } + return DefaultCounter +} + func applyFirewall(dir string) error { c := &nftables.Conn{} @@ -501,6 +523,37 @@ func applyFirewall(dir string) error { return err } + filter4 := c.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "filter", + }) + + filter6 := c.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv6, + Name: "filter", + }) + + for _, filter := range []*nftables.Table{filter4, filter6} { + forward := c.AddChain(&nftables.Chain{ + Name: "forward", + Hooknum: nftables.ChainHookForward, + Priority: nftables.ChainPriorityFilter, + Table: filter, + Type: nftables.ChainTypeFilter, + }) + + counter := getCounter(c, filter, forward) + + c.AddRule(&nftables.Rule{ + Table: filter, + Chain: forward, + Exprs: []expr.Any{ + // [ counter pkts 23 bytes 42 ] + &counter, + }, + }) + } + return c.Flush() }