Skip to content

Commit

Permalink
use previous geolookup for ios (#1267)
Browse files Browse the repository at this point in the history
* use previous geolookup for ios

* Call geolookup Refresh before getting country.

---------

Co-authored-by: Jigar-f <jigar@getlantern.org>
  • Loading branch information
garmr-ulfr and jigar-f authored Dec 17, 2024
1 parent 494b9df commit fc6a436
Show file tree
Hide file tree
Showing 5 changed files with 491 additions and 3 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ require (
github.com/coder/websocket v1.8.12 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/getlantern/fronted v0.0.0-20241212194832-a55b6db2616e // indirect
github.com/getlantern/geolookup v0.0.0-20230327091034-aebe73c6eef4 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ github.com/getlantern/fronted v0.0.0-20241212194832-a55b6db2616e h1:qk62Xhg+ha1s
github.com/getlantern/fronted v0.0.0-20241212194832-a55b6db2616e/go.mod h1:UOynqDcVIlDMFk3sdUyHzNyY1cz4GHtJ+8qvWESHWhg=
github.com/getlantern/geo v0.0.0-20241129152027-2fc88c10f91e h1:vpikNz6IzvEoqVYmiK5Uq+lE4TCzvMDqbZdxFbtGK1g=
github.com/getlantern/geo v0.0.0-20241129152027-2fc88c10f91e/go.mod h1:RjQ0krF8NTCc5xo2Q1995/vZBnYg33h8svn15do7dLg=
github.com/getlantern/geolookup v0.0.0-20230327091034-aebe73c6eef4 h1:Ju9l1RretVWJTNo2vpl/xAW8Dcuiyg5kJC6LRBpCigw=
github.com/getlantern/geolookup v0.0.0-20230327091034-aebe73c6eef4/go.mod h1:4UNvIsawdB8WclVxqYv46Oe1zzWJ8wMhUO+q6tUzATo=
github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5 h1:RBKofGGMt2k6eGBwX8mky9qunjL+KnAp9JdzXjiRkRw=
github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5/go.mod h1:kGHRXch95rnGLHjER/GhhFiHvfnqNz7KqWD9kGfATHY=
github.com/getlantern/go-tun2socks v1.16.12-0.20201218023150-b68f09e5ae93 h1:CFLw2b6vgOmpxsRWRiTd46tiR6YKg2crIuTu4cINYcY=
Expand Down
6 changes: 3 additions & 3 deletions internalsdk/ios/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import (
"github.com/getlantern/flashlight/v7/config"
"github.com/getlantern/flashlight/v7/email"
"github.com/getlantern/flashlight/v7/embeddedconfig"
"github.com/getlantern/flashlight/v7/geolookup"
"github.com/getlantern/flashlight/v7/proxied"

"github.com/getlantern/lantern-client/internalsdk/ios/geolookup"

"context"

"github.com/getlantern/lantern-client/internalsdk/common"
Expand Down Expand Up @@ -140,9 +141,8 @@ func (cf *configurer) Configure(userID int, proToken string, refreshProxies bool
if frontingErr := setupFronting(); frontingErr != nil {
log.Errorf("Unable to configure fronting, sticking with embedded configuration: %v", err)
} else {
log.Debug("Refreshing geolookup")

go func() {
go geolookup.Refresh()
cf.uc.Country = geolookup.GetCountry(1 * time.Minute)
log.Debugf("Successful geolookup: country %s", cf.uc.Country)
cf.uc.AllowProbes = global.FeatureEnabled(
Expand Down
262 changes: 262 additions & 0 deletions internalsdk/ios/geolookup/geolookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package geolookup

import (
"context"
"encoding/json"
"fmt"
"math"
"net/http"
"os"
"sync"
"time"

"github.com/getlantern/eventual/v2"
geo "github.com/getlantern/geolookup"
"github.com/getlantern/golog"

"github.com/getlantern/flashlight/v7/ops"
"github.com/getlantern/flashlight/v7/proxied"
)

var (
log = golog.LoggerFor("ios.geolookup")

refreshRequest = make(chan interface{}, 1)
currentGeoInfo = eventual.NewValue()
watchers []chan bool
persistToFile string
mx sync.Mutex
roundTripper http.RoundTripper
)

const (
maxTimeout = 10 * time.Minute
retryWaitMillis = 100
maxRetryWait = 30 * time.Second
)

type GeoInfo struct {
IP string
City *geo.City
FromDisk bool
}

func init() {
SetDefaultRoundTripper()
}

// GetIP gets the IP. If the IP hasn't been determined yet, waits up to the
// given timeout for an IP to become available.
func GetIP(timeout time.Duration) string {
gi, err := GetGeoInfo(timeout)
if err != nil {
log.Debugf("Could not get IP: %v", err)
return ""
}
return gi.IP
}

// GetCountry gets the country. If the country hasn't been determined yet, waits
// up to the given timeout for a country to become available.
func GetCountry(timeout time.Duration) string {
gi, err := GetGeoInfo(timeout)
if err != nil {
log.Debugf("Could not get country: %v", err)
return ""
}
return gi.City.Country.IsoCode
}

func GetGeoInfo(timeout time.Duration) (*GeoInfo, error) {
// We need to specially handle negative timeouts because some callers may use
// eventual.Forever (aka -1), expecting it to block forever.
if timeout < 0 {
timeout = maxTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

gi, err := currentGeoInfo.Get(ctx)
if err != nil {
return nil, fmt.Errorf(
"could not get geoinfo with timeout %v: %w",
timeout,
err,
)
}
if gi == nil {
return nil, fmt.Errorf("no geo info after %v", timeout)
}
return gi.(*GeoInfo), nil
}

// EnablePersistence enables persistence of the current geo info to disk at the named file and
// initializes current geo info from that file if necessary.
func EnablePersistence(geoFile string) {
mx.Lock()
defer mx.Unlock()

// use this file going forward
persistToFile = geoFile

log.Debugf("Will persist geolocation info to %v", persistToFile)

// initialize from file if necessary
knownCountry := GetCountry(0)
if knownCountry == "" {
file, err := os.Open(persistToFile)
if err == nil {
log.Debugf("Initializing geolocation info from %v", persistToFile)
dec := json.NewDecoder(file)
gi := &GeoInfo{
FromDisk: true,
}
decodeErr := dec.Decode(gi)
if decodeErr != nil {
log.Errorf(
"Error initializing geolocation info from %v: %v",
persistToFile,
decodeErr,
)
return
}
setGeoInfo(gi, false)
}
}
}

// Refresh refreshes the geolookup information by calling the remote geolookup
// service. It will keep calling the service until it's able to determine an IP
// and country.
func Refresh() {
select {
case refreshRequest <- true:
log.Debug("Requested refresh")
default:
log.Debug("Refresh already in progress")
}
}

// OnRefresh creates a channel that caller can receive on when new geolocation
// information is got.
func OnRefresh() <-chan bool {
ch := make(chan bool, 1)
mx.Lock()
watchers = append(watchers, ch)
mx.Unlock()
return ch
}

func init() {
go run()
}

func run() {
for range refreshRequest {
log.Debug("Refreshing geolocation info")
geoInfo := lookup()

// Check if the IP has changed and if the old IP is simply cached from
// disk. If it is cached, we should still notify anyone looking for
// a new IP because they won't have been notified of the IP on disk,
// as that is loaded very soon on startup.
if !isNew(geoInfo) {
log.Debug("public IP from network did not change - not notifying watchers")
continue
}
log.Debug("Setting new geolocation info")
mx.Lock()
setGeoInfo(geoInfo, true)
mx.Unlock()
}
}

func isNew(newGeoInfo *GeoInfo) bool {
if newGeoInfo == nil {
return false
}
oldGeoInfo, err := GetGeoInfo(0)
if err != nil {
return true
}
if oldGeoInfo == nil {
return true
}
return oldGeoInfo.IP != newGeoInfo.IP ||
oldGeoInfo.FromDisk != newGeoInfo.FromDisk
}

func setGeoInfo(gi *GeoInfo, persist bool) {
currentGeoInfo.Set(gi)
w := watchers
for _, ch := range w {
select {
case ch <- true:
default:
}
}
if persist && persistToFile != "" {
b, err := json.Marshal(gi)
if err != nil {
log.Errorf(
"Unable to marshal geolocation info to JSON for persisting: %v",
err,
)
return
}
writeErr := os.WriteFile(persistToFile, b, 0644)
if writeErr != nil {
log.Errorf(
"Error persisting geolocation info to %v: %v",
persistToFile,
err,
)
}
}
}

func lookup() *GeoInfo {
consecutiveFailures := 0

for {
gi, err := doLookup()
if err == nil {
return gi
}
log.Debugf("Unable to get current location: %s", err)
wait := time.Duration(
math.Pow(
2,
float64(consecutiveFailures),
)*float64(
retryWaitMillis,
),
) * time.Millisecond
if wait > maxRetryWait {
wait = maxRetryWait
}
log.Debugf("Waiting %v before retrying", wait)
time.Sleep(wait)
consecutiveFailures++
}
}

func doLookup() (*GeoInfo, error) {
op := ops.Begin("geolookup")
defer op.End()
city, ip, err := geo.LookupIP("", roundTripper)

if err != nil {
log.Errorf("Could not lookup IP %v", err)
return nil, op.FailIf(err)
}
return &GeoInfo{
IP: ip,
City: city,
FromDisk: false},
nil
}

func SetDefaultRoundTripper() {
roundTripper = proxied.ParallelPreferChained()
}
Loading

0 comments on commit fc6a436

Please sign in to comment.