From 98eedb5a7bf9ec6523e5c81193af7f34f36b7e3a Mon Sep 17 00:00:00 2001 From: Denis Machard <5562930+dmachard@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:33:22 +0200 Subject: [PATCH] add ecs support on geoip (#830) * add ecs support on geoip * update docs --- README.md | 4 ++-- docs/configuration.md | 5 +++- docs/transformers/transform_geoip.md | 4 ++++ pkgconfig/transformers.go | 1 + transformers/geoip.go | 26 ++++++++++++++++++++- transformers/geoip_test.go | 34 ++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d9eedb33..b5dea20a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@
- + - +
diff --git a/docs/configuration.md b/docs/configuration.md index 4c86710f..a51d4f9e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -104,7 +104,10 @@ Default directives: - `qclass`: dns query class - `qname`: dns query name - `latency`: computed latency between queries and replies -- `answercount`: the number of answer +- `qdcount`: the number of question +- `ancount`: the number of answer +- `arcount`: the number of additionnal answer +- `nscount`: the number of nameserver - `ttl`: answer ttl, only the first one - `answer`: rdata answer, only the first one, prefer to use the JSON format if you wamt all answers - `malformed`: malformed dns packet, integer value 1/0 diff --git a/docs/transformers/transform_geoip.md b/docs/transformers/transform_geoip.md index 01954f9c..660c46f4 100644 --- a/docs/transformers/transform_geoip.md +++ b/docs/transformers/transform_geoip.md @@ -17,12 +17,16 @@ Options: * `mmdb-asn-file` (string) > path file to your mmdb asn database +* `lookup-ecs` (bool) + > lookup for about the original client IP (or part of it) if provided + ```yaml transforms: geoip: mmdb-country-file: "/GeoIP/GeoLite2-Country.mmdb" mmdb-city-file: "" mmdb-asn-file: "" + lookup-ecs: false ``` When the feature is enabled, the following json field are populated in your DNS message: diff --git a/pkgconfig/transformers.go b/pkgconfig/transformers.go index 8d87f0c0..81ce79c8 100644 --- a/pkgconfig/transformers.go +++ b/pkgconfig/transformers.go @@ -59,6 +59,7 @@ type ConfigTransformers struct { } `yaml:"filtering"` GeoIP struct { Enable bool `yaml:"enable" default:"false"` + LookupECS bool `yaml:"lookup-ecs" default:"false"` DBCountryFile string `yaml:"mmdb-country-file" default:""` DBCityFile string `yaml:"mmdb-city-file" default:""` DBASNFile string `yaml:"mmdb-asn-file" default:""` diff --git a/transformers/geoip.go b/transformers/geoip.go index 02dd8545..9be36252 100644 --- a/transformers/geoip.go +++ b/transformers/geoip.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "strconv" + "strings" "github.com/dmachard/go-dnscollector/dnsutils" "github.com/dmachard/go-dnscollector/pkgconfig" @@ -138,7 +139,17 @@ func (t *GeoIPTransform) geoipTransform(dm *dnsutils.DNSMessage) (int, error) { dm.Geo = &dnsutils.TransformDNSGeo{CountryIsoCode: "-", City: "-", Continent: "-", AutonomousSystemNumber: "-", AutonomousSystemOrg: "-"} } - geoInfo, err := t.Lookup(dm.NetworkInfo.QueryIP) + clientIP := dm.NetworkInfo.QueryIP + + // lookup ecs ip instead of the query ip? + if t.config.GeoIP.LookupECS && len(dm.EDNS.Options) > 0 { + ecsIP := lookupECSIP(dm) + if ecsIP != "" { + clientIP = ecsIP + } + } + + geoInfo, err := t.Lookup(clientIP) if err != nil { return ReturnKeep, err } @@ -151,3 +162,16 @@ func (t *GeoIPTransform) geoipTransform(dm *dnsutils.DNSMessage) (int, error) { return ReturnKeep, nil } + +// lookupECSIP extracts the ECS IP from the EDNS options if available and valid. +func lookupECSIP(dm *dnsutils.DNSMessage) string { + for _, opt := range dm.EDNS.Options { + if opt.Code == 8 { + ecsIP := strings.Split(opt.Data, "/")[0] + if net.ParseIP(ecsIP) != nil { + return ecsIP + } + } + } + return "" +} diff --git a/transformers/geoip_test.go b/transformers/geoip_test.go index 03e65061..ce080106 100644 --- a/transformers/geoip_test.go +++ b/transformers/geoip_test.go @@ -8,6 +8,7 @@ import ( "github.com/dmachard/go-dnscollector/dnsutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" + "github.com/stretchr/testify/require" ) func TestGeoIP_Json(t *testing.T) { @@ -123,3 +124,36 @@ func TestGeoIP_LookupAsn(t *testing.T) { t.Errorf("asn organisation invalid want: XX got: %s", geoInfo.ASO) } } + +func TestGeoIP_Lookup_ECS(t *testing.T) { + // enable geoip + config := pkgconfig.GetFakeConfigTransformers() + config.GeoIP.Enable = true + config.GeoIP.DBCountryFile = "../tests/testsdata/GeoLite2-Country.mmdb" + config.GeoIP.LookupECS = true + + outChans := []chan dnsutils.DNSMessage{} + + // init the processor + geoip := NewDNSGeoIPTransform(config, logger.New(false), "test", 0, outChans) + _, err := geoip.GetTransforms() + if err != nil { + t.Fatalf("geoip init failed: %v+", err) + } + defer geoip.Close() + + // Create a test DNS message with EDNS ECS data + dm := dnsutils.GetFakeDNSMessage() + dm.NetworkInfo.QueryIP = "1.1.1.1" // AU + dm.EDNS.Options = append(dm.EDNS.Options, dnsutils.DNSOption{Code: 8, Name: "CSUBNET", Data: "1.58.30.6/32"}) // CN + + // Apply the transform + returnCode, err := geoip.geoipTransform(&dm) + require.NoError(t, err, "process transform failed") + + // Validate the country code + require.Equal(t, "CN", dm.Geo.CountryIsoCode, "country code mismatch") + + // Ensure the return code is ReturnKeep + require.Equal(t, ReturnKeep, returnCode, "unexpected return code") +}