engine: resources: Add firewalld resource

This is a simple firewalld resource to make the seamless opening of
firewall ports in standalone laptop (and other) environments easy.
This commit is contained in:
James Shubin
2024-03-10 16:55:13 -04:00
parent 6976f5f3f0
commit c5e3e0ee70
4 changed files with 684 additions and 6 deletions

View File

@@ -0,0 +1,651 @@
// Mgmt
// Copyright (C) 2013-2024+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources
import (
"context"
"fmt"
"os/user"
"strconv"
"strings"
"sync"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits"
"github.com/purpleidea/mgmt/lang/funcs/vars"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap"
"github.com/godbus/dbus/v5"
"github.com/google/nftables"
)
func init() {
// TODO: Should we split this into firewalld:service and firewalld:port?
engine.RegisterResource(KindFirewalld, func() engine.Res { return &FirewalldRes{} })
// const.res.firewalld.state.exists = "exists"
// const.res.firewalld.state.absent = "absent"
vars.RegisterResourceParams(KindFirewalld, map[string]map[string]func() interfaces.Var{
ParamFileState: {
FirewalldStateExists: func() interfaces.Var {
return &types.StrValue{
V: FirewalldStateExists,
}
},
FirewalldStateAbsent: func() interfaces.Var {
return &types.StrValue{
V: FirewalldStateAbsent,
}
},
// TODO: consider removing this field entirely
"undefined": func() interfaces.Var {
return &types.StrValue{
V: FirewalldStateUndefined, // empty string
}
},
},
})
}
const (
// KindFirewalld is the kind string used to identify this resource.
KindFirewalld = "firewalld"
// ParamFirewalldState is the name of the state field parameter.
ParamFirewalldState = "state"
// FirewalldStateExists is the string that represents that the service,
// or port, or other setting should be present.
FirewalldStateExists = "exists"
// FirewalldStateAbsent is the string that represents that the service,
// or port, or other setting should not exist.
FirewalldStateAbsent = "absent"
// FirewalldStateUndefined means the file state has not been specified.
// TODO: consider moving to *string and express this state as a nil.
FirewalldStateUndefined = ""
// ErrInvalidZone is a firewalld error from dbus.
ErrInvalidZone = engine.Error("invalid zone")
// ErrInvalidPort is a firewalld error from dbus.
ErrInvalidPort = engine.Error("invalid port")
// ErrInvalidService is a firewalld error from dbus.
ErrInvalidService = engine.Error("invalid service")
// ErrInvalidProtocol is a firewalld error from dbus.
ErrInvalidProtocol = engine.Error("invalid protocol")
// ErrInvalidCommand is a firewalld error from dbus.
ErrInvalidCommand = engine.Error("invalid command")
// ErrMissingProtocol is a firewalld error from dbus.
ErrMissingProtocol = engine.Error("missing protocol")
// ErrAlreadyEnabled is a firewalld error from dbus.
ErrAlreadyEnabled = engine.Error("already enabled")
// ErrNotEnabled is a firewalld error from dbus.
ErrNotEnabled = engine.Error("not enabled")
firewalld1Path = dbus.ObjectPath("/org/fedoraproject/FirewallD1")
firewalld1Iface = "org.fedoraproject.FirewallD1"
)
// FirewalldRes is a simple resource to interact with the firewalld service. It
// is not a replacement for a modern, robust tool like `shorewall`, but it has
// its uses such as for small, desktop use cases. The API of this resource might
// change to either add new features, split this into multiple resources, or to
// optimize the execution if it turns out to be too expensive to run large
// amounts of these as-is. The name variable currently has no useful purpose.
// Keep in mind that this resource requires root permissions to be able change
// the firewall settings and to monitor for changes. The change detection uses
// the nftables monitor facility.
type FirewalldRes struct {
traits.Base // add the base methods without re-implementation
// XXX: add traits.Reversible and make this undo itself on removal
init *engine.Init
// Zone is the name of the zone to manage. If unspecified, we will
// attempt to get the default zone automatically. In this situation, it
// is possible that this default changes over time if it is acted upon
// by external tools that use firewalld.
Zone string `lang:"zone" yaml:"zone"`
// State is the desired state.
State string `lang:"state" yaml:"state"`
// Services are the list of services to manage to the desired state.
// These are single lower case strings like `dhcp`, and `tftp`.
Services []string `lang:"services" yaml:"services"`
// Ports are the list of port/protocol combinations to manage to the
// desired state. These are strings of port number (slash) protocol like
// `4280/tcp` and `38/udp`.
Ports []string `lang:"ports" yaml:"ports"`
// TODO: add a boolean setting for persistence (across reboots)
// TODO: this is the equivalent of `firewall-cmd --permanent`
//Permanent bool `lang:"permanent" yaml:"permanent"`
zone string // cached name of the zone we're managing
call callFunc
wg *sync.WaitGroup
}
// Default returns some sensible defaults for this resource.
func (obj *FirewalldRes) Default() engine.Res {
return &FirewalldRes{}
}
// Validate if the params passed in are valid data.
func (obj *FirewalldRes) Validate() error {
// Check that we're running as root or with enough capabilities.
// XXX: check that we have enough capabilities if we're not root
currentUser, err := user.Current()
if err != nil {
return errwrap.Wrapf(err, "error looking up current user")
}
if currentUser.Uid != "0" {
// TODO: Technically we only need perms to watch state and to
// change things, so without this we could just error entirely
// if this were only used as a change detection tool.
return fmt.Errorf("running as root is required for firewalld")
}
if obj.State != "" {
if obj.State != FirewalldStateExists && obj.State != FirewalldStateAbsent {
return fmt.Errorf("invalid state: %s", obj.State)
}
}
for _, x := range obj.Services {
// TODO: we could check services from a list
if x == "" {
return fmt.Errorf("service is empty")
}
}
for _, x := range obj.Ports {
split := strings.Split(x, "/")
if len(split) != 2 {
return fmt.Errorf("port/protocol was invalid: %s", x)
}
if num, err := strconv.Atoi(split[0]); err != nil {
return err
} else if num <= 0 {
return fmt.Errorf("invalid number: %d", num)
}
// TODO: we could check protocols from a list
if split[1] == "" {
return fmt.Errorf("protocol is empty")
}
}
return nil
}
// Init runs some startup code for this resource.
func (obj *FirewalldRes) Init(init *engine.Init) error {
obj.init = init // save for later
obj.wg = &sync.WaitGroup{}
return nil
}
// Cleanup is run by the engine to clean up after the resource is done.
func (obj *FirewalldRes) Cleanup() error {
return nil
}
// Watch is the primary listener for this resource and it outputs events. This
// could have been implemented with an exec of `/usr/sbin/nft --json monitor`,
// but the wrapped polkit and difficulty getting permissions using capabilities
// and without using full root, was challenging. This is cleaner too.
func (obj *FirewalldRes) Watch(ctx context.Context) error {
defer obj.wg.Wait()
// `sudo setcap CAP_NET_ADMIN=+eip mgmt` seems to let us avoid root
opts := []nftables.ConnOption{}
conn, err := nftables.New(opts...) // (*nftables.Conn, error)
if err != nil {
return err
}
// XXX: filter out events we don't care about using WithMonitorObject
//opt := nftables.WithMonitorObject(???)
mopts := []nftables.MonitorOption{}
//mopts = append(mopts, opt)
monitor := nftables.NewMonitor(mopts...) // *nftables.Monitor
defer monitor.Close()
events, err := conn.AddMonitor(monitor)
if err != nil {
return err
}
obj.init.Running() // when started, notify engine that we're running
var send = false // send event?
for {
select {
case event, ok := <-events: // &nftables.MonitorEvent
if !ok {
return nil
}
if event.Error != nil {
return errwrap.Wrapf(err, "monitor err")
}
if obj.init.Debug {
//obj.init.Logf("event: %#v", event)
obj.init.Logf("event type: %v", event.Type)
//obj.init.Logf("event data: %+v", event.Data)
}
send = true
case <-ctx.Done(): // closed by the engine to signal shutdown
return nil
}
// do all our event sending all together to avoid duplicate msgs
if send {
send = false
obj.init.Event() // notify engine of an event (this can block)
}
}
}
// CheckApply checks the resource state and applies the resource if the bool
// input is true. It returns error info and if the state check passed or not.
func (obj *FirewalldRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
conn, err := util.SystemBusPrivateUsable()
if err != nil {
return false, errwrap.Wrapf(err, "failed to connect to the private system bus")
}
defer conn.Close()
firewalldObject := conn.Object(firewalld1Iface, firewalld1Path) // TODO: can we reuse this?
var flags dbus.Flags // none set
obj.call = func(ctx context.Context, method string, args ...interface{}) *dbus.Call {
fullMethod := firewalld1Iface + method // eg: ".getDefaultZone"
return firewalldObject.CallWithContext(ctx, fullMethod, flags, args...)
}
// NOTE: The default zone might change over time by external users...
obj.zone = obj.Zone // default to this
if obj.Zone == "" {
if err := obj.call(ctx, ".getDefaultZone").Store(&obj.zone); err != nil {
return false, err
}
if obj.zone == "" {
return false, fmt.Errorf("unexpected empty zone")
}
obj.init.Logf("zone: %s\n", obj.zone)
}
checkOK := true
// This ordering doesn't currently matter, but might change if we find
// any sort of relevant relationship.
for _, x := range obj.Services {
if c, err := obj.serviceCheckApply(ctx, apply, x); err != nil {
return false, err
} else if !c {
checkOK = false
}
}
for _, x := range obj.Ports {
if c, err := obj.portCheckApply(ctx, apply, x); err != nil {
return false, err
} else if !c {
checkOK = false
}
}
return checkOK, nil
}
// does the equivalent of: `firewall-cmd --zone=<zone> --list-services` and:
// `firewall-cmd --zone=<zone> --add-service=<service>` and: `firewall-cmd
// --zone=<zone> --remove-service=<service>`.
func (obj *FirewalldRes) serviceCheckApply(ctx context.Context, apply bool, service string) (bool, error) {
// .zone.getServices(s: zone) -> as
var services []string
args := []interface{}{obj.zone}
if err := obj.call(ctx, ".zone.getServices", args...).Store(&services); err != nil {
if parseError(err) == ErrInvalidZone {
obj.init.Logf("did the zone change?") // two managers!
}
return false, err
}
if obj.init.Debug {
obj.init.Logf("services: %+v", services)
}
found := util.StrInList(service, services)
exists := obj.State != FirewalldStateAbsent // other states mean "exists"
if exists && found || !exists && !found {
return true, nil
}
if !apply {
return false, nil
}
if !found {
// add it
// .zone.addService(s: zone, s: service, i: timeout) -> s
timeout := 0 // TODO: what should this be?
var output string
addArgs := []interface{}{obj.zone, service, timeout}
if err := obj.call(ctx, ".zone.addService", addArgs...).Store(&output); err != nil && parseError(err) != ErrAlreadyEnabled {
return false, err
}
obj.init.Logf("service added: %s", service)
if output != obj.zone {
// programming error
return false, fmt.Errorf("dbus API inconsistency")
}
return false, nil
}
// remove it
// .zone.removeService(s: zone, s: service) -> s
//timeout := 0
var output string
removeArgs := []interface{}{obj.zone, service}
if err := obj.call(ctx, ".zone.removeService", removeArgs...).Store(&output); err != nil && parseError(err) != ErrNotEnabled {
return false, err
}
obj.init.Logf("service removed: %s", service)
if output != obj.zone {
// programming error
return false, fmt.Errorf("dbus API inconsistency")
}
return false, nil
}
// does the equivalent of: `firewall-cmd --zone=<zone> --list-ports` and:
// `firewall-cmd --zone=<zone> --add-port=4280/tcp` and: `firewall-cmd
// --zone=<zone> --remove-port=4280/tcp`.
func (obj *FirewalldRes) portCheckApply(ctx context.Context, apply bool, pp string) (bool, error) {
split := strings.Split(pp, "/")
if len(split) != 2 { // should already be checked in Validate
return false, fmt.Errorf("port/protocol was invalid: %s", pp)
}
port, err := strconv.Atoi(split[0])
if err != nil {
return false, err
} else if port <= 0 {
return false, fmt.Errorf("invalid number: %d", port)
}
protocol := split[1]
// .zone.getPorts(s: zone) -> aas
var ports [][]string
args := []interface{}{obj.zone}
if err := obj.call(ctx, ".zone.getPorts", args...).Store(&ports); err != nil {
if parseError(err) == ErrInvalidZone {
obj.init.Logf("did the zone change?") // two managers!
}
return false, err
}
if obj.init.Debug {
obj.init.Logf("ports: %+v", ports)
}
found := false
for i, x := range ports {
// eg: ["1025-65535", "udp"]
if len(x) != 2 {
return false, fmt.Errorf("unexpected ports length (%d), got: %v", len(x), x)
}
// x[0] is range or single value, eg: "1025-65535" OR "42"
// x[1] is proto like "tcp" or "udp"
if obj.init.Debug {
obj.init.Logf("rule %d: %s/%s", i, x[0], x[1])
}
if protocol != x[1] {
continue // not found (not us)
}
// does the port number match?
split := strings.Split(x[0], "-") // split the range
if len(split) != 1 && len(split) != 2 {
return false, fmt.Errorf("unexpected ports format (%d), got: %v", len(split), split)
}
if len(split) == 1 { // standalone single value
if num, err := strconv.Atoi(split[0]); err != nil {
return false, err // programming error
} else if num != port {
continue // not found
}
found = true // yay!
break
}
//if len(split) == 2
lhs, err := strconv.Atoi(split[0])
if err != nil {
return false, err // programming error
}
rhs, err := strconv.Atoi(split[1])
if err != nil {
return false, err // programming error
}
if lhs <= port && port <= rhs { // ranges are inclusive on both boundss
found = true // yay!
break
}
}
exists := obj.State != FirewalldStateAbsent // other states mean "exists"
if exists && found || !exists && !found {
return true, nil
}
if !apply {
return false, nil
}
if !found {
// add it
// .zone.addPort(s: zone, s: port, s: protocol, i: timeout) -> s
timeout := 0 // TODO: what should this be?
var output string
addArgs := []interface{}{obj.zone, strconv.Itoa(port), protocol, timeout}
if err := obj.call(ctx, ".zone.addPort", addArgs...).Store(&output); err != nil && parseError(err) != ErrAlreadyEnabled {
return false, err
}
obj.init.Logf("port added: %s", pp)
if output != obj.zone {
// programming error
return false, fmt.Errorf("dbus API inconsistency")
}
return false, nil
}
// remove it
// .zone.removePort(s: zone, s: port, s: protocol) -> s
//timeout := 0
var output string
removeArgs := []interface{}{obj.zone, strconv.Itoa(port), protocol}
if err := obj.call(ctx, ".zone.removePort", removeArgs...).Store(&output); err != nil && parseError(err) != ErrNotEnabled {
return false, err
}
obj.init.Logf("port removed: %s", pp)
if output != obj.zone {
// programming error
return false, fmt.Errorf("dbus API inconsistency")
}
return false, nil
}
// Cmp compares two resources and returns an error if they are not equivalent.
func (obj *FirewalldRes) Cmp(r engine.Res) error {
// we can only compare FirewalldRes to others of the same resource kind
res, ok := r.(*FirewalldRes)
if !ok {
return fmt.Errorf("not a %s", obj.Kind())
}
if obj.Zone != res.Zone {
return fmt.Errorf("the Zone differs")
}
if obj.State != res.State {
return fmt.Errorf("the State differs")
}
if len(obj.Services) != len(res.Services) {
return fmt.Errorf("the Services differ")
}
for i, x := range obj.Services {
if x != res.Services[i] {
return fmt.Errorf("the service at index %d differs", i)
}
}
if len(obj.Ports) != len(res.Ports) {
return fmt.Errorf("the Ports differ")
}
for i, x := range obj.Ports {
if x != res.Ports[i] {
return fmt.Errorf("the port/protocol at index %d differs", i)
}
}
// TODO: consider adding this setting
//if obj.Permanent != res.Permanent {
// return fmt.Errorf("the Permanent differs")
//}
return nil
}
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
// primarily useful for setting the defaults.
func (obj *FirewalldRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes FirewalldRes // indirection to avoid infinite recursion
def := obj.Default() // get the default
res, ok := def.(*FirewalldRes) // put in the right format
if !ok {
return fmt.Errorf("could not convert to FirewalldRes")
}
raw := rawRes(*res) // convert; the defaults go here
if err := unmarshal(&raw); err != nil {
return err
}
*obj = FirewalldRes(raw) // restore from indirection with type conversion!
return nil
}
// callFunc is a helper func for simplifying the making of dbus calls.
type callFunc func(ctx context.Context, method string, args ...interface{}) *dbus.Call
// parseError converts a returned error into one of our error constants if it
// matches. If it doesn't match, then it passes the data through. This is a
// useful alternative mechanism to inline string matching on the returned error.
func parseError(err error) error {
if err == nil {
return nil // passthrough
}
dbusError, ok := err.(dbus.Error)
if !ok {
return err // passthrough
}
if s := firewalld1Iface + ".Exception"; dbusError.Name != s {
return err // passthrough
}
// eg:
// dbus.Error{
// Name:"org.fedoraproject.FirewallD1",
// Body:[]interface {}{
// "NOT_ENABLED: '4280:tcp' not in 'FedoraWorkstation'",
// },
//}
if len(dbusError.Body) != 1 { // TODO: does this happen?
return err // passthrough
}
body, ok := (dbusError.Body[0]).(string)
if !ok {
return err // passthrough
}
// eg: "NOT_ENABLED: '4280:tcp' not in 'FedoraWorkstation'"
sep := ": " // the first colon space after the error name
split := strings.Split(body, sep)
//other := strings.Join(split[1:], sep) // remaining data (detailed reason)
switch split[0] {
case "INVALID_ZONE":
return ErrInvalidZone
case "INVALID_PORT":
return ErrInvalidPort
case "INVALID_SERVICE":
return ErrInvalidService
case "INVALID_PROTOCOL":
return ErrInvalidProtocol
case "INVALID_COMMAND":
return ErrInvalidCommand
case "MISSING_PROTOCOL":
return ErrMissingProtocol
case "ALREADY_ENABLED": // we tried to add it but it was already there
return ErrAlreadyEnabled
case "NOT_ENABLED": // we tried to remove it but it was already gone
return ErrNotEnabled
}
return err // passthrough
}

View File

@@ -0,0 +1,9 @@
firewalld "misc" { # name is irrelevant
services => [
"dhcp",
"tftp",
],
ports => ["4280/tcp",],
state => $const.res.firewalld.state.absent,
}

14
go.mod
View File

@@ -32,8 +32,8 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.5.10 go.etcd.io/etcd/client/pkg/v3 v3.5.10
go.etcd.io/etcd/client/v3 v3.5.10 go.etcd.io/etcd/client/v3 v3.5.10
go.etcd.io/etcd/server/v3 v3.5.10 go.etcd.io/etcd/server/v3 v3.5.10
golang.org/x/crypto v0.16.0 golang.org/x/crypto v0.21.0
golang.org/x/sys v0.15.0 golang.org/x/sys v0.18.0
golang.org/x/time v0.5.0 golang.org/x/time v0.5.0
gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@@ -65,7 +65,8 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/nftables v0.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.7.2 // indirect github.com/gorilla/mux v1.7.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
@@ -92,9 +93,10 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/packet v1.0.0 // indirect github.com/mdlayher/packet v1.0.0 // indirect
github.com/mdlayher/raw v0.1.0 // indirect github.com/mdlayher/raw v0.1.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect github.com/mdlayher/socket v0.5.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
@@ -148,8 +150,8 @@ require (
go.uber.org/zap v1.23.0 // indirect go.uber.org/zap v1.23.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.5.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.16.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect

16
go.sum
View File

@@ -317,11 +317,15 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8=
github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -554,6 +558,8 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
@@ -564,6 +570,8 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -979,6 +987,8 @@ golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1084,6 +1094,8 @@ golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1114,6 +1126,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpi
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1205,6 +1219,8 @@ golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=