diff options
author | cyBerta <cyberta@riseup.net> | 2023-04-06 01:08:05 +0200 |
---|---|---|
committer | cyBerta <cyberta@riseup.net> | 2023-04-13 16:47:13 +0200 |
commit | 939901a89abb169648423473056260335d3af639 (patch) | |
tree | 7e49ac928013cdf5e7979c3a9384fb06f0b0f192 /app/src/main/java/se/leap | |
parent | f6017ab12d0c472ab4f22e81d9a768ad2510b134 (diff) |
first pass on obfs4-hop pt integration
Diffstat (limited to 'app/src/main/java/se/leap')
11 files changed, 431 insertions, 144 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 62fb1fd2..57653263 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 @@ -19,6 +19,7 @@ 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_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; @@ -184,13 +185,13 @@ public final class Provider implements Parcelable { public boolean supportsPluggableTransports() { if (useObfsVpn()) { - return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP), new Pair<>(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 Pair[]{new Pair<>(OBFS4, TCP)}); } public boolean supportsExperimentalPluggableTransports() { - return supportsTransports(new Pair[]{new Pair<>(OBFS4, KCP)}); + return supportsTransports(new Pair[]{new Pair<>(OBFS4, KCP), new Pair<>(OBFS4_HOP, TCP), new Pair<>(OBFS4_HOP, KCP)}); } private boolean supportsTransports(Pair<TransportType, TransportProtocol>[] transportTypes) { 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..7d9b61a7 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,65 @@ 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 = iatMode; + this.endpoints = endpoints; + this.portSeed = portSeed; + this.portCount = portCount; + this.experimental = experimental; + } + + @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 +129,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/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 141f6274..7229f7ff 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -17,19 +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_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; @@ -54,14 +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; + HashMap<TransportType, Transport> transports = new HashMap<>(); private final int apiVersion; private final boolean preferUDP; private final boolean experimentalTransports; @@ -113,19 +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; - break; - } + 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"); } } @@ -141,11 +138,15 @@ public class VpnConfigGenerator { e.printStackTrace(); } } - if (supportsObfs4()) { - try { - profiles.put(OBFS4, createProfile(OBFS4)); - } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { - e.printStackTrace(); + if (apiVersion >= 3) { + for (TransportType transportType : transports.keySet()) { + if (transportType.isPluggableTransport()) { + try { + profiles.put(transportType, createProfile(transportType)); + } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { + e.printStackTrace(); + } + } } } if (profiles.isEmpty()) { @@ -155,10 +156,9 @@ public class VpnConfigGenerator { } private boolean supportsOpenvpn() { - return !useObfuscationPinning && !gatewayConfiguration(OPENVPN).isEmpty(); - } - private boolean supportsObfs4(){ - return obfs4Transport != null || useObfuscationPinning; + return !useObfuscationPinning && + ((apiVersion >= 3 && transports.containsKey(OPENVPN)) || + (apiVersion < 3 && !gatewayConfiguration(OPENVPN).isEmpty())); } private String getConfigurationString(TransportType transportType) { @@ -176,11 +176,8 @@ public class VpnConfigGenerator { String configuration = getConfigurationString(transportType); ConfigParser icsOpenvpnConfigParser = new ConfigParser(); icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); - if (transportType == OBFS4) { - JSONArray protocols = obfs4Transport.getJSONArray(PROTOCOLS); - // FIXME: currently only one protocol per obfs4 bridge is supported in this client - String protocol = protocols.optString(0); - icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(obfs4Transport, protocol.equalsIgnoreCase("kcp"))); + if (transportType == OBFS4 || transportType == OBFS4_HOP) { + icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(transportType)); } VpnProfile profile = icsOpenvpnConfigParser.convertProfile(transportType); @@ -192,21 +189,19 @@ public class VpnConfigGenerator { return profile; } - 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() { @@ -254,8 +249,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) { @@ -271,11 +265,11 @@ public class VpnConfigGenerator { return remotes; } - 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)); } } @@ -294,19 +288,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)) { @@ -320,10 +311,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); @@ -333,31 +322,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: - return "tcp".equals(protocol) || "kcp".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 @@ -381,63 +357,97 @@ 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) { + 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().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)); + } + + public String getRemoteString(String ipAddress, Transport transport) { if (useObfsVpn()) { if (useObfuscationPinning) { - remote = REMOTE + " " + obfuscationPinningIP + " " + obfuscationPinningPort + " tcp" + newLine; - route = "route " + obfuscationPinningIP + " 255.255.255.255 net_gateway" + newLine; - } else { - remote = REMOTE + " " + ipAddress + " " + ports.getString(0) + " tcp" + newLine; + return REMOTE + " " + obfuscationPinningIP + " " + obfuscationPinningPort + " tcp" + newLine; } - } else { - remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " 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 getRouteString(String ipAddress, Transport transport) { + if (useObfuscationPinning) { + return "route " + obfuscationPinningIP + " 255.255.255.255 net_gateway" + newLine; + } + if (transport.getTransportType() == OBFS4) { + return "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; + } + return newLine; + } + + // 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; } - stringBuilder.append(route); - stringBuilder.append(remote); + Transport openvpnTransport = transports.get(OPENVPN); + if (openvpnTransport == null) { + return false; + } + + 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; + } + } + + 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..e885166a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java @@ -0,0 +1,49 @@ +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, + Transport transport, + int minHopSeconds, + int hopJitter) { + this.kcp = kcp; + this.proxyAddr = proxyAddr; + Transport.Endpoint[] endpoints = transport.getOptions().getEndpoints(); + 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..1b19213f --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java @@ -0,0 +1,72 @@ +package se.leap.bitmaskclient.pluggableTransports; + +import static de.blinkt.openvpn.core.connection.Connection.TransportProtocol.KCP; + +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]); + + if (options.transport.getOptions().getEndpoints() == null) { + throw new IllegalStateException("No Endpoints for hopping pt detected!"); + } + + HoppingConfig hoppingConfig = new HoppingConfig(kcp,IP+":"+PORT, options.transport, 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) { |