Files
wweir 51438c93a0 feat(router): split DialSmart/DialProxyOnly, fail-fast on bad MMDB/CIDR, add Close
- Add DialSmart (rule+detect+fallback) and DialProxyOnly (forced proxy)
  as explicit routing entry points; keep Dial as compatibility alias.
- NewRouter now returns (*Router, error); non-empty invalid MMDB path
  is a startup error instead of a silent warning.
- AddCountryCIDRs now returns error; invalid CIDR entries fail fast.
- Add Router.Close() to release geoip2.Reader on shutdown.
- Add country RWMutex so Close() does not race with concurrent reads.
- localSite checks all resolved IPs instead of only the first.
- Move accessCache from package-level global to Router instance field;
  inject accessProbeFunc for testability.
- Disable redirect following in pingClient to avoid misleading probe
  results on redirect responses.
2026-06-24 13:30:18 +08:00

59 lines
1.1 KiB
Go

package router
import (
"context"
"log/slog"
"net"
"time"
)
func (r *Router) localSite(ctx context.Context, domain string) bool {
if ctx == nil {
ctx = context.Background()
}
if ip := net.ParseIP(domain); ip != nil {
return r.localIP(domain, ip)
}
resolveCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
ips, err := net.DefaultResolver.LookupIP(resolveCtx, "ip", domain)
if err != nil || len(ips) == 0 {
slog.Warn("resolve domain", "error", err, "domain", domain, "ips", len(ips))
return false
}
for _, ip := range ips {
if r.localIP(domain, ip) {
return true
}
}
return false
}
func (r *Router) localIP(domain string, ip net.IP) bool {
// CIDR match
for _, cidr := range r.country.cidrs {
if cidr.Contains(ip) {
return true
}
}
r.country.RLock()
reader := r.country.Reader
if reader == nil {
r.country.RUnlock()
return false
}
city, err := reader.City(ip)
r.country.RUnlock()
if err != nil {
slog.Warn("mmdb search", "error", err, "domain", domain, "ip", ip)
return false
}
return city.Country.IsoCode == "CN"
}