Remove partial userspace firewall mode and open foreign chains via a table-less allower

This commit is contained in:
Viktor Liu
2026-06-03 22:01:22 +02:00
parent 38603c7552
commit f7e9df6ffa
31 changed files with 840 additions and 475 deletions
@@ -1,37 +0,0 @@
//go:build !windows
package uspfilter
import (
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/firewall/firewalld"
"github.com/netbirdio/netbird/client/internal/statemanager"
)
// Close cleans up the firewall manager by removing all rules and closing trackers
func (m *Manager) Close(stateManager *statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.resetState()
if m.nativeFirewall != nil {
return m.nativeFirewall.Close(stateManager)
}
if err := firewalld.UntrustInterface(m.wgIface.Name()); err != nil {
log.Warnf("failed to untrust interface in firewalld: %v", err)
}
return nil
}
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
if m.nativeFirewall != nil {
return m.nativeFirewall.AllowNetbird()
}
if err := firewalld.TrustInterface(m.wgIface.Name()); err != nil {
log.Warnf("failed to trust interface in firewalld: %v", err)
}
return nil
}
-17
View File
@@ -1,17 +0,0 @@
package common
import (
wgdevice "golang.zx2c4.com/wireguard/device"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/wgaddr"
)
// IFaceMapper defines subset methods of interface required for manager
type IFaceMapper interface {
Name() string
SetFilter(device.PacketFilter) error
Address() wgaddr.Address
GetWGDevice() *wgdevice.Device
GetDevice() *device.FilteredDevice
}
+170 -97
View File
@@ -19,14 +19,18 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/go-multierror"
log "github.com/sirupsen/logrus"
wgdevice "golang.zx2c4.com/wireguard/device"
nberrors "github.com/netbirdio/netbird/client/errors"
"github.com/netbirdio/netbird/client/firewall/firewalld"
firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
"github.com/netbirdio/netbird/client/firewall/uspfilter/forwarder"
nblog "github.com/netbirdio/netbird/client/firewall/uspfilter/log"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/netstack"
"github.com/netbirdio/netbird/client/iface/wgaddr"
nbid "github.com/netbirdio/netbird/client/internal/acl/id"
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
"github.com/netbirdio/netbird/client/internal/statemanager"
@@ -57,7 +61,10 @@ const (
// EnvDisableMSSClamping disables TCP MSS clamping for forwarded traffic.
EnvDisableMSSClamping = "NB_DISABLE_MSS_CLAMPING"
// EnvForceUserspaceRouter forces userspace routing even if native routing is available.
// EnvForceUserspaceRouter is a deprecated alias for
// NB_FORCE_USERSPACE_FIREWALL: the userspace firewall always routes in
// userspace, so forcing one forces the other. Kept for backward
// compatibility.
EnvForceUserspaceRouter = "NB_FORCE_USERSPACE_ROUTER"
// EnvEnableLocalForwarding enables forwarding of local traffic to the native stack for internal (non-NetBird) interfaces.
@@ -69,7 +76,10 @@ const (
EnvEnableNetstackLocalForwarding = "NB_ENABLE_NETSTACK_LOCAL_FORWARDING"
)
var errNatNotSupported = errors.New("nat not supported with userspace firewall")
// errNotSupported is returned by firewall operations that only make sense with
// a kernel firewall (kernel NAT/DNAT, eBPF) and are not implemented in
// userspace mode, where they should not be called.
var errNotSupported = errors.New("not supported with userspace firewall")
// peerRules is the canonical list-based storage for peer ACL rules.
// Match order is significant: drop rules come before accept rules so
@@ -105,17 +115,48 @@ type peerRuleSpec struct {
action firewall.Action
}
// Iface is the network interface the userspace firewall attaches to: the
// methods of the WireGuard device it actually uses.
type Iface interface {
Name() string
Address() wgaddr.Address
SetFilter(device.PacketFilter) error
GetWGDevice() *wgdevice.Device
}
// InterfaceAllower opens the NetBird interface in the host firewall so it
// doesn't drop traffic the userspace firewall handles, without taking over
// packet filtering. Implementations (nftables, iptables, firewalld, the windows
// netsh rule) are selected per platform and injected into Create; Apply runs at
// creation and Close on teardown.
type InterfaceAllower interface {
Apply() error
Close() error
}
// Config holds the dependencies and options for the userspace firewall.
type Config struct {
// IFace is the overlay interface the filter attaches to.
IFace Iface
// InterfaceAllower opens the NetBird interface in foreign kernel filter
// chains so the kernel doesn't drop traffic the userspace firewall handles.
// Nil in netstack mode, on non-Linux platforms without a backend, or when
// neither nftables nor iptables is available. firewalld trust is applied by
// the manager regardless, since firewalld owns its own chains and we cannot
// insert into them.
InterfaceAllower InterfaceAllower
// DisableServerRoutes indicates whether server routes are disabled.
DisableServerRoutes bool
FlowLogger nftypes.FlowLogger
MTU uint16
}
// Manager userspace firewall manager
type Manager struct {
decoders sync.Pool
wgIface common.IFaceMapper
// nativeFirewall is the kernel firewall (nftables/iptables) used for
// the split case where peer ACLs run in userspace here but routing
// stays in the kernel: when the userspace firewall is forced yet the
// router keeps using the kernel, route NAT/ACLs and DNAT are
// delegated to it. It is nil when no native backend is available.
nativeFirewall firewall.Manager
mutex sync.RWMutex
decoders sync.Pool
wgIface Iface
ifaceAllower InterfaceAllower
mutex sync.RWMutex
incomingDenyRules peerRules
incomingAcceptRules peerRules
@@ -236,7 +277,7 @@ func parseCreateEnv() (bool, bool, bool) {
return disableConntrack, enableLocalForwarding, disableMSSClamping
}
func Create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableServerRoutes bool, flowLogger nftypes.FlowLogger, mtu uint16) (*Manager, error) {
func Create(cfg Config) (_ *Manager, err error) {
disableConntrack, enableLocalForwarding, disableMSSClamping := parseCreateEnv()
m := &Manager{
@@ -259,13 +300,13 @@ func Create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
return d
},
},
wgIface: iface,
nativeFirewall: nativeFirewall,
wgIface: cfg.IFace,
ifaceAllower: cfg.InterfaceAllower,
localipmanager: newLocalIPManager(),
disableServerRoutes: disableServerRoutes,
disableServerRoutes: cfg.DisableServerRoutes,
stateful: !disableConntrack,
logger: nblog.NewFromLogrus(log.StandardLogger()),
flowLogger: flowLogger,
flowLogger: cfg.FlowLogger,
netstack: netstack.IsEnabled(),
localForwarding: enableLocalForwarding,
peerRulesMap: make(map[nbid.RuleID]*PeerRule),
@@ -273,46 +314,117 @@ func Create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
dnatMappings: make(map[netip.Addr]netip.Addr),
portDNATRules: []portDNATRule{},
netstackServices: make(map[serviceKey]struct{}),
mtu: mtu,
mtu: cfg.MTU,
}
m.routingEnabled.Store(false)
// Release the allower (and its monitor) if setup fails after it was wired in.
defer func() {
if err != nil {
m.closeAllowerOnError()
}
}()
if !disableMSSClamping {
m.mssClampEnabled = true
if mtu > ipv4TCPHeaderMinSize {
m.mssClampValueIPv4 = mtu - ipv4TCPHeaderMinSize
}
if mtu > ipv6TCPHeaderMinSize {
m.mssClampValueIPv6 = mtu - ipv6TCPHeaderMinSize
}
m.enableMSSClamping(cfg.MTU)
}
if err := m.localipmanager.UpdateLocalIPs(iface); err != nil {
if err := m.localipmanager.UpdateLocalIPs(cfg.IFace); err != nil {
return nil, fmt.Errorf("update local IPs: %w", err)
}
if disableConntrack {
log.Info("conntrack is disabled")
} else {
m.udpTracker = conntrack.NewUDPTracker(conntrack.DefaultUDPTimeout, m.logger, flowLogger)
m.icmpTracker = conntrack.NewICMPTracker(conntrack.DefaultICMPTimeout, m.logger, flowLogger)
m.tcpTracker = conntrack.NewTCPTracker(conntrack.DefaultTCPTimeout, m.logger, flowLogger)
}
m.setupConntrack(disableConntrack)
if m.netstack && m.localForwarding {
if err := m.initForwarder(); err != nil {
log.Errorf("failed to initialize forwarder: %v", err)
}
}
if err := iface.SetFilter(m); err != nil {
if err := cfg.IFace.SetFilter(m); err != nil {
return nil, fmt.Errorf("set filter: %w", err)
}
m.openHostFirewall(cfg.IFace.Name())
return m, nil
}
// closeAllowerOnError releases the allower (and its monitor) when Create fails
// after the allower was wired in.
func (m *Manager) closeAllowerOnError() {
if m.ifaceAllower == nil {
return
}
if err := m.ifaceAllower.Close(); err != nil {
log.Warnf("close interface allower after failed firewall setup: %v", err)
}
}
// enableMSSClamping enables MSS clamping and computes the per-family clamp values.
func (m *Manager) enableMSSClamping(mtu uint16) {
m.mssClampEnabled = true
if mtu > ipv4TCPHeaderMinSize {
m.mssClampValueIPv4 = mtu - ipv4TCPHeaderMinSize
}
if mtu > ipv6TCPHeaderMinSize {
m.mssClampValueIPv6 = mtu - ipv6TCPHeaderMinSize
}
}
// setupConntrack initializes the stateful trackers unless conntrack is disabled.
func (m *Manager) setupConntrack(disabled bool) {
if disabled {
log.Info("conntrack is disabled")
return
}
m.udpTracker = conntrack.NewUDPTracker(conntrack.DefaultUDPTimeout, m.logger, m.flowLogger)
m.icmpTracker = conntrack.NewICMPTracker(conntrack.DefaultICMPTimeout, m.logger, m.flowLogger)
m.tcpTracker = conntrack.NewTCPTracker(conntrack.DefaultTCPTimeout, m.logger, m.flowLogger)
}
// openHostFirewall opens the NetBird interface in the kernel firewall so it
// doesn't drop traffic the userspace firewall handles. Best-effort: failures
// here shouldn't prevent the firewall from coming up.
func (m *Manager) openHostFirewall(ifaceName string) {
if m.ifaceAllower != nil {
if err := m.ifaceAllower.Apply(); err != nil {
log.Errorf("failed to allow netbird interface traffic: %v", err)
}
}
// firewalld owns its own chains we can't insert into, so trust the interface
// there in addition to the allower. Netstack has no kernel interface.
if !m.netstack {
if err := firewalld.TrustInterface(ifaceName); err != nil {
log.Warnf("failed to trust interface in firewalld: %v", err)
}
}
}
// Close cleans up the firewall manager: removes rules, closes trackers, and
// closes the interface allower.
func (m *Manager) Close(*statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.resetState()
var merr *multierror.Error
if m.ifaceAllower != nil {
if err := m.ifaceAllower.Close(); err != nil {
merr = multierror.Append(merr, fmt.Errorf("close interface allower: %w", err))
}
}
if !m.netstack {
if err := firewalld.UntrustInterface(m.wgIface.Name()); err != nil {
merr = multierror.Append(merr, fmt.Errorf("untrust interface in firewalld: %w", err))
}
}
return nberrors.FormatErrorOrNil(merr)
}
// blockInvalidRouted installs drop rules for traffic to the wg overlay that
// arrives via the routing path. v4 and v6 are independent: a v6 install
// failure leaves v4 protection in place (and vice versa) so the returned
// slice always contains whatever was successfully installed, even on error.
// Callers must persist the slice so DisableRouting can clean partial state.
func (m *Manager) blockInvalidRouted(iface common.IFaceMapper) ([]firewall.Rule, error) {
func (m *Manager) blockInvalidRouted(iface Iface) ([]firewall.Rule, error) {
wgPrefix := iface.Address().Network
log.Debugf("blocking invalid routed traffic for %s", wgPrefix)
@@ -360,20 +472,14 @@ func (m *Manager) blockInvalidRouted(iface common.IFaceMapper) ([]firewall.Rule,
}
func (m *Manager) determineRouting() error {
var disableUspRouting, forceUserspaceRouter bool
var err error
var disableUspRouting bool
if val := os.Getenv(EnvDisableUserspaceRouting); val != "" {
var err error
disableUspRouting, err = strconv.ParseBool(val)
if err != nil {
log.Warnf("failed to parse %s: %v", EnvDisableUserspaceRouting, err)
}
}
if val := os.Getenv(EnvForceUserspaceRouter); val != "" {
forceUserspaceRouter, err = strconv.ParseBool(val)
if err != nil {
log.Warnf("failed to parse %s: %v", EnvForceUserspaceRouter, err)
}
}
switch {
case disableUspRouting:
@@ -388,26 +494,11 @@ func (m *Manager) determineRouting() error {
log.Info("server routes are disabled")
case forceUserspaceRouter:
m.routingEnabled.Store(true)
m.nativeRouter.Store(false)
log.Info("userspace routing is forced")
case !m.netstack && m.nativeFirewall != nil:
// if the OS supports routing natively, then we don't need to filter/route ourselves
// netstack mode won't support native routing as there is no interface
m.routingEnabled.Store(true)
m.nativeRouter.Store(true)
log.Info("native routing is enabled")
default:
m.routingEnabled.Store(true)
m.nativeRouter.Store(false)
log.Info("userspace routing enabled by default")
log.Info("userspace routing enabled")
}
if m.routingEnabled.Load() && !m.nativeRouter.Load() {
@@ -473,21 +564,14 @@ func (m *Manager) IsStateful() bool {
return m.stateful
}
func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.AddNatRule(pair)
}
func (m *Manager) AddNatRule(firewall.RouterPair) error {
// userspace routed packets are always SNATed to the inbound direction
// TODO: implement outbound SNAT
return nil
}
// RemoveNatRule removes a routing firewall rule
func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.RemoveNatRule(pair)
}
func (m *Manager) RemoveNatRule(firewall.RouterPair) error {
return nil
}
@@ -651,10 +735,7 @@ func (m *Manager) DeleteFilterRule(rule firewall.Rule) error {
return m.deletePeerRuleLocked(r)
}
// Either our *RouteRule or, under native delegation, a native
// route-rule object that implements firewall.Rule but isn't one of
// our concrete types. The route path forwards the latter to the
// native firewall.
// Anything else is a route rule (matched on the forward path).
return m.deleteRouteRule(rule)
}
@@ -666,10 +747,6 @@ func (m *Manager) addRouteRule(
sPort, dPort *firewall.Port,
action firewall.Action,
) (firewall.Rule, error) {
if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.AddFilterRule(id, sources, destination, proto, sPort, dPort, action)
}
ruleID := nbid.GenerateRuleID(sources, destination, proto, sPort, dPort, action)
if existingRule, ok := m.routeRulesMap[ruleID]; ok {
@@ -698,10 +775,6 @@ func (m *Manager) addRouteRule(
}
func (m *Manager) deleteRouteRule(rule firewall.Rule) error {
if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.DeleteFilterRule(rule)
}
ruleID := rule.ID()
trimmed, _, ok := removeRuleByID(m.routeRules, ruleID)
if !ok {
@@ -743,12 +816,11 @@ func removeRuleByID[S ~[]T, T firewall.Rule](rules S, ruleID firewall.RuleID) (S
return slices.Delete(rules, idx, idx+1), removed, true
}
// SetLegacyManagement doesn't need to be implemented for this manager
func (m *Manager) SetLegacyManagement(isLegacy bool) error {
if m.nativeFirewall == nil {
return nil
}
return m.nativeFirewall.SetLegacyManagement(isLegacy)
// SetLegacyManagement is a no-op for the userspace firewall: it only matters
// when an old management server can't send route firewall rules, which the
// userspace router doesn't rely on.
func (m *Manager) SetLegacyManagement(bool) error {
return nil
}
// Flush doesn't need to be implemented for this manager
@@ -793,21 +865,15 @@ func (m *Manager) resetState() {
}
}
// SetupEBPFProxyNoTrack creates notrack rules for eBPF proxy loopback traffic.
func (m *Manager) SetupEBPFProxyNoTrack(proxyPort, wgPort uint16) error {
if m.nativeFirewall == nil {
return nil
}
return m.nativeFirewall.SetupEBPFProxyNoTrack(proxyPort, wgPort)
// SetupEBPFProxyNoTrack is not supported by the userspace firewall: eBPF isn't
// used in userspace mode, so this should never be called.
func (m *Manager) SetupEBPFProxyNoTrack(uint16, uint16) error {
return errNotSupported
}
// UpdateSet updates the rule destinations associated with the given set
// by merging the existing prefixes with the new ones, then deduplicating.
func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
if m.nativeRouter.Load() && m.nativeFirewall != nil {
return m.nativeFirewall.UpdateSet(set, prefixes)
}
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -1591,10 +1657,13 @@ func (m *Manager) EnableRouting() error {
}
rules, err := m.blockInvalidRouted(m.wgIface)
// Persist whatever was installed even on partial failure, so DisableRouting
// can clean it up later.
m.blockRules = rules
if err != nil {
// Roll back so forwarding can't stay active without the full set of
// block rules.
if derr := m.disableRouting(); derr != nil {
log.Warnf("roll back routing after block rule failure: %v", derr)
}
return fmt.Errorf("block invalid routed: %w", err)
}
@@ -1605,6 +1674,10 @@ func (m *Manager) DisableRouting() error {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.disableRouting()
}
func (m *Manager) disableRouting() error {
fwder := m.forwarder.Load()
if fwder == nil {
return nil
+63 -33
View File
@@ -164,9 +164,12 @@ func BenchmarkCoreFiltering(b *testing.B) {
}
// Create manager and basic setup
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -204,9 +207,12 @@ func BenchmarkStateScaling(b *testing.B) {
for _, count := range connCounts {
b.Run(fmt.Sprintf("conns_%d", count), func(b *testing.B) {
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -247,9 +253,12 @@ func BenchmarkEstablishmentOverhead(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -405,9 +414,12 @@ func BenchmarkRoutedNetworkReturn(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -532,9 +544,12 @@ func BenchmarkLongLivedConnections(b *testing.B) {
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
}
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -615,9 +630,12 @@ func BenchmarkShortLivedConnections(b *testing.B) {
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
}
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -726,9 +744,12 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
}
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -806,9 +827,12 @@ func BenchmarkParallelShortLivedConnections(b *testing.B) {
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
}
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer b.Cleanup(func() {
require.NoError(b, manager.Close(nil))
})
@@ -1010,9 +1034,11 @@ func BenchmarkMSSClamping(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -1075,9 +1101,11 @@ func BenchmarkMSSClampingOverhead(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -1130,9 +1158,11 @@ func BenchmarkMSSClampingMemory(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -32,7 +32,7 @@ func TestPeerACLFiltering(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
require.NotNil(t, manager)
@@ -549,7 +549,7 @@ func TestPeerACLFilteringIPv6(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
@@ -788,7 +788,7 @@ func setupRoutedManager(tb testing.TB, network string) *Manager {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(tb, err)
require.NoError(tb, manager.EnableRouting())
require.NotNil(tb, manager)
@@ -1634,7 +1634,7 @@ func TestRouteACLSet(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, manager.Close(nil))
@@ -187,7 +187,7 @@ func TestBlockInvalidRoutedDualStack(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, manager.Close(nil))
@@ -235,7 +235,7 @@ func TestBlockInvalidRoutedIdempotent(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, manager.Close(nil))
@@ -298,7 +298,7 @@ func TestBlockRuleNotAccumulatedOnRepeatedEnableRouting(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, manager.Close(nil))
@@ -417,7 +417,7 @@ func setupTestManager(t *testing.T) *Manager {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
require.NoError(t, manager.EnableRouting())
+38 -27
View File
@@ -78,11 +78,12 @@ func TestManagerCreate(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
if m == nil {
t.Error("Manager is nil")
@@ -98,11 +99,12 @@ func TestManagerAddFilterRule(t *testing.T) {
},
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
@@ -131,11 +133,12 @@ func TestManagerDeleteRule(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
ip := netip.MustParseAddr("192.168.1.1")
proto := fw.ProtocolTCP
@@ -166,9 +169,11 @@ func TestManagerDeleteRule(t *testing.T) {
}
func TestSetUDPPacketHook(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
@@ -190,9 +195,11 @@ func TestSetUDPPacketHook(t *testing.T) {
}
func TestSetTCPPacketHook(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
@@ -220,7 +227,7 @@ func TestPeerRuleLifecycleDenyRules(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, m.Close(nil))
@@ -267,7 +274,7 @@ func TestPeerRuleAddAndDeleteDontLeak(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, m.Close(nil))
@@ -307,7 +314,7 @@ func TestMixedAllowDenyRulesSameIP(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, m.Close(nil))
@@ -360,7 +367,7 @@ func TestManagerReset(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
@@ -399,7 +406,7 @@ func TestNotMatchByIP(t *testing.T) {
},
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
@@ -462,7 +469,7 @@ func TestRemovePacketHook(t *testing.T) {
}
// creating manager instance
manager, err := Create(iface, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{IFace: iface, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
if err != nil {
t.Fatalf("Failed to create Manager: %s", err)
}
@@ -479,9 +486,11 @@ func TestRemovePacketHook(t *testing.T) {
}
func TestProcessOutgoingHooks(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
manager.udpTracker.Close()
@@ -566,7 +575,7 @@ func TestUSPFilterCreatePerformance(t *testing.T) {
ifaceMock := &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
manager, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
time.Sleep(time.Second)
@@ -591,9 +600,11 @@ func TestUSPFilterCreatePerformance(t *testing.T) {
}
func TestStatefulFirewall_UDPTracking(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
manager.udpTracker.Close() // Close the existing tracker
@@ -805,7 +816,7 @@ func TestUpdateSetMerge(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
manager, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, manager.Close(nil))
@@ -891,7 +902,7 @@ func TestUpdateSetDeduplication(t *testing.T) {
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
manager, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, manager.Close(nil))
@@ -1011,7 +1022,7 @@ func TestMSSClamping(t *testing.T) {
},
}
manager, err := Create(ifaceMock, nil, false, flowLogger, 1280)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: 1280})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -1203,7 +1214,7 @@ func TestShouldForward(t *testing.T) {
return wgaddr.Address{IP: wgIP, Network: netip.PrefixFrom(wgIP, 24)}
}
manager, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -1318,7 +1329,7 @@ func TestShouldForward(t *testing.T) {
// Re-create manager to pick up the new address with IPv6
require.NoError(t, manager.Close(nil))
manager, err = Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
manager, err = Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err)
v6Cases := []struct {
@@ -10,6 +10,7 @@ import (
"time"
log "github.com/sirupsen/logrus"
wgdevice "golang.zx2c4.com/wireguard/device"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
@@ -20,9 +21,9 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
nblog "github.com/netbirdio/netbird/client/firewall/uspfilter/log"
"github.com/netbirdio/netbird/client/iface/wgaddr"
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
)
@@ -33,6 +34,12 @@ const (
iosMaxInFlight = 256
)
// IFace provides the WireGuard device and overlay addresses the forwarder needs.
type IFace interface {
GetWGDevice() *wgdevice.Device
Address() wgaddr.Address
}
type Forwarder struct {
logger *nblog.Logger
flowLogger nftypes.FlowLogger
@@ -51,7 +58,7 @@ type Forwarder struct {
pingSemaphore chan struct{}
}
func New(iface common.IFaceMapper, logger *nblog.Logger, flowLogger nftypes.FlowLogger, netstack bool, mtu uint16) (*Forwarder, error) {
func New(iface IFace, logger *nblog.Logger, flowLogger nftypes.FlowLogger, netstack bool, mtu uint16) (*Forwarder, error) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocol,
@@ -9,7 +9,6 @@ import (
log "github.com/sirupsen/logrus"
nberrors "github.com/netbirdio/netbird/client/errors"
"github.com/netbirdio/netbird/client/internal/statemanager"
)
type action string
@@ -20,35 +19,20 @@ const (
firewallRuleName = "Netbird"
)
// Close cleans up the firewall manager by removing all rules and closing trackers
func (m *Manager) Close(*statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.resetState()
if !isWindowsFirewallReachable() {
return nil
}
var merr *multierror.Error
if isFirewallRuleActive(firewallRuleName) {
if err := manageFirewallRule(firewallRuleName, deleteRule); err != nil {
merr = multierror.Append(merr, fmt.Errorf("remove windows firewall rule: %w", err))
}
}
if isFirewallRuleActive(firewallRuleName + "-v6") {
if err := manageFirewallRule(firewallRuleName+"-v6", deleteRule); err != nil {
merr = multierror.Append(merr, fmt.Errorf("remove windows v6 firewall rule: %w", err))
}
}
return nberrors.FormatErrorOrNil(merr)
// WindowsInterfaceAllower opens the NetBird interface in the Windows firewall
// via netsh advfirewall rules. It implements InterfaceAllower for the userspace
// firewall on Windows.
type WindowsInterfaceAllower struct {
iface Iface
}
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
// NewWindowsInterfaceAllower builds the Windows netsh-based interface allower.
func NewWindowsInterfaceAllower(iface Iface) *WindowsInterfaceAllower {
return &WindowsInterfaceAllower{iface: iface}
}
// Apply adds inbound-allow netsh rules for the interface's addresses.
func (a *WindowsInterfaceAllower) Apply() error {
if !isWindowsFirewallReachable() {
return nil
}
@@ -60,13 +44,13 @@ func (m *Manager) AllowNetbird() error {
"enable=yes",
"action=allow",
"profile=any",
"localip="+m.wgIface.Address().IP.String(),
"localip="+a.iface.Address().IP.String(),
); err != nil {
return err
}
}
if v6 := m.wgIface.Address().IPv6; v6.IsValid() && !isFirewallRuleActive(firewallRuleName+"-v6") {
if v6 := a.iface.Address().IPv6; v6.IsValid() && !isFirewallRuleActive(firewallRuleName+"-v6") {
if err := manageFirewallRule(firewallRuleName+"-v6",
addRule,
"dir=in",
@@ -82,8 +66,27 @@ func (m *Manager) AllowNetbird() error {
return nil
}
func manageFirewallRule(ruleName string, action action, extraArgs ...string) error {
// Close removes the netsh rules added by Apply.
func (a *WindowsInterfaceAllower) Close() error {
if !isWindowsFirewallReachable() {
return nil
}
var merr *multierror.Error
if isFirewallRuleActive(firewallRuleName) {
if err := manageFirewallRule(firewallRuleName, deleteRule); err != nil {
merr = multierror.Append(merr, fmt.Errorf("remove windows firewall rule: %w", err))
}
}
if isFirewallRuleActive(firewallRuleName + "-v6") {
if err := manageFirewallRule(firewallRuleName+"-v6", deleteRule); err != nil {
merr = multierror.Append(merr, fmt.Errorf("remove windows v6 firewall rule: %w", err))
}
}
return nberrors.FormatErrorOrNil(merr)
}
func manageFirewallRule(ruleName string, action action, extraArgs ...string) error {
args := []string{"advfirewall", "firewall", string(action), "rule", "name=" + ruleName}
if action == addRule {
args = append(args, extraArgs...)
+1 -3
View File
@@ -7,8 +7,6 @@ import (
"sync/atomic"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
)
// localIPSnapshot is an immutable snapshot of local IP addresses, swapped
@@ -60,7 +58,7 @@ func processInterface(iface net.Interface, ips map[netip.Addr]struct{}, addresse
}
// UpdateLocalIPs rebuilds the local IP snapshot and swaps it in atomically.
func (m *localIPManager) UpdateLocalIPs(iface common.IFaceMapper) (err error) {
func (m *localIPManager) UpdateLocalIPs(iface Iface) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
+12 -23
View File
@@ -487,19 +487,13 @@ func incrementalUpdate(oldChecksum uint16, oldBytes, newBytes []byte) uint16 {
}
// AddDNATRule adds outbound DNAT rule for forwarding external traffic to NetBird network.
func (m *Manager) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {
if m.nativeFirewall == nil {
return nil, errNatNotSupported
}
return m.nativeFirewall.AddDNATRule(rule)
func (m *Manager) AddDNATRule(firewall.ForwardRule) (firewall.Rule, error) {
return nil, errNotSupported
}
// DeleteDNATRule deletes outbound DNAT rule.
func (m *Manager) DeleteDNATRule(rule firewall.Rule) error {
if m.nativeFirewall == nil {
return errNatNotSupported
}
return m.nativeFirewall.DeleteDNATRule(rule)
func (m *Manager) DeleteDNATRule(firewall.Rule) error {
return errNotSupported
}
// addPortRedirection adds a port redirection rule.
@@ -521,7 +515,6 @@ func (m *Manager) addPortRedirection(targetIP netip.Addr, protocol gopacket.Laye
}
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services.
// TODO: also delegate to nativeFirewall when available for kernel WG mode
func (m *Manager) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
var layerType gopacket.LayerType
switch protocol {
@@ -567,20 +560,16 @@ func (m *Manager) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Prot
return m.removePortRedirection(localAddr, layerType, originalPort, translatedPort)
}
// AddOutputDNAT delegates to the native firewall if available.
func (m *Manager) AddOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
if m.nativeFirewall == nil {
return fmt.Errorf("output DNAT not supported without native firewall")
}
return m.nativeFirewall.AddOutputDNAT(localAddr, protocol, originalPort, translatedPort)
// AddOutputDNAT is not supported by the userspace firewall: it backs kernel DNS
// redirection, but userspace DNS is served in-process on the gVisor netstack, so
// this should never be called.
func (m *Manager) AddOutputDNAT(netip.Addr, firewall.Protocol, uint16, uint16) error {
return errNotSupported
}
// RemoveOutputDNAT delegates to the native firewall if available.
func (m *Manager) RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
if m.nativeFirewall == nil {
return nil
}
return m.nativeFirewall.RemoveOutputDNAT(localAddr, protocol, originalPort, translatedPort)
// RemoveOutputDNAT is a no-op for the userspace firewall (see AddOutputDNAT).
func (m *Manager) RemoveOutputDNAT(netip.Addr, firewall.Protocol, uint16, uint16) error {
return nil
}
// translateInboundPortDNAT applies port-specific DNAT translation to inbound packets.
+25 -15
View File
@@ -64,9 +64,11 @@ func BenchmarkDNATTranslation(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -124,9 +126,11 @@ func BenchmarkDNATTranslation(b *testing.B) {
// BenchmarkDNATConcurrency tests DNAT performance under concurrent load
func BenchmarkDNATConcurrency(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -196,9 +200,11 @@ func BenchmarkDNATScaling(b *testing.B) {
for _, count := range mappingCounts {
b.Run(fmt.Sprintf("mappings_%d", count), func(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -308,9 +314,11 @@ func BenchmarkChecksumUpdate(b *testing.B) {
// BenchmarkDNATMemoryAllocations checks for memory allocations in DNAT operations
func BenchmarkDNATMemoryAllocations(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
@@ -481,9 +489,11 @@ func BenchmarkPortDNAT(b *testing.B) {
for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
defer func() {
require.NoError(b, manager.Close(nil))
+10 -6
View File
@@ -13,9 +13,11 @@ import (
// TestPortDNATBasic tests basic port DNAT functionality
func TestPortDNATBasic(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -49,9 +51,11 @@ func TestPortDNATBasic(t *testing.T) {
// TestPortDNATMultipleRules tests multiple port DNAT rules
func TestPortDNATMultipleRules(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
+20 -12
View File
@@ -15,9 +15,11 @@ import (
// TestDNATTranslationCorrectness verifies DNAT translation works correctly
func TestDNATTranslationCorrectness(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -104,9 +106,11 @@ func parsePacket(t testing.TB, packetData []byte) *decoder {
// TestDNATMappingManagement tests adding/removing DNAT mappings
func TestDNATMappingManagement(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -152,9 +156,11 @@ func TestDNATMappingManagement(t *testing.T) {
}
func TestInboundPortDNAT(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -202,9 +208,11 @@ func TestInboundPortDNAT(t *testing.T) {
}
func TestInboundPortDNATNegative(t *testing.T) {
manager, err := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
defer func() {
require.NoError(t, manager.Close(nil))
@@ -88,10 +88,13 @@ func runPeerACLBench(b *testing.B, m, k int, hit, v6 bool) {
IPv6Net: netip.PrefixFrom(dstIP, bits),
}
}
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
AddressFunc: func() wgaddr.Address { return mockAddr },
}, nil, false, flowLogger, iface.DefaultMTU)
manager, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
AddressFunc: func() wgaddr.Address { return mockAddr },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
b.Cleanup(func() { require.NoError(b, manager.Close(nil)) })
// Generate M policies × K source peers, all distinct.
@@ -169,9 +172,12 @@ func BenchmarkPeerACLIndexMemory(b *testing.B) {
b.Run(c.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
mgr, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}, nil, false, flowLogger, iface.DefaultMTU)
mgr, err := Create(Config{
IFace: &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
},
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(b, err)
populateIndexedRules(b, mgr, c.M, c.K, c.overlapFrac)
@@ -17,8 +17,9 @@ func newTestManager(t *testing.T) *Manager {
ifaceMock := &IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err, "create manager")
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
return m
}
@@ -29,7 +29,7 @@ func newV6TestManager(t *testing.T, localV6 string) *Manager {
}
},
}
m, err := Create(ifaceMock, nil, false, flowLogger, nbiface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
require.NoError(t, err, "create manager")
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
return m
+1 -1
View File
@@ -45,7 +45,7 @@ func TestTracePacket(t *testing.T) {
},
}
m, err := Create(ifaceMock, nil, false, flowLogger, iface.DefaultMTU)
m, err := Create(Config{IFace: ifaceMock, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
require.NoError(t, err)
if !statefulMode {