mirror of https://github.com/netbirdio/netbird.git
233 lines
5.2 KiB
Go
233 lines
5.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
)
|
|
|
|
const (
|
|
ephemeralLifeTime = 10 * time.Minute
|
|
)
|
|
|
|
var (
|
|
timeNow = time.Now
|
|
)
|
|
|
|
type ephemeralPeer struct {
|
|
id string
|
|
account *Account
|
|
deadline time.Time
|
|
next *ephemeralPeer
|
|
}
|
|
|
|
// todo: consider to remove peer from ephemeral list when the peer has been deleted via API. If we do not do it
|
|
// in worst case we will get invalid error message in this manager.
|
|
|
|
// EphemeralManager keep a list of ephemeral peers. After ephemeralLifeTime inactivity the peer will be deleted
|
|
// automatically. Inactivity means the peer disconnected from the Management server.
|
|
type EphemeralManager struct {
|
|
store Store
|
|
accountManager AccountManager
|
|
|
|
headPeer *ephemeralPeer
|
|
tailPeer *ephemeralPeer
|
|
peersLock sync.Mutex
|
|
timer *time.Timer
|
|
}
|
|
|
|
// NewEphemeralManager instantiate new EphemeralManager
|
|
func NewEphemeralManager(store Store, accountManager AccountManager) *EphemeralManager {
|
|
return &EphemeralManager{
|
|
store: store,
|
|
accountManager: accountManager,
|
|
}
|
|
}
|
|
|
|
// LoadInitialPeers load from the database the ephemeral type of peers and schedule a cleanup procedure to the head
|
|
// of the linked list (to the most deprecated peer). At the end of cleanup it schedules the next cleanup to the new
|
|
// head.
|
|
func (e *EphemeralManager) LoadInitialPeers(ctx context.Context) {
|
|
e.peersLock.Lock()
|
|
defer e.peersLock.Unlock()
|
|
|
|
e.loadEphemeralPeers(ctx)
|
|
if e.headPeer != nil {
|
|
e.timer = time.AfterFunc(ephemeralLifeTime, func() {
|
|
e.cleanup(ctx)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Stop timer
|
|
func (e *EphemeralManager) Stop() {
|
|
e.peersLock.Lock()
|
|
defer e.peersLock.Unlock()
|
|
|
|
if e.timer != nil {
|
|
e.timer.Stop()
|
|
}
|
|
}
|
|
|
|
// OnPeerConnected remove the peer from the linked list of ephemeral peers. Because it has been called when the peer
|
|
// is active the manager will not delete it while it is active.
|
|
func (e *EphemeralManager) OnPeerConnected(ctx context.Context, peer *nbpeer.Peer) {
|
|
if !peer.Ephemeral {
|
|
return
|
|
}
|
|
|
|
log.WithContext(ctx).Tracef("remove peer from ephemeral list: %s", peer.ID)
|
|
|
|
e.peersLock.Lock()
|
|
defer e.peersLock.Unlock()
|
|
|
|
e.removePeer(peer.ID)
|
|
|
|
// stop the unnecessary timer
|
|
if e.headPeer == nil && e.timer != nil {
|
|
e.timer.Stop()
|
|
e.timer = nil
|
|
}
|
|
}
|
|
|
|
// OnPeerDisconnected add the peer to the linked list of ephemeral peers. Because of the peer
|
|
// is inactive it will be deleted after the ephemeralLifeTime period.
|
|
func (e *EphemeralManager) OnPeerDisconnected(ctx context.Context, peer *nbpeer.Peer) {
|
|
if !peer.Ephemeral {
|
|
return
|
|
}
|
|
|
|
log.WithContext(ctx).Tracef("add peer to ephemeral list: %s", peer.ID)
|
|
|
|
a, err := e.store.GetAccountByPeerID(context.Background(), peer.ID)
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("failed to add peer to ephemeral list: %s", err)
|
|
return
|
|
}
|
|
|
|
e.peersLock.Lock()
|
|
defer e.peersLock.Unlock()
|
|
|
|
if e.isPeerOnList(peer.ID) {
|
|
return
|
|
}
|
|
|
|
e.addPeer(peer.ID, a, newDeadLine())
|
|
if e.timer == nil {
|
|
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
|
|
e.cleanup(ctx)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (e *EphemeralManager) loadEphemeralPeers(ctx context.Context) {
|
|
accounts := e.store.GetAllAccounts(context.Background())
|
|
t := newDeadLine()
|
|
count := 0
|
|
for _, a := range accounts {
|
|
for id, p := range a.Peers {
|
|
if p.Ephemeral {
|
|
count++
|
|
e.addPeer(id, a, t)
|
|
}
|
|
}
|
|
}
|
|
log.WithContext(ctx).Debugf("loaded ephemeral peer(s): %d", count)
|
|
}
|
|
|
|
func (e *EphemeralManager) cleanup(ctx context.Context) {
|
|
log.Tracef("on ephemeral cleanup")
|
|
deletePeers := make(map[string]*ephemeralPeer)
|
|
|
|
e.peersLock.Lock()
|
|
now := timeNow()
|
|
for p := e.headPeer; p != nil; p = p.next {
|
|
if now.Before(p.deadline) {
|
|
break
|
|
}
|
|
|
|
deletePeers[p.id] = p
|
|
e.headPeer = p.next
|
|
if p.next == nil {
|
|
e.tailPeer = nil
|
|
}
|
|
}
|
|
|
|
if e.headPeer != nil {
|
|
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
|
|
e.cleanup(ctx)
|
|
})
|
|
} else {
|
|
e.timer = nil
|
|
}
|
|
|
|
e.peersLock.Unlock()
|
|
|
|
for id, p := range deletePeers {
|
|
log.WithContext(ctx).Debugf("delete ephemeral peer: %s", id)
|
|
err := e.accountManager.DeletePeer(ctx, p.account.Id, id, activity.SystemInitiator)
|
|
if err != nil {
|
|
log.WithContext(ctx).Errorf("failed to delete ephemeral peer: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *EphemeralManager) addPeer(id string, account *Account, deadline time.Time) {
|
|
ep := &ephemeralPeer{
|
|
id: id,
|
|
account: account,
|
|
deadline: deadline,
|
|
}
|
|
|
|
if e.headPeer == nil {
|
|
e.headPeer = ep
|
|
}
|
|
if e.tailPeer != nil {
|
|
e.tailPeer.next = ep
|
|
}
|
|
e.tailPeer = ep
|
|
}
|
|
|
|
func (e *EphemeralManager) removePeer(id string) {
|
|
if e.headPeer == nil {
|
|
return
|
|
}
|
|
|
|
if e.headPeer.id == id {
|
|
e.headPeer = e.headPeer.next
|
|
if e.tailPeer.id == id {
|
|
e.tailPeer = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
for p := e.headPeer; p.next != nil; p = p.next {
|
|
if p.next.id == id {
|
|
// if we remove the last element from the chain then set the last-1 as tail
|
|
if e.tailPeer.id == id {
|
|
e.tailPeer = p
|
|
}
|
|
p.next = p.next.next
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *EphemeralManager) isPeerOnList(id string) bool {
|
|
for p := e.headPeer; p != nil; p = p.next {
|
|
if p.id == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func newDeadLine() time.Time {
|
|
return timeNow().Add(ephemeralLifeTime)
|
|
}
|