ooni-probe-cli/internal/engine/geolocate/geolocate.go
Simone Basso 57a3919d2a
fix(geolocate): always use netxlite functionality (#976)
This change ensures that, in turn, we're able to "remote" all the traffic generated by the `geolocate` package, rather than missing some bits of it that were still using the standard library and caused _some_ geolocations to geolocate as the local host rather than as the remote host.

Extracted from https://github.com/ooni/probe-cli/pull/969, where we tested this functionality.

Closes https://github.com/ooni/probe/issues/1383 (which was long overdue).

Part of https://github.com/ooni/probe/issues/2340, because it allows us to make progress with that.
2022-10-12 18:07:42 +02:00

157 lines
4.3 KiB
Go

// Package geolocate implements IP lookup, resolver lookup, and geolocation.
package geolocate
import (
"context"
"fmt"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/version"
)
// 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)
}
// 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 model.Resolver
// Logger is the logger to use. If not set, then we will
// use a logger that discards all messages.
Logger model.Logger
// UserAgent is the user agent to use. If not set, then
// we will use a default user agent.
UserAgent string
}
// NewTask creates a new instance of Task from config.
func NewTask(config Config) *Task {
if config.Logger == nil {
config.Logger = model.DiscardLogger
}
if config.UserAgent == "" {
config.UserAgent = fmt.Sprintf("ooniprobe-engine/%s", version.Version)
}
if config.Resolver == nil {
config.Resolver = netxlite.NewStdlibResolver(config.Logger)
}
return &Task{
countryLookupper: mmdbLookupper{},
probeIPLookupper: ipLookupClient(config),
probeASNLookupper: mmdbLookupper{},
resolverASNLookupper: mmdbLookupper{},
resolverIPLookupper: resolverLookupClient{
Resolver: config.Resolver,
},
}
}
// 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: model.DefaultProbeASN,
CountryCode: model.DefaultProbeCC,
NetworkName: model.DefaultProbeNetworkName,
ProbeIP: model.DefaultProbeIP,
ResolverASN: model.DefaultResolverASN,
ResolverIP: model.DefaultResolverIP,
ResolverNetworkName: model.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
}