Skip to content
Draft
10 changes: 10 additions & 0 deletions assets/images/proxyall.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions assets/locales/en-us.po
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -1860,5 +1866,8 @@ msgstr "Accept and Continue"
msgid "decline"
msgstr "Decline"

msgid "proxyless"
msgstr "Proxyless Dialing"



3 changes: 3 additions & 0 deletions desktop/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
flashlight.WithOnConfig(app.onConfigUpdate),
flashlight.WithOnProxies(app.onProxiesUpdate),
flashlight.WithOnSucceedingProxy(app.onSucceedingProxy),
flashlight.WithUseProxyless(func() bool {

Check failure on line 213 in desktop/app/app.go

View workflow job for this annotation

GitHub Actions / build

undefined: flashlight.WithUseProxyless
return !settings.GetProxyAll()
}), // Use proxyless if ProxyAll is not set
)
if err != nil {
return err
Expand Down
7 changes: 5 additions & 2 deletions hit_proxy.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}]}}"
Expand Down
4 changes: 2 additions & 2 deletions hit_track.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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")
Expand Down
18 changes: 18 additions & 0 deletions internalsdk/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions internalsdk/android_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
72 changes: 54 additions & 18 deletions internalsdk/ios/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions internalsdk/ios/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions internalsdk/session_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const (
pathCurrencyCode = "currency_Code"
pathReplicaAddr = "replicaAddr"
pathSplitTunneling = "/splitTunneling"
pathProxyless = "/proxyless"
pathLang = "lang"
pathAcceptedTermsVersion = "accepted_terms_version"
pathAdsEnabled = "adsEnabled"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions lib/common/ui/image_paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
63 changes: 59 additions & 4 deletions lib/features/account/settings.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -20,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());

Expand Down Expand Up @@ -80,14 +90,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),
),
Expand All @@ -103,7 +113,7 @@ class Settings extends StatelessWidget {
mirrorLTR(
context: context,
child: const ContinueArrow(),
)
),
],
onTap: () => context.pushRoute(BlockedUsers()),
)
Expand Down Expand Up @@ -144,7 +154,52 @@ class Settings extends StatelessWidget {
mirrorLTR(
context: context,
child: const ContinueArrow(),
)
),
],
),
),
if (Platform.isAndroid)
sessionModel.proxyless(
(BuildContext context, bool proxylessEnabled, Widget? child) =>
ListItemFactory.settingsItem(
icon: ImagePaths.proxyall,
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,
height: 24.0,
child: CupertinoSwitch(
value: proxylessEnabled,
activeTrackColor: CupertinoColors.activeGreen,
onChanged: (bool? value) {
var newValue = value ?? false;
sessionModel.setProxyless(newValue);
},
),
),
],
),
),
Expand Down
Loading
Loading