package routetable import ( "net/netip" "slices" "time" "github.com/bits-and-blooms/bitset" ) // PrefixSummary is what the table answers about one prefix. type PrefixSummary struct { Prefix netip.Prefix // OriginASN is the origin AS that the most peers carry the prefix with. // When several have as many peers, it is the lowest AS number. OriginASN int // Peers is how many peers carry the prefix. Peers int // Newest is the time of the newest kept route of the prefix, or nil when // it keeps none. Newest *time.Time } // OriginatedPrefix is one prefix an AS originates. type OriginatedPrefix struct { Prefix netip.Prefix // Newest is the time of the newest kept route of the prefix with that // origin AS, or nil when it keeps none. Newest *time.Time } // Counts are the table's figures for the stats. type Counts struct { // Pairs counts the prefix and origin AS pairs that some peer carries. Pairs int // Peers counts the peers that have a number: those given in // Params.PeerNumbers and those numbered since. Peers int IPv4Prefixes int IPv6Prefixes int // IPv4PrefixesByLength[n] and IPv6PrefixesByLength[n] are how many // prefixes of mask length n some peer carries. IPv4PrefixesByLength []int IPv6PrefixesByLength []int // IPv4Routes and IPv6Routes count peer and prefix pairs: each peer that // carries a prefix counts once for it. IPv4Routes int IPv6Routes int // OriginsWithoutKeptRoute counts the prefix and origin AS pairs that peers // carry but whose kept routes were all withdrawn. OriginsWithoutKeptRoute int // OldestKeptRoute and NewestKeptRoute are the times of the oldest and the // newest kept routes, or nil when the table keeps none. OldestKeptRoute *time.Time NewestKeptRoute *time.Time } // Lookup returns the most specific prefix that holds addr and that some peer // carries. It ignores prefixes shorter than /8 for IPv4 and /16 for IPv6. It // makes one map lookup per mask length, and reports false when no prefix // holds addr. func (t *Table) Lookup(addr netip.Addr) (PrefixSummary, bool) { t.mu.RLock() defer t.mu.RUnlock() // An IPv4 address written in IPv6 form (::ffff:192.0.2.1) is looked up as // IPv4. addr = addr.Unmap() f := t.familyOf(addr) for bits := addr.BitLen(); bits >= f.minLookupLength; bits-- { prefix := netip.PrefixFrom(addr, bits).Masked() origins, found := f.byLength[bits][prefix] if found { return summarize(prefix, origins), true } } return PrefixSummary{}, false } // OriginatedPrefixes returns the prefixes AS asn originates, in prefix order. func (t *Table) OriginatedPrefixes(asn int) []OriginatedPrefix { t.mu.RLock() defer t.mu.RUnlock() prefixes := make([]OriginatedPrefix, 0, len(t.byOrigin[asn])) for prefix, o := range t.byOrigin[asn] { prefixes = append(prefixes, OriginatedPrefix{Prefix: prefix, Newest: o.newestTime()}) } slices.SortFunc(prefixes, func(a, b OriginatedPrefix) int { return a.Prefix.Compare(b.Prefix) }) return prefixes } // Origins returns each origin AS of prefix, in AS number order, with its peers // and its kept routes. It returns nil when no peer carries the prefix. func (t *Table) Origins(prefix netip.Prefix) []Origin { t.mu.RLock() defer t.mu.RUnlock() if !prefix.IsValid() { return nil } var origins []Origin for _, o := range t.origins(prefix.Masked()) { origins = append(origins, Origin{ ASN: o.asn, Peers: t.addresses(&o.peers), Routes: t.exportRoutes(o.routes), }) } return origins } // SamplePrefixes returns up to limit of the prefixes of IP version ipVersion // (4 or 6) and mask length maskLength. It reads only the prefixes it returns. // Go starts each walk over a map at a random place, so each call returns // another sample. func (t *Table) SamplePrefixes(ipVersion, maskLength, limit int) []PrefixSummary { t.mu.RLock() defer t.mu.RUnlock() var f *family switch ipVersion { case ipVersion4: f = &t.ipv4 case ipVersion6: f = &t.ipv6 default: return nil } if maskLength < 0 || maskLength >= len(f.byLength) || limit <= 0 { return nil } prefixes := f.byLength[maskLength] sample := make([]PrefixSummary, 0, min(limit, len(prefixes))) for prefix, origins := range prefixes { if len(sample) == limit { break } sample = append(sample, summarize(prefix, origins)) } return sample } // Counts returns the table's figures for the stats. The writes keep them up to // date, so this reads no prefix. func (t *Table) Counts() Counts { t.mu.RLock() defer t.mu.RUnlock() counts := Counts{ Peers: len(t.peerNumbers), IPv4PrefixesByLength: t.ipv4.prefixesByLength(), IPv6PrefixesByLength: t.ipv6.prefixesByLength(), IPv4Routes: t.ipv4.routes, IPv6Routes: t.ipv6.routes, OriginsWithoutKeptRoute: t.originsWithoutKeptRoute, } for _, n := range counts.IPv4PrefixesByLength { counts.IPv4Prefixes += n } for _, n := range counts.IPv6PrefixesByLength { counts.IPv6Prefixes += n } for _, prefixes := range t.byOrigin { counts.Pairs += len(prefixes) } oldest, found := t.keptTimes.Min() if found { counts.OldestKeptRoute = &oldest.time } newest, found := t.keptTimes.Max() if found { counts.NewestKeptRoute = &newest.time } return counts } // addresses returns the addresses of the peers in peers, in number order. func (t *Table) addresses(peers *bitset.BitSet) []string { addresses := make([]string, 0, peers.Count()) for number := range peers.EachSet() { addresses = append(addresses, t.peerAddresses[number]) } return addresses } // summarize returns the summary of prefix, whose origins are in AS number // order. func summarize(prefix netip.Prefix, origins []*origin) PrefixSummary { summary := PrefixSummary{Prefix: prefix} mostPeers := 0 for _, o := range origins { peers := int(o.peers.Count()) summary.Peers += peers if peers > mostPeers { mostPeers = peers summary.OriginASN = o.asn } newest := o.newestTime() if newest != nil && (summary.Newest == nil || newest.After(*summary.Newest)) { summary.Newest = newest } } return summary }