bare minimum direct connections
This commit is contained in:
parent
783ed53d3a
commit
1c413edf06
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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("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");
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue