From b22b2dc458bcbef9a4e0fb8320dac9e58002230e Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Wed, 17 Jan 2024 15:02:36 +0000 Subject: [PATCH 1/4] Migrate to using built-in http client (#1192) --- .../velocitypowered/proxy/VelocityServer.java | 7 ++ .../client/InitialLoginSessionHandler.java | 117 +++++++++--------- 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index f113164b8..ddf302e78 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -71,6 +71,7 @@ import io.netty.channel.EventLoopGroup; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; +import java.net.http.HttpClient; import java.nio.file.Files; import java.nio.file.Path; import java.security.AccessController; @@ -143,6 +144,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { private final ConnectionManager cm; private final ProxyOptions options; + private final HttpClient httpClient; private @MonotonicNonNull VelocityConfiguration configuration; private @MonotonicNonNull KeyPair serverKeyPair; private final ServerMap servers; @@ -168,6 +170,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { scheduler = new VelocityScheduler(pluginManager); console = new VelocityConsole(this); cm = new ConnectionManager(this); + httpClient = HttpClient.newHttpClient(); servers = new ServerMap(this); serverListPingHandler = new ServerListPingHandler(this); this.options = options; @@ -591,6 +594,10 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { this.cm.closeEndpoints(false); } + public HttpClient getHttpClient() { + return httpClient; + } + public AsyncHttpClient getAsyncHttpClient() { return cm.getHttpClient(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 1409dc4ba..53fe21cad 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -42,19 +42,19 @@ import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import io.netty.buffer.ByteBuf; import java.net.InetSocketAddress; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.MessageDigest; import java.util.Arrays; import java.util.Optional; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Response; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** @@ -77,7 +77,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { private boolean forceKeyAuthentication; InitialLoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection, - LoginInboundConnection inbound) { + LoginInboundConnection inbound) { this.server = Preconditions.checkNotNull(server, "server"); this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection"); this.inbound = Preconditions.checkNotNull(inbound, "inbound"); @@ -209,63 +209,60 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { url += "&ip=" + urlFormParameterEscaper().escape(playerIp); } - ListenableFuture hasJoinedResponse = server.getAsyncHttpClient().prepareGet(url) - .execute(); - hasJoinedResponse.addListener(() -> { - if (mcConnection.isClosed()) { - // The player disconnected after we authenticated them. - return; - } - - // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption - // is enabled. - try { - mcConnection.enableEncryption(decryptedSharedSecret); - } catch (GeneralSecurityException e) { - logger.error("Unable to enable encryption for connection", e); - // At this point, the connection is encrypted, but something's wrong on our side and - // we can't do anything about it. - mcConnection.close(true); - return; - } - - try { - Response profileResponse = hasJoinedResponse.get(); - if (profileResponse.getStatusCode() == 200) { - final GameProfile profile = GENERAL_GSON.fromJson(profileResponse.getResponseBody(), - GameProfile.class); - // Not so fast, now we verify the public key for 1.19.1+ - if (inbound.getIdentifiedKey() != null - && inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 - && inbound.getIdentifiedKey() instanceof IdentifiedKeyImpl) { - IdentifiedKeyImpl key = (IdentifiedKeyImpl) inbound.getIdentifiedKey(); - if (!key.internalAddHolder(profile.getId())) { - inbound.disconnect( - Component.translatable("multiplayer.disconnect.invalid_public_key")); - } + HttpRequest httpRequest = HttpRequest.newBuilder().uri(URI.create(url)).build(); + server.getHttpClient().sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) + .whenCompleteAsync((response, throwable) -> { + if (mcConnection.isClosed()) { + // The player disconnected after we authenticated them. + return; } - // All went well, initialize the session. - mcConnection.setActiveSessionHandler(StateRegistry.LOGIN, - new AuthSessionHandler(server, inbound, profile, true)); - } else if (profileResponse.getStatusCode() == 204) { - // Apparently an offline-mode user logged onto this online-mode proxy. - inbound.disconnect( - Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED)); - } else { - // Something else went wrong - logger.error( - "Got an unexpected error code {} whilst contacting Mojang to log in {} ({})", - profileResponse.getStatusCode(), login.getUsername(), playerIp); - inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); - } - } catch (ExecutionException e) { - logger.error("Unable to authenticate with Mojang", e); - inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); - } catch (InterruptedException e) { - // not much we can do usefully - Thread.currentThread().interrupt(); - } - }, mcConnection.eventLoop()); + + if (throwable != null) { + logger.error("Unable to authenticate player", throwable); + inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); + return; + } + + // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption + // is enabled. + try { + mcConnection.enableEncryption(decryptedSharedSecret); + } catch (GeneralSecurityException e) { + logger.error("Unable to enable encryption for connection", e); + // At this point, the connection is encrypted, but something's wrong on our side and + // we can't do anything about it. + mcConnection.close(true); + return; + } + + if (response.statusCode() == 200) { + final GameProfile profile = GENERAL_GSON.fromJson(response.body(), + GameProfile.class); + // Not so fast, now we verify the public key for 1.19.1+ + if (inbound.getIdentifiedKey() != null + && inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 + && inbound.getIdentifiedKey() instanceof IdentifiedKeyImpl) { + IdentifiedKeyImpl key = (IdentifiedKeyImpl) inbound.getIdentifiedKey(); + if (!key.internalAddHolder(profile.getId())) { + inbound.disconnect( + Component.translatable("multiplayer.disconnect.invalid_public_key")); + } + } + // All went well, initialize the session. + mcConnection.setActiveSessionHandler(StateRegistry.LOGIN, + new AuthSessionHandler(server, inbound, profile, true)); + } else if (response.statusCode() == 204) { + // Apparently an offline-mode user logged onto this online-mode proxy. + inbound.disconnect( + Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED)); + } else { + // Something else went wrong + logger.error( + "Got an unexpected error code {} whilst contacting Mojang to log in {} ({})", + response.sslSession(), login.getUsername(), playerIp); + inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); + } + }, mcConnection.eventLoop()); } catch (GeneralSecurityException e) { logger.error("Unable to enable encryption", e); mcConnection.close(true); From 6d7335b21fb25054dbfd871b099a71c0eb6d7c11 Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:20:27 -0500 Subject: [PATCH 2/4] Fully replaced AsyncHttpClient (#1194) --- gradle/libs.versions.toml | 1 - proxy/build.gradle.kts | 1 - .../velocitypowered/proxy/VelocityServer.java | 5 --- .../client/InitialLoginSessionHandler.java | 6 +++- .../proxy/network/ConnectionManager.java | 32 ++++--------------- 5 files changed, 12 insertions(+), 33 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 25f58c3ac..1e7328dfd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,6 @@ spotless = "com.diffplug.spotless:6.12.0" adventure-bom = "net.kyori:adventure-bom:4.15.0-SNAPSHOT" adventure-facet = "net.kyori:adventure-platform-facet:4.3.0" asm = "org.ow2.asm:asm:9.5" -asynchttpclient = "org.asynchttpclient:async-http-client:2.12.3" brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT" bstats = "org.bstats:bstats-base:3.0.1" caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5" diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index 643689d40..b3026977a 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -119,7 +119,6 @@ dependencies { implementation(platform(libs.adventure.bom)) implementation("net.kyori:adventure-nbt") implementation(libs.adventure.facet) - implementation(libs.asynchttpclient) implementation(libs.completablefutures) implementation(libs.nightconfig) implementation(libs.bstats) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index ddf302e78..6efe04e25 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -101,7 +101,6 @@ import net.kyori.adventure.translation.GlobalTranslator; import net.kyori.adventure.translation.TranslationRegistry; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.asynchttpclient.AsyncHttpClient; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; @@ -595,10 +594,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { } public HttpClient getHttpClient() { - return httpClient; - } - - public AsyncHttpClient getAsyncHttpClient() { return cm.getHttpClient(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 53fe21cad..6ba98ae0e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -209,7 +209,11 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { url += "&ip=" + urlFormParameterEscaper().escape(playerIp); } - HttpRequest httpRequest = HttpRequest.newBuilder().uri(URI.create(url)).build(); + final HttpRequest httpRequest = HttpRequest.newBuilder() + .setHeader("User-Agent", + server.getVersion().getName() + "/" + server.getVersion().getVersion()) + .uri(URI.create(url)) + .build(); server.getHttpClient().sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) .whenCompleteAsync((response, throwable) -> { if (mcConnection.isClosed()) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 6175f82e2..23fd55c7d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -17,9 +17,6 @@ package com.velocitypowered.proxy.network; -import static org.asynchttpclient.Dsl.asyncHttpClient; -import static org.asynchttpclient.Dsl.config; - import com.google.common.base.Preconditions; import com.velocitypowered.api.event.proxy.ListenerBoundEvent; import com.velocitypowered.api.event.proxy.ListenerCloseEvent; @@ -37,15 +34,11 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.WriteBufferWaterMark; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; +import java.net.http.HttpClient; import java.util.HashMap; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterContext.FilterContextBuilder; -import org.asynchttpclient.filter.RequestFilter; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -69,7 +62,7 @@ public final class ConnectionManager { public final BackendChannelInitializerHolder backendChannelInitializer; private final SeparatePoolInetNameResolver resolver; - private final AsyncHttpClient httpClient; + private final HttpClient httpClient; /** * Initalizes the {@code ConnectionManager}. @@ -86,20 +79,9 @@ public final class ConnectionManager { this.backendChannelInitializer = new BackendChannelInitializerHolder( new BackendChannelInitializer(this.server)); this.resolver = new SeparatePoolInetNameResolver(GlobalEventExecutor.INSTANCE); - this.httpClient = asyncHttpClient(config() - .setEventLoopGroup(this.workerGroup) - .setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion()) - .addRequestFilter(new RequestFilter() { - @Override - public FilterContext filter(FilterContext ctx) { - return new FilterContextBuilder<>(ctx) - .request(new RequestBuilder(ctx.getRequest()) - .setNameResolver(resolver) - .build()) - .build(); - } - }) - .build()); + this.httpClient = HttpClient.newBuilder() + .executor(this.workerGroup) + .build(); } public void logChannelInformation() { @@ -256,8 +238,8 @@ public final class ConnectionManager { return this.serverChannelInitializer; } - public AsyncHttpClient getHttpClient() { - return httpClient; + public HttpClient getHttpClient() { + return this.httpClient; } public BackendChannelInitializerHolder getBackendChannelInitializer() { From c7c65e33399611dc2813ba661d4a86d6684c9d42 Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:00:39 -0500 Subject: [PATCH 3/4] Fixed unknown status code error (#1196) --- .../proxy/connection/client/InitialLoginSessionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 6ba98ae0e..ed7b3db4b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -263,7 +263,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { // Something else went wrong logger.error( "Got an unexpected error code {} whilst contacting Mojang to log in {} ({})", - response.sslSession(), login.getUsername(), playerIp); + response.statusCode(), login.getUsername(), playerIp); inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); } }, mcConnection.eventLoop()); From eabff2020fa50e1972dca22b24ba18cca4e9e007 Mon Sep 17 00:00:00 2001 From: Aaron <71191102+RealBauHD@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:10:30 +0100 Subject: [PATCH 4/4] fix legacy console disconnect messages (#1197) --- .../proxy/connection/client/ConnectedPlayer.java | 8 +++----- .../connection/client/InitialInboundConnection.java | 10 ++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index e531c2101..9cbc9ac37 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -103,13 +103,12 @@ import net.kyori.adventure.platform.facet.FacetPointers.Type; import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.title.Title.Times; import net.kyori.adventure.title.TitlePart; import net.kyori.adventure.translation.GlobalTranslator; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -126,7 +125,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build(); static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; - private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class); + private static final ComponentLogger logger = ComponentLogger.logger(ConnectedPlayer.class); private final Identity identity = new IdentityImpl(); /** @@ -577,8 +576,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, Component translated = this.translateMessage(reason); if (server.getConfiguration().isLogPlayerConnections()) { - logger.info("{} has disconnected: {}", this, - LegacyComponentSerializer.legacySection().serialize(translated)); + logger.info(Component.text(this + " has disconnected: ").append(translated)); } connection.closeWith(Disconnect.create(translated, this.getProtocolVersion(), duringLogin)); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java index 5a67dab30..d0fde45b3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java @@ -29,10 +29,8 @@ import java.net.InetSocketAddress; import java.util.Locale; import java.util.Optional; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.translation.GlobalTranslator; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Implements {@link InboundConnection} for a newly-established connection. @@ -40,7 +38,8 @@ import org.apache.logging.log4j.Logger; public final class InitialInboundConnection implements VelocityInboundConnection, MinecraftConnectionAssociation { - private static final Logger logger = LogManager.getLogger(InitialInboundConnection.class); + private static final ComponentLogger logger = ComponentLogger + .logger(InitialInboundConnection.class); private final MinecraftConnection connection; private final String cleanedAddress; @@ -97,8 +96,7 @@ public final class InitialInboundConnection implements VelocityInboundConnection Component translated = GlobalTranslator.render(reason, ClosestLocaleMatcher.INSTANCE .lookupClosest(Locale.getDefault())); if (connection.server.getConfiguration().isLogPlayerConnections()) { - logger.info("{} has disconnected: {}", this, - LegacyComponentSerializer.legacySection().serialize(translated)); + logger.info(Component.text(this + " has disconnected: ").append(translated)); } connection.closeWith(Disconnect.create(translated, getProtocolVersion(), true)); }