From 7b82062f5450c28b7210a5932c23e750d5dbf8fa Mon Sep 17 00:00:00 2001 From: Catfriend1 Date: Mon, 7 Jan 2019 20:48:04 +0100 Subject: [PATCH] Detect if another app/process is blocking the tcp port (fixes #193) (#209) * Add Util#isTcpPortListening (fixes #193) * Move model/Config.Gui to model/Gui * Constants: Add default tcp ports * Add string: webui_tcp_port_unavailable * Add ConfigXml#getWebGuiBindPort * Check if webUI tcp port is available before launching native (fixes #193) Output failure notification if the port is allocated by someone else. * Settings UI - Valid tcp ports are from 1024 to 65535 (fixes #211) * Add tcp6 listen port detection, check on connState == LISTEN * Update translations de --- .../activities/SettingsActivity.java | 7 ++-- .../syncthingandroid/model/Config.java | 27 --------------- .../nutomic/syncthingandroid/model/Gui.java | 28 ++++++++++++++++ .../syncthingandroid/service/Constants.java | 6 ++++ .../syncthingandroid/service/RestApi.java | 7 ++-- .../service/SyncthingService.java | 17 ++++++++-- .../syncthingandroid/util/ConfigXml.java | 12 +++++++ .../nutomic/syncthingandroid/util/Util.java | 33 +++++++++++++++++++ app/src/main/res/values-de/strings.xml | 3 ++ app/src/main/res/values/strings.xml | 3 ++ 10 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/com/nutomic/syncthingandroid/model/Gui.java diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java index 2d7a9cce..81cf3030 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java @@ -34,6 +34,7 @@ import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.model.Config; import com.nutomic.syncthingandroid.model.Device; +import com.nutomic.syncthingandroid.model.Gui; import com.nutomic.syncthingandroid.model.Options; import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.NotificationHandler; @@ -169,7 +170,7 @@ public class SettingsActivity extends SyncthingActivity { private RestApi mRestApi; private Options mOptions; - private Config.Gui mGui; + private Gui mGui; private Boolean mPendingConfig = false; @@ -488,8 +489,8 @@ public class SettingsActivity extends SyncthingActivity { webUITcpPort = Integer.parseInt((String) o); } catch (Exception e) { } - if (webUITcpPort < 1 || webUITcpPort > 65535) { - Toast.makeText(getActivity(), getResources().getString(R.string.invalid_port_number, 1, 65535), Toast.LENGTH_LONG) + if (webUITcpPort < 1024 || webUITcpPort > 65535) { + Toast.makeText(getActivity(), getResources().getString(R.string.invalid_port_number, 1024, 65535), Toast.LENGTH_LONG) .show(); return false; } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/model/Config.java b/app/src/main/java/com/nutomic/syncthingandroid/model/Config.java index e8e0980d..246e46b0 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/model/Config.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/model/Config.java @@ -10,31 +10,4 @@ public class Config { public Options options; public List pendingDevices; public List remoteIgnoredDevices; - - public class Gui { - public boolean enabled; - public String address; - public String user; - public String password; - public boolean useTLS; - public String apiKey; - public boolean insecureAdminAccess; - public String theme; - - public String getBindAddress() { - if (address == null) { - return ""; - } - String[] split = address.split(":"); - return split.length < 1 ? "" : split[0]; - } - - public String getBindPort() { - if (address == null) { - return ""; - } - String[] split = address.split(":"); - return split.length < 2 ? "" : split[1]; - } - } } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/model/Gui.java b/app/src/main/java/com/nutomic/syncthingandroid/model/Gui.java new file mode 100644 index 00000000..41b8fce4 --- /dev/null +++ b/app/src/main/java/com/nutomic/syncthingandroid/model/Gui.java @@ -0,0 +1,28 @@ +package com.nutomic.syncthingandroid.model; + +public class Gui { + public boolean enabled; + public String address; + public String user; + public String password; + public boolean useTLS; + public String apiKey; + public boolean insecureAdminAccess; + public String theme; + + public String getBindAddress() { + if (address == null) { + return ""; + } + String[] split = address.split(":"); + return split.length < 1 ? "" : split[0]; + } + + public String getBindPort() { + if (address == null) { + return ""; + } + String[] split = address.split(":"); + return split.length < 2 ? "" : split[1]; + } +} diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java index c42a139e..1d9fc6c3 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java @@ -89,6 +89,12 @@ public class Constants { public static final String FOLDER_TYPE_SEND_RECEIVE = "sendreceive"; public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly"; + /** + * Default listening ports. + */ + public static final Integer DEFAULT_WEBGUI_TCP_PORT = 8384; + public static final Integer DEFAULT_DATA_TCP_PORT = 22000; + /** * On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID. * This is the request code used when requesting the permission. diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java index 3091b6f2..ca4c6d04 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java @@ -32,6 +32,7 @@ import com.nutomic.syncthingandroid.model.Event; import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.FolderIgnoreList; import com.nutomic.syncthingandroid.model.FolderStatus; +import com.nutomic.syncthingandroid.model.Gui; import com.nutomic.syncthingandroid.model.IgnoredFolder; import com.nutomic.syncthingandroid.model.Options; import com.nutomic.syncthingandroid.model.PendingDevice; @@ -573,13 +574,13 @@ public class RestApi { } } - public Config.Gui getGui() { + public Gui getGui() { synchronized (mConfigLock) { - return deepCopy(mConfig.gui, Config.Gui.class); + return deepCopy(mConfig.gui, Gui.class); } } - public void editSettings(Config.Gui newGui, Options newOptions) { + public void editSettings(Gui newGui, Options newOptions) { synchronized (mConfigLock) { mConfig.gui = newGui; mConfig.options = newOptions; diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java b/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java index bb98b857..de5ba344 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java @@ -26,6 +26,7 @@ import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.util.ConfigXml; import com.nutomic.syncthingandroid.util.FileUtils; +import com.nutomic.syncthingandroid.util.Util; import java.io.File; import java.io.FileInputStream; @@ -506,8 +507,7 @@ public class SyncthingService extends Service { return; } } - Log.v(TAG, "Starting syncthing"); - onServiceStateChange(State.STARTING); + mConfig = new ConfigXml(this); try { mConfig.loadConfig(); @@ -519,6 +519,19 @@ public class SyncthingService extends Service { return; } + // Check if the SyncthingNative's configured webgui port is allocated by another app or process. (issue #193) + Integer webGuiTcpPort = mConfig.getWebGuiBindPort(); + Boolean isWebUIPortListening = Util.isTcpPortListening(webGuiTcpPort); + if (isWebUIPortListening) { + // We shouldn't start SyncthingNative as we would wait forever for life signs on the configured port. (ANR) + Log.e(TAG, "launchStartupTask: WebUI tcp port " + Integer.toString(webGuiTcpPort) + " unavailable. Second instance?"); + mNotificationHandler.showCrashedNotification(R.string.webui_tcp_port_unavailable, Integer.toString(webGuiTcpPort)); + return; + } + + Log.v(TAG, "Starting syncthing"); + onServiceStateChange(State.STARTING); + if (mRestApi == null) { mRestApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(), this::onApiAvailable, () -> onServiceStateChange(mCurrentState)); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java b/app/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java index 0a2134c4..84302878 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java @@ -10,6 +10,7 @@ import android.util.Log; import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.FolderIgnoreList; +import com.nutomic.syncthingandroid.model.Gui; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.SyncthingRunnable; @@ -204,6 +205,17 @@ public class ConfigXml { } } + public Integer getWebGuiBindPort() { + try { + Gui gui = new Gui(); + gui.address = getGuiElement().getElementsByTagName("address").item(0).getTextContent(); + return Integer.parseInt(gui.getBindPort()); + } catch (Exception e) { + Log.w(TAG, "getWebGuiBindPort: Failed with exception: ", e); + return Constants.DEFAULT_WEBGUI_TCP_PORT; + } + } + public String getApiKey() { return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent(); } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/util/Util.java b/app/src/main/java/com/nutomic/syncthingandroid/util/Util.java index 79fc0328..06cda012 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/util/Util.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/util/Util.java @@ -9,6 +9,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.preference.PreferenceManager; +import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -257,6 +258,38 @@ public class Util { return capturedStdOut; } + /** + * Check if a TCP is listening on the local device on a specific port. + */ + public static Boolean isTcpPortListening(Integer port) { + // t: tcp, l: listening, n: numeric + String output = runShellCommandGetOutput("netstat -t -l -n", false); + if (TextUtils.isEmpty(output)) { + Log.w(TAG, "isTcpPortListening: Failed to run netstat. Returning false."); + return false; + } + String[] results = output.split("\n"); + for (String line : results) { + if (TextUtils.isEmpty(output)) { + continue; + } + String[] words = line.split("\\s+"); + if (words.length > 5) { + String protocol = words[0]; + String localAddress = words[3]; + String connState = words[5]; + if (protocol.equals("tcp") || protocol.equals("tcp6")) { + if (localAddress.endsWith(":" + Integer.toString(port)) && + connState.equalsIgnoreCase("LISTEN")) { + // Port is listening. + return true; + } + } + } + } + return false; + } + /** * Make sure that dialog is showing and activity is valid before dismissing dialog, to prevent * various crashes. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8fc78cb7..c724de70 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -711,6 +711,9 @@ Bitte melden Sie auftretende Probleme via GitHub. Eine für den Betrieb wichtige Konfigurationsdatei fehlt + + WebGUI TCP-Port %s belegt. Zweite Instanz? + Kamera diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e2fddbaa..30c056fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -720,6 +720,9 @@ Please report any problems you encounter via Github. A config file crucial to operation is missing + + WebUI tcp port %s busy. Second instance? + Camera