closes #21 closes #20 closes #18 closes #17 closes #12 update dependencies, better list command

This commit is contained in:
creeper123123321 2020-11-20 12:38:30 -03:00
parent f6bd021f2f
commit ba5f3ae82c
8 changed files with 161 additions and 119 deletions

View File

@ -1,8 +1,9 @@
plugins {
id("com.github.johnrengelman.shadow") version "6.1.0"
id("com.github.ben-manes.versions") version "0.34.0"
id("com.github.ben-manes.versions") version "0.36.0"
id("com.palantir.git-version") version "0.12.3"
application
kotlin("jvm") version "1.4.10"
kotlin("jvm") version "1.4.20"
}
application {
@ -14,8 +15,15 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}
val gitVersion: groovy.lang.Closure<String> by extra
group = "com.github.creeper123123321.viaaas"
version = "0.0.1-SNAPSHOT"
version = "0.0.2-SNAPSHOT+" + try {
gitVersion()
} catch (e: Exception) {
"unknown"
}
extra.set("archivesBaseName", "VIAaaS")
repositories {
@ -37,7 +45,7 @@ dependencies {
implementation("net.minecrell:terminalconsoleappender:1.2.0")
implementation("org.jline:jline-terminal-jansi:3.12.1")
val ktorVersion = "1.4.1"
val ktorVersion = "1.4.2"
implementation(kotlin("stdlib-jdk8"))
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
@ -64,3 +72,11 @@ tasks {
dependsOn(named("dependencyUpdates"))
}
}
tasks.named<ProcessResources>("processResources") {
filesMatching("viaaas_info.json") {
filter<org.apache.tools.ant.filters.ReplaceTokens>("tokens" to mapOf(
"version" to project.property("version")
))
}
}

View File

@ -1,4 +1,4 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists

View File

@ -3,10 +3,11 @@ package com.github.creeper123123321.viaaas
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.Channel
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInitializer
import io.netty.handler.codec.*
import io.netty.handler.codec.ByteToMessageCodec
import io.netty.handler.codec.DecoderException
import io.netty.handler.codec.MessageToMessageCodec
import io.netty.handler.flow.FlowControlHandler
import io.netty.handler.timeout.ReadTimeoutHandler
import us.myles.ViaVersion.api.data.UserConnection
@ -24,37 +25,27 @@ object ChannelInit : ChannelInitializer<Channel>() {
val user = UserConnection(ch)
CloudPipeline(user)
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addLast("encrypt", CloudEncryptor())
.addLast("decrypt", CloudDecryptor())
.addLast("frame-encoder", FrameEncoder)
.addLast("frame-decoder", FrameDecoder())
.addLast("compress", CloudCompressor())
.addLast("decompress", CloudDecompressor())
// "crypto"
.addLast("frame", FrameCodec())
// "compress" / dummy "decompress"
.addLast("flow-handler", FlowControlHandler())
.addLast("via-encoder", CloudEncodeHandler(user))
.addLast("via-decoder", CloudDecodeHandler(user))
.addLast("via-codec", CloudViaCodec(user))
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = true))
}
}
class CloudDecryptor(var cipher: Cipher? = null) : MessageToMessageDecoder<ByteBuf>() {
class CloudCrypto(val cipherDecode: Cipher, var cipherEncode: Cipher) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
val i = msg.readerIndex()
val size = msg.readableBytes()
if (cipher != null) {
msg.writerIndex(i + cipher!!.update(msg.nioBuffer(), msg.nioBuffer(i, cipher!!.getOutputSize(size))))
}
msg.writerIndex(i + cipherDecode.update(msg.nioBuffer(), msg.nioBuffer(i, cipherDecode.getOutputSize(size))))
out.add(msg.retain())
}
}
class CloudEncryptor(var cipher: Cipher? = null) : MessageToMessageEncoder<ByteBuf>() {
override fun encode(ctx: ChannelHandlerContext?, msg: ByteBuf, out: MutableList<Any>) {
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
val i = msg.readerIndex()
val size = msg.readableBytes()
if (cipher != null) {
msg.writerIndex(i + cipher!!.update(msg.nioBuffer(), msg.nioBuffer(i, cipher!!.getOutputSize(size))))
}
msg.writerIndex(i + cipherEncode.update(msg.nioBuffer(), msg.nioBuffer(i, cipherEncode.getOutputSize(size))))
out.add(msg.retain())
}
}
@ -62,101 +53,113 @@ class CloudEncryptor(var cipher: Cipher? = null) : MessageToMessageEncoder<ByteB
class BackendInit(val user: UserConnection) : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) {
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addLast("encrypt", CloudEncryptor())
.addLast("decrypt", CloudDecryptor())
.addLast("frame-encoder", FrameEncoder)
.addLast("frame-decoder", FrameDecoder())
.addLast("compress", CloudCompressor())
.addLast("decompress", CloudDecompressor())
// "crypto"
.addLast("frame", FrameCodec())
// compress
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = false))
}
}
class CloudDecompressor(var threshold: Int = -1) : MessageToMessageDecoder<ByteBuf>() {
class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
private val inflater: Inflater = Inflater()
@Throws(Exception::class)
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
if (threshold == -1) {
out.add(input.retain())
return
}
if (input.isReadable) {
val outLength = Type.VAR_INT.readPrimitive(input)
if (outLength == 0) {
out.add(input.readBytes(input.readableBytes()))
} else {
if (outLength < threshold) {
throw DecoderException("Badly compressed packet - size of $outLength is below server threshold of $threshold")
}
if (outLength > 2097152) {
throw DecoderException("Badly compressed packet - size of $outLength is larger than protocol maximum of 2097152")
}
val temp = ByteArray(input.readableBytes())
input.readBytes(temp)
inflater.setInput(temp)
val output = ByteArray(outLength)
inflater.inflate(output)
out.add(Unpooled.wrappedBuffer(output))
inflater.reset()
}
}
}
}
class CloudCompressor(var threshold: Int = -1) : MessageToByteEncoder<ByteBuf>() {
// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
private val buffer = ByteArray(8192)
private val inflater: Inflater = Inflater()// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
private val deflater: Deflater = Deflater()
@Throws(Exception::class)
override fun encode(ctx: ChannelHandlerContext, input: ByteBuf, out: ByteBuf) {
if (threshold == -1) {
out.writeBytes(input)
return
}
override fun encode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
val frameLength = input.readableBytes()
if (frameLength < threshold) {
Type.VAR_INT.writePrimitive(out, 0)
out.writeBytes(input)
} else {
Type.VAR_INT.writePrimitive(out, frameLength)
val outBuf = ctx.alloc().heapBuffer()
try {
if (frameLength < threshold) {
outBuf.writeByte(0)
outBuf.writeBytes(input)
out.add(outBuf.retain())
return
}
Type.VAR_INT.writePrimitive(outBuf, frameLength)
val inBytes = ByteArray(frameLength)
input.readBytes(inBytes)
deflater.setInput(inBytes, 0, frameLength)
deflater.finish()
while (!deflater.finished()) {
val written = deflater.deflate(buffer)
out.writeBytes(buffer, 0, written)
outBuf.ensureWritable(8192)
val wIndex = outBuf.writerIndex()
outBuf.writerIndex(wIndex + deflater.deflate(outBuf.array(),
outBuf.arrayOffset() + wIndex, outBuf.writableBytes()))
}
out.add(outBuf.retain())
} finally {
outBuf.release()
deflater.reset()
}
}
@Throws(Exception::class)
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
if (input.isReadable) {
val outLength = Type.VAR_INT.readPrimitive(input)
if (outLength == 0) {
out.add(input.retain())
return
}
if (outLength < threshold) {
throw DecoderException("Badly compressed packet - size of $outLength is below server threshold of $threshold")
}
if (outLength > 2097152) {
throw DecoderException("Badly compressed packet - size of $outLength is larger than protocol maximum of 2097152")
}
val temp = ByteArray(input.readableBytes())
input.readBytes(temp)
inflater.setInput(temp)
val output = ByteArray(outLength)
inflater.inflate(output)
out.add(Unpooled.wrappedBuffer(output))
inflater.reset()
}
}
}
@ChannelHandler.Sharable
object FrameEncoder : MessageToMessageEncoder<ByteBuf>() {
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
val length = ctx.alloc().buffer(5)
Type.VAR_INT.writePrimitive(length, msg.readableBytes())
out.add(length)
out.add(msg.retain())
}
}
class FrameCodec : ByteToMessageCodec<ByteBuf>() {
val badLength = DecoderException("Invalid length!")
class FrameDecoder : ReplayingDecoder<ByteBuf>() {
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
if (!ctx.channel().isActive) {
input.clear() // Ignore, should prevent DoS https://github.com/SpigotMC/BungeeCord/pull/2908
return
}
val index = input.readerIndex()
var nByte = 0
val result = input.forEachByte {
nByte++
val hasNext = it.toInt().and(0x10000000) != 0
if (nByte > 3) throw badLength
hasNext
}
input.readerIndex(index)
if (result == -1) return // not readable
val length = Type.VAR_INT.readPrimitive(input)
if (length >= 2097152 || length < 0) throw DecoderException("Invalid length!")
if (length >= 2097152 || length < 0) throw badLength
if (!input.isReadable(length)) {
input.readerIndex(index)
return
}
out.add(input.readRetainedSlice(length))
checkpoint()
}
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) {
Type.VAR_INT.writePrimitive(out, msg.readableBytes())
out.writeBytes(msg)
}
}
class CloudDecodeHandler(val info: UserConnection) : MessageToMessageDecoder<ByteBuf>() {
class CloudViaCodec(val info: UserConnection) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
override fun decode(ctx: ChannelHandlerContext, bytebuf: ByteBuf, out: MutableList<Any>) {
if (!info.checkIncomingPacket()) throw CancelDecoderException.generate(null)
if (!info.shouldTransformPacket()) {
@ -171,9 +174,7 @@ class CloudDecodeHandler(val info: UserConnection) : MessageToMessageDecoder<Byt
transformedBuf.release()
}
}
}
class CloudEncodeHandler(val info: UserConnection) : MessageToMessageEncoder<ByteBuf>() {
override fun encode(ctx: ChannelHandlerContext, bytebuf: ByteBuf, out: MutableList<Any>) {
info.checkOutgoingPacket()
if (!info.shouldTransformPacket()) {

View File

@ -3,6 +3,7 @@ package com.github.creeper123123321.viaaas
import com.google.common.net.UrlEscapers
import com.google.gson.Gson
import com.google.gson.JsonObject
import de.gerrygames.viarewind.netty.EmptyChannelHandler
import io.ktor.client.request.*
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
@ -28,7 +29,6 @@ import java.security.PrivateKey
import java.security.PublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.*
import java.util.concurrent.TimeUnit
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@ -221,13 +221,12 @@ class LoginState : MinecraftConnectionState {
val threshold = Type.VAR_INT.readPrimitive(msg)
val backPipe = pipe.get(CloudMinecraftHandler::class.java).other!!.pipeline()
backPipe.get(CloudCompressor::class.java)?.threshold = threshold
backPipe.get(CloudDecompressor::class.java)?.threshold = threshold
backPipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
forward(handler, msg)
pipe.get(CloudCompressor::class.java).threshold = threshold
pipe.get(CloudDecompressor::class.java).threshold = threshold
pipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
pipe.addAfter("frame", "decompress", EmptyChannelHandler()) // ViaRewind compat workaround
}
fun handleCryptoRequest(handler: CloudMinecraftHandler, msg: ByteBuf) {
@ -273,8 +272,7 @@ class LoginState : MinecraftConnectionState {
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
handler.user.channel!!.pipeline().get(CloudEncryptor::class.java).cipher = aesEn
handler.user.channel!!.pipeline().get(CloudDecryptor::class.java).cipher = aesDe
handler.user.channel!!.pipeline().addBefore("frame", "crypto", CloudCrypto(aesDe, aesEn))
generateServerHash(handler.data!!.frontId!!, frontKey, mcCryptoKey.public)
}
@ -302,12 +300,11 @@ class LoginState : MinecraftConnectionState {
handler.data!!.backPublicKey!!
)
if (sessionJoin.first == 0) {
throw IllegalStateException("No browsers listening to this account, connect in /auth.html")
} else {
sessionJoin.second.get(15, TimeUnit.SECONDS)
val backChan = handler.other!!
backChan.eventLoop().submit {
val backChan = handler.other!!
sessionJoin.whenCompleteAsync({ _, throwable ->
if (throwable != null) {
handler.disconnect("Online mode error: $throwable")
} else {
val backMsg = ByteBufAllocator.DEFAULT.buffer()
try {
backMsg.writeByte(1) // Packet id
@ -318,13 +315,13 @@ class LoginState : MinecraftConnectionState {
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
backChan.pipeline().get(CloudEncryptor::class.java).cipher = backAesEn
backChan.pipeline().get(CloudDecryptor::class.java).cipher = backAesDe
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
} finally {
backMsg.release()
}
}
}
}, backChan.eventLoop())
} catch (e: Exception) {
handler.disconnect("Online mode error: $e")
}

View File

@ -25,6 +25,7 @@ import us.myles.ViaVersion.protocols.base.VersionProvider
import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider
import us.myles.ViaVersion.sponge.VersionInfo
import us.myles.ViaVersion.sponge.util.LoggerWrapper
import us.myles.ViaVersion.util.GsonUtil
import us.myles.viaversion.libs.gson.JsonObject
import java.io.File
import java.net.URL
@ -35,6 +36,9 @@ import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.logging.Logger
val viaaasVer = GsonUtil.getGson().fromJson(CloudPlatform::class.java.classLoader.getResourceAsStream("viaaas_info.json")!!
.reader(Charsets.UTF_8).readText(), JsonObject::class.java).get("version").asString
object CloudBackwards : ViaBackwardsPlatform {
val log = LoggerWrapper(LoggerFactory.getLogger("ViaBackwards"))
override fun getDataFolder() = File("config/viabackwards")
@ -60,8 +64,8 @@ object CloudLoader : ViaPlatformLoader {
object CloudCommands : ViaCommandHandler()
object CloudInjector : ViaInjector {
override fun getEncoderName(): String = "via-encoder"
override fun getDecoderName() = "via-decoder"
override fun getEncoderName(): String = "via-codec"
override fun getDecoderName() = "via-codec"
override fun getDump(): JsonObject = JsonObject()
override fun uninject() {
@ -124,7 +128,6 @@ object CloudPlatform : ViaPlatform<Unit> {
override fun runSync(runnable: Runnable): TaskId = CloudTask(eventLoop.submit(runnable))
override fun runSync(p0: Runnable, p1: Long): TaskId = CloudTask(eventLoop.schedule(p0, p1 * 50L, TimeUnit.MILLISECONDS))
override fun runRepeatingSync(p0: Runnable, p1: Long): TaskId = CloudTask(eventLoop.scheduleAtFixedRate(p0, 0, p1 * 50L, TimeUnit.MILLISECONDS))
override fun getPlatformVersion(): String = "VIAaaS"
override fun runAsync(p0: Runnable): TaskId = CloudTask(CompletableFuture.runAsync(p0, executor))
override fun getLogger(): Logger = LoggerWrapper(LoggerFactory.getLogger("ViaVersion"))
override fun getConnectionManager(): ViaConnectionManager = connMan
@ -137,6 +140,7 @@ object CloudPlatform : ViaPlatform<Unit> {
override fun getConfigurationProvider(): ConfigurationProvider = CloudConfig
override fun getPlatformName(): String = "VIAaaS"
override fun getPlatformVersion(): String = viaaasVer
override fun getPluginVersion(): String = VersionInfo.VERSION
override fun isOldClientsAllowed(): Boolean = true
override fun isProxy(): Boolean = true

View File

@ -49,7 +49,7 @@ val viaaasLogger = LoggerFactory.getLogger("VIAaaS")
val httpClient = HttpClient {
defaultRequest {
header("User-Agent", "VIAaaS")
header("User-Agent", "VIAaaS/$viaaasVer")
}
install(JsonFeature) {
serializer = GsonSerializer()
@ -89,6 +89,10 @@ fun channelSocketFactory(): ChannelFactory<SocketChannel> {
}
fun main(args: Array<String>) {
if (System.getProperty("java.net.preferIPv6Addresses") == null) {
System.setProperty("java.net.preferIPv6Addresses", "true")
}
File("config/https.jks").apply {
parentFile.mkdirs()
if (!exists()) generateCertificate(this)
@ -160,10 +164,17 @@ class VIAaaSConsole : SimpleTerminalConsole(), ViaCommandSender {
commands["?"] = commands["help"]!!
commands["list"] = { suggestion, _, _ ->
if (suggestion == null) {
sendMessage("List of player connections: ")
Via.getPlatform().connectionManager.connections.forEach {
sendMessage("${it.channel?.remoteAddress()} (${it.protocolInfo?.protocolVersion}) -> " +
"(${it.protocolInfo?.serverProtocolVersion}) " +
"${it.channel?.pipeline()?.get(CloudMinecraftHandler::class.java)?.other?.remoteAddress()}")
val pAddr = it.channel?.remoteAddress()
val pVer = it.protocolInfo?.protocolVersion?.let {
ProtocolVersion.getProtocol(it)}
val backName = it.protocolInfo?.username
val backVer = it.protocolInfo?.serverProtocolVersion?.let {
ProtocolVersion.getProtocol(it)}
val backAddr = it.channel?.pipeline()?.get(CloudMinecraftHandler::class.java)?.other?.remoteAddress()
val pName = it.channel?.pipeline()?.get(CloudMinecraftHandler::class.java)?.data?.frontName
sendMessage("$pAddr ($pVer) ($pName) -> ($backVer) ($backName) $backAddr")
}
}
}

View File

@ -20,6 +20,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import us.myles.ViaVersion.api.Via
import java.net.SocketAddress
import java.net.URLEncoder
import java.security.PublicKey
@ -29,6 +30,7 @@ import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlin.collections.set
@ -107,7 +109,8 @@ class WebDashboardServer {
suspend fun requestSessionJoin(id: UUID, name: String, hash: String,
address: SocketAddress, backKey: PublicKey)
: Pair<Int, CompletableFuture<Unit>> {
: CompletableFuture<Unit> {
val future = viaWebServer.pendingSessionHashes.get(hash)
var sent = 0
viaWebServer.listeners[id]?.forEach {
it.ws.send("""{"action": "session_hash_request", "user": "$name", "session_hash": "$hash",
@ -116,7 +119,14 @@ class WebDashboardServer {
it.ws.flush()
sent++
}
return sent to viaWebServer.pendingSessionHashes.get(hash)
if (sent != 0) {
Via.getPlatform().runSync({
future.completeExceptionally(TimeoutException("No response from browser"))
}, 15 * 20)
} else {
future.completeExceptionally(IllegalStateException("No browser listening"))
}
return future
}
suspend fun connected(ws: WebSocketServerSession) {

View File

@ -0,0 +1,3 @@
{
"version": "@version@"
}