use completablefuture with coroutines, autodetector fallback to -1

This commit is contained in:
creeper123123321 2021-04-18 09:06:09 -03:00
parent 8130cb8ede
commit 9107ca0bd6
10 changed files with 87 additions and 66 deletions

View File

@ -66,7 +66,7 @@ Example address: ```server.example.com._p25565._v1_12_2._of._uBACKUSERNAME.viaaa
Address parts: Address parts:
- ```server.example.com```: backend server address - ```server.example.com```: backend server address
- ```_p```: backend port - ```_p```: backend port
- ```_v```: backend version ([protocol id](https://wiki.vg/Protocol_version_numbers) or name with underline instead of dots). ```AUTO``` is default and 1.8 is fallback if it fails. - ```_v```: backend version ([protocol id](https://wiki.vg/Protocol_version_numbers) or name with underline instead of dots). ```AUTO``` is default and ``-1`` is fallback if it fails.
- ```_o```: ```t``` to force online mode in frontend, ```f``` to disable online mode in frontend. If not set, it will be based on backend online mode. - ```_o```: ```t``` to force online mode in frontend, ```f``` to disable online mode in frontend. If not set, it will be based on backend online mode.
- ```_u```: username to use in backend connection - ```_u```: username to use in backend connection
- ```viaaas.example.com```: hostname suffix (defined in config) - ```viaaas.example.com```: hostname suffix (defined in config)

View File

@ -11,7 +11,8 @@ plugins {
} }
application { application {
mainClassName = "com.viaversion.aas.VIAaaSKt" mainClass.set("com.viaversion.aas.VIAaaSKt")
mainClassName = mainClass.get()
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=true") applicationDefaultJvmArgs = listOf("-Dio.ktor.development=true")
} }

View File

@ -25,16 +25,13 @@ class ProtocolDetectorHandler(val connectionData: ConnectionData) : ChannelDuple
if (address is InetSocketAddress) { if (address is InetSocketAddress) {
val timeoutRun = ctx.executor().schedule({ val timeoutRun = ctx.executor().schedule({
mcLogger.warn("Timeout protocol A.D. $address") mcLogger.warn("Timeout protocol A.D. $address")
hold = false
drainQueue(ctx)
ctx.pipeline().remove(this) ctx.pipeline().remove(this)
}, 10, TimeUnit.SECONDS) }, 10, TimeUnit.SECONDS)
ProtocolDetector.detectVersion(address) ProtocolDetector.detectVersion(address).whenComplete { protocol, _ ->
.whenComplete { protocol, _ ->
if (protocol != null && protocol.version != -1) { if (protocol != null && protocol.version != -1) {
connectionData.viaBackServerVer = protocol.version connectionData.viaBackServerVer = protocol.version
} else { } else {
connectionData.viaBackServerVer = 47 // fallback connectionData.viaBackServerVer = -1 // fallback
} }
ctx.pipeline().remove(this) ctx.pipeline().remove(this)

View File

@ -8,6 +8,7 @@ import com.viaversion.aas.packet.Packet
import com.viaversion.aas.packet.handshake.Handshake import com.viaversion.aas.packet.handshake.Handshake
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader import com.google.common.cache.CacheLoader
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.RateLimiter import com.google.common.util.concurrent.RateLimiter
import com.viaversion.aas.util.StacklessException import com.viaversion.aas.util.StacklessException
import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelHandlerContext
@ -64,7 +65,7 @@ class HandshakeState : MinecraftConnectionState {
(handler.data.state as? LoginState)?.also { (handler.data.state as? LoginState)?.also {
it.frontOnline = frontOnline it.frontOnline = frontOnline
it.backName = parsed.username it.backName = parsed.username
it.backAddress = packet.address to packet.port it.backAddress = HostAndPort.fromParts(packet.address, packet.port)
} }
val playerAddr = handler.data.frontHandler.endRemoteAddress val playerAddr = handler.data.frontHandler.endRemoteAddress

View File

@ -1,5 +1,6 @@
package com.viaversion.aas.handler.state package com.viaversion.aas.handler.state
import com.google.common.net.HostAndPort
import com.google.gson.Gson import com.google.gson.Gson
import com.viaversion.aas.* import com.viaversion.aas.*
import com.viaversion.aas.codec.CompressionCodec import com.viaversion.aas.codec.CompressionCodec
@ -13,18 +14,20 @@ import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelHandlerContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import us.myles.ViaVersion.packets.State import us.myles.ViaVersion.packets.State
import java.util.*
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import javax.crypto.Cipher import javax.crypto.Cipher
class LoginState : MinecraftConnectionState { class LoginState : MinecraftConnectionState {
val callbackPlayerId = CompletableFuture<String>() val callbackPlayerId = CompletableFuture<UUID>()
lateinit var frontToken: ByteArray lateinit var frontToken: ByteArray
lateinit var frontServerId: String lateinit var frontServerId: String
var frontOnline: Boolean? = null var frontOnline: Boolean? = null
lateinit var frontName: String lateinit var frontName: String
lateinit var backAddress: Pair<String, Int> lateinit var backAddress: HostAndPort
var backName: String? = null var backName: String? = null
var started = false var started = false
override val state: State override val state: State
@ -64,7 +67,6 @@ class LoginState : MinecraftConnectionState {
if (threshold != -1) { if (threshold != -1) {
pipe.addAfter("frame", "compress", CompressionCodec(threshold)) pipe.addAfter("frame", "compress", CompressionCodec(threshold))
// todo viarewind backend compression
} else if (pipe.get("compress") != null) { } else if (pipe.get("compress") != null) {
pipe.remove("compress") pipe.remove("compress")
} }
@ -91,43 +93,34 @@ class LoginState : MinecraftConnectionState {
if (frontOnline == null) { if (frontOnline == null) {
authenticateOnlineFront(handler.data.frontChannel) authenticateOnlineFront(handler.data.frontChannel)
} }
val frontHandler = handler.data.frontHandler
val backChan = handler.data.backChannel!!
callbackPlayerId.whenComplete { playerId, e -> GlobalScope.launch(Dispatchers.IO) {
if (e != null) return@whenComplete try {
val frontHandler = handler.data.frontHandler val playerId = callbackPlayerId.await()
val backChan = handler.data.backChannel!!
GlobalScope.launch(Dispatchers.IO) { val backKey = generate128Bits()
try { val backHash = generateServerHash(backServerId, backKey, backPublicKey)
val backKey = generate128Bits()
val backHash = generateServerHash(backServerId, backKey, backPublicKey)
viaWebServer.requestSessionJoin( viaWebServer.requestSessionJoin(
parseUndashedId(playerId), playerId,
backName!!, backName!!,
backHash, backHash,
frontHandler.endRemoteAddress, frontHandler.endRemoteAddress,
handler.data.backHandler!!.endRemoteAddress handler.data.backHandler!!.endRemoteAddress
).whenCompleteAsync({ _, throwable -> ).await()
if (throwable != null) {
frontHandler.data.frontChannel.pipeline()
.fireExceptionCaught(throwable)
return@whenCompleteAsync
}
val cryptoResponse = CryptoResponse() val cryptoResponse = CryptoResponse()
cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey) cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey)
cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken) cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken)
forward(frontHandler, cryptoResponse, true) val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE) forward(frontHandler, cryptoResponse, true)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE) backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesDe, backAesEn))
backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesDe, backAesEn)) } catch (e: Exception) {
}, backChan.eventLoop()) frontHandler.data.frontChannel.pipeline().fireExceptionCaught(e)
} catch (e: Exception) {
frontHandler.data.frontChannel.pipeline()
.fireExceptionCaught(e)
}
} }
} }
} }
@ -152,7 +145,7 @@ class LoginState : MinecraftConnectionState {
val profile = hasJoined(frontName, frontHash) val profile = hasJoined(frontName, frontHash)
val id = profile.get("id")!!.asString val id = profile.get("id")!!.asString
callbackPlayerId.complete(id) callbackPlayerId.complete(parseUndashedId(id))
} catch (e: Exception) { } catch (e: Exception) {
callbackPlayerId.completeExceptionally(e) callbackPlayerId.completeExceptionally(e)
} }
@ -169,7 +162,7 @@ class LoginState : MinecraftConnectionState {
backName = backName ?: frontName backName = backName ?: frontName
val connect = { val connect = {
connectBack(handler, backAddress.first, backAddress.second, State.LOGIN) { connectBack(handler, backAddress.host, backAddress.port, State.LOGIN) {
loginStart.username = backName!! loginStart.username = backName!!
send(handler.data.backChannel!!, loginStart, true) send(handler.data.backChannel!!, loginStart, true)
} }
@ -187,7 +180,7 @@ class LoginState : MinecraftConnectionState {
} }
when (frontOnline) { when (frontOnline) {
false -> callbackPlayerId.complete(generateOfflinePlayerUuid(frontName).toString().replace("-", "")) false -> callbackPlayerId.complete(generateOfflinePlayerUuid(frontName))
true -> authenticateOnlineFront(handler.data.frontChannel) // forced true -> authenticateOnlineFront(handler.data.frontChannel) // forced
null -> connect() // Connect then authenticate null -> connect() // Connect then authenticate
} }

View File

@ -28,8 +28,8 @@ data class WebClient(
return server.listeners.put(uuid, this) return server.listeners.put(uuid, this)
} }
fun unlistenId(uuid: UUID) { fun unlistenId(uuid: UUID): Boolean {
server.listeners.remove(uuid, this) server.listeners.remove(uuid, this)
listenedIds.remove(uuid) return listenedIds.remove(uuid)
} }
} }

View File

@ -18,6 +18,7 @@ import io.ktor.client.request.*
import io.ktor.http.cio.websocket.* import io.ktor.http.cio.websocket.*
import io.ktor.websocket.* import io.ktor.websocket.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.time.delay import kotlinx.coroutines.time.delay
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.SocketAddress import java.net.SocketAddress
@ -45,7 +46,7 @@ class WebDashboardServer {
} }
fun checkToken(token: String): UUID? { fun checkToken(token: String): UUID? {
try { return try {
val verified = JWT.require(jwtAlgorithm) val verified = JWT.require(jwtAlgorithm)
.withSubject("mc_account") .withSubject("mc_account")
.withClaim("can_listen", true) .withClaim("can_listen", true)
@ -53,9 +54,9 @@ class WebDashboardServer {
.build() .build()
.verify(token) .verify(token)
return UUID.fromString(verified.getClaim("mc_uuid").asString()) UUID.fromString(verified.getClaim("mc_uuid").asString())
} catch (e: JWTVerificationException) { } catch (e: JWTVerificationException) {
return null null
} }
} }
@ -68,13 +69,11 @@ class WebDashboardServer {
) )
val usernameIdCache = CacheBuilder.newBuilder() val usernameIdCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS) .expireAfterWrite(1, TimeUnit.HOURS)
.build<String, UUID>(CacheLoader.from { name -> .build<String, CompletableFuture<UUID?>>(CacheLoader.from { name ->
runBlocking { GlobalScope.async(Dispatchers.IO) {
withContext(Dispatchers.IO) { httpClient.get<JsonObject?>("https://api.mojang.com/users/profiles/minecraft/$name")
httpClient.get<JsonObject?>("https://api.mojang.com/users/profiles/minecraft/$name") ?.get("id")?.asString?.let { parseUndashedId(it) }
?.get("id")?.asString?.let { parseUndashedId(it) } }.asCompletableFuture()
}
}
}) })
val sessionHashCallbacks = CacheBuilder.newBuilder() val sessionHashCallbacks = CacheBuilder.newBuilder()

View File

@ -10,6 +10,7 @@ import com.viaversion.aas.webLogger
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.cio.websocket.* import io.ktor.http.cio.websocket.*
import kotlinx.coroutines.future.await
import java.net.URLEncoder import java.net.URLEncoder
import java.util.* import java.util.*
@ -24,7 +25,6 @@ class WebLogin : WebState {
when (obj.getAsJsonPrimitive("action").asString) { when (obj.getAsJsonPrimitive("action").asString) {
"offline_login" -> { "offline_login" -> {
// todo add some spam check
val username = obj.get("username").asString.trim() val username = obj.get("username").asString.trim()
val uuid = generateOfflinePlayerUuid(username) val uuid = generateOfflinePlayerUuid(username)
@ -51,7 +51,8 @@ class WebLogin : WebState {
if (check.getAsJsonPrimitive("valid").asBoolean) { if (check.getAsJsonPrimitive("valid").asBoolean) {
val mcIdUser = check.get("username").asString val mcIdUser = check.get("username").asString
val uuid = check.get("uuid")?.asString?.let { parseUndashedId(it.replace("-", "")) } val uuid = check.get("uuid")?.asString?.let { parseUndashedId(it.replace("-", "")) }
?: webClient.server.usernameIdCache.get(mcIdUser) ?: webClient.server.usernameIdCache.get(mcIdUser).await()
?: throw StacklessException("Failed to get UUID from minecraft.id")
val token = webClient.server.generateToken(uuid) val token = webClient.server.generateToken(uuid)
webClient.ws.send(JsonObject().also { webClient.ws.send(JsonObject().also {
@ -90,7 +91,13 @@ class WebLogin : WebState {
} }
"unlisten_login_requests" -> { "unlisten_login_requests" -> {
val uuid = UUID.fromString(obj.getAsJsonPrimitive("uuid").asString) val uuid = UUID.fromString(obj.getAsJsonPrimitive("uuid").asString)
webClient.unlistenId(uuid) webLogger.info("Unlisten: ${webClient.id}: $uuid")
val response = JsonObject().also {
it.addProperty("action", "unlisten_login_requests_result")
it.addProperty("uuid", uuid.toString())
it.addProperty("success", webClient.unlistenId(uuid))
}
webClient.ws.send(response.toString())
} }
"session_hash_response" -> { "session_hash_response" -> {
val hash = obj.get("session_hash").asString val hash = obj.get("session_hash").asString

View File

@ -8,9 +8,13 @@ let urlParams = new URLSearchParams();
window.location.hash.substr(1).split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a))); window.location.hash.substr(1).split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a)));
var mcIdUsername = urlParams.get("username"); var mcIdUsername = urlParams.get("username");
var mcauth_code = urlParams.get("mcauth_code"); var mcauth_code = urlParams.get("mcauth_code");
if (urlParams.get("mcauth_success") == "false") { var mcauth_success = urlParams.get("mcauth_success");
if (mcauth_success == "false") {
addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg")); addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg"));
} }
if (mcauth_code != null) {
history.replaceState(null, null, "#");
}
// WS url // WS url
function defaultWs() { function defaultWs() {
@ -271,9 +275,25 @@ function addAction(text, onClick) {
actions.appendChild(p); actions.appendChild(p);
} }
function addListeningList(user) { function addListeningList(user) {
let msg = document.createElement("p"); let p = document.createElement("p");
msg.innerText = "Listening to login: " + user; let head = document.createElement("img");
listening.appendChild(msg); let n = document.createElement("span");
let remove = document.createElement("a");
n.innerText = " " + user + " ";
remove.innerText = "Unlisten";
remove.href = "javascript:";
remove.onclick = () => {
// todo remove the token
listening.removeChild(p);
unlisten(user);
};
head.className = "account_head";
head.alt = user + "'s head";
head.src = "https://crafthead.net/helm/" + user;
p.append(head);
p.append(n);
p.append(remove);
listening.appendChild(p);
} }
function addToast(title, msg) { function addToast(title, msg) {
let toast = document.createElement("div"); let toast = document.createElement("div");
@ -345,6 +365,9 @@ new BroadcastChannel("viaaas-notification").addEventListener("message", handleSW
function listen(token) { function listen(token) {
socket.send(JSON.stringify({"action": "listen_login_requests", "token": token})); socket.send(JSON.stringify({"action": "listen_login_requests", "token": token}));
} }
function unlisten(id) {
socket.send(JSON.stringify({"action": "unlisten_login_requests", "uuid": id}));
}
function confirmJoin(hash) { function confirmJoin(hash) {
socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash})); socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash}));
} }

View File

@ -63,7 +63,7 @@
<p>CORS Proxy status: <span id="cors_status" class="text-white bg-dark">?</span></p> <p>CORS Proxy status: <span id="cors_status" class="text-white bg-dark">?</span></p>
<hr> <hr>
<p><span id="actions"></span></p> <p><span id="actions"></span></p>
<p><span id="listening"></span></p> <p>Listening to logins from: <span id="listening"></span></p>
</div> </div>
<div id="settings" class="tab-pane fade" aria-labelledby="settings-tab"> <div id="settings" class="tab-pane fade" aria-labelledby="settings-tab">