mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-23 07:09:08 +02:00
Add support for IPv6 networks (on Linux clients) (#1459)
* Feat add basic support for IPv6 networks Newly generated networks automatically generate an IPv6 prefix of size 64 within the ULA address range, devices obtain a randomly generated address within this prefix. Currently, this is Linux only and does not yet support all features (routes currently cause an error). * Fix firewall configuration for IPv6 networks * Fix routing configuration for IPv6 networks * Feat provide info on IPv6 support for specific client to mgmt server * Feat allow configuration of IPv6 support through API, improve stability * Feat add IPv6 support to new firewall implementation * Fix peer list item response not containing IPv6 address * Fix nftables breaking on IPv6 address change * Fix build issues for non-linux systems * Fix intermittent disconnections when IPv6 is enabled * Fix test issues and make some minor revisions * Fix some more testing issues * Fix more CI issues due to IPv6 * Fix more testing issues * Add inheritance of IPv6 enablement status from groups * Fix IPv6 events not having associated messages * Address first review comments regarding IPv6 support * Fix IPv6 table being created even when IPv6 is disabled Also improved stability of IPv6 route and firewall handling on client side * Fix IPv6 routes not being removed * Fix DNS IPv6 issues, limit IPv6 nameservers to IPv6 peers * Improve code for IPv6 DNS server selection, add AAAA custom records * Ensure IPv6 routes can only exist for IPv6 routing peers * Fix IPv6 network generation randomness * Fix a bunch of compilation issues and test failures * Replace method calls that are unavailable in Go 1.21 * Fix nil dereference in cleanUpDefaultForwardRules6 * Fix nil pointer dereference when persisting IPv6 network in sqlite * Clean up of client-side code changes for IPv6 * Fix nil dereference in rule mangling and compilation issues * Add a bunch of client-side test cases for IPv6 * Fix IPv6 tests running on unsupported environments * Fix import cycle in tests * Add missing method SupportsIPv6() for windows * Require IPv6 default route for IPv6 tests * Fix panics in routemanager tests on non-linux * Fix some more route manager tests concerning IPv6 * Add some final client-side tests * Add IPv6 tests for management code, small fixes * Fix linting issues * Fix small test suite issues * Fix linter issues and builds on macOS and Windows again * fix builds for iOS because of IPv6 breakage
This commit is contained in:
@@ -280,7 +280,8 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*rou
|
||||
groupListMap := a.getPeerGroups(peerID)
|
||||
for _, peer := range aclPeers {
|
||||
activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID)
|
||||
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
|
||||
addressFamilyFilteredRoutes := a.filterRoutesByIPv6Enabled(activeRoutes, a.GetPeer(peerID).IP6 != nil && peer.IP6 != nil)
|
||||
groupFilteredRoutes := a.filterRoutesByGroups(addressFamilyFilteredRoutes, groupListMap)
|
||||
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
||||
routes = append(routes, filteredRoutes...)
|
||||
}
|
||||
@@ -315,6 +316,20 @@ func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap looku
|
||||
return filteredRoutes
|
||||
}
|
||||
|
||||
// Filters out IPv6 routes if the peer does not support them.
|
||||
func (a *Account) filterRoutesByIPv6Enabled(routes []*route.Route, v6Supported bool) []*route.Route {
|
||||
if v6Supported {
|
||||
return routes
|
||||
}
|
||||
var filteredRoutes []*route.Route
|
||||
for _, rt := range routes {
|
||||
if rt.Network.Addr().Is4() {
|
||||
filteredRoutes = append(filteredRoutes, rt)
|
||||
}
|
||||
}
|
||||
return filteredRoutes
|
||||
}
|
||||
|
||||
// getRoutingPeerRoutes returns the enabled and disabled lists of routes that the given routing peer serves
|
||||
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
||||
// If the given is not a routing peer, then the lists are empty.
|
||||
@@ -348,6 +363,10 @@ func (a *Account) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Ro
|
||||
}
|
||||
|
||||
for _, r := range a.Routes {
|
||||
// Skip IPv6 routes if IPv6 is currently not enabled.
|
||||
if peer.IP6 == nil && r.NetworkType == route.IPv6Network {
|
||||
continue
|
||||
}
|
||||
for _, groupID := range r.PeerGroups {
|
||||
group := a.GetGroup(groupID)
|
||||
if group == nil {
|
||||
@@ -429,7 +448,7 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string, validatedPeersMap
|
||||
|
||||
if dnsManagementStatus {
|
||||
var zones []nbdns.CustomZone
|
||||
peersCustomZone := getPeersCustomZone(a, dnsDomain)
|
||||
peersCustomZone := getPeersCustomZone(a, dnsDomain, peer.IP6 != nil)
|
||||
if peersCustomZone.Domain != "" {
|
||||
zones = append(zones, peersCustomZone)
|
||||
}
|
||||
@@ -665,6 +684,17 @@ func (a *Account) getTakenIPs() []net.IP {
|
||||
return takenIps
|
||||
}
|
||||
|
||||
func (a *Account) getTakenIP6s() []net.IP {
|
||||
var takenIps []net.IP
|
||||
for _, existingPeer := range a.Peers {
|
||||
if existingPeer.IP6 != nil {
|
||||
takenIps = append(takenIps, *existingPeer.IP6)
|
||||
}
|
||||
}
|
||||
|
||||
return takenIps
|
||||
}
|
||||
|
||||
func (a *Account) getPeerDNSLabels() lookupMap {
|
||||
existingLabels := make(lookupMap)
|
||||
for _, peer := range a.Peers {
|
||||
@@ -1006,7 +1036,6 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
|
||||
}
|
||||
|
||||
updatedAccount := account.UpdateSettings(newSettings)
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2015,7 +2044,7 @@ func addAllGroup(account *Account) error {
|
||||
func newAccountWithId(accountID, userID, domain string) *Account {
|
||||
log.Debugf("creating new account")
|
||||
|
||||
network := NewNetwork()
|
||||
network := NewNetwork(true)
|
||||
peers := make(map[string]*nbpeer.Peer)
|
||||
users := make(map[string]*User)
|
||||
routes := make(map[route.ID]*route.Route)
|
||||
|
||||
@@ -69,14 +69,15 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac
|
||||
Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=",
|
||||
Name: "test-host@netbird.io",
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -964,78 +965,136 @@ func TestAccountManager_DeleteAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAccountManager_AddPeer(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
peerIPv6Supported bool
|
||||
allGroupIPv6Enabled bool
|
||||
}{
|
||||
{
|
||||
name: "Peer and Group IPv6 enabled",
|
||||
peerIPv6Supported: true,
|
||||
allGroupIPv6Enabled: true,
|
||||
},
|
||||
{
|
||||
name: "Peer IPv6 enabled, Group IPv6 disabled",
|
||||
peerIPv6Supported: true,
|
||||
allGroupIPv6Enabled: false,
|
||||
},
|
||||
{
|
||||
name: "Peer IPv6 disabled, Group IPv6 enabled",
|
||||
peerIPv6Supported: false,
|
||||
allGroupIPv6Enabled: true,
|
||||
},
|
||||
{
|
||||
name: "Peer and Group IPv6 disabled",
|
||||
peerIPv6Supported: false,
|
||||
allGroupIPv6Enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
userID := "testingUser"
|
||||
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
for _, c := range testCases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
userID := "testingUser"
|
||||
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serial := account.Network.CurrentSerial() // should be 0
|
||||
|
||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
if err != nil {
|
||||
t.Fatal("error creating setup key")
|
||||
return
|
||||
}
|
||||
|
||||
if account.Network.Serial != 0 {
|
||||
t.Errorf("expecting account network to have an initial Serial=0")
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.allGroupIPv6Enabled {
|
||||
unlock := manager.Store.AcquireAccountWriteLock(account.Id)
|
||||
groupAll, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
groupAll.IPv6Enabled = true
|
||||
err = manager.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
unlock()
|
||||
}
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedSetupKey := setupKey.Key
|
||||
|
||||
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey, Ipv6Supported: c.peerIPv6Supported},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if peer.Key != expectedPeerKey {
|
||||
t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key)
|
||||
}
|
||||
|
||||
if !account.Network.Net.Contains(peer.IP) {
|
||||
t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String())
|
||||
}
|
||||
|
||||
if peer.SetupKey != expectedSetupKey {
|
||||
t.Errorf("expecting just added peer to have SetupKey = %s, got %s", expectedSetupKey, peer.SetupKey)
|
||||
}
|
||||
|
||||
if account.Network.CurrentSerial() != 1 {
|
||||
t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial())
|
||||
}
|
||||
ev := getEvent(t, account.Id, manager, activity.PeerAddedWithSetupKey)
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, peer.Name, ev.Meta["name"])
|
||||
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
||||
assert.Equal(t, setupKey.Id, ev.InitiatorID)
|
||||
assert.Equal(t, peer.ID, ev.TargetID)
|
||||
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
||||
assert.Equal(t, peer.V6Setting, nbpeer.V6Auto)
|
||||
if c.peerIPv6Supported && c.allGroupIPv6Enabled {
|
||||
assert.NotNil(t, peer.IP6)
|
||||
} else {
|
||||
assert.Nil(t, peer.IP6)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
serial := account.Network.CurrentSerial() // should be 0
|
||||
|
||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
if err != nil {
|
||||
t.Fatal("error creating setup key")
|
||||
return
|
||||
}
|
||||
|
||||
if account.Network.Serial != 0 {
|
||||
t.Errorf("expecting account network to have an initial Serial=0")
|
||||
return
|
||||
}
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedSetupKey := setupKey.Key
|
||||
|
||||
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if peer.Key != expectedPeerKey {
|
||||
t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key)
|
||||
}
|
||||
|
||||
if !account.Network.Net.Contains(peer.IP) {
|
||||
t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String())
|
||||
}
|
||||
|
||||
if peer.SetupKey != expectedSetupKey {
|
||||
t.Errorf("expecting just added peer to have SetupKey = %s, got %s", expectedSetupKey, peer.SetupKey)
|
||||
}
|
||||
|
||||
if account.Network.CurrentSerial() != 1 {
|
||||
t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial())
|
||||
}
|
||||
ev := getEvent(t, account.Id, manager, activity.PeerAddedWithSetupKey)
|
||||
|
||||
assert.NotNil(t, ev)
|
||||
assert.Equal(t, account.Id, ev.AccountID)
|
||||
assert.Equal(t, peer.Name, ev.Meta["name"])
|
||||
assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"])
|
||||
assert.Equal(t, setupKey.Id, ev.InitiatorID)
|
||||
assert.Equal(t, peer.ID, ev.TargetID)
|
||||
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
|
||||
}
|
||||
|
||||
func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
@@ -1455,9 +1514,22 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, prefix3, err := route.ParseNetwork("2001:db8:1234:5678::/64")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, prefix4, err := route.ParseNetwork("2001:db8:1234:6789::/64")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2IP6 := net.ParseIP("2001:db8:abcd:1234::12")
|
||||
account := &Account{
|
||||
Peers: map[string]*nbpeer.Peer{
|
||||
"peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
||||
"peer-1": {
|
||||
ID: "peer-1", Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"},
|
||||
},
|
||||
"peer-2": {ID: "peer-2", Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux", Ipv6Supported: true}, IP6: &peer2IP6},
|
||||
"peer-3": {ID: "peer-3", Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}},
|
||||
},
|
||||
Groups: map[string]*group.Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
||||
Routes: map[route.ID]*route.Route{
|
||||
@@ -1467,7 +1539,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
NetID: "network-1",
|
||||
Description: "network-1",
|
||||
Peer: "peer-1",
|
||||
NetworkType: 0,
|
||||
NetworkType: route.IPv4Network,
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
@@ -1479,7 +1551,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
NetID: "network-2",
|
||||
Description: "network-2",
|
||||
Peer: "peer-2",
|
||||
NetworkType: 0,
|
||||
NetworkType: route.IPv4Network,
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
@@ -1491,7 +1563,31 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
NetID: "network-1",
|
||||
Description: "network-1",
|
||||
Peer: "peer-2",
|
||||
NetworkType: 0,
|
||||
NetworkType: route.IPv4Network,
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
Groups: []string{"group1"},
|
||||
},
|
||||
"route-4": {
|
||||
ID: "route-4",
|
||||
Network: prefix3,
|
||||
NetID: "network-3",
|
||||
Description: "network-3",
|
||||
Peer: "peer-1",
|
||||
NetworkType: route.IPv6Network,
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
Groups: []string{"group1"},
|
||||
},
|
||||
"route-5": {
|
||||
ID: "route-5",
|
||||
Network: prefix4,
|
||||
NetID: "network-4",
|
||||
Description: "network-4",
|
||||
Peer: "peer-2",
|
||||
NetworkType: route.IPv6Network,
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
@@ -1500,17 +1596,29 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
||||
routes := account.getRoutesToSync("peer-1", []*nbpeer.Peer{{ID: "peer-2", Key: "peer-2"}, {ID: "peer-3", Key: "peer-3"}})
|
||||
|
||||
assert.Len(t, routes, 2)
|
||||
routeIDs := make(map[route.ID]struct{}, 2)
|
||||
for _, r := range routes {
|
||||
routeIDs[r.ID] = struct{}{}
|
||||
}
|
||||
assert.Contains(t, routeIDs, route.ID("route-1"))
|
||||
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||
|
||||
routes = account.getRoutesToSync("peer-2", []*nbpeer.Peer{{ID: "peer-1", Key: "peer-1"}, {ID: "peer-3", Key: "peer-3"}})
|
||||
|
||||
assert.Len(t, routes, 3)
|
||||
|
||||
routeIDs = make(map[route.ID]struct{}, 2)
|
||||
for _, r := range routes {
|
||||
routeIDs[r.ID] = struct{}{}
|
||||
}
|
||||
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||
assert.Contains(t, routeIDs, route.ID("route-3"))
|
||||
assert.Contains(t, routeIDs, route.ID("route-5"))
|
||||
|
||||
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
||||
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{ID: "peer-1", Key: "peer-1"}, {ID: "peer-2", Key: "peer-2"}})
|
||||
|
||||
assert.Len(t, emptyRoutes, 0)
|
||||
}
|
||||
|
||||
@@ -139,6 +139,14 @@ const (
|
||||
PostureCheckUpdated Activity = 61
|
||||
// PostureCheckDeleted indicates that the user deleted a posture check
|
||||
PostureCheckDeleted Activity = 62
|
||||
// PeerIPv6Enabled indicates that a user enabled IPv6 for a peer
|
||||
PeerIPv6Enabled Activity = 63
|
||||
// PeerIPv6Disabled indicates that a user disabled IPv6 for a peer
|
||||
PeerIPv6Disabled Activity = 64
|
||||
// PeerIPv6InheritEnabled indicates that IPv6 was enabled for a peer due to a change in group memberships.
|
||||
PeerIPv6InheritEnabled Activity = 65
|
||||
// PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships.
|
||||
PeerIPv6InheritDisabled Activity = 66
|
||||
)
|
||||
|
||||
var activityMap = map[Activity]Code{
|
||||
@@ -205,6 +213,10 @@ var activityMap = map[Activity]Code{
|
||||
PostureCheckCreated: {"Posture check created", "posture.check.created"},
|
||||
PostureCheckUpdated: {"Posture check updated", "posture.check.updated"},
|
||||
PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"},
|
||||
PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.ipv6.manual_enable"},
|
||||
PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.ipv6.manual_disable"},
|
||||
PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.ipv6.inherit_disable"},
|
||||
PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.ipv6.inherit_enable"},
|
||||
}
|
||||
|
||||
// StringCode returns a string code of the activity
|
||||
|
||||
@@ -149,7 +149,7 @@ func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
||||
return protoUpdate
|
||||
}
|
||||
|
||||
func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
func getPeersCustomZone(account *Account, dnsDomain string, enableIPv6 bool) nbdns.CustomZone {
|
||||
if dnsDomain == "" {
|
||||
log.Errorf("no dns domain is set, returning empty zone")
|
||||
return nbdns.CustomZone{}
|
||||
@@ -172,6 +172,16 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
TTL: defaultTTL,
|
||||
RData: peer.IP.String(),
|
||||
})
|
||||
|
||||
if peer.IP6 != nil && enableIPv6 {
|
||||
customZone.Records = append(customZone.Records, nbdns.SimpleRecord{
|
||||
Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain),
|
||||
Type: int(dns.TypeAAAA),
|
||||
Class: nbdns.DefaultClass,
|
||||
TTL: defaultTTL,
|
||||
RData: peer.IP6.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return customZone
|
||||
@@ -179,6 +189,7 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
|
||||
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
||||
groupList := account.getPeerGroups(peerID)
|
||||
peer := account.GetPeer(peerID)
|
||||
|
||||
var peerNSGroups []*nbdns.NameServerGroup
|
||||
|
||||
@@ -189,8 +200,18 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
||||
for _, gID := range nsGroup.Groups {
|
||||
_, found := groupList[gID]
|
||||
if found {
|
||||
if !peerIsNameserver(account.GetPeer(peerID), nsGroup) {
|
||||
peerNSGroups = append(peerNSGroups, nsGroup.Copy())
|
||||
if !peerIsNameserver(peer, nsGroup) {
|
||||
filteredNsGroup := nsGroup.Copy()
|
||||
var newNameserverList []nbdns.NameServer
|
||||
for _, nameserver := range filteredNsGroup.NameServers {
|
||||
if nameserver.IP.Is4() || peer.IP6 != nil {
|
||||
newNameserverList = append(newNameserverList, nameserver)
|
||||
}
|
||||
}
|
||||
if len(newNameserverList) > 0 {
|
||||
filteredNsGroup.NameServers = newNameserverList
|
||||
peerNSGroups = append(peerNSGroups, filteredNsGroup)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
dnsAdminUserID = "testingAdminUser"
|
||||
dnsRegularUserID = "testingRegularUser"
|
||||
dnsNSGroup1 = "ns1"
|
||||
dnsNSGroup2 = "ns2"
|
||||
)
|
||||
|
||||
func TestGetDNSSettings(t *testing.T) {
|
||||
@@ -184,7 +185,7 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2AccountDNSConfig.DNSConfig.CustomZones, 1, "DNS config should have one custom zone for peers not in the disabled group")
|
||||
require.True(t, peer2AccountDNSConfig.DNSConfig.ServiceEnable, "DNS config should have DNS service enabled for peers not in the disabled group")
|
||||
require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 1, "updated DNS config should have 1 nameserver groups since peer 2 is part of the group All")
|
||||
require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 2, "updated DNS config should have 2 nameserver groups since peer 2 is part of the group All and supports IPv6")
|
||||
}
|
||||
|
||||
func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
@@ -215,14 +216,15 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
||||
Key: dnsPeer1Key,
|
||||
Name: "test-host1@netbird.io",
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host1@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host1@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: false,
|
||||
},
|
||||
DNSLabel: dnsPeer1Key,
|
||||
}
|
||||
@@ -230,16 +232,18 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
||||
Key: dnsPeer2Key,
|
||||
Name: "test-host2@netbird.io",
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host2@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host2@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
DNSLabel: dnsPeer2Key,
|
||||
V6Setting: nbpeer.V6Enabled,
|
||||
DNSLabel: dnsPeer2Key,
|
||||
}
|
||||
|
||||
domain := "example.com"
|
||||
@@ -312,6 +316,20 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
||||
Groups: []string{allGroup.ID},
|
||||
}
|
||||
|
||||
account.NameServerGroups[dnsNSGroup2] = &dns.NameServerGroup{
|
||||
ID: dnsNSGroup2,
|
||||
Name: "ns-group-2",
|
||||
NameServers: []dns.NameServer{{
|
||||
IP: netip.MustParseAddr("2001:4860:4860:0:0:0:0:8888"), // Google DNS
|
||||
NSType: dns.UDPNameServerType,
|
||||
Port: dns.DefaultDNSPort,
|
||||
}},
|
||||
Primary: false,
|
||||
Domains: []string{"example.com"},
|
||||
Enabled: true,
|
||||
Groups: []string{allGroup.ID},
|
||||
}
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -149,6 +149,35 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
||||
oldGroup, exists := account.Groups[newGroup.ID]
|
||||
account.Groups[newGroup.ID] = newGroup
|
||||
|
||||
// Determine peer difference for group.
|
||||
addedPeers := make([]string, 0)
|
||||
removedPeers := make([]string, 0)
|
||||
if exists {
|
||||
addedPeers = difference(newGroup.Peers, oldGroup.Peers)
|
||||
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
|
||||
} else {
|
||||
addedPeers = append(addedPeers, newGroup.Peers...)
|
||||
}
|
||||
|
||||
// Need to check whether IPv6 status has changed for all potentially affected peers.
|
||||
peersToUpdate := make([]string, 0)
|
||||
// If group previously had IPv6 enabled, need to check all old peers for changes in IPv6 status.
|
||||
if exists && oldGroup.IPv6Enabled {
|
||||
peersToUpdate = removedPeers
|
||||
}
|
||||
// If group IPv6 status changed, need to check all current peers, if it did not, but IPv6 is enabled, only check
|
||||
// added peers, otherwise check no peers (as group can not affect IPv6 state).
|
||||
if exists && oldGroup.IPv6Enabled != newGroup.IPv6Enabled {
|
||||
peersToUpdate = append(peersToUpdate, newGroup.Peers...)
|
||||
} else if newGroup.IPv6Enabled {
|
||||
peersToUpdate = append(peersToUpdate, addedPeers...)
|
||||
}
|
||||
|
||||
_, err = am.updatePeerIPv6Status(account, userID, newGroup, peersToUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
@@ -158,13 +187,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
||||
|
||||
// the following snippet tracks the activity and stores the group events in the event store.
|
||||
// It has to happen after all the operations have been successfully performed.
|
||||
addedPeers := make([]string, 0)
|
||||
removedPeers := make([]string, 0)
|
||||
if exists {
|
||||
addedPeers = difference(newGroup.Peers, oldGroup.Peers)
|
||||
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
|
||||
} else {
|
||||
addedPeers = append(addedPeers, newGroup.Peers...)
|
||||
if !exists {
|
||||
am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
|
||||
}
|
||||
|
||||
@@ -314,6 +337,14 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
||||
|
||||
delete(account.Groups, groupID)
|
||||
|
||||
// Update IPv6 status of all group members if necessary.
|
||||
if g.IPv6Enabled {
|
||||
_, err = am.updatePeerIPv6Status(account, userId, g, g.Peers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
@@ -345,6 +376,7 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*nbgroup.Group,
|
||||
}
|
||||
|
||||
// GroupAddPeer appends peer to the group
|
||||
// TODO Question for devs: Is this method dead code? I can't seem to find any usages outside of tests...
|
||||
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
defer unlock()
|
||||
@@ -368,6 +400,14 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
||||
}
|
||||
if add {
|
||||
group.Peers = append(group.Peers, peerID)
|
||||
|
||||
if group.IPv6Enabled {
|
||||
// Update IPv6 status of added group member.
|
||||
_, err = am.updatePeerIPv6Status(account, "", group, []string{peerID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
@@ -381,6 +421,7 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
||||
}
|
||||
|
||||
// GroupDeletePeer removes peer from the group
|
||||
// TODO Question for devs: Same as above, this seems like dead code
|
||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
defer unlock()
|
||||
@@ -399,6 +440,15 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri
|
||||
for i, itemID := range group.Peers {
|
||||
if itemID == peerID {
|
||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||
|
||||
if group.IPv6Enabled {
|
||||
// Update IPv6 status of deleted group member.
|
||||
_, err = am.updatePeerIPv6Status(account, "", group, []string{peerID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -409,3 +459,24 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) updatePeerIPv6Status(account *Account, userID string, group *nbgroup.Group, peersToUpdate []string) (bool, error) {
|
||||
updated := false
|
||||
for _, peer := range peersToUpdate {
|
||||
peerObj := account.GetPeer(peer)
|
||||
update, err := am.DeterminePeerV6(account, peerObj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if update {
|
||||
updated = true
|
||||
account.UpdatePeer(peerObj)
|
||||
if peerObj.IP6 != nil {
|
||||
am.StoreEvent(userID, group.ID, account.Id, activity.PeerIPv6InheritEnabled, group.EventMeta())
|
||||
} else {
|
||||
am.StoreEvent(userID, group.ID, account.Id, activity.PeerIPv6InheritDisabled, group.EventMeta())
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ type Group struct {
|
||||
// Peers list of the group
|
||||
Peers []string `gorm:"serializer:json"`
|
||||
|
||||
IPv6Enabled bool
|
||||
|
||||
IntegrationReference integration_reference.IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
||||
}
|
||||
|
||||
@@ -38,6 +40,7 @@ func (g *Group) Copy() *Group {
|
||||
ID: g.ID,
|
||||
Name: g.Name,
|
||||
Issued: g.Issued,
|
||||
IPv6Enabled: g.IPv6Enabled,
|
||||
Peers: make([]string, len(g.Peers)),
|
||||
IntegrationReference: g.IntegrationReference,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
@@ -12,6 +14,8 @@ import (
|
||||
|
||||
const (
|
||||
groupAdminUserID = "testingAdminUser"
|
||||
groupPeer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8="
|
||||
groupPeer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI="
|
||||
)
|
||||
|
||||
func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
||||
@@ -20,7 +24,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
account, err := initTestGroupAccount(am)
|
||||
_, account, err := initTestGroupAccount(am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
@@ -55,7 +59,7 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
account, err := initTestGroupAccount(am)
|
||||
_, account, err := initTestGroupAccount(am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
@@ -131,10 +135,137 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
||||
func TestDefaultAccountManager_GroupIPv6Consistency(t *testing.T) {
|
||||
am, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
peers, account, err := initTestGroupAccount(am)
|
||||
peer1Id := peers[0]
|
||||
peer2Id := peers[1]
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
group := account.GetGroup("grp-for-ipv6")
|
||||
|
||||
// First, add one member to the IPv6 group before enabling IPv6.
|
||||
group.Peers = append(group.Peers, peer1Id)
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
require.NoError(t, err, "unable to update group")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to update account")
|
||||
group = account.GetGroup("grp-for-ipv6")
|
||||
require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address if the group doesn't have it enabled.")
|
||||
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address.")
|
||||
|
||||
// Now, enable IPv6.
|
||||
group.IPv6Enabled = true
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
require.NoError(t, err, "unable to update group")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to update account")
|
||||
group = account.GetGroup("grp-for-ipv6")
|
||||
require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as it is not a member of the IPv6-enabled group.")
|
||||
|
||||
// Add the second peer.
|
||||
group.Peers = append(group.Peers, peer2Id)
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
require.NoError(t, err, "unable to update group")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to update account")
|
||||
group = account.GetGroup("grp-for-ipv6")
|
||||
require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||
require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||
|
||||
// Disable IPv6 and simultaneously delete the first peer.
|
||||
group.IPv6Enabled = false
|
||||
group.Peers = group.Peers[1:]
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
require.NoError(t, err, "unable to update group")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to update account")
|
||||
group = account.GetGroup("grp-for-ipv6")
|
||||
require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address as it is not a member of any IPv6-enabled group.")
|
||||
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as the group has IPv6 disabled.")
|
||||
|
||||
// Enable IPv6 and simultaneously add the first peer again.
|
||||
group.IPv6Enabled = true
|
||||
group.Peers = append(group.Peers, peer1Id)
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
require.NoError(t, err, "unable to update group")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to update account")
|
||||
require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||
require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.")
|
||||
|
||||
// Force disable IPv6.
|
||||
peer1 := account.GetPeer(peer1Id)
|
||||
peer2 := account.GetPeer(peer2Id)
|
||||
peer1.V6Setting = nbpeer.V6Disabled
|
||||
peer2.V6Setting = nbpeer.V6Disabled
|
||||
_, err = am.UpdatePeer(account.Id, groupAdminUserID, peer1)
|
||||
require.NoError(t, err, "unable to update peer1")
|
||||
_, err = am.UpdatePeer(account.Id, groupAdminUserID, peer2)
|
||||
require.NoError(t, err, "unable to update peer2")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to fetch updated account")
|
||||
group = account.GetGroup("grp-for-ipv6")
|
||||
require.Nil(t, account.GetPeer(peer1Id).IP6, "peer1 should not have an IPv6 address as it is force disabled.")
|
||||
require.Nil(t, account.GetPeer(peer2Id).IP6, "peer2 should not have an IPv6 address as it is force disabled.")
|
||||
|
||||
// Delete Group.
|
||||
err = am.DeleteGroup(account.Id, groupAdminUserID, group.ID)
|
||||
require.NoError(t, err, "unable to delete group")
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to update account")
|
||||
group = account.GetGroup("grp-for-ipv6")
|
||||
require.Nil(t, group, "Group should no longer exist.")
|
||||
require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address as the only IPv6-enabled group was deleted.")
|
||||
require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as the only IPv6-enabled group was deleted.")
|
||||
}
|
||||
|
||||
func initTestGroupAccount(am *DefaultAccountManager) ([]string, *Account, error) {
|
||||
accountID := "testingAcc"
|
||||
domain := "example.com"
|
||||
|
||||
peer1 := &nbpeer.Peer{
|
||||
Key: peer1Key,
|
||||
Name: "peer1",
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host1@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
V6Setting: nbpeer.V6Auto,
|
||||
DNSLabel: groupPeer1Key,
|
||||
}
|
||||
peer2 := &nbpeer.Peer{
|
||||
Key: peer2Key,
|
||||
Name: "peer2",
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host2@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
V6Setting: nbpeer.V6Auto,
|
||||
DNSLabel: groupPeer2Key,
|
||||
}
|
||||
|
||||
groupForRoute := &nbgroup.Group{
|
||||
ID: "grp-for-route",
|
||||
AccountID: "account-id",
|
||||
@@ -191,6 +322,14 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
||||
Peers: make([]string, 0),
|
||||
}
|
||||
|
||||
groupForIPv6 := &nbgroup.Group{
|
||||
ID: "grp-for-ipv6",
|
||||
AccountID: "account-id",
|
||||
Name: "Group for IPv6",
|
||||
Issued: nbgroup.GroupIssuedAPI,
|
||||
Peers: make([]string, 0),
|
||||
}
|
||||
|
||||
routeResource := &route.Route{
|
||||
ID: "example route",
|
||||
Groups: []string{groupForRoute.ID},
|
||||
@@ -235,7 +374,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
||||
|
||||
err := am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute)
|
||||
@@ -245,6 +384,11 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForUsers)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForIntegration)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForIPv6)
|
||||
peer1, _, _ = am.AddPeer(setupKey.Key, user.Id, peer1)
|
||||
peer2, _, _ = am.AddPeer(setupKey.Key, user.Id, peer2)
|
||||
|
||||
return am.Store.GetAccount(account.Id)
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
|
||||
return []string{peer1.ID, peer2.ID}, account, err
|
||||
}
|
||||
|
||||
@@ -287,6 +287,7 @@ func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
|
||||
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
|
||||
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
|
||||
},
|
||||
Ipv6Supported: loginReq.GetMeta().GetIpv6Supported(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,21 +456,31 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
||||
|
||||
func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig {
|
||||
netmask, _ := network.Net.Mask.Size()
|
||||
address6 := ""
|
||||
if network.Net6 != nil && peer.IP6 != nil {
|
||||
netmask6, _ := network.Net6.Mask.Size()
|
||||
address6 = fmt.Sprintf("%s/%d", peer.IP6.String(), netmask6)
|
||||
}
|
||||
fqdn := peer.FQDN(dnsName)
|
||||
return &proto.PeerConfig{
|
||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
||||
Address6: address6,
|
||||
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||
Fqdn: fqdn,
|
||||
}
|
||||
}
|
||||
|
||||
func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
|
||||
func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string, v6Enabled bool) []*proto.RemotePeerConfig {
|
||||
remotePeers := []*proto.RemotePeerConfig{}
|
||||
for _, rPeer := range peers {
|
||||
fqdn := rPeer.FQDN(dnsName)
|
||||
allowedIps := []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)}
|
||||
if rPeer.IP6 != nil && v6Enabled {
|
||||
allowedIps = append(allowedIps, fmt.Sprintf(AllowedIP6sFormat, *rPeer.IP6))
|
||||
}
|
||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||
WgPubKey: rPeer.Key,
|
||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
||||
AllowedIps: allowedIps,
|
||||
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
|
||||
Fqdn: fqdn,
|
||||
})
|
||||
@@ -482,13 +493,13 @@ func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCred
|
||||
|
||||
pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
|
||||
|
||||
remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName)
|
||||
remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName, peer.IP6 != nil)
|
||||
|
||||
routesUpdate := toProtocolRoutes(networkMap.Routes)
|
||||
|
||||
dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig)
|
||||
|
||||
offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName)
|
||||
offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName, peer.IP6 != nil)
|
||||
|
||||
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
handler := initAccountsTestData(&server.Account{
|
||||
Id: accountID,
|
||||
Domain: "hotmail.com",
|
||||
Network: server.NewNetwork(),
|
||||
Network: server.NewNetwork(true),
|
||||
Users: map[string]*server.User{
|
||||
adminUser.Id: adminUser,
|
||||
},
|
||||
|
||||
@@ -247,10 +247,15 @@ components:
|
||||
description: (Cloud only) Indicates whether peer needs approval
|
||||
type: boolean
|
||||
example: true
|
||||
ipv6_enabled:
|
||||
type: string
|
||||
enum: [enabled, disabled, auto]
|
||||
example: auto
|
||||
required:
|
||||
- name
|
||||
- ssh_enabled
|
||||
- login_expiration_enabled
|
||||
- ipv6_enabled
|
||||
PeerBase:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PeerMinimum'
|
||||
@@ -264,6 +269,10 @@ components:
|
||||
description: Peer's public connection IP address
|
||||
type: string
|
||||
example: 35.64.0.1
|
||||
ip6:
|
||||
description: Peer's IPv6 address
|
||||
type: string
|
||||
example: 2001:db8::0123:4567:890a:bcde
|
||||
connected:
|
||||
description: Peer to Management connection status
|
||||
type: boolean
|
||||
@@ -310,6 +319,15 @@ components:
|
||||
description: Peer's desktop UI version
|
||||
type: string
|
||||
example: 0.14.0
|
||||
ipv6_supported:
|
||||
description: Whether this peer supports IPv6
|
||||
type: boolean
|
||||
example: true
|
||||
ipv6_enabled:
|
||||
description: Whether IPv6 is enabled for this peer.
|
||||
type: string
|
||||
enum: [enabled, disabled, auto]
|
||||
example: auto
|
||||
dns_label:
|
||||
description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||
type: string
|
||||
@@ -352,6 +370,8 @@ components:
|
||||
- kernel_version
|
||||
- last_login
|
||||
- last_seen
|
||||
- ipv6_supported
|
||||
- ipv6_enabled
|
||||
- login_expiration_enabled
|
||||
- login_expired
|
||||
- os
|
||||
@@ -627,8 +647,12 @@ components:
|
||||
items:
|
||||
type: string
|
||||
example: "ch8i4ug6lnn4g9hqv7m1"
|
||||
ipv6_enabled:
|
||||
description: Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- ipv6_enabled
|
||||
Group:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMinimum'
|
||||
@@ -639,8 +663,12 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PeerMinimum'
|
||||
ipv6_enabled:
|
||||
description: Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||
type: boolean
|
||||
required:
|
||||
- peers
|
||||
- ipv6_enabled
|
||||
PolicyRuleMinimum:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -88,12 +88,40 @@ const (
|
||||
NameserverNsTypeUdp NameserverNsType = "udp"
|
||||
)
|
||||
|
||||
// Defines values for PeerIpv6Enabled.
|
||||
const (
|
||||
PeerIpv6EnabledAuto PeerIpv6Enabled = "auto"
|
||||
PeerIpv6EnabledDisabled PeerIpv6Enabled = "disabled"
|
||||
PeerIpv6EnabledEnabled PeerIpv6Enabled = "enabled"
|
||||
)
|
||||
|
||||
// Defines values for PeerBaseIpv6Enabled.
|
||||
const (
|
||||
PeerBaseIpv6EnabledAuto PeerBaseIpv6Enabled = "auto"
|
||||
PeerBaseIpv6EnabledDisabled PeerBaseIpv6Enabled = "disabled"
|
||||
PeerBaseIpv6EnabledEnabled PeerBaseIpv6Enabled = "enabled"
|
||||
)
|
||||
|
||||
// Defines values for PeerBatchIpv6Enabled.
|
||||
const (
|
||||
PeerBatchIpv6EnabledAuto PeerBatchIpv6Enabled = "auto"
|
||||
PeerBatchIpv6EnabledDisabled PeerBatchIpv6Enabled = "disabled"
|
||||
PeerBatchIpv6EnabledEnabled PeerBatchIpv6Enabled = "enabled"
|
||||
)
|
||||
|
||||
// Defines values for PeerNetworkRangeCheckAction.
|
||||
const (
|
||||
PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow"
|
||||
PeerNetworkRangeCheckActionDeny PeerNetworkRangeCheckAction = "deny"
|
||||
)
|
||||
|
||||
// Defines values for PeerRequestIpv6Enabled.
|
||||
const (
|
||||
PeerRequestIpv6EnabledAuto PeerRequestIpv6Enabled = "auto"
|
||||
PeerRequestIpv6EnabledDisabled PeerRequestIpv6Enabled = "disabled"
|
||||
PeerRequestIpv6EnabledEnabled PeerRequestIpv6Enabled = "enabled"
|
||||
)
|
||||
|
||||
// Defines values for PolicyRuleAction.
|
||||
const (
|
||||
PolicyRuleActionAccept PolicyRuleAction = "accept"
|
||||
@@ -307,6 +335,9 @@ type Group struct {
|
||||
// Id Group ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||
Ipv6Enabled bool `json:"ipv6_enabled"`
|
||||
|
||||
// Issued How the group was issued (api, integration, jwt)
|
||||
Issued *GroupIssued `json:"issued,omitempty"`
|
||||
|
||||
@@ -343,6 +374,9 @@ type GroupMinimumIssued string
|
||||
|
||||
// GroupRequest defines model for GroupRequest.
|
||||
type GroupRequest struct {
|
||||
// Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "auto"
|
||||
Ipv6Enabled bool `json:"ipv6_enabled"`
|
||||
|
||||
// Name Group name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -502,6 +536,15 @@ type Peer struct {
|
||||
// Ip Peer's IP address
|
||||
Ip string `json:"ip"`
|
||||
|
||||
// Ip6 Peer's IPv6 address
|
||||
Ip6 *string `json:"ip6,omitempty"`
|
||||
|
||||
// Ipv6Enabled Whether IPv6 is enabled for this peer.
|
||||
Ipv6Enabled PeerIpv6Enabled `json:"ipv6_enabled"`
|
||||
|
||||
// Ipv6Supported Whether this peer supports IPv6
|
||||
Ipv6Supported bool `json:"ipv6_supported"`
|
||||
|
||||
// KernelVersion Peer's operating system kernel version
|
||||
KernelVersion string `json:"kernel_version"`
|
||||
|
||||
@@ -539,6 +582,9 @@ type Peer struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// PeerIpv6Enabled Whether IPv6 is enabled for this peer.
|
||||
type PeerIpv6Enabled string
|
||||
|
||||
// PeerBase defines model for PeerBase.
|
||||
type PeerBase struct {
|
||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||
@@ -574,6 +620,15 @@ type PeerBase struct {
|
||||
// Ip Peer's IP address
|
||||
Ip string `json:"ip"`
|
||||
|
||||
// Ip6 Peer's IPv6 address
|
||||
Ip6 *string `json:"ip6,omitempty"`
|
||||
|
||||
// Ipv6Enabled Whether IPv6 is enabled for this peer.
|
||||
Ipv6Enabled PeerBaseIpv6Enabled `json:"ipv6_enabled"`
|
||||
|
||||
// Ipv6Supported Whether this peer supports IPv6
|
||||
Ipv6Supported bool `json:"ipv6_supported"`
|
||||
|
||||
// KernelVersion Peer's operating system kernel version
|
||||
KernelVersion string `json:"kernel_version"`
|
||||
|
||||
@@ -611,6 +666,9 @@ type PeerBase struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// PeerBaseIpv6Enabled Whether IPv6 is enabled for this peer.
|
||||
type PeerBaseIpv6Enabled string
|
||||
|
||||
// PeerBatch defines model for PeerBatch.
|
||||
type PeerBatch struct {
|
||||
// AccessiblePeersCount Number of accessible peers
|
||||
@@ -649,6 +707,15 @@ type PeerBatch struct {
|
||||
// Ip Peer's IP address
|
||||
Ip string `json:"ip"`
|
||||
|
||||
// Ip6 Peer's IPv6 address
|
||||
Ip6 *string `json:"ip6,omitempty"`
|
||||
|
||||
// Ipv6Enabled Whether IPv6 is enabled for this peer.
|
||||
Ipv6Enabled PeerBatchIpv6Enabled `json:"ipv6_enabled"`
|
||||
|
||||
// Ipv6Supported Whether this peer supports IPv6
|
||||
Ipv6Supported bool `json:"ipv6_supported"`
|
||||
|
||||
// KernelVersion Peer's operating system kernel version
|
||||
KernelVersion string `json:"kernel_version"`
|
||||
|
||||
@@ -686,6 +753,9 @@ type PeerBatch struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// PeerBatchIpv6Enabled Whether IPv6 is enabled for this peer.
|
||||
type PeerBatchIpv6Enabled string
|
||||
|
||||
// PeerMinimum defines model for PeerMinimum.
|
||||
type PeerMinimum struct {
|
||||
// Id Peer ID
|
||||
@@ -710,12 +780,16 @@ type PeerNetworkRangeCheckAction string
|
||||
// PeerRequest defines model for PeerRequest.
|
||||
type PeerRequest struct {
|
||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
||||
Name string `json:"name"`
|
||||
SshEnabled bool `json:"ssh_enabled"`
|
||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||
Ipv6Enabled PeerRequestIpv6Enabled `json:"ipv6_enabled"`
|
||||
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
||||
Name string `json:"name"`
|
||||
SshEnabled bool `json:"ssh_enabled"`
|
||||
}
|
||||
|
||||
// PeerRequestIpv6Enabled defines model for PeerRequest.Ipv6Enabled.
|
||||
type PeerRequestIpv6Enabled string
|
||||
|
||||
// PersonalAccessToken defines model for PersonalAccessToken.
|
||||
type PersonalAccessToken struct {
|
||||
// CreatedAt Date the token was created
|
||||
|
||||
@@ -3,6 +3,7 @@ package http
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -82,16 +83,6 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
if allGroup.ID == groupID {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PutApiGroupsGroupIdJSONRequestBody
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
@@ -110,12 +101,42 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
peers = *req.Peers
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if allGroup.ID == groupID {
|
||||
if len(peers) != len(allGroup.Peers) || req.Name != allGroup.Name {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||
return
|
||||
}
|
||||
deduplicatedPeers := make(map[string]struct{})
|
||||
for _, peer := range peers {
|
||||
deduplicatedPeers[peer] = struct{}{}
|
||||
}
|
||||
if len(deduplicatedPeers) != len(peers) {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||
return
|
||||
}
|
||||
for peer := range deduplicatedPeers {
|
||||
if slices.Contains(allGroup.Peers, peer) {
|
||||
continue
|
||||
}
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
group := nbgroup.Group{
|
||||
ID: groupID,
|
||||
Name: req.Name,
|
||||
Peers: peers,
|
||||
Issued: eg.Issued,
|
||||
IntegrationReference: eg.IntegrationReference,
|
||||
IPv6Enabled: req.Ipv6Enabled,
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
||||
@@ -246,6 +267,7 @@ func toGroupResponse(account *server.Account, group *nbgroup.Group) *api.Group {
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
Issued: (*api.GroupIssued)(&group.Issued),
|
||||
Ipv6Enabled: group.IPv6Enabled,
|
||||
}
|
||||
|
||||
for _, pid := range group.Peers {
|
||||
|
||||
@@ -85,11 +85,17 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
||||
return
|
||||
}
|
||||
|
||||
v6Status := nbpeer.V6Auto
|
||||
if req.Ipv6Enabled != api.PeerRequestIpv6EnabledAuto {
|
||||
v6Status = nbpeer.V6Status(req.Ipv6Enabled)
|
||||
}
|
||||
|
||||
update := &nbpeer.Peer{
|
||||
ID: peerID,
|
||||
SSHEnabled: req.SshEnabled,
|
||||
Name: req.Name,
|
||||
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
||||
V6Setting: v6Status,
|
||||
}
|
||||
|
||||
if req.ApprovalRequired != nil {
|
||||
@@ -284,11 +290,22 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
||||
osVersion = peer.Meta.Core
|
||||
}
|
||||
|
||||
var ip6 *string
|
||||
if peer.IP6 != nil {
|
||||
ip6string := peer.IP6.String()
|
||||
ip6 = &ip6string
|
||||
}
|
||||
v6Status := api.PeerIpv6EnabledAuto
|
||||
if peer.V6Setting != nbpeer.V6Auto {
|
||||
v6Status = api.PeerIpv6Enabled(peer.V6Setting)
|
||||
}
|
||||
|
||||
return &api.Peer{
|
||||
Id: peer.ID,
|
||||
Name: peer.Name,
|
||||
Ip: peer.IP.String(),
|
||||
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||
Ip6: ip6,
|
||||
Connected: peer.Status.Connected,
|
||||
LastSeen: peer.Status.LastSeen,
|
||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||
@@ -300,6 +317,8 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
||||
Hostname: peer.Meta.Hostname,
|
||||
UserId: peer.UserID,
|
||||
UiVersion: peer.Meta.UIVersion,
|
||||
Ipv6Supported: peer.Meta.Ipv6Supported,
|
||||
Ipv6Enabled: v6Status,
|
||||
DnsLabel: fqdn(peer, dnsDomain),
|
||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||
LastLogin: peer.LastLogin,
|
||||
@@ -317,12 +336,22 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
||||
if osVersion == "" {
|
||||
osVersion = peer.Meta.Core
|
||||
}
|
||||
var ip6 *string
|
||||
if peer.IP6 != nil {
|
||||
ip6string := peer.IP6.String()
|
||||
ip6 = &ip6string
|
||||
}
|
||||
v6Status := api.PeerBatchIpv6EnabledAuto
|
||||
if peer.V6Setting != nbpeer.V6Auto {
|
||||
v6Status = api.PeerBatchIpv6Enabled(peer.V6Setting)
|
||||
}
|
||||
|
||||
return &api.PeerBatch{
|
||||
Id: peer.ID,
|
||||
Name: peer.Name,
|
||||
Ip: peer.IP.String(),
|
||||
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||
Ip6: ip6,
|
||||
Connected: peer.Status.Connected,
|
||||
LastSeen: peer.Status.LastSeen,
|
||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||
@@ -334,6 +363,8 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
||||
Hostname: peer.Meta.Hostname,
|
||||
UserId: peer.UserID,
|
||||
UiVersion: peer.Meta.UIVersion,
|
||||
Ipv6Supported: peer.Meta.Ipv6Supported,
|
||||
Ipv6Enabled: v6Status,
|
||||
DnsLabel: fqdn(peer, dnsDomain),
|
||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||
LastLogin: peer.LastLogin,
|
||||
|
||||
@@ -37,6 +37,12 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.V6Setting == nbpeer.V6Enabled && p.IP6 == nil {
|
||||
ip6 := net.ParseIP("2001:db8::dead:beef")
|
||||
p.IP6 = &ip6
|
||||
} else {
|
||||
p.IP6 = nil
|
||||
}
|
||||
p.SSHEnabled = update.SSHEnabled
|
||||
p.LoginExpirationEnabled = update.LoginExpirationEnabled
|
||||
p.Name = update.Name
|
||||
|
||||
@@ -789,6 +789,7 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: false,
|
||||
},
|
||||
}
|
||||
peer2 := &nbpeer.Peer{
|
||||
@@ -803,6 +804,7 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: false,
|
||||
},
|
||||
}
|
||||
existingNSGroup := nbdns.NameServerGroup{
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"github.com/c-robinson/iplib"
|
||||
"github.com/rs/xid"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/c-robinson/iplib"
|
||||
"github.com/rs/xid"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
@@ -20,11 +20,21 @@ const (
|
||||
SubnetSize = 16
|
||||
// NetSize is a global network size 100.64.0.0/10
|
||||
NetSize = 10
|
||||
// Subnet6Size is the size of an IPv6 subnet (in Bytes, not Bits)
|
||||
Subnet6Size = 8
|
||||
|
||||
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.64.30.1/32)
|
||||
AllowedIPsFormat = "%s/32"
|
||||
|
||||
// AllowedIP6sFormat generates Wireguard AllowedIPs format (e.g. 2001:db8::dead:beef/128)
|
||||
AllowedIP6sFormat = "%s/128"
|
||||
)
|
||||
|
||||
// Global random number generator for IP addresses
|
||||
// Accesses to the RNG must always be protected using rngLock (RNG sources are not thread-safe)
|
||||
var rng = initializeRng()
|
||||
var rngLock = sync.Mutex{}
|
||||
|
||||
type NetworkMap struct {
|
||||
Peers []*nbpeer.Peer
|
||||
Network *Network
|
||||
@@ -37,6 +47,7 @@ type NetworkMap struct {
|
||||
type Network struct {
|
||||
Identifier string `json:"id"`
|
||||
Net net.IPNet `gorm:"serializer:json"`
|
||||
Net6 *net.IPNet `gorm:"serializer:json"` // Can't use gob serializer, as it cannot encode nil values.
|
||||
Dns string
|
||||
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
|
||||
// Used to synchronize state to the client apps.
|
||||
@@ -45,24 +56,54 @@ type Network struct {
|
||||
mu sync.Mutex `json:"-" gorm:"-"`
|
||||
}
|
||||
|
||||
func initializeRng() *rand.Rand {
|
||||
seed := make([]byte, 8)
|
||||
_, err := crand.Read(seed)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
s := rand.NewSource(int64(binary.LittleEndian.Uint64(seed)))
|
||||
return rand.New(s)
|
||||
}
|
||||
|
||||
// NewNetwork creates a new Network initializing it with a Serial=0
|
||||
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
|
||||
func NewNetwork() *Network {
|
||||
func NewNetwork(enableV6 bool) *Network {
|
||||
|
||||
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize)
|
||||
sub, _ := n.Subnet(SubnetSize)
|
||||
rngLock.Lock()
|
||||
intn := rng.Intn(len(sub))
|
||||
rngLock.Unlock()
|
||||
|
||||
s := rand.NewSource(time.Now().Unix())
|
||||
r := rand.New(s)
|
||||
intn := r.Intn(len(sub))
|
||||
var n6 *net.IPNet = nil
|
||||
if enableV6 {
|
||||
n6 = GenerateNetwork6()
|
||||
}
|
||||
|
||||
return &Network{
|
||||
Identifier: xid.New().String(),
|
||||
Net: sub[intn].IPNet,
|
||||
Net6: n6,
|
||||
Dns: "",
|
||||
Serial: 0}
|
||||
}
|
||||
|
||||
func GenerateNetwork6() *net.IPNet {
|
||||
addrbuf := make([]byte, 16)
|
||||
addrbuf[0] = 0xfd
|
||||
addrbuf[1] = 0x00
|
||||
addrbuf[2] = 0xb1
|
||||
addrbuf[3] = 0x4d
|
||||
|
||||
rngLock.Lock()
|
||||
_, _ = rng.Read(addrbuf[4:Subnet6Size])
|
||||
rngLock.Unlock()
|
||||
|
||||
n6 := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet
|
||||
return &n6
|
||||
}
|
||||
|
||||
// IncSerial increments Serial by 1 reflecting that the network state has been changed
|
||||
func (n *Network) IncSerial() {
|
||||
n.mu.Lock()
|
||||
@@ -81,6 +122,7 @@ func (n *Network) Copy() *Network {
|
||||
return &Network{
|
||||
Identifier: n.Identifier,
|
||||
Net: n.Net,
|
||||
Net6: n.Net6,
|
||||
Dns: n.Dns,
|
||||
Serial: n.Serial,
|
||||
}
|
||||
@@ -103,13 +145,38 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
||||
}
|
||||
|
||||
// pick a random IP
|
||||
s := rand.NewSource(time.Now().Unix())
|
||||
r := rand.New(s)
|
||||
intn := r.Intn(len(ips))
|
||||
rngLock.Lock()
|
||||
intn := rng.Intn(len(ips))
|
||||
rngLock.Unlock()
|
||||
|
||||
return ips[intn], nil
|
||||
}
|
||||
|
||||
// AllocatePeerIP6 pics an available IPv6 from an net.IPNet.
|
||||
// This method considers already taken IPs and reuses IPs if there are gaps in takenIps.
|
||||
func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
||||
|
||||
takenIPMap := make(map[string]struct{})
|
||||
takenIPMap[ipNet.IP.String()] = struct{}{}
|
||||
for _, ip := range takenIps {
|
||||
takenIPMap[ip.String()] = struct{}{}
|
||||
}
|
||||
|
||||
maskSize, _ := ipNet.Mask.Size()
|
||||
|
||||
// TODO for small subnet sizes, randomly generating values until we don't get a duplicate is inefficient and could
|
||||
// lead to many loop iterations, using a method similar to IPv4 would be preferable here.
|
||||
|
||||
addrbuf := make(net.IP, 16)
|
||||
copy(addrbuf, ipNet.IP.To16())
|
||||
for duplicate := true; duplicate; _, duplicate = takenIPMap[addrbuf.String()] {
|
||||
rngLock.Lock()
|
||||
_, _ = rng.Read(addrbuf[(maskSize / 8):16])
|
||||
rngLock.Unlock()
|
||||
}
|
||||
return addrbuf, nil
|
||||
}
|
||||
|
||||
// generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list
|
||||
func generateIPs(ipNet *net.IPNet, exclusions map[string]struct{}) ([]net.IP, int) {
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
@@ -8,11 +9,19 @@ import (
|
||||
)
|
||||
|
||||
func TestNewNetwork(t *testing.T) {
|
||||
network := NewNetwork()
|
||||
network := NewNetwork(true)
|
||||
|
||||
// generated net should be a subnet of a larger 100.64.0.0/10 net
|
||||
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}}
|
||||
assert.Equal(t, ipNet.Contains(network.Net.IP), true)
|
||||
assert.True(t, ipNet.Contains(network.Net.IP))
|
||||
|
||||
// generated IPv6 net should be a subnet of the fd00:b14d::/32 prefix.
|
||||
_, ipNet6, err := net.ParseCIDR("fd00:b14d::/32")
|
||||
require.NoError(t, err, "unable to parse IPv6 prefix")
|
||||
assert.True(t, ipNet6.Contains(network.Net6.IP))
|
||||
// IPv6 prefix should be of size /64
|
||||
ones, _ := network.Net6.Mask.Size()
|
||||
assert.Equal(t, ones, 64)
|
||||
}
|
||||
|
||||
func TestAllocatePeerIP(t *testing.T) {
|
||||
@@ -38,6 +47,32 @@ func TestAllocatePeerIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocatePeerIP6(t *testing.T) {
|
||||
_, ipNet, err := net.ParseCIDR("2001:db8:abcd:1234::/64")
|
||||
require.NoError(t, err, "unable to parse IPv6 prefix")
|
||||
var ips []net.IP
|
||||
// Yeah, we better not check all 2^64 possible addresses, just generating a bunch of addresses should hopefully
|
||||
// reveal any possible bugs in the RNG.
|
||||
for i := 0; i < 252; i++ {
|
||||
ip, err := AllocatePeerIP6(*ipNet, ips)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
|
||||
assert.Len(t, ips, 252)
|
||||
|
||||
uniq := make(map[string]struct{})
|
||||
for _, ip := range ips {
|
||||
if _, ok := uniq[ip.String()]; !ok {
|
||||
uniq[ip.String()] = struct{}{}
|
||||
} else {
|
||||
t.Errorf("found duplicate IP %s", ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateIPs(t *testing.T) {
|
||||
ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 255, 0}}
|
||||
ips, ipsLen := generateIPs(&ipNet, map[string]struct{}{"100.64.0.0": {}})
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
nbroute "github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
|
||||
@@ -140,7 +142,66 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated.
|
||||
// Determines the current IPv6 status of the peer (including checks for inheritance) and generates a new or removes an
|
||||
// existing IPv6 address if necessary.
|
||||
// Additionally, disables IPv6 routes if peer no longer has an IPv6 address.
|
||||
// Note that this change does not get persisted here.
|
||||
//
|
||||
// Returns a boolean that indicates whether the peer and/or the account changed and needs to be updated in the data
|
||||
// source.
|
||||
func (am *DefaultAccountManager) DeterminePeerV6(account *Account, peer *nbpeer.Peer) (bool, error) {
|
||||
v6Setting := peer.V6Setting
|
||||
if peer.V6Setting == nbpeer.V6Auto {
|
||||
if peer.Meta.Ipv6Supported {
|
||||
for _, group := range account.Groups {
|
||||
if group.IPv6Enabled && slices.Contains(group.Peers, peer.ID) {
|
||||
v6Setting = nbpeer.V6Enabled
|
||||
break
|
||||
}
|
||||
}
|
||||
if v6Setting == nbpeer.V6Auto {
|
||||
for _, route := range account.Routes {
|
||||
if route.Peer == peer.ID && route.NetworkType == nbroute.IPv6Network {
|
||||
v6Setting = nbpeer.V6Enabled
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v6Setting == nbpeer.V6Auto {
|
||||
v6Setting = nbpeer.V6Disabled
|
||||
}
|
||||
}
|
||||
|
||||
if v6Setting == nbpeer.V6Enabled && peer.IP6 == nil {
|
||||
if !peer.Meta.Ipv6Supported {
|
||||
return false, status.Errorf(status.PreconditionFailed, "failed allocating new IPv6 for peer %s - peer does not support IPv6", peer.Name)
|
||||
}
|
||||
if account.Network.Net6 == nil {
|
||||
account.Network.Net6 = GenerateNetwork6()
|
||||
}
|
||||
v6tmp, err := AllocatePeerIP6(*account.Network.Net6, account.getTakenIP6s())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
peer.IP6 = &v6tmp
|
||||
return true, nil
|
||||
} else if v6Setting == nbpeer.V6Disabled && peer.IP6 != nil {
|
||||
peer.IP6 = nil
|
||||
|
||||
for _, route := range account.Routes {
|
||||
if route.NetworkType == nbroute.IPv6Network {
|
||||
route.Enabled = false
|
||||
account.Routes[route.ID] = route
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, Peer.V6Setting and Peer.LoginExpirationEnabled can be updated.
|
||||
func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
defer unlock()
|
||||
@@ -160,6 +221,20 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if peer.V6Setting != update.V6Setting {
|
||||
peer.V6Setting = update.V6Setting
|
||||
prevV6 := peer.IP6
|
||||
v6StatusChanged, err := am.DeterminePeerV6(account, peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v6StatusChanged && peer.IP6 != nil {
|
||||
am.StoreEvent(userID, peer.IP6.String(), account.Id, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain()))
|
||||
} else if v6StatusChanged && peer.IP6 == nil {
|
||||
am.StoreEvent(userID, prevV6.String(), account.Id, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain()))
|
||||
}
|
||||
}
|
||||
|
||||
if peer.SSHEnabled != update.SSHEnabled {
|
||||
peer.SSHEnabled = update.SSHEnabled
|
||||
event := activity.PeerSSHEnabled
|
||||
@@ -431,6 +506,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
||||
Key: peer.Key,
|
||||
SetupKey: upperKey,
|
||||
IP: nextIp,
|
||||
IP6: nil,
|
||||
Meta: peer.Meta,
|
||||
Name: peer.Meta.Hostname,
|
||||
DNSLabel: newLabel,
|
||||
@@ -443,6 +519,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
||||
LoginExpirationEnabled: addedByUser,
|
||||
Ephemeral: ephemeral,
|
||||
Location: peer.Location,
|
||||
V6Setting: peer.V6Setting, // empty string "" corresponds to "auto"
|
||||
}
|
||||
|
||||
// add peer to 'All' group
|
||||
@@ -475,6 +552,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
||||
|
||||
newPeer = am.integratedPeerValidator.PreparePeer(account.Id, newPeer, account.GetPeerGroupsList(newPeer.ID), account.Settings.Extra)
|
||||
|
||||
_, err = am.DeterminePeerV6(account, newPeer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if addedByUser {
|
||||
user, err := account.FindUser(userID)
|
||||
if err != nil {
|
||||
@@ -676,6 +758,14 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
|
||||
shouldStoreAccount = true
|
||||
}
|
||||
|
||||
updated, err = am.DeterminePeerV6(account, peer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if updated {
|
||||
shouldStoreAccount = true
|
||||
}
|
||||
|
||||
peer, err = am.checkAndUpdatePeerSSHKey(peer, account, login.SSHKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -20,6 +20,8 @@ type Peer struct {
|
||||
SetupKey string
|
||||
// IP address of the Peer
|
||||
IP net.IP `gorm:"serializer:json"`
|
||||
// IPv6 address of the Peer
|
||||
IP6 *net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip6"`
|
||||
// Meta is a Peer system meta data
|
||||
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
||||
// Name is peer's name (machine name)
|
||||
@@ -44,10 +46,23 @@ type Peer struct {
|
||||
CreatedAt time.Time
|
||||
// Indicate ephemeral peer attribute
|
||||
Ephemeral bool
|
||||
// Geo location based on connection IP
|
||||
// Geolocation based on connection IP
|
||||
Location Location `gorm:"embedded;embeddedPrefix:location_"`
|
||||
// Whether IPv6 should be enabled or not.
|
||||
V6Setting V6Status
|
||||
}
|
||||
|
||||
type V6Status string
|
||||
|
||||
const (
|
||||
// Inherit IPv6 settings from groups (=> if one group the peer is a member of has IPv6 enabled, it will be enabled).
|
||||
V6Auto V6Status = ""
|
||||
// Enable IPv6 regardless of group settings, as long as it is supported.
|
||||
V6Enabled V6Status = "enabled"
|
||||
// Disable IPv6 regardless of group settings.
|
||||
V6Disabled V6Status = "disabled"
|
||||
)
|
||||
|
||||
type PeerStatus struct { //nolint:revive
|
||||
// LastSeen is the last time peer was connected to the management service
|
||||
LastSeen time.Time
|
||||
@@ -96,6 +111,7 @@ type PeerSystemMeta struct { //nolint:revive
|
||||
SystemProductName string
|
||||
SystemManufacturer string
|
||||
Environment Environment `gorm:"serializer:json"`
|
||||
Ipv6Supported bool
|
||||
}
|
||||
|
||||
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
||||
@@ -130,7 +146,8 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
||||
p.SystemProductName == other.SystemProductName &&
|
||||
p.SystemManufacturer == other.SystemManufacturer &&
|
||||
p.Environment.Cloud == other.Environment.Cloud &&
|
||||
p.Environment.Platform == other.Environment.Platform
|
||||
p.Environment.Platform == other.Environment.Platform &&
|
||||
p.Ipv6Supported == other.Ipv6Supported
|
||||
}
|
||||
|
||||
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
|
||||
@@ -150,6 +167,7 @@ func (p *Peer) Copy() *Peer {
|
||||
Key: p.Key,
|
||||
SetupKey: p.SetupKey,
|
||||
IP: p.IP,
|
||||
IP6: p.IP6,
|
||||
Meta: p.Meta,
|
||||
Name: p.Name,
|
||||
DNSLabel: p.DNSLabel,
|
||||
@@ -162,6 +180,7 @@ func (p *Peer) Copy() *Peer {
|
||||
CreatedAt: p.CreatedAt,
|
||||
Ephemeral: p.Ephemeral,
|
||||
Location: p.Location,
|
||||
V6Setting: p.V6Setting,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -136,6 +137,112 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_DeterminePeerV6(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
userId := "account_creator"
|
||||
account, err := createAccount(manager, expectedId, userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userId, false)
|
||||
if err != nil {
|
||||
t.Fatal("error creating setup key")
|
||||
return
|
||||
}
|
||||
|
||||
peerKey1, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: peerKey1.PublicKey().String(),
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-peer-1",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
peerKey2, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: peerKey2.PublicKey().String(),
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-peer-2",
|
||||
Ipv6Supported: false,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to fetch updated account")
|
||||
|
||||
// Check if automatic setting defaults to "false".
|
||||
// (Other tests for interactions between the automatic setting and group/route memberships are already covered in
|
||||
// group_test.go and route_test.go)
|
||||
_, err = manager.DeterminePeerV6(account, peer1)
|
||||
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||
_, err = manager.DeterminePeerV6(account, peer2)
|
||||
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||
require.Nil(t, peer1.IP6, "peer1 IPv6 address did not default to nil.")
|
||||
require.Nil(t, peer1.IP6, "peer2 IPv6 address did not default to nil.")
|
||||
|
||||
peer1.V6Setting = nbpeer.V6Disabled
|
||||
peer2.V6Setting = nbpeer.V6Disabled
|
||||
_, err = manager.DeterminePeerV6(account, peer1)
|
||||
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||
_, err = manager.DeterminePeerV6(account, peer2)
|
||||
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||
require.Nil(t, peer1.IP6, "peer1 IPv6 address is not nil even though it is force disabled.")
|
||||
require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is force disabled and unsupported.")
|
||||
|
||||
peer1.V6Setting = nbpeer.V6Enabled
|
||||
peer2.V6Setting = nbpeer.V6Enabled
|
||||
_, err = manager.DeterminePeerV6(account, peer1)
|
||||
require.NoError(t, err, "unable to determine effective peer IPv6 status")
|
||||
_, err = manager.DeterminePeerV6(account, peer2)
|
||||
require.Error(t, err, "determining peer2 IPv6 address should fail as it is force enabled, but unsupported.")
|
||||
require.NotNil(t, peer1.IP6, "peer1 IPv6 address is nil even though it is force enabled.")
|
||||
require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is unsupported.")
|
||||
|
||||
// Test whether disabling IPv6 will disable IPv6 routes.
|
||||
allGroup, err := account.GetGroupAll()
|
||||
require.NoError(t, err, "unable to retrieve all group")
|
||||
route, err := manager.CreateRoute(account.Id, "2001:db8:2345:6789::/64", peer1.ID, make([]string, 0), "testroute", "testnet", false, 9999, []string{allGroup.ID}, true, userID)
|
||||
require.NoError(t, err, "unable to create test IPv6 route")
|
||||
require.True(t, route.Enabled, "created IPv6 test route should be enabled")
|
||||
|
||||
peer1.V6Setting = nbpeer.V6Disabled
|
||||
_, err = manager.UpdatePeer(account.Id, userID, peer1)
|
||||
require.NoError(t, err, "unable to update peer")
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
require.NoError(t, err, "unable to fetch updated account")
|
||||
|
||||
route = account.Routes[route.ID]
|
||||
require.False(t, route.Enabled, "disabling IPv6 for a peer should disable all of its IPv6 routes.")
|
||||
}
|
||||
|
||||
func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
|
||||
// TODO: disable until we start use policy again
|
||||
t.Skip()
|
||||
|
||||
@@ -195,6 +195,8 @@ type FirewallRule struct {
|
||||
// PeerIP of the peer
|
||||
PeerIP string
|
||||
|
||||
PeerIP6 string
|
||||
|
||||
// Direction of the traffic
|
||||
Direction int
|
||||
|
||||
@@ -278,8 +280,14 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*nbpeer.Peer, in
|
||||
peersExists[peer.ID] = struct{}{}
|
||||
}
|
||||
|
||||
ip6 := ""
|
||||
if peer.IP6 != nil {
|
||||
ip6 = peer.IP6.String()
|
||||
}
|
||||
|
||||
fr := FirewallRule{
|
||||
PeerIP: peer.IP.String(),
|
||||
PeerIP6: ip6,
|
||||
Direction: direction,
|
||||
Action: string(rule.Action),
|
||||
Protocol: string(rule.Protocol),
|
||||
@@ -287,6 +295,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*nbpeer.Peer, in
|
||||
|
||||
if isAll {
|
||||
fr.PeerIP = "0.0.0.0"
|
||||
fr.PeerIP6 = "::"
|
||||
}
|
||||
|
||||
ruleID := (rule.ID + fr.PeerIP + strconv.Itoa(direction) +
|
||||
@@ -474,6 +483,7 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule {
|
||||
|
||||
result[i] = &proto.FirewallRule{
|
||||
PeerIP: update[i].PeerIP,
|
||||
PeerIP6: update[i].PeerIP6,
|
||||
Direction: direction,
|
||||
Action: action,
|
||||
Protocol: protocol,
|
||||
|
||||
@@ -14,16 +14,20 @@ import (
|
||||
)
|
||||
|
||||
func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
peerAIP6 := net.ParseIP("2001:db8:abcd:1234::2")
|
||||
peerBIP6 := net.ParseIP("2001:db8:abcd:1234::3")
|
||||
account := &Account{
|
||||
Peers: map[string]*nbpeer.Peer{
|
||||
"peerA": {
|
||||
ID: "peerA",
|
||||
IP: net.ParseIP("100.65.14.88"),
|
||||
IP6: &peerAIP6,
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
},
|
||||
"peerB": {
|
||||
ID: "peerB",
|
||||
IP: net.ParseIP("100.65.80.39"),
|
||||
IP6: &peerBIP6,
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
},
|
||||
"peerC": {
|
||||
@@ -161,6 +165,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
epectedFirewallRules := []*FirewallRule{
|
||||
{
|
||||
PeerIP: "0.0.0.0",
|
||||
PeerIP6: "::",
|
||||
Direction: firewallRuleDirectionIN,
|
||||
Action: "accept",
|
||||
Protocol: "all",
|
||||
@@ -168,6 +173,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
},
|
||||
{
|
||||
PeerIP: "0.0.0.0",
|
||||
PeerIP6: "::",
|
||||
Direction: firewallRuleDirectionOUT,
|
||||
Action: "accept",
|
||||
Protocol: "all",
|
||||
@@ -175,6 +181,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
},
|
||||
{
|
||||
PeerIP: "100.65.14.88",
|
||||
PeerIP6: "2001:db8:abcd:1234::2",
|
||||
Direction: firewallRuleDirectionIN,
|
||||
Action: "accept",
|
||||
Protocol: "all",
|
||||
@@ -182,6 +189,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
},
|
||||
{
|
||||
PeerIP: "100.65.14.88",
|
||||
PeerIP6: "2001:db8:abcd:1234::2",
|
||||
Direction: firewallRuleDirectionOUT,
|
||||
Action: "accept",
|
||||
Protocol: "all",
|
||||
@@ -678,6 +686,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
||||
expectedFirewallRules := []*FirewallRule{
|
||||
{
|
||||
PeerIP: "0.0.0.0",
|
||||
PeerIP6: "::",
|
||||
Direction: firewallRuleDirectionOUT,
|
||||
Action: "accept",
|
||||
Protocol: "tcp",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"net/netip"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -180,6 +181,25 @@ func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string,
|
||||
|
||||
account.Routes[newRoute.ID] = &newRoute
|
||||
|
||||
// IPv6 route must only be created with IPv6 enabled peers, creating an IPv6 enabled route may enable IPv6 for
|
||||
// peers with V6Setting = Auto.
|
||||
if peerID != "" && prefixType == route.IPv6Network && newRoute.Enabled {
|
||||
peer := account.GetPeer(peerID)
|
||||
if peer.V6Setting == nbpeer.V6Disabled || !peer.Meta.Ipv6Supported {
|
||||
return nil, status.Errorf(
|
||||
status.InvalidArgument,
|
||||
"IPv6 must be enabled for peer %s to be used in route %s",
|
||||
peer.Name, newPrefix.String())
|
||||
} else if peer.IP6 == nil {
|
||||
_, err = am.DeterminePeerV6(account, peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account.UpdatePeer(peer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return nil, err
|
||||
@@ -222,6 +242,16 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time")
|
||||
}
|
||||
|
||||
if routeToSave.Peer != "" {
|
||||
peer := account.GetPeer(routeToSave.Peer)
|
||||
if peer == nil {
|
||||
return status.Errorf(status.InvalidArgument, "provided peer does not exist")
|
||||
}
|
||||
if routeToSave.NetworkType == route.IPv6Network && routeToSave.Enabled && (!peer.Meta.Ipv6Supported || peer.V6Setting == nbpeer.V6Disabled) {
|
||||
return status.Errorf(status.InvalidArgument, "peer with IPv6 disabled can't be used for IPv6 route")
|
||||
}
|
||||
}
|
||||
|
||||
if len(routeToSave.PeerGroups) > 0 {
|
||||
err = validateGroups(routeToSave.PeerGroups, account.Groups)
|
||||
if err != nil {
|
||||
@@ -239,8 +269,49 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave
|
||||
return err
|
||||
}
|
||||
|
||||
oldRoute := account.Routes[routeToSave.ID]
|
||||
|
||||
account.Routes[routeToSave.ID] = routeToSave
|
||||
|
||||
// Check if old peer's IPv6 status needs to be recalculated.
|
||||
// Must happen if route is an IPv6 route, and either:
|
||||
// - The routing peer has changed
|
||||
// - The route has been disabled
|
||||
// - (the route has been enabled) => caught in the next if-block
|
||||
if oldRoute.Peer != "" && routeToSave.NetworkType == route.IPv6Network && ((oldRoute.Enabled && !routeToSave.Enabled) || oldRoute.Peer != routeToSave.Peer) {
|
||||
oldPeer := account.GetPeer(oldRoute.Peer)
|
||||
if oldPeer.V6Setting == nbpeer.V6Auto {
|
||||
changed, err := am.DeterminePeerV6(account, oldPeer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if changed {
|
||||
account.UpdatePeer(oldPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if new peer's IPv6 status needs to be recalculated.
|
||||
// Must happen if route is an IPv6 route, and either:
|
||||
// - The routing peer has changed
|
||||
// - The route has been enabled
|
||||
// - (The route has been disabled) => caught in previous if-block
|
||||
if oldRoute.Peer != "" && routeToSave.NetworkType == route.IPv6Network && routeToSave.Enabled && (!oldRoute.Enabled || oldRoute.Peer != routeToSave.Peer) {
|
||||
newPeer := account.GetPeer(routeToSave.Peer)
|
||||
if newPeer.V6Setting == nbpeer.V6Disabled || !newPeer.Meta.Ipv6Supported {
|
||||
return status.Errorf(
|
||||
status.InvalidArgument,
|
||||
"IPv6 must be enabled for peer %s to be used in route %s",
|
||||
newPeer.Name, routeToSave.Network.String())
|
||||
} else if newPeer.IP6 == nil {
|
||||
_, err = am.DeterminePeerV6(account, newPeer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.UpdatePeer(newPeer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
@@ -269,6 +340,21 @@ func (am *DefaultAccountManager) DeleteRoute(accountID string, routeID route.ID,
|
||||
}
|
||||
delete(account.Routes, routeID)
|
||||
|
||||
// If the route was an IPv6 route, deleting it may update the automatic IPv6 enablement status of its routing peers,
|
||||
// check if this is the case and update accordingly.
|
||||
if routy.Peer != "" && routy.Enabled && routy.NetworkType == route.IPv6Network {
|
||||
oldPeer := account.GetPeer(routy.Peer)
|
||||
if oldPeer.V6Setting == nbpeer.V6Auto {
|
||||
changed, err := am.DeterminePeerV6(account, oldPeer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if changed {
|
||||
account.UpdatePeer(oldPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
|
||||
+252
-45
@@ -319,6 +319,88 @@ func TestCreateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "IPv6 route on peer with disabled IPv6 should fail",
|
||||
inputArgs: input{
|
||||
network: "2001:db8:7654:3210::/64",
|
||||
netID: "NewId",
|
||||
peerKey: peer4ID,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "IPv6 route on peer with unsupported IPv6 should fail",
|
||||
inputArgs: input{
|
||||
network: "2001:db8:7654:3210::/64",
|
||||
netID: "NewId",
|
||||
peerKey: peer5ID,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "IPv6 route on peer with automatic IPv6 setting should succeed",
|
||||
inputArgs: input{
|
||||
network: "2001:db8:7654:3210::/64",
|
||||
netID: "NewId",
|
||||
peerKey: peer1ID,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
Network: netip.MustParsePrefix("2001:db8:7654:3210::/64"),
|
||||
NetworkType: route.IPv6Network,
|
||||
NetID: "NewId",
|
||||
Peer: peer1ID,
|
||||
Description: "",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv6 route on peer with force enabled IPv6 setting should succeed",
|
||||
inputArgs: input{
|
||||
network: "2001:db8:7654:3211::/64",
|
||||
netID: "NewId",
|
||||
peerKey: peer2ID,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
Network: netip.MustParsePrefix("2001:db8:7654:3211::/64"),
|
||||
NetworkType: route.IPv6Network,
|
||||
NetID: "NewId",
|
||||
Peer: peer2ID,
|
||||
Description: "",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
@@ -377,6 +459,8 @@ func TestCreateRoute(t *testing.T) {
|
||||
func TestSaveRoute(t *testing.T) {
|
||||
validPeer := peer2ID
|
||||
validUsedPeer := peer5ID
|
||||
ipv6DisabledPeer := peer4ID
|
||||
ipv6UnsupportedPeer := peer5ID
|
||||
invalidPeer := "nonExisting"
|
||||
validPrefix := netip.MustParsePrefix("192.168.0.0/24")
|
||||
invalidPrefix, _ := netip.ParsePrefix("192.168.0.0/34")
|
||||
@@ -467,7 +551,7 @@ func TestSaveRoute(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Both peer and peers_roup Provided Should Fail",
|
||||
name: "Both peer and peers_group Provided Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -517,6 +601,38 @@ func TestSaveRoute(t *testing.T) {
|
||||
newPeer: &invalidPeer,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "IPv6 disabled host should not be allowed as peer for IPv6 route",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("2001:db8:4321:5678::/64"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv6Network,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &ipv6DisabledPeer,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "IPv6 unsupported host should not be allowed as peer for IPv6 route",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("2001:db8:4321:5678::/64"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv6Network,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &ipv6UnsupportedPeer,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Metric Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
@@ -772,7 +888,7 @@ func TestDeleteRoute(t *testing.T) {
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1Key,
|
||||
Peer: peer1ID,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
@@ -812,6 +928,77 @@ func TestDeleteRoute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouteIPv6Consistency(t *testing.T) {
|
||||
testingRoute := &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("2001:db8:0987:6543::/64"),
|
||||
NetworkType: route.IPv6Network,
|
||||
NetID: existingRouteID,
|
||||
Peer: peer1ID,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: false,
|
||||
Groups: []string{routeGroup1},
|
||||
}
|
||||
|
||||
am, err := createRouterManager(t)
|
||||
if err != nil {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
account, err := initTestRouteAccount(t, am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
account.Routes[testingRoute.ID] = testingRoute
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
t.Error("failed to save account")
|
||||
}
|
||||
|
||||
savedAccount, err := am.Store.GetAccount(account.Id)
|
||||
if err != nil {
|
||||
t.Error("failed to retrieve saved account with error: ", err)
|
||||
}
|
||||
|
||||
testingRoute = savedAccount.Routes[testingRoute.ID]
|
||||
testingRoute.Enabled = true
|
||||
|
||||
err = am.SaveRoute(account.Id, userID, testingRoute)
|
||||
if err != nil {
|
||||
t.Error("failed to save route")
|
||||
}
|
||||
|
||||
savedAccount, err = am.Store.GetAccount(account.Id)
|
||||
if err != nil {
|
||||
t.Error("failed to retrieve saved account with error: ", err)
|
||||
}
|
||||
|
||||
peer := savedAccount.GetPeer(peer1ID)
|
||||
require.NotNil(t, peer.IP6, "peer with enabled IPv6 route in automatic setting must have IPv6 active.")
|
||||
|
||||
err = am.DeleteRoute(account.Id, testingRoute.ID, userID)
|
||||
if err != nil {
|
||||
t.Error("deleting route failed with error: ", err)
|
||||
}
|
||||
|
||||
savedAccount, err = am.Store.GetAccount(account.Id)
|
||||
if err != nil {
|
||||
t.Error("failed to retrieve saved account with error: ", err)
|
||||
}
|
||||
|
||||
_, found := savedAccount.Routes[testingRoute.ID]
|
||||
if found {
|
||||
t.Error("route shouldn't be found after delete")
|
||||
}
|
||||
|
||||
peer = savedAccount.GetPeer(peer1ID)
|
||||
require.Nil(t, peer.IP6, "disabling the only IPv6 route for a peer with automatic IPv6 setting should disable IPv6")
|
||||
}
|
||||
|
||||
func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) {
|
||||
baseRoute := &route.Route{
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -1055,14 +1242,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
Name: "test-host1@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host1@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host1@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
}
|
||||
@@ -1073,24 +1261,32 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = account.getTakenIP6s()
|
||||
peer2IP6, err := AllocatePeerIP6(account.Network.Net, ips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer2 := &nbpeer.Peer{
|
||||
IP: peer2IP,
|
||||
IP6: &peer2IP6,
|
||||
ID: peer2ID,
|
||||
Key: peer2Key,
|
||||
Name: "test-host2@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host2@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host2@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
V6Setting: nbpeer.V6Enabled,
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
}
|
||||
account.Peers[peer2.ID] = peer2
|
||||
|
||||
@@ -1099,24 +1295,32 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = account.getTakenIP6s()
|
||||
peer3IP6, err := AllocatePeerIP6(account.Network.Net, ips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer3 := &nbpeer.Peer{
|
||||
IP: peer3IP,
|
||||
IP6: &peer3IP6,
|
||||
ID: peer3ID,
|
||||
Key: peer3Key,
|
||||
Name: "test-host3@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host3@netbird.io",
|
||||
GoOS: "darwin",
|
||||
Kernel: "Darwin",
|
||||
Core: "13.4.1",
|
||||
Platform: "arm64",
|
||||
OS: "darwin",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host3@netbird.io",
|
||||
GoOS: "darwin",
|
||||
Kernel: "Darwin",
|
||||
Core: "13.4.1",
|
||||
Platform: "arm64",
|
||||
OS: "darwin",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
V6Setting: nbpeer.V6Enabled,
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
}
|
||||
account.Peers[peer3.ID] = peer3
|
||||
|
||||
@@ -1133,14 +1337,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
Name: "test-host4@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host4@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host4@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: false,
|
||||
},
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
}
|
||||
@@ -1159,16 +1364,18 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
Name: "test-host4@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: nbpeer.PeerSystemMeta{
|
||||
Hostname: "test-host4@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Hostname: "test-host4@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
Ipv6Supported: true,
|
||||
},
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
Status: &nbpeer.PeerStatus{},
|
||||
V6Setting: nbpeer.V6Disabled,
|
||||
}
|
||||
account.Peers[peer5.ID] = peer5
|
||||
|
||||
|
||||
Reference in New Issue
Block a user