Compare commits
1
Commits
next
...
07debb21ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07debb21ea |
@@ -35,6 +35,12 @@ tested.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-11-02: like a failed WHOIS lookup, a lookup skipped because its RIR is
|
||||||
|
paused makes the fetcher wait longer before the next lookup. The longest wait
|
||||||
|
is one minute. The status page does not count a skip as a failure. A skip used
|
||||||
|
to leave the wait as it was. Each lookup asks IANA before it finds its RIR
|
||||||
|
paused. So while most stale ASes belonged to a paused RIR, the fetcher could
|
||||||
|
ask IANA every second (closes #222)
|
||||||
- 2026-11-02: a peer's session going down no longer stops the prefix handler.
|
- 2026-11-02: a peer's session going down no longer stops the prefix handler.
|
||||||
The handler runs the table's `PeerDown` on a separate goroutine, one peer at a
|
The handler runs the table's `PeerDown` on a separate goroutine, one peer at a
|
||||||
time, and goes on taking messages. `PeerDown` holds the table's lock for
|
time, and goes on taking messages. `PeerDown` holds the table's lock for
|
||||||
|
|||||||
@@ -187,25 +187,33 @@ func (f *ASNFetcher) recordSuccess() {
|
|||||||
f.statsMu.Unlock()
|
f.statsMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordFailure increases the interval on failed fetch using exponential backoff.
|
// lengthenInterval makes the interval 1.5 times as long, up to maxInterval.
|
||||||
func (f *ASNFetcher) recordFailure() {
|
// A failed lookup and a skipped one both lengthen it.
|
||||||
|
func (f *ASNFetcher) lengthenInterval() {
|
||||||
f.intervalMu.Lock()
|
f.intervalMu.Lock()
|
||||||
f.consecutiveFails++
|
defer f.intervalMu.Unlock()
|
||||||
|
|
||||||
// Exponential backoff: multiply by 1.5, capped at max
|
|
||||||
newInterval := min(
|
newInterval := min(
|
||||||
f.currentInterval*backoffNumerator/backoffDenominator, maxInterval)
|
f.currentInterval*backoffNumerator/backoffDenominator, maxInterval)
|
||||||
|
|
||||||
if newInterval != f.currentInterval {
|
if newInterval != f.currentInterval {
|
||||||
f.logger.Debug("Increased fetch interval due to failure",
|
f.logger.Debug("Increased fetch interval",
|
||||||
"old_interval", f.currentInterval,
|
"old_interval", f.currentInterval,
|
||||||
"new_interval", newInterval,
|
"new_interval", newInterval,
|
||||||
"consecutive_failures", f.consecutiveFails,
|
"consecutive_failures", f.consecutiveFails,
|
||||||
)
|
)
|
||||||
f.currentInterval = newInterval
|
f.currentInterval = newInterval
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recordFailure counts a failed fetch and lengthens the interval.
|
||||||
|
func (f *ASNFetcher) recordFailure() {
|
||||||
|
f.intervalMu.Lock()
|
||||||
|
f.consecutiveFails++
|
||||||
f.intervalMu.Unlock()
|
f.intervalMu.Unlock()
|
||||||
|
|
||||||
|
f.lengthenInterval()
|
||||||
|
|
||||||
// Record error time for stats, dropping those older than statsWindow
|
// Record error time for stats, dropping those older than statsWindow
|
||||||
f.statsMu.Lock()
|
f.statsMu.Lock()
|
||||||
now := f.now()
|
now := f.now()
|
||||||
@@ -291,9 +299,12 @@ func (f *ASNFetcher) fetchNextStale(ctx context.Context) {
|
|||||||
// The WHOIS client pauses an RIR's or NIC.br's server for whois.PauseTime
|
// The WHOIS client pauses an RIR's or NIC.br's server for whois.PauseTime
|
||||||
// after the server refuses a query. Lookups at the other servers go on. IANA
|
// after the server refuses a query. Lookups at the other servers go on. IANA
|
||||||
// is never paused, so a refusal by IANA is an ordinary failure. A lookup
|
// is never paused, so a refusal by IANA is an ordinary failure. A lookup
|
||||||
// whose RIR is paused fails with whois.ErrPaused. Such a lookup leaves the
|
// whose RIR is paused fails with whois.ErrPaused and is skipped. A skipped
|
||||||
// AS stale, and counts as neither a success nor a failure. The lookups that
|
// lookup leaves the AS stale and is not counted as a failure. Like a
|
||||||
// QueueImmediate asks for also run through fetchAndUpdate.
|
// failure, a skipped lookup lengthens the interval. Each lookup asks IANA
|
||||||
|
// before it finds its RIR paused, so with a one-second interval the fetcher
|
||||||
|
// would ask IANA every second. The lookups that QueueImmediate asks for also
|
||||||
|
// run through fetchAndUpdate.
|
||||||
func (f *ASNFetcher) fetchAndUpdate(ctx context.Context, asn int) bool {
|
func (f *ASNFetcher) fetchAndUpdate(ctx context.Context, asn int) bool {
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -317,6 +328,7 @@ func (f *ASNFetcher) fetchAndUpdate(ctx context.Context, asn int) bool {
|
|||||||
if errors.Is(err, whois.ErrPaused) {
|
if errors.Is(err, whois.ErrPaused) {
|
||||||
f.logger.Info("Skipped WHOIS lookup, the RIR's server is paused",
|
f.logger.Info("Skipped WHOIS lookup, the RIR's server is paused",
|
||||||
"asn", asn, "error", err)
|
"asn", asn, "error", err)
|
||||||
|
f.lengthenInterval()
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,48 @@ func TestFetchAndUpdateWaitsOutAPause(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFetchAndUpdateBacksOffDuringAPause looks up an AS at RIPE while RIPE
|
||||||
|
// refuses the query. The refusal pauses RIPE for an hour, so every later
|
||||||
|
// lookup in the test is skipped. The test sets the WHOIS client's clock and
|
||||||
|
// looks the AS up again each time the interval has passed. Like a failure,
|
||||||
|
// each skip must make the interval 1.5 times as long, up to one minute. The
|
||||||
|
// skips must not count as failures.
|
||||||
|
func TestFetchAndUpdateBacksOffDuringAPause(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
f, _ := newFetcherWithWHOISAnswers(t, map[string]string{
|
||||||
|
ianaWHOISServer: ianaRefersToRIPE,
|
||||||
|
ripeWHOISServer: ripeAccessDenied,
|
||||||
|
})
|
||||||
|
|
||||||
|
now := time.Date(2026, 11, 2, 0, 0, 0, 0, time.UTC)
|
||||||
|
f.whoisClient.Now = func() time.Time { return now }
|
||||||
|
|
||||||
|
f.fetchAndUpdate(t.Context(), testASNA)
|
||||||
|
|
||||||
|
for i, want := range []time.Duration{
|
||||||
|
33750 * time.Millisecond,
|
||||||
|
50625 * time.Millisecond,
|
||||||
|
time.Minute,
|
||||||
|
time.Minute,
|
||||||
|
} {
|
||||||
|
now = now.Add(f.getInterval())
|
||||||
|
|
||||||
|
f.fetchAndUpdate(t.Context(), testASNA)
|
||||||
|
|
||||||
|
if got := f.getInterval(); got != want {
|
||||||
|
t.Errorf("interval after %d skipped lookups = %v, want %v",
|
||||||
|
i+1, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
got := f.GetStats()
|
||||||
|
if got.ConsecutiveFails != 1 || got.ErrorsLastHour != 1 {
|
||||||
|
t.Errorf("%d failures in a row and %d errors, want 1 and 1",
|
||||||
|
got.ConsecutiveFails, got.ErrorsLastHour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestFetchAndUpdateKeepsAskingIANA looks up an AS while IANA's server
|
// TestFetchAndUpdateKeepsAskingIANA looks up an AS while IANA's server
|
||||||
// refuses the connection. The test sets the WHOIS client's clock. The next
|
// refuses the connection. The test sets the WHOIS client's clock. The next
|
||||||
// lookup comes a second before whois.PauseTime has passed. IANA is never
|
// lookup comes a second before whois.PauseTime has passed. IANA is never
|
||||||
|
|||||||
Reference in New Issue
Block a user