From e2593eda09e76104d4f3aef026c3bcf8bf8048a3 Mon Sep 17 00:00:00 2001 From: Mason Johnson Date: Mon, 2 Oct 2023 21:54:04 -0600 Subject: [PATCH] modify logic to allow subnets for InterfaceIPs and updated docs and tests to reflect it --- docs/config.md | 8 ++++---- pkg/agent/agent.go | 14 +++++++------- pkg/agent/config.go | 5 +++-- pkg/agent/filter.go | 18 +++++++++--------- pkg/agent/filter_test.go | 17 ++++++++++------- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/config.md b/docs/config.md index 50a174997..d62f25d95 100644 --- a/docs/config.md +++ b/docs/config.md @@ -24,10 +24,10 @@ The following environment variables are available to configure the NetObserv eBF excluded from flow tracing. It takes priority over `INTERFACES` values. If an entry is enclosed by slashes (e.g. `/br-/`), it will match as regular expression, otherwise it will be matched as a case-sensitive string. -* `INTERFACE_IPS` (optional) Comma-separated list of IPs in CIDR notation (i.e. 192.0.2.0/24) whose - interfaces should be listened on. This is an alternative to specifying `INTERFACES`, useful when - you know ahead of time what IPs an interface will have but not the OS-assigned interface name itself. - Exclusive with INTERFACES/EXCLUDE_INTERFACES. +* `INTERFACE_IPS` (optional) Comma-separated list of IPs/Subnets in CIDR notation (i.e. 192.0.2.0/24). + Any interface with an associated IP address within the given ranges will be listened on. This is an + alternative to specifying `INTERFACES`, useful when you know ahead of time what IP or IP range an + interface will have but not the OS-assigned interface name itself. Exclusive with INTERFACES/EXCLUDE_INTERFACES. * `SAMPLING` (default: disabled). Rate at which packets should be sampled and sent to the target collector. E.g. if set to 10, one out of 10 packets, on average, will be sent to the target collector. diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index a25d2c44d..3269d02dd 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -181,25 +181,25 @@ func flowsAgent(cfg *Config, if len(cfg.InterfaceIPs) > 0 { // configure ip interface filter - f, err := initIPInterfaceFilter(cfg.InterfaceIPs, func(ifaceName string) ([]netip.Prefix, error) { + f, err := initIPInterfaceFilter(cfg.InterfaceIPs, func(ifaceName string) ([]netip.Addr, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { - return []netip.Prefix{}, fmt.Errorf("error retrieving interface by name: %w", err) + return []netip.Addr{}, fmt.Errorf("error retrieving interface by name: %w", err) } addrs, err := iface.Addrs() if err != nil { - return []netip.Prefix{}, fmt.Errorf("error retrieving addresses from interface: %w", err) + return []netip.Addr{}, fmt.Errorf("error retrieving addresses from interface: %w", err) } - prefixes := []netip.Prefix{} + interfaceAddrs := []netip.Addr{} for _, addr := range addrs { prefix, err := netip.ParsePrefix(addr.String()) if err != nil { - return []netip.Prefix{}, fmt.Errorf("parsing given ip to netip.Prefix: %w", err) + return []netip.Addr{}, fmt.Errorf("parsing given ip to netip.Addr: %w", err) } - prefixes = append(prefixes, prefix) + interfaceAddrs = append(interfaceAddrs, prefix.Addr()) } - return prefixes, nil + return interfaceAddrs, nil }) if err != nil { diff --git a/pkg/agent/config.go b/pkg/agent/config.go index aa6676f73..fc07a6232 100644 --- a/pkg/agent/config.go +++ b/pkg/agent/config.go @@ -58,8 +58,9 @@ type Config struct { // BuffersLength establishes the length of communication channels between the different processing // stages BuffersLength int `env:"BUFFERS_LENGTH" envDefault:"50"` - // InterfaceIPs is a list of IPs whose interfaces should be listened on. This allows users to specify - // interfaces without knowing the OS-assigned interface names. Exclusive with Interfaces/ExcludeInterfaces. + // InterfaceIPs is a list of CIDR-notation IPs/Subnets where any interface containing an IP in the given ranges + // should be listened on. This allows users to specify interfaces without knowing the OS-assigned interface names. + // Exclusive with Interfaces/ExcludeInterfaces. InterfaceIPs []string `env:"INTERFACE_IPS" envSeparator:","` // ExporterBufferLength establishes the length of the buffer of flow batches (not individual flows) // that can be accumulated before the Kafka or GRPC exporter. When this buffer is full (e.g. diff --git a/pkg/agent/filter.go b/pkg/agent/filter.go index c2439c3b8..a87b84b4e 100644 --- a/pkg/agent/filter.go +++ b/pkg/agent/filter.go @@ -5,8 +5,6 @@ import ( "net/netip" "regexp" "strings" - - "golang.org/x/exp/slices" ) type InterfaceFilter interface { @@ -18,13 +16,13 @@ type ipInterfaceFilter struct { // Almost always going to be a wrapper around getting // the interface from net.InterfaceByName and then calling // .Addrs() on the interface - ipsFromIface func(ifaceName string) ([]netip.Prefix, error) + ipsFromIface func(ifaceName string) ([]netip.Addr, error) } -// initIPInterfaceFilter allows filtering network interfaces that are accepted/exlucded by the user, +// initIPInterfaceFilter allows filtering network interfaces that are accepted/excluded by the user, // according to the provided INTERFACE_IPS from the configuration. It allows interfaces where at least // one of the provided CIDRs are associated with it. -func initIPInterfaceFilter(ips []string, ipsFromIface func(ifaceName string) ([]netip.Prefix, error)) (ipInterfaceFilter, error) { +func initIPInterfaceFilter(ips []string, ipsFromIface func(ifaceName string) ([]netip.Addr, error)) (ipInterfaceFilter, error) { ipIfaceFilter := ipInterfaceFilter{} ipIfaceFilter.ipsFromIface = ipsFromIface @@ -40,14 +38,16 @@ func initIPInterfaceFilter(ips []string, ipsFromIface func(ifaceName string) ([] } func (f *ipInterfaceFilter) Allowed(iface string) (bool, error) { - prefixes, err := f.ipsFromIface(iface) + ifaceAddrs, err := f.ipsFromIface(iface) if err != nil { return false, fmt.Errorf("error calling ipsFromIface(): %w", err) } - for _, prefix := range prefixes { - if slices.Contains(f.allowedIPs, prefix) { - return true, nil + for _, ifaceAddr := range ifaceAddrs { + for _, allowedPrefix := range f.allowedIPs { + if allowedPrefix.Contains(ifaceAddr) { + return true, nil + } } } return false, nil diff --git a/pkg/agent/filter_test.go b/pkg/agent/filter_test.go index a75aafed7..93d2450f2 100644 --- a/pkg/agent/filter_test.go +++ b/pkg/agent/filter_test.go @@ -67,30 +67,33 @@ func TestInterfaceFilter_ExclusionTakesPriority(t *testing.T) { } func TestInterfaceFilter_InterfaceIPs(t *testing.T) { - mockIPByIface := func(iface string) ([]netip.Prefix, error) { + mockIPByIface := func(iface string) ([]netip.Addr, error) { switch iface { case "eth0": - return []netip.Prefix{netip.MustParsePrefix("198.51.100.1/24")}, nil + return []netip.Addr{netip.MustParsePrefix("198.51.100.1/24").Addr()}, nil case "eth1": - return []netip.Prefix{netip.MustParsePrefix("198.51.100.2/24")}, nil + return []netip.Addr{netip.MustParsePrefix("198.51.100.2/24").Addr()}, nil case "eth2": - return []netip.Prefix{netip.MustParsePrefix("2001:db8::1/32"), netip.MustParsePrefix("198.51.100.3/24")}, nil + return []netip.Addr{netip.MustParsePrefix("2001:db8::1/32").Addr(), netip.MustParsePrefix("198.51.100.3/24").Addr()}, nil case "eth3": - return []netip.Prefix{netip.MustParsePrefix("2001:db8::2/32")}, nil + return []netip.Addr{netip.MustParsePrefix("2001:db8::2/32").Addr()}, nil + + case "eth4": + return []netip.Addr{netip.MustParsePrefix("192.0.2.120/24").Addr()}, nil default: panic("unexpected interface name") } } - ifaces, err := initIPInterfaceFilter([]string{"198.51.100.1/24", "2001:db8::1/32"}, mockIPByIface) + ifaces, err := initIPInterfaceFilter([]string{"198.51.100.1/32", "2001:db8::1/128", "192.0.2.0/24"}, mockIPByIface) require.NoError(t, err) // Allowed - for _, iface := range []string{"eth0", "eth2"} { + for _, iface := range []string{"eth0", "eth2", "eth4"} { iface := iface allowed, err := ifaces.Allowed(iface) require.NoError(t, err)