mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Initial code for /geyser connectiontest
This command acts as a testing ground for debugging Unable to Connect to World. More checks will be added in the future.
This commit is contained in:
parent
467286060c
commit
0efd04dd87
6 changed files with 208 additions and 37 deletions
|
@ -51,6 +51,7 @@ import org.geysermc.geyser.util.FileUtils;
|
|||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -187,7 +188,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
|
||||
geyserLogger = new GeyserStandaloneLogger();
|
||||
|
||||
LoopbackUtil.checkLoopback(geyserLogger);
|
||||
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
|
||||
|
||||
try {
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
|
||||
|
|
|
@ -73,8 +73,6 @@ import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
|||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.*;
|
||||
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
@ -226,24 +224,13 @@ public class GeyserImpl implements GeyserApi {
|
|||
String remoteAddress = config.getRemote().getAddress();
|
||||
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
|
||||
if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) {
|
||||
int remotePort;
|
||||
try {
|
||||
// Searches for a server address and a port from a SRV record of the specified host name
|
||||
InitialDirContext ctx = new InitialDirContext();
|
||||
Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV");
|
||||
// size > 0 = SRV entry found
|
||||
if (attr != null && attr.size() > 0) {
|
||||
String[] record = ((String) attr.get(0)).split(" ");
|
||||
// Overwrites the existing address and port with that from the SRV record.
|
||||
String[] record = WebUtils.findSrvRecord(this, remoteAddress);
|
||||
if (record != null) {
|
||||
int remotePort = Integer.parseInt(record[2]);
|
||||
config.getRemote().setAddress(remoteAddress = record[3]);
|
||||
config.getRemote().setPort(remotePort = Integer.parseInt(record[2]));
|
||||
config.getRemote().setPort(remotePort);
|
||||
logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\"");
|
||||
}
|
||||
} catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes
|
||||
logger.debug("Exception while trying to find an SRV record for the remote host.");
|
||||
if (config.isDebugMode())
|
||||
ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
|
||||
|
|
|
@ -55,6 +55,7 @@ public abstract class CommandManager {
|
|||
registerCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
||||
registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
||||
registerCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
|
||||
registerCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
|
||||
if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) {
|
||||
registerCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.CommandSender;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ConnectionTestCommand extends GeyserCommand {
|
||||
private final GeyserImpl geyser;
|
||||
|
||||
public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable GeyserSession session, CommandSender sender, String[] args) {
|
||||
// Only allow the console to create dumps on Geyser Standalone
|
||||
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length == 0) {
|
||||
sender.sendMessage("Provide the Bedrock server IP you are trying to connect with. Example: `test.geysermc.org:19132`");
|
||||
return;
|
||||
}
|
||||
|
||||
// Still allow people to not supply a port and fallback to 19132
|
||||
String[] fullAddress = args[0].split(":", 2);
|
||||
int port;
|
||||
if (fullAddress.length == 2) {
|
||||
port = Integer.parseInt(fullAddress[1]);
|
||||
} else {
|
||||
port = 19132;
|
||||
}
|
||||
|
||||
// Issue: do the ports not line up?
|
||||
if (port != geyser.getConfig().getBedrock().getPort()) {
|
||||
sender.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration ("
|
||||
+ geyser.getConfig().getBedrock().getPort() + "). You can change it under `bedrock` `port`.");
|
||||
}
|
||||
|
||||
// Issue: is the `bedrock` `address` in the config different?
|
||||
if (!geyser.getConfig().getBedrock().getAddress().equals("0.0.0.0")) {
|
||||
sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
|
||||
}
|
||||
|
||||
// Issue: did someone turn on enable-proxy-protocol and they didn't mean it?
|
||||
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
|
||||
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
|
||||
}
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// Issue: SRV record?
|
||||
String ip = fullAddress[0];
|
||||
String[] record = WebUtils.findSrvRecord(geyser, ip);
|
||||
if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) {
|
||||
sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
|
||||
+ ". If that fails, re-run this command with that address and port.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue: does Loopback need applying?
|
||||
if (LoopbackUtil.needsLoopback(GeyserImpl.getInstance().getLogger())) {
|
||||
sender.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " +
|
||||
"See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer");
|
||||
}
|
||||
|
||||
// mcsrvstatus will likely be replaced in the future with our own service where we can also test
|
||||
// around the OVH workaround without worrying about caching
|
||||
JsonNode output = WebUtils.getJson("https://api.mcsrvstat.us/bedrock/2/" + args[0]);
|
||||
|
||||
long cacheTime = output.get("debug").get("cachetime").asLong();
|
||||
String when;
|
||||
if (cacheTime == 0) {
|
||||
when = "now";
|
||||
} else {
|
||||
when = ((System.currentTimeMillis() / 1000L) - cacheTime) + " seconds ago";
|
||||
}
|
||||
|
||||
if (output.get("online").asBoolean()) {
|
||||
sender.sendMessage("Your server is likely online as of " + when + "!");
|
||||
sendLinks(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage("Your server is likely unreachable from outside the network as of " + when + ".");
|
||||
sendLinks(sender);
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage("Error while trying to check your connection!");
|
||||
geyser.getLogger().error("Error while trying to check your connection!", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendLinks(CommandSender sender) {
|
||||
sender.sendMessage("If you still have issues, check to see if your hosting provider has a specific setup: " +
|
||||
"https://wiki.geysermc.org/geyser/supported-hosting-providers/" + ", see this page: "
|
||||
+ "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on our Discord: " + "https://discord.gg/geysermc");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -23,8 +23,9 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.platform.standalone;
|
||||
package org.geysermc.geyser.util;
|
||||
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
|
@ -32,32 +33,47 @@ import java.io.InputStream;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class LoopbackUtil {
|
||||
private static final String checkExemption = "powershell -Command \"CheckNetIsolation LoopbackExempt -s\""; // Java's Exec feature runs as CMD, NetIsolation is only accessible from PowerShell.
|
||||
private static final String loopbackCommand = "powershell -Command \"CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'\"";
|
||||
public final class LoopbackUtil {
|
||||
private static final String checkExemption = "CheckNetIsolation LoopbackExempt -s";
|
||||
private static final String loopbackCommand = "CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'";
|
||||
/**
|
||||
* This string needs to be checked in the event Minecraft is not installed - no Minecraft string will be present in the checkExemption command.
|
||||
*/
|
||||
private static final String minecraftApplication = "S-1-15-2-1958404141-86561845-1752920682-3514627264-368642714-62675701-733520436";
|
||||
private static final String startScript = "powershell -Command \"Start-Process 'cmd' -ArgumentList /c,%temp%/loopback_minecraft.bat -Verb runAs\"";
|
||||
|
||||
public static void checkLoopback(GeyserStandaloneLogger geyserLogger) {
|
||||
if (System.getProperty("os.name").equalsIgnoreCase("Windows 10")) {
|
||||
/**
|
||||
* @return true if loopback is not addressed properly.
|
||||
*/
|
||||
public static boolean needsLoopback(GeyserLogger logger) {
|
||||
String os = System.getProperty("os.name");
|
||||
if (os.equalsIgnoreCase("Windows 10") || os.equalsIgnoreCase("Windows 11")) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(checkExemption);
|
||||
process.waitFor();
|
||||
InputStream is = process.getInputStream();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while (process.isAlive()) {
|
||||
if (is.available() != 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (is.available() != 0) {
|
||||
sb.append((char) is.read());
|
||||
}
|
||||
|
||||
return !sb.toString().contains(minecraftApplication);
|
||||
} catch (Exception e) {
|
||||
logger.error("Couldn't detect if loopback has been added on Windows!", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String result = sb.toString();
|
||||
|
||||
if (!result.contains("minecraftuwp")) {
|
||||
public static void checkAndApplyLoopback(GeyserLogger geyserLogger) {
|
||||
if (needsLoopback(geyserLogger)) {
|
||||
try {
|
||||
Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes());
|
||||
Runtime.getRuntime().exec(startScript);
|
||||
|
||||
geyserLogger.info(ChatColor.AQUA + GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.added"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
|
@ -66,4 +82,6 @@ public class LoopbackUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private LoopbackUtil() {
|
||||
}
|
||||
}
|
|
@ -28,6 +28,9 @@ package org.geysermc.geyser.util;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
@ -170,4 +173,23 @@ public class WebUtils {
|
|||
|
||||
return connectionToString(con);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String[] findSrvRecord(GeyserImpl geyser, String remoteAddress) {
|
||||
try {
|
||||
// Searches for a server address and a port from a SRV record of the specified host name
|
||||
InitialDirContext ctx = new InitialDirContext();
|
||||
Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV");
|
||||
// size > 0 = SRV entry found
|
||||
if (attr != null && attr.size() > 0) {
|
||||
return ((String) attr.get(0)).split(" ");
|
||||
}
|
||||
} catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes
|
||||
if (geyser.getConfig().isDebugMode()) {
|
||||
geyser.getLogger().debug("Exception while trying to find an SRV record for the remote host.");
|
||||
ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue