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

make ConnectionTestCommand more foolproof #3951

Merged
merged 13 commits into from
Sep 6, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
package org.geysermc.geyser.command.defaults;

import com.fasterxml.jackson.databind.JsonNode;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
Expand All @@ -36,11 +36,20 @@
import org.geysermc.geyser.util.WebUtils;
import org.jetbrains.annotations.Nullable;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class ConnectionTestCommand extends GeyserCommand {
/*
* The MOTD is temporarily changed during the connection test.
* This allows us to check if we are pinging the correct Geyser instance
*/
public static String CONNECTION_TEST_MOTD = null;
onebeastchris marked this conversation as resolved.
Show resolved Hide resolved

private final GeyserImpl geyser;

private final Random random = new Random();

public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
this.geyser = geyser;
Expand All @@ -55,31 +64,69 @@ public void execute(@Nullable GeyserSession session, GeyserCommandSource sender,
}

if (args.length == 0) {
sender.sendMessage("Provide the Bedrock server IP you are trying to connect with. Example: `test.geysermc.org:19132`");
sender.sendMessage("Provide the server IP and port you are trying to test Bedrock connections for. Example: `test.geysermc.org:19132`");
return;
}

// Replace "<" and ">" symbols if they are present to avoid the common issue of people including them
String[] fullAddress = args[0].replace("<", "").replace(">", "").split(":", 2);

// 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]);
try {
port = Integer.parseInt(fullAddress[1]);
} catch (NumberFormatException e) {
// can occur if e.g. "/geyser connectiontest <ip>:<port> is ran
sender.sendMessage("Not a valid port! Specify a valid numeric port.");
return;
}
} else {
port = 19132;
}
String ip = fullAddress[0];

// Issue: people commonly checking placeholders
if (ip.equals("ip")) {
sender.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check.");
return;
}

// Issue: checking 0.0.0.0 won't work
if (ip.equals("0.0.0.0")) {
sender.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4.");
return;
}

// Issue: people testing local ip
if (ip.equals("localhost") || ip.startsWith("127.") || ip.startsWith("10.") || ip.startsWith("192.168.")) {
sender.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP.");
return;
}

// Issue: do the ports not line up?
if (port != geyser.getConfig().getBedrock().port()) {
sender.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration ("
+ geyser.getConfig().getBedrock().port() + "). You can change it under `bedrock` `port`.");
if (fullAddress.length == 2) {
sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
+ geyser.getConfig().getBedrock().port() + ")");
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
if (geyser.getConfig().getBedrock().isCloneRemotePort()) {
sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
}
} else {
sender.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
"and the default port 19132 does not match the port in your Geyser configuration ("
+ geyser.getConfig().getBedrock().port() + ")!");
sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
}
}

// Issue: is the `bedrock` `address` in the config different?
if (!geyser.getConfig().getBedrock().address().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?
// 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.");
Expand All @@ -88,7 +135,6 @@ public void execute(@Nullable GeyserSession session, GeyserCommandSource sender,
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]
Expand All @@ -102,37 +148,67 @@ public void execute(@Nullable GeyserSession session, GeyserCommandSource sender,
"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]);
// Generate some random, unique bits that another server wouldn't provide
byte[] randomBytes = new byte[2];
this.random.nextBytes(randomBytes);
StringBuilder randomStr = new StringBuilder();
for (byte b : randomBytes) {
randomStr.append(Integer.toHexString(b));
}
String connectionTestMotd = "Geyser Connection Test " + randomStr;
CONNECTION_TEST_MOTD = connectionTestMotd;

sender.sendMessage("Testing server connection now. Please wait...");
JsonNode output;
try {
output = WebUtils.getJson("https://checker.geysermc.org/ping?hostname=" + ip + "&port=" + port);
} finally {
CONNECTION_TEST_MOTD = null;
}

long cacheTime = output.get("debug").get("cachetime").asLong();
JsonNode cache = output.get("cache");
String when;
if (cacheTime == 0) {
when = "now";
if (cache.get("fromCache").asBoolean()) {
when = cache.get("secondsSince").asInt() + " seconds ago";
} else {
when = ((System.currentTimeMillis() / 1000L) - cacheTime) + " seconds ago";
when = "now";
}

if (output.get("online").asBoolean()) {
sender.sendMessage("Your server is likely online as of " + when + "!");
if (output.get("success").asBoolean()) {
JsonNode ping = output.get("ping");
JsonNode pong = ping.get("pong");
String remoteMotd = pong.get("motd").asText();
if (!connectionTestMotd.equals(remoteMotd)) {
sender.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
"Did you supply the correct IP and port of your server?");
sendLinks(sender);
return;
}

if (ping.get("tcpFirst").asBoolean()) {
sender.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
sendLinks(sender);
return;
}

sender.sendMessage("Your server is likely online and working 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!");
sender.sendMessage("An error occurred while trying to check your connection! Check the console for more information.");
geyser.getLogger().error("Error while trying to check your connection!", e);
}
});
}

private void sendLinks(GeyserCommandSource 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");
sender.sendMessage("If you still face issues, check the setup guide for instructions: " +
"https://wiki.geysermc.org/geyser/setup/");
sender.sendMessage("If that does not work, see " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on Discord: " + "https://discord.gg/geysermc");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler;
import org.cloudburstmc.protocol.bedrock.BedrockPong;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.event.type.GeyserBedrockPingEventImpl;
import org.geysermc.geyser.network.CIDRMatcher;
Expand Down Expand Up @@ -254,6 +255,12 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
pong.subMotd(GeyserImpl.NAME);
}

if (ConnectionTestCommand.CONNECTION_TEST_MOTD != null) {
// Force-override as we are testing the connection and want to verify we are connecting to the right server through the MOTD
pong.motd(ConnectionTestCommand.CONNECTION_TEST_MOTD);
pong.subMotd(GeyserImpl.NAME);
}

// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte[] motdArray = pong.motd().getBytes(StandardCharsets.UTF_8);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ public void translate(GeyserSession session, PlayerActionPacket packet) {
entity.setOnGround(false); // Increase block break time while jumping
break;
case MISSED_SWING:
// TODO Re-evaluate after pre-1.20.10 is supported?
// TODO Re-evaluate after pre-1.20.10 is no longer supported?
if (session.getArmAnimationTicks() == -1) {
session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND));
session.activateArmAnimationTicking();
Expand Down