Detailed changes
@@ -39,6 +39,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.security.DigestOutputStream;
@@ -359,6 +360,15 @@ public class FileBackend {
}
}
+ public static void close(final ServerSocket socket) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false;
@@ -20,6 +20,9 @@ public class SocksSocketFactory {
proxyOs.write(new byte[]{0x05, 0x01, 0x00});
byte[] response = new byte[2];
proxyIs.read(response);
+ if (response[0] != 0x05 || response[1] != 0x00) {
+ throw new SocksConnectionException();
+ }
byte[] dest = destination.getBytes();
ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
@@ -34,6 +37,15 @@ public class SocksSocketFactory {
}
}
+ public static boolean contains(byte needle, byte[] haystack) {
+ for(byte hay : haystack) {
+ if (hay == needle) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
Socket socket = new Socket();
try {
@@ -0,0 +1,52 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.UUID;
+
+import rocks.xmpp.addr.Jid;
+
+public class DirectConnectionUtils {
+
+ private static List<InetAddress> getLocalAddresses() {
+ final List<InetAddress> addresses = new ArrayList<>();
+ final Enumeration<NetworkInterface> interfaces;
+ try {
+ interfaces = NetworkInterface.getNetworkInterfaces();
+ } catch (SocketException e) {
+ return addresses;
+ }
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface networkInterface = interfaces.nextElement();
+ final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
+ while (inetAddressEnumeration.hasMoreElements()) {
+ final InetAddress inetAddress = inetAddressEnumeration.nextElement();
+ if (!inetAddress.isLoopbackAddress()) {
+ addresses.add(inetAddress);
+ }
+ }
+ }
+ return addresses;
+ }
+
+ public static List<JingleCandidate> getLocalCandidates(Jid jid) {
+ SecureRandom random = new SecureRandom();
+ ArrayList<JingleCandidate> candidates = new ArrayList<>();
+ for (InetAddress inetAddress : getLocalAddresses()) {
+ final JingleCandidate candidate = new JingleCandidate(UUID.randomUUID().toString(), true);
+ candidate.setHost(inetAddress.getHostAddress());
+ candidate.setPort(random.nextInt(60000) + 1024);
+ candidate.setType(JingleCandidate.TYPE_DIRECT);
+ candidate.setJid(jid);
+ candidate.setPriority(8257536 + candidates.size());
+ candidates.add(candidate);
+ }
+ return candidates;
+ }
+
+}
@@ -127,7 +127,9 @@ public class JingleCandidate {
element.setAttribute("cid", this.getCid());
element.setAttribute("host", this.getHost());
element.setAttribute("port", Integer.toString(this.getPort()));
- element.setAttribute("jid", this.getJid().toString());
+ if (jid != null) {
+ element.setAttribute("jid", jid.toEscapedString());
+ }
element.setAttribute("priority", Integer.toString(this.getPriority()));
if (this.getType() == TYPE_DIRECT) {
element.setAttribute("type", "direct");
@@ -11,7 +11,6 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
@@ -303,9 +302,15 @@ public class JingleConnection implements Transferable {
this.transportId = this.mJingleConnectionManager.nextRandomId();
if (this.initialTransport == Transport.IBB) {
this.sendInitRequest();
- } else if (this.candidates.size() > 0) {
- this.sendInitRequest(); //TODO we will never get here? Can probably be removed
} else {
+
+ final List<JingleCandidate> directCandidates = DirectConnectionUtils.getLocalCandidates(account.getJid());
+ for (JingleCandidate directCandidate : directCandidates) {
+ final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, directCandidate);
+ connections.put(directCandidate.getCid(), socksConnection);
+ candidates.add(directCandidate);
+ }
+
this.mJingleConnectionManager.getPrimaryCandidate(account, (success, candidate) -> {
if (success) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate);
@@ -690,7 +695,7 @@ public class JingleConnection implements Transferable {
onProxyActivated.failed();
return true;
} else if (content.socks5transport().hasChild("candidate-error")) {
- Log.d(Config.LOGTAG, "received candidate error");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received candidate error");
this.receivedCandidate = true;
if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) {
this.connect();
@@ -728,7 +733,7 @@ public class JingleConnection implements Transferable {
final JingleSocks5Transport connection = chooseConnection();
this.transport = connection;
if (connection == null) {
- Log.d(Config.LOGTAG, "could not find suitable candidate");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid()+": could not find suitable candidate");
this.disconnectSocks5Connections();
if (initiating()) {
this.sendFallbackToIbb();
@@ -755,6 +760,7 @@ public class JingleConnection implements Transferable {
.setContent(this.getCounterPart().toString());
mXmppConnectionService.sendIqPacket(account, activation, (account, response) -> {
if (response.getType() != IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + response.toString());
onProxyActivated.failed();
} else {
onProxyActivated.success();
@@ -1052,7 +1058,7 @@ public class JingleConnection implements Transferable {
}
private void sendCandidateError() {
- Log.d(Config.LOGTAG, "sending candidate error");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending candidate error");
JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId);
@@ -6,9 +6,12 @@ import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
+import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -29,6 +32,7 @@ public class JingleSocks5Transport extends JingleTransport {
private InputStream inputStream;
private boolean isEstablished = false;
private boolean activated = false;
+ private ServerSocket serverSocket;
private Socket socket;
JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) {
@@ -56,6 +60,88 @@ public class JingleSocks5Transport extends JingleTransport {
}
messageDigest.reset();
this.destination = CryptoHelper.bytesToHex(messageDigest.digest(destBuilder.toString().getBytes()));
+ if (candidate.isOurs() && candidate.getType() == JingleCandidate.TYPE_DIRECT) {
+ createServerSocket();
+ }
+ }
+
+ private void createServerSocket() {
+ try {
+ serverSocket = new ServerSocket();
+ serverSocket.bind(new InetSocketAddress(InetAddress.getByName(candidate.getHost()), candidate.getPort()));
+ new Thread(() -> {
+ try {
+ final Socket socket = serverSocket.accept();
+ new Thread(() -> {
+ try {
+ acceptIncomingSocketConnection(socket);
+ } catch (IOException e) {
+ Log.d(Config.LOGTAG,"unable to read from socket",e);
+
+ }
+ }).start();
+ } catch (IOException e) {
+ if (!serverSocket.isClosed()) {
+ Log.d(Config.LOGTAG, "unable to accept socket", e);
+ }
+ }
+ }).start();
+ } catch (IOException e) {
+ Log.d(Config.LOGTAG,"unable to bind server socket ",e);
+ }
+ }
+
+ private void acceptIncomingSocketConnection(Socket socket) throws IOException {
+ Log.d(Config.LOGTAG, "accepted connection from " + socket.getInetAddress().getHostAddress());
+ byte[] authBegin = new byte[2];
+ InputStream inputStream = socket.getInputStream();
+ OutputStream outputStream = socket.getOutputStream();
+ inputStream.read(authBegin);
+ if (authBegin[0] != 0x5) {
+ socket.close();
+ }
+ short methodCount = authBegin[1];
+ byte[] methods = new byte[methodCount];
+ inputStream.read(methods);
+ if (SocksSocketFactory.contains((byte) 0x00, methods)) {
+ outputStream.write(new byte[]{0x05,0x00});
+ } else {
+ outputStream.write(new byte[]{0x05,(byte) 0xff});
+ }
+ byte[] connectCommand = new byte[4];
+ inputStream.read(connectCommand);
+ if (connectCommand[0] == 0x05 && connectCommand[1] == 0x01 && connectCommand[3] == 0x03) {
+ int destinationCount = inputStream.read();
+ byte[] destination = new byte[destinationCount];
+ inputStream.read(destination);
+ int port = inputStream.read();
+ final String receivedDestination = new String(destination);
+ Log.d(Config.LOGTAG, "received destination " + receivedDestination + ":" + port + " - expected " + this.destination);
+ final ByteBuffer response = ByteBuffer.allocate(7 + destination.length);
+ final byte[] responseHeader;
+ final boolean success;
+ if (receivedDestination.equals(this.destination)) {
+ responseHeader = new byte[]{0x05, 0x00, 0x00, 0x03};
+ success = true;
+ } else {
+ responseHeader = new byte[]{0x05, 0x04, 0x00, 0x03};
+ success = false;
+ }
+ response.put(responseHeader);
+ response.put((byte) destination.length);
+ response.put(destination);
+ response.putShort((short) port);
+ outputStream.write(response.array());
+ outputStream.flush();
+ if (success) {
+ this.socket = socket;
+ this.inputStream = inputStream;
+ this.outputStream = outputStream;
+ this.isEstablished = true;
+ }
+ } else {
+ socket.close();
+ }
}
public void connect(final OnTransportConnected callback) {
@@ -71,7 +157,9 @@ public class JingleSocks5Transport extends JingleTransport {
}
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
+ socket.setSoTimeout(5000);
SocksSocketFactory.createSocksConnection(socket, destination, 0);
+ socket.setSoTimeout(0);
isEstablished = true;
callback.established();
} catch (IOException e) {
@@ -182,6 +270,7 @@ public class JingleSocks5Transport extends JingleTransport {
FileBackend.close(inputStream);
FileBackend.close(outputStream);
FileBackend.close(socket);
+ FileBackend.close(serverSocket);
}
public boolean isEstablished() {