diff --git a/src/main/java/cc.fascinated/Main.java b/src/main/java/cc.fascinated/Main.java index 079df56..1b01d49 100644 --- a/src/main/java/cc.fascinated/Main.java +++ b/src/main/java/cc.fascinated/Main.java @@ -34,6 +34,6 @@ public class Main { } log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config - SpringApplication.run(Main.class, args); + SpringApplication.run(Main.class, args); // Start the application } } \ No newline at end of file diff --git a/src/main/java/cc.fascinated/common/DNSUtils.java b/src/main/java/cc.fascinated/common/DNSUtils.java index a67078b..c509975 100644 --- a/src/main/java/cc.fascinated/common/DNSUtils.java +++ b/src/main/java/cc.fascinated/common/DNSUtils.java @@ -1,13 +1,13 @@ package cc.fascinated.common; +import cc.fascinated.model.dns.impl.ARecord; +import cc.fascinated.model.dns.impl.SRVRecord; import lombok.NonNull; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; +import org.xbill.DNS.Lookup; import org.xbill.DNS.Record; -import org.xbill.DNS.*; - -import java.net.InetAddress; -import java.net.InetSocketAddress; +import org.xbill.DNS.Type; /** * @author Braydon @@ -17,43 +17,42 @@ public final class DNSUtils { private static final String SRV_QUERY_PREFIX = "_minecraft._tcp.%s"; /** - * Resolve the hostname to an {@link InetSocketAddress}. + * Get the resolved address and port of the + * given hostname by resolving the SRV records. * * @param hostname the hostname to resolve - * @return the resolved {@link InetSocketAddress} + * @return the resolved address and port, null if none */ @SneakyThrows - public static InetSocketAddress resolveSRV(@NonNull String hostname) { + public static SRVRecord 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; + SRVRecord result = null; for (Record record : records) { - SRVRecord srv = (SRVRecord) record; - host = srv.getTarget().toString().replaceFirst("\\.$", ""); - port = srv.getPort(); + result = new SRVRecord((org.xbill.DNS.SRVRecord) record); } - return host == null ? null : new InetSocketAddress(host, port); + return result; } /** - * Resolve the hostname to an {@link InetAddress}. + * Get the resolved address of the given + * hostname by resolving the A records. * * @param hostname the hostname to resolve - * @return the resolved {@link InetAddress} + * @return the resolved address, null if none */ @SneakyThrows - public static InetAddress resolveA(@NonNull String hostname) { + public static ARecord 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; + ARecord result = null; for (Record record : records) { - address = ((ARecord) record).getAddress(); + result = new ARecord((org.xbill.DNS.ARecord) record); } - return address; + return result; } } \ No newline at end of file diff --git a/src/main/java/cc.fascinated/controller/HomeController.java b/src/main/java/cc.fascinated/controller/HomeController.java index 2ac7993..14aa159 100644 --- a/src/main/java/cc.fascinated/controller/HomeController.java +++ b/src/main/java/cc.fascinated/controller/HomeController.java @@ -12,14 +12,14 @@ public class HomeController { /** * The example UUID. */ - @SuppressWarnings("FieldCanBeLocal") private final String exampleUuid = "eeab5f8a-18dd-4d58-af78-2b3c4543da48"; + private final String exampleServer = "eeab5f8a-18dd-4d58-af78-2b3c4543da48"; @RequestMapping(value = "/") public String home(Model model) { model.addAttribute("player_example_url", Config.INSTANCE.getWebPublicUrl() + "/player/" + exampleUuid); - model.addAttribute("java_server_example_url", Config.INSTANCE.getWebPublicUrl() + "/server/java/play.hypixel.net"); - model.addAttribute("swagger_url", Config.INSTANCE.getWebPublicUrl() + "/swagger-ui.html"); + model.addAttribute("java_server_example_url", Config.INSTANCE.getWebPublicUrl() + "/server/java/" + exampleServer); + model.addAttribute("swagger_url", Config.INSTANCE.getWebPublicUrl() + "/docs"); return "index"; } } diff --git a/src/main/java/cc.fascinated/model/dns/DNSRecord.java b/src/main/java/cc.fascinated/model/dns/DNSRecord.java new file mode 100644 index 0000000..6fb4892 --- /dev/null +++ b/src/main/java/cc.fascinated/model/dns/DNSRecord.java @@ -0,0 +1,29 @@ +package cc.fascinated.model.dns; + +import io.micrometer.common.lang.NonNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter @Getter +@NoArgsConstructor @AllArgsConstructor +public abstract class DNSRecord { + /** + * The type of this record. + */ + @NonNull + private Type type; + + /** + * The TTL (Time To Live) of this record. + */ + private long ttl; + + /** + * Types of a record. + */ + public enum Type { + A, SRV + } +} diff --git a/src/main/java/cc.fascinated/model/dns/impl/ARecord.java b/src/main/java/cc.fascinated/model/dns/impl/ARecord.java new file mode 100644 index 0000000..cbfa379 --- /dev/null +++ b/src/main/java/cc.fascinated/model/dns/impl/ARecord.java @@ -0,0 +1,24 @@ +package cc.fascinated.model.dns.impl; + +import cc.fascinated.model.dns.DNSRecord; +import io.micrometer.common.lang.NonNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.net.InetAddress; + +@Setter @Getter +@NoArgsConstructor +public final class ARecord extends DNSRecord { + /** + * The address of this record, null if unresolved. + */ + private String address; + + public ARecord(@NonNull org.xbill.DNS.ARecord bootstrap) { + super(Type.A, bootstrap.getTTL()); + InetAddress address = bootstrap.getAddress(); + this.address = address == null ? null : address.getHostAddress(); + } +} \ No newline at end of file diff --git a/src/main/java/cc.fascinated/model/dns/impl/SRVRecord.java b/src/main/java/cc.fascinated/model/dns/impl/SRVRecord.java new file mode 100644 index 0000000..fe4f320 --- /dev/null +++ b/src/main/java/cc.fascinated/model/dns/impl/SRVRecord.java @@ -0,0 +1,53 @@ +package cc.fascinated.model.dns.impl; + +import cc.fascinated.model.dns.DNSRecord; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.micrometer.common.lang.NonNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.net.InetSocketAddress; + +@Setter @Getter +@NoArgsConstructor +public final class SRVRecord extends DNSRecord { + /** + * The priority of this record. + */ + private int priority; + + /** + * The weight of this record. + */ + private int weight; + + /** + * The port of this record. + */ + private int port; + + /** + * The target of this record. + */ + @NonNull private String target; + + public SRVRecord(@NonNull org.xbill.DNS.SRVRecord bootstrap) { + super(Type.SRV, bootstrap.getTTL()); + priority = bootstrap.getPriority(); + weight = bootstrap.getWeight(); + port = bootstrap.getPort(); + target = bootstrap.getTarget().toString().replaceFirst("\\.$", ""); + } + + /** + * Get a socket address from + * the target and port. + * + * @return the socket address + */ + @NonNull @JsonIgnore + public InetSocketAddress getSocketAddress() { + return new InetSocketAddress(target, port); + } +} \ No newline at end of file diff --git a/src/main/java/cc.fascinated/model/server/BedrockMinecraftServer.java b/src/main/java/cc.fascinated/model/server/BedrockMinecraftServer.java index 82e5114..b010b95 100644 --- a/src/main/java/cc.fascinated/model/server/BedrockMinecraftServer.java +++ b/src/main/java/cc.fascinated/model/server/BedrockMinecraftServer.java @@ -1,5 +1,6 @@ package cc.fascinated.model.server; +import cc.fascinated.model.dns.DNSRecord; import lombok.*; /** @@ -12,7 +13,7 @@ public final class BedrockMinecraftServer extends MinecraftServer { /** * The unique ID of this server. */ - @EqualsAndHashCode.Include @NonNull private final String uniqueId; + @EqualsAndHashCode.Include @NonNull private final String id; /** * The edition of this server. @@ -29,11 +30,11 @@ public final class BedrockMinecraftServer extends MinecraftServer { */ @NonNull private final GameMode gamemode; - private BedrockMinecraftServer(@NonNull String uniqueId, @NonNull String hostname, String ip, int port, - @NonNull Edition edition, @NonNull Version version, @NonNull Players players, - @NonNull MOTD motd, @NonNull GameMode gamemode) { - super(hostname, ip, port, motd, players); - this.uniqueId = uniqueId; + private BedrockMinecraftServer(@NonNull String id, @NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records, + @NonNull Edition edition, @NonNull Version version, @NonNull Players players, @NonNull MOTD motd, + @NonNull GameMode gamemode) { + super(hostname, ip, port, records, motd, players); + this.id = id; this.edition = edition; this.version = version; this.gamemode = gamemode; @@ -49,14 +50,25 @@ public final class BedrockMinecraftServer extends MinecraftServer { * @return the Bedrock Minecraft server */ @NonNull - public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port, @NonNull String token) { + public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, @NonNull String token) { String[] split = token.split(";"); // Split the token Edition edition = Edition.valueOf(split[0]); Version version = new Version(Integer.parseInt(split[2]), split[3]); Players players = new Players(Integer.parseInt(split[4]), Integer.parseInt(split[5]), null); MOTD motd = MOTD.create(split[1] + "\n" + split[7]); GameMode gameMode = new GameMode(split[8], Integer.parseInt(split[9])); - return new BedrockMinecraftServer(split[6], hostname, ip, port, edition, version, players, motd, gameMode); + return new BedrockMinecraftServer( + split[6], + hostname, + ip, + port, + records, + edition, + version, + players, + motd, + gameMode + ); } /** diff --git a/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java b/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java index a9184a8..e565281 100644 --- a/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java +++ b/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java @@ -4,6 +4,7 @@ import cc.fascinated.Main; import cc.fascinated.common.JavaMinecraftVersion; import cc.fascinated.common.ServerUtils; import cc.fascinated.config.Config; +import cc.fascinated.model.dns.DNSRecord; import cc.fascinated.model.token.JavaServerStatusToken; import com.google.gson.annotations.SerializedName; import lombok.*; @@ -60,14 +61,14 @@ public final class JavaMinecraftServer extends MinecraftServer { private boolean previewsChat; /** - * The mojang banned status of the server. + * The mojang blocked status for the server. */ - private boolean mojangBanned; + private boolean mojangBlocked; - public JavaMinecraftServer(String hostname, String ip, int port, MOTD motd, Players players, @NonNull Version version, - Favicon favicon, ForgeModInfo modInfo, ForgeData forgeData, boolean preventsChatReports, - boolean enforcesSecureChat, boolean previewsChat) { - super(hostname, ip, port, motd, players); + public JavaMinecraftServer(String hostname, String ip, int port, MOTD motd, Players players, DNSRecord[] records, + @NonNull Version version, Favicon favicon, ForgeModInfo modInfo, ForgeData forgeData, + boolean preventsChatReports, boolean enforcesSecureChat, boolean previewsChat) { + super(hostname, ip, port, records, motd, players); this.version = version; this.favicon = favicon; this.modInfo = modInfo; @@ -87,7 +88,7 @@ public final class JavaMinecraftServer extends MinecraftServer { * @return the Java Minecraft server */ @NonNull - public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, @NonNull JavaServerStatusToken token) { + public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, @NonNull JavaServerStatusToken token) { String motdString = token.getDescription() instanceof String ? (String) token.getDescription() : null; if (motdString == null) { // Not a string motd, convert from Json motdString = new TextComponent(ComponentSerializer.parse(Main.GSON.toJson(token.getDescription()))).toLegacyText(); @@ -98,6 +99,7 @@ public final class JavaMinecraftServer extends MinecraftServer { port, MinecraftServer.MOTD.create(motdString), token.getPlayers(), + records, token.getVersion().detailedCopy(), JavaMinecraftServer.Favicon.create(token.getFavicon(), ServerUtils.getAddress(hostname, port)), token.getModInfo(), diff --git a/src/main/java/cc.fascinated/model/server/MinecraftServer.java b/src/main/java/cc.fascinated/model/server/MinecraftServer.java index 6c80e7b..f4df03d 100644 --- a/src/main/java/cc.fascinated/model/server/MinecraftServer.java +++ b/src/main/java/cc.fascinated/model/server/MinecraftServer.java @@ -1,12 +1,14 @@ package cc.fascinated.model.server; import cc.fascinated.common.ColorUtils; +import cc.fascinated.model.dns.DNSRecord; import cc.fascinated.service.pinger.MinecraftServerPinger; import cc.fascinated.service.pinger.impl.BedrockMinecraftServerPinger; import cc.fascinated.service.pinger.impl.JavaMinecraftServerPinger; import io.micrometer.common.lang.NonNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.Setter; import lombok.ToString; import java.util.Arrays; @@ -15,8 +17,10 @@ import java.util.UUID; /** * @author Braydon */ -@AllArgsConstructor @Getter @ToString +@AllArgsConstructor +@Getter @Setter public class MinecraftServer { + /** * The hostname of the server. */ @@ -32,6 +36,11 @@ public class MinecraftServer { */ private final int port; + /** + * The DNS records for the server. + */ + private final DNSRecord[] records; + /** * The motd for the server. */ @@ -40,7 +49,7 @@ public class MinecraftServer { /** * The players on the server. */ - private Players players; + private final Players players; /** * A platform a Minecraft diff --git a/src/main/java/cc.fascinated/service/ServerService.java b/src/main/java/cc.fascinated/service/ServerService.java index fa6d33b..ccdc4a1 100644 --- a/src/main/java/cc.fascinated/service/ServerService.java +++ b/src/main/java/cc.fascinated/service/ServerService.java @@ -5,6 +5,9 @@ import cc.fascinated.common.EnumUtils; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.ResourceNotFoundException; import cc.fascinated.model.cache.CachedMinecraftServer; +import cc.fascinated.model.dns.DNSRecord; +import cc.fascinated.model.dns.impl.ARecord; +import cc.fascinated.model.dns.impl.SRVRecord; import cc.fascinated.model.server.JavaMinecraftServer; import cc.fascinated.model.server.MinecraftServer; import cc.fascinated.repository.MinecraftServerCacheRepository; @@ -13,7 +16,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Optional; @Service @Log4j2 @@ -63,21 +68,32 @@ public class ServerService { return cached.get(); } - // Resolve the SRV record if the platform is Java - InetSocketAddress address = platform == MinecraftServer.Platform.JAVA ? DNSUtils.resolveSRV(hostname) : null; - if (address != null) { - hostname = address.getHostName(); + List records = new ArrayList<>(); // The resolved DNS records for the server + + SRVRecord srvRecord = platform == MinecraftServer.Platform.JAVA ? DNSUtils.resolveSRV(hostname) : null; // Resolve the SRV record + if (srvRecord != null) { // SRV was resolved, use the hostname and port + records.add(srvRecord); // Going to need this for later + InetSocketAddress socketAddress = srvRecord.getSocketAddress(); + hostname = socketAddress.getHostName(); + port = socketAddress.getPort(); + } + + ARecord aRecord = DNSUtils.resolveA(hostname); // Resolve the A record so we can get the IPv4 address + String ip = aRecord == null ? null : aRecord.getAddress(); // Get the IP address + if (ip != null) { // Was the IP resolved? + records.add(aRecord); // Going to need this for later + log.info("Resolved hostname: {} -> {}", hostname, ip); } CachedMinecraftServer server = new CachedMinecraftServer( key, - platform.getPinger().ping(hostname, port), + platform.getPinger().ping(hostname, ip, port, records.toArray(new DNSRecord[0])), System.currentTimeMillis() ); // Check if the server is blocked by Mojang if (platform == MinecraftServer.Platform.JAVA) { - ((JavaMinecraftServer) server.getServer()).setMojangBanned(mojangService.isServerBlocked(hostname)); + ((JavaMinecraftServer) server.getServer()).setMojangBlocked(mojangService.isServerBlocked(hostname)); } log.info("Found server: {}:{}", hostname, port); diff --git a/src/main/java/cc.fascinated/service/pinger/MinecraftServerPinger.java b/src/main/java/cc.fascinated/service/pinger/MinecraftServerPinger.java index 2a14ed7..d0daac9 100644 --- a/src/main/java/cc.fascinated/service/pinger/MinecraftServerPinger.java +++ b/src/main/java/cc.fascinated/service/pinger/MinecraftServerPinger.java @@ -1,5 +1,6 @@ package cc.fascinated.service.pinger; +import cc.fascinated.model.dns.DNSRecord; import cc.fascinated.model.server.MinecraftServer; /** @@ -7,5 +8,5 @@ import cc.fascinated.model.server.MinecraftServer; * @param the type of server to ping */ public interface MinecraftServerPinger { - T ping(String hostname, int port); + T ping(String hostname, String ip, int port, DNSRecord[] records); } \ No newline at end of file diff --git a/src/main/java/cc.fascinated/service/pinger/impl/BedrockMinecraftServerPinger.java b/src/main/java/cc.fascinated/service/pinger/impl/BedrockMinecraftServerPinger.java index 8ca1aa5..6f81533 100644 --- a/src/main/java/cc.fascinated/service/pinger/impl/BedrockMinecraftServerPinger.java +++ b/src/main/java/cc.fascinated/service/pinger/impl/BedrockMinecraftServerPinger.java @@ -1,17 +1,19 @@ package cc.fascinated.service.pinger.impl; -import cc.fascinated.common.DNSUtils; import cc.fascinated.common.packet.impl.bedrock.BedrockPacketUnconnectedPing; import cc.fascinated.common.packet.impl.bedrock.BedrockPacketUnconnectedPong; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.ResourceNotFoundException; +import cc.fascinated.model.dns.DNSRecord; import cc.fascinated.model.server.BedrockMinecraftServer; import cc.fascinated.service.pinger.MinecraftServerPinger; -import lombok.NonNull; import lombok.extern.log4j.Log4j2; import java.io.IOException; -import java.net.*; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; /** * The {@link MinecraftServerPinger} for pinging @@ -31,12 +33,7 @@ public final class BedrockMinecraftServerPinger implements MinecraftServerPinger * @return the server that was pinged */ @Override - public BedrockMinecraftServer ping(@NonNull String hostname, int port) { - InetAddress inetAddress = DNSUtils.resolveA(hostname); // Resolve the hostname to an IP address - String ip = inetAddress == null ? null : inetAddress.getHostAddress(); // Get the IP address - if (ip != null) { // Was the IP resolved? - log.info("Resolved hostname: {} -> {}", hostname, ip); - } + public BedrockMinecraftServer ping(String hostname, String ip, int port, DNSRecord[] records) { log.info("Pinging {}:{}...", hostname, port); long before = System.currentTimeMillis(); // Timestamp before pinging @@ -58,7 +55,7 @@ public final class BedrockMinecraftServerPinger implements MinecraftServerPinger if (response == null) { // No pong response throw new ResourceNotFoundException("Server didn't respond to ping"); } - return BedrockMinecraftServer.create(hostname, ip, port, response); // Return the server + return BedrockMinecraftServer.create(hostname, ip, port, records, response); // Return the server } catch (IOException ex) { if (ex instanceof UnknownHostException) { throw new BadRequestException("Unknown hostname: %s".formatted(hostname)); diff --git a/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java b/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java index 0492c16..7d2328b 100644 --- a/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java +++ b/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java @@ -1,13 +1,13 @@ package cc.fascinated.service.pinger.impl; import cc.fascinated.Main; -import cc.fascinated.common.DNSUtils; import cc.fascinated.common.JavaMinecraftVersion; import cc.fascinated.common.ServerUtils; import cc.fascinated.common.packet.impl.java.JavaPacketHandshakingInSetProtocol; import cc.fascinated.common.packet.impl.java.JavaPacketStatusInStart; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.ResourceNotFoundException; +import cc.fascinated.model.dns.DNSRecord; import cc.fascinated.model.server.JavaMinecraftServer; import cc.fascinated.model.token.JavaServerStatusToken; import cc.fascinated.service.pinger.MinecraftServerPinger; @@ -26,12 +26,7 @@ public final class JavaMinecraftServerPinger implements MinecraftServerPinger {}", hostname, ip); - } + public JavaMinecraftServer ping(String hostname, String ip, int port, DNSRecord[] records) { log.info("Pinging {}:{}...", hostname, port); // Open a socket connection to the server @@ -49,7 +44,7 @@ public final class JavaMinecraftServerPinger implements MinecraftServerPinger