From c60d472d7683fa2c93a1ab632a3049ddd88a3018 Mon Sep 17 00:00:00 2001 From: Rafael Gomes Date: Thu, 17 Sep 2020 14:12:01 -0300 Subject: [PATCH] objectmap: Add objectmap library code Change-Id: I71e16b48d974cbe165e6d75b0ba3f416a8eb6c7a --- go.mod | 3 +- go.sum | 7 ++++ objectmap/mapper.go | 88 ++++++++++++++++++++++++++++++++++++++++ objectmap/mapper_test.go | 80 ++++++++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 objectmap/mapper.go create mode 100644 objectmap/mapper_test.go diff --git a/go.mod b/go.mod index dbda708..3d396d2 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,10 @@ require ( github.com/btcsuite/btcutil v1.0.2 // indirect github.com/calebcase/tmpfile v1.0.2 // indirect github.com/gogo/protobuf v1.3.1 // indirect + github.com/oschwald/maxminddb-golang v1.7.0 github.com/spacemonkeygo/monkit/v3 v3.0.7-0.20200515175308-072401d8c752 github.com/spf13/cobra v1.0.0 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 github.com/zeebo/errs v1.2.2 go.uber.org/zap v1.15.0 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de diff --git a/go.sum b/go.sum index 59abeec..46830ea 100644 --- a/go.sum +++ b/go.sum @@ -169,6 +169,8 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/oschwald/maxminddb-golang v1.7.0 h1:JmU4Q1WBv5Q+2KZy5xJI+98aUwTIrPPxZUkd5Cwr8Zc= +github.com/oschwald/maxminddb-golang v1.7.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -228,6 +230,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k= @@ -361,6 +365,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107144601-ef85f5a75ddf h1:9cZxTVBvFZgOnVi/DobY3JsafbPFPnP2rtN81d4wPpw= golang.org/x/sys v0.0.0-20200107144601-ef85f5a75ddf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA= @@ -454,6 +459,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/objectmap/mapper.go b/objectmap/mapper.go new file mode 100644 index 0000000..411c406 --- /dev/null +++ b/objectmap/mapper.go @@ -0,0 +1,88 @@ +// Copyright (C) 2020 Storj Labs, Inc. +// See LICENSE for copying information. + +package objectmap + +import ( + "net" + + "github.com/oschwald/maxminddb-golang" + "github.com/zeebo/errs" +) + +// Error is the default error class for objectmap. +var Error = errs.Class("objectmap error") + +// IPInfo represents the geolocation data from maxmind db. +type IPInfo struct { + Location struct { + Latitude float64 `maxminddb:"latitude"` + Longitude float64 `maxminddb:"longitude"` + } `maxminddb:"location"` + Postal struct { + Code string `maxminddb:"code"` + } `maxminddb:"postal"` + Country struct { + IsoCode string `maxminddb:"iso_code"` + } `maxminddb:"country"` +} + +// Reader is a maxmind database reader interface. +type Reader interface { + Lookup(ip net.IP, result interface{}) error + Close() error +} + +// IPDB holds the database file path and its reader. +type IPDB struct { + reader Reader +} + +// NewIPDB creates a new IPMapper instance. +func NewIPDB(dbPath string) (*IPDB, error) { + reader, err := maxminddb.Open(dbPath) + if err != nil { + return nil, Error.Wrap(err) + } + + return &IPDB{ + reader: reader, + }, nil +} + +// Close closes the IPMapper reader. +func (mapper *IPDB) Close() (err error) { + if mapper.reader != nil { + return mapper.reader.Close() + } + return nil +} + +// ValidateIP validate and remove port from IP address. +func ValidateIP(ipAddress string) (net.IP, error) { + + ip, _, err := net.SplitHostPort(ipAddress) + if err != nil { + ip = ipAddress // assume it had no port + } + + parsed := net.ParseIP(ip) + if parsed == nil { + return nil, errs.New("invalid IP address: %s", ip) + } + return parsed, nil +} + +// GetIPInfos returns the geolocation information from an IP address. +func (mapper *IPDB) GetIPInfos(ipAddress string) (_ *IPInfo, err error) { + + var record IPInfo + parsed, err := ValidateIP(ipAddress) + if err != nil { + return nil, Error.Wrap(err) + } + + err = mapper.reader.Lookup(parsed, &record) + + return &record, Error.Wrap(err) +} diff --git a/objectmap/mapper_test.go b/objectmap/mapper_test.go new file mode 100644 index 0000000..e03c792 --- /dev/null +++ b/objectmap/mapper_test.go @@ -0,0 +1,80 @@ +// Copyright (C) 2020 Storj Labs, Inc. +// See LICENSE for copying information. + +package objectmap + +import ( + "errors" + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +type MockReader struct{} + +func (mr *MockReader) Lookup(ip net.IP, result interface{}) error { + + // Valid geolocation case + if ip.Equal(net.IPv4(172, 146, 10, 1)) { + result.(*IPInfo).Location = mockIPInfo(-19.456, 20.123).Location + return nil + } + // Location not found + if ip.Equal(net.IPv4(1, 1, 1, 1)) { + return errors.New("Not found") + } + return nil +} + +func (mr *MockReader) Close() error { + return nil +} + +func mockIPInfo(latitude, longitude float64) *IPInfo { + return &IPInfo{ + Location: struct { + Latitude float64 `maxminddb:"latitude"` + Longitude float64 `maxminddb:"longitude"` + }{ + Latitude: latitude, + Longitude: longitude, + }, + } +} +func TestIPDB_GetIPInfos(t *testing.T) { + + mockReader := &MockReader{} + + tests := []struct { + name string + reader *MockReader + ipAddress string + expected *IPInfo + expectedErr bool + }{ + {"invalid IP", mockReader, "999.999.999.999", nil, true}, + {"invalid (IP:PORT)", mockReader, "999.999.999.999:42", nil, true}, + {"valid IP found geolocation", mockReader, "172.146.10.1", mockIPInfo(-19.456, 20.123), false}, + {"valid (IP:PORT) found geolocation", mockReader, "172.146.10.1:4545", mockIPInfo(-19.456, 20.123), false}, + {"valid IP geolocation not found", mockReader, "1.1.1.1", &IPInfo{}, true}, + {"valid (IP:PORT) geolocation not found", mockReader, "1.1.1.1:1000", &IPInfo{}, true}, + } + for _, tt := range tests { + mapper := &IPDB{ + reader: tt.reader, + } + testCase := tt + t.Run(tt.name, func(t *testing.T) { + got, err := mapper.GetIPInfos(testCase.ipAddress) + + if testCase.expectedErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.EqualValues(t, testCase.expected, got) + }) + } +}