From cd2e002c9e0f10079d8c1ec7af1d4be54a9de9e0 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 17 Jun 2024 02:52:06 +0200 Subject: update obfsvpn to version 1.0.0, this is a breaking API change. Obfsvpn requires openvpn in UDP as inner transport protocol from now on --- .../base/fragments/SettingsFragment.java | 5 +- .../leap/bitmaskclient/base/models/Provider.java | 6 +- .../base/utils/BuildConfigHelper.java | 9 -- .../bitmaskclient/base/utils/PreferenceHelper.java | 3 +- .../leap/bitmaskclient/eip/EipSetupObserver.java | 16 --- .../leap/bitmaskclient/eip/VpnConfigGenerator.java | 36 ++---- .../pluggableTransports/HoppingConfig.java | 57 -------- .../pluggableTransports/HoppingObfsVpnClient.java | 66 ---------- .../pluggableTransports/Obfs4Options.java | 16 --- .../pluggableTransports/ObfsVpnClient.java | 144 --------------------- .../pluggableTransports/ObfsvpnClient.java | 87 +++++++++++++ .../pluggableTransports/PtClientBuilder.java | 18 --- .../pluggableTransports/PtClientInterface.java | 9 -- .../pluggableTransports/ShapeshifterClient.java | 143 -------------------- .../pluggableTransports/models/HoppingConfig.java | 69 ++++++++++ .../pluggableTransports/models/KcpConfig.java | 39 ++++++ .../pluggableTransports/models/Obfs4Options.java | 16 +++ .../pluggableTransports/models/ObfsvpnConfig.java | 35 +++++ 18 files changed, 259 insertions(+), 515 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsvpnClient.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/HoppingConfig.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/KcpConfig.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/Obfs4Options.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/ObfsvpnConfig.java (limited to 'app/src/main/java/se/leap/bitmaskclient') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java index d7b62de2..33c8f388 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java @@ -8,7 +8,6 @@ import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP; import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES; import static se.leap.bitmaskclient.base.models.Constants.USE_IPv6_FIREWALL; import static se.leap.bitmaskclient.base.models.Constants.USE_OBFUSCATION_PINNING; -import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.useObfsVpn; import static se.leap.bitmaskclient.base.utils.ConfigHelper.isCalyxOSWithTetheringSupport; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getExcludedApps; @@ -261,7 +260,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh public void initObfuscationPinningEntry(View rootView) { IconSwitchEntry obfuscationPinning = rootView.findViewById(R.id.obfuscation_proxy_pinning); - if (!BuildConfig.BUILD_TYPE.equals("debug") || !useObfsVpn()) { + if (!BuildConfig.BUILD_TYPE.equals("debug")) { obfuscationPinning.setVisibility(GONE); return; } @@ -302,7 +301,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh public void initExperimentalTransportsEntry(View rootView) { IconSwitchEntry experimentalTransports = rootView.findViewById(R.id.experimental_transports); - if (useObfsVpn() && ProviderObservable.getInstance().getCurrentProvider().supportsExperimentalPluggableTransports()) { + if (ProviderObservable.getInstance().getCurrentProvider().supportsExperimentalPluggableTransports()) { experimentalTransports.setVisibility(VISIBLE); experimentalTransports.setChecked(allowExperimentalTransports()); experimentalTransports.setOnCheckedChangeListener((buttonView, isChecked) -> { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java index cb9bd520..64e57cda 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java @@ -28,7 +28,6 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGIS import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.TYPE; -import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.useObfsVpn; import static se.leap.bitmaskclient.base.utils.RSAHelper.parseRsaKeyFromString; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; @@ -190,10 +189,7 @@ public final class Provider implements Parcelable { } public boolean supportsPluggableTransports() { - if (useObfsVpn()) { - return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP), new Pair<>(OBFS4, KCP), new Pair<>(OBFS4_HOP, TCP), new Pair<>(OBFS4_HOP, KCP)}); - } - return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP)}); + return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP), new Pair<>(OBFS4, KCP), new Pair<>(OBFS4_HOP, TCP), new Pair<>(OBFS4_HOP, KCP)}); } public boolean supportsExperimentalPluggableTransports() { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java index e1f65b5e..22af1bfb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java @@ -11,7 +11,6 @@ import se.leap.bitmaskclient.BuildConfig; public class BuildConfigHelper { public interface BuildConfigHelperInterface { - boolean useObfsVpn(); boolean hasObfuscationPinningDefaults(); String obfsvpnIP(); String obfsvpnPort(); @@ -21,10 +20,6 @@ public class BuildConfigHelper { } public static class DefaultBuildConfigHelper implements BuildConfigHelperInterface { - @Override - public boolean useObfsVpn() { - return BuildConfig.use_obfsvpn; - } @Override public boolean hasObfuscationPinningDefaults() { @@ -72,10 +67,6 @@ public class BuildConfigHelper { instance = helperInterface; } - public static boolean useObfsVpn() { - return instance.useObfsVpn(); - } - public static boolean hasObfuscationPinningDefaults() { return instance.hasObfuscationPinningDefaults(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index eee3bfb2..8d1f21e5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -513,8 +513,7 @@ public class PreferenceHelper { } public static boolean useObfuscationPinning() { - return BuildConfigHelper.useObfsVpn() && - getUseBridges() && + return getUseBridges() && getBoolean(USE_OBFUSCATION_PINNING, false) && !TextUtils.isEmpty(getObfuscationPinningIP()) && !TextUtils.isEmpty(getObfuscationPinningCert()) && diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java index adb0fdc7..bd626ce5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -450,22 +450,6 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta if (BuildConfig.DEBUG) { Log.e("ERROR", logItem.getString(appContext)); } - switch (logItem.getErrorType()) { - case SHAPESHIFTER: - VpnProfile profile = VpnStatus.getLastConnectedVpnProfile(); - if (profile == null) { - EipCommand.startVPN(appContext, false, 0); - } else { - GatewaysManager gatewaysManager = new GatewaysManager(appContext); - int position = gatewaysManager.getPosition(profile); - setupNClosestGateway.set(position >= 0 ? position : 0); - selectNextGateway(); - } - break; - default: - break; - - } } } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 4c8fa797..a7a6bee8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -30,9 +30,6 @@ import static se.leap.bitmaskclient.base.models.Constants.REMOTE; import static se.leap.bitmaskclient.base.models.Constants.TCP; import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.UDP; -import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.useObfsVpn; -import static se.leap.bitmaskclient.pluggableTransports.ShapeshifterClient.DISPATCHER_IP; -import static se.leap.bitmaskclient.pluggableTransports.ShapeshifterClient.DISPATCHER_PORT; import androidx.annotation.VisibleForTesting; @@ -52,11 +49,12 @@ import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Connection.TransportType; +import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.Transport; import se.leap.bitmaskclient.base.utils.ConfigHelper; -import se.leap.bitmaskclient.pluggableTransports.HoppingObfsVpnClient; -import se.leap.bitmaskclient.pluggableTransports.Obfs4Options; +import se.leap.bitmaskclient.pluggableTransports.ObfsvpnClient; +import se.leap.bitmaskclient.pluggableTransports.models.Obfs4Options; public class VpnConfigGenerator { private final JSONObject generalConfiguration; @@ -384,28 +382,12 @@ public class VpnConfigGenerator { } stringBuilder.append(getRouteString(ipAddress, transport)); - stringBuilder.append(getRemoteString(ipAddress, transport)); - stringBuilder.append(getExtraOptions(transport)); - } - - public String getRemoteString(String ipAddress, Transport transport) { - if (useObfsVpn()) { - if (useObfuscationPinning) { - return REMOTE + " " + obfuscationPinningIP + " " + obfuscationPinningPort + " tcp" + newLine; - } - switch (transport.getTransportType()) { - case OBFS4: - return REMOTE + " " + ipAddress + " " + transport.getPorts()[0] + " tcp" + newLine; - case OBFS4_HOP: - return REMOTE + " " + HoppingObfsVpnClient.IP + " " + HoppingObfsVpnClient.PORT + " udp" + newLine; - default: - VpnStatus.logError("Unexpected pluggable transport type " + transport.getType() + " for gateway " + ipAddress); - return ""; - } - } - return REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine; + String transparentProxyRemote = REMOTE + " " + ObfsvpnClient.IP + " " + ObfsvpnClient.PORT + " udp" + newLine; + stringBuilder.append(transparentProxyRemote); } + // TODO: figure out if any of these configs still make sense ( + @Deprecated public String getExtraOptions(Transport transport) { if (transport.getTransportType() == OBFS4_HOP) { return "replay-window 65535" + newLine + @@ -437,7 +419,7 @@ public class VpnConfigGenerator { return ""; } - // While openvpn in TCP mode is required for obfs4, openvpn in UDP mode is required for obfs4-hop + // With obfsvpn 1.0.0 openvpn is always required to run in UDP to work with any obfs4 based pluggable transport. private boolean openvpnModeSupportsPt(Transport transport, String ipAddress) { if (useObfuscationPinning) { // we don't know if the manually pinned bridge points to a openvpn gateway with the right @@ -457,7 +439,7 @@ public class VpnConfigGenerator { return false; } - String requiredProtocol = transport.getTransportType() == OBFS4_HOP ? UDP : TCP; + String requiredProtocol = UDP; for (String protocol : protocols) { if (protocol.equals(requiredProtocol)) { return true; diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java deleted file mode 100644 index 3780b7dc..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import androidx.annotation.NonNull; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import se.leap.bitmaskclient.base.models.Transport; - -public class HoppingConfig { - final boolean kcp; - final String proxyAddr; - final String[] remotes; - final String[] certs; - final int portSeed; - final int portCount; - final int minHopSeconds; - final int hopJitter; - - public HoppingConfig(boolean kcp, - String proxyAddr, - Obfs4Options options, - int minHopSeconds, - int hopJitter) { - this.kcp = kcp; - this.proxyAddr = proxyAddr; - Transport transport = options.transport; - Transport.Endpoint[] endpoints = transport.getOptions().getEndpoints(); - if (endpoints == null) { - // only port hopping, we assume the gateway IP as hopping PT's IP - this.remotes = new String[]{ options.gatewayIP }; - this.certs = new String[] { transport.getOptions().getCert() }; - } else { - // port+ip hopping - this.remotes = new String[endpoints.length]; - this.certs = new String[endpoints.length]; - for (int i = 0; i < remotes.length; i++) { - remotes[i] = endpoints[i].getIp(); - certs[i] = endpoints[i].getCert(); - } - } - this.portSeed = transport.getOptions().getPortSeed(); - this.portCount = transport.getOptions().getPortCount(); - this.minHopSeconds = minHopSeconds; - this.hopJitter = hopJitter; - } - - @NonNull - @Override - public String toString() { - Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - return gson.toJson(this); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java deleted file mode 100644 index 751208ba..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java +++ /dev/null @@ -1,66 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import client.Client; -import client.HopClient; -import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.base.models.Constants; - -public class HoppingObfsVpnClient implements PtClientInterface { - - public static final int PORT = 8080; - public static final String IP = "127.0.0.1"; - - public final HopClient client; - - public HoppingObfsVpnClient(Obfs4Options options) throws IllegalStateException { - - //FIXME: use a different strategy here - //Basically we would want to track if the more performant transport protocol (KCP?/TCP?) usage was successful - //if so, we stick to it, otherwise we flip the flag - boolean kcp = Constants.KCP.equals(options.transport.getProtocols()[0]); - - HoppingConfig hoppingConfig = new HoppingConfig(kcp,IP+":"+PORT, options, 10, 10); - try { - client = Client.newFFIHopClient(hoppingConfig.toString()); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @Override - public int start() { - try { - client.setEventLogger(this); - return client.start() ? PORT : 0; - } catch (Exception e) { - e.printStackTrace(); - return 0; - } - } - - @Override - public void stop() { - try { - client.stop(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - client.setEventLogger(null); - } - } - - @Override - public boolean isStarted() { - return client.isStarted(); - } - - @Override - public void error(String s) { - VpnStatus.logError("[hopping-obfs4] " + s); - } - - @Override - public void log(String state, String message) { - VpnStatus.logDebug("[hopping-obfs4] " + state + ": " + message); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java deleted file mode 100644 index 0dd81eb8..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java +++ /dev/null @@ -1,16 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import java.io.Serializable; - -import se.leap.bitmaskclient.base.models.Transport; - -public class Obfs4Options implements Serializable { - public String gatewayIP; - public Transport transport; - - public Obfs4Options(String gatewayIP, - Transport transport) { - this.gatewayIP = gatewayIP; - this.transport = transport; - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java deleted file mode 100644 index 685349ed..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java +++ /dev/null @@ -1,144 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import static se.leap.bitmaskclient.base.models.Constants.KCP; - -import android.util.Log; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Observable; -import java.util.Observer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import client.Client; -import de.blinkt.openvpn.core.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.eip.EipStatus; - -public class ObfsVpnClient implements PropertyChangeListener, PtClientInterface { - - public static final AtomicInteger SOCKS_PORT = new AtomicInteger(4430); - public static final String SOCKS_IP = "127.0.0.1"; - private static final String ERR_BIND = "bind: address already in use"; - - private static final String TAG = ObfsVpnClient.class.getSimpleName(); - private volatile boolean noNetwork; - private final AtomicBoolean pendingNetworkErrorHandling = new AtomicBoolean(false); - private final AtomicInteger reconnectRetry = new AtomicInteger(0); - private static final int MAX_RETRY = 5; - - private final client.Client_ obfsVpnClient; - private final Object LOCK = new Object(); - - public ObfsVpnClient(Obfs4Options options) throws IllegalStateException{ - //FIXME: use a different strategy here - //Basically we would want to track if the more performant transport protocol (KCP?/TCP?) usage was successful - //if so, we stick to it, otherwise we flip the flag - boolean kcp = KCP.equals(options.transport.getProtocols()[0]); - - if (options.transport.getOptions().getCert() == null) { - throw new IllegalStateException("No cert found to establish a obfs4 connection"); - } - - obfsVpnClient = Client.newClient(kcp, SOCKS_IP+":"+SOCKS_PORT.get(), options.transport.getOptions().getCert()); - } - - /** - * starts the client - * @return the port ObfsVpn is running on - */ - public int start() { - synchronized (LOCK) { - obfsVpnClient.setEventLogger(this); - Log.d(TAG, "aquired LOCK"); - new Thread(this::startSync).start(); - waitUntilStarted(); - Log.d(TAG, "returning LOCK after " + (reconnectRetry.get() + 1) * 200 +" ms"); - } - return SOCKS_PORT.get(); - } - - // We're waiting here until the obfsvpn client has found a unbound port and started - private void waitUntilStarted() { - int count = -1; - try { - while (count < reconnectRetry.get() && reconnectRetry.get() < MAX_RETRY) { - Thread.sleep(200); - count++; - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - private void startSync() { - try { - obfsVpnClient.start(); - } catch (Exception e) { - Log.e(TAG, "[obfsvpn] exception: " + e.getLocalizedMessage()); - VpnStatus.logError("[obfsvpn] " + e.getLocalizedMessage()); - if (e.getLocalizedMessage() != null && e.getLocalizedMessage().contains(ERR_BIND) && reconnectRetry.get() < MAX_RETRY) { - reconnectRetry.addAndGet(1); - SOCKS_PORT.addAndGet(1); - obfsVpnClient.setSocksAddr(SOCKS_IP+":"+SOCKS_PORT.get()); - Log.d(TAG, "[obfsvpn] reconnecting on different port... " + SOCKS_PORT.get()); - VpnStatus.logDebug("[obfsvpn] reconnecting on different port... " + SOCKS_PORT.get()); - startSync(); - } else if (noNetwork) { - pendingNetworkErrorHandling.set(true); - } - } - } - - public void stop() { - synchronized (LOCK) { - Log.d(TAG, "stopping obfsVpnClient..."); - try { - obfsVpnClient.stop(); - reconnectRetry.set(0); - SOCKS_PORT.set(4430); - Thread.sleep(100); - } catch (Exception e) { - e.printStackTrace(); - VpnStatus.logError("[obfsvpn] " + e.getLocalizedMessage()); - } finally { - obfsVpnClient.setEventLogger(null); - } - pendingNetworkErrorHandling.set(false); - Log.d(TAG, "stopping obfsVpnClient releasing LOCK ..."); - } - } - - public boolean isStarted() { - return obfsVpnClient.isStarted(); - } - - // TODO: register observer! - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (EipStatus.PROPERTY_CHANGE.equals(evt.getPropertyName())) { - EipStatus status = (EipStatus) evt.getNewValue(); - if (status.getLevel() == ConnectionStatus.LEVEL_NONETWORK) { - noNetwork = true; - } else { - noNetwork = false; - if (pendingNetworkErrorHandling.getAndSet(false)) { - stop(); - start(); - } - } - } - } - - @Override - public void error(String s) { - VpnStatus.logError("[obfsvpn] " + s); - } - - @Override - public void log(String state, String message) { - VpnStatus.logDebug("[obfsvpn] " + state + " " + message); - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsvpnClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsvpnClient.java new file mode 100644 index 00000000..dfdfbdd5 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsvpnClient.java @@ -0,0 +1,87 @@ +package se.leap.bitmaskclient.pluggableTransports; + +import android.util.Log; + +import client.Client; +import client.Client_; +import client.EventLogger; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.connection.Connection; +import se.leap.bitmaskclient.base.models.Constants; +import se.leap.bitmaskclient.pluggableTransports.models.HoppingConfig; +import se.leap.bitmaskclient.pluggableTransports.models.KcpConfig; +import se.leap.bitmaskclient.pluggableTransports.models.Obfs4Options; +import se.leap.bitmaskclient.pluggableTransports.models.ObfsvpnConfig; + +public class ObfsvpnClient { + + public static final int PORT = 8080; + public static final String IP = "127.0.0.1"; + private final Object LOCK = new Object(); + + + private static final String TAG = ObfsvpnClient.class.getSimpleName(); + + public final Client_ client; + + public ObfsvpnClient(Obfs4Options options) throws IllegalStateException { + + //FIXME: use a different strategy here + //Basically we would want to track if the more performant transport protocol (KCP?/TCP?) usage was successful + //if so, we stick to it, otherwise we flip the flag + boolean kcpEnabled = Constants.KCP.equals(options.transport.getProtocols()[0]); + boolean hoppingEnabled = options.transport.getTransportType() == Connection.TransportType.OBFS4_HOP; + if (!hoppingEnabled && (options.transport.getPorts() == null || options.transport.getPorts().length == 0)) { + throw new IllegalStateException("obf4 based transport has no bridge ports configured"); + } + KcpConfig kcpConfig = new KcpConfig(kcpEnabled); + HoppingConfig hoppingConfig = new HoppingConfig(hoppingEnabled,IP+":"+PORT, options, 10, 10); + ObfsvpnConfig obfsvpnConfig = new ObfsvpnConfig(IP+":"+PORT, hoppingConfig, kcpConfig, options.bridgeIP, options.transport.getPorts()[0], options.transport.getOptions().getCert() ); + try { + Log.d(TAG, obfsvpnConfig.toString()); + client = Client.newFFIClient(obfsvpnConfig.toString()); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + public int start() { + synchronized (LOCK) { + new Thread(() -> { + try { + client.setEventLogger(new EventLogger() { + @Override + public void error(String s) { + VpnStatus.logError("[obfs4-client] " + s); + } + + @Override + public void log(String state, String message) { + VpnStatus.logDebug("[obfs4-client] " + state + ": " + message); + } + }); + client.start(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + return PORT; + } + } + + public void stop() { + synchronized (LOCK) { + try { + client.stop(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + client.setEventLogger(null); + } + } + } + + public boolean isStarted() { + return client.isStarted(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java deleted file mode 100644 index 945e3d7a..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java +++ /dev/null @@ -1,18 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import de.blinkt.openvpn.core.connection.Connection; -import de.blinkt.openvpn.core.connection.Obfs4Connection; -import de.blinkt.openvpn.core.connection.Obfs4HopConnection; - -public class PtClientBuilder { - public static PtClientInterface getPtClient(Connection connection) throws IllegalStateException { - switch (connection.getTransportType()) { - case OBFS4: - return new ObfsVpnClient(((Obfs4Connection) connection).getObfs4Options()); - case OBFS4_HOP: - return new HoppingObfsVpnClient(((Obfs4HopConnection) connection).getObfs4Options()); - default: - throw new IllegalStateException("Unexpected pluggable transport " + connection.getTransportType()); - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java deleted file mode 100644 index 28d19a97..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java +++ /dev/null @@ -1,9 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import client.EventLogger; - -public interface PtClientInterface extends EventLogger { - int start(); - void stop(); - boolean isStarted(); -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java deleted file mode 100644 index e57401f8..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright (c) 2020 LEAP Encryption Access Project and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package se.leap.bitmaskclient.pluggableTransports; - -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import de.blinkt.openvpn.core.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.eip.EipStatus; - -public class ShapeshifterClient implements PropertyChangeListener { - - public static final String DISPATCHER_PORT = "4430"; - public static final String DISPATCHER_IP = "127.0.0.1"; - private static final int MAX_RETRY = 5; - private static final int RETRY_TIME = 4000; - private static final String TAG = ShapeshifterClient.class.getSimpleName(); - - private final shapeshifter.Shapeshifter_ shapeShifter; - private boolean isErrorHandling; - private boolean noNetwork; - private int retry = 0; - private final Handler reconnectHandler; - - @Deprecated - public class ShapeshifterLogger implements shapeshifter.Logger { - @Override - public void log(String s) { - Log.e(TAG, "SHAPESHIFTER ERROR: " + s); - VpnStatus.logError(s); - isErrorHandling = true; - close(); - - if (retry < MAX_RETRY && !noNetwork) { - retry++; - reconnectHandler.postDelayed(ShapeshifterClient.this::reconnect, RETRY_TIME); - } else { - VpnStatus.logError(VpnStatus.ErrorType.SHAPESHIFTER); - } - } - } - - public ShapeshifterClient(Obfs4Options options) { - shapeShifter = new shapeshifter.Shapeshifter_(); - shapeShifter.setLogger(new ShapeshifterLogger()); - setup(options); - Looper.prepare(); - reconnectHandler = new Handler(); - EipStatus.getInstance().addObserver(this); - Log.d(TAG, "shapeshifter initialized with: \n" + shapeShifter.toString()); - } - - private void setup(Obfs4Options options) { - shapeShifter.setSocksAddr(DISPATCHER_IP+":"+DISPATCHER_PORT); - shapeShifter.setTarget(options.gatewayIP +":"+options.transport.getPorts()[0]); - shapeShifter.setCert(options.transport.getOptions().getCert()); - } - - public void setOptions(Obfs4Options options) { - setup(options); - } - - public void start() { - try { - shapeShifter.open(); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "SHAPESHIFTER ERROR: " + e.getLocalizedMessage()); - VpnStatus.logError(VpnStatus.ErrorType.SHAPESHIFTER); - VpnStatus.logError(e.getLocalizedMessage()); - } - } - - private void close() { - try { - shapeShifter.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void reconnect() { - try { - shapeShifter.open(); - retry = 0; - isErrorHandling = false; - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "SHAPESHIFTER RECONNECTION ERROR: " + e.getLocalizedMessage()); - VpnStatus.logError("Unable to reconnect shapeshifter: " + e.getLocalizedMessage()); - } - } - - public boolean stop() { - try { - shapeShifter.close(); - return true; - } catch (Exception e) { - e.printStackTrace(); - VpnStatus.logError(e.getLocalizedMessage()); - } - EipStatus.getInstance().deleteObserver(this); - return false; - } - - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (EipStatus.PROPERTY_CHANGE.equals(evt.getPropertyName())) { - EipStatus status = (EipStatus) evt.getNewValue(); - if (status.getLevel() == ConnectionStatus.LEVEL_NONETWORK) { - noNetwork = true; - } else { - noNetwork = false; - if (isErrorHandling) { - isErrorHandling = false; - close(); - start(); - } - } - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/HoppingConfig.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/HoppingConfig.java new file mode 100644 index 00000000..96b8c460 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/HoppingConfig.java @@ -0,0 +1,69 @@ +package se.leap.bitmaskclient.pluggableTransports.models; + +import androidx.annotation.NonNull; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import se.leap.bitmaskclient.base.models.Transport; + +public class HoppingConfig { + + /** + * Enabled bool `json:"enabled"` + * Remotes []string `json:"remotes"` + * Obfs4Certs []string `json:"obfs4_certs"` + * PortSeed int64 `json:"port_seed"` + * PortCount uint `json:"port_count"` + * MinHopSeconds uint `json:"min_hop_seconds"` + * HopJitter uint `json:"hop_jitter"` + * } + */ + + final boolean enabled; + final String proxyAddr; + final String[] remotes; + final String[] obfs4Certs; + final int portSeed; + final int portCount; + final int minHopSeconds; + final int hopJitter; + + public HoppingConfig(boolean enabled, + String proxyAddr, + Obfs4Options options, + int minHopSeconds, + int hopJitter) { + this.enabled = enabled; + this.proxyAddr = proxyAddr; + Transport transport = options.transport; + Transport.Endpoint[] endpoints = transport.getOptions().getEndpoints(); + if (endpoints == null) { + // only port hopping, we assume the gateway IP as hopping PT's IP + this.remotes = new String[]{ options.bridgeIP }; + this.obfs4Certs = new String[] { transport.getOptions().getCert() }; + } else { + // port+ip hopping + this.remotes = new String[endpoints.length]; + this.obfs4Certs = new String[endpoints.length]; + for (int i = 0; i < remotes.length; i++) { + remotes[i] = endpoints[i].getIp(); + obfs4Certs[i] = endpoints[i].getCert(); + } + } + this.portSeed = transport.getOptions().getPortSeed(); + this.portCount = transport.getOptions().getPortCount(); + this.minHopSeconds = minHopSeconds; + this.hopJitter = hopJitter; + } + + @NonNull + @Override + public String toString() { + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + return gson.toJson(this); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/KcpConfig.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/KcpConfig.java new file mode 100644 index 00000000..255e7dd7 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/KcpConfig.java @@ -0,0 +1,39 @@ +package se.leap.bitmaskclient.pluggableTransports.models; + +import androidx.annotation.NonNull; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class KcpConfig { + + // check OpenVPN's --sndbuf size and --rcvbuf size + public static final int DEFAULT_KCP_SEND_WINDOW_SIZE = 32; + public static final int DEFAULT_KCP_RECEIVE_WINDOW_SIZE = 32; + public static final int DEFAULT_KCP_READ_BUFFER = 16 * 1024 * 1024; + public static final int DEFAULT_KCP_WRITE_BUFFER = 16 * 1024 * 1024; + + final boolean enabled; + final int sendWindowSize; + final int receiveWindowSize; + final int readBuffer; + final int writeBuffer; + + public KcpConfig(boolean enabled) { + this.enabled = enabled; + this.sendWindowSize = DEFAULT_KCP_SEND_WINDOW_SIZE; + this.receiveWindowSize = DEFAULT_KCP_RECEIVE_WINDOW_SIZE; + this.readBuffer = DEFAULT_KCP_READ_BUFFER; + this.writeBuffer = DEFAULT_KCP_WRITE_BUFFER; + } + + @NonNull + @Override + public String toString() { + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + return gson.toJson(this); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/Obfs4Options.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/Obfs4Options.java new file mode 100644 index 00000000..1eec376a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/Obfs4Options.java @@ -0,0 +1,16 @@ +package se.leap.bitmaskclient.pluggableTransports.models; + +import java.io.Serializable; + +import se.leap.bitmaskclient.base.models.Transport; + +public class Obfs4Options implements Serializable { + public String bridgeIP; + public Transport transport; + + public Obfs4Options(String bridgeIP, + Transport transport) { + this.bridgeIP = bridgeIP; + this.transport = transport; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/ObfsvpnConfig.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/ObfsvpnConfig.java new file mode 100644 index 00000000..9f85c4a0 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/models/ObfsvpnConfig.java @@ -0,0 +1,35 @@ +package se.leap.bitmaskclient.pluggableTransports.models; + +import androidx.annotation.NonNull; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class ObfsvpnConfig { + + final String proxyAddr; + final HoppingConfig hoppingConfig; + final KcpConfig kcpConfig; + final String remoteIp; + final String remotePort; + final String obfs4Cert; + + public ObfsvpnConfig(String proxyAddress, HoppingConfig hoppingConfig, KcpConfig kcpConfig, String remoteIP, String remotePort, String obfsv4Cert) { + this.proxyAddr = proxyAddress; + this.hoppingConfig = hoppingConfig; + this.kcpConfig = kcpConfig; + this.remoteIp = remoteIP; + this.remotePort = remotePort; + this.obfs4Cert = obfsv4Cert; + } + + @NonNull + @Override + public String toString() { + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + return gson.toJson(this); + } +} -- cgit v1.2.3