Depuis la version 1.9 (Promiscuous Potato), une version portable
"lancement rapide" est disponible en plus de la programme
@@ -81,6 +81,8 @@
- Dayon! Assistant se comporte comme une application serveur (vers laquelle l'assistée
- va se connecter). Vous devez donc configurer le réseau pour rendre votre application visible depuis l'extérieur.
- Par défaut, le serveur écoute le Port 8080, mais vous pouvez le modifier si nécessaire. Ensuite, il faudra
- autoriser ce port dans votre parefeu et sans doute configurer un service NAT sur votre routeur DSL.
+ Dayon! Assistant agit comme une application serveur typique (l'assisté va se connecter) et en tant que tel, vous devez configurer votre réseau
+ pour le rendre visible du monde extérieur.
+ Par défaut, le serveur écoute le port 8080, mais vous pouvez le modifier si nécessaire.
+ Depuis la version 12, Dayon! crée indépendamment une règle de transfert de port correspondante. La condition préalable est que UPnP soit activé.
+ Sinon, il sera toujours nécessaire de rediriger le port (TCP) via NAT sur le routeur vers l'ordinateur correspondant.
- Consultez portforward.com pour un guide étape par étape pour les plus courants modèles de routeurs.
+ Consultez portforward.com pour un guide étape par étape des modèles de routeur les plus courants.
diff --git a/docs/index.html b/docs/index.html
index 2d9f8f2b..1acaba3b 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -68,7 +68,7 @@
Since release 1.9 (Promiscuous Potato), a portable "quick launch"
version is available in addition to the conventional installation
@@ -76,7 +76,8 @@
- Dayon! Assistant is acting as a typical server application (the
- assisted is going to connect to) and as such you've to configure your network
- to make it visible from the outside world. By default, the server listens to Port
- 8080, but you can change this if necessary. Authorize that port number in your firewall
- and possibly setup accordingly your NAT services (typically on your router).
+ Dayon! Assistant is acting as a typical server application (the assisted is going to connect to) and as such you've to configure your network
+ to make it visible from the outside world.
+ By default, the server listens to Port 8080, but you can change this if necessary.
+ Since version 12, Dayon! creates a corresponding port forwarding rule independently. The prerequisite for this is that UPnP is activated.
+ Otherwise, it will still be necessary to forward the port (TCP) via NAT on the router to the corresponding computer.
- Check out portforward.com for a step-by-step guide for
- the most common router models.
+ Check out portforward.com for a step-by-step guide for the most common router models.
diff --git a/docs/zh_download.html b/docs/zh_download.html
index 945ad7d4..fd2c5a82 100644
--- a/docs/zh_download.html
+++ b/docs/zh_download.html
@@ -111,6 +111,13 @@
- 通过访问代码连接
diff --git a/docs/zh_index.html b/docs/zh_index.html
index 489a70c9..b28481ea 100644
--- a/docs/zh_index.html
+++ b/docs/zh_index.html
@@ -39,10 +39,11 @@
Dayon! 通过压缩算法和发送缓存的黑白(最高 256 色)图像以最大限度地减少网络资源使用,尽可能多地提供实时体验。Dayon!所提供的黑白屏幕影像质量足以清除分辨菜单、图标和查看计算机设置等...
-
公告 (11)
+ 公告 (12)
从 1.9 (Promiscuous Potato) 版本开始,同主程序一起发布的还有绿色(Portable)、便携的 "快速启动(Quick Launch)"版本
目前发布有两个“快速启动”的Windows 二进制可执行文件(exe):一个是控制端(主控端 - Assistant),另一个是被控端(用户端 - Assisted)。
- 之所以称为“快速启动”版本,是由于它们无需安装便可运行。“快速启动”版本与同版本号的常规发布版本(需要安装的) 100% 兼容。
+ 之所以称为“快速启动”版本,是由于它们无需安装便可运行。“快速启动”版本与同版本号的常规发布版本(需要安装的) 100% 兼容。
+ 从第 12 版(Adorable Asteroid)开始,类似的可移植版本也可用于 Linux。
由于缺少苹果硬件,剪贴板传输功能只能在 Windows 10 和 11、Debian 和 Ubuntu 上做了测试 - 非常欢迎 macOS 用户的反馈!
讲个小故事…
Marc Polizzi 开发了Dayon! 。他当时住在菲律宾,通过Skype与远在欧洲的家人和朋友联络。
diff --git a/docs/zh_quickstart.html b/docs/zh_quickstart.html
index 0fd3c748..a861ae0a 100644
--- a/docs/zh_quickstart.html
+++ b/docs/zh_quickstart.html
@@ -63,17 +63,20 @@
快速入门
配置主控端(Assistant)——如何开始远程协助
Dayon! Assistant 充当典型的服务器应用程序(Assisted 将连接到),因此您必须配置您的网络以使其从外部世界可见 .
+
默认情况下,服务器侦听端口 8080,但您可以根据需要更改此设置。
- 在您的防火墙中授权该端口号,并可能相应地设置您的NAT服务(通常在您的 DSL 路由器上)。
+ 从第12版开始,Dayon! 独立对应的端口转发。 这样做的先决条件是 UPnP 已激活。
+ 否则仍然需要通过路由器上的NAT将端口(TCP)转发到相应的计算机。
译者注:上面说了那么多,说白就是你要让你的计算机暴露在公网,让用户可以访问得到你。如果你有公网IP,这自然是最方便
的。如果你和我一样,没有公网IP,那可以使用内网穿透、VPN等。比如FRP、Ngrok、OpenVPN、ZeroTier等,有些是需要自己有服
务器才能搭建,而有些则不需要,而且网上有很多热心网友贡献的免费通道,请自行百度,谢谢。
在 portforward.com 可以查询到常见的路由器(端口转发相关内容)的详细配置指南。
- 可选:调整传入连接的端口:
+ 可选:调整传入连接的端口:(左边有 UPnP,右边没有)
-
+
+
通过单击密钥生成访问令牌:
diff --git a/lib/WaifUPnP.jar b/lib/WaifUPnP.jar
new file mode 100644
index 00000000..301f6025
Binary files /dev/null and b/lib/WaifUPnP.jar differ
diff --git a/pom.xml b/pom.xml
index 3d05326e..58326427 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
reto@galante.ch
- 2016-2022
+ 2016-2023
GPL-3
@@ -38,6 +38,13 @@
+
+ com.dosse.upnp
+ WaifUPnP
+ 1.3.0
+ system
+ ${project.basedir}/lib/WaifUPnP.jar
+
org.tukaani
xz
diff --git a/src/main/java/mpo/dayon/assistant/gui/Assistant.java b/src/main/java/mpo/dayon/assistant/gui/Assistant.java
index a4e86de8..7648a5ed 100644
--- a/src/main/java/mpo/dayon/assistant/gui/Assistant.java
+++ b/src/main/java/mpo/dayon/assistant/gui/Assistant.java
@@ -1,5 +1,6 @@
package mpo.dayon.assistant.gui;
+import com.dosse.upnp.UPnP;
import mpo.dayon.assistant.control.ControlEngine;
import mpo.dayon.assistant.decompressor.DeCompressorEngine;
import mpo.dayon.assistant.decompressor.DeCompressorEngineListener;
@@ -36,12 +37,15 @@
import java.net.URL;
import java.util.List;
import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import static java.lang.Math.abs;
import static java.lang.String.format;
import static java.lang.String.valueOf;
+import static java.lang.Thread.sleep;
import static mpo.dayon.common.babylon.Babylon.translate;
import static mpo.dayon.common.gui.common.FrameType.ASSISTANT;
import static mpo.dayon.common.gui.common.ImageUtilities.getOrCreateIcon;
@@ -93,6 +97,8 @@ public class Assistant implements ClipboardOwner {
private String token;
+ private Boolean upnpEnabled;
+
public Assistant() {
receivedBitCounter = new BitCounter("receivedBits", translate("networkBandwidth"));
receivedBitCounter.start(1000);
@@ -144,12 +150,25 @@ public void start() {
FatalErrorHandler.attachFrame(frame);
frame.addListener(control);
frame.setVisible(true);
+ initUpnp();
+ }
+
+ private boolean isUpnpEnabled() {
+ while (upnpEnabled == null) {
+ try {
+ sleep(10L);
+ } catch (InterruptedException e) {
+ Log.warn("Swallowed", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+ return upnpEnabled;
}
private AssistantActions createAssistantActions() {
AssistantActions assistantActions = new AssistantActions();
assistantActions.setIpAddressAction(createWhatIsMyIpAction());
- assistantActions.setNetworkConfigurationAction(createNetworkAssistantConfigurationAction());
+ assistantActions.setNetworkConfigurationAction(createNetworkAssistantConfigurationAction(this));
assistantActions.setCaptureEngineConfigurationAction(createCaptureConfigurationAction());
assistantActions.setCompressionEngineConfigurationAction(createCompressionConfigurationAction());
assistantActions.setResetAction(createResetAction());
@@ -163,11 +182,8 @@ private AssistantActions createAssistantActions() {
return assistantActions;
}
- private void startNetwork() {
- network.start();
- }
-
private void stopNetwork() {
+ frame.hideSpinner();
network.cancel();
}
@@ -191,13 +207,16 @@ public void actionPerformed(ActionEvent ev) {
final Cursor cursor = frame.getCursor();
frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- final URL url = new URL(WHATSMYIP_SERVER_URL);
- try (final BufferedReader lines = new BufferedReader(new InputStreamReader(url.openStream()))) {
- publicIp = lines.readLine();
+ publicIp = UPnP.getExternalIP();
+ if (publicIp == null) {
+ final URL url = new URL(WHATSMYIP_SERVER_URL);
+ try (final BufferedReader lines = new BufferedReader(new InputStreamReader(url.openStream()))) {
+ publicIp = lines.readLine();
+ }
}
} catch (IOException ex) {
Log.error("Could not determine public IP", ex);
- JOptionPane.showMessageDialog(frame, translate("ipAddress.msg1"), translate("ipAddress"),
+ JOptionPane.showMessageDialog(frame, translate("ipAddress.msg2"), translate("ipAddress"),
JOptionPane.ERROR_MESSAGE);
} finally {
frame.setCursor(cursor);
@@ -284,19 +303,28 @@ private JMenuItem getJMenuItemCopyIpAndPort(JButton button) {
return menuItem;
}
- private Action createNetworkAssistantConfigurationAction() {
+ private Action createNetworkAssistantConfigurationAction(Assistant assistant) {
final Action exit = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ev) {
JFrame networkFrame = (JFrame) SwingUtilities.getRoot((Component) ev.getSource());
+
final JPanel pane = new JPanel();
- pane.setLayout(new GridLayout(1, 2, 10, 10));
+ pane.setLayout(new GridLayout(4, 1, 10, -10));
+ final JPanel subPane = new JPanel();
+ subPane.setLayout(new GridLayout(1, 2, 10, 10));
final JLabel portNumberLbl = new JLabel(translate("connection.settings.portNumber"));
portNumberLbl.setToolTipText(translate("connection.settings.portNumber.tooltip"));
final JTextField portNumberTextField = new JTextField();
portNumberTextField.setText(valueOf(networkConfiguration.getPort()));
- pane.add(portNumberLbl);
- pane.add(portNumberTextField);
+ final JLabel upnpStatus = new JLabel(format(translate("connection.settings.upnp." + assistant.isUpnpEnabled()), UPnP.getDefaultGatewayIP()));
+ final JLabel upnpHint = new JLabel(translate("connection.settings.portforward." + assistant.isUpnpEnabled()));
+ subPane.add(portNumberLbl);
+ subPane.add(portNumberTextField);
+ pane.add(upnpStatus);
+ pane.add(upnpHint);
+ pane.add(new JLabel(""));
+ pane.add(subPane);
final boolean ok = DialogFactory.showOkCancel(networkFrame, translate("connection.network"), pane, true, () -> {
final String portNumber = portNumberTextField.getText();
@@ -307,13 +335,13 @@ public void actionPerformed(ActionEvent ev) {
});
if (ok) {
- final NetworkAssistantEngineConfiguration xnetworkConfiguration = new NetworkAssistantEngineConfiguration(
+ final NetworkAssistantEngineConfiguration newNetworkConfiguration = new NetworkAssistantEngineConfiguration(
Integer.parseInt(portNumberTextField.getText()));
- if (!xnetworkConfiguration.equals(networkConfiguration)) {
- networkConfiguration = xnetworkConfiguration;
+ if (!newNetworkConfiguration.equals(networkConfiguration)) {
+ network.manageRouterPorts(networkConfiguration.getPort(), newNetworkConfiguration.getPort());
+ networkConfiguration = newNetworkConfiguration;
networkConfiguration.persist();
-
network.reconfigure(networkConfiguration);
}
}
@@ -673,7 +701,7 @@ private Action createStartAction() {
final Action startAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ev) {
- startNetwork();
+ new Assistant.NetWorker().execute();
}
};
startAction.putValue(Action.NAME, "start");
@@ -730,6 +758,42 @@ private void switchLookAndFeel(UIManager.LookAndFeelInfo lnf) {
}
}
+ private class NetWorker extends SwingWorker {
+ @Override
+ protected String doInBackground() {
+ if (!isCancelled()) {
+ startNetwork();
+ }
+ return null;
+ }
+
+ private void startNetwork() {
+ frame.onGettingReady();
+ network.start();
+ }
+
+ @Override
+ protected void done() {
+ try {
+ if (!isCancelled()) {
+ super.get();
+ Log.debug("NetWorker is done");
+ }
+ } catch (InterruptedException | ExecutionException ie) {
+ Log.info("NetWorker was cancelled");
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ private void initUpnp() {
+ CompletableFuture.supplyAsync(() -> {
+ upnpEnabled = Boolean.valueOf(UPnP.isUPnPAvailable());
+ Log.info(format("UPnP is %s", isUpnpEnabled() ? "enabled" : "disabled"));
+ return upnpEnabled;
+ });
+ }
+
private String margin(String in) {
return " " + in;
}
diff --git a/src/main/java/mpo/dayon/assistant/gui/AssistantFrame.java b/src/main/java/mpo/dayon/assistant/gui/AssistantFrame.java
index e2bc59ea..50c9d8f7 100644
--- a/src/main/java/mpo/dayon/assistant/gui/AssistantFrame.java
+++ b/src/main/java/mpo/dayon/assistant/gui/AssistantFrame.java
@@ -255,7 +255,7 @@ public void actionPerformed(ActionEvent ev) {
}
void onReady() {
- removeCenter();
+ hideSpinner();
validate();
repaint();
actions.getStartAction().setEnabled(true);
@@ -269,13 +269,20 @@ void onReady() {
}
void onHttpStarting(int port) {
- actions.getStartAction().setEnabled(false);
actions.getStopAction().setEnabled(true);
actions.getNetworkConfigurationAction().setEnabled(false);
actions.getIpAddressAction().setEnabled(false);
+ getStatusBar().setMessage(translate("listening", port));
+ }
+
+ void onGettingReady() {
+ showSpinner();
+ actions.getStartAction().setEnabled(false);
+ }
+
+ private void showSpinner() {
center = new Spinner();
add(center, BorderLayout.CENTER);
- getStatusBar().setMessage(translate("listening", port));
}
boolean onAccepted(Socket connection) {
@@ -284,7 +291,7 @@ boolean onAccepted(Socket connection) {
getOrCreateIcon(ImageNames.USERS), OK_CANCEL_OPTIONS, OK_CANCEL_OPTIONS[1]) == 0) {
return false;
}
- removeCenter();
+ hideSpinner();
getStatusBar().setMessage(translate("connection.incoming.msg2", connection.getInetAddress().getHostAddress()));
center = assistantPanelWrapper;
add(center, BorderLayout.CENTER);
@@ -330,7 +337,7 @@ void onIOError(IOException error) {
actions.getResetAction().setEnabled(false);
disableControls();
stopSessionTimer();
- removeCenter();
+ hideSpinner();
validate();
repaint();
String errorMessage = error.getMessage() != null ? translate("comm.error.msg1", translate(error.getMessage())) : translate("comm.error.msg1", "!");
@@ -385,7 +392,7 @@ private void stopSessionTimer() {
}
}
- private void removeCenter() {
+ void hideSpinner() {
if (center != null) {
remove(center);
}
diff --git a/src/main/java/mpo/dayon/assistant/network/NetworkAssistantEngine.java b/src/main/java/mpo/dayon/assistant/network/NetworkAssistantEngine.java
index f0783e3e..b50b876a 100644
--- a/src/main/java/mpo/dayon/assistant/network/NetworkAssistantEngine.java
+++ b/src/main/java/mpo/dayon/assistant/network/NetworkAssistantEngine.java
@@ -1,5 +1,6 @@
package mpo.dayon.assistant.network;
+import com.dosse.upnp.UPnP;
import mpo.dayon.assisted.compressor.CompressorEngineConfiguration;
import mpo.dayon.common.capture.CaptureEngineConfiguration;
import mpo.dayon.common.concurrent.RunnableEx;
@@ -36,6 +37,8 @@ public class NetworkAssistantEngine extends NetworkEngine implements ReConfigura
private SSLServerSocketFactory ssf;
+ private static final String APP_NAME = "Dayon!";
+
public NetworkAssistantEngine(NetworkCaptureMessageHandler captureMessageHandler, NetworkMouseLocationMessageHandler mouseMessageHandler, ClipboardOwner clipboardOwner) {
this.captureMessageHandler = captureMessageHandler;
this.mouseMessageHandler = mouseMessageHandler;
@@ -64,6 +67,9 @@ public void start() {
if (cancelling.get() || receiver != null) {
return;
}
+ if (UPnP.isUPnPAvailable() && !UPnP.isMappedTCP(configuration.getPort())) {
+ UPnP.openPortTCP(configuration.getPort(), APP_NAME);
+ }
receiver = new Thread(new RunnableEx() {
@Override
protected void doRun() throws NoSuchAlgorithmException, KeyManagementException {
@@ -84,6 +90,14 @@ public void cancel() {
fireOnDisconnecting();
}
+ public boolean manageRouterPorts(int oldPort, int newPort) {
+ if (UPnP.isUPnPAvailable()) {
+ UPnP.closePortTCP(oldPort);
+ return UPnP.openPortTCP(newPort, APP_NAME);
+ }
+ return false;
+ }
+
// right, keep streams open - forever!
@java.lang.SuppressWarnings({"squid:S2189", "squid:S2093"})
private void receivingLoop() throws NoSuchAlgorithmException, KeyManagementException {
@@ -112,6 +126,7 @@ private void receivingLoop() throws NoSuchAlgorithmException, KeyManagementExcep
handleIOException(ex);
} finally {
closeConnections();
+ UPnP.closePortTCP(configuration.getPort());
fireOnReady();
}
diff --git a/src/main/resources/Babylon.properties b/src/main/resources/Babylon.properties
index ebbee337..d6bd97b3 100644
--- a/src/main/resources/Babylon.properties
+++ b/src/main/resources/Babylon.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = Port number
connection.settings.portNumber.tooltip = Port number for the incoming connection
connection.settings.token = Token
connection.settings.invalidToken = This token is invalid.
+connection.settings.upnp.true = UPnP is enabled on this gateway %s
+connection.settings.upnp.false = UPnP is disabled in this network.
+connection.settings.portforward.true = Port forwarding is configured automatically.
+connection.settings.portforward.false = Remember to configure the port forwarding.
# Token
token = Token
diff --git a/src/main/resources/Babylon_de.properties b/src/main/resources/Babylon_de.properties
index 16f2847f..68cccbfa 100644
--- a/src/main/resources/Babylon_de.properties
+++ b/src/main/resources/Babylon_de.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = Portnummer
connection.settings.portNumber.tooltip = Portnummer f\u00FCr die eingehende Verbindungen
connection.settings.token = Token
connection.settings.invalidToken = Das Token ist ung\u00FCltig.
+connection.settings.upnp.true = UPnP ist aktiviert - Gateway %s
+connection.settings.upnp.false = UPnP ist in diesem Netzwerk deaktiviert.
+connection.settings.portforward.true = Portweiterleitung wird automatisch konfiguriert.
+connection.settings.portforward.false = Einrichten der Portweiterleitung nicht vergessen.
# Token
token = Token
diff --git a/src/main/resources/Babylon_es.properties b/src/main/resources/Babylon_es.properties
index 3444900e..f70ebba3 100644
--- a/src/main/resources/Babylon_es.properties
+++ b/src/main/resources/Babylon_es.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = N\u00FAmero de puerto
connection.settings.portNumber.tooltip = N\u00FAmero de puerto para la conexion entrante
connection.settings.token = C\u00F3digo
connection.settings.invalidToken = C\u00F3digo de acceso es inv\u00E1lido.
+connection.settings.upnp.true = UPnP est\u00E1 habilitado en esta puerta de enlace %s
+connection.settings.upnp.false = UPnP est\u00E1 deshabilitado en esta red.
+connection.settings.portforward.true = El reenv\u00EDo de puertos se configura autom\u00E1ticamente.
+connection.settings.portforward.false = Recuerde configurar el reenv\u00EDo de puertos.
# Token
token = C\u00F3digo
diff --git a/src/main/resources/Babylon_fr.properties b/src/main/resources/Babylon_fr.properties
index 384bc489..4c514969 100644
--- a/src/main/resources/Babylon_fr.properties
+++ b/src/main/resources/Babylon_fr.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = Num\u00E9ro du Port
connection.settings.portNumber.tooltip = Num\u00E9ro du port pour la connexion entrante
connection.settings.token = Code
connection.settings.invalidToken = Le code d'acc\u00E8s est invalide.
+connection.settings.upnp.true = UPnP est activ\u00E9 sur cette passerelle %s
+connection.settings.upnp.false = UPnP est d\u00E9sactiv\u00E9 dans ce r\u00E9seau.
+connection.settings.portforward.true = La redirection de port est configur\u00E9e automatiquement.
+connection.settings.portforward.false = N'oubliez pas de configurer la redirection de port.
# Token
token = Code
diff --git a/src/main/resources/Babylon_it.properties b/src/main/resources/Babylon_it.properties
index 0050e303..56092bfb 100644
--- a/src/main/resources/Babylon_it.properties
+++ b/src/main/resources/Babylon_it.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = Numero di porta
connection.settings.portNumber.tooltip = Numero di porta per la connessione in entrata
connection.settings.token = Token
connection.settings.invalidToken = Il token non \u00E8 valido.
+connection.settings.upnp.true = UPnP \u00E8 abilitato su questo gateway %s
+connection.settings.upnp.false = UPnP \u00E8 disabilitato in questa rete.
+connection.settings.portforward.true = Il port forwarding \u00E8 configurato automaticamente.
+connection.settings.portforward.false = Ricordati di configurare il port forwarding.
# Token
token = Token
diff --git a/src/main/resources/Babylon_ru.properties b/src/main/resources/Babylon_ru.properties
index c7f55824..2668bed3 100644
--- a/src/main/resources/Babylon_ru.properties
+++ b/src/main/resources/Babylon_ru.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = \u041D\u043E\u043C\u0435\u0440 \u043F\
connection.settings.portNumber.tooltip = \u041D\u043E\u043C\u0435\u0440 \u043F\u043E\u0440\u0442\u0430 \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u044F\u0449\u0435\u0433\u043E \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u044F
connection.settings.token = \u041A\u043E\u0434 \u0434\u043E\u0441\u0442\u0443\u043F\u0430
connection.settings.invalidToken = \u041A\u043E\u0434 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u0435\u043D.
+connection.settings.upnp.true = UPnP \u0432\u043A\u043B\u044E\u0447\u0435\u043D \u043D\u0430 \u044D\u0442\u043E\u043C \u0448\u043B\u044E\u0437\u0435 %s
+connection.settings.upnp.false = UPnP \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D \u0432 \u044D\u0442\u043E\u0439 \u0441\u0435\u0442\u0438.
+connection.settings.portforward.true = \u041F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044F \u043F\u043E\u0440\u0442\u043E\u0432 \u043D\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438.
+connection.settings.portforward.false = \u041D\u0435 \u0437\u0430\u0431\u0443\u0434\u044C\u0442\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044E \u043F\u043E\u0440\u0442\u043E\u0432.
# Token (access code)
token = \u041A\u043E\u0434 \u0434\u043E\u0441\u0442\u0443\u043F\u0430
diff --git a/src/main/resources/Babylon_tr.properties b/src/main/resources/Babylon_tr.properties
index 43438275..af92ef95 100644
--- a/src/main/resources/Babylon_tr.properties
+++ b/src/main/resources/Babylon_tr.properties
@@ -80,6 +80,10 @@ connection.settings.portNumber = Port numaras\u0131
connection.settings.portNumber.tooltip = Gelen ba\u011Flant\u0131 i\u00E7in port numaras\u0131
connection.settings.token = Giri\u015F kodu
connection.settings.invalidToken = Eri\u015Fim kodu ge\u00E7ersiz.
+connection.settings.upnp.true = UPnP bu a\u011F ge\u00E7idinde etkinle\u015Ftirildi %s
+connection.settings.upnp.false = UPnP bu a\u011Fda devre d\u0131\u015F\u0131.
+connection.settings.portforward.true = Ba\u011Flant\u0131 noktas\u0131 y\u00F6nlendirme otomatik olarak yap\u0131land\u0131r\u0131l\u0131r.
+connection.settings.portforward.false = Ba\u011Flant\u0131 noktas\u0131 y\u00F6nlendirmeyi yap\u0131land\u0131rmay\u0131 unutmay\u0131n.
# Token (access code)
token = Giri\u015F kodu
diff --git a/src/main/resources/Babylon_zh.properties b/src/main/resources/Babylon_zh.properties
index b5ba6252..849c3ed4 100644
--- a/src/main/resources/Babylon_zh.properties
+++ b/src/main/resources/Babylon_zh.properties
@@ -126,6 +126,10 @@ connection.settings.portNumber.tooltip = \u76D1\u542C\u7684\u7AEF\u53E3\u53F7
connection.settings.token = \u8BBF\u95EE\u4EE3\u7801
# The access token is invalid.
connection.settings.invalidToken = \u8BBF\u95EE\u4EE3\u7801\u65E0\u6548.
+connection.settings.upnp.true = \u6B64\u7F51\u5173 %s \u4E0A\u542F\u7528\u4E86 UPnP
+connection.settings.upnp.false = UPnP \u5728\u6B64\u7F51\u7EDC\u4E2D\u88AB\u7981\u7528\u3002
+connection.settings.portforward.true = \u7AEF\u53E3\u8F6C\u53D1\u662F\u81EA\u52A8\u914D\u7F6E\u7684\u3002
+connection.settings.portforward.false = \u8BF7\u8BB0\u4F4F\u914D\u7F6E\u7AEF\u53E3\u8F6C\u53D1\u3002
# Token (access code)
token = \u8BBF\u95EE\u4EE3\u7801