diff --git a/app/src/command.c b/app/src/command.c index 6a5a38e431..246d151586 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -45,6 +45,13 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } +process_t adb_forward_remove(const char *serial, uint16_t local_port) { + char local[4 + 5 + 1]; // tcp:PORT + sprintf(local, "tcp:%" PRIu16, local_port); + const char *const adb_cmd[] = {"forward", "--remove", local}; + return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); +} + process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME diff --git a/app/src/command.h b/app/src/command.h index 5308aa6add..ed7d5fafaa 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -39,6 +39,7 @@ SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); process_t adb_execute(const char *serial, const char *const adb_cmd[], int len); process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name); +process_t adb_forward_remove(const char *serial, uint16_t local_port); process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port); process_t adb_reverse_remove(const char *serial, const char *device_socket_name); process_t adb_push(const char *serial, const char *local, const char *remote); diff --git a/app/src/net.c b/app/src/net.c index 60fb3988b7..1d68f068f2 100644 --- a/app/src/net.c +++ b/app/src/net.c @@ -18,6 +18,26 @@ typedef struct in_addr IN_ADDR; #endif +socket_t net_connect(Uint32 addr, Uint16 port) { + socket_t sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + perror("socket"); + return INVALID_SOCKET; + } + + SOCKADDR_IN sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(addr); + sin.sin_port = htons(port); + + if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { + perror("connect"); + return INVALID_SOCKET; + } + + return sock; +} + socket_t net_listen(Uint32 addr, Uint16 port, int backlog) { socket_t sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { diff --git a/app/src/net.h b/app/src/net.h index 03ad8c3934..52a1e9a471 100644 --- a/app/src/net.h +++ b/app/src/net.h @@ -21,6 +21,7 @@ SDL_bool net_init(void); void net_cleanup(void); +socket_t net_connect(Uint32 addr, Uint16 port); socket_t net_listen(Uint32 addr, Uint16 port, int backlog); socket_t net_accept(socket_t server_socket); diff --git a/app/src/server.c b/app/src/server.c index 1e1c95bdc3..33f25b5562 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "config.h" #include "log.h" @@ -37,17 +38,45 @@ static SDL_bool remove_server(const char *serial) { return process_check_success(process, "adb shell rm"); } -static SDL_bool enable_tunnel(const char *serial, Uint16 local_port) { +static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) { process_t process = adb_reverse(serial, SOCKET_NAME, local_port); return process_check_success(process, "adb reverse"); } -static SDL_bool disable_tunnel(const char *serial) { +static SDL_bool disable_tunnel_reverse(const char *serial) { process_t process = adb_reverse_remove(serial, SOCKET_NAME); return process_check_success(process, "adb reverse --remove"); } -static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate) { +static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) { + process_t process = adb_forward(serial, local_port, SOCKET_NAME); + return process_check_success(process, "adb forward"); +} + +static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) { + process_t process = adb_forward_remove(serial, local_port); + return process_check_success(process, "adb forward --remove"); +} + +static SDL_bool enable_tunnel(struct server *server) { + if (enable_tunnel_reverse(server->serial, server->local_port)) { + return SDL_TRUE; + } + + LOGW("'adb reverse' failed, fallback to 'adb forward'"); + server->tunnel_forward = SDL_TRUE; + return enable_tunnel_forward(server->serial, server->local_port); +} + +static SDL_bool disable_tunnel(struct server *server) { + if (server->tunnel_forward) { + return disable_tunnel_forward(server->serial, server->local_port); + } + return disable_tunnel_reverse(server->serial); +} + +static process_t execute_server(const char *serial, + Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) { char max_size_string[6]; char bit_rate_string[11]; sprintf(max_size_string, "%"PRIu16, max_size); @@ -60,15 +89,48 @@ static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_ "com.genymobile.scrcpy.Server", max_size_string, bit_rate_string, + tunnel_forward ? "true" : "false", }; return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); } -static socket_t listen_on_port(Uint16 port) { #define IPV4_LOCALHOST 0x7F000001 + +static socket_t listen_on_port(Uint16 port) { return net_listen(IPV4_LOCALHOST, port, 1); } +static socket_t connect_and_read_byte(Uint16 port) { + socket_t socket = net_connect(IPV4_LOCALHOST, port); + if (socket == INVALID_SOCKET) { + return INVALID_SOCKET; + } + + char byte; + // the connection may succeed even if the server behind the "adb tunnel" + // is not listening, so read one byte to detect a working connection + if (net_recv_all(socket, &byte, 1) != 1) { + // the server is not listening yet behind the adb tunnel + return INVALID_SOCKET; + } + return socket; +} + +static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) { + do { + LOGD("Remaining connection attempts: %d", (int) attempts); + socket_t socket = connect_and_read_byte(port); + if (socket != INVALID_SOCKET) { + // it worked! + return socket; + } + if (attempts) { + SDL_Delay(delay); + } + } while (--attempts > 0); + return INVALID_SOCKET; +} + static void close_socket(socket_t *socket) { SDL_assert(*socket != INVALID_SOCKET); net_shutdown(*socket, SHUT_RDWR); @@ -85,6 +147,8 @@ void server_init(struct server *server) { SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) { + server->local_port = local_port; + if (serial) { server->serial = SDL_strdup(serial); } @@ -95,52 +159,67 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po server->server_copied_to_device = SDL_TRUE; - if (!enable_tunnel(serial, local_port)) { + if (!enable_tunnel(server)) { return SDL_FALSE; } - // At the application level, the device part is "the server" because it - // serves video stream and control. However, at the network level, the - // client listens and the server connects to the client. That way, the - // client can listen before starting the server app, so there is no need to - // try to connect until the server socket is listening on the device. - - server->server_socket = listen_on_port(local_port); - if (server->server_socket == INVALID_SOCKET) { - LOGE("Could not listen on port %" PRIu16, local_port); - disable_tunnel(serial); - return SDL_FALSE; + // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to + // "adb forward", so the app socket is the client + if (!server->tunnel_forward) { + // At the application level, the device part is "the server" because it + // serves video stream and control. However, at the network level, the + // client listens and the server connects to the client. That way, the + // client can listen before starting the server app, so there is no need to + // try to connect until the server socket is listening on the device. + + server->server_socket = listen_on_port(local_port); + if (server->server_socket == INVALID_SOCKET) { + LOGE("Could not listen on port %" PRIu16, local_port); + disable_tunnel(server); + return SDL_FALSE; + } } // server will connect to our server socket - server->process = execute_server(serial, max_size, bit_rate); + server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward); if (server->process == PROCESS_NONE) { - close_socket(&server->server_socket); - disable_tunnel(serial); + if (!server->tunnel_forward) { + close_socket(&server->server_socket); + } + disable_tunnel(server); return SDL_FALSE; } - server->adb_reverse_enabled = SDL_TRUE; + server->tunnel_enabled = SDL_TRUE; return SDL_TRUE; } socket_t server_connect_to(struct server *server, Uint32 timeout_ms) { - server->device_socket = net_accept(server->server_socket); + if (!server->tunnel_forward) { + server->device_socket = net_accept(server->server_socket); + } else { + Uint32 attempts = 10; + Uint32 delay = 100; // ms + server->device_socket = connect_to_server(server->local_port, attempts, delay); + } + if (server->device_socket == INVALID_SOCKET) { return INVALID_SOCKET; } - // we don't need the server socket anymore - close_socket(&server->server_socket); + if (!server->tunnel_forward) { + // we don't need the server socket anymore + close_socket(&server->server_socket); + } // the server is started, we can clean up the jar from the temporary folder remove_server(server->serial); // ignore failure server->server_copied_to_device = SDL_FALSE; // we don't need the adb tunnel anymore - disable_tunnel(server->serial); // ignore failure - server->adb_reverse_enabled = SDL_FALSE; + disable_tunnel(server); // ignore failure + server->tunnel_enabled = SDL_FALSE; return server->device_socket; } @@ -155,9 +234,9 @@ void server_stop(struct server *server) { cmd_simple_wait(server->process, NULL); // ignore exit code LOGD("Server terminated"); - if (server->adb_reverse_enabled) { + if (server->tunnel_enabled) { // ignore failure - disable_tunnel(server->serial); + disable_tunnel(server); } if (server->server_copied_to_device) { diff --git a/app/src/server.h b/app/src/server.h index 635473439d..fcd44b8df6 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -7,9 +7,11 @@ struct server { const char *serial; process_t process; - socket_t server_socket; + socket_t server_socket; // only used if !tunnel_forward socket_t device_socket; - SDL_bool adb_reverse_enabled; + Uint16 local_port; + SDL_bool tunnel_enabled; + SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse" SDL_bool server_copied_to_device; }; @@ -18,7 +20,9 @@ struct server { .process = PROCESS_NONE, \ .server_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \ - .adb_reverse_enabled = SDL_FALSE, \ + .local_port = 0, \ + .tunnel_enabled = SDL_FALSE, \ + .tunnel_forward = SDL_FALSE, \ .server_copied_to_device = SDL_FALSE, \ } diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 203ecc9b14..24735f7864 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; @@ -33,8 +34,20 @@ private static LocalSocket connect(String abstractName) throws IOException { return localSocket; } - public static DesktopConnection open(Device device) throws IOException { - LocalSocket socket = connect(SOCKET_NAME); + private static LocalSocket listenAndAccept(String abstractName) throws IOException { + LocalServerSocket localServerSocket = new LocalServerSocket(abstractName); + return localServerSocket.accept(); + } + + public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { + LocalSocket socket; + if (tunnelForward) { + socket = listenAndAccept(SOCKET_NAME); + // send one byte so the client may read() to detect a connection error + socket.getOutputStream().write(0); + } else { + socket = connect(SOCKET_NAME); + } DesktopConnection connection = new DesktopConnection(socket); Size videoSize = device.getScreenInfo().getVideoSize(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 6ab2f69451..332463e611 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -3,6 +3,7 @@ public class Options { private int maxSize; private int bitRate; + private boolean tunnelForward; public int getMaxSize() { return maxSize; @@ -19,4 +20,12 @@ public int getBitRate() { public void setBitRate(int bitRate) { this.bitRate = bitRate; } + + public boolean isTunnelForward() { + return tunnelForward; + } + + public void setTunnelForward(boolean tunnelForward) { + this.tunnelForward = tunnelForward; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5df436d12c..d7294ae078 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -10,7 +10,8 @@ private Server() { private static void scrcpy(Options options) throws IOException { final Device device = new Device(options); - try (DesktopConnection connection = DesktopConnection.open(device)) { + boolean tunnelForward = options.isTunnelForward(); + try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate()); // asynchronous @@ -55,6 +56,13 @@ private static Options createOptions(String... args) { int bitRate = Integer.parseInt(args[1]); options.setBitRate(bitRate); + if (args.length < 3) { + return options; + } + // use "adb forward" instead of "adb tunnel"? (so the server must listen) + boolean tunnelForward = Boolean.parseBoolean(args[2]); + options.setTunnelForward(tunnelForward); + return options; }