From 8ccd8175a586e32bb5abd941a422df42379f52da Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 22 Jul 2025 14:41:19 -0600 Subject: [PATCH 01/12] Added error message --- hit_proxy.bash | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hit_proxy.bash b/hit_proxy.bash index 709ab2fbf..b0032c038 100755 --- a/hit_proxy.bash +++ b/hit_proxy.bash @@ -11,10 +11,13 @@ TMPDIR="${TMP:-/tmp}/hit_lc_proxy/$PROXY" OUTFILE="$TMPDIR/user.conf" rm -rf "$TMPDIR" -mkdir -p "$TMPDIR" +mkdir -pv "$TMPDIR" echo "Generating config for ${PROXY} in ${OUTFILE}..." -CONFIG=$($LANTERN_CLOUD/bin/lc route dump-config $PROXY) +CONFIG=$($LANTERN_CLOUD/bin/lc route dump-config $PROXY) || { + echo "Failed to fetch proxy configuration from lantern-cloud. On Tailnet?" + exit 1 +} # wrap the proxy config to match the format expected by flashlight. # [ConfigResponse (getlantern/flashlight/apipb/types.proto)] OUTPUT="{\"country\": \"US\",\"proxy\":{\"proxies\":[${CONFIG}]}}" From 5fb2b75b49ff59b831237257ff8c6bcabd6d646d Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 25 Jul 2025 18:04:55 -0600 Subject: [PATCH 02/12] Initial pass at a proxyless setting --- assets/images/proxyall.svg | 10 ++++++++ internalsdk/session_model.go | 18 ++++++++++++++ lib/common/ui/image_paths.dart | 1 + lib/features/account/settings.dart | 30 ++++++++++++++++++++--- lib/features/account/split_tunneling.dart | 2 +- lib/features/home/session_model.dart | 17 ++++++++++++- 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 assets/images/proxyall.svg diff --git a/assets/images/proxyall.svg b/assets/images/proxyall.svg new file mode 100644 index 000000000..998103a93 --- /dev/null +++ b/assets/images/proxyall.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/internalsdk/session_model.go b/internalsdk/session_model.go index ebe01c4bd..628979621 100644 --- a/internalsdk/session_model.go +++ b/internalsdk/session_model.go @@ -80,6 +80,7 @@ const ( pathCurrencyCode = "currency_Code" pathReplicaAddr = "replicaAddr" pathSplitTunneling = "/splitTunneling" + pathProxyless = "/proxyless" pathLang = "lang" pathAcceptedTermsVersion = "accepted_terms_version" pathAdsEnabled = "adsEnabled" @@ -589,6 +590,13 @@ func (m *SessionModel) doInvokeMethod(method string, arguments Arguments) (inter return nil, err } return true, nil + case "setProxyless": + proxyless := arguments.Get("on").Bool() + err := m.setProxyless(proxyless) + if err != nil { + return nil, err + } + return true, nil case "denyAppAccess": appName := arguments.Get("packageName").String() @@ -850,6 +858,12 @@ func (session *SessionModel) setSplitTunneling(tunneling bool) error { }) } +func (session *SessionModel) setProxyless(proxyless bool) error { + return pathdb.Mutate(session.db, func(tx pathdb.TX) error { + return pathdb.Put(tx, pathProxyless, proxyless, "") + }) +} + func (session *SessionModel) paymentMethods() error { plans, err := session.proClient.PaymentMethodsV4(context.Background()) if err != nil { @@ -1310,6 +1324,10 @@ func (m *SessionModel) SplitTunnelingEnabled() (bool, error) { return pathdb.Get[bool](m.db, pathSplitTunneling) } +func (m *SessionModel) ProxylessEnabled() (bool, error) { + return pathdb.Get[bool](m.db, pathProxyless) +} + func (m *SessionModel) SetShowInterstitialAds(adsEnable bool) { log.Debugf("SetShowInterstitialAds %v", adsEnable) err := pathdb.Mutate(m.db, func(tx pathdb.TX) error { diff --git a/lib/common/ui/image_paths.dart b/lib/common/ui/image_paths.dart index 582363e1c..e9c926e6e 100644 --- a/lib/common/ui/image_paths.dart +++ b/lib/common/ui/image_paths.dart @@ -25,6 +25,7 @@ class ImagePaths { static const email = 'assets/images/email.svg'; static const translate = 'assets/images/translate.svg'; static const update = 'assets/images/update.svg'; + static const proxyall = 'assets/images/proxyall.svg'; static const mastercard = 'assets/images/mastercard.svg'; static const visa = 'assets/images/visa.svg'; static const unionpay = 'assets/images/unionpay.svg'; diff --git a/lib/features/account/settings.dart b/lib/features/account/settings.dart index eaee7c66e..641a35e5d 100644 --- a/lib/features/account/settings.dart +++ b/lib/features/account/settings.dart @@ -1,4 +1,5 @@ import 'package:intl/intl.dart'; +import 'package:flutter/cupertino.dart'; import 'package:lantern/core/app/app_loading_dialog.dart'; import 'package:lantern/core/localization/localization_constants.dart'; import 'package:lantern/core/utils/common.dart'; @@ -80,14 +81,14 @@ class Settings extends StatelessWidget { ), ), ), - mirrorLTR(context: context, child: const ContinueArrow()) + mirrorLTR(context: context, child: const ContinueArrow()), ], ), ListItemFactory.settingsItem( icon: ImagePaths.update, content: 'check_for_updates'.i18n, trailingArray: [ - mirrorLTR(context: context, child: const ContinueArrow()) + mirrorLTR(context: context, child: const ContinueArrow()), ], onTap: () => checkForUpdateTap(context), ), @@ -103,7 +104,7 @@ class Settings extends StatelessWidget { mirrorLTR( context: context, child: const ContinueArrow(), - ) + ), ], onTap: () => context.pushRoute(BlockedUsers()), ) @@ -144,10 +145,31 @@ class Settings extends StatelessWidget { mirrorLTR( context: context, child: const ContinueArrow(), - ) + ), ], ), ), + sessionModel.proxyless( + (BuildContext context, bool proxylessEnabled, Widget? child) => + ListItemFactory.settingsItem( + icon: ImagePaths.proxyall, + content: 'proxyless'.i18n, + trailingArray: [ + SizedBox( + width: 44.0, + height: 24.0, + child: CupertinoSwitch( + value: proxylessEnabled, + activeTrackColor: CupertinoColors.activeGreen, + onChanged: (bool? value) { + var newValue = value ?? false; + sessionModel.setProxyless(newValue); + }, + ), + ), + ], + ), + ), //* Proxy all if (isDesktop()) sessionModel.proxyAll( diff --git a/lib/features/account/split_tunneling.dart b/lib/features/account/split_tunneling.dart index c26c25b86..7afe99ca7 100644 --- a/lib/features/account/split_tunneling.dart +++ b/lib/features/account/split_tunneling.dart @@ -50,7 +50,7 @@ class _SplitTunnelingState extends State { height: 24.0, child: CupertinoSwitch( value: splitTunnelingEnabled, - activeColor: CupertinoColors.activeGreen, + activeTrackColor: CupertinoColors.activeGreen, onChanged: (bool? value) { var newValue = value ?? false; sessionModel.setSplitTunneling(newValue); diff --git a/lib/features/home/session_model.dart b/lib/features/home/session_model.dart index e7f521b30..f0883cd83 100644 --- a/lib/features/home/session_model.dart +++ b/lib/features/home/session_model.dart @@ -37,6 +37,7 @@ class SessionModel extends Model { late ValueNotifier proxyAvailable; late ValueNotifier country; late ValueNotifier proxyAllNotifier; + late ValueNotifier proxylessNotifier; late ValueNotifier serverInfoNotifier; late ValueNotifier userEmail; late ValueNotifier linkingCodeNotifier; @@ -91,6 +92,7 @@ class SessionModel extends Model { isTestPlayVersion = ValueNotifier(false); } if (Platform.isAndroid) { + proxylessNotifier = ValueNotifier(true); // By default when user starts the app we need to make sure that screenshot is disabled // if user goes to chat then screenshot will be disabled enableScreenShot(); @@ -961,7 +963,7 @@ class SessionModel extends Model { Widget splitTunneling(ValueWidgetBuilder builder) { if (isMobile()) { return subscribedSingleValueBuilder('/splitTunneling', - builder: builder, defaultValue: false); + builder: builder, defaultValue: false,); } return ValueListenableBuilder( valueListenable: configNotifier, @@ -987,6 +989,19 @@ class SessionModel extends Model { ); } + Widget proxyless(ValueWidgetBuilder builder) { + return subscribedSingleValueBuilder('/proxyless', + builder: builder, defaultValue: true,); + } + + Future setProxyless(bool on) async { + unawaited( + methodChannel.invokeMethod('setProxyless', { + 'on': on, + }), + ); + } + Widget appsData({ required ValueWidgetBuilder>> builder, }) { From 418905d446d21f3d51b268f51abb7e227be4a419 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 27 Jul 2025 11:52:40 -0600 Subject: [PATCH 03/12] Wired up proxyless setting with flashlight --- internalsdk/android.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/internalsdk/android.go b/internalsdk/android.go index 00a0f8867..c2fd74cc8 100644 --- a/internalsdk/android.go +++ b/internalsdk/android.go @@ -120,6 +120,7 @@ type Session interface { // used to implement GetInternalHeaders() map[string]string // Should return a JSON encoded map[string]string {"key":"val","key2":"val", ...} SerializedInternalHeaders() (string, error) + ProxylessEnabled() (bool, error) } // PanickingSession wraps the Session interface but panics instead of returning errors @@ -239,6 +240,15 @@ func (s *panickingSessionImpl) SplitTunnelingEnabled() bool { panicIfNecessary(err) return result } + +func (s *panickingSessionImpl) ProxylessEnabled() (bool, error) { + result, err := s.wrapped.ProxylessEnabled() + if err != nil { + panicIfNecessary(err) + } + return result, nil +} + func (s *panickingSessionImpl) ChatEnable() bool { return s.wrapped.ChatEnable() } @@ -582,6 +592,14 @@ func run(configDir, locale string, settings Settings, wrappedSession Session) { flashlight.WithOnSucceedingProxy(func() { session.SetOnSuccess(true) }), + flashlight.WithUseProxyless(func() bool { + proxyless, err := session.ProxylessEnabled() + if err != nil { + log.Errorf("Error checking if proxyless is enabled: %v", err) + return false + } + return proxyless + }), ) if err != nil { log.Fatalf("failed to start flashlight: %v", err) From 235af01a6e6d6374359ea8258c9560df50f06a12 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 27 Jul 2025 12:00:01 -0600 Subject: [PATCH 04/12] Use proxy all setting for proxyless on desktop --- desktop/app/app.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/desktop/app/app.go b/desktop/app/app.go index 29eaa2b37..476c15e38 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -210,6 +210,9 @@ func (app *App) Run(ctx context.Context) error { flashlight.WithOnConfig(app.onConfigUpdate), flashlight.WithOnProxies(app.onProxiesUpdate), flashlight.WithOnSucceedingProxy(app.onSucceedingProxy), + flashlight.WithUseProxyless(func() bool { + return !settings.GetProxyAll() + }), // Use proxyless if ProxyAll is not set ) if err != nil { return err From af7cde84ef257a1afdb2c9a33094eb7da31ad84b Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 27 Jul 2025 12:04:13 -0600 Subject: [PATCH 05/12] Added proxyless string --- assets/locales/en-us.po | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/locales/en-us.po b/assets/locales/en-us.po index c29702a2d..3ef1b8383 100644 --- a/assets/locales/en-us.po +++ b/assets/locales/en-us.po @@ -1860,5 +1860,8 @@ msgstr "Accept and Continue" msgid "decline" msgstr "Decline" +msgid "proxyless" +msgstr "Use Proxyless Dialing" + From bfba4cb440cedd6204018d7793f5effa20c77c1e Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 28 Jul 2025 11:28:13 -0600 Subject: [PATCH 06/12] Fixed overlay text --- assets/locales/en-us.po | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/locales/en-us.po b/assets/locales/en-us.po index 3ef1b8383..6c5803a00 100644 --- a/assets/locales/en-us.po +++ b/assets/locales/en-us.po @@ -284,6 +284,12 @@ msgstr "" "If enabled all traffic will be sent through Lantern (more secure). If " "disabled, only blocked traffic will be sent through Lantern (faster)." +msgid "description_proxyless_dialog" +msgstr "" +"If enabled, Lantern will always try to access sites directly using " +"various tricks to bypass censorship (faster). If disabled, Lantern will " +"never try to use proxyless strategies." + msgid "Account" msgstr "Account" From f09fbf53581dab9af3da51d2c0fa050f6a47e505 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 28 Jul 2025 11:53:27 -0600 Subject: [PATCH 07/12] only show proxyless setting on android --- lib/features/account/settings.dart | 51 ++++++++++++++++++------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/lib/features/account/settings.dart b/lib/features/account/settings.dart index 641a35e5d..e0920ba59 100644 --- a/lib/features/account/settings.dart +++ b/lib/features/account/settings.dart @@ -21,6 +21,15 @@ class Settings extends StatelessWidget { ); } + void openInfoProxyless(BuildContext context) { + CDialog.showInfo( + context, + title: 'proxyless'.i18n, + description: 'description_proxyless_dialog'.i18n, + iconPath: ImagePaths.key, + ); + } + void changeLanguage(BuildContext context) async => await context.pushRoute(Language()); @@ -149,27 +158,29 @@ class Settings extends StatelessWidget { ], ), ), - sessionModel.proxyless( - (BuildContext context, bool proxylessEnabled, Widget? child) => - ListItemFactory.settingsItem( - icon: ImagePaths.proxyall, - content: 'proxyless'.i18n, - trailingArray: [ - SizedBox( - width: 44.0, - height: 24.0, - child: CupertinoSwitch( - value: proxylessEnabled, - activeTrackColor: CupertinoColors.activeGreen, - onChanged: (bool? value) { - var newValue = value ?? false; - sessionModel.setProxyless(newValue); - }, - ), + if (Platform.isAndroid) + sessionModel.proxyless( + (BuildContext context, bool proxylessEnabled, Widget? child) => + ListItemFactory.settingsItem( + icon: ImagePaths.proxyall, + content: 'proxyless'.i18n, + onTap: () => openInfoProxyless(context), + trailingArray: [ + SizedBox( + width: 44.0, + height: 24.0, + child: CupertinoSwitch( + value: proxylessEnabled, + activeTrackColor: CupertinoColors.activeGreen, + onChanged: (bool? value) { + var newValue = value ?? false; + sessionModel.setProxyless(newValue); + }, + ), + ), + ], ), - ], - ), - ), + ), //* Proxy all if (isDesktop()) sessionModel.proxyAll( From ee5572ddd910ca616db42a9fc6a23cc5fcb34f8b Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 28 Jul 2025 12:22:57 -0600 Subject: [PATCH 08/12] Added info icon --- lib/features/account/settings.dart | 26 ++++++++++++++++++++++++-- lib/features/home/session_model.dart | 12 +++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/features/account/settings.dart b/lib/features/account/settings.dart index e0920ba59..687e25eb4 100644 --- a/lib/features/account/settings.dart +++ b/lib/features/account/settings.dart @@ -163,8 +163,30 @@ class Settings extends StatelessWidget { (BuildContext context, bool proxylessEnabled, Widget? child) => ListItemFactory.settingsItem( icon: ImagePaths.proxyall, - content: 'proxyless'.i18n, - onTap: () => openInfoProxyless(context), + content: CInkWell( + onTap: () => openInfoProxyless(context), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: CText( + 'proxyless'.i18n, + softWrap: false, + style: tsSubtitle1.short, + ), + ), + const Padding( + padding: EdgeInsetsDirectional.only(start: 4.0), + child: CAssetImage( + key: ValueKey('proxyless_icon'), + path: ImagePaths.info, + size: 12, + ), + ), + ], + ), + ), trailingArray: [ SizedBox( width: 44.0, diff --git a/lib/features/home/session_model.dart b/lib/features/home/session_model.dart index f0883cd83..c9f4d6275 100644 --- a/lib/features/home/session_model.dart +++ b/lib/features/home/session_model.dart @@ -995,11 +995,13 @@ class SessionModel extends Model { } Future setProxyless(bool on) async { - unawaited( - methodChannel.invokeMethod('setProxyless', { - 'on': on, - }), - ); + if (Platform.isAndroid) { + unawaited( + methodChannel.invokeMethod('setProxyless', { + 'on': on, + }), + ); + } } Widget appsData({ From 58e873a00a81f3381e0a828a6ec0a538926bdc2a Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 28 Jul 2025 12:26:22 -0600 Subject: [PATCH 09/12] Simpler title --- assets/locales/en-us.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/en-us.po b/assets/locales/en-us.po index 6c5803a00..26d118ed4 100644 --- a/assets/locales/en-us.po +++ b/assets/locales/en-us.po @@ -1867,7 +1867,7 @@ msgid "decline" msgstr "Decline" msgid "proxyless" -msgstr "Use Proxyless Dialing" +msgstr "Proxyless Dialing" From 45f4a13b7d45e33154541d341820fceea05b7430 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 28 Jul 2025 12:26:55 -0600 Subject: [PATCH 10/12] only create value builder on android --- lib/features/home/session_model.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/features/home/session_model.dart b/lib/features/home/session_model.dart index c9f4d6275..2d875cf5e 100644 --- a/lib/features/home/session_model.dart +++ b/lib/features/home/session_model.dart @@ -990,8 +990,10 @@ class SessionModel extends Model { } Widget proxyless(ValueWidgetBuilder builder) { - return subscribedSingleValueBuilder('/proxyless', - builder: builder, defaultValue: true,); + if (Platform.isAndroid) { + return subscribedSingleValueBuilder('/proxyless', + builder: builder, defaultValue: true,); + } } Future setProxyless(bool on) async { From 2a75fe9c8e2d667ad3ec6d584d4d7892a96fa78c Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 28 Jul 2025 12:29:36 -0600 Subject: [PATCH 11/12] fix compile issue --- lib/features/home/session_model.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/features/home/session_model.dart b/lib/features/home/session_model.dart index 2d875cf5e..a46dcb776 100644 --- a/lib/features/home/session_model.dart +++ b/lib/features/home/session_model.dart @@ -994,6 +994,10 @@ class SessionModel extends Model { return subscribedSingleValueBuilder('/proxyless', builder: builder, defaultValue: true,); } + return ValueListenableBuilder( + valueListenable: proxylessNotifier, + builder: builder, + ); } Future setProxyless(bool on) async { From f132ac51ce74285ed0a1424ae6f0b05615542cf9 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 31 Jul 2025 10:25:25 -0600 Subject: [PATCH 12/12] update proxyless on ios --- hit_track.bash | 4 +-- internalsdk/android_test.go | 1 + internalsdk/ios/ios.go | 72 +++++++++++++++++++++++++++---------- internalsdk/ios/tcp.go | 4 +-- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/hit_track.bash b/hit_track.bash index 068a57e53..220ce2fe1 100755 --- a/hit_track.bash +++ b/hit_track.bash @@ -8,7 +8,7 @@ echo "Fetching all proxies for "$@"" # First check for all proxies in a temporary directory from a prior run, and use them # if they exist. If not, fetch them from the lantern-cloud. -TMPDIR="${TMP:-/tmp}/hit_lc_proxy" +TMPDIR="${TMP:-/tmp}/hit_track" mkdir -pv "$TMPDIR" OUTFILE="$TMPDIR/"$@"_all_proxies.txt" # If the OUTFILE is older than 1 hour, delete it to force a refresh. @@ -22,7 +22,7 @@ if [ -f "$OUTFILE" ]; then else echo "No cached proxies found. Fetching from lantern-cloud..." $LANTERN_CLOUD/bin/lc routes list -T "$@" > "$OUTFILE" || { - echo "Failed to fetch proxies from lantern-cloud." + echo "Failed to fetch proxies from lantern-cloud. Are you running Tailscale?" exit 1 } ALLPROXIES=$(cat "$OUTFILE") diff --git a/internalsdk/android_test.go b/internalsdk/android_test.go index b636af8ea..22331cec4 100644 --- a/internalsdk/android_test.go +++ b/internalsdk/android_test.go @@ -86,6 +86,7 @@ func (c testSession) SetShowAppOpenAds(enabled bool) {} func (c testSession) SetHasConfigFetched(enabled bool) {} func (c testSession) SetHasProxyFetched(enabled bool) {} func (c testSession) ChatEnable() bool { return false } +func (c testSession) ProxylessEnabled() (bool, error) { return false, nil } func (c testSession) SetOnSuccess(enabled bool) { if !enabled { diff --git a/internalsdk/ios/ios.go b/internalsdk/ios/ios.go index 93a8ad766..1ac51c2e4 100644 --- a/internalsdk/ios/ios.go +++ b/internalsdk/ios/ios.go @@ -154,8 +154,9 @@ type iosClient struct { started time.Time bandwidthTracker BandwidthTracker statsTracker StatsTracker - dialer dialer.Dialer + dialer *protectedDialer tracker stats.Tracker + useProxyless func() bool } func Client(packetsOut Writer, udpDialer UDPDialer, memChecker MemChecker, configDir string, mtu int, @@ -178,8 +179,13 @@ func Client(packetsOut Writer, udpDialer UDPDialer, memChecker MemChecker, confi started: time.Now(), bandwidthTracker: bandwidthTracker, statsTracker: statsTracker, - dialer: dialer.NewProxylessDialer(), - tracker: stats.NewTracker(), + dialer: &protectedDialer{ + // This starts out as a purely proxyless dialer until we have + // proxies to use (either loaded from disk or fetched from the + // server). + dialer: dialer.NewProxylessDialer(), + }, + tracker: stats.NewTracker(), } optimizeMemoryUsage(&c.memoryAvailable) go c.gcPeriodically() @@ -227,7 +233,7 @@ func (c *iosClient) start() (ClientWriter, error) { return nil, errors.New("Unable to start dnsgrab: %v", err) } - c.tcpHandler = newProxiedTCPHandler(c, c.dialer, grabber) + c.tcpHandler = newProxiedTCPHandler(c, c.dialer.Get, grabber) c.udpHandler = newDirectUDPHandler(c, c.udpDialer, grabber, c.capturedDNSHost) ipStack := tun2socks.NewLWIPStack() @@ -257,21 +263,32 @@ func (c *iosClient) reconfigure() { } func (c *iosClient) onDialers(dialers []dialer.ProxyDialer) { - c.dialer.OnOptions(&dialer.Options{ - Dialers: dialers, - OnSuccess: func(pd dialer.ProxyDialer) { - c.tracker.SetHasSucceedingProxy(true) - countryCode, country, city := pd.Location() - previousStats := c.tracker.Latest() - if previousStats.CountryCode == "" || previousStats.CountryCode != countryCode { - c.tracker.SetActiveProxyLocation( - city, - country, - countryCode, - ) - } + newDialer := c.dialer.Get().OnOptions( + &dialer.Options{ + Dialers: dialers, + OnError: func(err error, hasSucceeding bool) { + log.Errorf("Error in dialer: %v", err) + }, + OnSuccess: func(d dialer.ProxyDialer) { + c.tracker.SetHasSucceedingProxy(true) + countryCode, country, city := d.Location() + previousStats := c.tracker.Latest() + if previousStats.CountryCode == "" || previousStats.CountryCode != countryCode { + c.tracker.SetActiveProxyLocation( + city, + country, + countryCode, + ) + } + }, + BanditDir: filepath.Join(c.configDir, "bandit"), + OnNewDialer: func(dialer dialer.Dialer) { + c.dialer.set(dialer) + }, + UseProxyless: c.useProxyless, }, - }) + ) + c.dialer.set(newDialer) } func bandwidthUpdates(bt BandwidthTracker) { @@ -358,3 +375,22 @@ func userConfigFor(userID int, proToken, deviceID string) *UserConfig { ), } } + +// protectedDialer protects a dialer.Dialer with a RWMutex. We can't use an atomic.Value here +// because dialer.Dialer is an interface. +type protectedDialer struct { + sync.RWMutex + dialer dialer.Dialer +} + +func (pd *protectedDialer) Get() dialer.Dialer { + pd.RLock() + defer pd.RUnlock() + return pd.dialer +} + +func (pd *protectedDialer) set(dialer dialer.Dialer) { + pd.Lock() + defer pd.Unlock() + pd.dialer = dialer +} diff --git a/internalsdk/ios/tcp.go b/internalsdk/ios/tcp.go index 8ecb77602..aa0f6dcda 100644 --- a/internalsdk/ios/tcp.go +++ b/internalsdk/ios/tcp.go @@ -38,9 +38,9 @@ type proxiedTCPHandler struct { mx sync.RWMutex } -func newProxiedTCPHandler(c *iosClient, dialer dialer.Dialer, grabber dnsgrab.Server) *proxiedTCPHandler { +func newProxiedTCPHandler(c *iosClient, dialer func() dialer.Dialer, grabber dnsgrab.Server) *proxiedTCPHandler { result := &proxiedTCPHandler{ - dialOut: dialer.DialContext, + dialOut: dialer().DialContext, client: c, grabber: grabber, mtu: c.mtu,