diff --git a/README.md b/README.md index 7bd3340..98c0308 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # RESTfulMC -A simple, yet useful RESTful API for Minecraft! \ No newline at end of file +A simple, yet useful RESTful API for Minecraft utilizing Springboot. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6ff77ab..b62f60e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,15 @@ + + + + + jitpack.io + https://jitpack.io + + + @@ -71,6 +80,14 @@ compile + + + com.github.dnsjava + dnsjava + v3.5.2 + compile + + org.springframework.boot diff --git a/src/main/java/me/braydon/mc/common/DNSUtils.java b/src/main/java/me/braydon/mc/common/DNSUtils.java new file mode 100644 index 0000000..efb2b45 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/DNSUtils.java @@ -0,0 +1,53 @@ +package me.braydon.mc.common; + +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.xbill.DNS.Record; +import org.xbill.DNS.*; + +import java.net.InetAddress; +import java.net.InetSocketAddress; + +/** + * @author Braydon + */ +@UtilityClass +public final class DNSUtils { + private static final String SRV_QUERY_PREFIX = "_minecraft._tcp.%s"; + + /** + * Resolve the hostname to an {@link InetSocketAddress}. + * + * @param hostname the hostname to resolve + * @return the resolved {@link InetSocketAddress} + */ + @SneakyThrows + public static InetSocketAddress resolveSRV(@NonNull String hostname) { + Record[] records = new Lookup(SRV_QUERY_PREFIX.formatted(hostname), Type.SRV).run(); // Resolve SRV records + if (records == null) { // No records exist + return null; + } + String host = null; + int port = -1; + for (Record record : records) { + SRVRecord srv = (SRVRecord) record; + host = srv.getTarget().toString().replaceFirst("\\.$", ""); + port = srv.getPort(); + } + return host == null ? null : new InetSocketAddress(host, port); + } + + @SneakyThrows + public static InetAddress resolveA(@NonNull String hostname) { + Record[] records = new Lookup(hostname, Type.A).run(); // Resolve A records + if (records == null) { // No records exist + return null; + } + InetAddress address = null; + for (Record record : records) { + address = ((ARecord) record).getAddress(); + } + return address; + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/model/MinecraftServer.java b/src/main/java/me/braydon/mc/model/MinecraftServer.java index 965e5a6..358542d 100644 --- a/src/main/java/me/braydon/mc/model/MinecraftServer.java +++ b/src/main/java/me/braydon/mc/model/MinecraftServer.java @@ -13,9 +13,14 @@ import me.braydon.mc.service.pinger.impl.JavaMinecraftServerPinger; @AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString public class MinecraftServer { /** - * The IP address of this server. + * The hostname of this server. */ - @EqualsAndHashCode.Include @NonNull private final String ip; + @EqualsAndHashCode.Include @NonNull private final String hostname; + + /** + * The IP address of this server, if resolved. + */ + private final String ip; /** * The port of this server. diff --git a/src/main/java/me/braydon/mc/model/server/BedrockMinecraftServer.java b/src/main/java/me/braydon/mc/model/server/BedrockMinecraftServer.java index 5225f4a..3561e91 100644 --- a/src/main/java/me/braydon/mc/model/server/BedrockMinecraftServer.java +++ b/src/main/java/me/braydon/mc/model/server/BedrockMinecraftServer.java @@ -9,7 +9,7 @@ import me.braydon.mc.model.MinecraftServer; * @author Braydon */ public final class BedrockMinecraftServer extends MinecraftServer { - private BedrockMinecraftServer(@NonNull String ip, int port, @NonNull Version version, @NonNull Players players) { - super(ip, port, version, players); + private BedrockMinecraftServer(@NonNull String hostname, String ip, int port, @NonNull Version version, @NonNull Players players) { + super(hostname, ip, port, version, players); } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/model/server/JavaMinecraftServer.java b/src/main/java/me/braydon/mc/model/server/JavaMinecraftServer.java index f0dc699..1f8f7b8 100644 --- a/src/main/java/me/braydon/mc/model/server/JavaMinecraftServer.java +++ b/src/main/java/me/braydon/mc/model/server/JavaMinecraftServer.java @@ -10,11 +10,21 @@ import me.braydon.mc.model.token.JavaServerStatusToken; * @author Braydon */ public final class JavaMinecraftServer extends MinecraftServer { - private JavaMinecraftServer(@NonNull String ip, int port, @NonNull Version version, @NonNull Players players) { - super(ip, port, version, players); + private JavaMinecraftServer(@NonNull String hostname, String ip, int port, @NonNull Version version, @NonNull Players players) { + super(hostname, ip, port, version, players); } - public static JavaMinecraftServer create(@NonNull String ip, int port, @NonNull JavaServerStatusToken token) { - return new JavaMinecraftServer(ip, port, token.getVersion(), token.getPlayers()); + /** + * Create a new Java Minecraft server. + * + * @param hostname the hostname of the server + * @param ip the IP address of the server + * @param port the port of the server + * @param token the status token + * @return the Java Minecraft server + */ + @NonNull + public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, @NonNull JavaServerStatusToken token) { + return new JavaMinecraftServer(hostname, ip, port, token.getVersion(), token.getPlayers()); } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index dec4dab..00a4932 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -2,6 +2,7 @@ package me.braydon.mc.service; import lombok.NonNull; import lombok.extern.log4j.Log4j2; +import me.braydon.mc.common.DNSUtils; import me.braydon.mc.common.EnumUtils; import me.braydon.mc.common.Tuple; import me.braydon.mc.common.UUIDUtils; @@ -21,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; +import java.net.InetSocketAddress; import java.util.Optional; import java.util.UUID; @@ -129,12 +131,28 @@ public final class MojangService { } } - public MinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname) throws InvalidMinecraftServerPlatform { + /** + * Resolve a Minecraft server on the given + * platform with the given hostname. + * + * @param platformName the name of the platform + * @param hostname the hostname of the server + * @return the resolved Minecraft server + * @throws InvalidMinecraftServerPlatform if the platform is invalid + * @throws ResourceNotFoundException if the server isn't found + */ + @NonNull + public MinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname) + throws InvalidMinecraftServerPlatform, ResourceNotFoundException { MinecraftServer.Platform platform = EnumUtils.getEnumConstant(MinecraftServer.Platform.class, platformName.toUpperCase()); if (platform == null) { // Invalid platform throw new InvalidMinecraftServerPlatform(); } - return platform.getPinger().ping(hostname, 25565); + InetSocketAddress address = DNSUtils.resolveSRV(hostname); // Resolve the SRV record + if (address == null) { // No address found + throw new ResourceNotFoundException("No SRV record found for hostname: %s".formatted(hostname)); + } + return platform.getPinger().ping(address.getHostName(), address.getPort()); // Ping the server and return with the response } /** diff --git a/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java b/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java index 89529e1..756d2ab 100644 --- a/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java +++ b/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java @@ -3,6 +3,7 @@ package me.braydon.mc.service.pinger.impl; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import me.braydon.mc.RESTfulMC; +import me.braydon.mc.common.DNSUtils; import me.braydon.mc.common.packet.impl.PacketHandshakingInSetProtocol; import me.braydon.mc.common.packet.impl.PacketStatusInStart; import me.braydon.mc.model.server.JavaMinecraftServer; @@ -12,6 +13,7 @@ import me.braydon.mc.service.pinger.MinecraftServerPinger; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; @@ -34,6 +36,11 @@ public final class JavaMinecraftServerPinger implements MinecraftServerPinger