add pattern matching and blacklist functionality to NICS env var. (#1190)

This commit is contained in:
henrygd
2025-09-24 16:27:37 -04:00
parent c1c0d8d672
commit 0dc9b3e273
3 changed files with 334 additions and 18 deletions

View File

@@ -3,6 +3,7 @@ package agent
import (
"fmt"
"log/slog"
"path"
"strings"
"time"
@@ -13,6 +14,69 @@ import (
var netInterfaceDeltaTracker = deltatracker.NewDeltaTracker[string, uint64]()
// NicConfig controls inclusion/exclusion of network interfaces via the NICS env var
//
// Behavior mirrors SensorConfig's matching logic:
// - Leading '-' means blacklist mode; otherwise whitelist mode
// - Supports '*' wildcards using path.Match
// - In whitelist mode with an empty list, no NICs are selected
// - In blacklist mode with an empty list, all NICs are selected
type NicConfig struct {
nics map[string]struct{}
isBlacklist bool
hasWildcards bool
}
func newNicConfig(nicsEnvVal string) *NicConfig {
cfg := &NicConfig{
nics: make(map[string]struct{}),
}
if strings.HasPrefix(nicsEnvVal, "-") {
cfg.isBlacklist = true
nicsEnvVal = nicsEnvVal[1:]
}
for nic := range strings.SplitSeq(nicsEnvVal, ",") {
nic = strings.TrimSpace(nic)
if nic != "" {
cfg.nics[nic] = struct{}{}
if strings.Contains(nic, "*") {
cfg.hasWildcards = true
}
}
}
return cfg
}
// isValidNic determines if a NIC should be included based on NicConfig rules
func isValidNic(nicName string, cfg *NicConfig) bool {
// Empty list behavior differs by mode: blacklist: allow all; whitelist: allow none
if len(cfg.nics) == 0 {
return cfg.isBlacklist
}
// Exact match: return true if whitelist, false if blacklist
if _, exactMatch := cfg.nics[nicName]; exactMatch {
return !cfg.isBlacklist
}
// If no wildcards, return true if blacklist, false if whitelist
if !cfg.hasWildcards {
return cfg.isBlacklist
}
// Check for wildcard patterns
for pattern := range cfg.nics {
if !strings.Contains(pattern, "*") {
continue
}
if match, _ := path.Match(pattern, nicName); match {
return !cfg.isBlacklist
}
}
return cfg.isBlacklist
}
func (a *Agent) updateNetworkStats(systemStats *system.Stats) {
// network stats
if len(a.netInterfaces) == 0 {
@@ -89,14 +153,11 @@ func (a *Agent) initializeNetIoStats() {
// reset valid network interfaces
a.netInterfaces = make(map[string]struct{}, 0)
// map of network interface names passed in via NICS env var
var nicsMap map[string]struct{}
nics, nicsEnvExists := GetEnv("NICS")
// parse NICS env var for whitelist / blacklist
nicsEnvVal, nicsEnvExists := GetEnv("NICS")
var nicCfg *NicConfig
if nicsEnvExists {
nicsMap = make(map[string]struct{}, 0)
for nic := range strings.SplitSeq(nics, ",") {
nicsMap[nic] = struct{}{}
}
nicCfg = newNicConfig(nicsEnvVal)
}
// reset network I/O stats
@@ -107,17 +168,11 @@ func (a *Agent) initializeNetIoStats() {
if netIO, err := psutilNet.IOCounters(true); err == nil {
a.netIoStats.Time = time.Now()
for _, v := range netIO {
switch {
// skip if nics exists and the interface is not in the list
case nicsEnvExists:
if _, nameInNics := nicsMap[v.Name]; !nameInNics {
continue
}
// otherwise run the interface name through the skipNetworkInterface function
default:
if a.skipNetworkInterface(v) {
continue
}
if nicsEnvExists && !isValidNic(v.Name, nicCfg) {
continue
}
if a.skipNetworkInterface(v) {
continue
}
slog.Info("Detected network interface", "name", v.Name, "sent", v.BytesSent, "recv", v.BytesRecv)
a.netIoStats.BytesSent += v.BytesSent

259
agent/network_test.go Normal file
View File

@@ -0,0 +1,259 @@
//go:build testing
package agent
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIsValidNic(t *testing.T) {
tests := []struct {
name string
nicName string
config *NicConfig
expectedValid bool
}{
{
name: "Whitelist - NIC in list",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: false,
},
expectedValid: true,
},
{
name: "Whitelist - NIC not in list",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: false,
},
expectedValid: false,
},
{
name: "Blacklist - NIC in list",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: true,
},
expectedValid: false,
},
{
name: "Blacklist - NIC not in list",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: true,
},
expectedValid: true,
},
{
name: "Whitelist with wildcard - matching pattern",
nicName: "eth1",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: true,
},
{
name: "Whitelist with wildcard - non-matching pattern",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: false,
},
{
name: "Blacklist with wildcard - matching pattern",
nicName: "eth1",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: true,
hasWildcards: true,
},
expectedValid: false,
},
{
name: "Blacklist with wildcard - non-matching pattern",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: true,
hasWildcards: true,
},
expectedValid: true,
},
{
name: "Empty whitelist config - no NICs allowed",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: false,
},
expectedValid: false,
},
{
name: "Empty blacklist config - all NICs allowed",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: true,
},
expectedValid: true,
},
{
name: "Multiple patterns - exact match",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan*": {}},
isBlacklist: false,
},
expectedValid: true,
},
{
name: "Multiple patterns - wildcard match",
nicName: "wlan1",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: true,
},
{
name: "Multiple patterns - no match",
nicName: "bond0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isValidNic(tt.nicName, tt.config)
assert.Equal(t, tt.expectedValid, result)
})
}
}
func TestNewNicConfig(t *testing.T) {
tests := []struct {
name string
nicsEnvVal string
expectedCfg *NicConfig
}{
{
name: "Empty string",
nicsEnvVal: "",
expectedCfg: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Single NIC whitelist",
nicsEnvVal: "eth0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Multiple NICs whitelist",
nicsEnvVal: "eth0,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan0": {}},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Blacklist mode",
nicsEnvVal: "-eth0,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan0": {}},
isBlacklist: true,
hasWildcards: false,
},
},
{
name: "With wildcards",
nicsEnvVal: "eth*,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth*": {}, "wlan0": {}},
isBlacklist: false,
hasWildcards: true,
},
},
{
name: "Blacklist with wildcards",
nicsEnvVal: "-eth*,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth*": {}, "wlan0": {}},
isBlacklist: true,
hasWildcards: true,
},
},
{
name: "With whitespace",
nicsEnvVal: "eth0, wlan0 , eth1",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan0": {}, "eth1": {}},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Only wildcards",
nicsEnvVal: "eth*,wlan*",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth*": {}, "wlan*": {}},
isBlacklist: false,
hasWildcards: true,
},
},
{
name: "Leading dash only",
nicsEnvVal: "-",
expectedCfg: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: true,
hasWildcards: false,
},
},
{
name: "Mixed exact and wildcard",
nicsEnvVal: "eth0,br-*",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "br-*": {}},
isBlacklist: false,
hasWildcards: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := newNicConfig(tt.nicsEnvVal)
require.NotNil(t, cfg)
assert.Equal(t, tt.expectedCfg.isBlacklist, cfg.isBlacklist)
assert.Equal(t, tt.expectedCfg.hasWildcards, cfg.hasWildcards)
assert.Equal(t, tt.expectedCfg.nics, cfg.nics)
})
}
}