fix: restore recurring GeoIP checks
This commit is contained in:
@@ -66,7 +66,7 @@ Die benötigten Pakete werden vom Installer auf Ubuntu/Debian automatisch instal
|
||||
```bash
|
||||
# Release-Archiv herunterladen und entpacken
|
||||
curl -fL -o adguard-shield-linux-amd64.tar.gz \
|
||||
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.2/adguard-shield-linux-amd64.tar.gz
|
||||
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.3/adguard-shield-linux-amd64.tar.gz
|
||||
tar -xzf adguard-shield-linux-amd64.tar.gz
|
||||
chmod +x ./adguard-shield
|
||||
```
|
||||
|
||||
@@ -92,7 +92,7 @@ ABUSEIPDB_CATEGORIES="4" # 4 = DDoS Attack (siehe abuseipdb.com/categ
|
||||
GEOIP_ENABLED=false
|
||||
GEOIP_MODE="blocklist" # blocklist oder allowlist
|
||||
GEOIP_COUNTRIES="" # ISO 3166-1 Alpha-2 Codes, z.B. "CN,RU,KP,IR"
|
||||
GEOIP_CHECK_INTERVAL=0 # Legacy: Daemon nutzt den zentralen CHECK_INTERVAL-Poller
|
||||
GEOIP_CHECK_INTERVAL=0 # 0 = nutzt CHECK_INTERVAL; sonst eigenes GeoIP-Prüfintervall
|
||||
GEOIP_NOTIFY=true
|
||||
GEOIP_SKIP_PRIVATE=true # Private IPs ausnehmen
|
||||
GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-Download)
|
||||
|
||||
BIN
dist/adguard-shield-v1.1.3-linux-amd64
vendored
Normal file
BIN
dist/adguard-shield-v1.1.3-linux-amd64
vendored
Normal file
Binary file not shown.
@@ -250,13 +250,13 @@ ABUSEIPDB_API_KEY="..."
|
||||
### Service gestartet
|
||||
|
||||
```text
|
||||
AdGuard Shield v1.1.2 wurde auf dns1 gestartet.
|
||||
AdGuard Shield v1.1.3 wurde auf dns1 gestartet.
|
||||
```
|
||||
|
||||
### Service gestoppt
|
||||
|
||||
```text
|
||||
AdGuard Shield v1.1.2 wurde auf dns1 gestoppt.
|
||||
AdGuard Shield v1.1.3 wurde auf dns1 gestoppt.
|
||||
```
|
||||
|
||||
### Rate-Limit-Sperre
|
||||
|
||||
@@ -560,7 +560,7 @@ ABUSEIPDB_CATEGORIES="4"
|
||||
| `GEOIP_ENABLED` | `false` | GeoIP-Filter aktivieren |
|
||||
| `GEOIP_MODE` | `blocklist` | Filtermodus |
|
||||
| `GEOIP_COUNTRIES` | leer | Ländercodes nach ISO 3166-1 Alpha-2 |
|
||||
| `GEOIP_CHECK_INTERVAL` | `0` | Legacy-Parameter (Go-Version nutzt den zentralen Poller) |
|
||||
| `GEOIP_CHECK_INTERVAL` | `0` | Eigenes GeoIP-Prüfintervall in Sekunden; `0` nutzt `CHECK_INTERVAL`. |
|
||||
| `GEOIP_NOTIFY` | `true` | Benachrichtigungen bei GeoIP-Sperren senden |
|
||||
| `GEOIP_SKIP_PRIVATE` | `true` | Private/lokale IPs überspringen |
|
||||
| `GEOIP_LICENSE_KEY` | leer | MaxMind-License-Key für automatischen Download |
|
||||
|
||||
@@ -34,7 +34,7 @@ Du brauchst ein fertiges Linux-Binary. Das kann aus einem Release, aus CI oder a
|
||||
|
||||
```bash
|
||||
curl -fL -o adguard-shield-linux-amd64.tar.gz \
|
||||
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.2/adguard-shield-linux-amd64.tar.gz
|
||||
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.3/adguard-shield-linux-amd64.tar.gz
|
||||
tar -xzf adguard-shield-linux-amd64.tar.gz
|
||||
chmod +x ./adguard-shield
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package appinfo
|
||||
|
||||
var Version = "v1.1.2"
|
||||
var Version = "v1.1.3"
|
||||
|
||||
const ProjectURL = "https://git.techniverse.net/scriptos/adguard-shield.git"
|
||||
|
||||
@@ -40,7 +40,7 @@ type Daemon struct {
|
||||
mu sync.Mutex
|
||||
seen map[string]time.Time
|
||||
events []queryEvent
|
||||
geoSeen map[string]bool
|
||||
geoSeen map[string]time.Time
|
||||
wl map[string]bool
|
||||
|
||||
serviceMu sync.Mutex
|
||||
@@ -93,7 +93,7 @@ func New(c *config.Config) (*Daemon, error) {
|
||||
d := &Daemon{
|
||||
Config: c, Store: st, FW: fw, Logger: logger,
|
||||
Client: &http.Client{Timeout: 20 * time.Second, Transport: tr},
|
||||
seen: map[string]time.Time{}, geoSeen: map[string]bool{},
|
||||
seen: map[string]time.Time{}, geoSeen: map[string]time.Time{},
|
||||
}
|
||||
d.Geo = geoip.New(c.GeoIPMMDBPath, c.GeoIPLicenseKey, filepath.Join(filepath.Dir(c.Path), "geoip"), c.GeoIPCacheTTL, st)
|
||||
return d, nil
|
||||
@@ -409,13 +409,6 @@ func (d *Daemon) checkGeoIP(ctx context.Context, ip string) {
|
||||
if d.Config.GeoIPSkipPrivate && geoip.IsPrivateIP(ip) {
|
||||
return
|
||||
}
|
||||
d.mu.Lock()
|
||||
if d.geoSeen[ip] {
|
||||
d.mu.Unlock()
|
||||
return
|
||||
}
|
||||
d.geoSeen[ip] = true
|
||||
d.mu.Unlock()
|
||||
if d.isWhitelisted(ip) {
|
||||
return
|
||||
}
|
||||
@@ -423,6 +416,9 @@ func (d *Daemon) checkGeoIP(ctx context.Context, ip string) {
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
if !d.shouldCheckGeoIP(ip, time.Now()) {
|
||||
return
|
||||
}
|
||||
cc, err := d.Geo.Lookup(ip)
|
||||
if err != nil || cc == "" {
|
||||
return
|
||||
@@ -432,6 +428,41 @@ func (d *Daemon) checkGeoIP(ctx context.Context, ip string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) shouldCheckGeoIP(ip string, now time.Time) bool {
|
||||
interval := d.geoIPCheckInterval()
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if last, ok := d.geoSeen[ip]; ok && now.Sub(last) < interval {
|
||||
return false
|
||||
}
|
||||
d.geoSeen[ip] = now
|
||||
d.pruneGeoSeenLocked(now, interval)
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *Daemon) geoIPCheckInterval() time.Duration {
|
||||
seconds := 0
|
||||
if d.Config != nil {
|
||||
seconds = d.Config.GeoIPCheckInterval
|
||||
if seconds <= 0 {
|
||||
seconds = d.Config.CheckInterval
|
||||
}
|
||||
}
|
||||
if seconds <= 0 {
|
||||
seconds = 1
|
||||
}
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
|
||||
func (d *Daemon) pruneGeoSeenLocked(now time.Time, interval time.Duration) {
|
||||
cutoff := now.Add(-2 * interval)
|
||||
for ip, last := range d.geoSeen {
|
||||
if last.Before(cutoff) {
|
||||
delete(d.geoSeen, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) Ban(ctx context.Context, ip, domain string, count int, proto, reason, source, country string, permanent bool) error {
|
||||
if d.isWhitelisted(ip) {
|
||||
return nil
|
||||
|
||||
@@ -363,3 +363,37 @@ func TestDryRunDoesNotInsertActiveBan(t *testing.T) {
|
||||
t.Fatal("dry-run must not create an active ban")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPCheckGateUsesConfiguredInterval(t *testing.T) {
|
||||
now := time.Unix(1000, 0)
|
||||
d := &Daemon{
|
||||
Config: &config.Config{CheckInterval: 10, GeoIPCheckInterval: 30},
|
||||
geoSeen: map[string]time.Time{},
|
||||
}
|
||||
if !d.shouldCheckGeoIP("203.0.113.7", now) {
|
||||
t.Fatal("first GeoIP check should be allowed")
|
||||
}
|
||||
if d.shouldCheckGeoIP("203.0.113.7", now.Add(29*time.Second)) {
|
||||
t.Fatal("GeoIP check inside configured interval should be skipped")
|
||||
}
|
||||
if !d.shouldCheckGeoIP("203.0.113.7", now.Add(30*time.Second)) {
|
||||
t.Fatal("GeoIP check after configured interval should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPCheckGateFallsBackToPollInterval(t *testing.T) {
|
||||
now := time.Unix(1000, 0)
|
||||
d := &Daemon{
|
||||
Config: &config.Config{CheckInterval: 10},
|
||||
geoSeen: map[string]time.Time{},
|
||||
}
|
||||
if !d.shouldCheckGeoIP("203.0.113.7", now) {
|
||||
t.Fatal("first GeoIP check should be allowed")
|
||||
}
|
||||
if d.shouldCheckGeoIP("203.0.113.7", now.Add(9*time.Second)) {
|
||||
t.Fatal("GeoIP check inside poll interval should be skipped")
|
||||
}
|
||||
if !d.shouldCheckGeoIP("203.0.113.7", now.Add(10*time.Second)) {
|
||||
t.Fatal("GeoIP check after poll interval should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
@@ -31,6 +32,7 @@ type Resolver struct {
|
||||
Dir string
|
||||
TTL int64
|
||||
Store Store
|
||||
mu sync.RWMutex
|
||||
reader *maxminddb.Reader
|
||||
cache map[string]string
|
||||
mtime int64
|
||||
@@ -65,7 +67,9 @@ func (r *Resolver) Open(ctx context.Context) error {
|
||||
r.mtime = st.ModTime().Unix()
|
||||
if r.Store != nil {
|
||||
if c, err := r.Store.LoadGeoIPCache(r.TTL, r.mtime); err == nil {
|
||||
r.mu.Lock()
|
||||
r.cache = c
|
||||
r.mu.Unlock()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -79,9 +83,12 @@ func (r *Resolver) Close() error {
|
||||
}
|
||||
|
||||
func (r *Resolver) Lookup(ip string) (string, error) {
|
||||
r.mu.RLock()
|
||||
if v, ok := r.cache[ip]; ok {
|
||||
r.mu.RUnlock()
|
||||
return v, nil
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
if r.reader == nil {
|
||||
return r.lookupLegacy(ip)
|
||||
}
|
||||
@@ -104,33 +111,44 @@ func (r *Resolver) Lookup(ip string) (string, error) {
|
||||
if cc == "" {
|
||||
cc = strings.ToUpper(rec.RegisteredCountry.ISOCode)
|
||||
}
|
||||
if cc != "" {
|
||||
r.cache[ip] = cc
|
||||
if r.Store != nil {
|
||||
_ = r.Store.UpsertGeoIP(ip, cc, r.mtime)
|
||||
}
|
||||
}
|
||||
r.storeCache(ip, cc)
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) lookupLegacy(ip string) (string, error) {
|
||||
if strings.Contains(ip, ":") {
|
||||
if cc, err := runGeoIPCommand("geoiplookup6", ip); err == nil && cc != "" {
|
||||
r.storeCache(ip, cc)
|
||||
return cc, nil
|
||||
}
|
||||
} else {
|
||||
if cc, err := runGeoIPCommand("geoiplookup", ip); err == nil && cc != "" {
|
||||
r.storeCache(ip, cc)
|
||||
return cc, nil
|
||||
}
|
||||
}
|
||||
if r.effectivePath != "" {
|
||||
if cc, err := runGeoIPCommand("mmdblookup", "--file", r.effectivePath, "--ip", ip, "country", "iso_code"); err == nil && cc != "" {
|
||||
r.storeCache(ip, cc)
|
||||
return cc, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no GeoIP result for %s", ip)
|
||||
}
|
||||
|
||||
func (r *Resolver) storeCache(ip, country string) {
|
||||
country = strings.ToUpper(strings.TrimSpace(country))
|
||||
if country == "" {
|
||||
return
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.cache[ip] = country
|
||||
r.mu.Unlock()
|
||||
if r.Store != nil {
|
||||
_ = r.Store.UpsertGeoIP(ip, country, r.mtime)
|
||||
}
|
||||
}
|
||||
|
||||
func runGeoIPCommand(name string, args ...string) (string, error) {
|
||||
if _, err := exec.LookPath(name); err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -2,6 +2,23 @@ package geoip
|
||||
|
||||
import "testing"
|
||||
|
||||
type fakeGeoIPStore struct {
|
||||
ip string
|
||||
country string
|
||||
mtime int64
|
||||
}
|
||||
|
||||
func (s *fakeGeoIPStore) LoadGeoIPCache(ttl, dbMtime int64) (map[string]string, error) {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
func (s *fakeGeoIPStore) UpsertGeoIP(ip, country string, dbMtime int64) error {
|
||||
s.ip = ip
|
||||
s.country = country
|
||||
s.mtime = dbMtime
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestShouldBlockModes(t *testing.T) {
|
||||
countries := []string{"CN", "RU"}
|
||||
if !ShouldBlock("cn", "blocklist", countries) {
|
||||
@@ -28,3 +45,18 @@ func TestIsPrivateIP(t *testing.T) {
|
||||
t.Fatal("8.8.8.8 should be public")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreCachePersistsLegacyLookupResult(t *testing.T) {
|
||||
store := &fakeGeoIPStore{}
|
||||
r := New("", "", "", 86400, store)
|
||||
r.mtime = 123
|
||||
|
||||
r.storeCache("203.0.113.7", "cn")
|
||||
|
||||
if got := r.cache["203.0.113.7"]; got != "CN" {
|
||||
t.Fatalf("cache = %q, want CN", got)
|
||||
}
|
||||
if store.ip != "203.0.113.7" || store.country != "CN" || store.mtime != 123 {
|
||||
t.Fatalf("store got ip=%q country=%q mtime=%d", store.ip, store.country, store.mtime)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user