bare minimum direct connections

This commit is contained in:
Daniel Gultsch 2019-09-01 15:06:59 +02:00
parent 783ed53d3a
commit 1c413edf06
6 changed files with 178 additions and 7 deletions

View File

@ -39,6 +39,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.URL; import java.net.URL;
import java.security.DigestOutputStream; 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) { public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false; return false;

View File

@ -20,6 +20,9 @@ public class SocksSocketFactory {
proxyOs.write(new byte[]{0x05, 0x01, 0x00}); proxyOs.write(new byte[]{0x05, 0x01, 0x00});
byte[] response = new byte[2]; byte[] response = new byte[2];
proxyIs.read(response); proxyIs.read(response);
if (response[0] != 0x05 || response[1] != 0x00) {
throw new SocksConnectionException();
}
byte[] dest = destination.getBytes(); byte[] dest = destination.getBytes();
ByteBuffer request = ByteBuffer.allocate(7 + dest.length); ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); 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 { public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
Socket socket = new Socket(); Socket socket = new Socket();
try { try {

View File

@ -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;
}
}

View File

@ -127,7 +127,9 @@ public class JingleCandidate {
element.setAttribute("cid", this.getCid()); element.setAttribute("cid", this.getCid());
element.setAttribute("host", this.getHost()); element.setAttribute("host", this.getHost());
element.setAttribute("port", Integer.toString(this.getPort())); 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())); element.setAttribute("priority", Integer.toString(this.getPriority()));
if (this.getType() == TYPE_DIRECT) { if (this.getType() == TYPE_DIRECT) {
element.setAttribute("type", "direct"); element.setAttribute("type", "direct");

View File

@ -11,7 +11,6 @@ import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -303,9 +302,15 @@ public class JingleConnection implements Transferable {
this.transportId = this.mJingleConnectionManager.nextRandomId(); this.transportId = this.mJingleConnectionManager.nextRandomId();
if (this.initialTransport == Transport.IBB) { if (this.initialTransport == Transport.IBB) {
this.sendInitRequest(); this.sendInitRequest();
} else if (this.candidates.size() > 0) {
this.sendInitRequest(); //TODO we will never get here? Can probably be removed
} else { } 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) -> { this.mJingleConnectionManager.getPrimaryCandidate(account, (success, candidate) -> {
if (success) { if (success) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate); final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate);
@ -690,7 +695,7 @@ public class JingleConnection implements Transferable {
onProxyActivated.failed(); onProxyActivated.failed();
return true; return true;
} else if (content.socks5transport().hasChild("candidate-error")) { } 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; this.receivedCandidate = true;
if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) { if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) {
this.connect(); this.connect();
@ -728,7 +733,7 @@ public class JingleConnection implements Transferable {
final JingleSocks5Transport connection = chooseConnection(); final JingleSocks5Transport connection = chooseConnection();
this.transport = connection; this.transport = connection;
if (connection == null) { 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(); this.disconnectSocks5Connections();
if (initiating()) { if (initiating()) {
this.sendFallbackToIbb(); this.sendFallbackToIbb();
@ -755,6 +760,7 @@ public class JingleConnection implements Transferable {
.setContent(this.getCounterPart().toString()); .setContent(this.getCounterPart().toString());
mXmppConnectionService.sendIqPacket(account, activation, (account, response) -> { mXmppConnectionService.sendIqPacket(account, activation, (account, response) -> {
if (response.getType() != IqPacket.TYPE.RESULT) { if (response.getType() != IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + response.toString());
onProxyActivated.failed(); onProxyActivated.failed();
} else { } else {
onProxyActivated.success(); onProxyActivated.success();
@ -1052,7 +1058,7 @@ public class JingleConnection implements Transferable {
} }
private void sendCandidateError() { 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"); JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName); Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId); content.setTransportId(this.transportId);

View File

@ -6,9 +6,12 @@ import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -29,6 +32,7 @@ public class JingleSocks5Transport extends JingleTransport {
private InputStream inputStream; private InputStream inputStream;
private boolean isEstablished = false; private boolean isEstablished = false;
private boolean activated = false; private boolean activated = false;
private ServerSocket serverSocket;
private Socket socket; private Socket socket;
JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) { JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) {
@ -56,6 +60,88 @@ public class JingleSocks5Transport extends JingleTransport {
} }
messageDigest.reset(); messageDigest.reset();
this.destination = CryptoHelper.bytesToHex(messageDigest.digest(destBuilder.toString().getBytes())); 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) { public void connect(final OnTransportConnected callback) {
@ -71,7 +157,9 @@ public class JingleSocks5Transport extends JingleTransport {
} }
inputStream = socket.getInputStream(); inputStream = socket.getInputStream();
outputStream = socket.getOutputStream(); outputStream = socket.getOutputStream();
socket.setSoTimeout(5000);
SocksSocketFactory.createSocksConnection(socket, destination, 0); SocksSocketFactory.createSocksConnection(socket, destination, 0);
socket.setSoTimeout(0);
isEstablished = true; isEstablished = true;
callback.established(); callback.established();
} catch (IOException e) { } catch (IOException e) {
@ -182,6 +270,7 @@ public class JingleSocks5Transport extends JingleTransport {
FileBackend.close(inputStream); FileBackend.close(inputStream);
FileBackend.close(outputStream); FileBackend.close(outputStream);
FileBackend.close(socket); FileBackend.close(socket);
FileBackend.close(serverSocket);
} }
public boolean isEstablished() { public boolean isEstablished() {