mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-24 06:19:08 +02:00
Remove partial userspace firewall mode and open foreign chains via a table-less allower
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build android || (!linux && !windows)
|
||||||
|
|
||||||
|
package firewall
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/client/firewall/uspfilter"
|
||||||
|
|
||||||
|
// interfaceAllower returns no allower: these platforms have no host firewall to
|
||||||
|
// open for the interface.
|
||||||
|
func interfaceAllower(IFaceMapper, uint16) uspfilter.InterfaceAllower {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package firewall
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/client/firewall/uspfilter"
|
||||||
|
|
||||||
|
// interfaceAllower returns the Windows netsh-based interface allower.
|
||||||
|
func interfaceAllower(iface IFaceMapper, _ uint16) uspfilter.InterfaceAllower {
|
||||||
|
return uspfilter.NewWindowsInterfaceAllower(iface)
|
||||||
|
}
|
||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
||||||
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
@@ -21,13 +19,11 @@ func NewFirewall(iface IFaceMapper, _ *statemanager.Manager, flowLogger nftypes.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use userspace packet filtering firewall
|
// use userspace packet filtering firewall
|
||||||
fm, err := uspfilter.Create(iface, nil, disableServerRoutes, flowLogger, mtu)
|
return uspfilter.Create(uspfilter.Config{
|
||||||
if err != nil {
|
IFace: iface,
|
||||||
return nil, err
|
DisableServerRoutes: disableServerRoutes,
|
||||||
}
|
FlowLogger: flowLogger,
|
||||||
err = fm.AllowNetbird()
|
MTU: mtu,
|
||||||
if err != nil {
|
InterfaceAllower: interfaceAllower(iface, mtu),
|
||||||
log.Warnf("failed to allow netbird interface traffic: %v", err)
|
})
|
||||||
}
|
|
||||||
return fm, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
nbnftables "github.com/netbirdio/netbird/client/firewall/nftables"
|
nbnftables "github.com/netbirdio/netbird/client/firewall/nftables"
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||||
)
|
)
|
||||||
@@ -40,19 +41,29 @@ var errNoFirewallManager = errors.New("no firewall manager found")
|
|||||||
type FWType int
|
type FWType int
|
||||||
|
|
||||||
func NewFirewall(iface IFaceMapper, stateManager *statemanager.Manager, flowLogger nftypes.FlowLogger, disableServerRoutes bool, mtu uint16) (firewall.Manager, error) {
|
func NewFirewall(iface IFaceMapper, stateManager *statemanager.Manager, flowLogger nftypes.FlowLogger, disableServerRoutes bool, mtu uint16) (firewall.Manager, error) {
|
||||||
// We run in userspace mode and force userspace firewall was requested.
|
// Userspace firewall without a native counterpart: routing is handled
|
||||||
if iface.IsUserspaceBind() && forceUserspaceFirewall() {
|
// entirely in userspace. The interface is opened in the kernel's foreign
|
||||||
nativeFw, err := createNativeFirewall(iface, stateManager, disableServerRoutes, mtu)
|
// filter chains via a table-less allower, except in netstack mode where no
|
||||||
if err != nil {
|
// kernel interface exists.
|
||||||
log.Warnf("failed to create native firewall: %v. Proceeding without it", err)
|
if netstack.IsEnabled() || (iface.IsUserspaceBind() && forceUserspaceFirewall()) {
|
||||||
|
if netstack.IsEnabled() {
|
||||||
|
log.Info("netstack mode, using userspace firewall")
|
||||||
|
} else {
|
||||||
|
log.Info("forcing userspace firewall")
|
||||||
|
}
|
||||||
|
cfg := uspfilter.Config{
|
||||||
|
IFace: iface,
|
||||||
|
DisableServerRoutes: disableServerRoutes,
|
||||||
|
FlowLogger: flowLogger,
|
||||||
|
MTU: mtu,
|
||||||
|
InterfaceAllower: interfaceAllower(iface, mtu),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("forcing userspace firewall")
|
return uspfilter.Create(cfg)
|
||||||
return createUserspaceFirewall(iface, nativeFw, disableServerRoutes, flowLogger, mtu)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use native firewall for either kernel or userspace, the interface appears identical to netfilter
|
// Use native firewall for either kernel or userspace, the interface appears identical to netfilter
|
||||||
fm, err := createNativeFirewall(iface, stateManager, disableServerRoutes, mtu)
|
fm, err := createNativeFirewall(iface, stateManager, mtu)
|
||||||
switch {
|
switch {
|
||||||
case err == nil && !iface.IsUserspaceBind():
|
case err == nil && !iface.IsUserspaceBind():
|
||||||
// Nothing to do, fall through
|
// Nothing to do, fall through
|
||||||
@@ -69,24 +80,41 @@ func NewFirewall(iface IFaceMapper, stateManager *statemanager.Manager, flowLogg
|
|||||||
case err != nil && iface.IsUserspaceBind():
|
case err != nil && iface.IsUserspaceBind():
|
||||||
// Fall back to the userspace packet filter if native is unavailable
|
// Fall back to the userspace packet filter if native is unavailable
|
||||||
logNativeFirewallUnavailable(err)
|
logNativeFirewallUnavailable(err)
|
||||||
return createUserspaceFirewall(iface, nil, disableServerRoutes, flowLogger, mtu)
|
return uspfilter.Create(uspfilter.Config{
|
||||||
|
IFace: iface,
|
||||||
|
DisableServerRoutes: disableServerRoutes,
|
||||||
|
FlowLogger: flowLogger,
|
||||||
|
MTU: mtu,
|
||||||
|
InterfaceAllower: interfaceAllower(iface, mtu),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return fm, nil
|
return fm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUserspaceFirewall builds the userspace packet filter, optionally
|
// interfaceAllower selects how the userspace firewall opens the interface in
|
||||||
// backed by a native firewall, and allows netbird interface traffic.
|
// foreign kernel chains: nftables when available (which also opens foreign nft
|
||||||
func createUserspaceFirewall(iface IFaceMapper, nativeFw firewall.Manager, disableServerRoutes bool, flowLogger nftypes.FlowLogger, mtu uint16) (firewall.Manager, error) {
|
// tables), else iptables (the legacy fallback, filter INPUT only), else nil.
|
||||||
fm, err := uspfilter.Create(iface, nativeFw, disableServerRoutes, flowLogger, mtu)
|
// firewalld trust is applied separately by the manager. Netstack has no kernel
|
||||||
if err != nil {
|
// interface to open.
|
||||||
return nil, err
|
func interfaceAllower(iface IFaceMapper, mtu uint16) uspfilter.InterfaceAllower {
|
||||||
|
if netstack.IsEnabled() {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fm.AllowNetbird(); err != nil {
|
nftAllower, err := nbnftables.NewInterfaceAllower(iface, mtu)
|
||||||
log.Errorf("failed to allow netbird interface traffic: %v", err)
|
if err == nil {
|
||||||
|
return nftAllower
|
||||||
}
|
}
|
||||||
return fm, nil
|
log.Infof("no nftables interface allower: %v", err)
|
||||||
|
|
||||||
|
iptAllower, err := nbiptables.NewInterfaceAllower(iface)
|
||||||
|
if err == nil {
|
||||||
|
return iptAllower
|
||||||
|
}
|
||||||
|
log.Infof("no iptables interface allower: %v", err)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// logNativeFirewallUnavailable logs the fallback to userspace at info level
|
// logNativeFirewallUnavailable logs the fallback to userspace at info level
|
||||||
@@ -99,7 +127,7 @@ func logNativeFirewallUnavailable(err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNativeFirewall(iface IFaceMapper, stateManager *statemanager.Manager, routes bool, mtu uint16) (firewall.Manager, error) {
|
func createNativeFirewall(iface IFaceMapper, stateManager *statemanager.Manager, mtu uint16) (firewall.Manager, error) {
|
||||||
fm, err := createFW(iface, mtu)
|
fm, err := createFW(iface, mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create firewall: %w", err)
|
return nil, fmt.Errorf("create firewall: %w", err)
|
||||||
@@ -193,15 +221,21 @@ func isIptablesClientAvailable(client *iptables.IPTables) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceUserspaceFirewall reports whether the userspace firewall is forced.
|
||||||
|
// NB_FORCE_USERSPACE_ROUTER is an alias: forcing userspace routing implies the
|
||||||
|
// userspace firewall, since the two are no longer separable.
|
||||||
func forceUserspaceFirewall() bool {
|
func forceUserspaceFirewall() bool {
|
||||||
val := os.Getenv(EnvForceUserspaceFirewall)
|
return envForceBool(EnvForceUserspaceFirewall) || envForceBool(uspfilter.EnvForceUserspaceRouter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envForceBool(name string) bool {
|
||||||
|
val := os.Getenv(name)
|
||||||
if val == "" {
|
if val == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
force, err := strconv.ParseBool(val)
|
force, err := strconv.ParseBool(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to parse %s: %v", EnvForceUserspaceFirewall, err)
|
log.Warnf("failed to parse %s: %v", name, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return force
|
return force
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package iptables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InterfaceAllower opens the NetBird interface on the iptables filter INPUT
|
||||||
|
// chain so the host firewall doesn't drop traffic the userspace firewall
|
||||||
|
// handles. It is the fallback used when nftables is unavailable (an
|
||||||
|
// iptables-legacy host).
|
||||||
|
//
|
||||||
|
// It opens INPUT only: the userspace router never forwards in the kernel.
|
||||||
|
// firewalld trust is handled by the uspfilter manager, not here.
|
||||||
|
type InterfaceAllower struct {
|
||||||
|
ifaceName string
|
||||||
|
ipt4 *iptables.IPTables
|
||||||
|
// ipt6 is nil when the interface has no IPv6 overlay address.
|
||||||
|
ipt6 *iptables.IPTables
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInterfaceAllower builds an iptables allower for the interface. It returns
|
||||||
|
// an error when iptables is unavailable, so the caller can fall back to
|
||||||
|
// firewalld trust.
|
||||||
|
func NewInterfaceAllower(wgIface iFaceMapper) (*InterfaceAllower, error) {
|
||||||
|
ipt4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("iptables not available: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := ipt4.ListChains(tableFilter); err != nil {
|
||||||
|
return nil, fmt.Errorf("iptables filter table not available: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &InterfaceAllower{ifaceName: wgIface.Name(), ipt4: ipt4}
|
||||||
|
|
||||||
|
// Missing v6 must not break the v4 path: open v4 only and continue.
|
||||||
|
if wgIface.Address().HasIPv6() {
|
||||||
|
ipt6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("ip6tables not available, opening interface on v4 only: %v", err)
|
||||||
|
} else if _, err := ipt6.ListChains(tableFilter); err != nil {
|
||||||
|
log.Warnf("ip6tables filter table not available, opening interface on v4 only: %v", err)
|
||||||
|
} else {
|
||||||
|
a.ipt6 = ipt6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply inserts the interface accept rule on the filter INPUT chain. It removes
|
||||||
|
// any stale rule first so an unclean exit (e.g. SIGKILL, where Close never ran)
|
||||||
|
// is recovered deterministically rather than accumulating duplicates.
|
||||||
|
func (a *InterfaceAllower) Apply() error {
|
||||||
|
var merr *multierror.Error
|
||||||
|
for _, ipt := range a.clients() {
|
||||||
|
if err := ipt.DeleteIfExists(tableFilter, chainInput, a.inputRule()...); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("clean stale interface accept rule: %w", err))
|
||||||
|
}
|
||||||
|
if err := ipt.Insert(tableFilter, chainInput, 1, a.inputRule()...); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("add interface accept rule: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes the interface accept rule.
|
||||||
|
func (a *InterfaceAllower) Close() error {
|
||||||
|
var merr *multierror.Error
|
||||||
|
for _, ipt := range a.clients() {
|
||||||
|
if err := ipt.DeleteIfExists(tableFilter, chainInput, a.inputRule()...); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("remove interface accept rule: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *InterfaceAllower) inputRule() []string {
|
||||||
|
return []string{"-i", a.ifaceName, "-j", "ACCEPT"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *InterfaceAllower) clients() []*iptables.IPTables {
|
||||||
|
clients := []*iptables.IPTables{a.ipt4}
|
||||||
|
if a.ipt6 != nil {
|
||||||
|
clients = append(clients, a.ipt6)
|
||||||
|
}
|
||||||
|
return clients
|
||||||
|
}
|
||||||
@@ -312,27 +312,6 @@ func (m *Manager) Close(stateManager *statemanager.Manager) error {
|
|||||||
return nberrors.FormatErrorOrNil(merr)
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowNetbird allows netbird interface traffic.
|
|
||||||
// This is called when USPFilter wraps the native firewall, adding blanket accept
|
|
||||||
// rules so that packet filtering is handled in userspace instead of by netfilter.
|
|
||||||
func (m *Manager) AllowNetbird() error {
|
|
||||||
var merr *multierror.Error
|
|
||||||
if _, err := m.AddFilterRule(nil, []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0)}, firewall.Network{}, firewall.ProtocolALL, nil, nil, firewall.ActionAccept); err != nil {
|
|
||||||
merr = multierror.Append(merr, fmt.Errorf("allow netbird v4 interface traffic: %w", err))
|
|
||||||
}
|
|
||||||
if m.hasIPv6() {
|
|
||||||
if _, err := m.AddFilterRule(nil, []netip.Prefix{netip.PrefixFrom(netip.IPv6Unspecified(), 0)}, firewall.Network{}, firewall.ProtocolALL, nil, nil, firewall.ActionAccept); err != nil {
|
|
||||||
merr = multierror.Append(merr, fmt.Errorf("allow netbird v6 interface traffic: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := firewalld.TrustInterface(m.wgIface.Name()); err != nil {
|
|
||||||
log.Warnf("failed to trust interface in firewalld: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nberrors.FormatErrorOrNil(merr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush doesn't need to be implemented for this manager
|
// Flush doesn't need to be implemented for this manager
|
||||||
func (m *Manager) Flush() error { return nil }
|
func (m *Manager) Flush() error { return nil }
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,14 @@ func (r *family) AddNatRule(pair firewall.RouterPair) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pair.Masquerade {
|
if pair.Masquerade {
|
||||||
return nil
|
if err := r.addNatRule(pair); err != nil {
|
||||||
}
|
return fmt.Errorf("add nat rule: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.addNatRule(pair); err != nil {
|
if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
|
||||||
return fmt.Errorf("add nat rule: %w", err)
|
return fmt.Errorf("add inverse nat rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
|
|
||||||
return fmt.Errorf("add inverse nat rule: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.updateState()
|
r.updateState()
|
||||||
@@ -244,7 +242,6 @@ func (r *family) addNatRule(pair firewall.RouterPair) error {
|
|||||||
|
|
||||||
r.rules[ruleID] = rule
|
r.rules[ruleID] = rule
|
||||||
|
|
||||||
r.updateState()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +261,5 @@ func (r *family) removeNatRule(pair firewall.RouterPair) error {
|
|||||||
log.Debugf("marking rule %s not found", ruleID)
|
log.Debugf("marking rule %s not found", ruleID)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.updateState()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,9 +115,6 @@ func (d Network) IsZero() bool {
|
|||||||
type Manager interface {
|
type Manager interface {
|
||||||
Init(stateManager *statemanager.Manager) error
|
Init(stateManager *statemanager.Manager) error
|
||||||
|
|
||||||
// AllowNetbird allows netbird interface traffic
|
|
||||||
AllowNetbird() error
|
|
||||||
|
|
||||||
// AddFilterRule adds a packet-filtering rule to the firewall.
|
// AddFilterRule adds a packet-filtering rule to the firewall.
|
||||||
//
|
//
|
||||||
// If destination is the zero Network, the rule applies to traffic
|
// If destination is the zero Network, the rule applies to traffic
|
||||||
|
|||||||
@@ -83,8 +83,9 @@ func (r *family) createContainers() error {
|
|||||||
log.Errorf("failed to add MSS clamping rules: %s", err)
|
log.Errorf("failed to add MSS clamping rules: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.acceptForwardRules(); err != nil {
|
// Kernel routing opens both INPUT and FORWARD.
|
||||||
log.Errorf("failed to add accept rules for the forward chain: %s", err)
|
if err := r.openInterface(true); err != nil {
|
||||||
|
log.Errorf("failed to open interface in foreign chains: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := firewalld.TrustInterface(r.wgIface.Name()); err != nil {
|
if err := firewalld.TrustInterface(r.wgIface.Name()); err != nil {
|
||||||
@@ -174,21 +175,26 @@ func (r *family) setupDataPlaneMark() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *family) acceptForwardRules() error {
|
// openInterface adds passthrough accept rules for the NetBird interface to the
|
||||||
|
// kernel's filter table and external chains so they don't drop our traffic.
|
||||||
|
// includeForward also opens the FORWARD chains (kernel routing); when false only
|
||||||
|
// INPUT is opened, which is all the userspace router needs since it never
|
||||||
|
// forwards in the kernel.
|
||||||
|
func (r *family) openInterface(includeForward bool) error {
|
||||||
var merr *multierror.Error
|
var merr *multierror.Error
|
||||||
|
|
||||||
if err := r.acceptFilterTableRules(); err != nil {
|
if err := r.acceptFilterTableRules(includeForward); err != nil {
|
||||||
merr = multierror.Append(merr, err)
|
merr = multierror.Append(merr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.acceptExternalChainsRules(); err != nil {
|
if err := r.acceptExternalChainsRules(includeForward); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("add accept rules to external chains: %w", err))
|
merr = multierror.Append(merr, fmt.Errorf("add accept rules to external chains: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nberrors.FormatErrorOrNil(merr)
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *family) acceptFilterTableRules() error {
|
func (r *family) acceptFilterTableRules(includeForward bool) error {
|
||||||
if r.filterTable == nil {
|
if r.filterTable == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -196,7 +202,7 @@ func (r *family) acceptFilterTableRules() error {
|
|||||||
fw := "iptables"
|
fw := "iptables"
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debugf("Used %s to add accept forward and input rules", fw)
|
log.Debugf("Used %s to add accept input/forward rules", fw)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Try iptables first and fallback to nftables if iptables is not available.
|
// Try iptables first and fallback to nftables if iptables is not available.
|
||||||
@@ -206,25 +212,27 @@ func (r *family) acceptFilterTableRules() error {
|
|||||||
log.Warnf("Will use nftables to manipulate the filter table because iptables is not available: %v", err)
|
log.Warnf("Will use nftables to manipulate the filter table because iptables is not available: %v", err)
|
||||||
|
|
||||||
fw = "nftables"
|
fw = "nftables"
|
||||||
return r.acceptFilterRulesNftables(r.filterTable)
|
return r.acceptFilterRulesNftables(r.filterTable, includeForward)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.acceptFilterRulesIptables(ipt); err != nil {
|
if err := r.acceptFilterRulesIptables(ipt, includeForward); err != nil {
|
||||||
log.Warnf("iptables failed (table may be incompatible), falling back to nftables: %v", err)
|
log.Warnf("iptables failed (table may be incompatible), falling back to nftables: %v", err)
|
||||||
fw = "nftables"
|
fw = "nftables"
|
||||||
return r.acceptFilterRulesNftables(r.filterTable)
|
return r.acceptFilterRulesNftables(r.filterTable, includeForward)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *family) acceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
func (r *family) acceptFilterRulesIptables(ipt *iptables.IPTables, includeForward bool) error {
|
||||||
var merr *multierror.Error
|
var merr *multierror.Error
|
||||||
|
|
||||||
for _, rule := range r.getAcceptForwardRules() {
|
if includeForward {
|
||||||
if err := ipt.Insert("filter", chainNameForward, 1, rule...); err != nil {
|
for _, rule := range r.getAcceptForwardRules() {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("add iptables forward rule: %v", err))
|
if err := ipt.Insert("filter", chainNameForward, 1, rule...); err != nil {
|
||||||
} else {
|
merr = multierror.Append(merr, fmt.Errorf("add iptables forward rule: %v", err))
|
||||||
log.Debugf("added iptables forward rule: %v", rule)
|
} else {
|
||||||
|
log.Debugf("added iptables forward rule: %v", rule)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,17 +260,19 @@ func (r *family) getAcceptInputRule() []string {
|
|||||||
|
|
||||||
// acceptFilterRulesNftables adds accept rules to the ip filter table using nftables.
|
// acceptFilterRulesNftables adds accept rules to the ip filter table using nftables.
|
||||||
// This is used when iptables is not available.
|
// This is used when iptables is not available.
|
||||||
func (r *family) acceptFilterRulesNftables(table *nftables.Table) error {
|
func (r *family) acceptFilterRulesNftables(table *nftables.Table, includeForward bool) error {
|
||||||
intf := ifname(r.wgIface.Name())
|
intf := ifname(r.wgIface.Name())
|
||||||
|
|
||||||
forwardChain := &nftables.Chain{
|
if includeForward {
|
||||||
Name: chainNameForward,
|
forwardChain := &nftables.Chain{
|
||||||
Table: table,
|
Name: chainNameForward,
|
||||||
Type: nftables.ChainTypeFilter,
|
Table: table,
|
||||||
Hooknum: nftables.ChainHookForward,
|
Type: nftables.ChainTypeFilter,
|
||||||
Priority: nftables.ChainPriorityFilter,
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Priority: nftables.ChainPriorityFilter,
|
||||||
|
}
|
||||||
|
r.insertForwardAcceptRules(forwardChain, intf)
|
||||||
}
|
}
|
||||||
r.insertForwardAcceptRules(forwardChain, intf)
|
|
||||||
|
|
||||||
inputChain := &nftables.Chain{
|
inputChain := &nftables.Chain{
|
||||||
Name: chainNameInput,
|
Name: chainNameInput,
|
||||||
@@ -278,7 +288,7 @@ func (r *family) acceptFilterRulesNftables(table *nftables.Table) error {
|
|||||||
|
|
||||||
// acceptExternalChainsRules adds accept rules to external chains (non-netbird, non-iptables tables).
|
// acceptExternalChainsRules adds accept rules to external chains (non-netbird, non-iptables tables).
|
||||||
// It dynamically finds chains at call time to handle chains that may have been created after startup.
|
// It dynamically finds chains at call time to handle chains that may have been created after startup.
|
||||||
func (r *family) acceptExternalChainsRules() error {
|
func (r *family) acceptExternalChainsRules(includeForward bool) error {
|
||||||
chains := r.findExternalChains()
|
chains := r.findExternalChains()
|
||||||
if len(chains) == 0 {
|
if len(chains) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -286,7 +296,7 @@ func (r *family) acceptExternalChainsRules() error {
|
|||||||
|
|
||||||
intf := ifname(r.wgIface.Name())
|
intf := ifname(r.wgIface.Name())
|
||||||
for _, chain := range chains {
|
for _, chain := range chains {
|
||||||
r.applyExternalChainAccept(chain, intf)
|
r.applyExternalChainAccept(chain, intf, includeForward)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.conn.Flush(); err != nil {
|
if err := r.conn.Flush(); err != nil {
|
||||||
@@ -295,7 +305,7 @@ func (r *family) acceptExternalChainsRules() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *family) applyExternalChainAccept(chain *nftables.Chain, intf []byte) {
|
func (r *family) applyExternalChainAccept(chain *nftables.Chain, intf []byte, includeForward bool) {
|
||||||
if chain.Hooknum == nil {
|
if chain.Hooknum == nil {
|
||||||
log.Debugf("skipping external chain %s/%s: hooknum is nil", chain.Table.Name, chain.Name)
|
log.Debugf("skipping external chain %s/%s: hooknum is nil", chain.Table.Name, chain.Name)
|
||||||
return
|
return
|
||||||
@@ -306,7 +316,9 @@ func (r *family) applyExternalChainAccept(chain *nftables.Chain, intf []byte) {
|
|||||||
|
|
||||||
switch *chain.Hooknum {
|
switch *chain.Hooknum {
|
||||||
case *nftables.ChainHookForward:
|
case *nftables.ChainHookForward:
|
||||||
r.insertForwardAcceptRules(chain, intf)
|
if includeForward {
|
||||||
|
r.insertForwardAcceptRules(chain, intf)
|
||||||
|
}
|
||||||
case *nftables.ChainHookInput:
|
case *nftables.ChainHookInput:
|
||||||
r.insertInputAcceptRule(chain, intf)
|
r.insertInputAcceptRule(chain, intf)
|
||||||
}
|
}
|
||||||
@@ -576,26 +588,6 @@ func (r *family) removeAcceptFilterRulesIptables(ipt *iptables.IPTables) error {
|
|||||||
return nberrors.FormatErrorOrNil(merr)
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *family) createDefaultAllowRules() error {
|
|
||||||
expIn := []expr.Any{
|
|
||||||
&expr.Verdict{
|
|
||||||
Kind: expr.VerdictAccept,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = r.conn.InsertRule(&nftables.Rule{
|
|
||||||
Table: r.workTable,
|
|
||||||
Chain: r.chainInputRules,
|
|
||||||
Position: 0,
|
|
||||||
Exprs: expIn,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := r.conn.Flush(); err != nil {
|
|
||||||
return fmt.Errorf(flushError, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush rule/chain/set operations from the buffer
|
// Flush rule/chain/set operations from the buffer
|
||||||
//
|
//
|
||||||
// Method also get all rules after flush and refreshes handle values in the rulesets
|
// Method also get all rules after flush and refreshes handle values in the rulesets
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
//go:build integration && !android
|
||||||
|
|
||||||
|
package nftables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/nftables"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInterfaceAllowerInputOnly verifies the userspace-mode allower opens the
|
||||||
|
// interface on the INPUT hook of foreign chains only (not FORWARD, since the
|
||||||
|
// userspace router never forwards in the kernel), creates no netbird work
|
||||||
|
// table, and removes its rules on Close.
|
||||||
|
func TestInterfaceAllowerInputOnly(t *testing.T) {
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
t.Skip("root required")
|
||||||
|
}
|
||||||
|
|
||||||
|
require.False(t, ipTableExists(t, getTableName()), "precondition: no stale netbird table")
|
||||||
|
|
||||||
|
conn := &nftables.Conn{}
|
||||||
|
extTable := conn.AddTable(&nftables.Table{Name: "nbtest_extchains", Family: nftables.TableFamilyINet})
|
||||||
|
inputChain := conn.AddChain(&nftables.Chain{
|
||||||
|
Name: "ext_input", Table: extTable,
|
||||||
|
Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter,
|
||||||
|
})
|
||||||
|
forwardChain := conn.AddChain(&nftables.Chain{
|
||||||
|
Name: "ext_forward", Table: extTable,
|
||||||
|
Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter,
|
||||||
|
})
|
||||||
|
require.NoError(t, conn.Flush(), "create external table and chains")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
c := &nftables.Conn{}
|
||||||
|
c.DelTable(extTable)
|
||||||
|
_ = c.Flush()
|
||||||
|
})
|
||||||
|
|
||||||
|
allower, err := NewInterfaceAllower(ifaceMock, iface.DefaultMTU)
|
||||||
|
require.NoError(t, err, "create allower")
|
||||||
|
require.NoError(t, allower.Apply(), "apply")
|
||||||
|
|
||||||
|
require.True(t, chainHasUserData(t, extTable, inputChain, userDataAcceptInputRule),
|
||||||
|
"external INPUT chain should get the accept rule")
|
||||||
|
require.Len(t, listRules(t, extTable, forwardChain), 0,
|
||||||
|
"external FORWARD chain must not be opened in userspace mode")
|
||||||
|
require.False(t, ipTableExists(t, getTableName()),
|
||||||
|
"allower must not create a netbird work table")
|
||||||
|
|
||||||
|
require.NoError(t, allower.Close(), "close")
|
||||||
|
require.False(t, chainHasUserData(t, extTable, inputChain, userDataAcceptInputRule),
|
||||||
|
"accept rule should be removed on close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRules(t *testing.T, table *nftables.Table, chain *nftables.Chain) []*nftables.Rule {
|
||||||
|
t.Helper()
|
||||||
|
c := &nftables.Conn{}
|
||||||
|
rules, err := c.GetRules(table, chain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
func chainHasUserData(t *testing.T, table *nftables.Table, chain *nftables.Chain, ud string) bool {
|
||||||
|
for _, r := range listRules(t, table, chain) {
|
||||||
|
if bytes.Equal(r.UserData, []byte(ud)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipTableExists(t *testing.T, name string) bool {
|
||||||
|
t.Helper()
|
||||||
|
c := &nftables.Conn{}
|
||||||
|
for _, fam := range []nftables.TableFamily{nftables.TableFamilyIPv4, nftables.TableFamilyIPv6} {
|
||||||
|
tbls, err := c.ListTablesOfFamily(fam)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, tb := range tbls {
|
||||||
|
if tb.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package nftables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/nftables"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
|
||||||
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InterfaceAllower opens the NetBird interface in the kernel's filter table and
|
||||||
|
// external chains and keeps them reconciled via a netlink monitor, so the host
|
||||||
|
// firewall doesn't drop traffic the NetBird firewall handles. It is used by the
|
||||||
|
// userspace firewall, where routing happens in the forwarder, so only INPUT is
|
||||||
|
// opened (the userspace router never forwards in the kernel).
|
||||||
|
//
|
||||||
|
// It owns its own families/connection and never creates a netbird work table.
|
||||||
|
// firewalld trust is handled by the caller, not here. Its operations are serial
|
||||||
|
// (Apply before the monitor starts; reconciles run on the single monitor
|
||||||
|
// goroutine; Close stops the monitor before removing), so it needs no locking.
|
||||||
|
//
|
||||||
|
// TODO: this opens nftables and the iptables-nft filter table (detected via
|
||||||
|
// nft), but not a legacy-iptables ruleset running in parallel with nftables.
|
||||||
|
// Such a host would keep its legacy filter chains closed for the interface.
|
||||||
|
type InterfaceAllower struct {
|
||||||
|
family4 *family
|
||||||
|
family6 *family
|
||||||
|
extMonitor *externalChainMonitor
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInterfaceAllower builds an allower for the given interface. It returns an
|
||||||
|
// error when nftables is unavailable (e.g. an iptables-legacy host), so the
|
||||||
|
// caller can fall back to firewalld trust.
|
||||||
|
func NewInterfaceAllower(wgIface iFaceMapper, mtu uint16) (*InterfaceAllower, error) {
|
||||||
|
tableName := getTableName()
|
||||||
|
|
||||||
|
family4, err := newFamily(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}, wgIface, mtu)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create family: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe nftables availability before committing to this backend.
|
||||||
|
if _, err := family4.conn.ListChainsOfTableFamily(nftables.TableFamilyINet); err != nil {
|
||||||
|
return nil, fmt.Errorf("nftables not available: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &InterfaceAllower{family4: family4}
|
||||||
|
|
||||||
|
if wgIface.Address().HasIPv6() {
|
||||||
|
family6, err := newFamily(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}, wgIface, mtu)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create v6 family: %w", err)
|
||||||
|
}
|
||||||
|
a.family6 = family6
|
||||||
|
}
|
||||||
|
|
||||||
|
a.extMonitor = newExternalChainMonitor(a)
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply opens the interface (INPUT only) in the foreign filter chains and starts
|
||||||
|
// reconciling them on nftables changes.
|
||||||
|
func (a *InterfaceAllower) Apply() error {
|
||||||
|
var merr *multierror.Error
|
||||||
|
for _, f := range a.families() {
|
||||||
|
// Remove any stale accepts first so a prior unclean exit (e.g. SIGKILL,
|
||||||
|
// where Close never ran) is recovered deterministically rather than
|
||||||
|
// accumulating duplicate rules on the iptables filter table.
|
||||||
|
if err := f.removeAcceptFilterRules(); err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("clean stale accept rules: %w", err))
|
||||||
|
}
|
||||||
|
if err := f.openInterface(false); err != nil {
|
||||||
|
merr = multierror.Append(merr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.extMonitor.start()
|
||||||
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// families returns the configured address families (v4, and v6 when present).
|
||||||
|
func (a *InterfaceAllower) families() []*family {
|
||||||
|
families := []*family{a.family4}
|
||||||
|
if a.family6 != nil {
|
||||||
|
families = append(families, a.family6)
|
||||||
|
}
|
||||||
|
return families
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconcileExternalChains re-applies the INPUT accepts to external chains. It
|
||||||
|
// implements externalChainReconciler for the monitor.
|
||||||
|
func (a *InterfaceAllower) reconcileExternalChains() error {
|
||||||
|
var merr *multierror.Error
|
||||||
|
for _, f := range a.families() {
|
||||||
|
if err := f.acceptExternalChainsRules(false); err != nil {
|
||||||
|
merr = multierror.Append(merr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stops the monitor and removes the accept rules.
|
||||||
|
func (a *InterfaceAllower) Close() error {
|
||||||
|
a.extMonitor.stop()
|
||||||
|
|
||||||
|
var merr *multierror.Error
|
||||||
|
for _, f := range a.families() {
|
||||||
|
if err := f.removeAcceptFilterRules(); err != nil {
|
||||||
|
merr = multierror.Append(merr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/firewall/firewalld"
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||||
@@ -140,19 +139,20 @@ func (m *Manager) Init(stateManager *statemanager.Manager) error {
|
|||||||
|
|
||||||
// reconcileExternalChains re-applies passthrough accept rules to external
|
// reconcileExternalChains re-applies passthrough accept rules to external
|
||||||
// filter chains for both IPv4 and IPv6 routers. Called by the monitor when
|
// filter chains for both IPv4 and IPv6 routers. Called by the monitor when
|
||||||
// tables or chains appear (e.g. after firewalld reloads).
|
// tables or chains appear (e.g. after firewalld reloads). Kernel routing opens
|
||||||
|
// both INPUT and FORWARD.
|
||||||
func (m *Manager) reconcileExternalChains() error {
|
func (m *Manager) reconcileExternalChains() error {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
var merr *multierror.Error
|
var merr *multierror.Error
|
||||||
if m.family4 != nil {
|
if m.family4 != nil {
|
||||||
if err := m.family4.acceptExternalChainsRules(); err != nil {
|
if err := m.family4.acceptExternalChainsRules(true); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("v4: %w", err))
|
merr = multierror.Append(merr, fmt.Errorf("v4: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m.hasIPv6() {
|
if m.hasIPv6() {
|
||||||
if err := m.family6.acceptExternalChainsRules(); err != nil {
|
if err := m.family6.acceptExternalChainsRules(true); err != nil {
|
||||||
merr = multierror.Append(merr, fmt.Errorf("v6: %w", err))
|
merr = multierror.Append(merr, fmt.Errorf("v6: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,39 +370,6 @@ func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
|
|||||||
return nberrors.FormatErrorOrNil(merr)
|
return nberrors.FormatErrorOrNil(merr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowNetbird allows netbird interface traffic.
|
|
||||||
// This is called when USPFilter wraps the native firewall, adding blanket accept
|
|
||||||
// rules so that packet filtering is handled in userspace instead of by netfilter.
|
|
||||||
//
|
|
||||||
// TODO: In USP mode this only adds ACCEPT to the netbird table's own chains,
|
|
||||||
// which doesn't override DROP rules in external tables (e.g. firewalld).
|
|
||||||
// Should add passthrough rules to external chains (like the native mode router's
|
|
||||||
// addExternalChainsRules does) for both the netbird table family and inet tables.
|
|
||||||
// The netbird table itself is fine (routing chains already exist there), but
|
|
||||||
// non-netbird tables with INPUT/FORWARD hooks can still DROP our WG traffic.
|
|
||||||
func (m *Manager) AllowNetbird() error {
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
|
|
||||||
if err := m.family4.createDefaultAllowRules(); err != nil {
|
|
||||||
return fmt.Errorf("create default allow rules: %w", err)
|
|
||||||
}
|
|
||||||
if m.hasIPv6() {
|
|
||||||
if err := m.family6.createDefaultAllowRules(); err != nil {
|
|
||||||
return fmt.Errorf("create v6 default allow rules: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := m.rConn.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("flush allow input netbird rules: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := firewalld.TrustInterface(m.wgIface.Name()); err != nil {
|
|
||||||
log.Warnf("failed to trust interface in firewalld: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLegacyManagement sets the route manager to use legacy management
|
// SetLegacyManagement sets the route manager to use legacy management
|
||||||
func (m *Manager) SetLegacyManagement(isLegacy bool) error {
|
func (m *Manager) SetLegacyManagement(isLegacy bool) error {
|
||||||
if err := firewall.SetLegacyManagement(m.family4, isLegacy); err != nil {
|
if err := firewall.SetLegacyManagement(m.family4, isLegacy); err != nil {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -19,14 +19,18 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
|
"github.com/netbirdio/netbird/client/firewall/firewalld"
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/forwarder"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter/forwarder"
|
||||||
nblog "github.com/netbirdio/netbird/client/firewall/uspfilter/log"
|
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/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
nbid "github.com/netbirdio/netbird/client/internal/acl/id"
|
nbid "github.com/netbirdio/netbird/client/internal/acl/id"
|
||||||
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||||
@@ -57,7 +61,10 @@ const (
|
|||||||
// EnvDisableMSSClamping disables TCP MSS clamping for forwarded traffic.
|
// EnvDisableMSSClamping disables TCP MSS clamping for forwarded traffic.
|
||||||
EnvDisableMSSClamping = "NB_DISABLE_MSS_CLAMPING"
|
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"
|
EnvForceUserspaceRouter = "NB_FORCE_USERSPACE_ROUTER"
|
||||||
|
|
||||||
// EnvEnableLocalForwarding enables forwarding of local traffic to the native stack for internal (non-NetBird) interfaces.
|
// 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"
|
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.
|
// peerRules is the canonical list-based storage for peer ACL rules.
|
||||||
// Match order is significant: drop rules come before accept rules so
|
// Match order is significant: drop rules come before accept rules so
|
||||||
@@ -105,17 +115,48 @@ type peerRuleSpec struct {
|
|||||||
action firewall.Action
|
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
|
// Manager userspace firewall manager
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
decoders sync.Pool
|
decoders sync.Pool
|
||||||
wgIface common.IFaceMapper
|
wgIface Iface
|
||||||
// nativeFirewall is the kernel firewall (nftables/iptables) used for
|
ifaceAllower InterfaceAllower
|
||||||
// the split case where peer ACLs run in userspace here but routing
|
mutex sync.RWMutex
|
||||||
// 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
|
|
||||||
|
|
||||||
incomingDenyRules peerRules
|
incomingDenyRules peerRules
|
||||||
incomingAcceptRules peerRules
|
incomingAcceptRules peerRules
|
||||||
@@ -236,7 +277,7 @@ func parseCreateEnv() (bool, bool, bool) {
|
|||||||
return disableConntrack, enableLocalForwarding, disableMSSClamping
|
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()
|
disableConntrack, enableLocalForwarding, disableMSSClamping := parseCreateEnv()
|
||||||
|
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
@@ -259,13 +300,13 @@ func Create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
|
|||||||
return d
|
return d
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wgIface: iface,
|
wgIface: cfg.IFace,
|
||||||
nativeFirewall: nativeFirewall,
|
ifaceAllower: cfg.InterfaceAllower,
|
||||||
localipmanager: newLocalIPManager(),
|
localipmanager: newLocalIPManager(),
|
||||||
disableServerRoutes: disableServerRoutes,
|
disableServerRoutes: cfg.DisableServerRoutes,
|
||||||
stateful: !disableConntrack,
|
stateful: !disableConntrack,
|
||||||
logger: nblog.NewFromLogrus(log.StandardLogger()),
|
logger: nblog.NewFromLogrus(log.StandardLogger()),
|
||||||
flowLogger: flowLogger,
|
flowLogger: cfg.FlowLogger,
|
||||||
netstack: netstack.IsEnabled(),
|
netstack: netstack.IsEnabled(),
|
||||||
localForwarding: enableLocalForwarding,
|
localForwarding: enableLocalForwarding,
|
||||||
peerRulesMap: make(map[nbid.RuleID]*PeerRule),
|
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),
|
dnatMappings: make(map[netip.Addr]netip.Addr),
|
||||||
portDNATRules: []portDNATRule{},
|
portDNATRules: []portDNATRule{},
|
||||||
netstackServices: make(map[serviceKey]struct{}),
|
netstackServices: make(map[serviceKey]struct{}),
|
||||||
mtu: mtu,
|
mtu: cfg.MTU,
|
||||||
}
|
}
|
||||||
m.routingEnabled.Store(false)
|
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 {
|
if !disableMSSClamping {
|
||||||
m.mssClampEnabled = true
|
m.enableMSSClamping(cfg.MTU)
|
||||||
if mtu > ipv4TCPHeaderMinSize {
|
|
||||||
m.mssClampValueIPv4 = mtu - ipv4TCPHeaderMinSize
|
|
||||||
}
|
|
||||||
if mtu > ipv6TCPHeaderMinSize {
|
|
||||||
m.mssClampValueIPv6 = mtu - ipv6TCPHeaderMinSize
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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)
|
return nil, fmt.Errorf("update local IPs: %w", err)
|
||||||
}
|
}
|
||||||
if disableConntrack {
|
m.setupConntrack(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)
|
|
||||||
}
|
|
||||||
if m.netstack && m.localForwarding {
|
if m.netstack && m.localForwarding {
|
||||||
if err := m.initForwarder(); err != nil {
|
if err := m.initForwarder(); err != nil {
|
||||||
log.Errorf("failed to initialize forwarder: %v", err)
|
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)
|
return nil, fmt.Errorf("set filter: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.openHostFirewall(cfg.IFace.Name())
|
||||||
|
|
||||||
return m, nil
|
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
|
// blockInvalidRouted installs drop rules for traffic to the wg overlay that
|
||||||
// arrives via the routing path. v4 and v6 are independent: a v6 install
|
// 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
|
// failure leaves v4 protection in place (and vice versa) so the returned
|
||||||
// slice always contains whatever was successfully installed, even on error.
|
// slice always contains whatever was successfully installed, even on error.
|
||||||
// Callers must persist the slice so DisableRouting can clean partial state.
|
// 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
|
wgPrefix := iface.Address().Network
|
||||||
log.Debugf("blocking invalid routed traffic for %s", wgPrefix)
|
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 {
|
func (m *Manager) determineRouting() error {
|
||||||
var disableUspRouting, forceUserspaceRouter bool
|
var disableUspRouting bool
|
||||||
var err error
|
|
||||||
if val := os.Getenv(EnvDisableUserspaceRouting); val != "" {
|
if val := os.Getenv(EnvDisableUserspaceRouting); val != "" {
|
||||||
|
var err error
|
||||||
disableUspRouting, err = strconv.ParseBool(val)
|
disableUspRouting, err = strconv.ParseBool(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to parse %s: %v", EnvDisableUserspaceRouting, err)
|
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 {
|
switch {
|
||||||
case disableUspRouting:
|
case disableUspRouting:
|
||||||
@@ -388,26 +494,11 @@ func (m *Manager) determineRouting() error {
|
|||||||
|
|
||||||
log.Info("server routes are disabled")
|
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:
|
default:
|
||||||
m.routingEnabled.Store(true)
|
m.routingEnabled.Store(true)
|
||||||
m.nativeRouter.Store(false)
|
m.nativeRouter.Store(false)
|
||||||
|
|
||||||
log.Info("userspace routing enabled by default")
|
log.Info("userspace routing enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.routingEnabled.Load() && !m.nativeRouter.Load() {
|
if m.routingEnabled.Load() && !m.nativeRouter.Load() {
|
||||||
@@ -473,21 +564,14 @@ func (m *Manager) IsStateful() bool {
|
|||||||
return m.stateful
|
return m.stateful
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
|
func (m *Manager) AddNatRule(firewall.RouterPair) error {
|
||||||
if m.nativeRouter.Load() && m.nativeFirewall != nil {
|
|
||||||
return m.nativeFirewall.AddNatRule(pair)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userspace routed packets are always SNATed to the inbound direction
|
// userspace routed packets are always SNATed to the inbound direction
|
||||||
// TODO: implement outbound SNAT
|
// TODO: implement outbound SNAT
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveNatRule removes a routing firewall rule
|
// RemoveNatRule removes a routing firewall rule
|
||||||
func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error {
|
func (m *Manager) RemoveNatRule(firewall.RouterPair) error {
|
||||||
if m.nativeRouter.Load() && m.nativeFirewall != nil {
|
|
||||||
return m.nativeFirewall.RemoveNatRule(pair)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,10 +735,7 @@ func (m *Manager) DeleteFilterRule(rule firewall.Rule) error {
|
|||||||
return m.deletePeerRuleLocked(r)
|
return m.deletePeerRuleLocked(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either our *RouteRule or, under native delegation, a native
|
// Anything else is a route rule (matched on the forward path).
|
||||||
// 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.
|
|
||||||
return m.deleteRouteRule(rule)
|
return m.deleteRouteRule(rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,10 +747,6 @@ func (m *Manager) addRouteRule(
|
|||||||
sPort, dPort *firewall.Port,
|
sPort, dPort *firewall.Port,
|
||||||
action firewall.Action,
|
action firewall.Action,
|
||||||
) (firewall.Rule, error) {
|
) (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)
|
ruleID := nbid.GenerateRuleID(sources, destination, proto, sPort, dPort, action)
|
||||||
|
|
||||||
if existingRule, ok := m.routeRulesMap[ruleID]; ok {
|
if existingRule, ok := m.routeRulesMap[ruleID]; ok {
|
||||||
@@ -698,10 +775,6 @@ func (m *Manager) addRouteRule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) deleteRouteRule(rule firewall.Rule) error {
|
func (m *Manager) deleteRouteRule(rule firewall.Rule) error {
|
||||||
if m.nativeRouter.Load() && m.nativeFirewall != nil {
|
|
||||||
return m.nativeFirewall.DeleteFilterRule(rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleID := rule.ID()
|
ruleID := rule.ID()
|
||||||
trimmed, _, ok := removeRuleByID(m.routeRules, ruleID)
|
trimmed, _, ok := removeRuleByID(m.routeRules, ruleID)
|
||||||
if !ok {
|
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
|
return slices.Delete(rules, idx, idx+1), removed, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLegacyManagement doesn't need to be implemented for this manager
|
// SetLegacyManagement is a no-op for the userspace firewall: it only matters
|
||||||
func (m *Manager) SetLegacyManagement(isLegacy bool) error {
|
// when an old management server can't send route firewall rules, which the
|
||||||
if m.nativeFirewall == nil {
|
// userspace router doesn't rely on.
|
||||||
return nil
|
func (m *Manager) SetLegacyManagement(bool) error {
|
||||||
}
|
return nil
|
||||||
return m.nativeFirewall.SetLegacyManagement(isLegacy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush doesn't need to be implemented for this manager
|
// 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.
|
// SetupEBPFProxyNoTrack is not supported by the userspace firewall: eBPF isn't
|
||||||
func (m *Manager) SetupEBPFProxyNoTrack(proxyPort, wgPort uint16) error {
|
// used in userspace mode, so this should never be called.
|
||||||
if m.nativeFirewall == nil {
|
func (m *Manager) SetupEBPFProxyNoTrack(uint16, uint16) error {
|
||||||
return nil
|
return errNotSupported
|
||||||
}
|
|
||||||
return m.nativeFirewall.SetupEBPFProxyNoTrack(proxyPort, wgPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSet updates the rule destinations associated with the given set
|
// UpdateSet updates the rule destinations associated with the given set
|
||||||
// by merging the existing prefixes with the new ones, then deduplicating.
|
// by merging the existing prefixes with the new ones, then deduplicating.
|
||||||
func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
|
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()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
@@ -1591,10 +1657,13 @@ func (m *Manager) EnableRouting() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rules, err := m.blockInvalidRouted(m.wgIface)
|
rules, err := m.blockInvalidRouted(m.wgIface)
|
||||||
// Persist whatever was installed even on partial failure, so DisableRouting
|
|
||||||
// can clean it up later.
|
|
||||||
m.blockRules = rules
|
m.blockRules = rules
|
||||||
if err != nil {
|
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)
|
return fmt.Errorf("block invalid routed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1605,6 +1674,10 @@ func (m *Manager) DisableRouting() error {
|
|||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
return m.disableRouting()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) disableRouting() error {
|
||||||
fwder := m.forwarder.Load()
|
fwder := m.forwarder.Load()
|
||||||
if fwder == nil {
|
if fwder == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -164,9 +164,12 @@ func BenchmarkCoreFiltering(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create manager and basic setup
|
// Create manager and basic setup
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
defer b.Cleanup(func() {
|
defer b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -204,9 +207,12 @@ func BenchmarkStateScaling(b *testing.B) {
|
|||||||
|
|
||||||
for _, count := range connCounts {
|
for _, count := range connCounts {
|
||||||
b.Run(fmt.Sprintf("conns_%d", count), func(b *testing.B) {
|
b.Run(fmt.Sprintf("conns_%d", count), func(b *testing.B) {
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -247,9 +253,12 @@ func BenchmarkEstablishmentOverhead(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -405,9 +414,12 @@ func BenchmarkRoutedNetworkReturn(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -532,9 +544,12 @@ func BenchmarkLongLivedConnections(b *testing.B) {
|
|||||||
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
||||||
}
|
}
|
||||||
|
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
defer b.Cleanup(func() {
|
defer b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -615,9 +630,12 @@ func BenchmarkShortLivedConnections(b *testing.B) {
|
|||||||
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
||||||
}
|
}
|
||||||
|
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
defer b.Cleanup(func() {
|
defer b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -726,9 +744,12 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
|
|||||||
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
||||||
}
|
}
|
||||||
|
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
defer b.Cleanup(func() {
|
defer b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -806,9 +827,12 @@ func BenchmarkParallelShortLivedConnections(b *testing.B) {
|
|||||||
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
require.NoError(b, os.Unsetenv("NB_DISABLE_CONNTRACK"))
|
||||||
}
|
}
|
||||||
|
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
defer b.Cleanup(func() {
|
defer b.Cleanup(func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
})
|
})
|
||||||
@@ -1010,9 +1034,11 @@ func BenchmarkMSSClamping(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
@@ -1075,9 +1101,11 @@ func BenchmarkMSSClampingOverhead(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
@@ -1130,9 +1158,11 @@ func BenchmarkMSSClampingMemory(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
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.NoError(t, err)
|
||||||
require.NotNil(t, manager)
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
|
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, err)
|
||||||
require.NoError(tb, manager.EnableRouting())
|
require.NoError(tb, manager.EnableRouting())
|
||||||
require.NotNil(tb, manager)
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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, err)
|
||||||
require.NoError(t, manager.EnableRouting())
|
require.NoError(t, manager.EnableRouting())
|
||||||
|
|
||||||
|
|||||||
@@ -78,11 +78,12 @@ func TestManagerCreate(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to create Manager: %v", err)
|
t.Errorf("failed to create Manager: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
|
||||||
|
|
||||||
if m == nil {
|
if m == nil {
|
||||||
t.Error("Manager is 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 {
|
if err != nil {
|
||||||
t.Errorf("failed to create Manager: %v", err)
|
t.Errorf("failed to create Manager: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
|
||||||
|
|
||||||
ip := net.ParseIP("192.168.1.1")
|
ip := net.ParseIP("192.168.1.1")
|
||||||
proto := fw.ProtocolTCP
|
proto := fw.ProtocolTCP
|
||||||
@@ -131,11 +133,12 @@ func TestManagerDeleteRule(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to create Manager: %v", err)
|
t.Errorf("failed to create Manager: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
|
||||||
|
|
||||||
ip := netip.MustParseAddr("192.168.1.1")
|
ip := netip.MustParseAddr("192.168.1.1")
|
||||||
proto := fw.ProtocolTCP
|
proto := fw.ProtocolTCP
|
||||||
@@ -166,9 +169,11 @@ func TestManagerDeleteRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetUDPPacketHook(t *testing.T) {
|
func TestSetUDPPacketHook(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, nbiface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
|
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
|
||||||
|
|
||||||
@@ -190,9 +195,11 @@ func TestSetUDPPacketHook(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetTCPPacketHook(t *testing.T) {
|
func TestSetTCPPacketHook(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, nbiface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { require.NoError(t, manager.Close(nil)) })
|
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 },
|
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)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, m.Close(nil))
|
require.NoError(t, m.Close(nil))
|
||||||
@@ -267,7 +274,7 @@ func TestPeerRuleAddAndDeleteDontLeak(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, m.Close(nil))
|
require.NoError(t, m.Close(nil))
|
||||||
@@ -307,7 +314,7 @@ func TestMixedAllowDenyRulesSameIP(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, m.Close(nil))
|
require.NoError(t, m.Close(nil))
|
||||||
@@ -360,7 +367,7 @@ func TestManagerReset(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to create Manager: %v", err)
|
t.Errorf("failed to create Manager: %v", err)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to create Manager: %v", err)
|
t.Errorf("failed to create Manager: %v", err)
|
||||||
return
|
return
|
||||||
@@ -462,7 +469,7 @@ func TestRemovePacketHook(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// creating manager instance
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create Manager: %s", err)
|
t.Fatalf("Failed to create Manager: %s", err)
|
||||||
}
|
}
|
||||||
@@ -479,9 +486,11 @@ func TestRemovePacketHook(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessOutgoingHooks(t *testing.T) {
|
func TestProcessOutgoingHooks(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, nbiface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
manager.udpTracker.Close()
|
manager.udpTracker.Close()
|
||||||
@@ -566,7 +575,7 @@ func TestUSPFilterCreatePerformance(t *testing.T) {
|
|||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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)
|
require.NoError(t, err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@@ -591,9 +600,11 @@ func TestUSPFilterCreatePerformance(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatefulFirewall_UDPTracking(t *testing.T) {
|
func TestStatefulFirewall_UDPTracking(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, nbiface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: nbiface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
manager.udpTracker.Close() // Close the existing tracker
|
manager.udpTracker.Close() // Close the existing tracker
|
||||||
@@ -805,7 +816,7 @@ func TestUpdateSetMerge(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
require.NoError(t, manager.Close(nil))
|
||||||
@@ -891,7 +902,7 @@ func TestUpdateSetDeduplication(t *testing.T) {
|
|||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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)}
|
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)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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
|
// Re-create manager to pick up the new address with IPv6
|
||||||
require.NoError(t, manager.Close(nil))
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
v6Cases := []struct {
|
v6Cases := []struct {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
@@ -20,9 +21,9 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
|
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter/conntrack"
|
||||||
nblog "github.com/netbirdio/netbird/client/firewall/uspfilter/log"
|
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"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +34,12 @@ const (
|
|||||||
iosMaxInFlight = 256
|
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 {
|
type Forwarder struct {
|
||||||
logger *nblog.Logger
|
logger *nblog.Logger
|
||||||
flowLogger nftypes.FlowLogger
|
flowLogger nftypes.FlowLogger
|
||||||
@@ -51,7 +58,7 @@ type Forwarder struct {
|
|||||||
pingSemaphore chan 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{
|
s := stack.New(stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||||
ipv4.NewProtocol,
|
ipv4.NewProtocol,
|
||||||
|
|||||||
+34
-31
@@ -9,7 +9,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type action string
|
type action string
|
||||||
@@ -20,35 +19,20 @@ const (
|
|||||||
firewallRuleName = "Netbird"
|
firewallRuleName = "Netbird"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Close cleans up the firewall manager by removing all rules and closing trackers
|
// WindowsInterfaceAllower opens the NetBird interface in the Windows firewall
|
||||||
func (m *Manager) Close(*statemanager.Manager) error {
|
// via netsh advfirewall rules. It implements InterfaceAllower for the userspace
|
||||||
m.mutex.Lock()
|
// firewall on Windows.
|
||||||
defer m.mutex.Unlock()
|
type WindowsInterfaceAllower struct {
|
||||||
|
iface Iface
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowNetbird allows netbird interface traffic
|
// NewWindowsInterfaceAllower builds the Windows netsh-based interface allower.
|
||||||
func (m *Manager) AllowNetbird() error {
|
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() {
|
if !isWindowsFirewallReachable() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -60,13 +44,13 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
"enable=yes",
|
"enable=yes",
|
||||||
"action=allow",
|
"action=allow",
|
||||||
"profile=any",
|
"profile=any",
|
||||||
"localip="+m.wgIface.Address().IP.String(),
|
"localip="+a.iface.Address().IP.String(),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
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",
|
if err := manageFirewallRule(firewallRuleName+"-v6",
|
||||||
addRule,
|
addRule,
|
||||||
"dir=in",
|
"dir=in",
|
||||||
@@ -82,8 +66,27 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
return nil
|
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}
|
args := []string{"advfirewall", "firewall", string(action), "rule", "name=" + ruleName}
|
||||||
if action == addRule {
|
if action == addRule {
|
||||||
args = append(args, extraArgs...)
|
args = append(args, extraArgs...)
|
||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// localIPSnapshot is an immutable snapshot of local IP addresses, swapped
|
// 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.
|
// 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() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("panic: %v", r)
|
err = fmt.Errorf("panic: %v", r)
|
||||||
|
|||||||
@@ -487,19 +487,13 @@ func incrementalUpdate(oldChecksum uint16, oldBytes, newBytes []byte) uint16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddDNATRule adds outbound DNAT rule for forwarding external traffic to NetBird network.
|
// AddDNATRule adds outbound DNAT rule for forwarding external traffic to NetBird network.
|
||||||
func (m *Manager) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {
|
func (m *Manager) AddDNATRule(firewall.ForwardRule) (firewall.Rule, error) {
|
||||||
if m.nativeFirewall == nil {
|
return nil, errNotSupported
|
||||||
return nil, errNatNotSupported
|
|
||||||
}
|
|
||||||
return m.nativeFirewall.AddDNATRule(rule)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDNATRule deletes outbound DNAT rule.
|
// DeleteDNATRule deletes outbound DNAT rule.
|
||||||
func (m *Manager) DeleteDNATRule(rule firewall.Rule) error {
|
func (m *Manager) DeleteDNATRule(firewall.Rule) error {
|
||||||
if m.nativeFirewall == nil {
|
return errNotSupported
|
||||||
return errNatNotSupported
|
|
||||||
}
|
|
||||||
return m.nativeFirewall.DeleteDNATRule(rule)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addPortRedirection adds a port redirection rule.
|
// 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.
|
// 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 {
|
func (m *Manager) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
|
||||||
var layerType gopacket.LayerType
|
var layerType gopacket.LayerType
|
||||||
switch protocol {
|
switch protocol {
|
||||||
@@ -567,20 +560,16 @@ func (m *Manager) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Prot
|
|||||||
return m.removePortRedirection(localAddr, layerType, originalPort, translatedPort)
|
return m.removePortRedirection(localAddr, layerType, originalPort, translatedPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOutputDNAT delegates to the native firewall if available.
|
// AddOutputDNAT is not supported by the userspace firewall: it backs kernel DNS
|
||||||
func (m *Manager) AddOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
|
// redirection, but userspace DNS is served in-process on the gVisor netstack, so
|
||||||
if m.nativeFirewall == nil {
|
// this should never be called.
|
||||||
return fmt.Errorf("output DNAT not supported without native firewall")
|
func (m *Manager) AddOutputDNAT(netip.Addr, firewall.Protocol, uint16, uint16) error {
|
||||||
}
|
return errNotSupported
|
||||||
return m.nativeFirewall.AddOutputDNAT(localAddr, protocol, originalPort, translatedPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOutputDNAT delegates to the native firewall if available.
|
// RemoveOutputDNAT is a no-op for the userspace firewall (see AddOutputDNAT).
|
||||||
func (m *Manager) RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
|
func (m *Manager) RemoveOutputDNAT(netip.Addr, firewall.Protocol, uint16, uint16) error {
|
||||||
if m.nativeFirewall == nil {
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return m.nativeFirewall.RemoveOutputDNAT(localAddr, protocol, originalPort, translatedPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// translateInboundPortDNAT applies port-specific DNAT translation to inbound packets.
|
// translateInboundPortDNAT applies port-specific DNAT translation to inbound packets.
|
||||||
|
|||||||
@@ -64,9 +64,11 @@ func BenchmarkDNATTranslation(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
@@ -124,9 +126,11 @@ func BenchmarkDNATTranslation(b *testing.B) {
|
|||||||
|
|
||||||
// BenchmarkDNATConcurrency tests DNAT performance under concurrent load
|
// BenchmarkDNATConcurrency tests DNAT performance under concurrent load
|
||||||
func BenchmarkDNATConcurrency(b *testing.B) {
|
func BenchmarkDNATConcurrency(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
@@ -196,9 +200,11 @@ func BenchmarkDNATScaling(b *testing.B) {
|
|||||||
|
|
||||||
for _, count := range mappingCounts {
|
for _, count := range mappingCounts {
|
||||||
b.Run(fmt.Sprintf("mappings_%d", count), func(b *testing.B) {
|
b.Run(fmt.Sprintf("mappings_%d", count), func(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
@@ -308,9 +314,11 @@ func BenchmarkChecksumUpdate(b *testing.B) {
|
|||||||
|
|
||||||
// BenchmarkDNATMemoryAllocations checks for memory allocations in DNAT operations
|
// BenchmarkDNATMemoryAllocations checks for memory allocations in DNAT operations
|
||||||
func BenchmarkDNATMemoryAllocations(b *testing.B) {
|
func BenchmarkDNATMemoryAllocations(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
@@ -481,9 +489,11 @@ func BenchmarkPortDNAT(b *testing.B) {
|
|||||||
|
|
||||||
for _, sc := range scenarios {
|
for _, sc := range scenarios {
|
||||||
b.Run(sc.name, func(b *testing.B) {
|
b.Run(sc.name, func(b *testing.B) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(b, manager.Close(nil))
|
require.NoError(b, manager.Close(nil))
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import (
|
|||||||
|
|
||||||
// TestPortDNATBasic tests basic port DNAT functionality
|
// TestPortDNATBasic tests basic port DNAT functionality
|
||||||
func TestPortDNATBasic(t *testing.T) {
|
func TestPortDNATBasic(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
require.NoError(t, manager.Close(nil))
|
||||||
@@ -49,9 +51,11 @@ func TestPortDNATBasic(t *testing.T) {
|
|||||||
|
|
||||||
// TestPortDNATMultipleRules tests multiple port DNAT rules
|
// TestPortDNATMultipleRules tests multiple port DNAT rules
|
||||||
func TestPortDNATMultipleRules(t *testing.T) {
|
func TestPortDNATMultipleRules(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
require.NoError(t, manager.Close(nil))
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import (
|
|||||||
|
|
||||||
// TestDNATTranslationCorrectness verifies DNAT translation works correctly
|
// TestDNATTranslationCorrectness verifies DNAT translation works correctly
|
||||||
func TestDNATTranslationCorrectness(t *testing.T) {
|
func TestDNATTranslationCorrectness(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
require.NoError(t, manager.Close(nil))
|
||||||
@@ -104,9 +106,11 @@ func parsePacket(t testing.TB, packetData []byte) *decoder {
|
|||||||
|
|
||||||
// TestDNATMappingManagement tests adding/removing DNAT mappings
|
// TestDNATMappingManagement tests adding/removing DNAT mappings
|
||||||
func TestDNATMappingManagement(t *testing.T) {
|
func TestDNATMappingManagement(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
require.NoError(t, manager.Close(nil))
|
||||||
@@ -152,9 +156,11 @@ func TestDNATMappingManagement(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundPortDNAT(t *testing.T) {
|
func TestInboundPortDNAT(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
require.NoError(t, manager.Close(nil))
|
||||||
@@ -202,9 +208,11 @@ func TestInboundPortDNAT(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundPortDNATNegative(t *testing.T) {
|
func TestInboundPortDNATNegative(t *testing.T) {
|
||||||
manager, err := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, manager.Close(nil))
|
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),
|
IPv6Net: netip.PrefixFrom(dstIP, bits),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
manager, _ := Create(&IFaceMock{
|
manager, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
AddressFunc: func() wgaddr.Address { return mockAddr },
|
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
AddressFunc: func() wgaddr.Address { return mockAddr },
|
||||||
|
},
|
||||||
|
FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
|
require.NoError(b, err)
|
||||||
b.Cleanup(func() { require.NoError(b, manager.Close(nil)) })
|
b.Cleanup(func() { require.NoError(b, manager.Close(nil)) })
|
||||||
|
|
||||||
// Generate M policies × K source peers, all distinct.
|
// 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.Run(c.name, func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
mgr, _ := Create(&IFaceMock{
|
mgr, err := Create(Config{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
IFace: &IFaceMock{
|
||||||
}, nil, false, flowLogger, iface.DefaultMTU)
|
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)
|
populateIndexedRules(b, mgr, c.M, c.K, c.overlapFrac)
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ func newTestManager(t *testing.T) *Manager {
|
|||||||
ifaceMock := &IFaceMock{
|
ifaceMock := &IFaceMock{
|
||||||
SetFilterFunc: func(device.PacketFilter) error { return nil },
|
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")
|
require.NoError(t, err, "create manager")
|
||||||
|
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
|
||||||
return m
|
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")
|
require.NoError(t, err, "create manager")
|
||||||
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
|
t.Cleanup(func() { require.NoError(t, m.Close(nil)) })
|
||||||
return m
|
return m
|
||||||
|
|||||||
@@ -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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if !statefulMode {
|
if !statefulMode {
|
||||||
|
|||||||
@@ -900,7 +900,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pf, err := uspfilter.Create(wgIface, nil, false, flowLogger, iface.DefaultMTU)
|
pf, err := uspfilter.Create(uspfilter.Config{IFace: wgIface, FlowLogger: flowLogger, MTU: iface.DefaultMTU})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create uspfilter: %v", err)
|
t.Fatalf("failed to create uspfilter: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user