Reverse lookups (internal/names, POST /names) put a name beside each inside address in connections, the log, quota usage, the dashboard's quotas and the statistics' devices and blocked sources, in the web UI and hc (--no-names to leave them out). Only inside addresses are looked up: an inside zone's networks, and private ones outside the external zones'. Names are cached (names.cache, 10m) and never stored; names.resolver picks the DNS server. Also: hc stats top devices listed each device twice; the dashboard's quotas named the rule's ID twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
1.6 KiB
Go
58 lines
1.6 KiB
Go
package names
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"net/netip"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNames(t *testing.T) {
|
|
var calls atomic.Int32
|
|
slow := make(chan struct{})
|
|
r := NewFunc(func(ctx context.Context, addr string) ([]string, error) {
|
|
calls.Add(1)
|
|
switch addr {
|
|
case "10.0.0.1":
|
|
return []string{"nas.example.net."}, nil
|
|
case "10.0.0.2":
|
|
return nil, &net.DNSError{Err: "no such host", Name: addr, IsNotFound: true}
|
|
case "10.0.0.3":
|
|
return nil, errors.New("server failure")
|
|
case "10.0.0.4":
|
|
<-slow
|
|
return []string{"slow.example.net."}, nil
|
|
}
|
|
return nil, nil
|
|
}, time.Hour)
|
|
a := netip.MustParseAddr
|
|
all := []netip.Addr{a("10.0.0.1"), a("10.0.0.2"), a("10.0.0.3"), a("10.0.0.4")}
|
|
|
|
got := r.Names(context.Background(), all, 200*time.Millisecond)
|
|
if len(got) != 1 || got[a("10.0.0.1")] != "nas.example.net" {
|
|
t.Fatalf("first call: %v", got)
|
|
}
|
|
// The slow lookup finishes later and is there next time; the others
|
|
// come from the cache, the failure too until it is retried.
|
|
close(slow)
|
|
time.Sleep(50 * time.Millisecond)
|
|
got = r.Names(context.Background(), all, 200*time.Millisecond)
|
|
if len(got) != 2 || got[a("10.0.0.4")] != "slow.example.net" {
|
|
t.Fatalf("second call: %v", got)
|
|
}
|
|
if n := calls.Load(); n != 4 {
|
|
t.Fatalf("%d lookups, want 4 (cached)", n)
|
|
}
|
|
|
|
// A failure is tried again after retry, a missing name after ttl.
|
|
now := time.Now().Add(2 * time.Minute)
|
|
r.now = func() time.Time { return now }
|
|
r.Names(context.Background(), all, 200*time.Millisecond)
|
|
if n := calls.Load(); n != 5 {
|
|
t.Fatalf("%d lookups, want 5 (the failure again)", n)
|
|
}
|
|
}
|