mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-08 17:59:56 +00:00
[management, client] Add IPv6 overlay support (#5631)
This commit is contained in:
@@ -20,6 +20,9 @@ const (
|
||||
MaxMetric = 9999
|
||||
// MaxNetIDChar Max Network Identifier
|
||||
MaxNetIDChar = 40
|
||||
|
||||
// V6ExitSuffix is appended to a v4 exit node NetID to form its v6 counterpart.
|
||||
V6ExitSuffix = "-v6"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -215,3 +218,61 @@ func ParseNetwork(networkString string) (NetworkType, netip.Prefix, error) {
|
||||
|
||||
return IPv4Network, masked, nil
|
||||
}
|
||||
|
||||
var (
|
||||
v4Default = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||
v6Default = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||
)
|
||||
|
||||
// IsV4DefaultRoute reports whether p is the IPv4 default route (0.0.0.0/0).
|
||||
func IsV4DefaultRoute(p netip.Prefix) bool { return p == v4Default }
|
||||
|
||||
// IsV6DefaultRoute reports whether p is the IPv6 default route (::/0).
|
||||
func IsV6DefaultRoute(p netip.Prefix) bool { return p == v6Default }
|
||||
|
||||
// ExpandV6ExitPairs appends the paired "-v6" exit node NetID for any v4 exit
|
||||
// node (0.0.0.0/0) in ids that has a matching v6 counterpart (::/0) in routesMap.
|
||||
// It modifies and returns the input slice.
|
||||
func ExpandV6ExitPairs(ids []NetID, routesMap map[NetID][]*Route) []NetID {
|
||||
for _, id := range ids {
|
||||
rt, ok := routesMap[id]
|
||||
if !ok || len(rt) == 0 || !IsV4DefaultRoute(rt[0].Network) {
|
||||
continue
|
||||
}
|
||||
v6ID := NetID(string(id) + V6ExitSuffix)
|
||||
if v6Rt, ok := routesMap[v6ID]; ok && len(v6Rt) > 0 && IsV6DefaultRoute(v6Rt[0].Network) {
|
||||
if !slices.Contains(ids, v6ID) {
|
||||
ids = append(ids, v6ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// V6ExitMergeSet scans routesMap and returns the set of v6 exit node NetIDs
|
||||
// that should be hidden from the UI because they are paired with a v4 exit node.
|
||||
// A v6 ID is paired when it has suffix "-v6", its route is ::/0, and the base
|
||||
// name (without "-v6") exists with route 0.0.0.0/0.
|
||||
func V6ExitMergeSet(routesMap map[NetID][]*Route) map[NetID]struct{} {
|
||||
merged := make(map[NetID]struct{})
|
||||
for id, rt := range routesMap {
|
||||
if len(rt) == 0 {
|
||||
continue
|
||||
}
|
||||
name := string(id)
|
||||
if !IsV6DefaultRoute(rt[0].Network) || !strings.HasSuffix(name, V6ExitSuffix) {
|
||||
continue
|
||||
}
|
||||
baseName := NetID(strings.TrimSuffix(name, V6ExitSuffix))
|
||||
if baseRt, ok := routesMap[baseName]; ok && len(baseRt) > 0 && IsV4DefaultRoute(baseRt[0].Network) {
|
||||
merged[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// HasV6ExitPair reports whether id has a paired v6 exit node in the merge set.
|
||||
func HasV6ExitPair(id NetID, v6Merged map[NetID]struct{}) bool {
|
||||
_, ok := v6Merged[NetID(string(id)+"-v6")]
|
||||
return ok
|
||||
}
|
||||
|
||||
108
route/route_test.go
Normal file
108
route/route_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExpandV6ExitPairs(t *testing.T) {
|
||||
v4ExitRoute := &Route{Network: netip.MustParsePrefix("0.0.0.0/0")}
|
||||
v6ExitRoute := &Route{Network: netip.MustParsePrefix("::/0")}
|
||||
regularRoute := &Route{Network: netip.MustParsePrefix("10.0.0.0/8")}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ids []NetID
|
||||
routesMap map[NetID][]*Route
|
||||
expected []NetID
|
||||
}{
|
||||
{
|
||||
name: "v4 exit node with matching v6 pair",
|
||||
ids: []NetID{"exit-node"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"exit-node": {v4ExitRoute},
|
||||
"exit-node-v6": {v6ExitRoute},
|
||||
},
|
||||
expected: []NetID{"exit-node", "exit-node-v6"},
|
||||
},
|
||||
{
|
||||
name: "v4 exit node without v6 pair",
|
||||
ids: []NetID{"exit-node"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"exit-node": {v4ExitRoute},
|
||||
},
|
||||
expected: []NetID{"exit-node"},
|
||||
},
|
||||
{
|
||||
name: "regular route is not expanded",
|
||||
ids: []NetID{"office"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"office": {regularRoute},
|
||||
"office-v6": {v6ExitRoute},
|
||||
},
|
||||
expected: []NetID{"office"},
|
||||
},
|
||||
{
|
||||
name: "v6 already included is not duplicated",
|
||||
ids: []NetID{"exit-node", "exit-node-v6"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"exit-node": {v4ExitRoute},
|
||||
"exit-node-v6": {v6ExitRoute},
|
||||
},
|
||||
expected: []NetID{"exit-node", "exit-node-v6"},
|
||||
},
|
||||
{
|
||||
name: "multiple exit nodes expanded independently",
|
||||
ids: []NetID{"exit-a", "exit-b"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"exit-a": {v4ExitRoute},
|
||||
"exit-a-v6": {v6ExitRoute},
|
||||
"exit-b": {v4ExitRoute},
|
||||
"exit-b-v6": {v6ExitRoute},
|
||||
},
|
||||
expected: []NetID{"exit-a", "exit-b", "exit-a-v6", "exit-b-v6"},
|
||||
},
|
||||
{
|
||||
name: "v6 suffix but not exit node network",
|
||||
ids: []NetID{"office"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"office": {regularRoute},
|
||||
"office-v6": {regularRoute},
|
||||
},
|
||||
expected: []NetID{"office"},
|
||||
},
|
||||
{
|
||||
name: "user-chosen name for exit node with v6 pair",
|
||||
ids: []NetID{"my-exit"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"my-exit": {v4ExitRoute},
|
||||
"my-exit-v6": {v6ExitRoute},
|
||||
},
|
||||
expected: []NetID{"my-exit", "my-exit-v6"},
|
||||
},
|
||||
{
|
||||
name: "real-world management-generated IDs",
|
||||
ids: []NetID{"0.0.0.0/0"},
|
||||
routesMap: map[NetID][]*Route{
|
||||
"0.0.0.0/0": {v4ExitRoute},
|
||||
"0.0.0.0/0-v6": {v6ExitRoute},
|
||||
},
|
||||
expected: []NetID{"0.0.0.0/0", "0.0.0.0/0-v6"},
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
ids: []NetID{},
|
||||
routesMap: map[NetID][]*Route{},
|
||||
expected: []NetID{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ExpandV6ExitPairs(tt.ids, tt.routesMap)
|
||||
assert.ElementsMatch(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user