Skip to content

Commit

Permalink
feat: add ASN info & accuracy radius
Browse files Browse the repository at this point in the history
Signed-off-by: Liam Stanley <[email protected]>
  • Loading branch information
lrstanley committed Aug 21, 2022
1 parent b89d415 commit b6cf62b
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 227 deletions.
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ go-debug: go-prepare
--http.max-concurrent 0 \
--dns.resolver "8.8.8.8" \
--dns.resolver "1.1.1.1" \
--log.quiet
# --debug
--debug

go-build: go-prepare go-fetch
CGO_ENABLED=0 \
Expand Down
154 changes: 154 additions & 0 deletions internal/lookup/geoip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Liam Stanley <[email protected]>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package lookup

import (
"context"
"fmt"
"net"
"strings"

"github.com/lrstanley/geoip/internal/models"
"github.com/lrstanley/go-bogon"
maxminddb "github.com/oschwald/maxminddb-golang"
)

// Lookup does a geoip lookup of an address.
func (s *Service) Lookup(ctx context.Context, r *models.LookupRequest) (result *models.Response, err error) {
if r.Language == "" {
r.Language = s.config.DefaultLanguage
}

if val := s.cache.Get(r.CacheID()); val != nil {
val.Cached = true
return val, nil
}

ip := net.ParseIP(r.Address)
if ip == nil {
ip, err = s.rslv.GetIP(ctx, r.Address)
if err != nil || ip == nil {
s.logger.WithError(err).WithField("addr", r.Address).Debug("error looking up addr as hostname")

return &models.Response{Error: fmt.Sprintf("invalid ip/host specified (or timed out): %s", r.Address)}, nil
}
}

if is, _ := bogon.Is(ip.String()); is {
return &models.Response{Error: "internal address"}, nil
}

geo, err := s.lookupGeo(ctx, ip)
if err != nil {
return nil, err
}

network, asn, err := s.lookupASN(ctx, ip)
if err != nil {
return nil, err
}

result = &models.Response{
IP: ip.String(),
City: geo.City.Names[r.Language],
Country: geo.Country.Names[r.Language],
CountryCode: geo.Country.Code,
Continent: geo.Continent.Names[r.Language],
ContinentCode: geo.Continent.Code,
Lat: geo.Location.Lat,
Long: geo.Location.Long,
Timezone: geo.Location.TimeZone,
AccuracyRadiusKM: geo.Location.AccuracyRadiusKM,
PostalCode: geo.Postal.Code,
ASN: fmt.Sprintf("ASN%d", asn.AutonomousSystemNumber),
ASNOrg: asn.AutonomousSystemOrg,
Network: network.String(),
}

if v4 := ip.To4(); v4 != nil {
result.IPType = 4
} else if v6 := ip.To16(); v6 != nil {
result.IPType = 6
}

var subdiv []string
for i := 0; i < len(geo.Subdivisions); i++ {
subdiv = append(subdiv, geo.Subdivisions[i].Names[r.Language])
}
result.Subdivision = strings.Join(subdiv, ", ")

var summary []string
if result.City != "" {
summary = append(summary, result.City)
}

if result.Subdivision != "" && result.City != result.Subdivision {
summary = append(summary, result.Subdivision)
}

if result.Country != "" && len(summary) == 0 {
summary = append(summary, result.Country)
} else if result.CountryCode != "" {
summary = append(summary, result.CountryCode)
}

if result.Continent != "" && len(summary) == 0 {
summary = append(summary, result.Continent)
} else if result.ContinentCode != "" && result.Subdivision == "" && result.City == "" {
summary = append(summary, result.ContinentCode)
}

result.Summary = strings.Join(summary, ", ")

if result.Summary == "" {
result.Error = "no results found"
}

result.Host, err = s.rslv.GetReverse(ctx, ip)
if err != nil {
s.logger.WithError(err).WithField("ip", ip.String()).Debug("error looking up reverse dns for ip")
}

s.cache.Set(r.CacheID(), result)
return result, nil
}

func (s *Service) lookupASN(ctx context.Context, ip net.IP) (network *net.IPNet, query *models.ASNQuery, err error) {
var db *maxminddb.Reader

db, err = maxminddb.Open(s.config.ASNPath)
if err != nil {
s.logger.WithError(err).Error("error opening asn db")
return nil, nil, err
}
defer db.Close()

query = &models.ASNQuery{}
if network, _, err = db.LookupNetwork(ip, &query); err != nil {
s.logger.WithError(err).WithField("ip", ip.String()).Error("error looking up ip asn info")
return nil, nil, err
}

return network, query, nil
}

func (s *Service) lookupGeo(ctx context.Context, ip net.IP) (query *models.GeoQuery, err error) {
var db *maxminddb.Reader

db, err = maxminddb.Open(s.config.GeoIPPath)
if err != nil {
s.logger.WithError(err).Error("error opening geoip db")
return nil, err
}
defer db.Close()

query = &models.GeoQuery{}
if err = db.Lookup(ip, query); err != nil {
s.logger.WithError(err).WithField("ip", ip.String()).Error("error looking up ip geoip info")
return nil, err
}

return query, nil
}
45 changes: 0 additions & 45 deletions internal/lookup/languages.go

This file was deleted.

131 changes: 30 additions & 101 deletions internal/lookup/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ package lookup

import (
"context"
"fmt"
"net"
"strings"
"sync/atomic"

"github.com/apex/log"
"github.com/lrstanley/geoip/internal/cache"
"github.com/lrstanley/geoip/internal/dns"
"github.com/lrstanley/geoip/internal/models"
"github.com/lrstanley/go-bogon"
maxminddb "github.com/oschwald/maxminddb-golang"
)

Expand All @@ -24,124 +21,56 @@ type Service struct {
logger log.Interface
config models.ConfigDB

cache *cache.Cache[string, *models.GeoResult]
cache *cache.Cache[string, *models.Response]
metadata atomic.Pointer[maxminddb.Metadata]

rslv *dns.Resolver
}

func NewService(ctx context.Context, logger log.Interface, config models.ConfigDB, rslv *dns.Resolver) *Service {
return &Service{
ctx: ctx,
logger: logger.WithFields(log.Fields{
"src": "lookup",
"path": config.Path,
}),
ctx: ctx,
logger: logger.WithFields(log.Fields{"src": "lookup"}),
config: config,
cache: cache.New[string, *models.GeoResult](config.CacheSize, config.CacheExpire),
cache: cache.New[string, *models.Response](config.CacheSize, config.CacheExpire),
rslv: rslv,
}
}

// Lookup does a geoip lookup of an address.
func (s *Service) Lookup(ctx context.Context, r *models.LookupRequest) (*models.GeoResult, error) {
var result *models.GeoResult
var err error

if r.Language == "" {
r.Language = s.config.DefaultLanguage
}
func (s *Service) Metadata() *maxminddb.Metadata {
return s.metadata.Load()
}

if val := s.cache.Get(r.CacheID()); val != nil {
val.Cached = true
return val, nil
func (s *Service) MatchLanguage(lang string) (match string) {
if lang == "" {
return ""
}

ip := net.ParseIP(r.Address)
if ip == nil {
ip, err = s.rslv.GetIP(ctx, r.Address)
if err != nil || ip == nil {
s.logger.WithError(err).WithField("addr", r.Address).Debug("error looking up addr as hostname")
supported := s.Metadata().Languages

return &models.GeoResult{Error: fmt.Sprintf("invalid ip/host specified: %s", r.Address)}, nil
for i := 0; i < len(supported); i++ {
if strings.EqualFold(lang, supported[i]) {
return supported[i]
}
}

if is, _ := bogon.Is(ip.String()); is {
return &models.GeoResult{Error: "internal address"}, nil
}

db, err := maxminddb.Open(s.config.Path)
if err != nil {
s.logger.WithError(err).Error("error opening db")
return nil, err
}

var query models.GeoQuery

err = db.Lookup(ip, &query)
db.Close()

if err != nil {
return nil, err
}

result = &models.GeoResult{
IP: ip,
City: query.City.Names[r.Language],
Country: query.Country.Names[r.Language],
CountryCode: query.Country.Code,
Continent: query.Continent.Names[r.Language],
ContinentCode: query.Continent.Code,
Lat: query.Location.Lat,
Long: query.Location.Long,
Timezone: query.Location.TimeZone,
PostalCode: query.Postal.Code,
Proxy: query.Traits.Proxy,
}

var subdiv []string
for i := 0; i < len(query.Subdivisions); i++ {
subdiv = append(subdiv, query.Subdivisions[i].Names[r.Language])
}
result.Subdivision = strings.Join(subdiv, ", ")
if j := strings.Index(supported[i], "-"); j > 0 {
if strings.EqualFold(lang, supported[i][:j]) {
return supported[i]
}

var summary []string
if result.City != "" {
summary = append(summary, result.City)
}

if result.Subdivision != "" && result.City != result.Subdivision {
summary = append(summary, result.Subdivision)
}

if result.Country != "" && len(summary) == 0 {
summary = append(summary, result.Country)
} else if result.CountryCode != "" {
summary = append(summary, result.CountryCode)
}

if result.Continent != "" && len(summary) == 0 {
summary = append(summary, result.Continent)
} else if result.ContinentCode != "" && result.Subdivision == "" && result.City == "" {
summary = append(summary, result.ContinentCode)
}

result.Summary = strings.Join(summary, ", ")

if result.Summary == "" {
result.Error = "no results found"
}
if k := strings.Index(lang, "-"); k > 0 {
if strings.EqualFold(lang[:k], supported[i][:j]) {
return supported[i]
}
}
}

result.Host, err = s.rslv.GetReverse(ctx, ip)
if err != nil {
s.logger.WithError(err).WithField("ip", ip.String()).Debug("error looking up reverse dns for ip")
if j := strings.Index(lang, "-"); j > 0 {
if strings.EqualFold(lang[:j], supported[i]) {
return supported[i]
}
}
}

s.cache.Set(r.CacheID(), result)
return result, nil
}

func (s *Service) Metadata() *maxminddb.Metadata {
return s.metadata.Load()
return ""
}
Loading

0 comments on commit b6cf62b

Please sign in to comment.