Skip to content

Commit 3ef30d3

Browse files
committed
fix host resolve
1 parent 111dcd9 commit 3ef30d3

File tree

3 files changed

+71
-18
lines changed

3 files changed

+71
-18
lines changed

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ dependencies {
3434
api("com.squareup.moshi:moshi-kotlin:1.15.2")
3535
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2")
3636

37+
// DNSJava
38+
api("dnsjava:dnsjava:3.6.3")
39+
implementation("org.slf4j:slf4j-nop:2.0.17")
40+
3741
testImplementation(kotlin("test"))
3842
}
3943

src/main/kotlin/tech/aliorpse/mcutils/modules/server/JavaServer.kt

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
66
import kotlinx.coroutines.Dispatchers
77
import kotlinx.coroutines.runBlocking
88
import kotlinx.coroutines.withContext
9+
import org.xbill.DNS.AAAARecord
10+
import org.xbill.DNS.ARecord
11+
import org.xbill.DNS.CNAMERecord
12+
import org.xbill.DNS.Lookup
13+
import org.xbill.DNS.SRVRecord
14+
import org.xbill.DNS.Type
915
import tech.aliorpse.mcutils.model.server.ColorAdapter
1016
import tech.aliorpse.mcutils.model.server.Description
1117
import tech.aliorpse.mcutils.model.server.DescriptionAdapter
@@ -22,7 +28,6 @@ import java.net.IDN
2228
import java.net.InetSocketAddress
2329
import java.net.Socket
2430
import java.nio.charset.StandardCharsets
25-
import javax.naming.directory.InitialDirContext
2631

2732
/**
2833
* Provides functionality to fetch and parse the status of a Java Minecraft server.
@@ -81,20 +86,22 @@ object JavaServer {
8186
): JavaServerStatus = withContext(Dispatchers.IO) {
8287
val asciiHost = IDN.toASCII(host)
8388

84-
val (resolvedHost, resolvedPort) = if (enableSrv) {
89+
val (srvTarget, srvPort) = if (enableSrv) {
8590
resolveSrvRecord(asciiHost) ?: (asciiHost to port)
8691
} else {
8792
asciiHost to port
8893
}
8994

95+
val resolvedHost = resolveToIpOrHost(srvTarget) ?: srvTarget
96+
9097
Socket().use { socket ->
9198
socket.soTimeout = timeout
92-
socket.connect(InetSocketAddress(resolvedHost, resolvedPort), timeout)
99+
socket.connect(InetSocketAddress(resolvedHost, srvPort), timeout)
93100

94101
val out = DataOutputStream(socket.getOutputStream())
95102
val input = DataInputStream(socket.getInputStream())
96103

97-
sendHandshake(out, asciiHost, resolvedPort)
104+
sendHandshake(out, asciiHost, srvPort)
98105
sendStatusRequest(out)
99106

100107
val jsonStr = readStatusResponse(input)
@@ -129,21 +136,63 @@ object JavaServer {
129136
getStatus(host, port, timeout, enableSrv)
130137
}
131138

132-
private fun resolveSrvRecord(host: String): Pair<String, Int>? {
139+
@Suppress("ReturnCount")
140+
fun resolveToIpOrHost(host: String, depth: Int = 5): String? {
141+
if (depth <= 0) return null
142+
143+
try {
144+
// A
145+
val lookupA = Lookup(host, Type.A)
146+
lookupA.run()
147+
val aRecords = lookupA.result.takeIf { lookupA.result == Lookup.SUCCESSFUL }
148+
?.let { lookupA.answers.filterIsInstance<ARecord>() }
149+
150+
if (!aRecords.isNullOrEmpty()) {
151+
return aRecords[0].address.hostAddress
152+
}
153+
154+
// AAAA
155+
val lookupAAAA = Lookup(host, Type.AAAA)
156+
lookupAAAA.run()
157+
val aaaaRecords = lookupAAAA.result.takeIf { lookupAAAA.result == Lookup.SUCCESSFUL }
158+
?.let { lookupAAAA.answers.filterIsInstance<AAAARecord>() }
159+
160+
if (!aaaaRecords.isNullOrEmpty()) {
161+
return aaaaRecords[0].address.hostAddress
162+
}
163+
164+
// CNAME
165+
val lookupCNAME = Lookup(host, Type.CNAME)
166+
lookupCNAME.run()
167+
val cnameRecords = lookupCNAME.result.takeIf { lookupCNAME.result == Lookup.SUCCESSFUL }
168+
?.let { lookupCNAME.answers.filterIsInstance<CNAMERecord>() }
169+
170+
if (!cnameRecords.isNullOrEmpty()) {
171+
val cnameTarget = cnameRecords[0].target.toString(true)
172+
if (cnameTarget != host) {
173+
return resolveToIpOrHost(cnameTarget, depth - 1)
174+
}
175+
}
176+
177+
} catch (_: Exception) {
178+
return null
179+
}
180+
return null
181+
}
182+
183+
fun resolveSrvRecord(host: String): Pair<String, Int>? {
133184
return try {
134-
val dnsContext = InitialDirContext()
135-
val records = dnsContext.getAttributes(
136-
"_minecraft._tcp.$host",
137-
arrayOf("SRV")
138-
).get("SRV")?.toString()?.lines()?.firstOrNull()
139-
140-
records?.let {
141-
val parts = it.trim().split("\\s+".toRegex())
142-
if (parts.size == 4) {
143-
val target = parts[3].removeSuffix(".")
144-
val port = parts[2].toInt()
185+
val lookup = Lookup("_minecraft._tcp.$host", Type.SRV)
186+
lookup.run()
187+
if (lookup.result == Lookup.SUCCESSFUL && lookup.answers.isNotEmpty()) {
188+
val srv = lookup.answers.filterIsInstance<SRVRecord>().minByOrNull { it.priority }
189+
srv?.let {
190+
val target = it.target.toString(true).removeSuffix(".")
191+
val port = it.port
145192
target to port
146-
} else null
193+
}
194+
} else {
195+
null
147196
}
148197
} catch (_: Exception) {
149198
null

src/test/kotlin/tech/aliorpse/mcutils/server/ServerStatusTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import kotlin.test.Test
77
class ServerStatusTest {
88
@Test
99
fun javaGetStatusTest() {
10-
val result = JavaServer.getStatusBlocking("bedrock.mineseed.org")
10+
val result = JavaServer.getStatusBlocking("mc.hypixel.net")
1111

1212
println(result)
1313
assert(result.ping > 0)

0 commit comments

Comments
 (0)