dns handling: srv resolving

This commit is contained in:
Bixilon 2020-08-23 15:41:32 +02:00
parent 983c3844dd
commit 04ab1b5254
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
12 changed files with 221 additions and 67 deletions

View File

@ -1,35 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="RemoteRepositoriesConfiguration"> <component name="RemoteRepositoriesConfiguration">
<remote-repository> <remote-repository>
<option name="id" value="jitpack.io" /> <option name="id" value="jitpack.io"/>
<option name="name" value="jitpack.io" /> <option name="name" value="jitpack.io"/>
<option name="url" value="http://www.jitpack.io" /> <option name="url" value="http://www.jitpack.io"/>
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="jitpack.io" /> <option name="id" value="jitpack.io"/>
<option name="name" value="jitpack.io" /> <option name="name" value="jitpack.io"/>
<option name="url" value="https://www.jitpack.io" /> <option name="url" value="https://www.jitpack.io"/>
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="central" /> <option name="id" value="central"/>
<option name="name" value="Central Repository" /> <option name="name" value="Central Repository"/>
<option name="url" value="https://repo.maven.apache.org/maven2" /> <option name="url" value="https://repo.maven.apache.org/maven2"/>
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="central" /> <option name="id" value="jitpack.io"/>
<option name="name" value="Maven Central repository" /> <option name="name" value="jitpack.io"/>
<option name="url" value="https://repo1.maven.org/maven2" /> <option name="url" value="https://jitpack.io"/>
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="jitpack.io" /> <option name="id" value="central"/>
<option name="name" value="jitpack.io" /> <option name="name" value="Maven Central repository"/>
<option name="url" value="https://jitpack.io" /> <option name="url" value="https://repo1.maven.org/maven2"/>
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="jboss.community" /> <option name="id" value="jboss.community"/>
<option name="name" value="JBoss Community repository" /> <option name="name" value="JBoss Community repository"/>
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/"/>
</remote-repository> </remote-repository>
</component> </component>
</project> </project>

10
pom.xml
View File

@ -26,8 +26,8 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<source>11</source> <source>14</source>
<target>11</target> <target>14</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
@ -54,5 +54,11 @@
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>29.0-jre</version> <version>29.0-jre</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/dnsjava/dnsjava -->
<dependency>
<groupId>dnsjava</groupId>
<artifactId>dnsjava</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -21,6 +21,7 @@ import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevel; import de.bixilon.minosoft.logging.LogLevel;
import de.bixilon.minosoft.mojang.api.MojangAccount; import de.bixilon.minosoft.mojang.api.MojangAccount;
import de.bixilon.minosoft.protocol.network.Connection; 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.FolderUtil;
import de.bixilon.minosoft.util.OSUtil; import de.bixilon.minosoft.util.OSUtil;
import de.bixilon.minosoft.util.Util; import de.bixilon.minosoft.util.Util;
@ -67,7 +68,7 @@ public class Minosoft {
checkClientToken(); checkClientToken();
Connection c = new Connection(config.getString("debug.host"), config.getInteger("debug.port")); Connection c = new Connection(config.getString("debug.host"));
accountList = config.getMojangAccounts(); accountList = config.getMojangAccounts();
if (accountList.size() == 0) { 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!"); Log.mojang("Could not refresh session, you will not be able to join premium servers!");
} }
c.setPlayer(new Player(account)); c.setPlayer(new Player(account));
c.connect(); c.resolve(ConnectionReason.CONNECT); // resolve dns address and connect
} }
/** /**

View File

@ -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.PacketStatusPing;
import de.bixilon.minosoft.protocol.packets.serverbound.status.PacketStatusRequest; import de.bixilon.minosoft.protocol.packets.serverbound.status.PacketStatusRequest;
import de.bixilon.minosoft.protocol.protocol.*; 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; import java.util.ArrayList;
public class Connection { public class Connection {
final String host; final ArrayList<ServerAddress> addresses;
final int port;
final Network network; final Network network;
final PacketHandler handler; final PacketHandler handler;
final PacketSender sender; final PacketSender sender;
final ArrayList<ClientboundPacket> handlingQueue; final ArrayList<ClientboundPacket> handlingQueue;
final VelocityHandler velocityHandler = new VelocityHandler(this); final VelocityHandler velocityHandler = new VelocityHandler(this);
ServerAddress address;
PluginChannelHandler pluginChannelHandler; PluginChannelHandler pluginChannelHandler;
Thread handleThread; Thread handleThread;
Version version = Versions.getLowestVersionSupported(); // default Version version = Versions.getLowestVersionSupported(); // default
@ -50,11 +53,16 @@ public class Connection {
Player player; Player player;
ConnectionState state = ConnectionState.DISCONNECTED; ConnectionState state = ConnectionState.DISCONNECTED;
ConnectionReason reason; ConnectionReason reason;
ConnectionReason nextReason;
ConnectionPing connectionStatusPing; ConnectionPing connectionStatusPing;
public Connection(String host, int port) { public Connection(String hostname) {
this.host = host; try {
this.port = port; addresses = DNSUtil.getServerAddresses(hostname);
} catch (TextParseException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
network = new Network(this); network = new Network(this);
handlingQueue = new ArrayList<>(); handlingQueue = new ArrayList<>();
handler = new PacketHandler(this); handler = new PacketHandler(this);
@ -65,29 +73,41 @@ public class Connection {
* Sends an server ping to the server (player count, motd, ...) * Sends an server ping to the server (player count, motd, ...)
*/ */
public void ping() { public void ping() {
Log.info(String.format("Pinging server: %s:%d", host, port)); Log.info(String.format("Pinging server: %s", address));
reason = ConnectionReason.PING; 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 * Tries to connect to the server and login
*/ */
public void connect() { public void connect() {
Log.info(String.format("Connecting to server: %s:%d", host, port)); Log.info(String.format("Connecting to server: %s", address));
if (reason == null) { if (reason == null || reason == ConnectionReason.DNS) {
// first get version, then login // first get version, then login
reason = ConnectionReason.GET_VERSION; reason = ConnectionReason.GET_VERSION;
} }
network.connect(); network.connect(address);
} }
public String getHost() { public ServerAddress getAddress() {
return host; return address;
} }
public int getPort() { public ArrayList<ServerAddress> getAvailableAddresses() {
return port; return addresses;
} }
public ConnectionState getConnectionState() { public ConnectionState getConnectionState() {
@ -105,7 +125,17 @@ public class Connection {
// connection established, starting threads and logging in // connection established, starting threads and logging in
startHandlingThread(); startHandlingThread();
ConnectionState next = ((reason == ConnectionReason.CONNECT) ? ConnectionState.LOGIN : ConnectionState.STATUS); 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 // after sending it, switch to next state
setConnectionState(next); setConnectionState(next);
break; break;
@ -128,6 +158,18 @@ public class Connection {
// unregister all custom recipes // unregister all custom recipes
Recipes.removeCustomRecipes(); 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;
} }
} }

View File

@ -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.clientbound.login.PacketLoginSuccess;
import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse; import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse;
import de.bixilon.minosoft.protocol.protocol.*; import de.bixilon.minosoft.protocol.protocol.*;
import de.bixilon.minosoft.util.ServerAddress;
import de.bixilon.minosoft.util.Util; import de.bixilon.minosoft.util.Util;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@ -30,7 +31,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList; import java.util.ArrayList;
public class Network { public class Network {
@ -51,7 +54,7 @@ public class Network {
this.queue = new ArrayList<>(); this.queue = new ArrayList<>();
} }
public void connect() { public void connect(ServerAddress address) {
// wait for data or send until it should disconnect // wait for data or send until it should disconnect
// first send, then receive // first send, then receive
// something to send it, send it // something to send it, send it
@ -61,7 +64,9 @@ public class Network {
// Could not connect // Could not connect
socketThread = new Thread(() -> { socketThread = new Thread(() -> {
try { 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; connected = true;
connection.setConnectionState(ConnectionState.HANDSHAKING); connection.setConnectionState(ConnectionState.HANDSHAKING);
socket.setKeepAlive(true); socket.setKeepAlive(true);
@ -197,7 +202,9 @@ public class Network {
connection.setConnectionState(ConnectionState.DISCONNECTED); connection.setConnectionState(ConnectionState.DISCONNECTED);
} catch (IOException e) { } catch (IOException e) {
// Could not connect // Could not connect
connection.setConnectionState(ConnectionState.DISCONNECTED); if (e instanceof SocketTimeoutException) {
connection.setConnectionState(ConnectionState.FAILED);
}
e.printStackTrace(); e.printStackTrace();
} }
}); });

View File

@ -19,26 +19,23 @@ import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
import de.bixilon.minosoft.protocol.protocol.ConnectionState; import de.bixilon.minosoft.protocol.protocol.ConnectionState;
import de.bixilon.minosoft.protocol.protocol.OutPacketBuffer; import de.bixilon.minosoft.protocol.protocol.OutPacketBuffer;
import de.bixilon.minosoft.protocol.protocol.Packets; 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 { public class PacketHandshake implements ServerboundPacket {
final String address; final ServerAddress address;
final int port;
final ConnectionState nextState; final ConnectionState nextState;
final int version; 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.address = address;
this.port = port;
this.nextState = nextState; this.nextState = nextState;
this.version = version; this.version = version;
} }
public PacketHandshake(String address, int version) { public PacketHandshake(ServerAddress address, int version) {
this.address = address; this.address = address;
this.version = version; this.version = version;
this.port = ProtocolDefinition.DEFAULT_PORT;
this.nextState = ConnectionState.STATUS; this.nextState = ConnectionState.STATUS;
} }
@ -46,14 +43,14 @@ public class PacketHandshake implements ServerboundPacket {
public OutPacketBuffer write(Connection connection) { public OutPacketBuffer write(Connection connection) {
OutPacketBuffer buffer = new OutPacketBuffer(connection, Packets.Serverbound.HANDSHAKING_HANDSHAKE); OutPacketBuffer buffer = new OutPacketBuffer(connection, Packets.Serverbound.HANDSHAKING_HANDSHAKE);
buffer.writeVarInt((nextState == ConnectionState.STATUS ? -1 : connection.getVersion().getProtocolVersion())); // get best protocol version buffer.writeVarInt((nextState == ConnectionState.STATUS ? -1 : connection.getVersion().getProtocolVersion())); // get best protocol version
buffer.writeString(address); buffer.writeString(address.getHostname());
buffer.writeShort((short) port); buffer.writeShort((short) address.getPort());
buffer.writeVarInt(nextState.getId()); buffer.writeVarInt(nextState.getId());
return buffer; return buffer;
} }
@Override @Override
public void log() { 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));
} }
} }

View File

@ -14,6 +14,7 @@
package de.bixilon.minosoft.protocol.protocol; package de.bixilon.minosoft.protocol.protocol;
public enum ConnectionReason { public enum ConnectionReason {
DNS,
PING, PING,
GET_VERSION, GET_VERSION,
CONNECT CONNECT

View File

@ -20,7 +20,8 @@ public enum ConnectionState {
LOGIN(2), LOGIN(2),
PLAY(3), PLAY(3),
DISCONNECTING(5), DISCONNECTING(5),
DISCONNECTED(6); DISCONNECTED(6),
FAILED(7);
final int id; final int id;

View File

@ -16,6 +16,8 @@ package de.bixilon.minosoft.protocol.protocol;
public final class ProtocolDefinition { public final class ProtocolDefinition {
public static final int STRING_MAX_LEN = 32767; public static final int STRING_MAX_LEN = 32767;
public static final int DEFAULT_PORT = 25565; 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 int PROTOCOL_PACKET_MAX_SIZE = 2097152;
public static final float ANGLE_CALCULATION_CONSTANT = 360.0F / 256.0F; public static final float ANGLE_CALCULATION_CONSTANT = 360.0F / 256.0F;

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<ServerAddress> getServerAddresses(String hostname) throws TextParseException {
ServerAddress fallbackAddress = getServerAddress(hostname);
ArrayList<ServerAddress> 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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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();
}
}

View File

@ -17,6 +17,5 @@ account:
# this will be removed soon, only for debugging (pre alpha stage): some features are not implemented yet -/- # this will be removed soon, only for debugging (pre alpha stage): some features are not implemented yet -/-
debug: debug:
host: "127.0.0.1" server: "127.0.0.1"
port: 25565
version: -1 version: -1