diff --git a/net/ipgate/domains.go b/net/ipgate/domains.go index c38b389..1c27e11 100644 --- a/net/ipgate/domains.go +++ b/net/ipgate/domains.go @@ -47,7 +47,8 @@ func NewDomainSet(ctx context.Context, staticPrefixes []string, domains []string } func (ds *DomainSet) Contains(addr netip.Addr) bool { - return ds.cohort.Load().ContainsAddr(addr) + cohort := ds.cohort.Load() + return cohort != nil && cohort.ContainsAddr(addr) } func (ds *DomainSet) refreshLoop(ctx context.Context) { @@ -119,4 +120,3 @@ func (ds *DomainSet) rebuildCohort() { ds.cohort.Store(cohort) } } - diff --git a/net/ipgate/ipprefix.go b/net/ipgate/ipprefix.go index a6a6f31..8a2fbb7 100644 --- a/net/ipgate/ipprefix.go +++ b/net/ipgate/ipprefix.go @@ -45,7 +45,8 @@ func NewPrefixSet(ctx context.Context, repoURL, dataPath string, files []string) } func (ps *PrefixSet) Contains(addr netip.Addr) bool { - return ps.cohort.Load().ContainsAddr(addr) + cohort := ps.cohort.Load() + return cohort != nil && cohort.ContainsAddr(addr) } func (ps *PrefixSet) reload(ctx context.Context) error { @@ -53,15 +54,15 @@ func (ps *PrefixSet) reload(ctx context.Context) error { if err != nil { return err } - if !updated && ps.cohort.Load().Size() > 0 { - return nil - } - paths := make([]string, len(ps.files)) for i, f := range ps.files { paths[i] = ps.repo.FilePath(f) } + if cachedCohortValid(updated, ps.cohort.Load(), paths) { + return nil + } + cohort, err := ipcohort.LoadFiles(paths...) if err != nil { return fmt.Errorf("load files: %w", err) @@ -73,6 +74,19 @@ func (ps *PrefixSet) reload(ctx context.Context) error { return nil } +func cachedCohortValid(updated bool, cohort *ipcohort.Cohort, paths []string) bool { + return !updated && cohort != nil && cohort.Size() > 0 && filesPresent(paths) +} + +func filesPresent(paths []string) bool { + for _, path := range paths { + if _, err := os.Stat(path); err != nil { + return false + } + } + return true +} + func (ps *PrefixSet) refreshLoop(ctx context.Context) { if err := ps.reload(ctx); err != nil { log().Warn("prefix set initial load (will retry)", "err", err) diff --git a/net/ipgate/ipprefix_test.go b/net/ipgate/ipprefix_test.go new file mode 100644 index 0000000..3545b24 --- /dev/null +++ b/net/ipgate/ipprefix_test.go @@ -0,0 +1,46 @@ +package ipgate + +import ( + "net/netip" + "os" + "path/filepath" + "testing" + + "github.com/therootcompany/golib/net/ipcohort" +) + +func TestPrefixSetContainsZeroValue(t *testing.T) { + var ps PrefixSet + if ps.Contains(netip.MustParseAddr("192.0.2.1")) { + t.Fatal("zero-value PrefixSet must not contain an address") + } +} + +func TestCachedCohortValidRequiresFiles(t *testing.T) { + dir := t.TempDir() + present := filepath.Join(dir, "present") + if err := writeFile(present); err != nil { + t.Fatal(err) + } + cohort, err := ipcohort.Parse([]string{"192.0.2.0/24"}) + if err != nil { + t.Fatal(err) + } + + if !cachedCohortValid(false, cohort, []string{present}) { + t.Fatal("fresh metadata and present files should use the cache") + } + if cachedCohortValid(false, cohort, []string{present, filepath.Join(dir, "gone")}) { + t.Fatal("missing data file must invalidate the cache") + } + if cachedCohortValid(false, nil, []string{present}) { + t.Fatal("nil cohort must not be used") + } + if cachedCohortValid(true, cohort, []string{present}) { + t.Fatal("updated repo must reload") + } +} + +func writeFile(path string) error { + return os.WriteFile(path, nil, 0o600) +}