diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index b00065041..7c4f4954b 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -1,35 +1,35 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b52f2e34e..0d5610083 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,8 +26,8 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 11
- 11
+ 14
+ 14
@@ -54,5 +54,11 @@
guava29.0-jre
+
+
+ dnsjava
+ dnsjava
+ 3.2.2
+
\ No newline at end of file
diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.java b/src/main/java/de/bixilon/minosoft/Minosoft.java
index 706e2f0ce..a32af1446 100644
--- a/src/main/java/de/bixilon/minosoft/Minosoft.java
+++ b/src/main/java/de/bixilon/minosoft/Minosoft.java
@@ -21,6 +21,7 @@ import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevel;
import de.bixilon.minosoft.mojang.api.MojangAccount;
import de.bixilon.minosoft.protocol.network.Connection;
+import de.bixilon.minosoft.protocol.protocol.ConnectionReason;
import de.bixilon.minosoft.util.FolderUtil;
import de.bixilon.minosoft.util.OSUtil;
import de.bixilon.minosoft.util.Util;
@@ -67,7 +68,7 @@ public class Minosoft {
checkClientToken();
- Connection c = new Connection(config.getString("debug.host"), config.getInteger("debug.port"));
+ Connection c = new Connection(config.getString("debug.host"));
accountList = config.getMojangAccounts();
if (accountList.size() == 0) {
/*
@@ -84,7 +85,7 @@ public class Minosoft {
Log.mojang("Could not refresh session, you will not be able to join premium servers!");
}
c.setPlayer(new Player(account));
- c.connect();
+ c.resolve(ConnectionReason.CONNECT); // resolve dns address and connect
}
/**
diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java
index 852417d8d..2ecc6c324 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java
@@ -32,17 +32,20 @@ import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketLoginStart;
import de.bixilon.minosoft.protocol.packets.serverbound.status.PacketStatusPing;
import de.bixilon.minosoft.protocol.packets.serverbound.status.PacketStatusRequest;
import de.bixilon.minosoft.protocol.protocol.*;
+import de.bixilon.minosoft.util.DNSUtil;
+import de.bixilon.minosoft.util.ServerAddress;
+import org.xbill.DNS.TextParseException;
import java.util.ArrayList;
public class Connection {
- final String host;
- final int port;
+ final ArrayList addresses;
final Network network;
final PacketHandler handler;
final PacketSender sender;
final ArrayList handlingQueue;
final VelocityHandler velocityHandler = new VelocityHandler(this);
+ ServerAddress address;
PluginChannelHandler pluginChannelHandler;
Thread handleThread;
Version version = Versions.getLowestVersionSupported(); // default
@@ -50,11 +53,16 @@ public class Connection {
Player player;
ConnectionState state = ConnectionState.DISCONNECTED;
ConnectionReason reason;
+ ConnectionReason nextReason;
ConnectionPing connectionStatusPing;
- public Connection(String host, int port) {
- this.host = host;
- this.port = port;
+ public Connection(String hostname) {
+ try {
+ addresses = DNSUtil.getServerAddresses(hostname);
+ } catch (TextParseException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
network = new Network(this);
handlingQueue = new ArrayList<>();
handler = new PacketHandler(this);
@@ -65,29 +73,41 @@ public class Connection {
* Sends an server ping to the server (player count, motd, ...)
*/
public void ping() {
- Log.info(String.format("Pinging server: %s:%d", host, port));
+ Log.info(String.format("Pinging server: %s", address));
reason = ConnectionReason.PING;
- network.connect();
+ network.connect(address);
+ }
+
+ public void resolve(ConnectionReason reason) {
+ address = addresses.get(0);
+ this.nextReason = reason;
+ Log.info(String.format("Trying to connect to %s", address));
+ resolve(address);
+ }
+
+ public void resolve(ServerAddress address) {
+ reason = ConnectionReason.DNS;
+ network.connect(address);
}
/**
* Tries to connect to the server and login
*/
public void connect() {
- Log.info(String.format("Connecting to server: %s:%d", host, port));
- if (reason == null) {
+ Log.info(String.format("Connecting to server: %s", address));
+ if (reason == null || reason == ConnectionReason.DNS) {
// first get version, then login
reason = ConnectionReason.GET_VERSION;
}
- network.connect();
+ network.connect(address);
}
- public String getHost() {
- return host;
+ public ServerAddress getAddress() {
+ return address;
}
- public int getPort() {
- return port;
+ public ArrayList getAvailableAddresses() {
+ return addresses;
}
public ConnectionState getConnectionState() {
@@ -105,7 +125,17 @@ public class Connection {
// connection established, starting threads and logging in
startHandlingThread();
ConnectionState next = ((reason == ConnectionReason.CONNECT) ? ConnectionState.LOGIN : ConnectionState.STATUS);
- network.sendPacket(new PacketHandshake(getHost(), getPort(), next, (next == ConnectionState.STATUS) ? -1 : getVersion().getProtocolVersion()));
+ if (reason == ConnectionReason.DNS) {
+ // valid hostname found
+ if (nextReason == ConnectionReason.CONNECT) {
+ // connecting, we must get the version first
+ reason = ConnectionReason.GET_VERSION;
+ } else {
+ reason = nextReason;
+ }
+ Log.info(String.format("Connection to %s seems to be okay, connecting...", address));
+ }
+ network.sendPacket(new PacketHandshake(address, next, (next == ConnectionState.STATUS) ? -1 : getVersion().getProtocolVersion()));
// after sending it, switch to next state
setConnectionState(next);
break;
@@ -128,6 +158,18 @@ public class Connection {
// unregister all custom recipes
Recipes.removeCustomRecipes();
}
+ break;
+ case FAILED:
+ // connect to next hostname, if available
+ int nextIndex = addresses.indexOf(address) + 1;
+ if (addresses.size() > nextIndex) {
+ ServerAddress nextAddress = addresses.get(nextIndex);
+ Log.warn(String.format("Could not connect to %s, trying next hostname: %s", address, nextAddress));
+ this.address = nextAddress;
+ resolve(address);
+ }
+ // else: failed
+ break;
}
}
diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java
index 5b6565cd3..12168e2ff 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java
@@ -20,6 +20,7 @@ import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.PacketCompres
import de.bixilon.minosoft.protocol.packets.clientbound.login.PacketLoginSuccess;
import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse;
import de.bixilon.minosoft.protocol.protocol.*;
+import de.bixilon.minosoft.util.ServerAddress;
import de.bixilon.minosoft.util.Util;
import javax.crypto.Cipher;
@@ -30,7 +31,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
+import java.net.InetSocketAddress;
import java.net.Socket;
+import java.net.SocketTimeoutException;
import java.util.ArrayList;
public class Network {
@@ -51,7 +54,7 @@ public class Network {
this.queue = new ArrayList<>();
}
- public void connect() {
+ public void connect(ServerAddress address) {
// wait for data or send until it should disconnect
// first send, then receive
// something to send it, send it
@@ -61,7 +64,9 @@ public class Network {
// Could not connect
socketThread = new Thread(() -> {
try {
- socket = new Socket(connection.getHost(), connection.getPort());
+ socket = new Socket();
+ socket.setSoTimeout(ProtocolDefinition.SOCKET_CONNECT_TIMEOUT);
+ socket.connect(new InetSocketAddress(address.getHostname(), address.getPort()), ProtocolDefinition.SOCKET_CONNECT_TIMEOUT);
connected = true;
connection.setConnectionState(ConnectionState.HANDSHAKING);
socket.setKeepAlive(true);
@@ -197,7 +202,9 @@ public class Network {
connection.setConnectionState(ConnectionState.DISCONNECTED);
} catch (IOException e) {
// Could not connect
- connection.setConnectionState(ConnectionState.DISCONNECTED);
+ if (e instanceof SocketTimeoutException) {
+ connection.setConnectionState(ConnectionState.FAILED);
+ }
e.printStackTrace();
}
});
diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/serverbound/handshaking/PacketHandshake.java b/src/main/java/de/bixilon/minosoft/protocol/packets/serverbound/handshaking/PacketHandshake.java
index bb47e7075..826fd8829 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/packets/serverbound/handshaking/PacketHandshake.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/packets/serverbound/handshaking/PacketHandshake.java
@@ -19,26 +19,23 @@ import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
import de.bixilon.minosoft.protocol.protocol.ConnectionState;
import de.bixilon.minosoft.protocol.protocol.OutPacketBuffer;
import de.bixilon.minosoft.protocol.protocol.Packets;
-import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
+import de.bixilon.minosoft.util.ServerAddress;
public class PacketHandshake implements ServerboundPacket {
- final String address;
- final int port;
+ final ServerAddress address;
final ConnectionState nextState;
final int version;
- public PacketHandshake(String address, int port, ConnectionState nextState, int version) {
+ public PacketHandshake(ServerAddress address, ConnectionState nextState, int version) {
this.address = address;
- this.port = port;
this.nextState = nextState;
this.version = version;
}
- public PacketHandshake(String address, int version) {
+ public PacketHandshake(ServerAddress address, int version) {
this.address = address;
this.version = version;
- this.port = ProtocolDefinition.DEFAULT_PORT;
this.nextState = ConnectionState.STATUS;
}
@@ -46,14 +43,14 @@ public class PacketHandshake implements ServerboundPacket {
public OutPacketBuffer write(Connection connection) {
OutPacketBuffer buffer = new OutPacketBuffer(connection, Packets.Serverbound.HANDSHAKING_HANDSHAKE);
buffer.writeVarInt((nextState == ConnectionState.STATUS ? -1 : connection.getVersion().getProtocolVersion())); // get best protocol version
- buffer.writeString(address);
- buffer.writeShort((short) port);
+ buffer.writeString(address.getHostname());
+ buffer.writeShort((short) address.getPort());
buffer.writeVarInt(nextState.getId());
return buffer;
}
@Override
public void log() {
- Log.protocol(String.format("Sending handshake packet (host=%s, port=%d)", address, port));
+ Log.protocol(String.format("Sending handshake packet (address=%s)", address));
}
}
diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionReason.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionReason.java
index db4b1c643..e31c27f9a 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionReason.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionReason.java
@@ -14,6 +14,7 @@
package de.bixilon.minosoft.protocol.protocol;
public enum ConnectionReason {
+ DNS,
PING,
GET_VERSION,
CONNECT
diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionState.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionState.java
index 828325e4a..7778cb66a 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionState.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/ConnectionState.java
@@ -20,7 +20,8 @@ public enum ConnectionState {
LOGIN(2),
PLAY(3),
DISCONNECTING(5),
- DISCONNECTED(6);
+ DISCONNECTED(6),
+ FAILED(7);
final int id;
diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java
index 17c321c07..51d0d6628 100644
--- a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java
+++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java
@@ -16,6 +16,8 @@ package de.bixilon.minosoft.protocol.protocol;
public final class ProtocolDefinition {
public static final int STRING_MAX_LEN = 32767;
public static final int DEFAULT_PORT = 25565;
+ public static final int SOCKET_CONNECT_TIMEOUT = 5000;
+ public static final int SOCKET_TIMEOUT = 30000;
public static final int PROTOCOL_PACKET_MAX_SIZE = 2097152;
public static final float ANGLE_CALCULATION_CONSTANT = 360.0F / 256.0F;
diff --git a/src/main/java/de/bixilon/minosoft/util/DNSUtil.java b/src/main/java/de/bixilon/minosoft/util/DNSUtil.java
new file mode 100644
index 000000000..ed90a65c0
--- /dev/null
+++ b/src/main/java/de/bixilon/minosoft/util/DNSUtil.java
@@ -0,0 +1,61 @@
+/*
+ * Codename Minosoft
+ * Copyright (C) 2020 Moritz Zwerger
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program. If not, see .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.util;
+
+import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
+import org.xbill.DNS.Record;
+import org.xbill.DNS.*;
+
+import java.util.ArrayList;
+
+public class DNSUtil {
+
+ public static ArrayList getServerAddresses(String hostname) throws TextParseException {
+ ServerAddress fallbackAddress = getServerAddress(hostname);
+ ArrayList ret = new ArrayList<>();
+ if (hostname.contains(":")) {
+ // port provided, skip srv check
+ ret.add(fallbackAddress);
+ return ret;
+ }
+ String query = "_minecraft._tcp." + hostname;
+ Record[] records = new Lookup(query, Type.SRV).run();
+ if (records == null) {
+ ret.add(fallbackAddress);
+ return ret;
+ }
+ for (Record record : records) {
+ SRVRecord srvRecord = (SRVRecord) record;
+ ret.add(new ServerAddress(srvRecord.getTarget().toString(true), srvRecord.getPort()));
+ }
+ ret.add(fallbackAddress);
+ return ret;
+ }
+
+ private static ServerAddress getServerAddress(String hostname) {
+ String[] splitHostname = hostname.split(":", 2);
+ if (splitHostname.length == 1) {
+ return new ServerAddress(splitHostname[0], ProtocolDefinition.DEFAULT_PORT);
+ }
+ return new ServerAddress(splitHostname[0], Integer.parseInt(splitHostname[1]));
+
+ }
+
+ public String correctHostName(String hostname) {
+ // replaces invalid chars to avoid copy and paste issues (like spaces, ...)
+ hostname = hostname.replaceAll("\\s", "");
+ return hostname;
+ }
+
+}
diff --git a/src/main/java/de/bixilon/minosoft/util/ServerAddress.java b/src/main/java/de/bixilon/minosoft/util/ServerAddress.java
new file mode 100644
index 000000000..5e7968c2a
--- /dev/null
+++ b/src/main/java/de/bixilon/minosoft/util/ServerAddress.java
@@ -0,0 +1,37 @@
+/*
+ * Codename Minosoft
+ * Copyright (C) 2020 Moritz Zwerger
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program. If not, see .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.util;
+
+public class ServerAddress {
+ final String hostname;
+ final int port;
+
+ public ServerAddress(String hostname, int port) {
+ this.hostname = hostname;
+ this.port = port;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ @Override
+ public String toString() {
+ return getHostname() + ":" + getPort();
+ }
+}
diff --git a/src/main/resources/config/game.yml b/src/main/resources/config/game.yml
index d3d6e9d5d..e657ebb87 100644
--- a/src/main/resources/config/game.yml
+++ b/src/main/resources/config/game.yml
@@ -17,6 +17,5 @@ account:
# this will be removed soon, only for debugging (pre alpha stage): some features are not implemented yet -/-
debug:
- host: "127.0.0.1"
- port: 25565
+ server: "127.0.0.1"
version: -1
\ No newline at end of file