ooni-probe-cli/internal/engine/geolocate/geolocate.go
Simone Basso f271e71c0b
geolocate: first pass of code review and minor fixes (#359)
* doc(geolocate): minor cleanup

* more minor cleanups of geolocate

* remove disabled test and see whether now it works
2021-06-04 16:06:24 +02:00

209 lines
5.6 KiB
Go

// Package geolocate implements IP lookup, resolver lookup, and geolocation.
package geolocate
import (
"context"
"fmt"
"github.com/ooni/probe-cli/v3/internal/engine/netx"
"github.com/ooni/probe-cli/v3/internal/version"
)
const (
// DefaultProbeASN is the default probe ASN as a number.
DefaultProbeASN uint = 0
// DefaultProbeCC is the default probe CC.
DefaultProbeCC = "ZZ"
// DefaultProbeIP is the default probe IP.
DefaultProbeIP = "127.0.0.1"
// DefaultProbeNetworkName is the default probe network name.
DefaultProbeNetworkName = ""
// DefaultResolverASN is the default resolver ASN.
DefaultResolverASN uint = 0
// DefaultResolverIP is the default resolver IP.
DefaultResolverIP = "127.0.0.2"
// DefaultResolverNetworkName is the default resolver network name.
DefaultResolverNetworkName = ""
)
var (
// DefaultProbeASNString is the default probe ASN as a string.
DefaultProbeASNString = fmt.Sprintf("AS%d", DefaultProbeASN)
// DefaultResolverASNString is the default resolver ASN as a string.
DefaultResolverASNString = fmt.Sprintf("AS%d", DefaultResolverASN)
)
// Logger is the definition of Logger used by this package.
type Logger interface {
Debug(msg string)
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
}
// Results contains geolocate results.
type Results struct {
// ASN is the autonomous system number.
ASN uint
// CountryCode is the country code.
CountryCode string
// didResolverLookup indicates whether we did a resolver lookup.
didResolverLookup bool
// NetworkName is the network name.
NetworkName string
// IP is the probe IP.
ProbeIP string
// ResolverASN is the resolver ASN.
ResolverASN uint
// ResolverIP is the resolver IP.
ResolverIP string
// ResolverNetworkName is the resolver network name.
ResolverNetworkName string
}
// ASNString returns the ASN as a string.
func (r *Results) ASNString() string {
return fmt.Sprintf("AS%d", r.ASN)
}
type probeIPLookupper interface {
LookupProbeIP(ctx context.Context) (addr string, err error)
}
type asnLookupper interface {
LookupASN(ip string) (asn uint, network string, err error)
}
type countryLookupper interface {
LookupCC(ip string) (cc string, err error)
}
type resolverIPLookupper interface {
LookupResolverIP(ctx context.Context) (addr string, err error)
}
// Resolver is a DNS resolver.
type Resolver interface {
LookupHost(ctx context.Context, domain string) ([]string, error)
Network() string
Address() string
}
// Config contains configuration for a geolocate Task.
type Config struct {
// Resolver is the resolver we should use when
// making requests for discovering the IP. When
// this field is not set, we use the stdlib.
Resolver Resolver
// Logger is the logger to use. If not set, then we will
// use a logger that discards all messages.
Logger Logger
// UserAgent is the user agent to use. If not set, then
// we will use a default user agent.
UserAgent string
}
// discardLogger just ignores log messages thrown at it.
type discardLogger struct{}
func (*discardLogger) Debug(msg string) {}
func (*discardLogger) Debugf(format string, v ...interface{}) {}
func (*discardLogger) Infof(format string, v ...interface{}) {}
// NewTask creates a new instance of Task from config.
func NewTask(config Config) *Task {
if config.Logger == nil {
config.Logger = &discardLogger{}
}
if config.UserAgent == "" {
config.UserAgent = fmt.Sprintf("ooniprobe-engine/%s", version.Version)
}
if config.Resolver == nil {
config.Resolver = netx.NewResolver(
netx.Config{Logger: config.Logger})
}
return &Task{
countryLookupper: mmdbLookupper{},
probeIPLookupper: ipLookupClient(config),
probeASNLookupper: mmdbLookupper{},
resolverASNLookupper: mmdbLookupper{},
resolverIPLookupper: resolverLookupClient{},
}
}
// Task performs a geolocation. You must create a new
// instance of Task using the NewTask factory.
type Task struct {
countryLookupper countryLookupper
probeIPLookupper probeIPLookupper
probeASNLookupper asnLookupper
resolverASNLookupper asnLookupper
resolverIPLookupper resolverIPLookupper
}
// Run runs the task.
func (op Task) Run(ctx context.Context) (*Results, error) {
var err error
out := &Results{
ASN: DefaultProbeASN,
CountryCode: DefaultProbeCC,
NetworkName: DefaultProbeNetworkName,
ProbeIP: DefaultProbeIP,
ResolverASN: DefaultResolverASN,
ResolverIP: DefaultResolverIP,
ResolverNetworkName: DefaultResolverNetworkName,
}
ip, err := op.probeIPLookupper.LookupProbeIP(ctx)
if err != nil {
return out, fmt.Errorf("lookupProbeIP failed: %w", err)
}
out.ProbeIP = ip
asn, networkName, err := op.probeASNLookupper.LookupASN(out.ProbeIP)
if err != nil {
return out, fmt.Errorf("lookupASN failed: %w", err)
}
out.ASN = asn
out.NetworkName = networkName
cc, err := op.countryLookupper.LookupCC(out.ProbeIP)
if err != nil {
return out, fmt.Errorf("lookupProbeCC failed: %w", err)
}
out.CountryCode = cc
out.didResolverLookup = true
// Note: ignoring the result of lookupResolverIP and lookupASN
// here is intentional. We don't want this (~minor) failure
// to influence the result of the overall lookup. Another design
// here could be that of retrying the operation N times?
resolverIP, err := op.resolverIPLookupper.LookupResolverIP(ctx)
if err != nil {
return out, nil // intentional
}
out.ResolverIP = resolverIP
resolverASN, resolverNetworkName, err := op.resolverASNLookupper.LookupASN(
out.ResolverIP,
)
if err != nil {
return out, nil // intentional
}
out.ResolverASN = resolverASN
out.ResolverNetworkName = resolverNetworkName
return out, nil
}