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:
- ```server.example.com```: backend server address
- ```_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.
- ```_u```: username to use in backend connection
- ```viaaas.example.com```: hostname suffix (defined in config)

View File

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

View File

@ -25,16 +25,13 @@ class ProtocolDetectorHandler(val connectionData: ConnectionData) : ChannelDuple
if (address is InetSocketAddress) {
val timeoutRun = ctx.executor().schedule({
mcLogger.warn("Timeout protocol A.D. $address")
hold = false
drainQueue(ctx)
ctx.pipeline().remove(this)
}, 10, TimeUnit.SECONDS)
ProtocolDetector.detectVersion(address)
.whenComplete { protocol, _ ->
ProtocolDetector.detectVersion(address).whenComplete { protocol, _ ->
if (protocol != null && protocol.version != -1) {
connectionData.viaBackServerVer = protocol.version
} else {
connectionData.viaBackServerVer = 47 // fallback
connectionData.viaBackServerVer = -1 // fallback
}
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.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.RateLimiter
import com.viaversion.aas.util.StacklessException
import io.netty.channel.ChannelHandlerContext
@ -64,7 +65,7 @@ class HandshakeState : MinecraftConnectionState {
(handler.data.state as? LoginState)?.also {
it.frontOnline = frontOnline
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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import com.viaversion.aas.webLogger
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.http.cio.websocket.*
import kotlinx.coroutines.future.await
import java.net.URLEncoder
import java.util.*
@ -24,7 +25,6 @@ class WebLogin : WebState {
when (obj.getAsJsonPrimitive("action").asString) {
"offline_login" -> {
// todo add some spam check
val username = obj.get("username").asString.trim()
val uuid = generateOfflinePlayerUuid(username)
@ -51,7 +51,8 @@ class WebLogin : WebState {
if (check.getAsJsonPrimitive("valid").asBoolean) {
val mcIdUser = check.get("username").asString
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)
webClient.ws.send(JsonObject().also {
@ -90,7 +91,13 @@ class WebLogin : WebState {
}
"unlisten_login_requests" -> {
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" -> {
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)));
var mcIdUsername = urlParams.get("username");
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"));
}
if (mcauth_code != null) {
history.replaceState(null, null, "#");
}
// WS url
function defaultWs() {
@ -271,9 +275,25 @@ function addAction(text, onClick) {
actions.appendChild(p);
}
function addListeningList(user) {
let msg = document.createElement("p");
msg.innerText = "Listening to login: " + user;
listening.appendChild(msg);
let p = document.createElement("p");
let head = document.createElement("img");
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) {
let toast = document.createElement("div");
@ -345,6 +365,9 @@ new BroadcastChannel("viaaas-notification").addEventListener("message", handleSW
function listen(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) {
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>
<hr>
<p><span id="actions"></span></p>
<p><span id="listening"></span></p>
<p>Listening to logins from: <span id="listening"></span></p>
</div>
<div id="settings" class="tab-pane fade" aria-labelledby="settings-tab">