2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-29 21:27:40 +00:00

Further simplified lanbackend

This commit is contained in:
Albert Vaca 2016-06-20 14:26:49 +02:00
parent d3ab18b721
commit 4fc6ca8d4f
3 changed files with 98 additions and 138 deletions

View File

@ -21,7 +21,6 @@
package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import org.json.JSONObject;
@ -45,8 +44,6 @@ import java.net.SocketTimeoutException;
import java.nio.channels.NotYetConnectedException;
import java.security.PublicKey;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
public class LanLink extends BaseLink {
@ -62,6 +59,10 @@ public class LanLink extends BaseLink {
private Socket socket = null;
interface SocketClosedCallback {
void socketClosed(LanLink brokenLink, boolean linkHasAnotherSocket);
};
@Override
public void disconnect() {
@ -81,7 +82,7 @@ public class LanLink extends BaseLink {
}
//Returns the old socket
public Socket reset(final Socket newSocket, ConnectionStarted connectionSource, final LanLinkProvider linkProvider) throws IOException {
public Socket reset(final Socket newSocket, ConnectionStarted connectionSource, final SocketClosedCallback callback) throws IOException {
Socket oldSocket = socket;
socket = newSocket;
@ -92,8 +93,8 @@ public class LanLink extends BaseLink {
oldSocket.close(); //This should cancel the readThread
}
Log.e("LanLink", "Start listening");
//Start listening
//Log.e("LanLink", "Start listening");
//Create a thread to take care of incoming data for the new socket
new Thread(new Runnable() {
@Override
public void run() {
@ -107,16 +108,18 @@ public class LanLink extends BaseLink {
continue;
}
if (packet == null) {
throw new IOException("Read null");
throw new IOException("End of stream");
}
if (packet.isEmpty()) {
continue;
}
if (packet.isEmpty()) continue;
NetworkPackage np = NetworkPackage.unserialize(packet);
injectNetworkPackage(np);
receivedNetworkPackage(np);
}
} catch (Exception e) {
Log.e("LanLink", "Socket closed " + newSocket.hashCode() + " reason: " + e.getMessage());
Log.i("LanLink", "Socket closed: " + newSocket.hashCode() + ". Reason: " + e.getMessage());
boolean thereIsaANewSocket = (newSocket != socket);
linkProvider.socketClosed(newSocket, thereIsaANewSocket);
callback.socketClosed(LanLink.this, thereIsaANewSocket);
}
}
}).start();
@ -153,7 +156,7 @@ public class LanLink extends BaseLink {
//Prepare socket for the payload
final ServerSocket server;
if (np.hasPayload()) {
server = openTcpSocketOnFreePort(context, getDeviceId());
server = LanLinkProvider.openServerSocketOnFreePort(LanLinkProvider.PAYLOAD_TRANSFER_MIN_PORT);
JSONObject payloadTransferInfo = new JSONObject();
payloadTransferInfo.put("port", server.getLocalPort());
np.setPayloadTransferInfo(payloadTransferInfo);
@ -182,38 +185,48 @@ public class LanLink extends BaseLink {
//Send payload
if (server != null) {
OutputStream socket = null;
Socket payloadSocket = null;
OutputStream outputStream = null;
InputStream inputStream = null;
try {
//Wait a maximum of 10 seconds for the other end to establish a connection with our socket, close it afterwards
server.setSoTimeout(10*1000);
socket = server.accept().getOutputStream();
payloadSocket = server.accept();
//Convert to SSL if needed
if (socket instanceof SSLSocket) {
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, false);
}
outputStream = payloadSocket.getOutputStream();
inputStream = np.getPayload();
Log.i("KDE/LanLink", "Beginning to send payload");
byte[] buffer = new byte[4096];
int bytesRead;
long progress = 0;
InputStream stream = np.getPayload();
while ((bytesRead = stream.read(buffer)) != -1) {
while ((bytesRead = inputStream.read(buffer)) != -1) {
//Log.e("ok",""+bytesRead);
progress += bytesRead;
socket.write(buffer, 0, bytesRead);
outputStream.write(buffer, 0, bytesRead);
if (np.getPayloadSize() > 0) {
callback.sendProgress((int)(progress / np.getPayloadSize()));
}
}
socket.flush();
stream.close();
outputStream.flush();
outputStream.close();
Log.i("KDE/LanLink", "Finished sending payload ("+progress+" bytes written)");
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/sendPackage", "Exception: "+e);
callback.sendFailure(e);
return;
} finally {
if (socket != null) {
try { socket.close(); } catch (Exception e) { }
}
try { server.close(); } catch (Exception e) { }
try { payloadSocket.close(); } catch (Exception e) { }
try { inputStream.close(); } catch (Exception e) { }
try { outputStream.close(); } catch (Exception e) { }
}
}
@ -244,7 +257,7 @@ public class LanLink extends BaseLink {
sendPackageInternal(np, callback, key);
}
public void injectNetworkPackage(NetworkPackage np) {
private void receivedNetworkPackage(NetworkPackage np) {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
try {
@ -257,16 +270,12 @@ public class LanLink extends BaseLink {
if (np.hasPayloadTransferInfo()) {
Socket payloadSocket = null;
Socket payloadSocket = new Socket();
try {
// Use ssl if existing link is on ssl
if (socket instanceof SSLSocket) {
SSLContext sslContext = SslHelper.getSslContext(context, getDeviceId(), true);
payloadSocket = sslContext.getSocketFactory().createSocket();
} else {
payloadSocket = new Socket();
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, true);
}
int tcpPort = np.getPayloadTransferInfo().getInt("port");
InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress();
payloadSocket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
@ -282,49 +291,6 @@ public class LanLink extends BaseLink {
packageReceived(np);
}
ServerSocket openTcpSocketOnFreePort(Context context, String deviceId) throws IOException {
if (socket instanceof SSLSocket) {
return openSecureServerSocket(context, deviceId);
} else {
return openUnsecureSocketOnFreePort(1739);
}
}
static ServerSocket openUnsecureSocketOnFreePort(int minPort) throws IOException {
int tcpPort = minPort;
while(tcpPort < 1764) {
try {
ServerSocket candidateServer = new ServerSocket();
candidateServer.bind(new InetSocketAddress(tcpPort));
Log.i("KDE/LanLink", "Using port "+tcpPort);
return candidateServer;
} catch(IOException e) {
tcpPort++;
}
}
Log.e("KDE/LanLink", "No more ports available");
throw new IOException("No more ports available");
}
static ServerSocket openSecureServerSocket(Context context, String deviceId) throws IOException{
SSLContext tlsContext = SslHelper.getSslContext(context, deviceId, true);
SSLServerSocketFactory sslServerSocketFactory = tlsContext.getServerSocketFactory();
int tcpPort = 1739;
while(tcpPort < 1764) {
try {
ServerSocket candidateServer = sslServerSocketFactory.createServerSocket();
candidateServer.bind(new InetSocketAddress(tcpPort));
Log.i("KDE/LanLink", "Using port "+tcpPort);
return candidateServer;
} catch(IOException e) {
tcpPort++;
}
}
Log.e("KDE/LanLink", "No more ports available");
throw new IOException("No more ports available");
}
@Override
public boolean linkShouldBeKeptAlive() {

View File

@ -43,6 +43,7 @@ import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
@ -56,22 +57,20 @@ import javax.net.SocketFactory;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class LanLinkProvider extends BaseLinkProvider {
public class LanLinkProvider extends BaseLinkProvider implements LanLink.SocketClosedCallback {
public static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
public static final int MIN_VERSION_WITH_NEW_PORT_SUPPORT = 7;
public static final String KEY_CUSTOM_DEVLIST_PREFERENCE = "device_list_preference";
private final static int oldPort = 1714;
private final static int port = 1716;
final static int MIN_PORT_LEGACY = 1714;
final static int MIN_PORT = 1716;
final static int MAX_PORT = 1764;
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
private final Context context;
private final HashMap<String, LanLink> visibleComputers = new HashMap<>(); //Links by device id
private final HashMap<Socket, LanLink> nioLinks = new HashMap<>(); //Links by socket
private ServerSocket tcpServer;
private DatagramSocket udpServer;
@ -82,26 +81,12 @@ public class LanLinkProvider extends BaseLinkProvider {
// To prevent infinte loop between Android < IceCream because both device can only broadcast identity package but cannot connect via TCP
private ArrayList<InetAddress> reverseConnectionBlackList = new ArrayList<>();
public void socketClosed(Socket socket, boolean linkHasAnotherSocket) {
final LanLink brokenLink = nioLinks.remove(socket);
if (brokenLink != null) {
@Override // SocketClosedCallback
public void socketClosed(final LanLink brokenLink, boolean linkHasAnotherSocket) {
if (!linkHasAnotherSocket) {
String deviceId = brokenLink.getDeviceId();
visibleComputers.remove(deviceId);
new Thread(new Runnable() {
@Override
public void run() {
//Wait a bit before emitting connectionLost, in case the same device re-appears
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
connectionLost(brokenLink);
}
}).start();
}
}
}
@ -113,26 +98,22 @@ public class LanLinkProvider extends BaseLinkProvider {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = reader.readLine();
networkPackage = NetworkPackage.unserialize(message);
//Log.i("TcpListener","Received TCP package: "+networkPackage.serialize());
//Log.e("TcpListener","Received TCP package: "+networkPackage.serialize());
} catch (Exception e) {
e.printStackTrace();
return;
}
if (networkPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
if (!networkPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
Log.e("KDE/LanLinkProvider", "Expecting an identity package instead of " + networkPackage.getType());
return;
}
Log.i("KDE/LanLinkProvider", "Identity package received from a TCP connection from " + networkPackage.getString("deviceName"));
identityPackageReceived(networkPackage, socket, LanLink.ConnectionStarted.Locally);
} else {
LanLink link = nioLinks.get(socket);
if (link== null) {
Log.e("KDE/LanLinkProvider","Expecting an identity package instead of " + networkPackage.getType());
} else {
link.injectNetworkPackage(networkPackage);
}
}
}
//I've received their broadcast and should connect to their TCP socket and send my identity.
protected void udpPacketReceived(DatagramPacket packet) throws Exception {
final InetAddress address = packet.getAddress();
@ -153,21 +134,22 @@ public class LanLinkProvider extends BaseLinkProvider {
}
}
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_NEW_PORT_SUPPORT && identityPackage.getInt("tcpPort") < port) {
Log.w("KDE/LanLinkProvider", "Ignoring a udp broadcast from an old port because it comes from a device which knows about the new port.");
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_NEW_PORT_SUPPORT && identityPackage.getInt("tcpPort") < MIN_PORT) {
Log.w("KDE/LanLinkProvider", "Ignoring a udp broadcast from legacy port because it comes from a device which knows about the new port.");
return;
}
Log.i("KDE/LanLinkProvider", "Broadcast identity package received from " + identityPackage.getString("deviceName"));
int tcpPort = identityPackage.getInt("tcpPort", port);
int tcpPort = identityPackage.getInt("tcpPort", MIN_PORT);
SocketFactory socketFactory = SocketFactory.getDefault();
Socket socket = socketFactory.createSocket(address, tcpPort);
configureSocket(socket);
OutputStream out = socket.getOutputStream();
out.write(NetworkPackage.createIdentityPackage(context).serialize().getBytes());
NetworkPackage myIdentity = NetworkPackage.createIdentityPackage(context);
out.write(myIdentity.serialize().getBytes());
out.flush();
identityPackageReceived(identityPackage, socket, LanLink.ConnectionStarted.Remotely);
@ -188,7 +170,6 @@ public class LanLinkProvider extends BaseLinkProvider {
// Try to cause a reverse connection
onNetworkChange();
}
}
}
@ -217,14 +198,11 @@ public class LanLinkProvider extends BaseLinkProvider {
if (identityPackage.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
final boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
Log.i("KDE/LanLinkProvider","Starting SSL handshake with " + identityPackage.getString("deviceName") + " trusted:"+isDeviceTrusted);
SSLSocketFactory sslsocketFactory = SslHelper.getSslContext(context, deviceId, isDeviceTrusted).getSocketFactory();
final SSLSocket sslsocket = (SSLSocket)sslsocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
SslHelper.configureSslSocket(sslsocket, isDeviceTrusted, clientMode);
configureSocket(sslsocket);
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
sslsocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
@Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
@ -276,19 +254,10 @@ public class LanLinkProvider extends BaseLinkProvider {
//Update old link
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
final Socket oldSocket = currentLink.reset(socket, connectionOrigin, this);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
nioLinks.remove(oldSocket);
//Log.e("KDE/LanLinkProvider", "Forgetting about socket " + socket.hashCode());
}
}, 500); //Stop accepting messages from the old socket after 500ms
nioLinks.put(socket, currentLink);
//Log.e("KDE/LanLinkProvider", "Replacing socket. old: "+ oldSocket.hashCode() + " - new: "+ socket.hashCode());
} else {
//Let's create the link
LanLink link = new LanLink(context, deviceId, this, socket, connectionOrigin);
nioLinks.put(socket, link);
visibleComputers.put(deviceId, link);
connectionAccepted(identityPackage, link);
}
@ -333,7 +302,7 @@ public class LanLinkProvider extends BaseLinkProvider {
private void setupTcpListener() {
try {
tcpServer = LanLink.openUnsecureSocketOnFreePort(port);
tcpServer = openServerSocketOnFreePort(MIN_PORT);
new Thread(new Runnable() {
@Override
public void run() {
@ -356,13 +325,29 @@ public class LanLinkProvider extends BaseLinkProvider {
}
}
static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException {
int tcpPort = minPort;
while(tcpPort < MAX_PORT) {
try {
ServerSocket candidateServer = new ServerSocket();
candidateServer.bind(new InetSocketAddress(tcpPort));
Log.i("KDE/LanLink", "Using port "+tcpPort);
return candidateServer;
} catch(IOException e) {
tcpPort++;
}
}
Log.e("KDE/LanLink", "No ports available");
throw new IOException("No ports available");
}
void broadcastUdpPackage() {
new Thread(new Runnable() {
@Override
public void run() {
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_CUSTOM_DEVLIST_PREFERENCE, "");
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(CustomDevicesActivity.KEY_CUSTOM_DEVLIST_PREFERENCE, "");
ArrayList<String> iplist = new ArrayList<>();
if (!deviceListPrefs.isEmpty()) {
iplist = CustomDevicesActivity.deserializeIpList(deviceListPrefs);
@ -370,7 +355,7 @@ public class LanLinkProvider extends BaseLinkProvider {
iplist.add("255.255.255.255"); //Default: broadcast.
NetworkPackage identity = NetworkPackage.createIdentityPackage(context);
identity.set("tcpPort", port);
identity.set("tcpPort", MIN_PORT);
DatagramSocket socket = null;
byte[] bytes = null;
try {
@ -388,8 +373,8 @@ public class LanLinkProvider extends BaseLinkProvider {
for (String ipstr : iplist) {
try {
InetAddress client = InetAddress.getByName(ipstr);
socket.send(new DatagramPacket(bytes, bytes.length, client, port));
socket.send(new DatagramPacket(bytes, bytes.length, client, oldPort));
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT));
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT_LEGACY));
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+client);
} catch (Exception e) {
e.printStackTrace();
@ -412,8 +397,8 @@ public class LanLinkProvider extends BaseLinkProvider {
listening = true;
udpServer = setupUdpListener(port);
udpServerOldPort = setupUdpListener(oldPort);
udpServer = setupUdpListener(MIN_PORT);
udpServerOldPort = setupUdpListener(MIN_PORT_LEGACY);
// Due to certificate request from SSL server to client, the certificate request message from device with latest android version to device with
// old android version causes a FATAL ALERT message stating that incorrect certificate request

View File

@ -41,6 +41,7 @@ import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.nio.channels.ServerSocketChannel;
import java.security.KeyStore;
import java.security.MessageDigest;
@ -58,6 +59,7 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
@ -132,6 +134,7 @@ public class SslHelper {
}
public static SSLContext getSslContext(Context context, String deviceId, boolean isDeviceTrusted) {
//TODO: Cache
try {
// Get device private key
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
@ -225,6 +228,13 @@ public class SslHelper {
}
public static SSLSocket convertToSslSocket(Context context, Socket socket, String deviceId, boolean isDeviceTrusted, boolean clientMode) throws IOException {
SSLSocketFactory sslsocketFactory = SslHelper.getSslContext(context, deviceId, isDeviceTrusted).getSocketFactory();
SSLSocket sslsocket = (SSLSocket)sslsocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
SslHelper.configureSslSocket(sslsocket, isDeviceTrusted, clientMode);
return sslsocket;
}
public static String getCertificateHash(Certificate certificate) {
try {
byte[] hash = MessageDigest.getInstance("SHA-1").digest(certificate.getEncoded());
@ -238,7 +248,6 @@ public class SslHelper {
} catch (Exception e) {
return null;
}
}
public static Certificate parseCertificate(byte[] certificateBytes) throws IOException, CertificateException {