Skip to content

Commit eddea14

Browse files
authored
[client] Clean up bsd routes independently of the state file (#4688)
1 parent b9ef214 commit eddea14

File tree

9 files changed

+106
-13
lines changed

9 files changed

+106
-13
lines changed

client/internal/routemanager/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ type DefaultManager struct {
106106
func NewManager(config ManagerConfig) *DefaultManager {
107107
mCTX, cancel := context.WithCancel(config.Context)
108108
notifier := notifier.NewNotifier()
109-
sysOps := systemops.NewSysOps(config.WGInterface, notifier)
109+
sysOps := systemops.New(config.WGInterface, notifier)
110110

111111
if runtime.GOOS == "windows" && config.WGInterface != nil {
112112
nbnet.SetVPNInterfaceName(config.WGInterface.Name())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//go:build !((darwin && !ios) || dragonfly || freebsd || netbsd || openbsd)
2+
3+
package systemops
4+
5+
// FlushMarkedRoutes is a no-op on non-BSD platforms.
6+
func (r *SysOps) FlushMarkedRoutes() error {
7+
return nil
8+
}

client/internal/routemanager/systemops/state.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ func (s *ShutdownState) Name() string {
1313
}
1414

1515
func (s *ShutdownState) Cleanup() error {
16-
sysops := NewSysOps(nil, nil)
17-
sysops.refCounter = refcounter.New[netip.Prefix, struct{}, Nexthop](nil, sysops.removeFromRouteTable)
18-
sysops.refCounter.LoadData((*ExclusionCounter)(s))
16+
sysOps := New(nil, nil)
17+
sysOps.refCounter = refcounter.New[netip.Prefix, struct{}, Nexthop](nil, sysOps.removeFromRouteTable)
18+
sysOps.refCounter.LoadData((*ExclusionCounter)(s))
1919

20-
return sysops.refCounter.Flush()
20+
return sysOps.refCounter.Flush()
2121
}
2222

2323
func (s *ShutdownState) MarshalJSON() ([]byte, error) {

client/internal/routemanager/systemops/systemops.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ type SysOps struct {
8383
localSubnetsCacheTime time.Time
8484
}
8585

86-
func NewSysOps(wgInterface wgIface, notifier *notifier.Notifier) *SysOps {
86+
func New(wgInterface wgIface, notifier *notifier.Notifier) *SysOps {
8787
return &SysOps{
8888
wgInterface: wgInterface,
8989
notifier: notifier,

client/internal/routemanager/systemops/systemops_bsd_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestConcurrentRoutes(t *testing.T) {
4242
_, intf = setupDummyInterface(t)
4343
nexthop = Nexthop{netip.Addr{}, intf}
4444

45-
r := NewSysOps(nil, nil)
45+
r := New(nil, nil)
4646

4747
var wg sync.WaitGroup
4848
for i := 0; i < 1024; i++ {
@@ -146,7 +146,7 @@ func createAndSetupDummyInterface(t *testing.T, intf string, ipAddressCIDR strin
146146

147147
nexthop := Nexthop{netip.Addr{}, netIntf}
148148

149-
r := NewSysOps(nil, nil)
149+
r := New(nil, nil)
150150
err = r.addToRouteTable(prefix, nexthop)
151151
require.NoError(t, err, "Failed to add route to table")
152152

client/internal/routemanager/systemops/systemops_generic_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func TestAddVPNRoute(t *testing.T) {
143143

144144
wgInterface := createWGInterface(t, fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100+n)
145145

146-
r := NewSysOps(wgInterface, nil)
146+
r := New(wgInterface, nil)
147147
advancedRouting := nbnet.AdvancedRouting()
148148
err := r.SetupRouting(nil, nil, advancedRouting)
149149
require.NoError(t, err)
@@ -342,7 +342,7 @@ func TestAddRouteToNonVPNIntf(t *testing.T) {
342342

343343
wgInterface := createWGInterface(t, fmt.Sprintf("utun54%d", n), "100.65.75.2/24", 33200+n)
344344

345-
r := NewSysOps(wgInterface, nil)
345+
r := New(wgInterface, nil)
346346
advancedRouting := nbnet.AdvancedRouting()
347347
err := r.SetupRouting(nil, nil, advancedRouting)
348348
require.NoError(t, err)
@@ -486,7 +486,7 @@ func setupTestEnv(t *testing.T) {
486486
assert.NoError(t, wgInterface.Close())
487487
})
488488

489-
r := NewSysOps(wgInterface, nil)
489+
r := New(wgInterface, nil)
490490
advancedRouting := nbnet.AdvancedRouting()
491491
err := r.SetupRouting(nil, nil, advancedRouting)
492492
require.NoError(t, err, "setupRouting should not return err")

client/internal/routemanager/systemops/systemops_unix.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,39 @@ import (
77
"fmt"
88
"net"
99
"net/netip"
10+
"os"
1011
"strconv"
1112
"syscall"
1213
"time"
1314
"unsafe"
1415

1516
"github.com/cenkalti/backoff/v4"
17+
"github.com/hashicorp/go-multierror"
1618
log "github.com/sirupsen/logrus"
1719
"golang.org/x/net/route"
1820
"golang.org/x/sys/unix"
1921

22+
nberrors "github.com/netbirdio/netbird/client/errors"
2023
"github.com/netbirdio/netbird/client/internal/statemanager"
2124
)
2225

26+
const (
27+
envRouteProtoFlag = "NB_ROUTE_PROTO_FLAG"
28+
)
29+
30+
var routeProtoFlag int
31+
32+
func init() {
33+
switch os.Getenv(envRouteProtoFlag) {
34+
case "2":
35+
routeProtoFlag = unix.RTF_PROTO2
36+
case "3":
37+
routeProtoFlag = unix.RTF_PROTO3
38+
default:
39+
routeProtoFlag = unix.RTF_PROTO1
40+
}
41+
}
42+
2343
func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager, advancedRouting bool) error {
2444
return r.setupRefCounter(initAddresses, stateManager)
2545
}
@@ -28,6 +48,62 @@ func (r *SysOps) CleanupRouting(stateManager *statemanager.Manager, advancedRout
2848
return r.cleanupRefCounter(stateManager)
2949
}
3050

51+
// FlushMarkedRoutes removes single IP exclusion routes marked with the configured RTF_PROTO flag.
52+
func (r *SysOps) FlushMarkedRoutes() error {
53+
rib, err := retryFetchRIB()
54+
if err != nil {
55+
return fmt.Errorf("fetch routing table: %w", err)
56+
}
57+
58+
msgs, err := route.ParseRIB(route.RIBTypeRoute, rib)
59+
if err != nil {
60+
return fmt.Errorf("parse routing table: %w", err)
61+
}
62+
63+
var merr *multierror.Error
64+
flushedCount := 0
65+
66+
for _, msg := range msgs {
67+
rtMsg, ok := msg.(*route.RouteMessage)
68+
if !ok {
69+
continue
70+
}
71+
72+
if rtMsg.Flags&routeProtoFlag == 0 {
73+
continue
74+
}
75+
76+
routeInfo, err := MsgToRoute(rtMsg)
77+
if err != nil {
78+
log.Debugf("Skipping route flush: %v", err)
79+
continue
80+
}
81+
82+
if !routeInfo.Dst.IsValid() || !routeInfo.Dst.IsSingleIP() {
83+
continue
84+
}
85+
86+
nexthop := Nexthop{
87+
IP: routeInfo.Gw,
88+
Intf: routeInfo.Interface,
89+
}
90+
91+
if err := r.removeFromRouteTable(routeInfo.Dst, nexthop); err != nil {
92+
merr = multierror.Append(merr, fmt.Errorf("remove route %s: %w", routeInfo.Dst, err))
93+
continue
94+
}
95+
96+
flushedCount++
97+
log.Debugf("Flushed marked route: %s", routeInfo.Dst)
98+
}
99+
100+
if flushedCount > 0 {
101+
log.Infof("Flushed %d residual NetBird routes from previous session", flushedCount)
102+
}
103+
104+
return nberrors.FormatErrorOrNil(merr)
105+
}
106+
31107
func (r *SysOps) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error {
32108
return r.routeSocket(unix.RTM_ADD, prefix, nexthop)
33109
}
@@ -105,7 +181,7 @@ func (r *SysOps) routeOp(action int, prefix netip.Prefix, nexthop Nexthop) func(
105181
func (r *SysOps) buildRouteMessage(action int, prefix netip.Prefix, nexthop Nexthop) (msg *route.RouteMessage, err error) {
106182
msg = &route.RouteMessage{
107183
Type: action,
108-
Flags: unix.RTF_UP,
184+
Flags: unix.RTF_UP | routeProtoFlag,
109185
Version: unix.RTM_VERSION,
110186
Seq: r.getSeq(),
111187
}

client/internal/statemanager/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ func (m *Manager) loadStateFile(deleteCorrupt bool) (map[string]json.RawMessage,
295295
data, err := os.ReadFile(m.filePath)
296296
if err != nil {
297297
if errors.Is(err, fs.ErrNotExist) {
298-
log.Debug("state file does not exist")
298+
log.Debugf("state file %s does not exist", m.filePath)
299299
return nil, nil // nolint:nilnil
300300
}
301301
return nil, fmt.Errorf("read state file: %w", err)

client/server/state.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import (
1010

1111
nberrors "github.com/netbirdio/netbird/client/errors"
1212
"github.com/netbirdio/netbird/client/internal"
13+
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
1314
"github.com/netbirdio/netbird/client/internal/statemanager"
15+
nbnet "github.com/netbirdio/netbird/client/net"
1416
"github.com/netbirdio/netbird/client/proto"
1517
)
1618

@@ -135,5 +137,12 @@ func restoreResidualState(ctx context.Context, statePath string) error {
135137
merr = multierror.Append(merr, fmt.Errorf("persist state: %w", err))
136138
}
137139

140+
// clean up any remaining routes independently of the state file
141+
if !nbnet.AdvancedRouting() {
142+
if err := systemops.New(nil, nil).FlushMarkedRoutes(); err != nil {
143+
merr = multierror.Append(merr, fmt.Errorf("flush marked routes: %w", err))
144+
}
145+
}
146+
138147
return nberrors.FormatErrorOrNil(merr)
139148
}

0 commit comments

Comments
 (0)