Create geoip plugin (#4688)
* Create geoip plugin Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update plugin/geoip/README.md Co-authored-by: Miek Gieben <miek@miek.nl> Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update plugin/geoip/README.md Co-authored-by: Miek Gieben <miek@miek.nl> Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update plugin/geoip/README.md Co-authored-by: Miek Gieben <miek@miek.nl> Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Move DBFILE bullet below example Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update plugin/geoip/README.md Co-authored-by: Miek Gieben <miek@miek.nl> Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Remove plugin name test case Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Remove languages option Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update free database link Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Remove last language bits Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Use 127.0.0.1 as probing IP Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update plugin/geoip/geoip.go Co-authored-by: Miek Gieben <miek@miek.nl> Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Update plugin/geoip/geoip.go Co-authored-by: Miek Gieben <miek@miek.nl> Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Use relative path for fixtures dir Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Set names with default string zero value Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Remove unused db types Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Remove non city databases in testdata Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Remove create databases main Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Fix metadata label format test case Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Fix import path block Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * go fmt after changes Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Tidy up go.mod and go.sum Signed-off-by: Sven Nebel <nebel.sven@gmail.com> * Add plugin to CODEOWNERS Signed-off-by: Sven Nebel <nebel.sven@gmail.com> Co-authored-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
parent
936b483a3a
commit
21f1207afe
17 changed files with 573 additions and 6 deletions
|
@ -30,6 +30,7 @@ go.mod @miekg @chrisohaver @johnbelamaric @yongtang @stp-ip
|
|||
/plugin/etcd/ @miekg @nitisht
|
||||
/plugin/file/ @miekg @yongtang @stp-ip
|
||||
/plugin/forward/ @johnbelamaric @miekg @rdrozhdzh
|
||||
/plugin/geoip/ @miekg @snebel29
|
||||
/plugin/grpc/ @inigohu @miekg @zouyee
|
||||
/plugin/health/ @fastest963 @miekg @zouyee
|
||||
/plugin/hosts/ @johnbelamaric @pmoroney
|
||||
|
|
|
@ -11,6 +11,7 @@ package dnsserver
|
|||
// care what plugin above them are doing.
|
||||
var Directives = []string{
|
||||
"metadata",
|
||||
"geoip",
|
||||
"cancel",
|
||||
"tls",
|
||||
"reload",
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
_ "github.com/coredns/coredns/plugin/etcd"
|
||||
_ "github.com/coredns/coredns/plugin/file"
|
||||
_ "github.com/coredns/coredns/plugin/forward"
|
||||
_ "github.com/coredns/coredns/plugin/geoip"
|
||||
_ "github.com/coredns/coredns/plugin/grpc"
|
||||
_ "github.com/coredns/coredns/plugin/health"
|
||||
_ "github.com/coredns/coredns/plugin/hosts"
|
||||
|
|
1
go.mod
1
go.mod
|
@ -23,6 +23,7 @@ require (
|
|||
github.com/opentracing/opentracing-go v1.2.0
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
|
||||
github.com/openzipkin/zipkin-go v0.2.2
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
|
|
5
go.sum
5
go.sum
|
@ -342,6 +342,10 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
|
|||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
|
@ -558,6 +562,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
# log:log
|
||||
|
||||
metadata:metadata
|
||||
geoip:geoip
|
||||
cancel:cancel
|
||||
tls:tls
|
||||
reload:reload
|
||||
|
|
73
plugin/geoip/README.md
Normal file
73
plugin/geoip/README.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
# geoip
|
||||
|
||||
## Name
|
||||
*geoip* - Lookup maxmind geoip2 databases using the client IP, then add associated geoip data to the context request.
|
||||
|
||||
## Description
|
||||
The *geoip* plugin add geo location data associated with the client IP, it allows you to configure a [geoIP2 maxmind database](https://dev.maxmind.com/geoip/docs/databases) to add the geo location data associated with the IP address.
|
||||
|
||||
The data is added leveraging the *metadata* plugin, values can then be retrieved using it as well, for example:
|
||||
|
||||
```go
|
||||
import (
|
||||
"strconv"
|
||||
"github.com/coredns/coredns/plugin/metadata"
|
||||
)
|
||||
// ...
|
||||
if getLongitude := metadata.ValueFunc(ctx, "geoip/longitude"); getLongitude != nil {
|
||||
if longitude, err := strconv.ParseFloat(getLongitude(), 64); err == nil {
|
||||
// Do something useful with longitude.
|
||||
}
|
||||
} else {
|
||||
// The metadata label geoip/longitude for some reason, was not set.
|
||||
}
|
||||
// ...
|
||||
```
|
||||
|
||||
## Databases
|
||||
The supported databases use city schema such as `City` and `Enterprise`. Other databases types with different schemas are not supported yet.
|
||||
|
||||
You can download a [free and public City database](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data).
|
||||
|
||||
## Syntax
|
||||
```txt
|
||||
geoip [DBFILE]
|
||||
```
|
||||
* **DBFILE** the mmdb database file path.
|
||||
|
||||
## Examples
|
||||
The following configuration configures the `City` database.
|
||||
```txt
|
||||
. {
|
||||
geoip /opt/geoip2/db/GeoLite2-City.mmdb
|
||||
metadata # Note that metadata plugin must be enabled as well.
|
||||
}
|
||||
```
|
||||
|
||||
## Metadatada Labels
|
||||
A limited set of fields will be exported as labels, all values are stored using strings **regardless of their underlying value type**, and therefore you may have to convert it back to its original type, note that numeric values are always represented in base 10.
|
||||
|
||||
| Label | Type | Example | Description
|
||||
| :----------------------------------- | :-------- | :-------------- | :------------------
|
||||
| `geoip/city/name` | `string` | `Cambridge` | Then city name in English language.
|
||||
| `geoip/country/code` | `string` | `GB` | Country [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) code.
|
||||
| `geoip/country/name` | `string` | `United Kingdom` | The country name in English language.
|
||||
| `geoip/country/is_in_european_union` | `bool` | `false` | Either `true` or `false`.
|
||||
| `geoip/continent/code` | `string` | `EU` | See [Continent codes](#ContinentCodes).
|
||||
| `geoip/continent/name` | `string` | `Europe` | The continent name in English language.
|
||||
| `geoip/latitude` | `float64` | `52.2242` | Base 10, max available precision.
|
||||
| `geoip/longitude` | `float64` | `0.1315` | Base 10, max available precision.
|
||||
| `geoip/timezone` | `string` | `Europe/London` | The timezone.
|
||||
| `geoip/postalcode` | `string` | `CB4` | The postal code.
|
||||
|
||||
## Continent Codes
|
||||
|
||||
| Value | Continent (EN) |
|
||||
| :---- | :------------- |
|
||||
| AF | Africa |
|
||||
| AN | Antarctica |
|
||||
| AS | Asia |
|
||||
| EU | Europe |
|
||||
| NA | North America |
|
||||
| OC | Oceania |
|
||||
| SA | South America |
|
58
plugin/geoip/city.go
Normal file
58
plugin/geoip/city.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/coredns/coredns/plugin/metadata"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
const defaultLang = "en"
|
||||
|
||||
func (g GeoIP) setCityMetadata(ctx context.Context, data *geoip2.City) {
|
||||
// Set labels for city, country and continent names.
|
||||
cityName := data.City.Names[defaultLang]
|
||||
metadata.SetValueFunc(ctx, pluginName+"/city/name", func() string {
|
||||
return cityName
|
||||
})
|
||||
countryName := data.Country.Names[defaultLang]
|
||||
metadata.SetValueFunc(ctx, pluginName+"/country/name", func() string {
|
||||
return countryName
|
||||
})
|
||||
continentName := data.Continent.Names[defaultLang]
|
||||
metadata.SetValueFunc(ctx, pluginName+"/continent/name", func() string {
|
||||
return continentName
|
||||
})
|
||||
|
||||
countryCode := data.Country.IsoCode
|
||||
metadata.SetValueFunc(ctx, pluginName+"/country/code", func() string {
|
||||
return countryCode
|
||||
})
|
||||
isInEurope := strconv.FormatBool(data.Country.IsInEuropeanUnion)
|
||||
metadata.SetValueFunc(ctx, pluginName+"/country/is_in_european_union", func() string {
|
||||
return isInEurope
|
||||
})
|
||||
continentCode := data.Continent.Code
|
||||
metadata.SetValueFunc(ctx, pluginName+"/continent/code", func() string {
|
||||
return continentCode
|
||||
})
|
||||
|
||||
latitude := strconv.FormatFloat(float64(data.Location.Latitude), 'f', -1, 64)
|
||||
metadata.SetValueFunc(ctx, pluginName+"/latitude", func() string {
|
||||
return latitude
|
||||
})
|
||||
longitude := strconv.FormatFloat(float64(data.Location.Longitude), 'f', -1, 64)
|
||||
metadata.SetValueFunc(ctx, pluginName+"/longitude", func() string {
|
||||
return longitude
|
||||
})
|
||||
timeZone := data.Location.TimeZone
|
||||
metadata.SetValueFunc(ctx, pluginName+"/timezone", func() string {
|
||||
return timeZone
|
||||
})
|
||||
postalCode := data.Postal.Code
|
||||
metadata.SetValueFunc(ctx, pluginName+"/postalcode", func() string {
|
||||
return postalCode
|
||||
})
|
||||
}
|
95
plugin/geoip/geoip.go
Normal file
95
plugin/geoip/geoip.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Package geoip implements a max mind database plugin.
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
var log = clog.NewWithPlugin(pluginName)
|
||||
|
||||
// GeoIP is a plugin that add geo location data to the request context by looking up a maxmind
|
||||
// geoIP2 database, and which data can be later consumed by other middlewares.
|
||||
type GeoIP struct {
|
||||
Next plugin.Handler
|
||||
db db
|
||||
}
|
||||
|
||||
type db struct {
|
||||
*geoip2.Reader
|
||||
// provides defines the schemas that can be obtained by querying this database, by using
|
||||
// bitwise operations.
|
||||
provides int
|
||||
}
|
||||
|
||||
const (
|
||||
city = 1 << iota
|
||||
)
|
||||
|
||||
var probingIP = net.ParseIP("127.0.0.1")
|
||||
|
||||
func newGeoIP(dbPath string) (*GeoIP, error) {
|
||||
reader, err := geoip2.Open(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database file: %v", err)
|
||||
}
|
||||
db := db{Reader: reader}
|
||||
schemas := []struct {
|
||||
provides int
|
||||
name string
|
||||
validate func() error
|
||||
}{
|
||||
{name: "city", provides: city, validate: func() error { _, err := reader.City(probingIP); return err }},
|
||||
}
|
||||
// Query the database to figure out the database type.
|
||||
for _, schema := range schemas {
|
||||
if err := schema.validate(); err != nil {
|
||||
// If we get an InvalidMethodError then we know this database does not provide that schema.
|
||||
if _, ok := err.(geoip2.InvalidMethodError); !ok {
|
||||
return nil, fmt.Errorf("unexpected failure looking up database %q schema %q: %v", filepath.Base(dbPath), schema.name, err)
|
||||
}
|
||||
} else {
|
||||
db.provides = db.provides | schema.provides
|
||||
}
|
||||
}
|
||||
|
||||
if db.provides&city == 0 {
|
||||
return nil, fmt.Errorf("database does not provide city schema")
|
||||
}
|
||||
|
||||
return &GeoIP{db: db}, nil
|
||||
}
|
||||
|
||||
// ServeDNS implements the plugin.Handler interface.
|
||||
func (g GeoIP) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
return plugin.NextOrFailure(pluginName, g.Next, ctx, w, r)
|
||||
}
|
||||
|
||||
// Metadata implements the metadata.Provider Interface in the metadata plugin, and is used to store
|
||||
// the data associated with the source IP of every request.
|
||||
func (g GeoIP) Metadata(ctx context.Context, state request.Request) context.Context {
|
||||
srcIP := net.ParseIP(state.IP())
|
||||
|
||||
switch {
|
||||
case g.db.provides&city == city:
|
||||
data, err := g.db.City(srcIP)
|
||||
if err != nil {
|
||||
log.Debugf("Setting up metadata failed due to database lookup error: %v", err)
|
||||
return ctx
|
||||
}
|
||||
g.setCityMetadata(ctx, data)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Name implements the Handler interface.
|
||||
func (g GeoIP) Name() string { return pluginName }
|
61
plugin/geoip/geoip_test.go
Normal file
61
plugin/geoip/geoip_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin/metadata"
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
"github.com/coredns/coredns/request"
|
||||
)
|
||||
|
||||
func TestMetadata(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
dbPath string
|
||||
label string
|
||||
expectedValue string
|
||||
}{
|
||||
{cityDBPath, "geoip/city/name", "Cambridge"},
|
||||
|
||||
{cityDBPath, "geoip/country/code", "GB"},
|
||||
{cityDBPath, "geoip/country/name", "United Kingdom"},
|
||||
// is_in_european_union is set to true only to work around bool zero value, and test is really being set.
|
||||
{cityDBPath, "geoip/country/is_in_european_union", "true"},
|
||||
|
||||
{cityDBPath, "geoip/continent/code", "EU"},
|
||||
{cityDBPath, "geoip/continent/name", "Europe"},
|
||||
|
||||
{cityDBPath, "geoip/latitude", "52.2242"},
|
||||
{cityDBPath, "geoip/longitude", "0.1315"},
|
||||
{cityDBPath, "geoip/timezone", "Europe/London"},
|
||||
{cityDBPath, "geoip/postalcode", "CB4"},
|
||||
}
|
||||
|
||||
for i, _test := range tests {
|
||||
geoIP, err := newGeoIP(_test.dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: unable to create geoIP plugin: %v", i, err)
|
||||
}
|
||||
state := request.Request{
|
||||
W: &test.ResponseWriter{RemoteIP: "81.2.69.142"}, // This IP should be be part of the CDIR address range used to create the database fixtures.
|
||||
}
|
||||
ctx := metadata.ContextWithMetadata(context.Background())
|
||||
rCtx := geoIP.Metadata(ctx, state)
|
||||
if fmt.Sprintf("%p", ctx) != fmt.Sprintf("%p", rCtx) {
|
||||
t.Errorf("Test %d: returned context is expected to be the same one passed in the Metadata function", i)
|
||||
}
|
||||
|
||||
fn := metadata.ValueFunc(ctx, _test.label)
|
||||
if fn == nil {
|
||||
t.Errorf("Test %d: label %q not set in metadata plugin context", i, _test.label)
|
||||
continue
|
||||
}
|
||||
value := fn()
|
||||
if value != _test.expectedValue {
|
||||
t.Errorf("Test %d: expected value for label %q should be %q, got %q instead",
|
||||
i, _test.label, _test.expectedValue, value)
|
||||
}
|
||||
}
|
||||
}
|
53
plugin/geoip/setup.go
Normal file
53
plugin/geoip/setup.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"github.com/coredns/caddy"
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
)
|
||||
|
||||
const pluginName = "geoip"
|
||||
|
||||
func init() { plugin.Register(pluginName, setup) }
|
||||
|
||||
func setup(c *caddy.Controller) error {
|
||||
geoip, err := geoipParse(c)
|
||||
if err != nil {
|
||||
return plugin.Error(pluginName, err)
|
||||
}
|
||||
|
||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||
geoip.Next = next
|
||||
return geoip
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func geoipParse(c *caddy.Controller) (*GeoIP, error) {
|
||||
var dbPath string
|
||||
|
||||
for c.Next() {
|
||||
if !c.NextArg() {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
if dbPath != "" {
|
||||
return nil, c.Errf("configuring multiple databases is not supported")
|
||||
}
|
||||
dbPath = c.Val()
|
||||
// There shouldn't be any more arguments.
|
||||
if len(c.RemainingArgs()) != 0 {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
// The plugin should not have any config block.
|
||||
if c.NextBlock() {
|
||||
return nil, c.Err("unexpected config block")
|
||||
}
|
||||
}
|
||||
|
||||
geoIP, err := newGeoIP(dbPath)
|
||||
if err != nil {
|
||||
return geoIP, c.Err(err.Error())
|
||||
}
|
||||
return geoIP, nil
|
||||
}
|
109
plugin/geoip/setup_test.go
Normal file
109
plugin/geoip/setup_test.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package geoip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
)
|
||||
|
||||
var (
|
||||
fixturesDir = "./testdata"
|
||||
cityDBPath = filepath.Join(fixturesDir, "GeoLite2-City.mmdb")
|
||||
unknownDBPath = filepath.Join(fixturesDir, "GeoLite2-UnknownDbType.mmdb")
|
||||
)
|
||||
|
||||
func TestProbingIP(t *testing.T) {
|
||||
if probingIP == nil {
|
||||
t.Fatalf("Invalid probing IP: %q", probingIP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
c := caddy.NewTestController("dns", fmt.Sprintf("%s %s", pluginName, cityDBPath))
|
||||
plugins := dnsserver.GetConfig(c).Plugin
|
||||
if len(plugins) != 0 {
|
||||
t.Fatalf("Expected zero plugins after setup, %d found", len(plugins))
|
||||
}
|
||||
if err := setup(c); err != nil {
|
||||
t.Fatalf("Expected no errors, but got: %v", err)
|
||||
}
|
||||
plugins = dnsserver.GetConfig(c).Plugin
|
||||
if len(plugins) != 1 {
|
||||
t.Fatalf("Expected one plugin after setup, %d found", len(plugins))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPParse(t *testing.T) {
|
||||
c := caddy.NewTestController("dns", fmt.Sprintf("%s %s", pluginName, cityDBPath))
|
||||
if err := setup(c); err != nil {
|
||||
t.Fatalf("Expected no errors, but got: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
shouldErr bool
|
||||
config string
|
||||
expectedErr string
|
||||
expectedDBType int
|
||||
}{
|
||||
// Valid
|
||||
{false, fmt.Sprintf("%s %s\n", pluginName, cityDBPath), "", city},
|
||||
|
||||
// Invalid
|
||||
{true, pluginName, "Wrong argument count", 0},
|
||||
{true, fmt.Sprintf("%s %s {\n\tlanguages en fr es zh-CN\n}\n", pluginName, cityDBPath), "unexpected config block", 0},
|
||||
{true, fmt.Sprintf("%s %s\n%s %s\n", pluginName, cityDBPath, pluginName, cityDBPath), "configuring multiple databases is not supported", 0},
|
||||
{true, fmt.Sprintf("%s 1 2 3", pluginName), "Wrong argument count", 0},
|
||||
{true, fmt.Sprintf("%s { }", pluginName), "Error during parsing", 0},
|
||||
{true, fmt.Sprintf("%s /dbpath { city }", pluginName), "unexpected config block", 0},
|
||||
{true, fmt.Sprintf("%s /invalidPath\n", pluginName), "failed to open database file: open /invalidPath: no such file or directory", 0},
|
||||
{true, fmt.Sprintf("%s %s\n", pluginName, unknownDBPath), "reader does not support the \"UnknownDbType\" database type", 0},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
c := caddy.NewTestController("dns", test.config)
|
||||
geoIP, err := geoipParse(c)
|
||||
|
||||
if test.shouldErr && err == nil {
|
||||
t.Errorf("Test %d: expected error but found none for input %s", i, test.config)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !test.shouldErr {
|
||||
t.Errorf("Test %d: expected no error but found one for input %s, got: %v", i, test.config, err)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Errorf("Test %d: expected error to contain: %v, found error: %v, input: %s", i, test.expectedErr, err, test.config)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if geoIP.db.Reader == nil {
|
||||
t.Errorf("Test %d: after parsing database reader should be initialized", i)
|
||||
}
|
||||
|
||||
if geoIP.db.provides&test.expectedDBType == 0 {
|
||||
t.Errorf("Test %d: expected db type %d not found, database file provides %d", i, test.expectedDBType, geoIP.db.provides)
|
||||
}
|
||||
}
|
||||
|
||||
// Set nil probingIP to test unexpected validate error()
|
||||
defer func(ip net.IP) { probingIP = ip }(probingIP)
|
||||
probingIP = nil
|
||||
|
||||
c = caddy.NewTestController("dns", fmt.Sprintf("%s %s\n", pluginName, cityDBPath))
|
||||
_, err := geoipParse(c)
|
||||
if err != nil {
|
||||
expectedErr := "unexpected failure looking up database"
|
||||
if !strings.Contains(err.Error(), expectedErr) {
|
||||
t.Errorf("expected error to contain: %s", expectedErr)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("with a nil probingIP test is expected to fail")
|
||||
}
|
||||
}
|
BIN
plugin/geoip/testdata/GeoLite2-City.mmdb
vendored
Normal file
BIN
plugin/geoip/testdata/GeoLite2-City.mmdb
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
plugin/geoip/testdata/GeoLite2-UnknownDbType.mmdb
vendored
Normal file
BIN
plugin/geoip/testdata/GeoLite2-UnknownDbType.mmdb
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
112
plugin/geoip/testdata/README.md
vendored
Normal file
112
plugin/geoip/testdata/README.md
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
# testdata
|
||||
This directory contains mmdb database files used during the testing of this plugin.
|
||||
|
||||
# Create mmdb database files
|
||||
If you need to change them to add a new value, or field the best is to recreate them, the code snipped used to create them initially is provided next.
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/maxmind/mmdbwriter"
|
||||
"github.com/maxmind/mmdbwriter/inserter"
|
||||
"github.com/maxmind/mmdbwriter/mmdbtype"
|
||||
)
|
||||
|
||||
const cdir = "81.2.69.142/32"
|
||||
|
||||
// Create new mmdb database fixtures in this directory.
|
||||
func main() {
|
||||
createCityDB("GeoLite2-City.mmdb", "DBIP-City-Lite")
|
||||
// Create unkwnon database type.
|
||||
createCityDB("GeoLite2-UnknownDbType.mmdb", "UnknownDbType")
|
||||
}
|
||||
|
||||
func createCityDB(dbName, dbType string) {
|
||||
// Load a database writer.
|
||||
writer, err := mmdbwriter.New(mmdbwriter.Options{DatabaseType: dbType})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Define and insert the new data.
|
||||
_, ip, err := net.ParseCIDR(cdir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// TODO(snebel29): Find an alternative location in Europe Union.
|
||||
record := mmdbtype.Map{
|
||||
"city": mmdbtype.Map{
|
||||
"geoname_id": mmdbtype.Uint64(2653941),
|
||||
"names": mmdbtype.Map{
|
||||
"en": mmdbtype.String("Cambridge"),
|
||||
"es": mmdbtype.String("Cambridge"),
|
||||
},
|
||||
},
|
||||
"continent": mmdbtype.Map{
|
||||
"code": mmdbtype.String("EU"),
|
||||
"geoname_id": mmdbtype.Uint64(6255148),
|
||||
"names": mmdbtype.Map{
|
||||
"en": mmdbtype.String("Europe"),
|
||||
"es": mmdbtype.String("Europa"),
|
||||
},
|
||||
},
|
||||
"country": mmdbtype.Map{
|
||||
"iso_code": mmdbtype.String("GB"),
|
||||
"geoname_id": mmdbtype.Uint64(2635167),
|
||||
"names": mmdbtype.Map{
|
||||
"en": mmdbtype.String("United Kingdom"),
|
||||
"es": mmdbtype.String("Reino Unido"),
|
||||
},
|
||||
"is_in_european_union": mmdbtype.Bool(true),
|
||||
},
|
||||
"location": mmdbtype.Map{
|
||||
"accuracy_radius": mmdbtype.Uint16(200),
|
||||
"latitude": mmdbtype.Float64(52.2242),
|
||||
"longitude": mmdbtype.Float64(0.1315),
|
||||
"metro_code": mmdbtype.Uint64(0),
|
||||
"time_zone": mmdbtype.String("Europe/London"),
|
||||
},
|
||||
"postal": mmdbtype.Map{
|
||||
"code": mmdbtype.String("CB4"),
|
||||
},
|
||||
"registered_country": mmdbtype.Map{
|
||||
"iso_code": mmdbtype.String("GB"),
|
||||
"geoname_id": mmdbtype.Uint64(2635167),
|
||||
"names": mmdbtype.Map{"en": mmdbtype.String("United Kingdom")},
|
||||
"is_in_european_union": mmdbtype.Bool(false),
|
||||
},
|
||||
"subdivisions": mmdbtype.Slice{
|
||||
mmdbtype.Map{
|
||||
"iso_code": mmdbtype.String("ENG"),
|
||||
"geoname_id": mmdbtype.Uint64(6269131),
|
||||
"names": mmdbtype.Map{"en": mmdbtype.String("England")},
|
||||
},
|
||||
mmdbtype.Map{
|
||||
"iso_code": mmdbtype.String("CAM"),
|
||||
"geoname_id": mmdbtype.Uint64(2653940),
|
||||
"names": mmdbtype.Map{"en": mmdbtype.String("Cambridgeshire")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := writer.InsertFunc(ip, inserter.TopLevelMergeWith(record)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Write the DB to the filesystem.
|
||||
fh, err := os.Create(dbName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = writer.WriteTo(fh)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
|
@ -72,12 +72,12 @@ func TestLabelFormat(t *testing.T) {
|
|||
{"plugin/LABEL", true},
|
||||
{"p/LABEL", true},
|
||||
{"plugin/L", true},
|
||||
{"PLUGIN/LABEL/SUB-LABEL", true},
|
||||
// fails
|
||||
{"LABEL", false},
|
||||
{"plugin.LABEL", false},
|
||||
{"/NO-PLUGIN-NOT-ACCEPTED", false},
|
||||
{"ONLY-PLUGIN-NOT-ACCEPTED/", false},
|
||||
{"PLUGIN/LABEL/SUB-LABEL", false},
|
||||
{"/", false},
|
||||
{"//", false},
|
||||
}
|
||||
|
|
|
@ -56,17 +56,13 @@ type Provider interface {
|
|||
// Func is the type of function in the metadata, when called they return the value of the label.
|
||||
type Func func() string
|
||||
|
||||
// IsLabel checks that the provided name is a valid label name, i.e. two words separated by a slash.
|
||||
// IsLabel checks that the provided name is a valid label name, i.e. two or more words separated by a slash.
|
||||
func IsLabel(label string) bool {
|
||||
p := strings.Index(label, "/")
|
||||
if p <= 0 || p >= len(label)-1 {
|
||||
// cannot accept namespace empty nor label empty
|
||||
return false
|
||||
}
|
||||
if strings.LastIndex(label, "/") != p {
|
||||
// several slash in the Label
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue