idk, srv, trying http

This commit is contained in:
creeper123123321 2020-09-20 12:43:26 -03:00
parent 9e45c31465
commit f9fc21dc40
8 changed files with 297 additions and 76 deletions

View File

@ -1,2 +0,0 @@
<doctype html>
<!-- insert here online mode auth code with wss -->

View File

@ -23,12 +23,21 @@ repositories {
} }
dependencies { dependencies {
implementation("us.myles:viaversion:3.1.1-SNAPSHOT") implementation("us.myles:viaversion:3.1.1")
implementation("nl.matsv:viabackwards-all:3.1.1-SNAPSHOT") implementation("nl.matsv:viabackwards-all:3.1.1")
implementation("de.gerrygames:viarewind-all:1.5.1") implementation("de.gerrygames:viarewind-all:1.5.1")
implementation("net.md-5:bungeecord-chat:1.16-R0.3")
implementation("io.netty:netty-all:4.1.51.Final") implementation("io.netty:netty-all:4.1.51.Final")
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
val ktorVersion = "1.4.0"
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-websockets:$ktorVersion")
implementation("ch.qos.logback:logback-classic:1.2.3")
testCompile("io.ktor:ktor-server-test-host:$ktorVersion")
} }
val run: JavaExec by tasks val run: JavaExec by tasks
run.standardInput = System.`in` run.standardInput = System.`in`

View File

@ -24,25 +24,25 @@ object ChannelInit : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) { override fun initChannel(ch: Channel) {
val user = UserConnection(ch) val user = UserConnection(ch)
CloudPipeline(user) CloudPipeline(user)
ch.pipeline().addLast("frame-encoder", FrameEncoder) ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addLast("frame-encoder", FrameEncoder)
.addLast("frame-decoder", FrameDecoder()) .addLast("frame-decoder", FrameDecoder())
.addLast("compress", CloudCompressor()) .addLast("compress", CloudCompressor())
.addLast("decompress", CloudDecompressor()) .addLast("decompress", CloudDecompressor())
.addLast("flow-handler", FlowControlHandler())
.addLast("via-encoder", CloudEncodeHandler(user)) .addLast("via-encoder", CloudEncodeHandler(user))
.addLast("via-decoder", CloudDecodeHandler(user)) .addLast("via-decoder", CloudDecodeHandler(user))
.addLast("flow-handler", FlowControlHandler())
.addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addLast("handler", CloudSideForwarder(user, null)) .addLast("handler", CloudSideForwarder(user, null))
} }
} }
class BackendInit(val user: UserConnection) : ChannelInitializer<Channel>() { class BackendInit(val user: UserConnection) : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) { override fun initChannel(ch: Channel) {
ch.pipeline().addLast("frame-encoder", FrameEncoder) ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addLast("frame-encoder", FrameEncoder)
.addLast("frame-decoder", FrameDecoder()) .addLast("frame-decoder", FrameDecoder())
.addLast("compress", CloudCompressor()) .addLast("compress", CloudCompressor())
.addLast("decompress", CloudDecompressor()) .addLast("decompress", CloudDecompressor())
.addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addLast("handler", CloudSideForwarder(user, null)) .addLast("handler", CloudSideForwarder(user, null))
} }
} }
@ -57,7 +57,7 @@ class CloudDecompressor(var threshold: Int = -1) : MessageToMessageDecoder<ByteB
out.add(input.retain()) out.add(input.retain())
return return
} }
if (input.readableBytes() != 0) { if (input.isReadable) {
val outLength = Type.VAR_INT.readPrimitive(input) val outLength = Type.VAR_INT.readPrimitive(input)
if (outLength == 0) { if (outLength == 0) {
out.add(input.readBytes(input.readableBytes())) out.add(input.readBytes(input.readableBytes()))

View File

@ -12,10 +12,12 @@ import us.myles.ViaVersion.packets.State
import java.util.logging.Logger import java.util.logging.Logger
val logger = Logger.getLogger("CloudHandler") val logger = Logger.getLogger("CloudHandler")
class CloudSideForwarder(val userConnection: UserConnection, var other: Channel?) : SimpleChannelInboundHandler<ByteBuf>() {
class CloudSideForwarder(val userConnection: UserConnection, var other: Channel?) : SimpleChannelInboundHandler<ByteBuf>() {
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) { override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
other?.write(msg.retain()) if (!userConnection.isPendingDisconnect) {
other!!.write(msg.retain())
}
} }
override fun channelInactive(ctx: ChannelHandlerContext) { override fun channelInactive(ctx: ChannelHandlerContext) {
@ -39,17 +41,17 @@ class CloudSideForwarder(val userConnection: UserConnection, var other: Channel?
cause.printStackTrace() cause.printStackTrace()
} }
fun disconnect(s: String) { fun disconnect(s: String) {
if (userConnection.channel?.isActive != true) return if (userConnection.channel?.isActive != true) return
val msg = "[VIAaaS] $s";
logger.info("Disconnecting " + userConnection.channel!!.remoteAddress() + ": " + s) logger.info("Disconnecting " + userConnection.channel!!.remoteAddress() + ": " + s)
when (userConnection.protocolInfo!!.state) { when (userConnection.protocolInfo!!.state) {
State.LOGIN -> { State.LOGIN -> {
val packet = ByteBufAllocator.DEFAULT.buffer() val packet = ByteBufAllocator.DEFAULT.buffer()
try { try {
packet.writeByte(0) // id 0 disconnect packet.writeByte(0) // id 0 disconnect
Type.STRING.write(packet, Gson().toJson(msg)) Type.STRING.write(packet, Gson().toJson("[VIAaaS] §c$s"))
userConnection.sendRawPacketFuture(packet.retain()).addListener { userConnection.channel?.close() } userConnection.sendRawPacketFuture(packet.retain()).addListener { userConnection.channel?.close() }
} finally { } finally {
packet.release() packet.release()
@ -60,8 +62,7 @@ class CloudSideForwarder(val userConnection: UserConnection, var other: Channel?
try { try {
packet.writeByte(0) // id 0 disconnect packet.writeByte(0) // id 0 disconnect
Type.STRING.write(packet, """{"version": {"name": "VIAaaS","protocol": -1}, Type.STRING.write(packet, """{"version": {"name": "VIAaaS","protocol": -1},
"players": {"max": 0,"online": 0,"sample": []}, "players": {"max": 0,"online": 0,"sample": []},"description": {"text": ${Gson().toJson("§c$s")}}}""")
"description": {"text": ${Gson().toJson(msg)}}}""")
userConnection.sendRawPacketFuture(packet.retain()).addListener { userConnection.channel?.close() } userConnection.sendRawPacketFuture(packet.retain()).addListener { userConnection.channel?.close() }
} finally { } finally {
packet.release() packet.release()

View File

@ -3,18 +3,25 @@ package com.github.creeper123123321.viaaas
import io.netty.bootstrap.Bootstrap import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBufAllocator import io.netty.buffer.ByteBufAllocator
import io.netty.channel.Channel import io.netty.channel.Channel
import io.netty.channel.ChannelOption
import io.netty.channel.socket.SocketChannel import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel import io.netty.channel.socket.nio.NioSocketChannel
import us.myles.ViaVersion.api.PacketWrapper import us.myles.ViaVersion.api.PacketWrapper
import us.myles.ViaVersion.api.Via import us.myles.ViaVersion.api.Via
import us.myles.ViaVersion.api.data.UserConnection import us.myles.ViaVersion.api.data.UserConnection
import us.myles.ViaVersion.api.protocol.* import us.myles.ViaVersion.api.protocol.Protocol
import us.myles.ViaVersion.api.protocol.ProtocolPipeline
import us.myles.ViaVersion.api.protocol.ProtocolRegistry
import us.myles.ViaVersion.api.protocol.SimpleProtocol
import us.myles.ViaVersion.api.remapper.PacketRemapper import us.myles.ViaVersion.api.remapper.PacketRemapper
import us.myles.ViaVersion.api.type.Type import us.myles.ViaVersion.api.type.Type
import us.myles.ViaVersion.packets.State import us.myles.ViaVersion.packets.State
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.util.logging.Logger import java.util.logging.Logger
import javax.naming.NameNotFoundException
import javax.naming.directory.InitialDirContext
class CloudPipeline(userConnection: UserConnection) : ProtocolPipeline(userConnection) { class CloudPipeline(userConnection: UserConnection) : ProtocolPipeline(userConnection) {
override fun registerPackets() { override fun registerPackets() {
@ -35,63 +42,42 @@ object CloudHandlerProtocol : SimpleProtocol() {
this.registerIncoming(State.HANDSHAKE, 0, 0, object : PacketRemapper() { this.registerIncoming(State.HANDSHAKE, 0, 0, object : PacketRemapper() {
override fun registerMap() { override fun registerMap() {
handler { wrapper: PacketWrapper -> handler { wrapper: PacketWrapper ->
wrapper.cancel()
val playerVer = wrapper.passthrough(Type.VAR_INT) val playerVer = wrapper.passthrough(Type.VAR_INT)
val addr = wrapper.passthrough(Type.STRING) // Server Address val addr = wrapper.passthrough(Type.STRING) // Server Address
wrapper.passthrough(Type.UNSIGNED_SHORT) wrapper.passthrough(Type.UNSIGNED_SHORT)
val nextState = wrapper.passthrough(Type.VAR_INT) val nextState = wrapper.passthrough(Type.VAR_INT)
val addrParts = addr.split(0.toChar())[0].split(".") val parsed = ViaaaSAddress().parse(addr)
var foundDomain = false
var foundOptions = false
var port = 25565
var online = true // todo implement this between proxy and player
var backProtocol = 47 // todo auto protocol
var backAddr = ""
addrParts.reversed().forEach {
if (foundDomain) {
if (!foundOptions) {
if (it.startsWith("_")) {
val arg = it.substring(2)
when {
it.startsWith("_p", ignoreCase = true) -> port = arg.toInt()
it.startsWith("_o", ignoreCase = true) -> online = arg.toBoolean()
it.startsWith("_v", ignoreCase = true) -> {
try {
backProtocol = Integer.parseInt(arg)
} catch (e: NumberFormatException) {
val closest = ProtocolVersion.getClosest(arg.replace("_", "."))
if (closest != null) {
backProtocol = closest.id
}
}
}
}
} else {
foundOptions = true
}
}
if (foundOptions) {
backAddr = "$it.$backAddr"
}
} else if (it.equals("viaaas", ignoreCase = true)) {
foundDomain = true
}
}
backAddr = backAddr.replace(Regex("\\.$"), "")
logger.info("connecting ${wrapper.user().channel!!.remoteAddress()} ($playerVer) to $backAddr:$port ($backProtocol)") logger.info("connecting ${wrapper.user().channel!!.remoteAddress()} ($playerVer) to ${parsed.realAddress}:${parsed.port} (${parsed.protocol})")
wrapper.user().channel!!.setAutoRead(false) wrapper.user().channel!!.setAutoRead(false)
wrapper.user().put(CloudData( wrapper.user().put(CloudData(
backendVer = backProtocol, backendVer = parsed.protocol,
userConnection = wrapper.user(), userConnection = wrapper.user(),
frontOnline = online frontOnline = parsed.online
)) ))
Via.getPlatform().runAsync { Via.getPlatform().runAsync {
val frontForwarder = wrapper.user().channel!!.pipeline().get(CloudSideForwarder::class.java) val frontForwarder = wrapper.user().channel!!.pipeline().get(CloudSideForwarder::class.java)
try { try {
val socketAddr = InetSocketAddress(InetAddress.getByName(backAddr), port) var srvResolvedAddr = parsed.realAddress
var srvResolvedPort = parsed.port
if (srvResolvedPort == 25565) {
try {
// https://github.com/GeyserMC/Geyser/blob/99e72f35b308542cf0dbfb5b58816503c3d6a129/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
val ctx = InitialDirContext()
val attr = ctx.getAttributes("dns:///_minecraft._tcp.${parsed.realAddress}", arrayOf("SRV"))["SRV"]
if (attr != null && attr.size() > 0) {
val record = (attr.get(0) as String).split(" ").toTypedArray()
srvResolvedAddr = record[3]
srvResolvedPort = record[2].toInt()
}
} catch (ignored: NameNotFoundException) {
}
}
val socketAddr = InetSocketAddress(InetAddress.getByName(srvResolvedAddr), srvResolvedPort)
val addrInfo = socketAddr.address val addrInfo = socketAddr.address
if (addrInfo.isSiteLocalAddress if (addrInfo.isSiteLocalAddress
|| addrInfo.isLoopbackAddress || addrInfo.isLoopbackAddress
@ -100,6 +86,7 @@ object CloudHandlerProtocol : SimpleProtocol() {
val bootstrap = Bootstrap().handler(BackendInit(wrapper.user())) val bootstrap = Bootstrap().handler(BackendInit(wrapper.user()))
.channel(NioSocketChannel::class.java) .channel(NioSocketChannel::class.java)
.group(wrapper.user().channel!!.eventLoop()) .group(wrapper.user().channel!!.eventLoop())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
.connect(socketAddr) .connect(socketAddr)
bootstrap.addListener { bootstrap.addListener {
@ -110,28 +97,23 @@ object CloudHandlerProtocol : SimpleProtocol() {
frontForwarder.other = chann frontForwarder.other = chann
val backHandshake = ByteBufAllocator.DEFAULT.buffer() val backHandshake = ByteBufAllocator.DEFAULT.buffer()
try { try {
val nullParts = addr.split(0.toChar())
backHandshake.writeByte(0) // Packet 0 handshake backHandshake.writeByte(0) // Packet 0 handshake
val connProto = val connProto = if (ProtocolRegistry.getProtocolPath(playerVer, parsed.protocol) != null) parsed.protocol else playerVer
if (ProtocolRegistry.getProtocolPath(playerVer, backProtocol) != null) {
backProtocol
} else playerVer
Type.VAR_INT.writePrimitive(backHandshake, connProto) Type.VAR_INT.writePrimitive(backHandshake, connProto)
val nullPos = addr.indexOf(0.toChar()) Type.STRING.write(backHandshake, srvResolvedAddr + (if (nullParts.size == 2) 0.toChar() + nullParts[1] else "")) // Server Address
Type.STRING.write(backHandshake, backAddr backHandshake.writeShort(srvResolvedPort)
+ (if (nullPos != -1) addr.substring(nullPos) else "")) // Server Address
backHandshake.writeShort(port)
Type.VAR_INT.writePrimitive(backHandshake, nextState) Type.VAR_INT.writePrimitive(backHandshake, nextState)
chann.writeAndFlush(backHandshake.retain()) chann.writeAndFlush(backHandshake.retain())
} finally { } finally {
backHandshake.release() backHandshake.release()
} }
wrapper.user().channel!!.setAutoRead(true)
} else { } else {
wrapper.user().channel!!.eventLoop().submit { wrapper.user().channel!!.eventLoop().submit {
frontForwarder.disconnect("Couldn't connect: " + it.cause().toString()) frontForwarder.disconnect("Couldn't connect: " + it.cause().toString())
} }
} }
wrapper.user().channel!!.setAutoRead(true)
} }
} catch (e: Exception) { } catch (e: Exception) {
wrapper.user().channel!!.eventLoop().submit { wrapper.user().channel!!.eventLoop().submit {
@ -156,9 +138,9 @@ object CloudHandlerProtocol : SimpleProtocol() {
pipe.get(CloudCompressor::class.java).threshold = threshold pipe.get(CloudCompressor::class.java).threshold = threshold
pipe.get(CloudDecompressor::class.java).threshold = threshold pipe.get(CloudDecompressor::class.java).threshold = threshold
val backPipe = pipe.get(CloudSideForwarder::class.java).other?.pipeline() val backPipe = pipe.get(CloudSideForwarder::class.java).other!!.pipeline()
backPipe?.get(CloudCompressor::class.java)?.threshold = threshold backPipe.get(CloudCompressor::class.java)?.threshold = threshold
backPipe?.get(CloudDecompressor::class.java)?.threshold = threshold backPipe.get(CloudDecompressor::class.java)?.threshold = threshold
} }
} }
}) })

View File

@ -1,16 +1,31 @@
package com.github.creeper123123321.viaaas package com.github.creeper123123321.viaaas
import de.gerrygames.viarewind.api.ViaRewindConfigImpl import de.gerrygames.viarewind.api.ViaRewindConfigImpl
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.cio.websocket.*
import io.ktor.http.content.*
import io.ktor.network.tls.certificates.*
import io.ktor.routing.*
import io.ktor.server.netty.*
import io.ktor.websocket.*
import io.netty.bootstrap.ServerBootstrap import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel
import kotlinx.coroutines.channels.consumeEach
import us.myles.ViaVersion.ViaManager import us.myles.ViaVersion.ViaManager
import us.myles.ViaVersion.api.Via import us.myles.ViaVersion.api.Via
import us.myles.ViaVersion.api.data.MappingDataLoader import us.myles.ViaVersion.api.data.MappingDataLoader
import us.myles.ViaVersion.api.protocol.ProtocolVersion
import java.io.File import java.io.File
import java.net.InetAddress
import java.time.Duration
import java.util.concurrent.ConcurrentHashMap
import kotlin.system.exitProcess import kotlin.system.exitProcess
fun main() {
fun main(args: Array<String>) {
val args = args.mapIndexed { i, content -> i to content }.toMap()
Via.init(ViaManager.builder() Via.init(ViaManager.builder()
.injector(CloudInjector) .injector(CloudInjector)
.loader(CloudLoader) .loader(CloudLoader)
@ -30,9 +45,11 @@ fun main() {
val future = ServerBootstrap().group(boss, worker) val future = ServerBootstrap().group(boss, worker)
.channel(NioServerSocketChannel::class.java) .channel(NioServerSocketChannel::class.java)
.childHandler(ChannelInit) .childHandler(ChannelInit)
.bind(25565) .bind(InetAddress.getByName(args[0] ?: "::"), args[1]?.toIntOrNull() ?: 25565)
.addListener { println(it) }
println("Binded minecraft into " + future.sync().channel().localAddress())
Thread { EngineMain.main(arrayOf()) }.start()
loop@ while (true) { loop@ while (true) {
try { try {
@ -55,3 +72,155 @@ fun main() {
exitProcess(0) // todo what's stucking? exitProcess(0) // todo what's stucking?
} }
class ViaaaSAddress {
var protocol = 0
var viaSuffix: String? = null
var realAddress: String? = null
var port: Int = 25565
var online: Boolean = false
fun parse(address: String): ViaaaSAddress {
val parts = address.split('.')
var foundDomain = false
var foundOptions = false
val ourParts = StringBuilder()
val realAddrBuilder = StringBuilder()
for (i in parts.indices.reversed()) {
val part = parts[i]
var realAddrPart = false
if (foundDomain) {
if (!foundOptions) {
if (part.startsWith("_")) {
val arg = part.substring(2)
when {
part.startsWith("_p", ignoreCase = true) -> port = arg.toInt()
part.startsWith("_o", ignoreCase = true) -> online = arg.toBoolean()
part.startsWith("_v", ignoreCase = true) -> {
try {
protocol = arg.toInt()
} catch (e: NumberFormatException) {
val closest = ProtocolVersion.getClosest(arg.replace("_", "."))
if (closest != null) {
protocol = closest.id
}
}
}
}
} else {
foundOptions = true
}
}
if (foundOptions) {
realAddrPart = true
}
} else if (part.equals("viaaas", ignoreCase = true)) {
foundDomain = true
}
if (realAddrPart) {
realAddrBuilder.insert(0, "$part.")
} else {
ourParts.insert(0, "$part.")
}
}
val realAddr = realAddrBuilder.toString().replace("\\.$".toRegex(), "")
val suffix = ourParts.toString().replace("\\.$".toRegex(), "")
if (realAddr.isEmpty()) {
realAddress = address
} else {
realAddress = realAddr
viaSuffix = suffix
}
return this
}
}
fun Application.mainWeb() {
ViaWebApp().apply { main() }
}
class ViaWebApp {
data class WebSession(val id: String)
val server = WebDashboardServer()
fun Application.main() {
install(DefaultHeaders)
install(CallLogging)
install(WebSockets) {
pingPeriod = Duration.ofMinutes(1)
}
routing {
webSocket("/ws") {
server.connected(this)
try {
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
server.onMessage(this, frame.readText())
}
}
} finally {
server.disconnected(this)
}
}
static {
defaultResource("auth.html", "web")
resources("web")
}
}
}
}
class WebDashboardServer {
val clients = ConcurrentHashMap<WebSocketSession, WebClient>()
suspend fun connected(ws: WebSocketSession) {
clients[ws] = WebClient(ws, WebLogin())
}
suspend fun onMessage(ws: WebSocketSession, msg: String) {
val client = clients[ws]!!
client.state.onMessage(client, msg)
}
suspend fun disconnected(ws: WebSocketSession) {
val client = clients[ws]!!
client.state.disconnected(client)
clients.remove(ws)
}
}
data class WebClient(val ws: WebSocketSession, val state: WebState) {
}
interface WebState {
fun onMessage(webClient: WebClient, msg: String)
fun disconnected(webClient: WebClient)
}
class WebLogin : WebState {
override fun onMessage(webClient: WebClient, msg: String) {
TODO("Not yet implemented")
}
override fun disconnected(webClient: WebClient) {
TODO("Not yet implemented")
}
}
object CertificateGenerator {
@JvmStatic
fun main(args: Array<String>) {
val jksFile = File("build/temporary.jks").apply {
parentFile.mkdirs()
}
if (!jksFile.exists()) {
generateCertificate(jksFile) // Generates the certificate
}
}
}

View File

@ -0,0 +1,19 @@
# You can read more about this file: https://ktor.io/servers/configuration.html#hocon-file
ktor {
deployment {
sslPort = 8443
}
application {
modules = [ com.github.creeper123123321.viaaas.ViaaaSKt.mainWeb ]
}
security {
ssl {
keyStore = build/temporary.jks
keyAlias = mykey
keyStorePassword = changeit
privateKeyPassword = changeit
}
}
}

View File

@ -0,0 +1,43 @@
<!doctype html>
<!-- insert here online mode auth code with wss -->
<html>
<head>
<script>
// Global variable to hold the websocket.
var socket = null;
function connect() {
console.log("Begin connect");
socket = new WebSocket("wss://" + window.location.host + "/ws");
socket.onerror = function() {
console.log("socket error");
};
socket.onopen = function() {
console.log("Connected");
};
socket.onclose = function(evt) {
// Try to gather an explanation about why this was closed.
var explanation = "";
if (evt.reason && evt.reason.length > 0) {
explanation = "reason: " + evt.reason;
} else {
explanation = "without a reason specified";
}
console.log("Disconnected with close code " + evt.code + " and " + explanation);
setTimeout(connect, 5000);
};
socket.onmessage = function(event) {
console.log(event.data.toString());
};
}
connect();
</script>
</head>
<body>
</body>
</html>