summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2023-04-17 19:23:25 +0000
committercyberta <cyberta@riseup.net>2023-04-17 19:23:25 +0000
commit821cac0b60b85d0956cbe97de84766f660b907a6 (patch)
tree386f1736bdd93404c96f22fb4d522b87ae269746 /app/src/main/java/se/leap/bitmaskclient
parenta4deca391ce064510002e24ba9f18d965f0dee59 (diff)
parentb613b0cd4dc44b48859add528a4fb83b4a17c3fa (diff)
Merge branch 'update_obfsvpn' into 'master'
Update obfsvpn See merge request leap/bitmask_android!243
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java31
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java126
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java12
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java17
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java7
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java44
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java301
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java57
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java66
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java21
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java25
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java18
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java5
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java58
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java2
18 files changed, 571 insertions, 238 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
index ee5bd2a7..57467974 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
@@ -179,10 +179,19 @@ public interface Constants {
String PORTS = "ports";
String PROTOCOLS = "protocols";
String UDP = "udp";
+ String TCP = "tcp";
+ String KCP = "kcp";
String CAPABILITIES = "capabilities";
String TRANSPORT = "transport";
String TYPE = "type";
String OPTIONS = "options";
+ String IAT_MODE = "iatMode";
+ String CERT = "cert";
+ String CERTS = "certs";
+ String ENDPOINTS = "endpoints";
+ String PORT_SEED = "port_seed";
+ String PORT_COUNT = "port_count";
+ String EXPERIMENTAL = "experimental";
String VERSION = "version";
String NAME = "name";
String TIMEZONE = "timezone";
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 53f864cf..08e13cf6 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
@@ -16,11 +16,14 @@
*/
package se.leap.bitmaskclient.base.models;
+import static de.blinkt.openvpn.core.connection.Connection.TransportProtocol.KCP;
+import static de.blinkt.openvpn.core.connection.Connection.TransportProtocol.TCP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS;
import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS;
+import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGISTERED;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS;
import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT;
@@ -46,6 +49,7 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
+import de.blinkt.openvpn.core.connection.Connection.TransportProtocol;
import de.blinkt.openvpn.core.connection.Connection.TransportType;
import motd.IStringCollection;
import motd.Motd;
@@ -122,8 +126,10 @@ public final class Provider implements Parcelable {
setGeoipUrl(geoipUrl);
}
- public Provider(String mainUrl, String providerIp, String providerApiIp) {
- this(mainUrl, null, null, providerIp, providerApiIp);
+ public static Provider createCustomProvider(String mainUrl, String domain) {
+ Provider p = new Provider(mainUrl);
+ p.domain = domain;
+ return p;
}
public Provider(String mainUrl, String geoipUrl, String motdUrl, String providerIp, String providerApiIp) {
@@ -181,16 +187,16 @@ public final class Provider implements Parcelable {
public boolean supportsPluggableTransports() {
if (useObfsVpn()) {
- return supportsTransports(new TransportType[]{OBFS4, OBFS4_KCP});
+ 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 TransportType[]{OBFS4});
+ return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP)});
}
public boolean supportsExperimentalPluggableTransports() {
- return supportsTransports(new TransportType[]{OBFS4_KCP});
+ return supportsTransports(new Pair[]{new Pair<>(OBFS4, KCP), new Pair<>(OBFS4_HOP, TCP), new Pair<>(OBFS4_HOP, KCP)});
}
- private boolean supportsTransports(TransportType[] transportTypes) {
+ private boolean supportsTransports(Pair<TransportType, TransportProtocol>[] transportTypes) {
try {
JSONArray gatewayJsons = eipServiceJson.getJSONArray(GATEWAYS);
for (int i = 0; i < gatewayJsons.length(); i++) {
@@ -199,9 +205,13 @@ public final class Provider implements Parcelable {
getJSONArray(TRANSPORT);
for (int j = 0; j < transports.length(); j++) {
String supportedTransportType = transports.getJSONObject(j).getString(TYPE);
- for (TransportType transportType : transportTypes) {
- if (transportType.toString().equals(supportedTransportType)) {
- return true;
+ JSONArray transportProtocols = transports.getJSONObject(j).getJSONArray(PROTOCOLS);
+ for (Pair<TransportType, TransportProtocol> transportPair : transportTypes) {
+ for (int k = 0; k < transportProtocols.length(); k++) {
+ if (transportPair.first.toString().equals(supportedTransportType) &&
+ transportPair.second.toString().equals(transportProtocols.getString(k))) {
+ return true;
+ }
}
}
}
@@ -512,6 +522,7 @@ public final class Provider implements Parcelable {
JSONObject json = new JSONObject();
try {
json.put(Provider.MAIN_URL, mainUrl);
+ json.put(Provider.DOMAIN, domain);
} catch (JSONException e) {
e.printStackTrace();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java
index 90a033dd..33fbbf7a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java
@@ -1,21 +1,57 @@
package se.leap.bitmaskclient.base.models;
+import androidx.annotation.Nullable;
+
+import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
import org.json.JSONObject;
-public class Transport {
+import java.io.Serializable;
+
+import de.blinkt.openvpn.core.connection.Connection;
+
+public class Transport implements Serializable {
private String type;
private String[] protocols;
+ @Nullable
private String[] ports;
+ @Nullable
private Options options;
public Transport(String type, String[] protocols, String[] ports, String cert) {
+ this(type, protocols, ports, new Options(cert, "0"));
+ }
+
+ public Transport(String type, String[] protocols, String[] ports, Options options) {
this.type = type;
this.protocols = protocols;
this.ports = ports;
- this.options = new Options(cert);
+ this.options = options;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Connection.TransportType getTransportType() {
+ return Connection.TransportType.fromString(type);
+ }
+
+ public String[] getProtocols() {
+ return protocols;
+ }
+
+ @Nullable
+ public String[] getPorts() {
+ return ports;
+ }
+
+ @Nullable
+ public Options getOptions() {
+ return options;
}
@Override
@@ -25,16 +61,71 @@ public class Transport {
public static Transport fromJson(JSONObject json) {
GsonBuilder builder = new GsonBuilder();
- return builder.create().fromJson(json.toString(), Transport.class);
+ return builder.
+ setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).
+ create().
+ fromJson(json.toString(), Transport.class);
}
- public static class Options {
+ public static class Options implements Serializable {
+ @Nullable
private String cert;
+ @SerializedName("iatMode")
private String iatMode;
- public Options(String cert) {
+ @Nullable
+ private Endpoint[] endpoints;
+
+ private boolean experimental;
+
+ private int portSeed;
+
+ private int portCount;
+
+
+ public Options(String cert, String iatMode) {
this.cert = cert;
- this.iatMode = "0";
+ this.iatMode = iatMode;
+ }
+
+ public Options(String iatMode, Endpoint[] endpoints, int portSeed, int portCount, boolean experimental) {
+ this(iatMode, endpoints, null, portSeed, portCount, experimental);
+ }
+
+ public Options(String iatMode, Endpoint[] endpoints, String cert, int portSeed, int portCount, boolean experimental) {
+ this.iatMode = iatMode;
+ this.endpoints = endpoints;
+ this.portSeed = portSeed;
+ this.portCount = portCount;
+ this.experimental = experimental;
+ this.cert = cert;
+ }
+
+
+ @Nullable
+ public String getCert() {
+ return cert;
+ }
+
+ public String getIatMode() {
+ return iatMode;
+ }
+
+ @Nullable
+ public Endpoint[] getEndpoints() {
+ return endpoints;
+ }
+
+ public boolean isExperimental() {
+ return experimental;
+ }
+
+ public int getPortSeed() {
+ return portSeed;
+ }
+
+ public int getPortCount() {
+ return portCount;
}
@Override
@@ -44,6 +135,29 @@ public class Transport {
}
+ public static class Endpoint implements Serializable {
+ private String ip;
+ private String cert;
+
+ public Endpoint(String ip, String cert) {
+ this.ip = ip;
+ this.cert = cert;
+ }
+
+ @Override
+ public String toString() {
+ return new Gson().toJson(this);
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public String getCert() {
+ return cert;
+ }
+ }
+
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java
index 8a526499..8e6273a7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java
@@ -23,22 +23,12 @@ public class InputStreamHelper {
return s.hasNext() ? s.next() : "";
}
- public static String extractKeyFromInputStream(InputStream inputStream, String key) {
- String value = "";
-
- JSONObject fileContents = inputStreamToJson(inputStream);
- if (fileContents != null)
- value = fileContents.optString(key);
- return value;
- }
-
public static JSONObject inputStreamToJson(InputStream inputStream) {
- JSONObject json = null;
+ JSONObject json = new JSONObject();
try {
byte[] bytes = new byte[inputStream.available()];
if (inputStream.read(bytes) > 0)
json = new JSONObject(new String(bytes));
- inputStream.reset();
} catch (IOException | JSONException e) {
e.printStackTrace();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
index 88cdc715..5b082448 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -91,6 +91,7 @@ import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.models.Pair;
import se.leap.bitmaskclient.base.models.ProviderObservable;
import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.eip.GatewaysManager.GatewayOptions;
/**
* EIP is the abstract base class for interacting with and managing the Encrypted
@@ -255,8 +256,8 @@ public final class EIP extends JobIntentService implements Observer {
return;
}
- Pair<Gateway, Connection.TransportType> gatewayTransportTypePair = gatewaysManager.select(nClosestGateway);
- launchActiveGateway(gatewayTransportTypePair, nClosestGateway, result);
+ GatewayOptions gatewayOptions = gatewaysManager.select(nClosestGateway);
+ launchActiveGateway(gatewayOptions, nClosestGateway, result);
if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) {
tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result);
} else {
@@ -270,7 +271,7 @@ public final class EIP extends JobIntentService implements Observer {
*/
private void startEIPAlwaysOnVpn() {
GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext());
- Pair<Gateway, Connection.TransportType> gatewayTransportTypePair = gatewaysManager.select(0);
+ GatewayOptions gatewayOptions = gatewaysManager.select(0);
Bundle result = new Bundle();
if (shouldUpdateVPNCertificate()) {
@@ -279,7 +280,7 @@ public final class EIP extends JobIntentService implements Observer {
ProviderObservable.getInstance().updateProvider(p);
}
- launchActiveGateway(gatewayTransportTypePair, 0, result);
+ launchActiveGateway(gatewayOptions, 0, result);
if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){
VpnStatus.logWarning("ALWAYS-ON VPN: " + getString(R.string.no_vpn_profiles_defined));
}
@@ -323,13 +324,13 @@ public final class EIP extends JobIntentService implements Observer {
/**
* starts the VPN and connects to the given gateway
*
- * @param gatewayTransportTypePair Pair of Gateway and associated transport used to connect
+ * @param gatewayOptions GatewayOptions model containing a Gateway and the associated transport used to connect
*/
- private void launchActiveGateway(@Nullable Pair<Gateway, Connection.TransportType> gatewayTransportTypePair, int nClosestGateway, Bundle result) {
+ private void launchActiveGateway(@Nullable GatewayOptions gatewayOptions, int nClosestGateway, Bundle result) {
VpnProfile profile;
- if (gatewayTransportTypePair == null || gatewayTransportTypePair.first == null ||
- (profile = gatewayTransportTypePair.first.getProfile(gatewayTransportTypePair.second)) == null) {
+ if (gatewayOptions == null || gatewayOptions.gateway == null ||
+ (profile = gatewayOptions.gateway.getProfile(gatewayOptions.transportType)) == null) {
String preferredLocation = getPreferredCity(getApplicationContext());
if (preferredLocation != null) {
setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name), preferredLocation);
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
index 929935eb..719b960e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
@@ -16,6 +16,7 @@
*/
package se.leap.bitmaskclient.eip;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT;
import static se.leap.bitmaskclient.base.models.Constants.FULLNESS;
import static se.leap.bitmaskclient.base.models.Constants.HOST;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS;
@@ -52,7 +53,6 @@ import java.util.HashSet;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConfigParser;
import de.blinkt.openvpn.core.connection.Connection;
-import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.utils.ConfigHelper;
/**
@@ -77,6 +77,9 @@ public class Gateway {
private String name;
private int timezone;
private int apiVersion;
+ /** FIXME: We expect here that not more than one obfs4 transport is offered by a gateway, however
+ * it's possible to setup gateways that have obfs4 over kcp and tcp which result in different VpnProfiles each
+ */
private HashMap<Connection.TransportType, VpnProfile> vpnProfiles;
/**
@@ -209,7 +212,7 @@ public class Gateway {
}
public boolean supportsTransport(Connection.TransportType transportType) {
- if (transportType == Connection.TransportType.PT) {
+ if (transportType == PT) {
return supportsPluggableTransports();
}
return vpnProfiles.get(transportType) != null;
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java
index 52030ce3..ad95c823 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java
@@ -55,6 +55,7 @@ public class GatewaySelector {
return offsets.isEmpty() ? null : offsets.firstEntry().getValue().iterator().next();
}
+ // calculateOffsets randomizes the order of Gateways with the same distance, e.g. from the same location
private TreeMap<Integer, Set<Gateway>> calculateOffsets() {
TreeMap<Integer, Set<Gateway>> offsets = new TreeMap<Integer, Set<Gateway>>();
int localOffset = getCurrentTimezone();
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
index 521d095e..d114665b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
@@ -17,7 +17,7 @@
package se.leap.bitmaskclient.eip;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT;
import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS;
@@ -61,7 +61,6 @@ import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.models.GatewayJson;
import se.leap.bitmaskclient.base.models.Location;
-import se.leap.bitmaskclient.base.models.Pair;
import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.models.ProviderObservable;
import se.leap.bitmaskclient.base.models.Transport;
@@ -103,6 +102,16 @@ public class GatewaysManager {
}
}
+ public static class GatewayOptions {
+ public Gateway gateway;
+ public TransportType transportType;
+
+ public GatewayOptions(Gateway gateway, TransportType transportType) {
+ this.gateway = gateway;
+ this.transportType = transportType;
+ }
+ }
+
private static final String TAG = GatewaysManager.class.getSimpleName();
public static final String PINNED_OBFUSCATION_PROXY = "pinned.obfuscation.proxy";
@@ -113,6 +122,8 @@ public class GatewaysManager {
private ArrayList<Location> locations = new ArrayList<>();
private TransportType selectedTransport;
+ GatewaySelector gatewaySelector;
+
public GatewaysManager(Context context) {
this.context = context;
configureFromCurrentProvider();
@@ -122,7 +133,7 @@ public class GatewaysManager {
* select closest Gateway
* @return the n closest Gateway
*/
- public Pair<Gateway, TransportType> select(int nClosest) {
+ public GatewayOptions select(int nClosest) {
if (PreferenceHelper.useObfuscationPinning(context)) {
if (nClosest > 2) {
// no need to try again the pinned proxy, probably configuration error
@@ -132,14 +143,14 @@ public class GatewaysManager {
if (gateway == null) {
return null;
}
- return new Pair<>(gateway, getObfuscationPinningKCP(context) ? OBFS4_KCP : OBFS4);
+ return new GatewayOptions(gateway, OBFS4);
}
String selectedCity = getPreferredCity(context);
return select(nClosest, selectedCity);
}
- public Pair<Gateway, TransportType> select(int nClosest, String city) {
- TransportType[] transportTypes = getUseBridges(context) ? new TransportType[]{OBFS4, OBFS4_KCP} : new TransportType[]{OPENVPN};
+ public GatewayOptions select(int nClosest, String city) {
+ TransportType[] transportTypes = getUseBridges(context) ? new TransportType[]{OBFS4, OBFS4_HOP} : new TransportType[]{OPENVPN};
if (presortedList.size() > 0) {
return getGatewayFromPresortedList(nClosest, transportTypes, city);
}
@@ -266,9 +277,11 @@ public class GatewaysManager {
return Load.getLoadByValue(location.getAverageLoad(transportType));
}
- private Pair<Gateway, TransportType> getGatewayFromTimezoneCalculation(int nClosest, TransportType[] transportTypes, @Nullable String city) {
+ private GatewayOptions getGatewayFromTimezoneCalculation(int nClosest, TransportType[] transportTypes, @Nullable String city) {
List<Gateway> list = new ArrayList<>(gateways.values());
- GatewaySelector gatewaySelector = new GatewaySelector(list);
+ if (gatewaySelector == null) {
+ gatewaySelector = new GatewaySelector(list);
+ }
Gateway gateway;
int found = 0;
int i = 0;
@@ -277,7 +290,7 @@ public class GatewaysManager {
if ((city == null && gateway.supportsTransport(transportType)) ||
(gateway.getName().equals(city) && gateway.supportsTransport(transportType))) {
if (found == nClosest) {
- return new Pair<>(gateway, transportType);
+ return new GatewayOptions(gateway, transportType);
}
found++;
}
@@ -287,14 +300,14 @@ public class GatewaysManager {
return null;
}
- private Pair<Gateway, TransportType> getGatewayFromPresortedList(int nClosest, TransportType[] transportTypes, @Nullable String city) {
+ private GatewayOptions getGatewayFromPresortedList(int nClosest, TransportType[] transportTypes, @Nullable String city) {
int found = 0;
for (Gateway gateway : presortedList) {
for (TransportType transportType : transportTypes) {
if ((city == null && gateway.supportsTransport(transportType)) ||
(gateway.getName().equals(city) && gateway.supportsTransport(transportType))) {
if (found == nClosest) {
- return new Pair<>(gateway, transportType);
+ return new GatewayOptions(gateway, transportType);
}
found++;
}
@@ -333,7 +346,9 @@ public class GatewaysManager {
private int getPositionFromTimezoneCalculatedList(VpnProfile profile) {
TransportType transportType = profile.getTransportType();
- GatewaySelector gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values()));
+ if (gatewaySelector == null) {
+ gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values()));
+ }
Gateway gateway;
int nClosest = 0;
int i = 0;
@@ -387,10 +402,9 @@ public class GatewaysManager {
if (PreferenceHelper.useObfuscationPinning(context)) {
try {
- TransportType transportType = getObfuscationPinningKCP(context) ? OBFS4_KCP : OBFS4;
Transport[] transports = new Transport[]{
- new Transport(transportType.toString(),
- new String[]{"tcp"},
+ new Transport(OBFS4.toString(),
+ new String[]{getObfuscationPinningKCP(context) ? "kcp" : "tcp"},
new String[]{getObfuscationPinningPort(context)},
getObfuscationPinningCert(context))};
GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false);
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 72a0d80a..2c22d4f7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
@@ -17,20 +17,19 @@
package se.leap.bitmaskclient.eip;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT;
import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6;
-import static se.leap.bitmaskclient.base.models.Constants.OPTIONS;
+import static se.leap.bitmaskclient.base.models.Constants.KCP;
import static se.leap.bitmaskclient.base.models.Constants.PORTS;
import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE;
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.TYPE;
import static se.leap.bitmaskclient.base.models.Constants.UDP;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn;
import static se.leap.bitmaskclient.pluggableTransports.ShapeshifterClient.DISPATCHER_IP;
@@ -55,15 +54,16 @@ import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.connection.Connection;
import de.blinkt.openvpn.core.connection.Connection.TransportType;
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;
public class VpnConfigGenerator {
private final JSONObject generalConfiguration;
private final JSONObject gateway;
private final JSONObject secrets;
- private JSONObject obfs4Transport;
- private JSONObject obfs4TKcpTransport;
+ HashMap<TransportType, Transport> transports = new HashMap<>();
private final int apiVersion;
private final boolean preferUDP;
private final boolean experimentalTransports;
@@ -115,23 +115,14 @@ public class VpnConfigGenerator {
public void checkCapabilities() throws ConfigParser.ConfigParseError {
try {
-
if (apiVersion >= 3) {
JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT);
for (int i = 0; i < supportedTransports.length(); i++) {
- JSONObject transport = supportedTransports.getJSONObject(i);
- if (transport.getString(TYPE).equals(OBFS4.toString())) {
- obfs4Transport = transport;
- if (!experimentalTransports && !obfuscationPinningKCP) {
- break;
- }
- } else if ((experimentalTransports || obfuscationPinningKCP) && transport.getString(TYPE).equals(OBFS4_KCP.toString())) {
- obfs4TKcpTransport = transport;
- }
+ Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i));
+ transports.put(transport.getTransportType(), transport);
}
}
-
- } catch (JSONException e) {
+ } catch (Exception e) {
throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields");
}
}
@@ -147,18 +138,20 @@ public class VpnConfigGenerator {
e.printStackTrace();
}
}
- if (supportsObfs4()) {
- try {
- profiles.put(OBFS4, createProfile(OBFS4));
- } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) {
- e.printStackTrace();
- }
- }
- if (supportsObfs4Kcp()) {
- try {
- profiles.put(OBFS4_KCP, createProfile(OBFS4_KCP));
- } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) {
- e.printStackTrace();
+ if (apiVersion >= 3) {
+ for (TransportType transportType : transports.keySet()) {
+ Transport transport = transports.get(transportType);
+ if (transportType.isPluggableTransport()) {
+ Transport.Options transportOptions = transport.getOptions();
+ if (!experimentalTransports && transportOptions != null && transportOptions.isExperimental()) {
+ continue;
+ }
+ try {
+ profiles.put(transportType, createProfile(transportType));
+ } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) {
+ e.printStackTrace();
+ }
+ }
}
}
if (profiles.isEmpty()) {
@@ -168,14 +161,9 @@ public class VpnConfigGenerator {
}
private boolean supportsOpenvpn() {
- return !useObfuscationPinning && !gatewayConfiguration(OPENVPN).isEmpty();
- }
- private boolean supportsObfs4(){
- return obfs4Transport != null && !(useObfuscationPinning && obfuscationPinningKCP);
- }
-
- private boolean supportsObfs4Kcp() {
- return obfs4TKcpTransport != null && !(useObfuscationPinning && !obfuscationPinningKCP);
+ return !useObfuscationPinning &&
+ ((apiVersion >= 3 && transports.containsKey(OPENVPN)) ||
+ (apiVersion < 3 && !gatewayConfiguration(OPENVPN).isEmpty()));
}
private String getConfigurationString(TransportType transportType) {
@@ -193,10 +181,8 @@ public class VpnConfigGenerator {
String configuration = getConfigurationString(transportType);
ConfigParser icsOpenvpnConfigParser = new ConfigParser();
icsOpenvpnConfigParser.parseConfig(new StringReader(configuration));
- if (transportType == OBFS4) {
- icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(obfs4Transport, false));
- } else if (transportType == OBFS4_KCP) {
- icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(obfs4TKcpTransport, true));
+ if (transportType == OBFS4 || transportType == OBFS4_HOP) {
+ icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(transportType));
}
VpnProfile profile = icsOpenvpnConfigParser.convertProfile(transportType);
@@ -208,22 +194,19 @@ public class VpnConfigGenerator {
return profile;
}
- // TODO: whad does
- private Obfs4Options getObfs4Options(JSONObject transportJson, boolean useUdp) throws JSONException {
- JSONObject transportOptions = transportJson.getJSONObject(OPTIONS);
- String iatMode = transportOptions.getString("iatMode");
- String cert = transportOptions.getString("cert");
- String port = transportJson.getJSONArray(PORTS).getString(0);
+ private Obfs4Options getObfs4Options(TransportType transportType) throws JSONException {
String ip = gateway.getString(IP_ADDRESS);
- boolean udp = useUdp;
-
+ Transport transport;
if (useObfuscationPinning) {
- cert = obfuscationPinningCert;
- port = obfuscationPinningPort;
+ transport = new Transport(OBFS4.toString(),
+ new String[]{obfuscationPinningKCP ? KCP : TCP},
+ new String[]{obfuscationPinningPort},
+ obfuscationPinningCert);
ip = obfuscationPinningIP;
- udp = obfuscationPinningKCP;
+ } else {
+ transport = transports.get(transportType);
}
- return new Obfs4Options(ip, port, cert, iatMode, udp);
+ return new Obfs4Options(ip, transport);
}
private String generalConfiguration() {
@@ -250,7 +233,7 @@ public class VpnConfigGenerator {
}
private String gatewayConfiguration(TransportType transportType) {
- String remotes = "";
+ String configs = "";
StringBuilder stringBuilder = new StringBuilder();
try {
@@ -271,8 +254,7 @@ public class VpnConfigGenerator {
new String[]{ipAddress} :
new String[]{ipAddress6, ipAddress};
- JSONArray transports = capabilities.getJSONArray(TRANSPORT);
- gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses, transports);
+ gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses);
break;
}
} catch (JSONException e) {
@@ -280,19 +262,19 @@ public class VpnConfigGenerator {
e.printStackTrace();
}
- remotes = stringBuilder.toString();
- if (remotes.endsWith(newLine)) {
- remotes = remotes.substring(0, remotes.lastIndexOf(newLine));
+ configs = stringBuilder.toString();
+ if (configs.endsWith(newLine)) {
+ configs = configs.substring(0, configs.lastIndexOf(newLine));
}
- return remotes;
+ return configs;
}
- private void gatewayConfigMinApiv3(TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
- if (transportType.getMetaType() == PT) {
- ptGatewayConfigMinApiv3(stringBuilder, ipAddresses, transportType, transports);
+ private void gatewayConfigMinApiv3(TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses) throws JSONException {
+ if (transportType.isPluggableTransport()) {
+ ptGatewayConfigMinApiv3(stringBuilder, ipAddresses, transports.get(transportType));
} else {
- ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports);
+ ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports.get(OPENVPN));
}
}
@@ -311,19 +293,16 @@ public class VpnConfigGenerator {
}
}
- private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
- String port;
- String protocol;
- JSONObject openvpnTransport = getTransport(transports, OPENVPN);
- JSONArray ports = openvpnTransport.getJSONArray(PORTS);
- JSONArray protocols = openvpnTransport.getJSONArray(PROTOCOLS);
+ private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, Transport transport) {
+ if (transport.getProtocols() == null || transport.getPorts() == null) {
+ VpnStatus.logError("Misconfigured provider: missing details for transport openvpn on gateway " + ipAddresses[0]);
+ return;
+ }
if (preferUDP) {
StringBuilder udpRemotes = new StringBuilder();
StringBuilder tcpRemotes = new StringBuilder();
- for (int i = 0; i < protocols.length(); i++) {
- protocol = protocols.optString(i);
- for (int j = 0; j < ports.length(); j++) {
- port = ports.optString(j);
+ for (String protocol : transport.getProtocols()) {
+ for (String port : transport.getPorts()) {
for (String ipAddress : ipAddresses) {
String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
if (UDP.equals(protocol)) {
@@ -337,10 +316,8 @@ public class VpnConfigGenerator {
stringBuilder.append(udpRemotes.toString());
stringBuilder.append(tcpRemotes.toString());
} else {
- for (int j = 0; j < ports.length(); j++) {
- port = ports.getString(j);
- for (int k = 0; k < protocols.length(); k++) {
- protocol = protocols.optString(k);
+ for (String protocol : transport.getProtocols()) {
+ for (String port : transport.getPorts()) {
for (String ipAddress : ipAddresses) {
String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
stringBuilder.append(newRemote);
@@ -350,32 +327,18 @@ public class VpnConfigGenerator {
}
}
- private JSONObject getTransport(JSONArray transports, TransportType transportType) throws JSONException {
- JSONObject selectedTransport = new JSONObject();
- for (int i = 0; i < transports.length(); i++) {
- JSONObject transport = transports.getJSONObject(i);
- if (transport.getString(TYPE).equals(transportType.toString())) {
- selectedTransport = transport;
- break;
- }
- }
- return selectedTransport;
- }
-
private boolean isAllowedProtocol(TransportType transportType, String protocol) {
switch (transportType) {
case OPENVPN:
- return "tcp".equals(protocol) || "udp".equals(protocol);
+ return TCP.equals(protocol) || UDP.equals(protocol);
+ case OBFS4_HOP:
case OBFS4:
- case OBFS4_KCP:
- return "tcp".equals(protocol);
+ return TCP.equals(protocol) || KCP.equals(protocol);
}
return false;
}
- private void ptGatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, TransportType transportType, JSONArray transports) throws JSONException {
- JSONObject ptTransport = getTransport(transports, transportType);
- JSONArray ptProtocols = ptTransport.getJSONArray(PROTOCOLS);
+ private void ptGatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, Transport transport) {
//for now only use ipv4 gateway the syntax route remote_host 255.255.255.255 net_gateway is not yet working
// https://community.openvpn.net/openvpn/ticket/1161
@@ -399,63 +362,123 @@ public class VpnConfigGenerator {
}
if (ipAddress == null) {
- VpnStatus.logError("No matching IPv4 address found to configure obfs4.");
+ VpnStatus.logError("Misconfigured provider: No matching IPv4 address found to configure obfs4.");
return;
}
- if (!useObfuscationPinning) {
- // check if at least one openvpn protocol is TCP, openvpn in UDP is currently not supported for obfs4,
- // however on the wire UDP might be used
- boolean hasOpenvpnTcp = false;
- JSONObject openvpnTransport = getTransport(transports, OPENVPN);
- JSONArray gatewayProtocols = openvpnTransport.getJSONArray(PROTOCOLS);
- for (int i = 0; i < gatewayProtocols.length(); i++) {
- String protocol = gatewayProtocols.getString(i);
- if (protocol.contains("tcp")) {
- hasOpenvpnTcp = true;
- break;
- }
- }
- if (!hasOpenvpnTcp) {
- VpnStatus.logError("obfs4 currently only allows openvpn in TCP mode! Skipping obfs4 config for ip " + ipAddress);
- return;
- }
- }
-
- boolean hasAllowedPTProtocol = false;
- for (int i = 0; i < ptProtocols.length(); i++) {
- String protocol = ptProtocols.getString(i);
- if (isAllowedProtocol(transportType, protocol)) {
- hasAllowedPTProtocol = true;
- break;
- }
+ if (!openvpnModeSupportsPt(transport, ipAddress) || !hasPTAllowedProtocol(transport, ipAddress)) {
+ return;
}
- if (!hasAllowedPTProtocol) {
- VpnStatus.logError("Misconfigured provider: wrong protocol defined in " + transportType.toString()+ " transport JSON.");
+ TransportType transportType = transport.getTransportType();
+ if (transportType == OBFS4 && (transport.getPorts() == null || transport.getPorts().length == 0)) {
+ VpnStatus.logError("Misconfigured provider: no ports defined in " + transport.getType() + " transport JSON for gateway " + ipAddress);
return;
}
- JSONArray ports = ptTransport.getJSONArray(PORTS);
- if (ports.isNull(0)){
- VpnStatus.logError("Misconfigured provider: no ports defined in " + transportType.toString()+ " transport JSON.");
+ if (transportType == OBFS4_HOP &&
+ (transport.getOptions() == null ||
+ (transport.getOptions().getEndpoints() == null && transport.getOptions().getCert() == null) ||
+ transport.getOptions().getPortCount() == 0)) {
+ VpnStatus.logError("Misconfigured provider: missing properties for transport " + transport.getType() + " on gateway " + ipAddress);
return;
}
- String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
- String remote;
+ 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) {
- remote = REMOTE + " " + obfuscationPinningIP + " " + obfuscationPinningPort + newLine;
- route = "route " + obfuscationPinningIP + " 255.255.255.255 net_gateway" + newLine;
- } else {
- remote = REMOTE + " " + ipAddress + " " + ports.getString(0) + newLine;
+ 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;
+ }
+
+ public String getExtraOptions(Transport transport) {
+ if (transport.getTransportType() == OBFS4_HOP) {
+ return "replay-window 65535" + newLine +
+ "ping-restart 300" + newLine +
+ "tun-mtu 48000" + newLine;
+ }
+ return "";
+ }
+
+ public String getRouteString(String ipAddress, Transport transport) {
+ if (useObfuscationPinning) {
+ return "route " + obfuscationPinningIP + " 255.255.255.255 net_gateway" + newLine;
+ }
+ switch (transport.getTransportType()) {
+ case OBFS4:
+ return "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
+ case OBFS4_HOP:
+ if (transport.getOptions().getEndpoints() != null) {
+ StringBuilder routes = new StringBuilder();
+ for (Transport.Endpoint endpoint : transport.getOptions().getEndpoints()) {
+ routes.append("route " + endpoint.getIp() + " 255.255.255.255 net_gateway" + newLine);
+ }
+ return routes.toString();
+ } else {
+ return "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
+ }
+ }
+
+ return "";
+ }
+
+ // While openvpn in TCP mode is required for obfs4, openvpn in UDP mode is required for obfs4-hop
+ 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
+ // configuration, so we assume yes
+ return true;
+ }
+ Transport openvpnTransport = transports.get(OPENVPN);
+ if (openvpnTransport == null) {
+ // the bridge seems to be to be decoupled from the gateway, we can't say if the openvpn gateway
+ // will support this PT and hope the admins configured the gateway correctly
+ return true;
+ }
+
+ String[] protocols = openvpnTransport.getProtocols();
+ if (protocols == null) {
+ VpnStatus.logError("Misconfigured provider: Protocol array is missing for openvpn gateway " + ipAddress);
+ return false;
+ }
+
+ String requiredProtocol = transport.getTransportType() == OBFS4_HOP ? UDP : TCP;
+ for (String protocol : protocols) {
+ if (protocol.equals(requiredProtocol)) {
+ return true;
}
- } else {
- remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine;
}
- stringBuilder.append(route);
- stringBuilder.append(remote);
+
+ VpnStatus.logError("Misconfigured provider: " + transport.getTransportType().toString() + " currently only allows openvpn in " + requiredProtocol + " mode! Skipping config for ip " + ipAddress);
+ return false;
+ }
+
+ private boolean hasPTAllowedProtocol(Transport transport, String ipAddress) {
+ String[] ptProtocols = transport.getProtocols();
+ for (String protocol : ptProtocols) {
+ if (isAllowedProtocol(transport.getTransportType(), protocol)) {
+ return true;
+ }
+ }
+
+ VpnStatus.logError("Misconfigured provider: wrong protocol defined in " + transport.getType() + " transport JSON for gateway " + ipAddress);
+ return false;
}
private String secretsConfiguration() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java
new file mode 100644
index 00000000..3780b7dc
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java
@@ -0,0 +1,57 @@
+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
new file mode 100644
index 00000000..751208ba
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java
@@ -0,0 +1,66 @@
+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
index b96f88ca..0dd81eb8 100644
--- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java
@@ -2,20 +2,15 @@ package se.leap.bitmaskclient.pluggableTransports;
import java.io.Serializable;
+import se.leap.bitmaskclient.base.models.Transport;
+
public class Obfs4Options implements Serializable {
- public String cert;
- public String iatMode;
- public String remoteIP;
- public String remotePort;
- // openvpn is still using tcp, obfs4 is wrapped in kcp, if udp == true
- public boolean udp;
+ public String gatewayIP;
+ public Transport transport;
- public Obfs4Options(String remoteIP, String remotePort, String cert, String iatMode, boolean udp) {
- this.cert = cert;
- this.iatMode = iatMode;
- this.remoteIP = remoteIP;
- this.remotePort = remotePort;
- this.udp = udp;
+ 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
index f6c8837e..9d5ddcf9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java
@@ -1,5 +1,7 @@
package se.leap.bitmaskclient.pluggableTransports;
+import static se.leap.bitmaskclient.base.models.Constants.KCP;
+
import android.util.Log;
import java.util.Observable;
@@ -7,12 +9,12 @@ import java.util.Observer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import client.Client_;
+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 Observer, client.EventLogger {
+public class ObfsVpnClient implements Observer, PtClientInterface {
public static final AtomicInteger SOCKS_PORT = new AtomicInteger(4430);
public static final String SOCKS_IP = "127.0.0.1";
@@ -27,9 +29,17 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
private final client.Client_ obfsVpnClient;
private final Object LOCK = new Object();
- public ObfsVpnClient(Obfs4Options options) {
- obfsVpnClient = new Client_(options.udp, SOCKS_IP+":"+SOCKS_PORT.get(), options.cert);
- obfsVpnClient.setEventLogger(this);
+ 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());
}
/**
@@ -38,6 +48,7 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
*/
public int start() {
synchronized (LOCK) {
+ obfsVpnClient.setEventLogger(this);
Log.d(TAG, "aquired LOCK");
new Thread(this::startSync).start();
waitUntilStarted();
@@ -46,6 +57,7 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
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 {
@@ -88,6 +100,8 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
} catch (Exception e) {
e.printStackTrace();
VpnStatus.logError("[obfsvpn] " + e.getLocalizedMessage());
+ } finally {
+ obfsVpnClient.setEventLogger(null);
}
pendingNetworkErrorHandling.set(false);
Log.d(TAG, "stopping obfsVpnClient releasing LOCK ...");
@@ -98,6 +112,7 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
return obfsVpnClient.isStarted();
}
+ // TODO: register observer!
@Override
public void update(Observable observable, Object arg) {
if (observable instanceof EipStatus) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java
new file mode 100644
index 00000000..945e3d7a
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java
@@ -0,0 +1,18 @@
+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
new file mode 100644
index 00000000..28d19a97
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java
@@ -0,0 +1,9 @@
+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
index f1eb0f1b..102dcf35 100644
--- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java
@@ -42,6 +42,7 @@ public class ShapeshifterClient implements Observer {
private int retry = 0;
private final Handler reconnectHandler;
+ @Deprecated
public class ShapeshifterLogger implements shapeshifter.Logger {
@Override
public void log(String s) {
@@ -71,8 +72,8 @@ public class ShapeshifterClient implements Observer {
private void setup(Obfs4Options options) {
shapeShifter.setSocksAddr(DISPATCHER_IP+":"+DISPATCHER_PORT);
- shapeShifter.setTarget(options.remoteIP+":"+options.remotePort);
- shapeShifter.setCert(options.cert);
+ shapeShifter.setTarget(options.gatewayIP +":"+options.transport.getPorts()[0]);
+ shapeShifter.setCert(options.transport.getOptions().getCert());
}
public void setOptions(Obfs4Options options) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java
index 1ae2a033..775e174a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java
@@ -1,11 +1,28 @@
package se.leap.bitmaskclient.providersetup;
+import static se.leap.bitmaskclient.base.models.Constants.EXT_JSON;
+import static se.leap.bitmaskclient.base.models.Constants.EXT_PEM;
+import static se.leap.bitmaskclient.base.models.Constants.URLS;
+import static se.leap.bitmaskclient.base.models.Provider.DOMAIN;
+import static se.leap.bitmaskclient.base.models.Provider.GEOIP_URL;
+import static se.leap.bitmaskclient.base.models.Provider.MAIN_URL;
+import static se.leap.bitmaskclient.base.models.Provider.MOTD_URL;
+import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP;
+import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP;
+import static se.leap.bitmaskclient.base.utils.FileHelper.createFile;
+import static se.leap.bitmaskclient.base.utils.FileHelper.persistFile;
+import static se.leap.bitmaskclient.base.utils.InputStreamHelper.getInputStreamFrom;
+import static se.leap.bitmaskclient.base.utils.InputStreamHelper.inputStreamToJson;
+import static se.leap.bitmaskclient.base.utils.InputStreamHelper.loadInputStreamAsString;
+
import android.content.res.AssetManager;
import androidx.annotation.VisibleForTesting;
import com.pedrogomez.renderers.AdapteeCollection;
+import org.json.JSONObject;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -19,20 +36,6 @@ import java.util.Set;
import se.leap.bitmaskclient.base.models.Provider;
-import static se.leap.bitmaskclient.base.models.Constants.EXT_JSON;
-import static se.leap.bitmaskclient.base.models.Constants.EXT_PEM;
-import static se.leap.bitmaskclient.base.models.Constants.URLS;
-import static se.leap.bitmaskclient.base.models.Provider.GEOIP_URL;
-import static se.leap.bitmaskclient.base.models.Provider.MAIN_URL;
-import static se.leap.bitmaskclient.base.models.Provider.MOTD_URL;
-import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP;
-import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP;
-import static se.leap.bitmaskclient.base.utils.FileHelper.createFile;
-import static se.leap.bitmaskclient.base.utils.FileHelper.persistFile;
-import static se.leap.bitmaskclient.base.utils.InputStreamHelper.extractKeyFromInputStream;
-import static se.leap.bitmaskclient.base.utils.InputStreamHelper.getInputStreamFrom;
-import static se.leap.bitmaskclient.base.utils.InputStreamHelper.loadInputStreamAsString;
-
/**
* Created by parmegv on 4/12/14.
*/
@@ -96,11 +99,14 @@ public class ProviderManager implements AdapteeCollection<Provider> {
try {
String provider = file.substring(0, file.length() - ".url".length());
InputStream providerFile = assetsManager.open(directory + "/" + file);
- mainUrl = extractKeyFromInputStream(providerFile, MAIN_URL);
- providerIp = extractKeyFromInputStream(providerFile, PROVIDER_IP);
- providerApiIp = extractKeyFromInputStream(providerFile, PROVIDER_API_IP);
- geoipUrl = extractKeyFromInputStream(providerFile, GEOIP_URL);
- motdUrl = extractKeyFromInputStream(providerFile, MOTD_URL);
+ JSONObject providerConfig = inputStreamToJson(providerFile);
+ if (providerConfig != null) {
+ mainUrl = providerConfig.optString(MAIN_URL);
+ providerIp = providerConfig.optString(PROVIDER_IP);
+ providerApiIp = providerConfig.optString(PROVIDER_API_IP);
+ geoipUrl = providerConfig.optString(GEOIP_URL);
+ motdUrl = providerConfig.optString(MOTD_URL);
+ }
certificate = loadInputStreamAsString(assetsManager.open(provider + EXT_PEM));
providerDefinition = loadInputStreamAsString(assetsManager.open(provider + EXT_JSON));
} catch (IOException e) {
@@ -116,20 +122,20 @@ public class ProviderManager implements AdapteeCollection<Provider> {
private void addCustomProviders(File externalFilesDir) {
this.externalFilesDir = externalFilesDir;
customProviders = externalFilesDir != null && externalFilesDir.isDirectory() ?
- providersFromFiles(externalFilesDir.list()) :
+ customProvidersFromFiles(externalFilesDir.list()) :
new HashSet<>();
customProviderURLs = getProviderUrlSetFromProviderSet(customProviders);
}
- private Set<Provider> providersFromFiles(String[] files) {
+ private Set<Provider> customProvidersFromFiles(String[] files) {
Set<Provider> providers = new HashSet<>();
try {
for (String file : files) {
InputStream inputStream = getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file);
- String mainUrl = extractKeyFromInputStream(inputStream, MAIN_URL);
- String providerIp = extractKeyFromInputStream(inputStream, PROVIDER_IP);
- String providerApiIp = extractKeyFromInputStream(inputStream, PROVIDER_API_IP);
- providers.add(new Provider(mainUrl, providerIp, providerApiIp));
+ JSONObject providerConfig = inputStreamToJson(inputStream);
+ String mainUrl = providerConfig.optString(MAIN_URL);
+ String domain = providerConfig.optString(DOMAIN);
+ providers.add(Provider.createCustomProvider(mainUrl, domain));
}
} catch (FileNotFoundException | NullPointerException e) {
e.printStackTrace();
@@ -238,7 +244,7 @@ public class ProviderManager implements AdapteeCollection<Provider> {
*/
private void deleteLegacyCustomProviders() throws IOException, SecurityException {
Set<Provider> persistedCustomProviders = externalFilesDir != null && externalFilesDir.isDirectory() ?
- providersFromFiles(externalFilesDir.list()) : new HashSet<Provider>();
+ customProvidersFromFiles(externalFilesDir.list()) : new HashSet<Provider>();
persistedCustomProviders.removeAll(customProviders);
for (Provider providerToDelete : persistedCustomProviders) {
File providerFile = createFile(externalFilesDir, providerToDelete.getName() + EXT_JSON);
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java
index 90ebfb4d..eb9898b8 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java
@@ -114,7 +114,7 @@ public abstract class ProviderListBaseActivity extends ProviderSetupBaseActivity
}
public void showAndSelectProvider(String newURL) {
- provider = new Provider(newURL, null, null);
+ provider = new Provider(newURL);
autoSelectProvider();
}