Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connection selftest #196

Merged
merged 1 commit into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions src/main/java/mpo/dayon/assistant/gui/Assistant.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import static java.lang.String.valueOf;
import static javax.swing.SwingConstants.HORIZONTAL;
import static mpo.dayon.common.babylon.Babylon.translate;
import static mpo.dayon.common.configuration.Configuration.DEFAULT_TOKEN_SERVER_URL;
import static mpo.dayon.common.gui.common.ImageUtilities.getOrCreateIcon;
import static mpo.dayon.common.utils.SystemUtilities.*;

Expand Down Expand Up @@ -250,17 +251,6 @@ public void actionPerformed(ActionEvent ev) {
final Point toolbarLocation = frame.getToolBar().getLocationOnScreen();
choices.setLocation(frameLocation.x + 20, toolbarLocation.y + frame.getToolBar().getHeight());
}

private void resolvePublicIp() throws IOException, InterruptedException {
// HttpClient doesn't implement AutoCloseable nor close before Java 21!
@java.lang.SuppressWarnings("squid:S2095")
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(WHATSMYIP_SERVER_URL))
.timeout(Duration.ofSeconds(5))
.build();
publicIp = client.send(request, HttpResponse.BodyHandlers.ofString()).body().trim();
}
};
ip.putValue("DISPLAY_NAME", publicIp); // always a selection
// ...
Expand All @@ -269,6 +259,17 @@ private void resolvePublicIp() throws IOException, InterruptedException {
return ip;
}

private void resolvePublicIp() throws IOException, InterruptedException {
// HttpClient doesn't implement AutoCloseable nor close before Java 21!
@java.lang.SuppressWarnings("squid:S2095")
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(WHATSMYIP_SERVER_URL))
.timeout(Duration.ofSeconds(5))
.build();
publicIp = client.send(request, HttpResponse.BodyHandlers.ofString()).body().trim();
}

private JMenuItem getJMenuItemCopyIpAndPort(JButton button) {
final JMenuItem menuItem = new JMenuItem(translate("copy.msg"));
menuItem.addActionListener(ev12 -> {
Expand Down Expand Up @@ -563,6 +564,7 @@ public void actionPerformed(ActionEvent ev) {
return token;
}).thenAcceptAsync(tokenString -> {
if (tokenString != null) {
token = tokenString;
button.setText(format(" %s", tokenString));
button.setToolTipText(translate("token.copy.msg"));
}
Expand Down Expand Up @@ -684,6 +686,19 @@ protected String doInBackground() {

private void startNetwork() {
frame.onGettingReady();
if (publicIp == null) {
try {
resolvePublicIp();
} catch (IOException | InterruptedException ex) {
Log.error("Could not determine public IP", ex);
if (ex instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
}
}
if (!networkEngine.selfTest(publicIp)) {
JOptionPane.showMessageDialog(frame, translate("port.error.msg1", networkConfiguration.getPort()), translate("port.error"), JOptionPane.WARNING_MESSAGE);
}
networkEngine.start(compatibilityModeActive.get());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -66,14 +68,32 @@ public void addListener(NetworkAssistantEngineListener listener) {
listeners.add(listener);
}

public boolean selfTest(String publicIp) {
if (publicIp == null) {
return false;
}
if (!manageRouterPorts(0, configuration.getPort())) {
try (ServerSocket listener = new ServerSocket(configuration.getPort())) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(publicIp, configuration.getPort()), 1000);
}
Log.info("Port " + configuration.getPort() + " is reachable from the outside");
} catch (IOException e) {
Log.error("Port " + configuration.getPort() + " is not reachable from the outside");
return false;
}
}
return true;
}

/**
* Called from a GUI action => do not block the AWT thread (!)
*/
public void start(boolean compatibilityMode) {
if (cancelling.get() || receiver != null) {
return;
}
manageRouterPorts(0, configuration.getPort());

receiver = new Thread(new RunnableEx() {
@Override
protected void doRun() throws NoSuchAlgorithmException, KeyManagementException {
Expand All @@ -93,9 +113,9 @@ public void cancel() {
fireOnDisconnecting();
}

public static void manageRouterPorts(int oldPort, int newPort) {
public static boolean manageRouterPorts(int oldPort, int newPort) {
if (!UPnP.isUPnPAvailable()) {
return;
return false;
}
if (oldPort != 0 && UPnP.isMappedTCP(oldPort)) {
UPnP.closePortTCP(oldPort);
Expand All @@ -104,10 +124,12 @@ public static void manageRouterPorts(int oldPort, int newPort) {
if (!UPnP.isMappedTCP(newPort)) {
if (UPnP.openPortTCP(newPort, APP_NAME)) {
Log.info(format("Enabled forwarding for port %d", newPort));
return;
return true;
}
Log.warn(format("Failed to enable forwarding for port %d", newPort));
return false;
}
return true;
}

// right, keep streams open - forever!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

import java.util.Objects;

import static mpo.dayon.common.utils.SystemUtilities.DEFAULT_TOKEN_SERVER_URL;

public class NetworkAssistantEngineConfiguration extends Configuration {
private static final String PREF_VERSION = "assistant.network.version";

Expand Down
12 changes: 8 additions & 4 deletions src/main/java/mpo/dayon/assisted/gui/Assisted.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import static java.lang.String.format;
import static java.lang.String.valueOf;
import static mpo.dayon.common.babylon.Babylon.translate;
import static mpo.dayon.common.configuration.Configuration.DEFAULT_TOKEN_SERVER_URL;
import static mpo.dayon.common.gui.common.ImageUtilities.getOrCreateIcon;
import static mpo.dayon.common.utils.SystemUtilities.*;

Expand All @@ -62,6 +63,8 @@ public class Assisted implements Subscriber, ClipboardOwner {

private final AtomicBoolean shareAllScreens = new AtomicBoolean(false);

private String token;

public Assisted(String tokenServerUrl) {
networkConfiguration = new NetworkAssistedEngineConfiguration();

Expand Down Expand Up @@ -144,7 +147,7 @@ private boolean configureConnection(String serverName, String portNumber, boolea

private boolean requestConnectionSettings() {
networkConfiguration = new NetworkAssistedEngineConfiguration();
ConnectionSettingsDialog connectionSettingsDialog = new ConnectionSettingsDialog(networkConfiguration);
ConnectionSettingsDialog connectionSettingsDialog = new ConnectionSettingsDialog(networkConfiguration, token);

final boolean ok = DialogFactory.showOkCancel(frame, translate("connection.settings"), connectionSettingsDialog.getTabbedPane(), false, () -> {
final String token = connectionSettingsDialog.getToken().trim();
Expand Down Expand Up @@ -186,8 +189,9 @@ private static String validatePortNumber(String portNumber) {
private void applyConnectionSettings(ConnectionSettingsDialog connectionSettingsDialog) {
CompletableFuture.supplyAsync(() -> {
final NetworkAssistedEngineConfiguration newConfiguration;
String token = connectionSettingsDialog.getToken().trim();
if (!token.isEmpty()) {
String tokenString = connectionSettingsDialog.getToken().trim();
if (!tokenString.isEmpty()) {
this.token = tokenString;
final Cursor cursor = frame.getCursor();
frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
String connectionParams = null;
Expand All @@ -212,6 +216,7 @@ private void applyConnectionSettings(ConnectionSettingsDialog connectionSettings
networkConfiguration = newConfiguration;
networkConfiguration.persist();
networkEngine.configure(networkConfiguration);
frame.onConnecting(networkConfiguration.getServerName(), networkConfiguration.getServerPort());
}
Log.info("NetworkConfiguration " + networkConfiguration);
});
Expand Down Expand Up @@ -265,7 +270,6 @@ private class NetWorker extends SwingWorker<String, String> {
@Override
protected String doInBackground() {
if (isConfigured() && !isCancelled()) {
frame.onConnecting(networkConfiguration.getServerName(), networkConfiguration.getServerPort());
networkEngine.configure(networkConfiguration);
networkEngine.connect();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ConnectionSettingsDialog {

private final JTextField assistantTokenTextField;

ConnectionSettingsDialog(NetworkAssistedEngineConfiguration configuration) {
ConnectionSettingsDialog(NetworkAssistedEngineConfiguration configuration, String token) {

JPanel connectionSettingsDialog = new JPanel(new GridLayout(2, 2, 10, 10));
connectionSettingsDialog.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
Expand All @@ -46,7 +46,7 @@ class ConnectionSettingsDialog {
connectionTokenDialog.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));

final JLabel assistantToken = new JLabel(getOrCreateIcon(ImageNames.KEY));
assistantTokenTextField = new JTextField("", 7);
assistantTokenTextField = new JTextField(token, 7);
assistantTokenTextField.setMargin(new Insets(2,2,2,2));
assistantTokenTextField.setFont(new Font("Sans Serif", Font.PLAIN, 26));
assistantTokenTextField.addMouseListener(clearTextOnDoubleClick(assistantTokenTextField));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

import java.util.Objects;

import static mpo.dayon.common.utils.SystemUtilities.DEFAULT_TOKEN_SERVER_URL;

public class NetworkAssistedEngineConfiguration extends Configuration {
private static final String PREF_VERSION = "assisted.network.version";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package mpo.dayon.common.configuration;

public abstract class Configuration {
public static final String DEFAULT_TOKEN_SERVER_URL = "https://fensterkitt.ch/dayon/";

public final String getDefaultTokenServerUrl() {
return DEFAULT_TOKEN_SERVER_URL;
}

public final void persist() {
persist(false);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/mpo/dayon/common/gui/common/BaseFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.lang.String.format;
import static mpo.dayon.common.babylon.Babylon.translate;
import static mpo.dayon.common.configuration.Configuration.DEFAULT_TOKEN_SERVER_URL;
import static mpo.dayon.common.gui.common.ImageNames.FINGERPRINT;
import static mpo.dayon.common.gui.common.ImageUtilities.getOrCreateIcon;
import static mpo.dayon.common.gui.toolbar.ToolBar.*;
Expand Down
1 change: 0 additions & 1 deletion src/main/java/mpo/dayon/common/utils/SystemUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public final class SystemUtilities {
public static final String JAVA_CLASS_PATH = "java.class.path";
public static final String FLATPAK_BROWSER = "/app/bin/dayon.browser";
private static final String JAVA_VENDOR = "java.vendor";
public static final String DEFAULT_TOKEN_SERVER_URL = "https://fensterkitt.ch/dayon/";
private static final Pattern FQ_HOSTNAME_REGEX = Pattern.compile("^([a-zA-Z\\d][a-zA-Z\\d\\-]{0,61}[a-zA-Z\\d]\\.)*[a-zA-Z]{2,}$");
private static final Pattern IPV4_REGEX = Pattern.compile("(\\d{1,3})");

Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = You should use the same input language on both computers.
comm.error = Communication error
comm.error.msg1 = The program has encountered a communication error [%s].

port.error = Warning
port.error.msg1 = Port %s is not reachable from the internet.\nPeers from outside your network will not be able to connect.

# Connection ...

connected = Connected - happy session!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ keyboard.error.msg3 = Sie sollten auf beiden Rechnern dieselbe Eingabesprache nu
comm.error = Kommunikationsfehler
comm.error.msg1 = Es ist ein Kommunikationsfelher aufgetreten [%s].

port.error = Warnung
port.error.msg1 = Der Port %s ist vom Internet her nicht erreichbar.\nComputer ausserhalb ihres Netzwerks werden sich nicht verbinden k\u00F6nnen.

# Connection ...

connected = Verbunden - Happy Session!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = Deber\u00EDas usar el mismo idioma de entrada en las dos c
comm.error = Error de comunicaci\u00F3n
comm.error.msg1 = El programa ha tenido un error de comunicaci\u00F3n [%s].

port.error = Aviso de advertencia
port.error.msg1 = El puerto %s no es accesible desde Internet.\nLos pares de fuera de su red no podr\u00E1n conectarse.

# Connection ...

connected = Conectado - \u00A1Feliz sesi\u00F3n!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = Vous devez utiliser la m\u00EAme langue de clavier sur les
comm.error = Erreur de Communication
comm.error.msg1 = Une erreur de communication est survenue dans l'application [%s].

port.error = Attention
port.error.msg1 = Le port X n'est pas accessible depuis l'internet.\nLes homologues ext\u00E9rieurs \u00E0 votre r\u00E9seau ne pourront pas se connecter.

# Connection ...

connected = Connect\u00E9 - bonne session!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_it.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = Dovresti usare la stessa lingua di input su entrambi i com
comm.error = Errore di comunicazione
comm.error.msg1 = Il programma ha riscontrato un errore di comunicazione [%s].

port.error = Attenzione
port.error.msg1 = La porta %s non \u00E8 raggiungibile da Internet.\nI peer esterni alla rete non potranno connettersi.

# Connection ...

connected = Connesso - buona sessione!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_ru.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = \u0412\u044B \u0434\u043E\u043B\u0436\u043D\u044B \u0438\u
comm.error = \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0432\u044F\u0437\u0438
comm.error.msg1 = \u041F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0438\u043B\u0430 \u043E\u0448\u0438\u0431\u043A\u0443 \u0441\u0432\u044F\u0437\u0438 [%s].

port.error = \u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435
port.error.msg1 = \u041F\u043E\u0440\u0442 %s \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0438\u0437 \u0418\u043D\u0442\u0435\u0440\u043D\u0435\u0442\u0430.\n\u041F\u0438\u0440\u0438\u043D\u0433\u0438, \u043D\u0430\u0445\u043E\u0434\u044F\u0449\u0438\u0435\u0441\u044F \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u043C\u0438 \u0432\u0430\u0448\u0435\u0439 \u0441\u0435\u0442\u0438, \u043D\u0435 \u0441\u043C\u043E\u0433\u0443\u0442 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F.

# Connection ...

connected = \u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u043B\u0441\u044F - \u0443\u0434\u0430\u0447\u043D\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_sv.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = Undvik olika tangentbordsspr\u00E5k p\u00E5 datorerna.
comm.error = Kommunikationsfel
comm.error.msg1 = Programmet har st\u00F6tt p\u00E5 ett kommunikationsfel [%s].

port.error = Varning
port.error.msg1 = Port %s \u00E4r inte n\u00E5bar fr\u00E5n internet. Motparter utanf\u00F6r ditt n\u00E4tverk kommer inte att kunna ansluta.

# Connection ...

connected = Ansluten - Lycka till!
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/Babylon_tr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ keyboard.error.msg3 = \u0130ki bilgisayarda da ayn\u0131 giri\u015F dilini kulla
comm.error = Ileti\u015Fim hatas\u0131
comm.error.msg1 = Program ileti\u015Fim hatas\u0131yla kar\u015F\u0131la\u015Ft\u0131 [%s].

port.error = Uyar\u0131
port.error.msg1 = Port %s internetten eri\u015Filebilir de\u011Fil.\nA\u011F\u0131n\u0131z\u0131n d\u0131\u015F\u0131ndan gelen e\u015Fler ba\u011Flanamayacakt\u0131r.

# Connection ...

connected = Ba\u011Fland\u0131 - mutlu ba\u011Flant\u0131lar!
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/Babylon_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ comm.error = \u4F1A\u8BDD\u4E2D\u65AD
# The program has encountered a communication error [%s].
comm.error.msg1 = \u8FDE\u63A5\u88AB\u4E2D\u65AD\u4E86 [%s]

# Warning
port.error = \u8B66\u544A
# Port %s is not reachable from the internet.\nPeers from outside your network will not be able to connect.
port.error.msg1 = \u7AEF\u53E3 %s \u65E0\u6CD5\u4ECE\u4E92\u8054\u7F51\u8BBF\u95EE\u3002\u7F51\u7EDC\u5916\u90E8\u7684\u5BF9\u7B49\u65B9\u5C06\u65E0\u6CD5\u8FDE\u63A5\u3002

# Connection ...

# Connected - happy session!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;

class NetworkAssistantEngineTest {
Expand Down Expand Up @@ -41,4 +43,34 @@ void testCancel() {
// then
verify(listener).onDisconnecting();
}

@Test
void selfTestShouldFailIfPublicIpIsNull() {
// given
String publicIp = null;
engine.configure(new NetworkAssistantEngineConfiguration(12345, ""));

// when // then
assertFalse(engine.selfTest(publicIp));
}

@Test
void selfTestShouldFailForUnreachablePort() {
// given
String publicIp = "1.2.3.4";
engine.configure(new NetworkAssistantEngineConfiguration(5, ""));

// when // then
assertFalse(engine.selfTest(publicIp));
}

@Test
void selfTestShouldSucceedForReachablePort() {
// given
String publicIp = "127.0.0.1";
engine.configure(new NetworkAssistantEngineConfiguration(12345, ""));

// when // then
assertTrue(engine.selfTest(publicIp));
}
}
Loading