1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-11-26 06:11:19 +00:00

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
This commit is contained in:
Catfriend1 2019-01-07 20:48:04 +01:00 committed by GitHub
parent 1b37db6213
commit 7b82062f54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 35 deletions

View file

@ -34,6 +34,7 @@ import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.model.Config; import com.nutomic.syncthingandroid.model.Config;
import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Device;
import com.nutomic.syncthingandroid.model.Gui;
import com.nutomic.syncthingandroid.model.Options; import com.nutomic.syncthingandroid.model.Options;
import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.NotificationHandler; import com.nutomic.syncthingandroid.service.NotificationHandler;
@ -169,7 +170,7 @@ public class SettingsActivity extends SyncthingActivity {
private RestApi mRestApi; private RestApi mRestApi;
private Options mOptions; private Options mOptions;
private Config.Gui mGui; private Gui mGui;
private Boolean mPendingConfig = false; private Boolean mPendingConfig = false;
@ -488,8 +489,8 @@ public class SettingsActivity extends SyncthingActivity {
webUITcpPort = Integer.parseInt((String) o); webUITcpPort = Integer.parseInt((String) o);
} catch (Exception e) { } catch (Exception e) {
} }
if (webUITcpPort < 1 || webUITcpPort > 65535) { if (webUITcpPort < 1024 || webUITcpPort > 65535) {
Toast.makeText(getActivity(), getResources().getString(R.string.invalid_port_number, 1, 65535), Toast.LENGTH_LONG) Toast.makeText(getActivity(), getResources().getString(R.string.invalid_port_number, 1024, 65535), Toast.LENGTH_LONG)
.show(); .show();
return false; return false;
} }

View file

@ -10,31 +10,4 @@ public class Config {
public Options options; public Options options;
public List<PendingDevice> pendingDevices; public List<PendingDevice> pendingDevices;
public List<RemoteIgnoredDevice> remoteIgnoredDevices; public List<RemoteIgnoredDevice> 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];
}
}
} }

View file

@ -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];
}
}

View file

@ -89,6 +89,12 @@ public class Constants {
public static final String FOLDER_TYPE_SEND_RECEIVE = "sendreceive"; public static final String FOLDER_TYPE_SEND_RECEIVE = "sendreceive";
public static final String FOLDER_TYPE_RECEIVE_ONLY = "receiveonly"; 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. * On Android 8.1, ACCESS_COARSE_LOCATION is required to access WiFi SSID.
* This is the request code used when requesting the permission. * This is the request code used when requesting the permission.

View file

@ -32,6 +32,7 @@ import com.nutomic.syncthingandroid.model.Event;
import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.model.FolderIgnoreList; import com.nutomic.syncthingandroid.model.FolderIgnoreList;
import com.nutomic.syncthingandroid.model.FolderStatus; import com.nutomic.syncthingandroid.model.FolderStatus;
import com.nutomic.syncthingandroid.model.Gui;
import com.nutomic.syncthingandroid.model.IgnoredFolder; import com.nutomic.syncthingandroid.model.IgnoredFolder;
import com.nutomic.syncthingandroid.model.Options; import com.nutomic.syncthingandroid.model.Options;
import com.nutomic.syncthingandroid.model.PendingDevice; import com.nutomic.syncthingandroid.model.PendingDevice;
@ -573,13 +574,13 @@ public class RestApi {
} }
} }
public Config.Gui getGui() { public Gui getGui() {
synchronized (mConfigLock) { 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) { synchronized (mConfigLock) {
mConfig.gui = newGui; mConfig.gui = newGui;
mConfig.options = newOptions; mConfig.options = newOptions;

View file

@ -26,6 +26,7 @@ import com.nutomic.syncthingandroid.model.Device;
import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.util.ConfigXml; import com.nutomic.syncthingandroid.util.ConfigXml;
import com.nutomic.syncthingandroid.util.FileUtils; import com.nutomic.syncthingandroid.util.FileUtils;
import com.nutomic.syncthingandroid.util.Util;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -506,8 +507,7 @@ public class SyncthingService extends Service {
return; return;
} }
} }
Log.v(TAG, "Starting syncthing");
onServiceStateChange(State.STARTING);
mConfig = new ConfigXml(this); mConfig = new ConfigXml(this);
try { try {
mConfig.loadConfig(); mConfig.loadConfig();
@ -519,6 +519,19 @@ public class SyncthingService extends Service {
return; 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) { if (mRestApi == null) {
mRestApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(), mRestApi = new RestApi(this, mConfig.getWebGuiUrl(), mConfig.getApiKey(),
this::onApiAvailable, () -> onServiceStateChange(mCurrentState)); this::onApiAvailable, () -> onServiceStateChange(mCurrentState));

View file

@ -10,6 +10,7 @@ import android.util.Log;
import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Device;
import com.nutomic.syncthingandroid.model.Folder; import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.model.FolderIgnoreList; import com.nutomic.syncthingandroid.model.FolderIgnoreList;
import com.nutomic.syncthingandroid.model.Gui;
import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.SyncthingRunnable; 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() { public String getApiKey() {
return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent(); return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent();
} }

View file

@ -9,6 +9,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
@ -257,6 +258,38 @@ public class Util {
return capturedStdOut; 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 * Make sure that dialog is showing and activity is valid before dismissing dialog, to prevent
* various crashes. * various crashes.

View file

@ -711,6 +711,9 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
<!-- Toast shown if a config file crucial to operation is missing --> <!-- Toast shown if a config file crucial to operation is missing -->
<string name="config_file_missing">Eine für den Betrieb wichtige Konfigurationsdatei fehlt</string> <string name="config_file_missing">Eine für den Betrieb wichtige Konfigurationsdatei fehlt</string>
<!-- Toast shown if a listening tcp port is unavailable -->
<string name="webui_tcp_port_unavailable">WebGUI TCP-Port %s belegt. Zweite Instanz?</string>
<!-- Label of the default folder created of first start (camera folder). --> <!-- Label of the default folder created of first start (camera folder). -->
<string name="default_folder_label">Kamera</string> <string name="default_folder_label">Kamera</string>

View file

@ -720,6 +720,9 @@ Please report any problems you encounter via Github.</string>
<!-- Toast shown if a config file crucial to operation is missing --> <!-- Toast shown if a config file crucial to operation is missing -->
<string name="config_file_missing">A config file crucial to operation is missing</string> <string name="config_file_missing">A config file crucial to operation is missing</string>
<!-- Toast shown if a listening tcp port is unavailable -->
<string name="webui_tcp_port_unavailable">WebUI tcp port %s busy. Second instance?</string>
<!-- Label of the default folder created of first start (camera folder). --> <!-- Label of the default folder created of first start (camera folder). -->
<string name="default_folder_label">Camera</string> <string name="default_folder_label">Camera</string>