From c2980bc8cf31c24fdfb1f0bc48353b3c8b2c2b28 Mon Sep 17 00:00:00 2001
From: Misha Bragin
Date: Wed, 12 Mar 2025 21:18:19 +0100
Subject: [PATCH 1/4] Update link to kubernetes operator (#3489)
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index a063c821d..e39382acd 100644
--- a/README.md
+++ b/README.md
@@ -34,8 +34,8 @@
-
- Webinar: Securely Access Kubernetes without Port Forwarding and Jump Hosts
+
+ New: NetBird Kubernetes Operator
From 4a3cbcd38a06fdd7dcd391351ae4ed7959ead81a Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Thu, 13 Mar 2025 00:04:00 +0100
Subject: [PATCH 2/4] Nil check on route manager (#3486)
---
client/server/network.go | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/client/server/network.go b/client/server/network.go
index d310f4da1..1b7962b78 100644
--- a/client/server/network.go
+++ b/client/server/network.go
@@ -36,8 +36,13 @@ func (s *Server) ListNetworks(context.Context, *proto.ListNetworksRequest) (*pro
return nil, fmt.Errorf("not connected")
}
- routesMap := engine.GetRouteManager().GetClientRoutesWithNetID()
- routeSelector := engine.GetRouteManager().GetRouteSelector()
+ routeMgr := engine.GetRouteManager()
+ if routeMgr == nil {
+ return nil, fmt.Errorf("no route manager")
+ }
+
+ routesMap := routeMgr.GetClientRoutesWithNetID()
+ routeSelector := routeMgr.GetRouteSelector()
var routes []*selectRoute
for id, rt := range routesMap {
@@ -123,6 +128,10 @@ func (s *Server) SelectNetworks(_ context.Context, req *proto.SelectNetworksRequ
}
routeManager := engine.GetRouteManager()
+ if routeManager == nil {
+ return nil, fmt.Errorf("no route manager")
+ }
+
routeSelector := routeManager.GetRouteSelector()
if req.GetAll() {
routeSelector.SelectAllRoutes()
@@ -165,6 +174,10 @@ func (s *Server) DeselectNetworks(_ context.Context, req *proto.SelectNetworksRe
}
routeManager := engine.GetRouteManager()
+ if routeManager == nil {
+ return nil, fmt.Errorf("no route manager")
+ }
+
routeSelector := routeManager.GetRouteSelector()
if req.GetAll() {
routeSelector.DeselectAllRoutes()
From a2faae5d62291b61876643c0a9a19b066b9cbae0 Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Fri, 14 Mar 2025 11:38:16 +0100
Subject: [PATCH 3/4] [client] Fix anonymized addresses documentation (#3505)
---
client/anonymize/anonymize.go | 2 +-
client/server/debug.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/anonymize/anonymize.go b/client/anonymize/anonymize.go
index 89552724a..2fc9d49d3 100644
--- a/client/anonymize/anonymize.go
+++ b/client/anonymize/anonymize.go
@@ -26,7 +26,7 @@ type Anonymizer struct {
}
func DefaultAddresses() (netip.Addr, netip.Addr) {
- // 192.51.100.0, 100::
+ // 198.51.100.0, 100::
return netip.AddrFrom4([4]byte{198, 51, 100, 0}), netip.AddrFrom16([16]byte{0x01})
}
diff --git a/client/server/debug.go b/client/server/debug.go
index 749220d62..39a67c92c 100644
--- a/client/server/debug.go
+++ b/client/server/debug.go
@@ -53,7 +53,7 @@ The files in this bundle have been anonymized to protect sensitive information.
IP Addresses
-IPv4 addresses are replaced with addresses starting from 192.51.100.0
+IPv4 addresses are replaced with addresses starting from 198.51.100.0
IPv6 addresses are replaced with addresses starting from 100::
IP addresses from non public ranges and well known addresses are not anonymized (e.g. 8.8.8.8, 100.64.0.0/10, addresses starting with 192.168., 172.16., 10., etc.).
From 6f82e96d6a55e04bf3866672de7841e92dd5d748 Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Fri, 14 Mar 2025 22:34:41 +0100
Subject: [PATCH 4/4] [client] Set info logs (#3504)
collect and log connection stats per peer every 10 minutes
---
client/internal/peer/conn.go | 27 ++++--
client/internal/peer/handshaker.go | 13 ++-
client/internal/peer/state_dump.go | 112 ++++++++++++++++++++++++
client/internal/peer/wg_watcher.go | 5 +-
client/internal/peer/wg_watcher_test.go | 4 +-
client/internal/peer/worker_relay.go | 4 +-
6 files changed, 147 insertions(+), 18 deletions(-)
create mode 100644 client/internal/peer/state_dump.go
diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go
index b91cfe33c..dad757e38 100644
--- a/client/internal/peer/conn.go
+++ b/client/internal/peer/conn.go
@@ -114,6 +114,9 @@ type Conn struct {
guard *guard.Guard
semaphore *semaphoregroup.SemaphoreGroup
+
+ // debug purpose
+ dumpState *stateDump
}
// NewConn creates a new not opened Conn to the remote peer.
@@ -140,7 +143,7 @@ func NewConn(engineCtx context.Context, config ConnConfig, statusRecorder *Statu
}
ctrl := isController(config)
- conn.workerRelay = NewWorkerRelay(connLog, ctrl, config, conn, relayManager)
+ conn.workerRelay = NewWorkerRelay(connLog, ctrl, config, conn, relayManager, conn.dumpState)
relayIsSupportedLocally := conn.workerRelay.RelayIsSupportedLocally()
workerICE, err := NewWorkerICE(ctx, connLog, config, conn, signaler, iFaceDiscover, statusRecorder, relayIsSupportedLocally)
@@ -160,6 +163,8 @@ func NewConn(engineCtx context.Context, config ConnConfig, statusRecorder *Statu
go conn.handshaker.Listen()
+ conn.dumpState = newStateDump(connLog)
+ go conn.dumpState.Start(ctx)
return conn, nil
}
@@ -193,6 +198,7 @@ func (conn *Conn) startHandshakeAndReconnect(ctx context.Context) {
defer conn.semaphore.Done(conn.ctx)
conn.waitInitialRandomSleepTime(ctx)
+ conn.dumpState.SendOffer()
err := conn.handshaker.sendOffer()
if err != nil {
conn.log.Errorf("failed to send initial offer: %v", err)
@@ -251,12 +257,14 @@ func (conn *Conn) Close() {
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
// doesn't block, discards the message if connection wasn't ready
func (conn *Conn) OnRemoteAnswer(answer OfferAnswer) bool {
- conn.log.Debugf("OnRemoteAnswer, status ICE: %s, status relay: %s", conn.statusICE, conn.statusRelay)
+ conn.dumpState.RemoteAnswer()
+ conn.log.Infof("OnRemoteAnswer, status ICE: %s, status relay: %s", conn.statusICE, conn.statusRelay)
return conn.handshaker.OnRemoteAnswer(answer)
}
// OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer.
func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMap) {
+ conn.dumpState.RemoteCandidate()
conn.workerICE.OnRemoteCandidate(candidate, haRoutes)
}
@@ -278,7 +286,8 @@ func (conn *Conn) SetOnDisconnected(handler func(remotePeer string)) {
}
func (conn *Conn) OnRemoteOffer(offer OfferAnswer) bool {
- conn.log.Debugf("OnRemoteOffer, on status ICE: %s, status Relay: %s", conn.statusICE, conn.statusRelay)
+ conn.dumpState.RemoteOffer()
+ conn.log.Infof("OnRemoteOffer, on status ICE: %s, status Relay: %s", conn.statusICE, conn.statusRelay)
return conn.handshaker.OnRemoteOffer(offer)
}
@@ -322,6 +331,7 @@ func (conn *Conn) onICEConnectionIsReady(priority ConnPriority, iceConnInfo ICEC
}
conn.log.Infof("set ICE to active connection")
+ conn.dumpState.P2PConnected()
var (
ep *net.UDPAddr
@@ -329,6 +339,7 @@ func (conn *Conn) onICEConnectionIsReady(priority ConnPriority, iceConnInfo ICEC
err error
)
if iceConnInfo.RelayedOnLocal {
+ conn.dumpState.NewLocalProxy()
wgProxy, err = conn.newProxy(iceConnInfo.RemoteConn)
if err != nil {
conn.log.Errorf("failed to add turn net.Conn to local proxy: %v", err)
@@ -390,6 +401,7 @@ func (conn *Conn) onICEStateDisconnected() {
// switch back to relay connection
if conn.isReadyToUpgrade() {
conn.log.Infof("ICE disconnected, set Relay to active connection")
+ conn.dumpState.SwitchToRelay()
conn.wgProxyRelay.Work()
if err := conn.configureWGEndpoint(conn.wgProxyRelay.EndpointAddr()); err != nil {
@@ -432,6 +444,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
return
}
+ conn.dumpState.RelayConnected()
conn.log.Debugf("Relay connection has been established, setup the WireGuard")
wgProxy, err := conn.newProxy(rci.relayedConn)
@@ -439,6 +452,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
conn.log.Errorf("failed to add relayed net.Conn to local proxy: %v", err)
return
}
+ conn.dumpState.NewLocalProxy()
conn.log.Infof("created new wgProxy for relay connection: %s", wgProxy.EndpointAddr().String())
@@ -481,10 +495,10 @@ func (conn *Conn) onRelayDisconnected() {
return
}
- conn.log.Debugf("relay connection is disconnected")
+ conn.log.Infof("relay connection is disconnected")
if conn.currentConnPriority == connPriorityRelay {
- conn.log.Debugf("clean up WireGuard config")
+ conn.log.Infof("clean up WireGuard config")
if err := conn.removeWgPeer(); err != nil {
conn.log.Errorf("failed to remove wg endpoint: %v", err)
}
@@ -516,7 +530,8 @@ func (conn *Conn) listenGuardEvent(ctx context.Context) {
for {
select {
case <-conn.guard.Reconnect:
- conn.log.Debugf("send offer to peer")
+ conn.log.Infof("send offer to peer")
+ conn.dumpState.SendOffer()
if err := conn.handshaker.SendOffer(); err != nil {
conn.log.Errorf("failed to send offer: %v", err)
}
diff --git a/client/internal/peer/handshaker.go b/client/internal/peer/handshaker.go
index 545f81966..d23727e96 100644
--- a/client/internal/peer/handshaker.go
+++ b/client/internal/peer/handshaker.go
@@ -76,19 +76,19 @@ func (h *Handshaker) AddOnNewOfferListener(offer func(remoteOfferAnswer *OfferAn
func (h *Handshaker) Listen() {
for {
- h.log.Debugf("wait for remote offer confirmation")
+ h.log.Info("wait for remote offer confirmation")
remoteOfferAnswer, err := h.waitForRemoteOfferConfirmation()
if err != nil {
var connectionClosedError *ConnectionClosedError
if errors.As(err, &connectionClosedError) {
- h.log.Tracef("stop handshaker")
+ h.log.Info("exit from handshaker")
return
}
h.log.Errorf("failed to received remote offer confirmation: %s", err)
continue
}
- h.log.Debugf("received connection confirmation, running version %s and with remote WireGuard listen port %d", remoteOfferAnswer.Version, remoteOfferAnswer.WgListenPort)
+ h.log.Infof("received connection confirmation, running version %s and with remote WireGuard listen port %d", remoteOfferAnswer.Version, remoteOfferAnswer.WgListenPort)
for _, listener := range h.onNewOfferListeners {
go listener(remoteOfferAnswer)
}
@@ -108,7 +108,7 @@ func (h *Handshaker) OnRemoteOffer(offer OfferAnswer) bool {
case h.remoteOffersCh <- offer:
return true
default:
- h.log.Debugf("OnRemoteOffer skipping message because is not ready")
+ h.log.Warnf("OnRemoteOffer skipping message because is not ready")
// connection might not be ready yet to receive so we ignore the message
return false
}
@@ -131,8 +131,7 @@ func (h *Handshaker) waitForRemoteOfferConfirmation() (*OfferAnswer, error) {
select {
case remoteOfferAnswer := <-h.remoteOffersCh:
// received confirmation from the remote peer -> ready to proceed
- err := h.sendAnswer()
- if err != nil {
+ if err := h.sendAnswer(); err != nil {
return nil, err
}
return &remoteOfferAnswer, nil
@@ -168,7 +167,7 @@ func (h *Handshaker) sendOffer() error {
}
func (h *Handshaker) sendAnswer() error {
- h.log.Debugf("sending answer")
+ h.log.Infof("sending answer")
uFrag, pwd := h.ice.GetLocalUserCredentials()
answer := OfferAnswer{
diff --git a/client/internal/peer/state_dump.go b/client/internal/peer/state_dump.go
new file mode 100644
index 000000000..9c148a850
--- /dev/null
+++ b/client/internal/peer/state_dump.go
@@ -0,0 +1,112 @@
+package peer
+
+import (
+ "context"
+ "sync"
+ "time"
+
+ log "github.com/sirupsen/logrus"
+)
+
+type stateDump struct {
+ log *log.Entry
+
+ sentOffer int
+ remoteOffer int
+ remoteAnswer int
+ remoteCandidate int
+ p2pConnected int
+ switchToRelay int
+ wgCheckSuccess int
+ relayConnected int
+ localProxies int
+
+ mu sync.Mutex
+}
+
+func newStateDump(log *log.Entry) *stateDump {
+ return &stateDump{
+ log: log,
+ }
+}
+
+func (s *stateDump) Start(ctx context.Context) {
+ ticker := time.NewTicker(10 * time.Minute)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ticker.C:
+ s.dumpState()
+ case <-ctx.Done():
+ return
+ }
+ }
+}
+
+func (s *stateDump) RemoteOffer() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.remoteOffer++
+}
+
+func (s *stateDump) RemoteCandidate() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.remoteCandidate++
+}
+
+func (s *stateDump) SendOffer() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.sentOffer++
+}
+
+func (s *stateDump) dumpState() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.log.Infof("Dump stat: SentOffer: %d, RemoteOffer: %d, RemoteAnswer: %d, RemoteCandidate: %d, P2PConnected: %d, SwitchToRelay: %d, WGCheckSuccess: %d, RelayConnected: %d, LocalProxies: %d",
+ s.sentOffer, s.remoteOffer, s.remoteAnswer, s.remoteCandidate, s.p2pConnected, s.switchToRelay, s.wgCheckSuccess, s.relayConnected, s.localProxies)
+}
+
+func (s *stateDump) RemoteAnswer() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.remoteAnswer++
+}
+
+func (s *stateDump) P2PConnected() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.p2pConnected++
+}
+
+func (s *stateDump) SwitchToRelay() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.switchToRelay++
+}
+
+func (s *stateDump) WGcheckSuccess() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.wgCheckSuccess++
+}
+
+func (s *stateDump) RelayConnected() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.relayConnected++
+}
+
+func (s *stateDump) NewLocalProxy() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.localProxies++
+}
diff --git a/client/internal/peer/wg_watcher.go b/client/internal/peer/wg_watcher.go
index 6670c6517..49049b3d0 100644
--- a/client/internal/peer/wg_watcher.go
+++ b/client/internal/peer/wg_watcher.go
@@ -27,6 +27,7 @@ type WGWatcher struct {
log *log.Entry
wgIfaceStater WGInterfaceStater
peerKey string
+ stateDump *stateDump
ctx context.Context
ctxCancel context.CancelFunc
@@ -34,11 +35,12 @@ type WGWatcher struct {
waitGroup sync.WaitGroup
}
-func NewWGWatcher(log *log.Entry, wgIfaceStater WGInterfaceStater, peerKey string) *WGWatcher {
+func NewWGWatcher(log *log.Entry, wgIfaceStater WGInterfaceStater, peerKey string, stateDump *stateDump) *WGWatcher {
return &WGWatcher{
log: log,
wgIfaceStater: wgIfaceStater,
peerKey: peerKey,
+ stateDump: stateDump,
}
}
@@ -105,6 +107,7 @@ func (w *WGWatcher) periodicHandshakeCheck(ctx context.Context, ctxCancel contex
resetTime := time.Until(handshake.Add(checkPeriod))
timer.Reset(resetTime)
+ w.stateDump.WGcheckSuccess()
w.log.Debugf("WireGuard watcher reset timer: %v", resetTime)
case <-ctx.Done():
diff --git a/client/internal/peer/wg_watcher_test.go b/client/internal/peer/wg_watcher_test.go
index a5b9026ad..473485941 100644
--- a/client/internal/peer/wg_watcher_test.go
+++ b/client/internal/peer/wg_watcher_test.go
@@ -43,7 +43,7 @@ func TestWGWatcher_EnableWgWatcher(t *testing.T) {
mlog := log.WithField("peer", "tet")
mocWgIface := &MocWgIface{}
- watcher := NewWGWatcher(mlog, mocWgIface, "")
+ watcher := NewWGWatcher(mlog, mocWgIface, "", newStateDump(mlog))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -72,7 +72,7 @@ func TestWGWatcher_ReEnable(t *testing.T) {
mlog := log.WithField("peer", "tet")
mocWgIface := &MocWgIface{}
- watcher := NewWGWatcher(mlog, mocWgIface, "")
+ watcher := NewWGWatcher(mlog, mocWgIface, "", newStateDump(mlog))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
diff --git a/client/internal/peer/worker_relay.go b/client/internal/peer/worker_relay.go
index 56c19cd1e..aa8f7d635 100644
--- a/client/internal/peer/worker_relay.go
+++ b/client/internal/peer/worker_relay.go
@@ -33,14 +33,14 @@ type WorkerRelay struct {
wgWatcher *WGWatcher
}
-func NewWorkerRelay(log *log.Entry, ctrl bool, config ConnConfig, conn *Conn, relayManager relayClient.ManagerService) *WorkerRelay {
+func NewWorkerRelay(log *log.Entry, ctrl bool, config ConnConfig, conn *Conn, relayManager relayClient.ManagerService, stateDump *stateDump) *WorkerRelay {
r := &WorkerRelay{
log: log,
isController: ctrl,
config: config,
conn: conn,
relayManager: relayManager,
- wgWatcher: NewWGWatcher(log, config.WgConfig.WgInterface, config.Key),
+ wgWatcher: NewWGWatcher(log, config.WgConfig.WgInterface, config.Key, stateDump),
}
return r
}