Rename net/ipgate to net/ippolicy and compose independently refreshed whitelist and blacklist sources into one IP decision policy. - rename slog group and error prefixes from ipgate to ippolicy - delete unused ParseDomainSet and RecordReader from domainsetcsv.go - rewrite README to match the actual Config API - document nil-whitelist behavior on New() - bump iplist dependency to v0.8.5 - go mod tidy: remove stale ipgate and iplist entries
net/ippolicy
ippolicy composes independently refreshed IP sources into a fast policy
check. It uses net/iplist for local/HTTP source refresh and provides IP
matching, DNS refresh, and the legacy git blacklist.
Quick start
Construct each source with net/iplist.NewSource, then pass them to
ippolicy.New:
whitelist, err := iplist.NewSource(ctx, iplist.SourceConfig{
Source: "allowed.csv",
CacheDir: "/var/cache/app/iplist",
// HTTPClient: nil, // uses golib's internal client
})
if err != nil {
return err
}
defer whitelist.Close()
blacklistExtra, err := iplist.NewSource(ctx, iplist.SourceConfig{
Source: "blocked-ips-extra.tsv",
CacheDir: "/var/cache/app/iplist",
Optional: true,
})
if err != nil {
return err
}
defer blacklistExtra.Close()
gitBlacklist, err := ippolicy.NewPrefixSet(
ctx,
"https://github.com/bitwire-it/ipblocklist.git",
"/var/cache/app/ipblocklist",
[]string{"ipblocklist.txt"},
ippolicy.DefaultPrefixSetRefreshInterval,
)
if err != nil {
return err
}
defer gitBlacklist.Close()
policy := ippolicy.New(ctx, ippolicy.Config{
Whitelist: whitelist,
Blacklist: gitBlacklist,
BlacklistExtra: blacklistExtra,
OnRefresh: func(e ippolicy.RefreshEvent) {
slog.Info("ippolicy refresh", "kind", e.Kind, "err", e.Err)
},
})
defer policy.Close()
switch policy.Evaluate(addr) {
case ippolicy.Blacklisted:
// reject
case ippolicy.Whitelisted, ippolicy.Unlisted:
// continue
}
Behavior
Each source refreshes independently and keeps its last valid data on failure.
Whitelist matches take precedence over blacklist matches. The policy does not
log; use OnRefresh for application logging and metrics.
A nil Whitelist produces a policy where every address evaluates to
Unlisted. This intentionally drops the blacklists as well: without a
whitelist the policy cannot distinguish "allowed" from "unknown", so blocking
would be meaningless. The caller receives a RefreshFallback event so it can
log or alert.