Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[client] Cleanup firewall state on startup #2768

Merged
merged 33 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2a1ec9b
Add state manager, migrate dns cleanup and add route cleanup
lixmal Oct 16, 2024
d4ef622
Add mobile dummies
lixmal Oct 18, 2024
c0adf17
Remove broken state files
lixmal Oct 18, 2024
a6cc9f2
Remove obsolete return
lixmal Oct 18, 2024
c4ac044
Fix log msg
lixmal Oct 18, 2024
631b8dc
Fix android build
lixmal Oct 18, 2024
57b350c
Fix some tests
lixmal Oct 18, 2024
db9e805
Fix linter
lixmal Oct 18, 2024
d1240fd
Fix dns test
lixmal Oct 18, 2024
9f6eb39
Remove obsolete return
lixmal Oct 18, 2024
79c7a83
Ignore go lint for permissions
lixmal Oct 18, 2024
17a2b04
Fix freebsd
lixmal Oct 18, 2024
21e224a
Remove route state on stop
lixmal Oct 18, 2024
3c1a1bc
Cleanup firewall rules on unclean shutdown
lixmal Oct 21, 2024
ecdd1f7
Fix tests
lixmal Oct 22, 2024
7c3dbb6
Persist nftables early as well
lixmal Oct 22, 2024
84d5e0f
Remove obsolete marshal methods
lixmal Oct 22, 2024
301979d
Exclude android
lixmal Oct 22, 2024
ab81b60
Remove ref.go
lixmal Oct 22, 2024
3302be5
Remove obsolete SetFunctions
lixmal Oct 22, 2024
c86a8de
Move build flag to correct place
lixmal Oct 22, 2024
f4e4eec
Fix copied mutex issue
lixmal Oct 22, 2024
d4cb34d
Add android flag to generic state
lixmal Oct 22, 2024
d3c1084
Fix removed import
lixmal Oct 22, 2024
80a0b72
Fix windows Reset method
lixmal Oct 22, 2024
e34e91b
Make state files Linux only
lixmal Oct 22, 2024
a80ad7f
Remove unused context
lixmal Oct 23, 2024
236c74f
Fix test
lixmal Oct 23, 2024
f2e48d3
Simplify route state
lixmal Oct 23, 2024
248f5e1
Reduce cognitive complexity
lixmal Oct 23, 2024
6fe6a90
Merge branch 'main' into cleanup-firewall
lixmal Oct 24, 2024
5a53cef
Fix regressions
lixmal Oct 24, 2024
f45ae2e
Fix more regressions
lixmal Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/firewall/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (

firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/firewall/uspfilter"
"github.com/netbirdio/netbird/client/internal/statemanager"
)

// NewFirewall creates a firewall manager instance
func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager, error) {
func NewFirewall(_ context.Context, iface IFaceMapper, _ *statemanager.Manager) (firewall.Manager, error) {
lixmal marked this conversation as resolved.
Show resolved Hide resolved
if !iface.IsUserspaceBind() {
return nil, fmt.Errorf("not implemented for this OS: %s", runtime.GOOS)
}
Expand Down
9 changes: 8 additions & 1 deletion client/firewall/create_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
firewall "github.com/netbirdio/netbird/client/firewall/manager"
nbnftables "github.com/netbirdio/netbird/client/firewall/nftables"
"github.com/netbirdio/netbird/client/firewall/uspfilter"
"github.com/netbirdio/netbird/client/internal/statemanager"
)

const (
Expand All @@ -32,7 +33,7 @@ const SKIP_NFTABLES_ENV = "NB_SKIP_NFTABLES_CHECK"
// FWType is the type for the firewall type
type FWType int

func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager, error) {
func NewFirewall(context context.Context, iface IFaceMapper, stateManager *statemanager.Manager) (firewall.Manager, error) {
// on the linux system we try to user nftables or iptables
// in any case, because we need to allow netbird interface traffic
// so we use AllowNetbird traffic from these firewall managers
Expand All @@ -58,6 +59,12 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager,
log.Info("no firewall manager found, trying to use userspace packet filtering firewall")
}

if fm != nil {
if err := fm.Init(stateManager); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will be if the iface.IsUserspaceBind() scope happens an error? Should not we revert this initialization?

Copy link
Contributor Author

@lixmal lixmal Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, but this is out of scope for the PR. I just split out the init logic in the Create functions so it is not run when cleaning up only.

log.Errorf("failed to init nftables manager: %s", err)
}
}

if iface.IsUserspaceBind() {
var errUsp error
if errFw == nil {
Expand Down
79 changes: 63 additions & 16 deletions client/firewall/iptables/acl_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
log "github.com/sirupsen/logrus"

firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/internal/statemanager"
nbnet "github.com/netbirdio/netbird/util/net"
)

Expand All @@ -22,6 +23,8 @@ const (
chainNameOutputRules = "NETBIRD-ACL-OUTPUT"
)

type aclEntries map[string][][]string

type entry struct {
spec []string
position int
Expand All @@ -32,9 +35,11 @@ type aclManager struct {
wgIface iFaceMapper
routingFwChainName string

entries map[string][][]string
entries aclEntries
optionalEntries map[string][]entry
ipsetStore *ipsetStore

stateManager *statemanager.Manager
}

func newAclManager(iptablesClient *iptables.IPTables, wgIface iFaceMapper, routingFwChainName string) (*aclManager, error) {
Expand All @@ -48,24 +53,30 @@ func newAclManager(iptablesClient *iptables.IPTables, wgIface iFaceMapper, routi
ipsetStore: newIpsetStore(),
}

err := ipset.Init()
if err != nil {
return nil, fmt.Errorf("failed to init ipset: %w", err)
if err := ipset.Init(); err != nil {
return nil, fmt.Errorf("init ipset: %w", err)
}

return m, nil
}

func (m *aclManager) init(stateManager *statemanager.Manager) error {
m.stateManager = stateManager

m.seedInitialEntries()
m.seedInitialOptionalEntries()

err = m.cleanChains()
if err != nil {
return nil, err
if err := m.cleanChains(); err != nil {
return fmt.Errorf("clean chains: %w", err)
}

err = m.createDefaultChains()
if err != nil {
return nil, err
if err := m.createDefaultChains(); err != nil {
return fmt.Errorf("create default chains: %w", err)
}
return m, nil

m.updateState()

return nil
}

func (m *aclManager) AddPeerFiltering(
Expand Down Expand Up @@ -146,6 +157,8 @@ func (m *aclManager) AddPeerFiltering(
chain: chain,
}

m.updateState()

return []firewall.Rule{rule}, nil
}

Expand Down Expand Up @@ -180,15 +193,23 @@ func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
}
}

err := m.iptablesClient.Delete(tableName, r.chain, r.specs...)
if err != nil {
log.Debugf("failed to delete rule, %s, %v: %s", r.chain, r.specs, err)
if err := m.iptablesClient.Delete(tableName, r.chain, r.specs...); err != nil {
return fmt.Errorf("failed to delete rule: %s, %v: %w", r.chain, r.specs, err)
}
return err

m.updateState()

return nil
}

func (m *aclManager) Reset() error {
return m.cleanChains()
if err := m.cleanChains(); err != nil {
return fmt.Errorf("clean chains: %w", err)
}

m.updateState()

return nil
}

// todo write less destructive cleanup mechanism
Expand Down Expand Up @@ -348,6 +369,32 @@ func (m *aclManager) appendToEntries(chainName string, spec []string) {
m.entries[chainName] = append(m.entries[chainName], spec)
}

func (m *aclManager) updateState() {
if m.stateManager == nil {
return
}

var currentState *ShutdownState
if existing := m.stateManager.GetState(&ShutdownState{}); existing != nil {
if existingState, ok := existing.(*ShutdownState); ok {
currentState = existingState
}
}
if currentState == nil {
currentState = &ShutdownState{}
}

currentState.Lock()
defer currentState.Unlock()

currentState.ACLEntries = m.entries
currentState.ACLIPsetStore = m.ipsetStore

if err := m.stateManager.UpdateState(currentState); err != nil {
log.Errorf("failed to update state: %v", err)
}
}

// filterRuleSpecs returns the specs of a filtering rule
func filterRuleSpecs(
ip net.IP, protocol string, sPort, dPort string, direction firewall.RuleDirection, action firewall.Action, ipsetName string,
Expand Down
67 changes: 53 additions & 14 deletions client/firewall/iptables/manager_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (
"sync"

"github.com/coreos/go-iptables/iptables"
"github.com/hashicorp/go-multierror"
log "github.com/sirupsen/logrus"

nberrors "github.com/netbirdio/netbird/client/errors"
firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/internal/statemanager"
)

// Manager of iptables firewall
Expand All @@ -36,7 +39,7 @@ type iFaceMapper interface {
func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) {
iptablesClient, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err != nil {
return nil, fmt.Errorf("iptables is not installed in the system or not supported")
return nil, fmt.Errorf("init iptables: %w", err)
}

m := &Manager{
Expand All @@ -46,18 +49,47 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) {

m.router, err = newRouter(context, iptablesClient, wgIface)
if err != nil {
log.Debugf("failed to initialize route related chains: %s", err)
return nil, err
return nil, fmt.Errorf("create router: %w", err)
}

m.aclMgr, err = newAclManager(iptablesClient, wgIface, chainRTFWD)
if err != nil {
log.Debugf("failed to initialize ACL manager: %s", err)
return nil, err
return nil, fmt.Errorf("create acl manager: %w", err)
}

return m, nil
}

func (m *Manager) Init(stateManager *statemanager.Manager) error {
state := &ShutdownState{
InterfaceState: &InterfaceState{
NameStr: m.wgIface.Name(),
WGAddress: m.wgIface.Address(),
UserspaceBind: m.wgIface.IsUserspaceBind(),
},
}
stateManager.RegisterState(state)
if err := stateManager.UpdateState(state); err != nil {
log.Errorf("failed to update state: %v", err)
}

if err := m.router.init(stateManager); err != nil {
return fmt.Errorf("router init: %w", err)
}

if err := m.aclMgr.init(stateManager); err != nil {
// TODO: cleanup router
return fmt.Errorf("acl manager init: %w", err)
}

// persist early to ensure cleanup of chains
if err := stateManager.PersistState(context.Background()); err != nil {
log.Errorf("failed to persist state: %v", err)
}

return nil
}

// AddPeerFiltering adds a rule to the firewall
//
// Comment will be ignored because some system this feature is not supported
Expand Down Expand Up @@ -133,20 +165,27 @@ func (m *Manager) SetLegacyManagement(isLegacy bool) error {
}

// Reset firewall to the default state
func (m *Manager) Reset() error {
func (m *Manager) Reset(stateManager *statemanager.Manager) error {
m.mutex.Lock()
defer m.mutex.Unlock()

errAcl := m.aclMgr.Reset()
if errAcl != nil {
log.Errorf("failed to clean up ACL rules from firewall: %s", errAcl)
var merr *multierror.Error

if err := m.aclMgr.Reset(); err != nil {
merr = multierror.Append(merr, fmt.Errorf("reset acl manager: %w", err))
}
if err := m.router.Reset(); err != nil {
merr = multierror.Append(merr, fmt.Errorf("reset router: %w", err))
}
errMgr := m.router.Reset()
if errMgr != nil {
log.Errorf("failed to clean up router rules from firewall: %s", errMgr)
return errMgr

// attempt to delete state only if all other operations succeeded
if merr == nil {
if err := stateManager.DeleteState(&ShutdownState{}); err != nil {
merr = multierror.Append(merr, fmt.Errorf("delete state: %w", err))
}
}
return errAcl

return nberrors.FormatErrorOrNil(merr)
}

// AllowNetbird allows netbird interface traffic
Expand Down
13 changes: 8 additions & 5 deletions client/firewall/iptables/manager_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ func TestIptablesManager(t *testing.T) {
// just check on the local interface
manager, err := Create(context.Background(), ifaceMock)
require.NoError(t, err)
require.NoError(t, manager.Init(nil))

time.Sleep(time.Second)

defer func() {
err := manager.Reset()
err := manager.Reset(nil)
require.NoError(t, err, "clear the manager state")

time.Sleep(time.Second)
Expand Down Expand Up @@ -122,7 +123,7 @@ func TestIptablesManager(t *testing.T) {
_, err = manager.AddPeerFiltering(ip, "udp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "", "accept Fake DNS traffic")
require.NoError(t, err, "failed to add rule")

err = manager.Reset()
err = manager.Reset(nil)
require.NoError(t, err, "failed to reset")

ok, err := ipv4Client.ChainExists("filter", chainNameInputRules)
Expand Down Expand Up @@ -156,11 +157,12 @@ func TestIptablesManagerIPSet(t *testing.T) {
// just check on the local interface
manager, err := Create(context.Background(), mock)
require.NoError(t, err)
require.NoError(t, manager.Init(nil))

time.Sleep(time.Second)

defer func() {
err := manager.Reset()
err := manager.Reset(nil)
require.NoError(t, err, "clear the manager state")

time.Sleep(time.Second)
Expand Down Expand Up @@ -219,7 +221,7 @@ func TestIptablesManagerIPSet(t *testing.T) {
})

t.Run("reset check", func(t *testing.T) {
err = manager.Reset()
err = manager.Reset(nil)
require.NoError(t, err, "failed to reset")
})
}
Expand Down Expand Up @@ -253,10 +255,11 @@ func TestIptablesCreatePerformance(t *testing.T) {
// just check on the local interface
manager, err := Create(context.Background(), mock)
require.NoError(t, err)
require.NoError(t, manager.Init(nil))
time.Sleep(time.Second)

defer func() {
err := manager.Reset()
err := manager.Reset(nil)
require.NoError(t, err, "clear the manager state")

time.Sleep(time.Second)
Expand Down
Loading
Loading