Compare commits

...

6 Commits

Author SHA1 Message Date
Dmitri Dolguikh
1520caf175 Merge remote-tracking branch 'origin/main' into dmitri-catch-disconnected-peer
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 16:58:08 +02:00
Dmitri Dolguikh
98b8373db7 use 1 min ticker interval
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 16:34:06 +02:00
Dmitri Dolguikh
a1254da9a7 fix time interval check
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 16:27:42 +02:00
Dmitri Dolguikh
b19183df2c Merge remote-tracking branch 'origin/main' into dmitri-catch-disconnected-peer
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 16:23:27 +02:00
Dmitri Dolguikh
218febdf79 fix linter issues
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 16:23:12 +02:00
Dmitri Dolguikh
cf4b9f8333 use peer.Status.LastSeen field to detect stale peer.Status.Connected values
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 16:06:50 +02:00
6 changed files with 52 additions and 0 deletions

View File

@@ -424,6 +424,9 @@ func (s *Server) handleUpdates(ctx context.Context, accountID string, peerKey wg
debouncer := NewUpdateDebouncer(1000 * time.Millisecond)
defer debouncer.Stop()
tickerInterval := 1 * time.Minute
ticker := time.NewTicker(tickerInterval)
for {
select {
// condition when there are some updates
@@ -448,6 +451,12 @@ func (s *Server) handleUpdates(ctx context.Context, accountID string, peerKey wg
}
}
ticker.Reset(tickerInterval)
if err := s.accountManager.RefreshPeerLastSeen(ctx, accountID, peer.ID); err != nil {
log.WithContext(ctx).Debugf("error updating peer's last seen timestamp %s: %v", peerKey.String(), err)
return err
}
// Timer expired - quiet period reached, send pending updates if any
case <-debouncer.TimerChannel():
pendingUpdates := debouncer.GetPendingUpdates()
@@ -462,6 +471,18 @@ func (s *Server) handleUpdates(ctx context.Context, accountID string, peerKey wg
}
}
ticker.Reset(tickerInterval)
if err := s.accountManager.RefreshPeerLastSeen(ctx, accountID, peer.ID); err != nil {
log.WithContext(ctx).Debugf("error updating peer's last seen timestamp %s: %v", peerKey.String(), err)
return err
}
case <-ticker.C:
if err := s.accountManager.RefreshPeerLastSeen(ctx, accountID, peer.ID); err != nil {
log.WithContext(ctx).Debugf("error updating peer's last seen timestamp %s: %v", peerKey.String(), err)
return err
}
// condition when client <-> server connection has been terminated
case <-srv.Context().Done():
// happens when connection drops, e.g. client disconnects

View File

@@ -64,6 +64,7 @@ type Manager interface {
GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
MarkPeerConnected(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64, nmap *types.NetworkMap) error
MarkPeerDisconnected(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64) error
RefreshPeerLastSeen(ctx context.Context, accountId, peerId string) error
DeletePeer(ctx context.Context, accountID, peerID, userID string) error
UpdatePeer(ctx context.Context, accountID, userID string, p *nbpeer.Peer) (*nbpeer.Peer, error)
UpdatePeerIP(ctx context.Context, accountID, userID, peerID string, newIP netip.Addr) error

View File

@@ -1382,6 +1382,20 @@ func (mr *MockManagerMockRecorder) OnPeerDisconnected(ctx, accountID, peerPubKey
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnPeerDisconnected", reflect.TypeOf((*MockManager)(nil).OnPeerDisconnected), ctx, accountID, peerPubKey, streamStartTime)
}
// RefreshPeerLastSeen mocks base method.
func (m *MockManager) RefreshPeerLastSeen(ctx context.Context, accountId, peerId string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshPeerLastSeen", ctx, accountId, peerId)
ret0, _ := ret[0].(error)
return ret0
}
// RefreshPeerLastSeen indicates an expected call of RefreshPeerLastSeen.
func (mr *MockManagerMockRecorder) RefreshPeerLastSeen(ctx, accountId, peerId any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshPeerLastSeen", reflect.TypeOf((*MockManager)(nil).RefreshPeerLastSeen), ctx, accountId, peerId)
}
// RegenerateUserInvite mocks base method.
func (m *MockManager) RegenerateUserInvite(ctx context.Context, accountID, initiatorUserID, inviteID string, expiresIn int) (*types.UserInvite, error) {
m.ctrl.T.Helper()

View File

@@ -41,6 +41,7 @@ type MockAccountManager struct {
GetPeersFunc func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
MarkPeerConnectedFunc func(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64, nmap *types.NetworkMap) error
MarkPeerDisconnectedFunc func(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64) error
RefreshPeerLastSeenFunc func(ctx context.Context, accountId, peerId string) error
SyncAndMarkPeerFunc func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP, syncTime time.Time) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, int64, error)
DeletePeerFunc func(ctx context.Context, accountID, peerKey, userID string) error
GetNetworkMapFunc func(ctx context.Context, peerKey string) (*types.NetworkMap, error)
@@ -360,6 +361,13 @@ func (am *MockAccountManager) MarkPeerDisconnected(ctx context.Context, peerKey
return status.Errorf(codes.Unimplemented, "method MarkPeerDisconnected is not implemented")
}
func (am *MockAccountManager) RefreshPeerLastSeen(ctx context.Context, accountId, peerId string) error {
if am.RefreshPeerLastSeenFunc != nil {
return am.RefreshPeerLastSeenFunc(ctx, accountId, peerId)
}
return status.Errorf(codes.Unimplemented, "method RefreshPeerLastSeen is not implemented")
}
// DeleteAccount mock implementation of DeleteAccount from server.AccountManager interface
func (am *MockAccountManager) DeleteAccount(ctx context.Context, accountID, userID string) error {
if am.DeleteAccountFunc != nil {

View File

@@ -136,6 +136,11 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
return nil
}
func (am *DefaultAccountManager) RefreshPeerLastSeen(ctx context.Context, accountId, peerId string) error {
_, err := am.Store.RefreshPeerLastSeen(ctx, accountId, peerId, time.Now())
return err
}
// schedulePeerExpirations reschedules the account's login/inactivity expiration
// timers for an SSO peer that just connected.
func (am *DefaultAccountManager) schedulePeerExpirations(ctx context.Context, accountID string, peer *nbpeer.Peer) error {

View File

@@ -1962,6 +1962,9 @@ func (s *SqlStore) getPeers(ctx context.Context, accountID string) ([]nbpeer.Pee
}
if peerStatusConnected.Valid {
p.Status.Connected = peerStatusConnected.Bool
if peerStatusLastSeen.Valid {
p.Status.Connected = p.Status.Connected && peerStatusLastSeen.Time.After(time.Now().Add(-5*time.Minute))
}
}
if peerStatusLoginExpired.Valid {
p.Status.LoginExpired = peerStatusLoginExpired.Bool