diff --git a/DEVELOP.md b/DEVELOP.md index 22e390d2..28fed6ef 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -137,6 +137,14 @@ Each [`Client`] manages a TCP socket, used to transmit raw IP packets from and to the _Gnirehtet_ Android client. Thus, these IP packets are encapsulated into TCP (they are transmitted as the TCP payload). +When a client connects, the relay server assigns an integer id to it, which it +writes to the TCP socket. The client considers itself connected to the relay +server only once it has received this number. This allows to detect any +end-to-end connection issue immediately. For instance, a TCP _connect_ initiated +by a client succeeds whenever a port redirection is enabled (typically through +`adb reverse`), even if the relay server is not listening. In that case, the +first _read_ will fail. + The [`IPv4Packet`] class provides a structured view to read and write packet data, which is physically stored in the buffers (the little squares on the schema). Since we handle one packet at a time with asynchronous I/O, there is no diff --git a/app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java b/app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java index 0ec51dbe..10f8e518 100644 --- a/app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java +++ b/app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java @@ -22,9 +22,8 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.nio.channels.SocketChannel; public final class RelayTunnel implements Tunnel { @@ -48,7 +47,7 @@ public static RelayTunnel open(VpnService vpnService) throws IOException { public void connect() throws IOException { channel.connect(new InetSocketAddress(Inet4Address.getLocalHost(), DEFAULT_PORT)); - fakeRead(channel.socket()); + readClientId(channel); } /** @@ -60,30 +59,30 @@ public void connect() throws IOException { * As a consequence, the connection state of the relay server would be invalid temporarily (we * would switch to CONNECTED state then switch back to DISCONNECTED). *
- * To avoid this problem, we must actually try to read from the server, so that an error occurs + * To avoid this problem, we must actually read from the server, so that an error occurs * immediately if the relay server is not accessible. *
- * To do so, we set the lowest timeout possible (1) that is not "infinity" (0), try to read,
- * then reset the timeout option. Since the client is always the first peer to send data, there
- * is never anything to read.
+ * Therefore, the relay server immediately sends the client id: consume it and log it.
*
- * @param socket the socket to read
+ * @param channel the channel to communicate with the relay server
* @throws IOException if an I/O error occurs
*/
- private static void fakeRead(Socket socket) throws IOException {
- int timeout = socket.getSoTimeout();
- socket.setSoTimeout(1);
- boolean eof = false;
- try {
- eof = socket.getInputStream().read() == -1;
- } catch (SocketTimeoutException e) {
- // ignore, expected timeout
- } finally {
- socket.setSoTimeout(timeout);
- }
- if (eof) {
- throw new IOException("Cannot connect to the relay server");
- }
+ private static void readClientId(ReadableByteChannel channel) throws IOException {
+ Log.d(TAG, "Requesting client id");
+ int clientId = readInt(channel);
+ Log.d(TAG, "Connected to the relay server as #" + Binary.unsigned(clientId));
+ }
+
+ private static int readInt(ReadableByteChannel channel) throws IOException {
+ final int intSize = 4;
+ ByteBuffer buffer = ByteBuffer.allocate(intSize);
+ do {
+ if (channel.read(buffer) == -1) {
+ throw new IOException("Cannot read from channel");
+ }
+ } while (buffer.hasRemaining());
+ buffer.flip();
+ return buffer.getInt();
}
@Override
diff --git a/relay/src/main/java/com/genymobile/relay/Client.java b/relay/src/main/java/com/genymobile/relay/Client.java
index 4a5b18f6..a07263c5 100644
--- a/relay/src/main/java/com/genymobile/relay/Client.java
+++ b/relay/src/main/java/com/genymobile/relay/Client.java
@@ -17,6 +17,7 @@
package com.genymobile.relay;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
@@ -42,10 +43,14 @@ public class Client {
private final List