diff options
author | cyberta <cyberta@riseup.net> | 2023-04-17 19:23:25 +0000 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2023-04-17 19:23:25 +0000 |
commit | 821cac0b60b85d0956cbe97de84766f660b907a6 (patch) | |
tree | 386f1736bdd93404c96f22fb4d522b87ae269746 /app/src/main/java/se/leap/bitmaskclient/eip | |
parent | a4deca391ce064510002e24ba9f18d965f0dee59 (diff) | |
parent | b613b0cd4dc44b48859add528a4fb83b4a17c3fa (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/eip')
5 files changed, 206 insertions, 164 deletions
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() { |