package routetable import ( "net/netip" "slices" "time" ) // maxWaitingOriginChanges is the most origin changes the table keeps waiting // for the prefix handler to write. The oldest change past it is dropped and // counted. const maxWaitingOriginChanges = 100000 // OriginChange is one change of a prefix and origin AS pair: the origin AS // gained its first peer, or lost its last. type OriginChange struct { Prefix netip.Prefix OriginASN int // Announced says the origin AS gained its first peer. When false, it // lost its last peer. Announced bool // PeerCount is how many peers carry the prefix just after the change, // counting every origin AS. PeerCount int // Time is when RouteWatch applied the change. Time time.Time } // recordOriginChange records that the pair of prefix and asn gained its first // peer, when announced, or lost its last, when not. The caller holds the lock. // peerCount is how many peers carry the prefix just after the change. func (t *Table) recordOriginChange( prefix netip.Prefix, asn int, announced bool, peerCount int, ) { change := OriginChange{ Prefix: prefix, OriginASN: asn, Announced: announced, PeerCount: peerCount, Time: t.now(), } t.originChanges = append(t.originChanges, change) t.trimOriginChanges() } // TakeOriginChanges returns at most limit of the origin changes that wait to // be written, oldest first, and how many were dropped since the last call. It // then forgets what it returned. func (t *Table) TakeOriginChanges(limit int) ([]OriginChange, int) { t.mu.Lock() defer t.mu.Unlock() n := min(limit, len(t.originChanges)) changes := slices.Clone(t.originChanges[:n]) t.originChanges = t.originChanges[n:] dropped := t.originChangesDropped t.originChangesDropped = 0 return changes, dropped } // PutBackOriginChanges records again what one TakeOriginChanges call returned, // for a caller that could not write it. The next TakeOriginChanges returns // those changes first, before the ones that arrived since. The oldest changes // past the cap are dropped and counted. func (t *Table) PutBackOriginChanges(changes []OriginChange) { t.mu.Lock() defer t.mu.Unlock() t.originChanges = append(changes, t.originChanges...) t.trimOriginChanges() } // trimOriginChanges drops the oldest origin changes past the cap and counts // them. The caller holds the lock. func (t *Table) trimOriginChanges() { if extra := len(t.originChanges) - maxWaitingOriginChanges; extra > 0 { t.originChangesDropped += extra t.originChanges = t.originChanges[extra:] } } // OriginChangeCount returns how many origin changes wait for TakeOriginChanges. func (t *Table) OriginChangeCount() int { t.mu.RLock() defer t.mu.RUnlock() return len(t.originChanges) } // prefixPeerCount returns how many peers carry prefix, counting every origin // AS. The caller holds the lock. func (t *Table) prefixPeerCount(prefix netip.Prefix) int { count := 0 for _, o := range t.origins(prefix) { count += int(o.peers.Count()) } return count }