From ad4f0a6fdfae53a569cf20b78610567a78f83f02 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Thu, 31 Oct 2024 23:18:35 +0100 Subject: [PATCH 1/6] [client] Nil check on ICE remote conn (#2806) --- client/internal/peer/conn.go | 5 +++++ client/internal/peer/nilcheck.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 client/internal/peer/nilcheck.go diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 56b772759..84a8c221f 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -309,6 +309,11 @@ func (conn *Conn) iCEConnectionIsReady(priority ConnPriority, iceConnInfo ICECon return } + if remoteConnNil(conn.log, iceConnInfo.RemoteConn) { + conn.log.Errorf("remote ICE connection is nil") + return + } + conn.log.Debugf("ICE connection is ready") if conn.currentConnPriority > priority { diff --git a/client/internal/peer/nilcheck.go b/client/internal/peer/nilcheck.go new file mode 100644 index 000000000..058fe9a26 --- /dev/null +++ b/client/internal/peer/nilcheck.go @@ -0,0 +1,21 @@ +package peer + +import ( + "net" + + log "github.com/sirupsen/logrus" +) + +func remoteConnNil(log *log.Entry, conn net.Conn) bool { + if conn == nil { + log.Errorf("ice conn is nil") + return true + } + + if conn.RemoteAddr() == nil { + log.Errorf("ICE remote address is nil") + return true + } + + return false +} From 9812de853bac5c61ac38f6497014e7db1e4e290e Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Fri, 1 Nov 2024 00:33:25 +0100 Subject: [PATCH 2/6] Allocate new buffer for every package (#2823) --- client/iface/wgproxy/bind/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/iface/wgproxy/bind/proxy.go b/client/iface/wgproxy/bind/proxy.go index e986d6d7b..e0883715a 100644 --- a/client/iface/wgproxy/bind/proxy.go +++ b/client/iface/wgproxy/bind/proxy.go @@ -104,8 +104,8 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) { } }() - buf := make([]byte, 1500) for { + buf := make([]byte, 1500) n, err := p.remoteConn.Read(buf) if err != nil { if ctx.Err() != nil { From bac95ace1820d89f5642a528f877aad4e96539d9 Mon Sep 17 00:00:00 2001 From: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:58:39 +0100 Subject: [PATCH 3/6] [management] Add DB access duration to logs for context cancel (#2781) --- management/server/sql_store.go | 202 ++++++++++++++++++++++++++++-- management/server/status/error.go | 6 + 2 files changed, 198 insertions(+), 10 deletions(-) diff --git a/management/server/sql_store.go b/management/server/sql_store.go index 27238d28e..b1b8330ba 100644 --- a/management/server/sql_store.go +++ b/management/server/sql_store.go @@ -292,6 +292,8 @@ func (s *SqlStore) GetInstallationID() string { } func (s *SqlStore) SavePeer(ctx context.Context, accountID string, peer *nbpeer.Peer) error { + startTime := time.Now() + // To maintain data integrity, we create a copy of the peer's to prevent unintended updates to other fields. peerCopy := peer.Copy() peerCopy.AccountID = accountID @@ -317,6 +319,9 @@ func (s *SqlStore) SavePeer(ctx context.Context, accountID string, peer *nbpeer. }) if err != nil { + if errors.Is(err, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return err } @@ -324,6 +329,8 @@ func (s *SqlStore) SavePeer(ctx context.Context, accountID string, peer *nbpeer. } func (s *SqlStore) UpdateAccountDomainAttributes(ctx context.Context, accountID string, domain string, category string, isPrimaryDomain bool) error { + startTime := time.Now() + accountCopy := Account{ Domain: domain, DomainCategory: category, @@ -336,6 +343,9 @@ func (s *SqlStore) UpdateAccountDomainAttributes(ctx context.Context, accountID Where(idQueryCondition, accountID). Updates(&accountCopy) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return result.Error } @@ -347,6 +357,8 @@ func (s *SqlStore) UpdateAccountDomainAttributes(ctx context.Context, accountID } func (s *SqlStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error { + startTime := time.Now() + var peerCopy nbpeer.Peer peerCopy.Status = &peerStatus @@ -359,6 +371,9 @@ func (s *SqlStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.Pe Where(accountAndIDQueryCondition, accountID, peerID). Updates(&peerCopy) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return result.Error } @@ -370,6 +385,8 @@ func (s *SqlStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.Pe } func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.Peer) error { + startTime := time.Now() + // To maintain data integrity, we create a copy of the peer's location to prevent unintended updates to other fields. var peerCopy nbpeer.Peer // Since the location field has been migrated to JSON serialization, @@ -381,6 +398,9 @@ func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.P Updates(peerCopy) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return result.Error } @@ -394,6 +414,8 @@ func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.P // SaveUsers saves the given list of users to the database. // It updates existing users if a conflict occurs. func (s *SqlStore) SaveUsers(accountID string, users map[string]*User) error { + startTime := time.Now() + usersToSave := make([]User, 0, len(users)) for _, user := range users { user.AccountID = accountID @@ -403,15 +425,28 @@ func (s *SqlStore) SaveUsers(accountID string, users map[string]*User) error { } usersToSave = append(usersToSave, *user) } - return s.db.Session(&gorm.Session{FullSaveAssociations: true}). + err := s.db.Session(&gorm.Session{FullSaveAssociations: true}). Clauses(clause.OnConflict{UpdateAll: true}). Create(&usersToSave).Error + if err != nil { + if errors.Is(err, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } + return status.Errorf(status.Internal, "failed to save users to store: %v", err) + } + + return nil } // SaveUser saves the given user to the database. func (s *SqlStore) SaveUser(ctx context.Context, lockStrength LockingStrength, user *User) error { + startTime := time.Now() + result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Save(user) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "failed to save user to store: %v", result.Error) } return nil @@ -419,12 +454,17 @@ func (s *SqlStore) SaveUser(ctx context.Context, lockStrength LockingStrength, u // SaveGroups saves the given list of groups to the database. func (s *SqlStore) SaveGroups(ctx context.Context, lockStrength LockingStrength, groups []*nbgroup.Group) error { + startTime := time.Now() + if len(groups) == 0 { return nil } result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Save(&groups) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "failed to save groups to store: %v", result.Error) } return nil @@ -451,6 +491,8 @@ func (s *SqlStore) GetAccountByPrivateDomain(ctx context.Context, domain string) } func (s *SqlStore) GetAccountIDByPrivateDomain(ctx context.Context, lockStrength LockingStrength, domain string) (string, error) { + startTime := time.Now() + var accountID string result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&Account{}).Select("id"). Where("domain = ? and is_domain_primary_account = ? and domain_category = ?", @@ -460,6 +502,9 @@ func (s *SqlStore) GetAccountIDByPrivateDomain(ctx context.Context, lockStrength if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.Errorf(status.NotFound, "account not found: provided domain is not registered or is not private") } + if errors.Is(result.Error, context.Canceled) { + return "", status.NewStoreContextCanceledError(time.Since(startTime)) + } log.WithContext(ctx).Errorf("error when getting account from the store: %s", result.Error) return "", status.NewGetAccountFromStoreError(result.Error) } @@ -468,12 +513,17 @@ func (s *SqlStore) GetAccountIDByPrivateDomain(ctx context.Context, lockStrength } func (s *SqlStore) GetAccountBySetupKey(ctx context.Context, setupKey string) (*Account, error) { + startTime := time.Now() + var key SetupKey result := s.db.WithContext(ctx).Select("account_id").First(&key, keyQueryCondition, setupKey) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.NewSetupKeyNotFoundError(result.Error) } @@ -485,12 +535,17 @@ func (s *SqlStore) GetAccountBySetupKey(ctx context.Context, setupKey string) (* } func (s *SqlStore) GetTokenIDByHashedToken(ctx context.Context, hashedToken string) (string, error) { + startTime := time.Now() + var token PersonalAccessToken result := s.db.First(&token, "hashed_token = ?", hashedToken) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return "", status.NewStoreContextCanceledError(time.Since(startTime)) + } log.WithContext(ctx).Errorf("error when getting token from the store: %s", result.Error) return "", status.NewGetAccountFromStoreError(result.Error) } @@ -499,12 +554,17 @@ func (s *SqlStore) GetTokenIDByHashedToken(ctx context.Context, hashedToken stri } func (s *SqlStore) GetUserByTokenID(ctx context.Context, tokenID string) (*User, error) { + startTime := time.Now() + var token PersonalAccessToken result := s.db.First(&token, idQueryCondition, tokenID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } log.WithContext(ctx).Errorf("error when getting token from the store: %s", result.Error) return nil, status.NewGetAccountFromStoreError(result.Error) } @@ -528,6 +588,8 @@ func (s *SqlStore) GetUserByTokenID(ctx context.Context, tokenID string) (*User, } func (s *SqlStore) GetUserByUserID(ctx context.Context, lockStrength LockingStrength, userID string) (*User, error) { + startTime := time.Now() + var user User result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}). Preload(clause.Associations).First(&user, idQueryCondition, userID) @@ -535,6 +597,9 @@ func (s *SqlStore) GetUserByUserID(ctx context.Context, lockStrength LockingStre if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.NewUserNotFoundError(userID) } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.NewGetUserFromStoreError() } @@ -542,12 +607,17 @@ func (s *SqlStore) GetUserByUserID(ctx context.Context, lockStrength LockingStre } func (s *SqlStore) GetAccountUsers(ctx context.Context, accountID string) ([]*User, error) { + startTime := time.Now() + var users []*User result := s.db.Find(&users, accountIDCondition, accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "accountID not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } log.WithContext(ctx).Errorf("error when getting users from the store: %s", result.Error) return nil, status.Errorf(status.Internal, "issue getting users from store") } @@ -556,12 +626,17 @@ func (s *SqlStore) GetAccountUsers(ctx context.Context, accountID string) ([]*Us } func (s *SqlStore) GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) { + startTime := time.Now() + var groups []*nbgroup.Group result := s.db.Find(&groups, accountIDCondition, accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "accountID not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } log.WithContext(ctx).Errorf("error when getting groups from the store: %s", result.Error) return nil, status.Errorf(status.Internal, "issue getting groups from store") } @@ -661,12 +736,17 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*Account, } func (s *SqlStore) GetAccountByUser(ctx context.Context, userID string) (*Account, error) { + startTime := time.Now() + var user User result := s.db.WithContext(ctx).Select("account_id").First(&user, idQueryCondition, userID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.NewGetAccountFromStoreError(result.Error) } @@ -678,12 +758,17 @@ func (s *SqlStore) GetAccountByUser(ctx context.Context, userID string) (*Accoun } func (s *SqlStore) GetAccountByPeerID(ctx context.Context, peerID string) (*Account, error) { + startTime := time.Now() + var peer nbpeer.Peer result := s.db.WithContext(ctx).Select("account_id").First(&peer, idQueryCondition, peerID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.NewGetAccountFromStoreError(result.Error) } @@ -695,13 +780,17 @@ func (s *SqlStore) GetAccountByPeerID(ctx context.Context, peerID string) (*Acco } func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (*Account, error) { - var peer nbpeer.Peer + startTime := time.Now() + var peer nbpeer.Peer result := s.db.WithContext(ctx).Select("account_id").First(&peer, keyQueryCondition, peerKey) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.NewGetAccountFromStoreError(result.Error) } @@ -713,6 +802,8 @@ func (s *SqlStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) ( } func (s *SqlStore) GetAccountIDByPeerPubKey(ctx context.Context, peerKey string) (string, error) { + startTime := time.Now() + var peer nbpeer.Peer var accountID string result := s.db.WithContext(ctx).Model(&peer).Select("account_id").Where(keyQueryCondition, peerKey).First(&accountID) @@ -720,6 +811,9 @@ func (s *SqlStore) GetAccountIDByPeerPubKey(ctx context.Context, peerKey string) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return "", status.NewStoreContextCanceledError(time.Since(startTime)) + } return "", status.NewGetAccountFromStoreError(result.Error) } @@ -727,12 +821,17 @@ func (s *SqlStore) GetAccountIDByPeerPubKey(ctx context.Context, peerKey string) } func (s *SqlStore) GetAccountIDByUserID(userID string) (string, error) { + startTime := time.Now() + var accountID string result := s.db.Model(&User{}).Select("account_id").Where(idQueryCondition, userID).First(&accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return "", status.NewStoreContextCanceledError(time.Since(startTime)) + } return "", status.NewGetAccountFromStoreError(result.Error) } @@ -740,12 +839,17 @@ func (s *SqlStore) GetAccountIDByUserID(userID string) (string, error) { } func (s *SqlStore) GetAccountIDBySetupKey(ctx context.Context, setupKey string) (string, error) { + startTime := time.Now() + var accountID string result := s.db.WithContext(ctx).Model(&SetupKey{}).Select("account_id").Where(keyQueryCondition, setupKey).First(&accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", status.Errorf(status.NotFound, "account not found: index lookup failed") } + if errors.Is(result.Error, context.Canceled) { + return "", status.NewStoreContextCanceledError(time.Since(startTime)) + } return "", status.NewSetupKeyNotFoundError(result.Error) } @@ -757,6 +861,8 @@ func (s *SqlStore) GetAccountIDBySetupKey(ctx context.Context, setupKey string) } func (s *SqlStore) GetTakenIPs(ctx context.Context, lockStrength LockingStrength, accountID string) ([]net.IP, error) { + startTime := time.Now() + var ipJSONStrings []string // Fetch the IP addresses as JSON strings @@ -767,6 +873,9 @@ func (s *SqlStore) GetTakenIPs(ctx context.Context, lockStrength LockingStrength if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "no peers found for the account") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.Errorf(status.Internal, "issue getting IPs from store: %s", result.Error) } @@ -784,8 +893,9 @@ func (s *SqlStore) GetTakenIPs(ctx context.Context, lockStrength LockingStrength } func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountID string) ([]string, error) { - var labels []string + startTime := time.Now() + var labels []string result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&nbpeer.Peer{}). Where("account_id = ?", accountID). Pluck("dns_label", &labels) @@ -794,6 +904,9 @@ func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength Lock if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "no peers found for the account") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } log.WithContext(ctx).Errorf("error when getting dns labels from the store: %s", result.Error) return nil, status.Errorf(status.Internal, "issue getting dns labels from store: %s", result.Error) } @@ -802,24 +915,33 @@ func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength Lock } func (s *SqlStore) GetAccountNetwork(ctx context.Context, lockStrength LockingStrength, accountID string) (*Network, error) { - var accountNetwork AccountNetwork + startTime := time.Now() + var accountNetwork AccountNetwork if err := s.db.WithContext(ctx).Model(&Account{}).Where(idQueryCondition, accountID).First(&accountNetwork).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.NewAccountNotFoundError(accountID) } + if errors.Is(err, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.Errorf(status.Internal, "issue getting network from store: %s", err) } return accountNetwork.Network, nil } func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) { + startTime := time.Now() + var peer nbpeer.Peer result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).First(&peer, keyQueryCondition, peerKey) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "peer not found") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.Errorf(status.Internal, "issue getting peer from store: %s", result.Error) } @@ -827,11 +949,16 @@ func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength Locking } func (s *SqlStore) GetAccountSettings(ctx context.Context, lockStrength LockingStrength, accountID string) (*Settings, error) { + startTime := time.Now() + var accountSettings AccountSettings if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&Account{}).Where(idQueryCondition, accountID).First(&accountSettings).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "settings not found") } + if errors.Is(err, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.Errorf(status.Internal, "issue getting settings from store: %s", err) } return accountSettings.Settings, nil @@ -839,13 +966,17 @@ func (s *SqlStore) GetAccountSettings(ctx context.Context, lockStrength LockingS // SaveUserLastLogin stores the last login time for a user in DB. func (s *SqlStore) SaveUserLastLogin(ctx context.Context, accountID, userID string, lastLogin time.Time) error { - var user User + startTime := time.Now() + var user User result := s.db.WithContext(ctx).First(&user, accountAndIDQueryCondition, accountID, userID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return status.NewUserNotFoundError(userID) } + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.NewGetUserFromStoreError() } user.LastLogin = lastLogin @@ -854,6 +985,8 @@ func (s *SqlStore) SaveUserLastLogin(ctx context.Context, accountID, userID stri } func (s *SqlStore) GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) { + startTime := time.Now() + definitionJSON, err := json.Marshal(checks) if err != nil { return nil, err @@ -862,6 +995,9 @@ func (s *SqlStore) GetPostureCheckByChecksDefinition(accountID string, checks *p var postureCheck posture.Checks err = s.db.Where("account_id = ? AND checks = ?", accountID, string(definitionJSON)).First(&postureCheck).Error if err != nil { + if errors.Is(err, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, err } @@ -971,6 +1107,8 @@ func NewPostgresqlStoreFromSqlStore(ctx context.Context, sqliteStore *SqlStore, } func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength LockingStrength, key string) (*SetupKey, error) { + startTime := time.Now() + var setupKey SetupKey result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}). First(&setupKey, keyQueryCondition, key) @@ -978,12 +1116,17 @@ func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength Locking if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "setup key not found") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.NewSetupKeyNotFoundError(result.Error) } return &setupKey, nil } func (s *SqlStore) IncrementSetupKeyUsage(ctx context.Context, setupKeyID string) error { + startTime := time.Now() + result := s.db.WithContext(ctx).Model(&SetupKey{}). Where(idQueryCondition, setupKeyID). Updates(map[string]interface{}{ @@ -992,6 +1135,9 @@ func (s *SqlStore) IncrementSetupKeyUsage(ctx context.Context, setupKeyID string }) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue incrementing setup key usage count: %s", result.Error) } @@ -1003,13 +1149,17 @@ func (s *SqlStore) IncrementSetupKeyUsage(ctx context.Context, setupKeyID string } func (s *SqlStore) AddPeerToAllGroup(ctx context.Context, accountID string, peerID string) error { - var group nbgroup.Group + startTime := time.Now() + var group nbgroup.Group result := s.db.WithContext(ctx).Where("account_id = ? AND name = ?", accountID, "All").First(&group) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return status.Errorf(status.NotFound, "group 'All' not found for account") } + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue finding group 'All': %s", result.Error) } @@ -1022,6 +1172,9 @@ func (s *SqlStore) AddPeerToAllGroup(ctx context.Context, accountID string, peer group.Peers = append(group.Peers, peerID) if err := s.db.Save(&group).Error; err != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue updating group 'All': %s", err) } @@ -1029,13 +1182,17 @@ func (s *SqlStore) AddPeerToAllGroup(ctx context.Context, accountID string, peer } func (s *SqlStore) AddPeerToGroup(ctx context.Context, accountId string, peerId string, groupID string) error { - var group nbgroup.Group + startTime := time.Now() + var group nbgroup.Group result := s.db.WithContext(ctx).Where(accountAndIDQueryCondition, accountId, groupID).First(&group) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return status.Errorf(status.NotFound, "group not found for account") } + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue finding group: %s", result.Error) } @@ -1048,6 +1205,9 @@ func (s *SqlStore) AddPeerToGroup(ctx context.Context, accountId string, peerId group.Peers = append(group.Peers, peerId) if err := s.db.Save(&group).Error; err != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue updating group: %s", err) } @@ -1060,7 +1220,12 @@ func (s *SqlStore) GetUserPeers(ctx context.Context, lockStrength LockingStrengt } func (s *SqlStore) AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) error { + startTime := time.Now() + if err := s.db.WithContext(ctx).Create(peer).Error; err != nil { + if errors.Is(err, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue adding peer to account: %s", err) } @@ -1068,8 +1233,13 @@ func (s *SqlStore) AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) erro } func (s *SqlStore) IncrementNetworkSerial(ctx context.Context, accountId string) error { + startTime := time.Now() + result := s.db.WithContext(ctx).Model(&Account{}).Where(idQueryCondition, accountId).Update("network_serial", gorm.Expr("network_serial + 1")) if result.Error != nil { + if errors.Is(result.Error, context.Canceled) { + return status.NewStoreContextCanceledError(time.Since(startTime)) + } return status.Errorf(status.Internal, "issue incrementing network serial count: %s", result.Error) } return nil @@ -1100,14 +1270,18 @@ func (s *SqlStore) GetDB() *gorm.DB { } func (s *SqlStore) GetAccountDNSSettings(ctx context.Context, lockStrength LockingStrength, accountID string) (*DNSSettings, error) { - var accountDNSSettings AccountDNSSettings + startTime := time.Now() + var accountDNSSettings AccountDNSSettings result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&Account{}). First(&accountDNSSettings, idQueryCondition, accountID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, status.Errorf(status.NotFound, "dns settings not found") } + if errors.Is(result.Error, context.Canceled) { + return nil, status.NewStoreContextCanceledError(time.Since(startTime)) + } return nil, status.Errorf(status.Internal, "failed to get dns settings from store: %v", result.Error) } return &accountDNSSettings.DNSSettings, nil @@ -1115,14 +1289,18 @@ func (s *SqlStore) GetAccountDNSSettings(ctx context.Context, lockStrength Locki // AccountExists checks whether an account exists by the given ID. func (s *SqlStore) AccountExists(ctx context.Context, lockStrength LockingStrength, id string) (bool, error) { - var accountID string + startTime := time.Now() + var accountID string result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&Account{}). Select("id").First(&accountID, idQueryCondition, id) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return false, nil } + if errors.Is(result.Error, context.Canceled) { + return false, status.NewStoreContextCanceledError(time.Since(startTime)) + } return false, result.Error } @@ -1131,14 +1309,18 @@ func (s *SqlStore) AccountExists(ctx context.Context, lockStrength LockingStreng // GetAccountDomainAndCategory retrieves the Domain and DomainCategory fields for an account based on the given accountID. func (s *SqlStore) GetAccountDomainAndCategory(ctx context.Context, lockStrength LockingStrength, accountID string) (string, string, error) { - var account Account + startTime := time.Now() + var account Account result := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&Account{}).Select("domain", "domain_category"). Where(idQueryCondition, accountID).First(&account) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return "", "", status.Errorf(status.NotFound, "account not found") } + if errors.Is(result.Error, context.Canceled) { + return "", "", status.NewStoreContextCanceledError(time.Since(startTime)) + } return "", "", status.Errorf(status.Internal, "failed to get domain category from store: %v", result.Error) } diff --git a/management/server/status/error.go b/management/server/status/error.go index e9fc8c15e..a145edf80 100644 --- a/management/server/status/error.go +++ b/management/server/status/error.go @@ -3,6 +3,7 @@ package status import ( "errors" "fmt" + "time" ) const ( @@ -115,6 +116,11 @@ func NewGetUserFromStoreError() error { return Errorf(Internal, "issue getting user from store") } +// NewStoreContextCanceledError creates a new Error with Internal type for a canceled store context +func NewStoreContextCanceledError(duration time.Duration) error { + return Errorf(Internal, "store access: context canceled after %v", duration) +} + // NewInvalidKeyIDError creates a new Error with InvalidArgument type for an issue getting a setup key func NewInvalidKeyIDError() error { return Errorf(InvalidArgument, "invalid key ID") From 0eb99c266affccaa03d9c363862655edd8798b22 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Fri, 1 Nov 2024 12:33:29 +0100 Subject: [PATCH 4/6] Fix unused servers cleanup (#2826) The cleanup loop did not manage those situations well when a connection failed or the connection success but the code did not add a peer connection to it yet. - in the cleanup loop check if a connection failed to a server - after adding a foreign server connection force to keep it a minimum 5 sec --- relay/client/manager.go | 18 +++++++++++++++++- relay/client/manager_test.go | 5 +++-- relay/client/picker_test.go | 3 +-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/relay/client/manager.go b/relay/client/manager.go index 3981415fc..b14a7701b 100644 --- a/relay/client/manager.go +++ b/relay/client/manager.go @@ -16,6 +16,7 @@ import ( var ( relayCleanupInterval = 60 * time.Second + keepUnusedServerTime = 5 * time.Second ErrRelayClientNotConnected = fmt.Errorf("relay client not connected") ) @@ -27,10 +28,13 @@ type RelayTrack struct { sync.RWMutex relayClient *Client err error + created time.Time } func NewRelayTrack() *RelayTrack { - return &RelayTrack{} + return &RelayTrack{ + created: time.Now(), + } } type OnServerCloseListener func() @@ -302,6 +306,18 @@ func (m *Manager) cleanUpUnusedRelays() { for addr, rt := range m.relayClients { rt.Lock() + // if the connection failed to the server the relay client will be nil + // but the instance will be kept in the relayClients until the next locking + if rt.err != nil { + rt.Unlock() + continue + } + + if time.Since(rt.created) <= keepUnusedServerTime { + rt.Unlock() + continue + } + if rt.relayClient.HasConns() { rt.Unlock() continue diff --git a/relay/client/manager_test.go b/relay/client/manager_test.go index e9cc2c581..bfc342f25 100644 --- a/relay/client/manager_test.go +++ b/relay/client/manager_test.go @@ -288,8 +288,9 @@ func TestForeginAutoClose(t *testing.T) { t.Fatalf("failed to close connection: %s", err) } - t.Logf("waiting for relay cleanup: %s", relayCleanupInterval+1*time.Second) - time.Sleep(relayCleanupInterval + 1*time.Second) + timeout := relayCleanupInterval + keepUnusedServerTime + 1*time.Second + t.Logf("waiting for relay cleanup: %s", timeout) + time.Sleep(timeout) if len(mgr.relayClients) != 0 { t.Errorf("expected 0, got %d", len(mgr.relayClients)) } diff --git a/relay/client/picker_test.go b/relay/client/picker_test.go index eb14581e0..4800e05ba 100644 --- a/relay/client/picker_test.go +++ b/relay/client/picker_test.go @@ -4,7 +4,6 @@ import ( "context" "errors" "testing" - "time" ) func TestServerPicker_UnavailableServers(t *testing.T) { @@ -13,7 +12,7 @@ func TestServerPicker_UnavailableServers(t *testing.T) { PeerID: "test", } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), connectionTimeout+1) defer cancel() go func() { From 5f06b202c364dd66e57b8a58f178a8647a6ddfce Mon Sep 17 00:00:00 2001 From: Viktor Liu <17948409+lixmal@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:08:22 +0100 Subject: [PATCH 5/6] [client] Log windows panics (#2829) --- client/server/panic_generic.go | 7 +++ client/server/panic_windows.go | 83 ++++++++++++++++++++++++++++++++++ client/server/server.go | 4 ++ 3 files changed, 94 insertions(+) create mode 100644 client/server/panic_generic.go create mode 100644 client/server/panic_windows.go diff --git a/client/server/panic_generic.go b/client/server/panic_generic.go new file mode 100644 index 000000000..f027b954b --- /dev/null +++ b/client/server/panic_generic.go @@ -0,0 +1,7 @@ +//go:build !windows + +package server + +func handlePanicLog() error { + return nil +} diff --git a/client/server/panic_windows.go b/client/server/panic_windows.go new file mode 100644 index 000000000..1d4ba4b75 --- /dev/null +++ b/client/server/panic_windows.go @@ -0,0 +1,83 @@ +package server + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/util" +) + +const ( + windowsPanicLogEnvVar = "NB_WINDOWS_PANIC_LOG" + // STD_ERROR_HANDLE ((DWORD)-12) = 4294967284 + stdErrorHandle = ^uintptr(11) +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + + // https://learn.microsoft.com/en-us/windows/console/setstdhandle + setStdHandleFn = kernel32.NewProc("SetStdHandle") +) + +func handlePanicLog() error { + logPath := os.Getenv(windowsPanicLogEnvVar) + if logPath == "" { + return nil + } + + // Ensure the directory exists + logDir := filepath.Dir(logPath) + if err := os.MkdirAll(logDir, 0750); err != nil { + return fmt.Errorf("create panic log directory: %w", err) + } + if err := util.EnforcePermission(logPath); err != nil { + return fmt.Errorf("enforce permission on panic log file: %w", err) + } + + // Open log file with append mode + f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("open panic log file: %w", err) + } + + // Redirect stderr to the file + if err = redirectStderr(f); err != nil { + if closeErr := f.Close(); closeErr != nil { + log.Warnf("failed to close file after redirect error: %v", closeErr) + } + return fmt.Errorf("redirect stderr: %w", err) + } + + log.Infof("successfully configured panic logging to: %s", logPath) + return nil +} + +// redirectStderr redirects stderr to the provided file +func redirectStderr(f *os.File) error { + // Get the current process's stderr handle + if err := setStdHandle(f); err != nil { + return fmt.Errorf("failed to set stderr handle: %w", err) + } + + // Also set os.Stderr for Go's standard library + os.Stderr = f + + return nil +} + +func setStdHandle(f *os.File) error { + handle := f.Fd() + r0, _, e1 := setStdHandleFn.Call(stdErrorHandle, handle) + if r0 == 0 { + if e1 != nil { + return e1 + } + return syscall.EINVAL + } + return nil +} diff --git a/client/server/server.go b/client/server/server.go index a03322081..4d921851f 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -97,6 +97,10 @@ func (s *Server) Start() error { defer s.mutex.Unlock() state := internal.CtxGetState(s.rootCtx) + if err := handlePanicLog(); err != nil { + log.Warnf("failed to redirect stderr: %v", err) + } + if err := restoreResidualState(s.rootCtx); err != nil { log.Warnf(errRestoreResidualState, err) } From a9d06b883fe742c5dd03b822ba2385203e1b1682 Mon Sep 17 00:00:00 2001 From: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:09:08 +0100 Subject: [PATCH 6/6] add all group to add peer affected peers network map check (#2830) --- management/server/peer.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/management/server/peer.go b/management/server/peer.go index 96ede1511..7cc2209c5 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -589,6 +589,12 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s return nil, nil, nil, fmt.Errorf("error getting account: %w", err) } + allGroup, err := account.GetGroupAll() + if err != nil { + return nil, nil, nil, fmt.Errorf("error getting all group ID: %w", err) + } + + groupsToAdd = append(groupsToAdd, allGroup.ID) if areGroupChangesAffectPeers(account, groupsToAdd) { am.updateAccountPeers(ctx, account) }