Detailed changes
@@ -1,20 +1,30 @@
package eu.siacs.conversations.utils;
import com.google.common.net.InetAddresses;
-
+import java.net.InetAddress;
import java.util.regex.Pattern;
public class IP {
- private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
- private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
-
- public static boolean matches(String server) {
- return server != null && (
- PATTERN_IPV4.matcher(server).matches()
+ private static final Pattern PATTERN_IPV4 =
+ Pattern.compile(
+ "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)"
+ + " ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_6HEX4DEC =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEXCOMPRESSED =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ private static final Pattern PATTERN_IPV6 =
+ Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+
+ public static boolean matches(final String server) {
+ return server != null
+ && (PATTERN_IPV4.matcher(server).matches()
|| PATTERN_IPV6.matcher(server).matches()
|| PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
|| PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
@@ -22,8 +32,14 @@ public class IP {
}
public static String wrapIPv6(final String host) {
- if (matches(host)) {
- return String.format("[%s]", host);
+ if (InetAddresses.isInetAddress(host)) {
+ final InetAddress inetAddress;
+ try {
+ inetAddress = InetAddresses.forString(host);
+ } catch (final IllegalArgumentException e) {
+ return host;
+ }
+ return InetAddresses.toUriString(inetAddress);
} else {
return host;
}
@@ -31,12 +47,11 @@ public class IP {
public static String unwrapIPv6(final String host) {
if (host.length() > 2 && host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') {
- final String ip = host.substring(1,host.length() -1);
+ final String ip = host.substring(1, host.length() - 1);
if (InetAddresses.isInetAddress(ip)) {
return ip;
}
}
return host;
}
-
}
@@ -3,9 +3,7 @@ package eu.siacs.conversations.utils;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
-
import androidx.annotation.NonNull;
-
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
@@ -18,26 +16,11 @@ import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-
import de.gultsch.minidns.AndroidDNSClient;
import de.gultsch.minidns.ResolverResult;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.Conversations;
import eu.siacs.conversations.xmpp.Jid;
-
-import org.minidns.dnsmessage.Question;
-import org.minidns.dnsname.DnsName;
-import org.minidns.dnsname.InvalidDnsNameException;
-import org.minidns.dnsqueryresult.DnsQueryResult;
-import org.minidns.record.A;
-import org.minidns.record.AAAA;
-import org.minidns.record.CNAME;
-import org.minidns.record.Data;
-import org.minidns.record.InternetAddressRR;
-import org.minidns.record.Record;
-import org.minidns.record.SRV;
-
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -49,6 +32,17 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import org.minidns.dnsmessage.Question;
+import org.minidns.dnsname.DnsName;
+import org.minidns.dnsname.InvalidDnsNameException;
+import org.minidns.dnsqueryresult.DnsQueryResult;
+import org.minidns.record.A;
+import org.minidns.record.AAAA;
+import org.minidns.record.CNAME;
+import org.minidns.record.Data;
+import org.minidns.record.InternetAddressRR;
+import org.minidns.record.Record;
+import org.minidns.record.SRV;
public class Resolver {
@@ -156,8 +150,8 @@ public class Resolver {
if (IP.matches(domain)) {
final InetAddress inetAddress;
try {
- inetAddress = InetAddress.getByName(domain);
- } catch (final UnknownHostException e) {
+ inetAddress = InetAddresses.forString(domain);
+ } catch (final IllegalArgumentException e) {
return Collections.emptyList();
}
return Result.createWithDefaultPorts(null, inetAddress);
@@ -442,6 +436,10 @@ public class Resolver {
.toString();
}
+ public String asDestination() {
+ return ip != null ? InetAddresses.toAddrString(ip) : hostname.toString();
+ }
+
public ContentValues toContentValues() {
final ContentValues contentValues = new ContentValues();
contentValues.put(IP, ip == null ? null : ip.getAddress());
@@ -1,37 +1,56 @@
package eu.siacs.conversations.utils;
import com.google.common.io.ByteStreams;
-
+import com.google.common.net.InetAddresses;
+import eu.siacs.conversations.Config;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
-import eu.siacs.conversations.Config;
-
public class SocksSocketFactory {
- private static final byte[] LOCALHOST = new byte[]{127, 0, 0, 1};
+ private static final byte[] LOCALHOST = new byte[] {127, 0, 0, 1};
- public static void createSocksConnection(final Socket socket, final String destination, final int port) throws IOException {
- //TODO use different Socks Addr Type if destination is IP or IPv6
+ public static void createSocksConnection(
+ final Socket socket, final String destination, final int port) throws IOException {
final InputStream proxyIs = socket.getInputStream();
final OutputStream proxyOs = socket.getOutputStream();
- proxyOs.write(new byte[]{0x05, 0x01, 0x00});
+ proxyOs.write(new byte[] {0x05, 0x01, 0x00});
proxyOs.flush();
final byte[] handshake = new byte[2];
ByteStreams.readFully(proxyIs, handshake);
if (handshake[0] != 0x05 || handshake[1] != 0x00) {
throw new SocksConnectionException("Socks 5 handshake failed");
}
- final byte[] dest = destination.getBytes();
- final ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
- request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
- request.put((byte) dest.length);
- request.put(dest);
+ final byte type;
+ final ByteBuffer request;
+ if (InetAddresses.isInetAddress(destination)) {
+ final var ip = InetAddresses.forString(destination);
+ final var dest = ip.getAddress();
+ request = ByteBuffer.allocate(6 + dest.length);
+ if (ip instanceof Inet4Address) {
+ type = 0x01;
+ } else if (ip instanceof Inet6Address) {
+ type = 0x04;
+ } else {
+ throw new IOException("IP address is of unknown subtype");
+ }
+ request.put(new byte[] {0x05, 0x01, 0x00, type});
+ request.put(dest);
+ } else {
+ final byte[] dest = destination.getBytes();
+ type = 0x03;
+ request = ByteBuffer.allocate(7 + dest.length);
+ request.put(new byte[] {0x05, 0x01, 0x00, type});
+ request.put((byte) dest.length);
+ request.put(dest);
+ }
request.putShort((short) port);
proxyOs.write(request.array());
proxyOs.flush();
@@ -42,13 +61,16 @@ public class SocksSocketFactory {
throw new IOException(String.format("Unknown Socks version %02X ", ver));
}
final byte status = response[1];
- final byte bndAddrType = response[3];
- final byte[] bndDestination = readDestination(bndAddrType, proxyIs);
+ final byte bndAddressType = response[3];
+ final byte[] bndDestination = readDestination(bndAddressType, proxyIs);
final byte[] bndPort = new byte[2];
- if (bndAddrType == 0x03) {
+ if (bndAddressType == 0x03) {
final String receivedDestination = new String(bndDestination);
if (!receivedDestination.equalsIgnoreCase(destination)) {
- throw new IOException(String.format("Destination mismatch. Received %s Expected %s", receivedDestination, destination));
+ throw new IOException(
+ String.format(
+ "Destination mismatch. Received %s Expected %s",
+ receivedDestination, destination));
}
}
ByteStreams.readFully(proxyIs, bndPort);
@@ -63,7 +85,8 @@ public class SocksSocketFactory {
}
}
- private static byte[] readDestination(final byte type, final InputStream inputStream) throws IOException {
+ private static byte[] readDestination(final byte type, final InputStream inputStream)
+ throws IOException {
final byte[] bndDestination;
if (type == 0x01) {
bndDestination = new byte[4];
@@ -88,7 +111,8 @@ public class SocksSocketFactory {
return false;
}
- private static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
+ private static Socket createSocket(InetSocketAddress address, String destination, int port)
+ throws IOException {
Socket socket = new Socket();
try {
socket.connect(address, Config.CONNECT_TIMEOUT * 1000);
@@ -100,7 +124,10 @@ public class SocksSocketFactory {
}
public static Socket createSocketOverTor(String destination, int port) throws IOException {
- return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port);
+ return createSocket(
+ new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050),
+ destination,
+ port);
}
private static class SocksConnectionException extends IOException {
@@ -109,9 +136,7 @@ public class SocksSocketFactory {
}
}
- public static class SocksProxyNotFoundException extends IOException {
-
- }
+ public static class SocksProxyNotFoundException extends IOException {}
public static class HostNotFoundException extends SocksConnectionException {
HostNotFoundException(String message) {
@@ -19,6 +19,7 @@ import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
@@ -290,9 +291,9 @@ public class XmppConnection implements Runnable {
this.quickStartInProgress = false;
this.isBound = false;
this.attempt++;
- this.verifiedHostname =
- null; // will be set if user entered hostname is being used or hostname was verified
- // with dnssec
+ this.currentResolverResult = null;
+ // will be set if user entered hostname is being used or hostname was verified with dnssec
+ this.verifiedHostname = null;
try {
Socket localSocket;
shouldAuthenticate = !account.isOptionSet(Account.OPTION_REGISTER);
@@ -300,33 +301,53 @@ public class XmppConnection implements Runnable {
final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
if (useTor) {
- String destination;
- if (account.getHostname().isEmpty() || account.isOnion()) {
- destination = account.getServer();
+ final var seeOtherHost = this.seeOtherHostResolverResult;
+ final Resolver.Result resume = streamId == null ? null : streamId.location;
+ final Resolver.Result viaTor;
+ if (account.isOnion()) {
+ // for .onion JIDs we always connect to the onion address no matter what
+ viaTor =
+ Iterables.getOnlyElement(
+ Resolver.fromHardCoded(
+ account.getServer(), Resolver.XMPP_PORT_STARTTLS));
+ } else if (resume != null) {
+ viaTor = resume;
+ } else if (seeOtherHost != null) {
+ viaTor = seeOtherHost;
+ } else if (account.getHostname().isEmpty()) {
+ viaTor =
+ Iterables.getOnlyElement(
+ Resolver.fromHardCoded(
+ account.getServer(), Resolver.XMPP_PORT_STARTTLS));
} else {
- destination = account.getHostname();
- this.verifiedHostname = destination;
+ viaTor =
+ Iterables.getOnlyElement(
+ Resolver.fromHardCoded(
+ account.getHostname(), account.getPort()));
+ this.verifiedHostname = account.getHostname();
}
- final int port = account.getPort();
- final boolean directTls = Resolver.useDirectTls(port);
-
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": connect to "
- + destination
+ + viaTor.asDestination()
+ " via Tor. directTls="
- + directTls);
- localSocket = SocksSocketFactory.createSocketOverTor(destination, port);
+ + viaTor.isDirectTls());
+ localSocket =
+ SocksSocketFactory.createSocketOverTor(
+ viaTor.asDestination(), viaTor.getPort());
- if (directTls) {
+ if (viaTor.isDirectTls()) {
localSocket = upgradeSocketToTls(localSocket);
features.encryptionEnabled = true;
}
try {
- startXmpp(localSocket);
+ if (startXmpp(localSocket)) {
+ this.currentResolverResult = viaTor;
+ this.seeOtherHostResolverResult = null;
+ }
} catch (final InterruptedException e) {
Log.d(
Config.LOGTAG,
@@ -442,9 +463,8 @@ public class XmppConnection implements Runnable {
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) {
- localSocket.setSoTimeout(
- 0); // reset to 0; once the connection is established we donβt
- // want this
+ // reset to 0; once the connection is established we don't want this
+ localSocket.setSoTimeout(0);
if (!hardcoded && !result.equals(storedBackupResult)) {
mXmppConnectionService.databaseBackend.saveResolverResult(
domain, result);