Add IPv6 overlay address support to client interface and engine

This commit is contained in:
Viktor Liu
2026-03-24 06:56:49 +01:00
parent 013770070a
commit b852ce1a99
60 changed files with 4077 additions and 1647 deletions

View File

@@ -345,6 +345,7 @@ func (a *Auth) setSystemInfoFlags(info *system.Info) {
a.config.DisableFirewall,
a.config.BlockLANAccess,
a.config.BlockInbound,
a.config.DisableIPv6,
a.config.LazyConnectionEnabled,
a.config.EnableSSHRoot,
a.config.EnableSSHSFTP,

View File

@@ -14,10 +14,13 @@ import (
"github.com/cenkalti/backoff/v4"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/device"
"github.com/netbirdio/netbird/client/iface/netstack"
@@ -520,9 +523,20 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf
if config.NetworkMonitor != nil {
nm = *config.NetworkMonitor
}
wgAddr, err := wgaddr.ParseWGAddress(peerConfig.Address)
if err != nil {
return nil, fmt.Errorf("parse overlay address %q: %w", peerConfig.Address, err)
}
if !config.DisableIPv6 {
if err := wgAddr.SetIPv6FromCompact(peerConfig.GetAddressV6()); err != nil {
log.Warnf(err.Error())
}
}
engineConf := &EngineConfig{
WgIfaceName: config.WgIface,
WgAddr: peerConfig.Address,
WgAddr: wgAddr,
IFaceBlackList: config.IFaceBlackList,
DisableIPv6Discovery: config.DisableIPv6Discovery,
WgPrivateKey: key,
@@ -547,6 +561,7 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf
DisableFirewall: config.DisableFirewall,
BlockLANAccess: config.BlockLANAccess,
BlockInbound: config.BlockInbound,
DisableIPv6: config.DisableIPv6,
LazyConnectionEnabled: config.LazyConnectionEnabled,
@@ -627,6 +642,7 @@ func loginToManagement(ctx context.Context, client mgm.Client, pubSSHKey []byte,
config.DisableFirewall,
config.BlockLANAccess,
config.BlockInbound,
config.DisableIPv6,
config.LazyConnectionEnabled,
config.EnableSSHRoot,
config.EnableSSHSFTP,

View File

@@ -522,6 +522,7 @@ func (g *BundleGenerator) addCommonConfigFields(configContent *strings.Builder)
configContent.WriteString(fmt.Sprintf("DisableFirewall: %v\n", g.internalConfig.DisableFirewall))
configContent.WriteString(fmt.Sprintf("BlockLANAccess: %v\n", g.internalConfig.BlockLANAccess))
configContent.WriteString(fmt.Sprintf("BlockInbound: %v\n", g.internalConfig.BlockInbound))
configContent.WriteString(fmt.Sprintf("DisableIPv6: %v\n", g.internalConfig.DisableIPv6))
if g.internalConfig.DisableNotifications != nil {
configContent.WriteString(fmt.Sprintf("DisableNotifications: %v\n", *g.internalConfig.DisableNotifications))

View File

@@ -347,7 +347,7 @@ func TestUpdateDNSServer(t *testing.T) {
opts := iface.WGIFaceOpts{
IFaceName: fmt.Sprintf("utun230%d", n),
Address: fmt.Sprintf("100.66.100.%d/32", n+1),
Address: wgaddr.MustParseWGAddress(fmt.Sprintf("100.66.100.%d/32", n+1)),
WGPort: 33100,
WGPrivKey: privKey.String(),
MTU: iface.DefaultMTU,
@@ -448,7 +448,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
privKey, _ := wgtypes.GeneratePrivateKey()
opts := iface.WGIFaceOpts{
IFaceName: "utun2301",
Address: "100.66.100.1/32",
Address: wgaddr.MustParseWGAddress("100.66.100.1/32"),
WGPort: 33100,
WGPrivKey: privKey.String(),
MTU: iface.DefaultMTU,
@@ -929,7 +929,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
opts := iface.WGIFaceOpts{
IFaceName: "utun2301",
Address: "100.66.100.2/24",
Address: wgaddr.MustParseWGAddress("100.66.100.2/24"),
WGPort: 33100,
WGPrivKey: privKey.String(),
MTU: iface.DefaultMTU,

View File

@@ -28,6 +28,8 @@ import (
"github.com/netbirdio/netbird/client/firewall"
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/shared/netiputil"
"github.com/netbirdio/netbird/client/iface/device"
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
"github.com/netbirdio/netbird/client/iface/udpmux"
@@ -84,8 +86,9 @@ type EngineConfig struct {
WgPort int
WgIfaceName string
// WgAddr is a Wireguard local address (Netbird Network IP)
WgAddr string
// WgAddr is the Wireguard local address (Netbird Network IP).
// Contains both v4 and optional v6 overlay addresses.
WgAddr wgaddr.Address
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
WgPrivateKey wgtypes.Key
@@ -130,6 +133,7 @@ type EngineConfig struct {
DisableFirewall bool
BlockLANAccess bool
BlockInbound bool
DisableIPv6 bool
LazyConnectionEnabled bool
@@ -703,7 +707,7 @@ func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
if !ok {
continue
}
if !compareNetIPLists(allowedIPs, p.GetAllowedIps()) {
if !compareNetIPLists(allowedIPs, e.filterAllowedIPs(p.GetAllowedIps())) {
modified = append(modified, p)
continue
}
@@ -977,6 +981,7 @@ func (e *Engine) updateChecksIfNew(checks []*mgmProto.Checks) error {
e.config.DisableFirewall,
e.config.BlockLANAccess,
e.config.BlockInbound,
e.config.DisableIPv6,
e.config.LazyConnectionEnabled,
e.config.EnableSSHRoot,
e.config.EnableSSHSFTP,
@@ -1004,6 +1009,13 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
return ErrResetConnection
}
if !e.config.DisableIPv6 && e.hasIPv6Changed(conf) {
log.Infof("peer IPv6 address changed, restarting client")
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
e.clientCancel()
return ErrResetConnection
}
if conf.GetSshConfig() != nil {
if err := e.updateSSH(conf.GetSshConfig()); err != nil {
log.Warnf("failed handling SSH server setup: %v", err)
@@ -1012,6 +1024,7 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
state := e.statusRecorder.GetLocalPeerState()
state.IP = e.wgInterface.Address().String()
state.IPv6 = e.wgInterface.Address().IPv6String()
state.PubKey = e.config.WgPrivateKey.PublicKey().String()
state.KernelInterface = !e.wgInterface.IsUserspaceBind()
state.FQDN = conf.GetFqdn()
@@ -1020,6 +1033,26 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
return nil
}
// hasIPv6Changed reports whether the IPv6 overlay address in the peer config
// differs from the current interface address (added, removed, or changed).
func (e *Engine) hasIPv6Changed(conf *mgmProto.PeerConfig) bool {
current := e.wgInterface.Address()
raw := conf.GetAddressV6()
if len(raw) == 0 {
return current.HasIPv6()
}
addr, err := netiputil.DecodeAddr(raw)
if err != nil {
log.Warnf("decode v6 overlay address: %v", err)
return false
}
return !current.HasIPv6() || current.IPv6 != addr
}
func (e *Engine) receiveJobEvents() {
e.jobExecutorWG.Add(1)
go func() {
@@ -1117,6 +1150,7 @@ func (e *Engine) receiveManagementEvents() {
e.config.DisableFirewall,
e.config.BlockLANAccess,
e.config.BlockInbound,
e.config.DisableIPv6,
e.config.LazyConnectionEnabled,
e.config.EnableSSHRoot,
e.config.EnableSSHSFTP,
@@ -1437,8 +1471,10 @@ func (e *Engine) updateOfflinePeers(offlinePeers []*mgmProto.RemotePeerConfig) {
replacement := make([]peer.State, len(offlinePeers))
for i, offlinePeer := range offlinePeers {
log.Debugf("added offline peer %s", offlinePeer.Fqdn)
v4, v6 := splitAllowedIPs(offlinePeer.GetAllowedIps(), e.wgInterface.Address().IPv6Net)
replacement[i] = peer.State{
IP: strings.Join(offlinePeer.GetAllowedIps(), ","),
IP: v4,
IPv6: v6,
PubKey: offlinePeer.GetWgPubKey(),
FQDN: offlinePeer.GetFqdn(),
ConnStatus: peer.StatusIdle,
@@ -1449,6 +1485,30 @@ func (e *Engine) updateOfflinePeers(offlinePeers []*mgmProto.RemotePeerConfig) {
e.statusRecorder.ReplaceOfflinePeers(replacement)
}
// splitAllowedIPs separates the peer's overlay v4 (/32) and v6 (/128) addresses
// from a list of AllowedIPs CIDRs. The v6 address is only matched if it falls
// within ourV6Net (the local overlay v6 subnet), to avoid confusing routed /128
// prefixes with the peer's overlay address.
func splitAllowedIPs(allowedIPs []string, ourV6Net netip.Prefix) (v4, v6 string) {
for _, cidr := range allowedIPs {
prefix, err := netip.ParsePrefix(cidr)
if err != nil {
log.Warnf("failed to parse AllowedIP %q: %v", cidr, err)
continue
}
switch {
case prefix.Addr().Is4() && prefix.Bits() == 32 && v4 == "":
v4 = prefix.Addr().String()
case prefix.Addr().Is6() && prefix.Bits() == 128 && ourV6Net.Contains(prefix.Addr()) && v6 == "":
v6 = prefix.Addr().String()
}
if v4 != "" && v6 != "" {
break
}
}
return
}
// addNewPeers adds peers that were not know before but arrived from the Management service with the update
func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
for _, p := range peersUpdate {
@@ -1474,6 +1534,9 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
log.Errorf("failed to parse allowedIPS: %v", err)
return err
}
if allowedNetIP.Addr().Is6() && !e.wgInterface.Address().HasIPv6() {
continue
}
peerIPs = append(peerIPs, allowedNetIP)
}
@@ -1482,7 +1545,15 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
return fmt.Errorf("create peer connection: %w", err)
}
err = e.statusRecorder.AddPeer(peerKey, peerConfig.Fqdn, peerIPs[0].Addr().String())
var peerIPv6 string
ourV6Net := e.wgInterface.Address().IPv6Net
for _, pip := range peerIPs {
if pip.Addr().Is6() && pip.Bits() == 128 && ourV6Net.Contains(pip.Addr()) {
peerIPv6 = pip.Addr().String()
break
}
}
err = e.statusRecorder.AddPeer(peerKey, peerConfig.Fqdn, peerIPs[0].Addr().String(), peerIPv6)
if err != nil {
log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err)
}
@@ -1705,6 +1776,7 @@ func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, bool, err
e.config.DisableFirewall,
e.config.BlockLANAccess,
e.config.BlockInbound,
e.config.DisableIPv6,
e.config.LazyConnectionEnabled,
e.config.EnableSSHRoot,
e.config.EnableSSHSFTP,
@@ -1760,7 +1832,8 @@ func (e *Engine) wgInterfaceCreate() (err error) {
case "android":
err = e.wgInterface.CreateOnAndroid(e.routeManager.InitialRouteRange(), e.dnsServer.DnsIP().String(), e.dnsServer.SearchDomains())
case "ios":
e.mobileDep.NetworkChangeListener.SetInterfaceIP(e.config.WgAddr)
e.mobileDep.NetworkChangeListener.SetInterfaceIP(e.config.WgAddr.String())
e.mobileDep.NetworkChangeListener.SetInterfaceIPv6(e.config.WgAddr.IPv6String())
err = e.wgInterface.Create()
default:
err = e.wgInterface.Create()
@@ -2269,6 +2342,24 @@ func getInterfacePrefixes() ([]netip.Prefix, error) {
return prefixes, nberrors.FormatErrorOrNil(merr)
}
// filterAllowedIPs strips IPv6 entries when the local interface has no v6 address.
// This covers both the explicit --disable-ipv6 flag (v6 never assigned) and the
// case where OS v6 assignment failed (ClearIPv6). Without this, WireGuard would
// accept v6 traffic that the native firewall cannot filter.
func (e *Engine) filterAllowedIPs(ips []string) []string {
if e.wgInterface.Address().HasIPv6() {
return ips
}
filtered := make([]string, 0, len(ips))
for _, s := range ips {
p, err := netip.ParsePrefix(s)
if err != nil || !p.Addr().Is6() {
filtered = append(filtered, s)
}
}
return filtered
}
// compareNetIPLists compares a list of netip.Prefix with a list of strings.
// return true if both lists are equal, false otherwise.
func compareNetIPLists(list1 []netip.Prefix, list2 []string) bool {

View File

@@ -66,6 +66,7 @@ import (
mgmt "github.com/netbirdio/netbird/shared/management/client"
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
relayClient "github.com/netbirdio/netbird/shared/relay/client"
"github.com/netbirdio/netbird/shared/netiputil"
signal "github.com/netbirdio/netbird/shared/signal/client"
"github.com/netbirdio/netbird/shared/signal/proto"
signalServer "github.com/netbirdio/netbird/signal/server"
@@ -94,7 +95,7 @@ type MockWGIface struct {
AddressFunc func() wgaddr.Address
ToInterfaceFunc func() *net.Interface
UpFunc func() (*udpmux.UniversalUDPMuxDefault, error)
UpdateAddrFunc func(newAddr string) error
UpdateAddrFunc func(newAddr wgaddr.Address) error
UpdatePeerFunc func(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
RemovePeerFunc func(peerKey string) error
AddAllowedIPFunc func(peerKey string, allowedIP netip.Prefix) error
@@ -156,7 +157,7 @@ func (m *MockWGIface) Up() (*udpmux.UniversalUDPMuxDefault, error) {
return m.UpFunc()
}
func (m *MockWGIface) UpdateAddr(newAddr string) error {
func (m *MockWGIface) UpdateAddr(newAddr wgaddr.Address) error {
return m.UpdateAddrFunc(newAddr)
}
@@ -253,7 +254,7 @@ func TestEngine_SSH(t *testing.T) {
ctx, cancel,
&EngineConfig{
WgIfaceName: "utun101",
WgAddr: "100.64.0.1/24",
WgAddr: wgaddr.MustParseWGAddress("100.64.0.1/24"),
WgPrivateKey: key,
WgPort: 33100,
ServerSSHAllowed: true,
@@ -430,7 +431,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
engine := NewEngine(ctx, cancel, &EngineConfig{
WgIfaceName: "utun102",
WgAddr: "100.64.0.1/24",
WgAddr: wgaddr.MustParseWGAddress("100.64.0.1/24"),
WgPrivateKey: key,
WgPort: 33100,
MTU: iface.DefaultMTU,
@@ -654,7 +655,7 @@ func TestEngine_Sync(t *testing.T) {
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
engine := NewEngine(ctx, cancel, &EngineConfig{
WgIfaceName: "utun103",
WgAddr: "100.64.0.1/24",
WgAddr: wgaddr.MustParseWGAddress("100.64.0.1/24"),
WgPrivateKey: key,
WgPort: 33100,
MTU: iface.DefaultMTU,
@@ -824,7 +825,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
engine := NewEngine(ctx, cancel, &EngineConfig{
WgIfaceName: wgIfaceName,
WgAddr: wgAddr,
WgAddr: wgaddr.MustParseWGAddress(wgAddr),
WgPrivateKey: key,
WgPort: 33100,
MTU: iface.DefaultMTU,
@@ -842,7 +843,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
opts := iface.WGIFaceOpts{
IFaceName: wgIfaceName,
Address: wgAddr,
Address: wgaddr.MustParseWGAddress(wgAddr),
WGPort: engine.config.WgPort,
WGPrivKey: key.String(),
MTU: iface.DefaultMTU,
@@ -1031,7 +1032,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
engine := NewEngine(ctx, cancel, &EngineConfig{
WgIfaceName: wgIfaceName,
WgAddr: wgAddr,
WgAddr: wgaddr.MustParseWGAddress(wgAddr),
WgPrivateKey: key,
WgPort: 33100,
MTU: iface.DefaultMTU,
@@ -1049,7 +1050,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
}
opts := iface.WGIFaceOpts{
IFaceName: wgIfaceName,
Address: wgAddr,
Address: wgaddr.MustParseWGAddress(wgAddr),
WGPort: 33100,
WGPrivKey: key.String(),
MTU: iface.DefaultMTU,
@@ -1559,7 +1560,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
wgPort := 33100 + i
conf := &EngineConfig{
WgIfaceName: ifaceName,
WgAddr: resp.PeerConfig.Address,
WgAddr: wgaddr.MustParseWGAddress(resp.PeerConfig.Address),
WgPrivateKey: key,
WgPort: wgPort,
MTU: iface.DefaultMTU,
@@ -1704,3 +1705,205 @@ func getPeers(e *Engine) int {
return len(e.peerStore.PeersPubKey())
}
func TestEngine_hasIPv6Changed(t *testing.T) {
v4Only := wgaddr.MustParseWGAddress("100.64.0.1/16")
v4v6 := wgaddr.MustParseWGAddress("100.64.0.1/16")
v4v6.IPv6 = netip.MustParseAddr("fd00::1")
v4v6.IPv6Net = netip.MustParsePrefix("fd00::1/64").Masked()
tests := []struct {
name string
current wgaddr.Address
confV6 []byte
expected bool
}{
{
name: "no v6 before, no v6 now",
current: v4Only,
confV6: nil,
expected: false,
},
{
name: "no v6 before, v6 added",
current: v4Only,
confV6: netiputil.EncodeAddr(netip.MustParseAddr("fd00::1")),
expected: true,
},
{
name: "had v6, now removed",
current: v4v6,
confV6: nil,
expected: true,
},
{
name: "had v6, same v6",
current: v4v6,
confV6: netiputil.EncodeAddr(netip.MustParseAddr("fd00::1")),
expected: false,
},
{
name: "had v6, different v6",
current: v4v6,
confV6: netiputil.EncodeAddr(netip.MustParseAddr("fd00::2")),
expected: true,
},
{
name: "decode error keeps status quo",
current: v4Only,
confV6: []byte{1, 2, 3},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
engine := &Engine{
wgInterface: &MockWGIface{
AddressFunc: func() wgaddr.Address { return tt.current },
},
}
conf := &mgmtProto.PeerConfig{
AddressV6: tt.confV6,
}
assert.Equal(t, tt.expected, engine.hasIPv6Changed(conf))
})
}
}
func TestFilterAllowedIPs(t *testing.T) {
v4v6Addr := wgaddr.MustParseWGAddress("100.64.0.1/16")
v4v6Addr.IPv6 = netip.MustParseAddr("fd00::1")
v4v6Addr.IPv6Net = netip.MustParsePrefix("fd00::1/64").Masked()
v4OnlyAddr := wgaddr.MustParseWGAddress("100.64.0.1/16")
tests := []struct {
name string
addr wgaddr.Address
input []string
expected []string
}{
{
name: "interface has v6, keep all",
addr: v4v6Addr,
input: []string{"100.64.0.1/32", "fd00::1/128"},
expected: []string{"100.64.0.1/32", "fd00::1/128"},
},
{
name: "no v6, strip v6",
addr: v4OnlyAddr,
input: []string{"100.64.0.1/32", "fd00::1/128"},
expected: []string{"100.64.0.1/32"},
},
{
name: "no v6, only v4",
addr: v4OnlyAddr,
input: []string{"100.64.0.1/32", "10.0.0.0/8"},
expected: []string{"100.64.0.1/32", "10.0.0.0/8"},
},
{
name: "no v6, only v6 input",
addr: v4OnlyAddr,
input: []string{"fd00::1/128", "::/0"},
expected: []string{},
},
{
name: "no v6, invalid prefix preserved",
addr: v4OnlyAddr,
input: []string{"100.64.0.1/32", "garbage"},
expected: []string{"100.64.0.1/32", "garbage"},
},
{
name: "no v6, empty input",
addr: v4OnlyAddr,
input: []string{},
expected: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
addr := tt.addr
engine := &Engine{
config: &EngineConfig{},
wgInterface: &MockWGIface{
AddressFunc: func() wgaddr.Address { return addr },
},
}
result := engine.filterAllowedIPs(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestSplitAllowedIPs(t *testing.T) {
ourV6Net := netip.MustParsePrefix("fd00:1234:5678:abcd::/64")
tests := []struct {
name string
allowedIPs []string
ourV6Net netip.Prefix
wantV4 string
wantV6 string
}{
{
name: "v4 only",
allowedIPs: []string{"100.64.0.1/32"},
ourV6Net: ourV6Net,
wantV4: "100.64.0.1",
wantV6: "",
},
{
name: "v4 and v6 overlay",
allowedIPs: []string{"100.64.0.1/32", "fd00:1234:5678:abcd::1/128"},
ourV6Net: ourV6Net,
wantV4: "100.64.0.1",
wantV6: "fd00:1234:5678:abcd::1",
},
{
name: "v4, routed v6, overlay v6",
allowedIPs: []string{"100.64.0.1/32", "2001:db8::1/128", "fd00:1234:5678:abcd::1/128"},
ourV6Net: ourV6Net,
wantV4: "100.64.0.1",
wantV6: "fd00:1234:5678:abcd::1",
},
{
name: "routed v6 /128 outside our subnet is ignored",
allowedIPs: []string{"100.64.0.1/32", "2001:db8::1/128"},
ourV6Net: ourV6Net,
wantV4: "100.64.0.1",
wantV6: "",
},
{
name: "routed v6 prefix is ignored",
allowedIPs: []string{"100.64.0.1/32", "fd00:1234:5678:abcd::/64"},
ourV6Net: ourV6Net,
wantV4: "100.64.0.1",
wantV6: "",
},
{
name: "no v6 subnet configured",
allowedIPs: []string{"100.64.0.1/32", "fd00:1234:5678:abcd::1/128"},
ourV6Net: netip.Prefix{},
wantV4: "100.64.0.1",
wantV6: "",
},
{
name: "v4 /24 route is ignored",
allowedIPs: []string{"100.64.0.0/24", "100.64.0.1/32"},
ourV6Net: ourV6Net,
wantV4: "100.64.0.1",
wantV6: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v4, v6 := splitAllowedIPs(tt.allowedIPs, tt.ourV6Net)
assert.Equal(t, tt.wantV4, v4, "v4")
assert.Equal(t, tt.wantV6, v6, "v6")
})
}
}

View File

@@ -26,7 +26,7 @@ type wgIfaceBase interface {
Address() wgaddr.Address
ToInterface() *net.Interface
Up() (*udpmux.UniversalUDPMuxDefault, error)
UpdateAddr(newAddr string) error
UpdateAddr(newAddr wgaddr.Address) error
GetProxy() wgproxy.Proxy
GetProxyPort() uint16
UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error

View File

@@ -5,4 +5,5 @@ type NetworkChangeListener interface {
// OnNetworkChanged invoke when network settings has been changed
OnNetworkChanged(string)
SetInterfaceIP(string)
SetInterfaceIPv6(string)
}

View File

@@ -53,6 +53,7 @@ type RouterState struct {
type State struct {
Mux *sync.RWMutex
IP string
IPv6 string
PubKey string
FQDN string
ConnStatus ConnStatus
@@ -106,6 +107,7 @@ func (s *State) GetRoutes() map[string]struct{} {
// LocalPeerState contains the latest state of the local peer
type LocalPeerState struct {
IP string
IPv6 string
PubKey string
KernelInterface bool
FQDN string
@@ -259,7 +261,7 @@ func (d *Status) ReplaceOfflinePeers(replacement []State) {
}
// AddPeer adds peer to Daemon status map
func (d *Status) AddPeer(peerPubKey string, fqdn string, ip string) error {
func (d *Status) AddPeer(peerPubKey string, fqdn string, ip string, ipv6 string) error {
d.mux.Lock()
defer d.mux.Unlock()
@@ -270,6 +272,7 @@ func (d *Status) AddPeer(peerPubKey string, fqdn string, ip string) error {
d.peers[peerPubKey] = State{
PubKey: peerPubKey,
IP: ip,
IPv6: ipv6,
ConnStatus: StatusIdle,
FQDN: fqdn,
Mux: new(sync.RWMutex),
@@ -1239,6 +1242,7 @@ func (fs FullStatus) ToProto() *proto.FullStatus {
}
pbFullStatus.LocalPeerState.IP = fs.LocalPeerState.IP
pbFullStatus.LocalPeerState.Ipv6 = fs.LocalPeerState.IPv6
pbFullStatus.LocalPeerState.PubKey = fs.LocalPeerState.PubKey
pbFullStatus.LocalPeerState.KernelInterface = fs.LocalPeerState.KernelInterface
pbFullStatus.LocalPeerState.Fqdn = fs.LocalPeerState.FQDN
@@ -1254,6 +1258,7 @@ func (fs FullStatus) ToProto() *proto.FullStatus {
pbPeerState := &proto.PeerState{
IP: peerState.IP,
Ipv6: peerState.IPv6,
PubKey: peerState.PubKey,
ConnStatus: peerState.ConnStatus.String(),
ConnStatusUpdate: timestamppb.New(peerState.ConnStatusUpdate),

View File

@@ -14,13 +14,13 @@ func TestAddPeer(t *testing.T) {
key := "abc"
ip := "100.108.254.1"
status := NewRecorder("https://mgm")
err := status.AddPeer(key, "abc.netbird", ip)
err := status.AddPeer(key, "abc.netbird", ip, "")
assert.NoError(t, err, "shouldn't return error")
_, exists := status.peers[key]
assert.True(t, exists, "value was found")
err = status.AddPeer(key, "abc.netbird", ip)
err = status.AddPeer(key, "abc.netbird", ip, "")
assert.Error(t, err, "should return error on duplicate")
}
@@ -29,7 +29,7 @@ func TestGetPeer(t *testing.T) {
key := "abc"
ip := "100.108.254.1"
status := NewRecorder("https://mgm")
err := status.AddPeer(key, "abc.netbird", ip)
err := status.AddPeer(key, "abc.netbird", ip, "")
assert.NoError(t, err, "shouldn't return error")
peerStatus, err := status.GetPeer(key)
@@ -46,7 +46,7 @@ func TestUpdatePeerState(t *testing.T) {
ip := "10.10.10.10"
fqdn := "peer-a.netbird.local"
status := NewRecorder("https://mgm")
_ = status.AddPeer(key, fqdn, ip)
_ = status.AddPeer(key, fqdn, ip, "")
peerState := State{
PubKey: key,
@@ -85,7 +85,7 @@ func TestGetPeerStateChangeNotifierLogic(t *testing.T) {
key := "abc"
ip := "10.10.10.10"
status := NewRecorder("https://mgm")
_ = status.AddPeer(key, "abc.netbird", ip)
_ = status.AddPeer(key, "abc.netbird", ip, "")
sub := status.SubscribeToPeerStateChanges(context.Background(), key)
assert.NotNil(t, sub, "channel shouldn't be nil")

View File

@@ -77,6 +77,7 @@ type ConfigInput struct {
DisableFirewall *bool
BlockLANAccess *bool
BlockInbound *bool
DisableIPv6 *bool
DisableNotifications *bool
@@ -115,6 +116,7 @@ type Config struct {
DisableFirewall bool
BlockLANAccess bool
BlockInbound bool
DisableIPv6 bool
DisableNotifications *bool
@@ -530,6 +532,16 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
updated = true
}
if input.DisableIPv6 != nil && *input.DisableIPv6 != config.DisableIPv6 {
if *input.DisableIPv6 {
log.Infof("disabling IPv6 overlay")
} else {
log.Infof("enabling IPv6 overlay")
}
config.DisableIPv6 = *input.DisableIPv6
updated = true
}
if input.DisableNotifications != nil && input.DisableNotifications != config.DisableNotifications {
if *input.DisableNotifications {
log.Infof("disabling notifications")

View File

@@ -46,7 +46,7 @@ func generateBenchmarkData(tier benchmarkTier) (*peer.Status, map[route.ID]*rout
fqdn := fmt.Sprintf("peer-%d.example.com", i)
ip := fmt.Sprintf("10.0.%d.%d", i/256, i%256)
err := statusRecorder.AddPeer(peerKey, fqdn, ip)
err := statusRecorder.AddPeer(peerKey, fqdn, ip, "")
if err != nil {
panic(fmt.Sprintf("failed to add peer: %v", err))
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/route"
)
@@ -409,7 +410,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}
opts := iface.WGIFaceOpts{
IFaceName: fmt.Sprintf("utun43%d", n),
Address: "100.65.65.2/24",
Address: wgaddr.MustParseWGAddress("100.65.65.2/24"),
WGPort: 33100,
WGPrivKey: peerPrivateKey.String(),
MTU: iface.DefaultMTU,

View File

@@ -21,6 +21,7 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/routemanager/vars"
nbnet "github.com/netbirdio/netbird/client/net"
)
@@ -441,7 +442,7 @@ func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listen
opts := iface.WGIFaceOpts{
IFaceName: interfaceName,
Address: ipAddressCIDR,
Address: wgaddr.MustParseWGAddress(ipAddressCIDR),
WGPrivKey: peerPrivateKey.String(),
WGPort: listenPort,
MTU: iface.DefaultMTU,