From bf0b6c5f8ae6150ef26dab4256a4e88efdbe3da0 Mon Sep 17 00:00:00 2001
From: Maycon Santos <mlsmaycon@gmail.com>
Date: Fri, 12 Apr 2024 18:44:01 +0200
Subject: [PATCH 1/5] Delete route from system only if added by the client

This adds a flags to check if the addVPNRoute executed without issues before removing a route from the system
---
 client/internal/routemanager/client.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go
index d41ed422b81..eb9f6adea1e 100644
--- a/client/internal/routemanager/client.go
+++ b/client/internal/routemanager/client.go
@@ -39,6 +39,7 @@ type clientNetwork struct {
 	chosenRoute         *route.Route
 	network             netip.Prefix
 	updateSerial        uint64
+	systemUpdated       bool
 }
 
 func newClientNetworkWatcher(ctx context.Context, wgInterface *iface.WGIface, statusRecorder *peer.Status, network netip.Prefix) *clientNetwork {
@@ -215,8 +216,11 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
 
 func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
 	if c.chosenRoute != nil {
-		if err := removeVPNRoute(c.network, c.wgInterface.Name()); err != nil {
-			return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
+		if c.systemUpdated {
+			if err := removeVPNRoute(c.network, c.wgInterface.Name()); err != nil {
+				return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
+			}
+			c.systemUpdated = false
 		}
 
 		if err := c.removeRouteFromWireguardPeer(c.chosenRoute.Peer); err != nil {
@@ -260,6 +264,8 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
 			return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
 				c.network.String(), c.wgInterface.Address().IP.String(), err)
 		}
+
+		c.systemUpdated = true
 	}
 
 	c.chosenRoute = c.routes[chosen]

From c4f17771e356d3a8859556db7c5baf7225e32844 Mon Sep 17 00:00:00 2001
From: Maycon Santos <mlsmaycon@gmail.com>
Date: Fri, 12 Apr 2024 18:44:48 +0200
Subject: [PATCH 2/5] return error instead of log

---
 client/internal/routemanager/systemops.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/client/internal/routemanager/systemops.go b/client/internal/routemanager/systemops.go
index 1ee54b746d8..ad7edd366ef 100644
--- a/client/internal/routemanager/systemops.go
+++ b/client/internal/routemanager/systemops.go
@@ -256,8 +256,7 @@ func addNonExistingRoute(prefix netip.Prefix, intf string) error {
 		return fmt.Errorf("exists in route table: %w", err)
 	}
 	if ok {
-		log.Warnf("Skipping adding a new route for network %s because it already exists", prefix)
-		return nil
+		return fmt.Errorf("Skipping adding a new route for network %s because it already exists", prefix)
 	}
 
 	ok, err = isSubRange(prefix)

From 56450e1b4fc9fcddfff369e2aeea4594cc50d9c2 Mon Sep 17 00:00:00 2001
From: Maycon Santos <mlsmaycon@gmail.com>
Date: Sun, 14 Apr 2024 16:39:30 +0200
Subject: [PATCH 3/5] Update client/internal/routemanager/systemops.go

Co-authored-by: Viktor Liu <viktor@netbird.io>
---
 client/internal/routemanager/systemops.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/internal/routemanager/systemops.go b/client/internal/routemanager/systemops.go
index ad7edd366ef..6c14fb77501 100644
--- a/client/internal/routemanager/systemops.go
+++ b/client/internal/routemanager/systemops.go
@@ -256,7 +256,7 @@ func addNonExistingRoute(prefix netip.Prefix, intf string) error {
 		return fmt.Errorf("exists in route table: %w", err)
 	}
 	if ok {
-		return fmt.Errorf("Skipping adding a new route for network %s because it already exists", prefix)
+		return fmt.Errorf("skipping adding a new route for network %s because it already exists", prefix)
 	}
 
 	ok, err = isSubRange(prefix)

From c4ca7ef1b15842646b7e4086c288e7b0170cb64f Mon Sep 17 00:00:00 2001
From: Maycon Santos <mlsmaycon@gmail.com>
Date: Sun, 14 Apr 2024 16:43:56 +0200
Subject: [PATCH 4/5] add debug log

---
 client/internal/routemanager/client.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go
index eb9f6adea1e..28a862a9258 100644
--- a/client/internal/routemanager/client.go
+++ b/client/internal/routemanager/client.go
@@ -221,6 +221,8 @@ func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
 				return fmt.Errorf("remove route %s from system, err: %v", c.network, err)
 			}
 			c.systemUpdated = false
+		} else {
+			log.Debugf("system is not updated for network %s, skiping removal from system route table", c.network)
 		}
 
 		if err := c.removeRouteFromWireguardPeer(c.chosenRoute.Peer); err != nil {

From 64faeb5fae46c4053b64242577778c3381fee754 Mon Sep 17 00:00:00 2001
From: Maycon Santos <mlsmaycon@gmail.com>
Date: Sun, 14 Apr 2024 22:45:01 +0200
Subject: [PATCH 5/5] fix tests

---
 .../internal/routemanager/systemops_test.go   | 60 ++++++++-----------
 1 file changed, 25 insertions(+), 35 deletions(-)

diff --git a/client/internal/routemanager/systemops_test.go b/client/internal/routemanager/systemops_test.go
index 9f906c06fbe..887f43418bb 100644
--- a/client/internal/routemanager/systemops_test.go
+++ b/client/internal/routemanager/systemops_test.go
@@ -3,18 +3,14 @@
 package routemanager
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"net"
 	"net/netip"
-	"os"
 	"runtime"
-	"strings"
 	"testing"
 
 	"github.com/pion/transport/v3/stdnet"
-	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -41,8 +37,8 @@ func TestAddRemoveRoutes(t *testing.T) {
 			shouldBeRemoved:        true,
 		},
 		{
-			name:                   "Should Not Add Or Remove Route 127.0.0.1/32",
-			prefix:                 netip.MustParsePrefix("127.0.0.1/32"),
+			name:                   "Should Not Add Or Remove Route 127.0.1.1/32",
+			prefix:                 netip.MustParsePrefix("127.0.1.1/32"),
 			shouldRouteToWireguard: false,
 			shouldBeRemoved:        false,
 		},
@@ -140,10 +136,10 @@ func TestGetNextHop(t *testing.T) {
 }
 
 func TestAddExistAndRemoveRoute(t *testing.T) {
-	defaultGateway, _, err := getNextHop(netip.MustParseAddr("0.0.0.0"))
+	defaultGateway, _, errGW := getNextHop(netip.MustParseAddr("0.0.0.0"))
 	t.Log("defaultGateway: ", defaultGateway)
-	if err != nil {
-		t.Fatal("shouldn't return error when fetching the gateway: ", err)
+	if errGW != nil {
+		t.Fatal("shouldn't return error when fetching the gateway: ", errGW)
 	}
 	testCases := []struct {
 		name              string
@@ -159,7 +155,7 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
 		{
 			name:           "Should Not Add Route if overlaps with default gateway",
 			prefix:         netip.MustParsePrefix(defaultGateway.String() + "/31"),
-			shouldAddRoute: false,
+			shouldAddRoute: true,
 		},
 		{
 			name:              "Should Add Route if bigger network exists",
@@ -182,11 +178,6 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
 	}
 
 	for n, testCase := range testCases {
-		var buf bytes.Buffer
-		log.SetOutput(&buf)
-		defer func() {
-			log.SetOutput(os.Stderr)
-		}()
 		t.Run(testCase.name, func(t *testing.T) {
 			peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
 			newNet, err := stdnet.NewNet()
@@ -202,34 +193,33 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
 
 			// Prepare the environment
 			if testCase.preExistingPrefix.IsValid() {
-				err := genericAddVPNRoute(testCase.preExistingPrefix, wgInterface.Name())
+				err = genericAddVPNRoute(testCase.preExistingPrefix, wgInterface.Name())
 				require.NoError(t, err, "should not return err when adding pre-existing route")
+				ok, err := existsInRouteTable(testCase.preExistingPrefix)
+				require.NoError(t, err, "should not return err")
+				require.True(t, ok, "route should exist")
 			}
 
 			// Add the route
 			err = genericAddVPNRoute(testCase.prefix, wgInterface.Name())
-			require.NoError(t, err, "should not return err when adding route")
-
-			if testCase.shouldAddRoute {
-				// test if route exists after adding
-				ok, err := existsInRouteTable(testCase.prefix)
-				require.NoError(t, err, "should not return err")
-				require.True(t, ok, "route should exist")
-
-				// remove route again if added
-				err = genericRemoveVPNRoute(testCase.prefix, wgInterface.Name())
-				require.NoError(t, err, "should not return err")
+			if !testCase.shouldAddRoute {
+				require.Error(t, err, "should return err when adding existing route")
+				return
 			}
-
-			// route should either not have been added or should have been removed
-			// In case of already existing route, it should not have been added (but still exist)
+			require.NoError(t, err, "should not return err when adding route")
+			// test if route exists after adding
 			ok, err := existsInRouteTable(testCase.prefix)
-			t.Log("Buffer string: ", buf.String())
 			require.NoError(t, err, "should not return err")
+			require.True(t, ok, "route should exist")
 
-			if !strings.Contains(buf.String(), "because it already exists") {
-				require.False(t, ok, "route should not exist")
-			}
+			// remove route again if added
+			err = genericRemoveVPNRoute(testCase.prefix, wgInterface.Name())
+			require.NoError(t, err, "should not return err")
+
+			// test if route was removed
+			ok, err = existsInRouteTable(testCase.prefix)
+			require.NoError(t, err, "should not return err")
+			require.False(t, ok, "route should not exist")
 		})
 	}
 }
@@ -355,7 +345,7 @@ func setupTestEnv(t *testing.T) {
 
 	// 10.0.0.0/8 route exists in main table and vpn table
 	err = addVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Name())
-	require.NoError(t, err, "addVPNRoute should not return err")
+	require.Error(t, err, "addVPNRoute should return err")
 	t.Cleanup(func() {
 		err = removeVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), wgIface.Name())
 		assert.NoError(t, err, "removeVPNRoute should not return err")