diff --git a/app/src/main/java/org/torproject/android/ConfigConnectionBottomSheet.kt b/app/src/main/java/org/torproject/android/ConfigConnectionBottomSheet.kt index de00e9c9d..68ebe528e 100644 --- a/app/src/main/java/org/torproject/android/ConfigConnectionBottomSheet.kt +++ b/app/src/main/java/org/torproject/android/ConfigConnectionBottomSheet.kt @@ -1,6 +1,7 @@ package org.torproject.android import IPtProxy.IPtProxy +import IPtProxy.OnTransportStopped import android.content.Context import android.os.Bundle import android.telephony.TelephonyManager @@ -16,6 +17,7 @@ import androidx.appcompat.content.res.AppCompatResources import org.torproject.android.circumvention.Bridges import org.torproject.android.circumvention.CircumventionApiManager import org.torproject.android.circumvention.SettingsRequest +import org.torproject.android.service.OrbotConstants import org.torproject.android.service.OrbotService import org.torproject.android.service.util.Prefs import java.io.File @@ -201,8 +203,9 @@ class ConfigConnectionBottomSheet() : fileCacheDir.mkdir() } - IPtProxy.setStateLocation(fileCacheDir.absolutePath) - IPtProxy.startLyrebird("DEBUG", false, false, null) + val proxy = OrbotService.getIptProxyController(context) + proxy.start(IPtProxy.MeekLite,null); + val pUsername = "url=" + OrbotService.getCdnFront("moat-url") + ";front=" + OrbotService.getCdnFront("moat-front") val pPassword = "\u0000" @@ -219,7 +222,7 @@ class ConfigConnectionBottomSheet() : val countryCodeValue: String = getDeviceCountryCode(requireContext()) - CircumventionApiManager().getSettings(SettingsRequest(countryCodeValue), { + CircumventionApiManager(proxy.port(IPtProxy.MeekLite)).getSettings(SettingsRequest(countryCodeValue), { it?.let { circumventionApiBridges = it.settings if (circumventionApiBridges == null) { @@ -237,7 +240,7 @@ class ConfigConnectionBottomSheet() : setPreferenceForSmartConnect() } - IPtProxy.stopLyrebird() + proxy.stop(IPtProxy.MeekLite) } }, { // TODO what happens to the app in this case?! diff --git a/app/src/main/java/org/torproject/android/MoatBottomSheet.kt b/app/src/main/java/org/torproject/android/MoatBottomSheet.kt index 8fcef516d..bb3a2922f 100644 --- a/app/src/main/java/org/torproject/android/MoatBottomSheet.kt +++ b/app/src/main/java/org/torproject/android/MoatBottomSheet.kt @@ -1,7 +1,10 @@ package org.torproject.android +import IPtProxy.Controller import IPtProxy.IPtProxy +import IPtProxy.IPtProxy.Obfs4 import android.app.Activity +import android.content.pm.ApplicationInfo import android.graphics.BitmapFactory import android.os.Bundle import android.os.Handler @@ -24,9 +27,11 @@ import org.json.JSONException import org.json.JSONObject import org.torproject.android.service.OrbotService import org.torproject.android.service.util.Prefs +import org.torproject.android.service.util.TCPSourceApp.getApplicationInfo import org.torproject.android.ui.onboarding.ProxiedHurlStack import java.io.File + class MoatBottomSheet(private val callbacks: ConnectionHelperCallbacks) : OrbotBottomSheetDialogFragment(), View.OnClickListener { override fun onCreateView( @@ -65,19 +70,21 @@ class MoatBottomSheet(private val callbacks: ConnectionHelperCallbacks) : private lateinit var mQueue: RequestQueue private lateinit var mBtnAction: Button + private lateinit var mController : Controller + private fun setupMoat() { val fileCacheDir = File(requireActivity().cacheDir, "pt") if (!fileCacheDir.exists()) { fileCacheDir.mkdir() } - IPtProxy.setStateLocation(fileCacheDir.absolutePath) - - IPtProxy.startLyrebird("DEBUG", false, false, null) + val isDebuggable = (0 != (context?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_DEBUGGABLE))) + mController = OrbotService.getIptProxyController(context) + mController.start(IPtProxy.Obfs4,"") val phs = ProxiedHurlStack( "127.0.0.1", - IPtProxy.meekPort().toInt(), + mController.port(Obfs4).toInt(), "url=" + OrbotService.getCdnFront("moat-url") + ";front=" + OrbotService.getCdnFront("moat-front"), "\u0000" ) diff --git a/app/src/main/java/org/torproject/android/circumvention/CircumventionApi.kt b/app/src/main/java/org/torproject/android/circumvention/CircumventionApi.kt index 0111c8c7b..54a05ed1e 100644 --- a/app/src/main/java/org/torproject/android/circumvention/CircumventionApi.kt +++ b/app/src/main/java/org/torproject/android/circumvention/CircumventionApi.kt @@ -38,26 +38,30 @@ interface CircumventionEndpoints { object ServiceBuilder { - var proxy: Proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", IPtProxy.meekPort().toInt())) + fun buildService(service: Class, proxyPort: Long): T { - private val client = OkHttpClient.Builder().proxy(proxy).build() + var proxy: Proxy = + Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", proxyPort.toInt())) - private val retrofit = Retrofit.Builder() - .baseUrl("https://bridges.torproject.org/moat/circumvention/") - .addConverterFactory(GsonConverterFactory.create()) - .client(client) - .build() + val client = OkHttpClient.Builder().proxy(proxy).build() - fun buildService(service: Class): T = retrofit.create(service) + val retrofit = Retrofit.Builder() + .baseUrl("https://bridges.torproject.org/moat/circumvention/") + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build() + + return retrofit.create(service) + } } -class CircumventionApiManager { +class CircumventionApiManager (port: Long) { companion object { const val BRIDGE_TYPE_OBFS4 = "obfs4" const val BRIDGE_TYPE_SNOWFLAKE = "snowflake" } - private val retrofit = ServiceBuilder.buildService(CircumventionEndpoints::class.java) + private val retrofit = ServiceBuilder.buildService(CircumventionEndpoints::class.java, port) fun getCountries(onResult: (List?) -> Unit, onError: ((Throwable) -> Unit)? = null) { retrofit.getCountries().enqueue(object: Callback> { diff --git a/orbotservice/src/main/java/org/torproject/android/service/OrbotService.java b/orbotservice/src/main/java/org/torproject/android/service/OrbotService.java index a68716b6b..52fd87415 100644 --- a/orbotservice/src/main/java/org/torproject/android/service/OrbotService.java +++ b/orbotservice/src/main/java/org/torproject/android/service/OrbotService.java @@ -69,7 +69,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import IPtProxy.Controller; import IPtProxy.IPtProxy; +import IPtProxy.SnowflakeProxy; +import IPtProxy.SnowflakeClientConnected; +import IPtProxy.OnTransportStopped; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; @@ -77,6 +81,7 @@ import androidx.core.content.ContextCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; + @SuppressWarnings("StringConcatenationInsideStringBufferAppend") public class OrbotService extends VpnService implements OrbotConstants { @@ -116,6 +121,22 @@ public class OrbotService extends VpnService implements OrbotConstants { private boolean mHasPower = false; private boolean mHasWifi = false; + private static Controller mIptProxy = null; + private SnowflakeProxy mSnowflakeProxy = null; + + public static synchronized Controller getIptProxyController (Context context) { + + if (mIptProxy == null) { + mIptProxy = IPtProxy.newController(context.getCacheDir().getPath(), true, false, "DEBUG", new OnTransportStopped() { + @Override + public void stopped(String s, Exception e) { + Log.e(TAG, "IPtProxy Error", e); + } + }); + } + + return mIptProxy; + } /** * @param bridgeList bridges that were manually entered into Orbot settings * @return Array with each bridge as an element, no whitespace entries see issue #289... @@ -278,9 +299,12 @@ private void stopTorAsync(boolean showNotification) { // todo this needs to handle a lot of different cases that haven't been defined yet // todo particularly this is true for the smart connection case... if (connectionPathway.startsWith(Prefs.PATHWAY_SNOWFLAKE) || Prefs.getPrefSmartTrySnowflake()) { - IPtProxy.stopSnowflake(); + // mIptProxy.stop + mIptProxy.stop(IPtProxy.Snowflake); } else if (connectionPathway.equals(Prefs.PATHWAY_CUSTOM) || Prefs.getPrefSmartTryObfs4() != null) { - IPtProxy.stopLyrebird(); + // IPtProxy.stopLyrebird(); + mIptProxy.stop(IPtProxy.Obfs4); + } stopTor(); @@ -368,7 +392,19 @@ private void startSnowflakeClientDomainFronting() { // @param maxPeers Capacity for number of multiplexed WebRTC peers. DEFAULTs to 1 if less than that. // @return Port number where Snowflake will listen on, if no error happens during start up. */ - IPtProxy.startSnowflake(stunServer, target, front, null, null, null, null,true, false, false, 1); + // IPtProxy.startSnowflake(stunServer, target, front, null, null, null, null,true, false, false, 1); + + + try { + mIptProxy.setSnowflakeBrokerUrl(target); + mIptProxy.setSnowflakeFrontDomains(front); + mIptProxy.setSnowflakeIceServers(stunServer); + mIptProxy.setSnowflakeMaxPeers(1); + mIptProxy.start(IPtProxy.Snowflake,""); + + } catch (Exception e) { + throw new RuntimeException(e); + } } @@ -377,13 +413,25 @@ private void startSnowflakeClientAmpRendezvous() { var target = getCdnFront("snowflake-target-direct"); var front = getCdnFront("snowflake-amp-front"); var ampCache = getCdnFront("snowflake-amp-cache"); - IPtProxy.startSnowflake(stunServers, target, front, ampCache, null, null, null, true, false, false, 1); + //IPtProxy.startSnowflake(stunServers, target, front, ampCache, null, null, null, true, false, false, 1); + + + try { + mIptProxy.setSnowflakeBrokerUrl(target); + mIptProxy.setSnowflakeFrontDomains(front); + mIptProxy.setSnowflakeIceServers(stunServers); + mIptProxy.setSnowflakeAmpCacheUrl(ampCache); + mIptProxy.setSnowflakeMaxPeers(1); + mIptProxy.start(IPtProxy.Snowflake,""); + + } catch (Exception e) { + throw new RuntimeException(e); + } } private final SecureRandom mSecureRandGen = new SecureRandom(); //used to randomly select STUN servers for snowflake public synchronized void enableSnowflakeProxy() { // This is to host a snowflake entrance node / bridge - if (!IPtProxy.isSnowflakeProxyRunning()) { if (Prefs.limitSnowflakeProxyingWifi() && (!mHasWifi)) return; @@ -391,22 +439,37 @@ public synchronized void enableSnowflakeProxy() { // This is to host a snowflake if (Prefs.limitSnowflakeProxyingCharging() && (!mHasPower)) return; - var capacity = 1; - var keepLocalAddresses = false; - var unsafeLogging = false; - var stunServers = getCdnFront("snowflake-stun").split(","); - - int randomIndex = mSecureRandGen.nextInt(stunServers.length); - var stunUrl = stunServers[randomIndex]; - var relayUrl = getCdnFront("snowflake-relay-url"); - var natProbeUrl = getCdnFront("snowflake-nat-probe"); - var brokerUrl = getCdnFront("snowflake-target-direct"); - IPtProxy.startSnowflakeProxy(capacity, brokerUrl, relayUrl, stunUrl, natProbeUrl, null, keepLocalAddresses, unsafeLogging, () -> { - Prefs.addSnowflakeServed(); - if (!Prefs.showSnowflakeProxyMessage()) return; - var message = String.format(getString(R.string.snowflake_proxy_client_connected_msg), ONION_EMOJI, ONION_EMOJI); - new Handler(getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show()); - }); + if (mSnowflakeProxy == null) { + var capacity = 1; + var stunServers = getCdnFront("snowflake-stun").split(","); + + int randomIndex = mSecureRandGen.nextInt(stunServers.length); + var stunUrl = stunServers[randomIndex]; + var relayUrl = getCdnFront("snowflake-relay-url"); + var natProbeUrl = getCdnFront("snowflake-nat-probe"); + var brokerUrl = getCdnFront("snowflake-target-direct"); + + mSnowflakeProxy = new SnowflakeProxy(); + mSnowflakeProxy.setBrokerUrl(brokerUrl); + mSnowflakeProxy.setCapacity(capacity); + mSnowflakeProxy.setRelayUrl(relayUrl); + mSnowflakeProxy.setStunServer(stunUrl); + mSnowflakeProxy.setNatProbeUrl(natProbeUrl); + + + mSnowflakeProxy.setClientConnected( new SnowflakeClientConnected (){ + + @Override + public void connected() { + snowflakeProxyClientConnected(); + } + + }); + mSnowflakeProxy.start(); + + + } + logNotice(getString(R.string.log_notice_snowflake_proxy_enabled)); if (Prefs.showSnowflakeProxyMessage()) { @@ -414,10 +477,16 @@ public synchronized void enableSnowflakeProxy() { // This is to host a snowflake new Handler(getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show()); } - } } + private void snowflakeProxyClientConnected () { + Prefs.addSnowflakeServed(); + if (!Prefs.showSnowflakeProxyMessage()) return; + var message = String.format(getString(R.string.snowflake_proxy_client_connected_msg), ONION_EMOJI, ONION_EMOJI); + new Handler(getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show()); + } + private void enableSnowflakeProxyNetworkListener () { if (Prefs.limitSnowflakeProxyingWifi()) { //check if on wifi @@ -475,16 +544,19 @@ private void checkNetworkForSnowflakeProxy () { } public synchronized void disableSnowflakeProxy() { - if (IPtProxy.isSnowflakeProxyRunning()) { - IPtProxy.stopSnowflakeProxy(); + + if (mSnowflakeProxy != null) { + mSnowflakeProxy.stop(); logNotice(getString(R.string.log_notice_snowflake_proxy_disabled)); if (Prefs.showSnowflakeProxyMessage()) { var message = getString(R.string.log_notice_snowflake_proxy_disabled); new Handler(getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show()); } - + mSnowflakeProxy = null; } + + } // if someone stops during startup, we may have to wait for the conn port to be setup, so we can properly shutdown tor @@ -520,6 +592,9 @@ protected void logNotice(String msg) { public void onCreate() { super.onCreate(); configLanguage(); + + getIptProxyController(this); + try { //set proper content URIs for current build flavor V3_ONION_SERVICES_CONTENT_URI = Uri.parse("content://" + getApplicationContext().getPackageName() + ".ui.v3onionservice/v3"); @@ -574,9 +649,6 @@ public void onCreate() { } } - - pluggableTransportInstall(); - mVpnManager = new OrbotVpnManager(this); loadSnowflakeBridges(this); loadCdnFronts(this); @@ -615,21 +687,6 @@ protected String getCurrentStatus() { return mCurrentStatus; } - private void pluggableTransportInstall() { - var fileCacheDir = new File(getCacheDir(), "pt"); - if (!fileCacheDir.exists()) - //noinspection ResultOfMethodCallIgnored - fileCacheDir.mkdir(); - - try { - IPtProxy.setStateLocation(fileCacheDir.getAbsolutePath()); - debug("IPtProxy state: " + IPtProxy.getStateLocation()); - } catch (Error e) { - debug("IPtProxy state: not installed; " + e.getLocalizedMessage()); - - } - } - private File updateTorrcCustomFile() throws IOException { var prefs = Prefs.getSharedPrefs(getApplicationContext()); var extraLines = new StringBuffer(); @@ -1208,7 +1265,7 @@ private StringBuffer processSettingsImpl(StringBuffer extraLines) throws IOExcep private StringBuffer processSettingsImplSnowflake(StringBuffer extraLines) { Log.d(TAG, "in snowflake torrc config"); - extraLines.append("ClientTransportPlugin snowflake socks5 127.0.0.1:" + IPtProxy.snowflakePort()).append('\n'); + extraLines.append("ClientTransportPlugin snowflake socks5 127.0.0.1:" + mIptProxy.port(IPtProxy.Snowflake)).append('\n'); for (String bridge : mSnowflakeBridges) { extraLines.append("Bridge ").append(bridge).append("\n"); } @@ -1217,7 +1274,8 @@ private StringBuffer processSettingsImplSnowflake(StringBuffer extraLines) { private StringBuffer processSettingsImplObfs4(StringBuffer extraLines) { Log.d(TAG, "in obfs4 torrc config"); - extraLines.append("ClientTransportPlugin obfs4 socks5 127.0.0.1:" + IPtProxy.obfs4Port()).append('\n'); + extraLines.append("ClientTransportPlugin obfs4 socks5 127.0.0.1:" + mIptProxy.port(IPtProxy.Obfs4)).append('\n'); + var bridgeList = ""; if (Prefs.getConnectionPathway().equals(Prefs.PATHWAY_CUSTOM)) { bridgeList = Prefs.getBridgesList(); @@ -1468,7 +1526,12 @@ public void run() { } else if (connectionPathway.equals(Prefs.PATHWAY_SNOWFLAKE_AMP)) { startSnowflakeClientAmpRendezvous(); } else if (connectionPathway.equals(Prefs.PATHWAY_CUSTOM) || Prefs.getPrefSmartTryObfs4() != null) { - IPtProxy.startLyrebird("DEBUG", false, false, null); + //IPtProxy.startLyrebird("DEBUG", false, false, null); + try { + mIptProxy.start(IPtProxy.Obfs4,""); + } catch (Exception e) { + throw new RuntimeException(e); + } } startTor(); replyWithStatus(mIntent);