Add GeoIP support for unique IP aggregation
- Introduced GeoIP configuration options in config.example.yaml to enable geolocation lookups for the /api/agg/unique-ips endpoint. - Updated the aggregate handler to include optional GeoIP data in responses, enriching unique IP information with country and city details, as well as ASN data if available. - Enhanced documentation in AGGREGATE.md and README.md to reflect the new GeoIP functionality and its usage. - Added a dependency on the geoip2-golang library in go.mod for GeoIP lookups. - Modified tests to accommodate the new GeoIP integration in the aggregate handler.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DownloadMMDB downloads an MMDB URL to destPath. If the URL ends with `.gz`, the body is gunzipped.
|
||||
// Otherwise the response body is written as-is (raw `.mmdb`, e.g. GitHub raw).
|
||||
func DownloadMMDB(ctx context.Context, client *http.Client, url, destPath string) error {
|
||||
if strings.HasSuffix(strings.ToLower(strings.TrimSpace(url)), ".gz") {
|
||||
return downloadGzippedMMDB(ctx, client, url, destPath)
|
||||
}
|
||||
return downloadRawMMDB(ctx, client, url, destPath)
|
||||
}
|
||||
|
||||
func downloadRawMMDB(ctx context.Context, client *http.Client, url, destPath string) error {
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("geoip download: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("geoip download: http %s", resp.Status)
|
||||
}
|
||||
dir := filepath.Dir(destPath)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return fmt.Errorf("geoip mkdir: %w", err)
|
||||
}
|
||||
tmp, err := os.CreateTemp(dir, "geolite-*.mmdb.part")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpPath := tmp.Name()
|
||||
_, copyErr := io.Copy(tmp, resp.Body)
|
||||
closeErr := tmp.Close()
|
||||
if copyErr != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
return copyErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
return closeErr
|
||||
}
|
||||
_ = os.Remove(destPath)
|
||||
if err := os.Rename(tmpPath, destPath); err != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
return fmt.Errorf("geoip rename: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadGzippedMMDB(ctx context.Context, client *http.Client, url, destPath string) error {
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("geoip download: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("geoip download: http %s", resp.Status)
|
||||
}
|
||||
gzr, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("geoip gzip: %w", err)
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
dir := filepath.Dir(destPath)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return fmt.Errorf("geoip mkdir: %w", err)
|
||||
}
|
||||
tmp, err := os.CreateTemp(dir, "geolite-*.mmdb.part")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpPath := tmp.Name()
|
||||
_, copyErr := io.Copy(tmp, gzr)
|
||||
closeErr := tmp.Close()
|
||||
if copyErr != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
return copyErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
return closeErr
|
||||
}
|
||||
_ = os.Remove(destPath)
|
||||
if err := os.Rename(tmpPath, destPath); err != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
return fmt.Errorf("geoip rename: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user