3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-23 23:00:35 +01:00

Merge remote-tracking branch 'upstream/dev/1.1.0'

Dieser Commit ist enthalten in:
Gabik21 2019-08-29 19:47:04 +02:00
Commit 18a56d8b1d
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: A95DB353715365DF
37 geänderte Dateien mit 708 neuen und 538 gelöschten Zeilen

Datei anzeigen

@ -7,4 +7,4 @@ cache:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
jdk:
- oraclejdk8
- openjdk8

Datei anzeigen

@ -34,15 +34,4 @@ Once you've built Velocity, you can copy and run the `-all` JAR from
and you can configure it from there.
Alternatively, you can get the proxy JAR from the [downloads](https://www.velocitypowered.com/downloads)
page.
## Status
Velocity is currently in beta. Production networks are successfully running
Velocity with many hundreds of concurrent players online, but your mileage
may vary.
Velocity supports Minecraft 1.8-1.14.3. Velocity is best supported with Paper
and SpongeVanilla. Minecraft Forge is fully supported but mod compatibility
may vary. Generally, Velocity will support many mods better than BungeeCord
or Waterfall do but compatibility can not always be ensured.
page.

Datei anzeigen

@ -24,7 +24,7 @@ dependencies {
compile "net.kyori:text-serializer-plain:${textVersion}"
compile 'com.moandjiezana.toml:toml4j:0.7.2'
compile "org.slf4j:slf4j-api:${slf4jVersion}"
compile 'com.google.inject:guice:4.2.0'
compile 'com.google.inject:guice:4.2.2'
compile "org.checkerframework:checker-qual:${checkerFrameworkVersion}"
compile "org.spongepowered:configurate-hocon:${configurateVersion}"

Datei anzeigen

@ -0,0 +1,62 @@
package com.velocitypowered.api.event.player;
import static com.google.common.base.Preconditions.checkNotNull;
import com.velocitypowered.api.proxy.Player;
import java.util.ArrayList;
import java.util.List;
/**
* This event is fired after a tab complete response is sent by the remote server, for clients on
* 1.12.2 and below. You have the opportunity to modify the response sent to the remote player.
*/
public class TabCompleteEvent {
private final Player player;
private final String partialMessage;
private final List<String> suggestions;
/**
* Constructs a new TabCompleteEvent instance.
* @param player the player
* @param partialMessage the partial message
* @param suggestions the initial list of suggestions
*/
public TabCompleteEvent(Player player, String partialMessage, List<String> suggestions) {
this.player = checkNotNull(player, "player");
this.partialMessage = checkNotNull(partialMessage, "partialMessage");
this.suggestions = new ArrayList<>(checkNotNull(suggestions, "suggestions"));
}
/**
* Returns the player requesting the tab completion.
* @return the requesting player
*/
public Player getPlayer() {
return player;
}
/**
* Returns the message being partially completed.
* @return the partial message
*/
public String getPartialMessage() {
return partialMessage;
}
/**
* Returns all the suggestions provided to the user, as a mutable list.
* @return the suggestions
*/
public List<String> getSuggestions() {
return suggestions;
}
@Override
public String toString() {
return "TabCompleteEvent{"
+ "player=" + player
+ ", partialMessage='" + partialMessage + '\''
+ ", suggestions=" + suggestions
+ '}';
}
}

Datei anzeigen

@ -32,7 +32,8 @@ public enum ProtocolVersion {
MINECRAFT_1_14(477, "1.14"),
MINECRAFT_1_14_1(480, "1.14.1"),
MINECRAFT_1_14_2(485, "1.14.2"),
MINECRAFT_1_14_3(490, "1.14.3");
MINECRAFT_1_14_3(490, "1.14.3"),
MINECRAFT_1_14_4(498, "1.14.4");
private final int protocol;
private final String name;

Datei anzeigen

@ -182,6 +182,19 @@ public final class ServerPing {
return this;
}
/**
* Uses the modified {@code mods} list in the response.
* @param mods the mods list to use
* @return this build, for chaining
*/
public Builder mods(ModInfo mods) {
Preconditions.checkNotNull(mods, "mods");
this.modType = mods.getType();
this.mods.clear();
this.mods.addAll(mods.getMods());
return this;
}
public Builder clearMods() {
this.mods.clear();
return this;

Datei anzeigen

@ -20,11 +20,11 @@ allprojects {
ext {
// dependency versions
textVersion = '3.0.1'
textVersion = '3.0.2'
junitVersion = '5.3.0-M1'
slf4jVersion = '1.7.25'
log4jVersion = '2.11.2'
nettyVersion = '4.1.37.Final'
nettyVersion = '4.1.38.Final'
guavaVersion = '25.1-jre'
checkerFrameworkVersion = '2.7.0'
configurateVersion = '3.6'

Datei anzeigen

@ -48,7 +48,6 @@ dependencies {
compile "io.netty:netty-handler:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
compile "io.netty:netty-resolver-dns:${nettyVersion}"
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
@ -57,14 +56,18 @@ dependencies {
compile "org.apache.logging.log4j:log4j-iostreams:${log4jVersion}"
compile 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options
compile 'net.minecrell:terminalconsoleappender:1.1.1'
runtime 'net.java.dev.jna:jna:4.5.2' // Needed for JLine
compile 'net.minecrell:terminalconsoleappender:1.2.0'
runtime 'org.jline:jline-terminal-jansi:3.12.1' // Needed for JLine
runtime 'com.lmax:disruptor:3.4.2' // Async loggers
compile 'it.unimi.dsi:fastutil:8.2.2'
compile 'it.unimi.dsi:fastutil:8.2.3'
compile 'net.kyori:event-method-asm:3.0.0'
compile 'com.mojang:brigadier:1.0.15'
compile 'org.asynchttpclient:async-http-client:2.10.1'
compile 'com.spotify:completable-futures:0.3.2'
testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"

Datei anzeigen

@ -3,12 +3,9 @@ package com.velocitypowered.proxy;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpHeaderNames;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
@ -21,11 +18,14 @@ import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
/**
* bStats collects some data for plugin authors.
@ -185,40 +185,44 @@ public class Metrics {
}
// Compress the data to save bandwidth
ByteBuf reqBody = createResponseBody(data);
server.getHttpClient().post(new URL(URL), reqBody, request -> {
request.headers().add(HttpHeaderNames.CONTENT_ENCODING, "gzip");
request.headers().add(HttpHeaderNames.ACCEPT, "application/json");
request.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/json");
})
.whenCompleteAsync((resp, exc) -> {
if (logFailedRequests) {
if (exc != null) {
logger.error("Unable to send metrics to bStats", exc);
} else if (resp.getCode() != 429) {
logger.error("Got HTTP status code {} when sending metrics to bStats",
resp.getCode());
}
ListenableFuture<Response> future = server.getAsyncHttpClient()
.preparePost(URL)
.addHeader(HttpHeaderNames.CONTENT_ENCODING, "gzip")
.addHeader(HttpHeaderNames.ACCEPT, "application/json")
.addHeader(HttpHeaderNames.CONTENT_TYPE, "application/json")
.setBody(createResponseBody(data))
.execute();
future.addListener(() -> {
if (logFailedRequests) {
try {
Response r = future.get();
if (r.getStatusCode() != 429) {
logger.error("Got HTTP status code {} when sending metrics to bStats",
r.getStatusCode());
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
logger.error("Unable to send metrics to bStats", e);
}
}
}, null);
}
private static ByteBuf createResponseBody(JsonObject object) throws IOException {
ByteBuf buf = Unpooled.buffer();
private static byte[] createResponseBody(JsonObject object) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (Writer writer =
new BufferedWriter(
new OutputStreamWriter(
new GZIPOutputStream(new ByteBufOutputStream(buf)), StandardCharsets.UTF_8
new GZIPOutputStream(os), StandardCharsets.UTF_8
)
)
) {
VelocityServer.GSON.toJson(object, writer);
} catch (IOException e) {
buf.release();
throw e;
}
return buf;
return os.toByteArray();
}
/**

Datei anzeigen

@ -30,7 +30,6 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.plugin.VelocityEventManager;
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.protocol.packet.Chat;
@ -70,12 +69,15 @@ import java.util.function.IntFunction;
import java.util.stream.Collectors;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent;
import net.kyori.text.serializer.gson.GsonComponentSerializer;
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;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public class VelocityServer implements ProxyServer {
@ -89,7 +91,6 @@ public class VelocityServer implements ProxyServer {
private final ConnectionManager cm;
private final ProxyOptions options;
private @MonotonicNonNull VelocityConfiguration configuration;
private @MonotonicNonNull NettyHttpClient httpClient;
private @MonotonicNonNull KeyPair serverKeyPair;
private final ServerMap servers;
private final VelocityCommandManager commandManager = new VelocityCommandManager();
@ -201,7 +202,6 @@ public class VelocityServer implements ProxyServer {
}
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
httpClient = new NettyHttpClient(this);
loadPlugins();
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
@ -264,15 +264,7 @@ public class VelocityServer implements ProxyServer {
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
}
public EventLoopGroup getWorkerGroup() {
return this.cm.getWorkerGroup();
}
public Bootstrap initializeGenericBootstrap() {
return this.cm.createWorker();
}
public Bootstrap initializeGenericBootstrap(EventLoopGroup group) {
public Bootstrap createBootstrap(@Nullable EventLoopGroup group) {
return this.cm.createWorker(group);
}
@ -433,8 +425,8 @@ public class VelocityServer implements ProxyServer {
thread.start();
}
public NettyHttpClient getHttpClient() {
return ensureInitialized(httpClient);
public AsyncHttpClient getAsyncHttpClient() {
return ensureInitialized(cm).getHttpClient();
}
public Ratelimiter getIpAttemptLimiter() {
@ -454,6 +446,9 @@ public class VelocityServer implements ProxyServer {
* @return {@code true} if we can register the connection, {@code false} if not
*/
public boolean canRegisterConnection(ConnectedPlayer connection) {
if (configuration.isOnlineMode() && configuration.isOnlineModeKickExistingPlayers()) {
return true;
}
String lowerName = connection.getUsername().toLowerCase(Locale.US);
return !(connectionsByName.containsKey(lowerName)
|| connectionsByUuid.containsKey(connection.getUniqueId()));
@ -466,12 +461,24 @@ public class VelocityServer implements ProxyServer {
*/
public boolean registerConnection(ConnectedPlayer connection) {
String lowerName = connection.getUsername().toLowerCase(Locale.US);
if (connectionsByName.putIfAbsent(lowerName, connection) != null) {
return false;
}
if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) {
connectionsByName.remove(lowerName, connection);
return false;
if (!this.configuration.isOnlineModeKickExistingPlayers()) {
if (connectionsByName.putIfAbsent(lowerName, connection) != null) {
return false;
}
if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) {
connectionsByName.remove(lowerName, connection);
return false;
}
} else {
ConnectedPlayer existing = connectionsByUuid.get(connection.getUniqueId());
if (existing != null) {
existing.disconnect(TranslatableComponent.of("multiplayer.disconnect.duplicate_login"));
}
// We can now replace the entries as needed.
connectionsByName.put(lowerName, connection);
connectionsByUuid.put(connection.getUniqueId(), connection);
}
return true;
}
@ -498,7 +505,7 @@ public class VelocityServer implements ProxyServer {
Preconditions.checkNotNull(component, "component");
Chat chat = Chat.createClientbound(component);
for (ConnectedPlayer player : connectionsByUuid.values()) {
player.getMinecraftConnection().write(chat);
player.getConnection().write(chat);
}
}

Datei anzeigen

@ -0,0 +1,7 @@
package com.velocitypowered.proxy.config;
public enum PingPassthroughMode {
DISABLED,
MODS,
ALL
}

Datei anzeigen

@ -72,11 +72,36 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("forwarding-secret")
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8);
@Comment({"Announce whether or not your server supports Forge. If you run a modded server, we",
"suggest turning this on."})
@Comment({
"Announce whether or not your server supports Forge. If you run a modded server, we",
"suggest turning this on.",
"",
"If your network runs one modpack consistently, consider using ping-passthrough = \"mods\"",
"instead for a nicer display in the server list."
})
@ConfigKey("announce-forge")
private boolean announceForge = false;
@Comment({"If enabled (default is false) and the proxy is in online mode, Velocity will kick",
"any existing player who is online if a duplicate connection attempt is made."})
@ConfigKey("kick-existing-players")
private boolean onlineModeKickExistingPlayers = false;
@Comment({
"Should Velocity pass server list ping requests to a backend server?",
"Available options:",
"- \"disabled\": No pass-through will be done. The velocity.toml and server-icon.png",
" will determine the initial server list ping response.",
"- \"mods\": Passes only the mod list from your backend server into the response.",
" The first server in your try list (or forced host) with a mod list will be",
" used. If no backend servers can be contacted, Velocity will not display any",
" mod information.",
"- \"all\": Passes everything from the backend server into the response. The Velocity",
" configuration is used if no servers could be contacted."
})
@ConfigKey("ping-passthrough")
private PingPassthroughMode pingPassthrough;
@Table("[servers]")
private final Servers servers;
@ -109,7 +134,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) {
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, Servers servers,
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) {
this.bind = bind;
this.motd = motd;
this.showMaxPlayers = showMaxPlayers;
@ -117,6 +143,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.announceForge = announceForge;
this.playerInfoForwardingMode = playerInfoForwardingMode;
this.forwardingSecret = forwardingSecret;
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
this.pingPassthrough = pingPassthrough;
this.servers = servers;
this.forcedHosts = forcedHosts;
this.advanced = advanced;
@ -365,10 +393,18 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return advanced.isProxyProtocol();
}
public boolean useTcpFastOpen() {
return advanced.tcpFastOpen;
}
public Metrics getMetrics() {
return metrics;
}
public PingPassthroughMode getPingPassthrough() {
return pingPassthrough;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -416,6 +452,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
.toUpperCase(Locale.US);
String passThroughName = toml.getString("ping-passthrough", "DISABLED")
.toUpperCase(Locale.US);
return new VelocityConfiguration(
toml.getString("bind", "0.0.0.0:25577"),
@ -425,6 +463,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
toml.getBoolean("announce-forge", false),
PlayerInfoForwarding.valueOf(forwardingModeName),
forwardingSecret,
toml.getBoolean("kick-existing-players", false),
PingPassthroughMode.valueOf(passThroughName),
servers,
forcedHosts,
advanced,
@ -443,6 +483,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return builder.toString();
}
public boolean isOnlineModeKickExistingPlayers() {
return onlineModeKickExistingPlayers;
}
private static class Servers {
@IsMap
@ -596,6 +640,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("proxy-protocol")
private boolean proxyProtocol = false;
@Comment("Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.")
@ConfigKey("tcp-fast-open")
private boolean tcpFastOpen = false;
private Advanced() {
}
@ -607,6 +655,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
this.proxyProtocol = toml.getBoolean("proxy-protocol", false);
this.tcpFastOpen = toml.getBoolean("tcp-fast-open", false);
}
}
@ -634,6 +683,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return proxyProtocol;
}
public boolean isTcpFastOpen() {
return tcpFastOpen;
}
@Override
public String toString() {
return "Advanced{"
@ -643,6 +696,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
+ ", connectionTimeout=" + connectionTimeout
+ ", readTimeout=" + readTimeout
+ ", proxyProtocol=" + proxyProtocol
+ ", tcpFastOpen=" + tcpFastOpen
+ '}';
}
}

Datei anzeigen

@ -155,6 +155,10 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
}
}
private void ensureInEventLoop() {
Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop");
}
public EventLoop eventLoop() {
return channel.eventLoop();
}
@ -233,6 +237,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* @param autoReading whether or not we should read data automatically
*/
public void setAutoReading(boolean autoReading) {
ensureInEventLoop();
channel.config().setAutoRead(autoReading);
if (autoReading) {
// For some reason, the channel may not completely read its queued contents once autoread
@ -249,6 +255,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* @param state the new state
*/
public void setState(StateRegistry state) {
ensureInEventLoop();
this.state = state;
this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
@ -263,6 +271,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* @param protocolVersion the protocol version to use
*/
public void setProtocolVersion(ProtocolVersion protocolVersion) {
ensureInEventLoop();
this.protocolVersion = protocolVersion;
this.nextProtocolVersion = protocolVersion;
if (protocolVersion != ProtocolVersion.LEGACY) {
@ -284,6 +294,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* @param sessionHandler the handler to use
*/
public void setSessionHandler(MinecraftSessionHandler sessionHandler) {
ensureInEventLoop();
if (this.sessionHandler != null) {
this.sessionHandler.deactivated();
}
@ -302,6 +314,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/
public void setCompressionThreshold(int threshold) {
ensureOpen();
ensureInEventLoop();
if (threshold == -1) {
channel.pipeline().remove(COMPRESSION_DECODER);
@ -325,6 +338,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/
public void enableEncryption(byte[] secret) throws GeneralSecurityException {
ensureOpen();
ensureInEventLoop();
SecretKey key = new SecretKeySpec(secret, "AES");
@ -342,6 +356,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
}
public void setAssociation(MinecraftConnectionAssociation association) {
ensureInEventLoop();
this.association = association;
}

Datei anzeigen

@ -36,7 +36,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
this.server = server;
this.serverConn = serverConn;
this.playerConnection = serverConn.getPlayer().getMinecraftConnection();
this.playerConnection = serverConn.getPlayer().getConnection();
MinecraftSessionHandler psh = playerConnection.getSessionHandler();
if (!(psh instanceof ClientPlaySessionHandler)) {
@ -190,7 +190,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
public void disconnected() {
serverConn.getServer().removePlayer(serverConn.getPlayer());
if (!serverConn.isGracefulDisconnect() && !exceptionTriggered) {
serverConn.getPlayer().disconnect(ConnectionMessages.UNEXPECTED_DISCONNECT);
serverConn.getPlayer().handleConnectionException(serverConn.getServer(),
Disconnect.create(ConnectionMessages.UNEXPECTED_DISCONNECT), true);
}
}
}

Datei anzeigen

@ -117,32 +117,28 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
GameProfile profile) {
ByteBuf dataToForward = Unpooled.buffer();
ByteBuf finalData = Unpooled.buffer();
ByteBuf forwarded = Unpooled.buffer(2048);
try {
ProtocolUtils.writeVarInt(dataToForward, VelocityConstants.FORWARDING_VERSION);
ProtocolUtils.writeString(dataToForward, address);
ProtocolUtils.writeUuid(dataToForward, profile.getId());
ProtocolUtils.writeString(dataToForward, profile.getName());
ProtocolUtils.writeProperties(dataToForward, profile.getProperties());
ProtocolUtils.writeVarInt(forwarded, VelocityConstants.FORWARDING_VERSION);
ProtocolUtils.writeString(forwarded, address);
ProtocolUtils.writeUuid(forwarded, profile.getId());
ProtocolUtils.writeString(forwarded, profile.getName());
ProtocolUtils.writeProperties(forwarded, profile.getProperties());
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes());
mac.update(forwarded.array(), forwarded.arrayOffset(), forwarded.readableBytes());
byte[] sig = mac.doFinal();
finalData.writeBytes(sig);
finalData.writeBytes(dataToForward);
return finalData;
return Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(sig), forwarded);
} catch (InvalidKeyException e) {
finalData.release();
forwarded.release();
throw new RuntimeException("Unable to authenticate data", e);
} catch (NoSuchAlgorithmException e) {
// Should never happen
finalData.release();
forwarded.release();
throw new AssertionError(e);
} finally {
dataToForward.release();
}
}
}

Datei anzeigen

@ -84,13 +84,13 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
.whenCompleteAsync((x, error) -> {
// Strap on the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler;
if (serverConn.getPlayer().getMinecraftConnection().getSessionHandler()
if (serverConn.getPlayer().getConnection().getSessionHandler()
instanceof ClientPlaySessionHandler) {
playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getMinecraftConnection()
playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getConnection()
.getSessionHandler();
} else {
playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer());
serverConn.getPlayer().getMinecraftConnection().setSessionHandler(playHandler);
serverConn.getPlayer().getConnection().setSessionHandler(playHandler);
}
playHandler.handleBackendJoinGame(packet, serverConn);
@ -167,7 +167,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
return true;
}
serverConn.getPlayer().getMinecraftConnection().write(packet.retain());
serverConn.getPlayer().getConnection().write(packet.retain());
return true;
}

Datei anzeigen

@ -12,7 +12,6 @@ import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.ServerInfo;
@ -77,7 +76,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
CompletableFuture<Impl> result = new CompletableFuture<>();
// Note: we use the event loop for the connection the player is on. This reduces context
// switches.
server.initializeGenericBootstrap(proxyPlayer.getMinecraftConnection().eventLoop())
server.createBootstrap(proxyPlayer.getConnection().eventLoop())
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
@ -139,23 +138,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode();
// Initiate the handshake.
ProtocolVersion protocolVersion = proxyPlayer.getMinecraftConnection().getNextProtocolVersion();
ProtocolVersion protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion();
Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.LOGIN_ID);
handshake.setProtocolVersion(protocolVersion);
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
handshake.setServerAddress(createLegacyForwardingAddress());
} else if (proxyPlayer.getMinecraftConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
} else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
handshake.setServerAddress(handshake.getServerAddress() + HANDSHAKE_HOSTNAME_TOKEN);
} else {
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
}
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
mc.write(handshake);
mc.delayedWrite(handshake);
mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN);
mc.write(new ServerLogin(proxyPlayer.getUsername()));
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername()));
mc.flush();
}
public @Nullable MinecraftConnection getConnection() {

Datei anzeigen

@ -7,6 +7,7 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
@ -59,7 +60,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final List<UUID> serverBossBars = new ArrayList<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server;
private @Nullable TabCompleteRequest legacyCommandTabComplete;
private @Nullable TabCompleteRequest outstandingTabComplete;
/**
* Constructs a client play session handler.
@ -77,7 +78,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
.getProtocolVersion());
if (!channels.isEmpty()) {
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
player.getMinecraftConnection().write(register);
player.getConnection().write(register);
player.getKnownChannels().addAll(channels);
}
}
@ -155,61 +156,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public boolean handle(TabCompleteRequest packet) {
boolean isCommand = !packet.isAssumeCommand() && packet.getCommand().startsWith("/");
if (!isCommand) {
// We can't deal with anything else.
return false;
}
// In 1.13+, we need to do additional work for the richer suggestions available.
String command = packet.getCommand().substring(1);
int spacePos = command.indexOf(' ');
if (spacePos == -1) {
return false;
}
String commandLabel = command.substring(0, spacePos);
if (!server.getCommandManager().hasCommand(commandLabel)) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// tab list completion support for command names. In 1.13, Brigadier handles everything for
// us.
legacyCommandTabComplete = packet;
}
return false;
}
List<String> suggestions = server.getCommandManager().offerSuggestions(player, command);
if (suggestions.isEmpty()) {
return false;
}
List<Offer> offers = new ArrayList<>();
int longestLength = 0;
for (String suggestion : suggestions) {
offers.add(new Offer(suggestion));
if (suggestion.length() > longestLength) {
longestLength = suggestion.length();
}
}
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
int length;
if (startPos == 0) {
startPos = packet.getCommand().length() + 1;
length = longestLength;
if (isCommand) {
return this.handleCommandTabComplete(packet);
} else {
length = packet.getCommand().length() - startPos;
return this.handleRegularTabComplete(packet);
}
resp.setStart(startPos);
resp.setLength(length);
resp.getOffers().addAll(offers);
player.getMinecraftConnection().write(resp);
return true;
}
@Override
@ -325,7 +276,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public void writabilityChanged() {
VelocityServerConnection serverConn = player.getConnectedServer();
if (serverConn != null) {
boolean writable = player.getMinecraftConnection().getChannel().isWritable();
boolean writable = player.getConnection().getChannel().isWritable();
MinecraftConnection smc = serverConn.getConnection();
if (smc != null) {
smc.setAutoReading(writable);
@ -345,7 +296,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (!spawned) {
// Nothing special to do with regards to spawning the player
spawned = true;
player.getMinecraftConnection().delayedWrite(joinGame);
player.getConnection().delayedWrite(joinGame);
// Required for Legacy Forge
player.getPhase().onFirstJoin(player);
@ -365,12 +316,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Most notably, by having the client accept the join game packet, we can work around the need
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
// improving compatibility with mods.
player.getMinecraftConnection().delayedWrite(joinGame);
player.getConnection().delayedWrite(joinGame);
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
player.getMinecraftConnection().delayedWrite(
player.getConnection().delayedWrite(
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(),
joinGame.getLevelType()));
player.getMinecraftConnection().delayedWrite(
player.getConnection().delayedWrite(
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
joinGame.getLevelType()));
}
@ -381,7 +332,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
BossBar deletePacket = new BossBar();
deletePacket.setUuid(serverBossBar);
deletePacket.setAction(BossBar.REMOVE);
player.getMinecraftConnection().delayedWrite(deletePacket);
player.getConnection().delayedWrite(deletePacket);
}
serverBossBars.clear();
@ -399,12 +350,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Clear any title from the previous server.
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
player.getMinecraftConnection()
player.getConnection()
.delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
}
// Flush everything
player.getMinecraftConnection().flush();
player.getConnection().flush();
serverMc.flush();
destination.completeJoin();
}
@ -413,29 +364,116 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return serverBossBars;
}
private boolean handleCommandTabComplete(TabCompleteRequest packet) {
// In 1.13+, we need to do additional work for the richer suggestions available.
String command = packet.getCommand().substring(1);
int spacePos = command.indexOf(' ');
if (spacePos == -1) {
spacePos = command.length();
}
String commandLabel = command.substring(0, spacePos);
if (!server.getCommandManager().hasCommand(commandLabel)) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// additional tab completion support.
outstandingTabComplete = packet;
}
return false;
}
List<String> suggestions = server.getCommandManager().offerSuggestions(player, command);
if (suggestions.isEmpty()) {
return false;
}
List<Offer> offers = new ArrayList<>();
int longestLength = 0;
for (String suggestion : suggestions) {
offers.add(new Offer(suggestion));
if (suggestion.length() > longestLength) {
longestLength = suggestion.length();
}
}
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
int length;
if (startPos == 0) {
startPos = packet.getCommand().length() + 1;
length = longestLength;
} else {
length = packet.getCommand().length() - startPos;
}
resp.setStart(startPos);
resp.setLength(length);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
return true;
}
private boolean handleRegularTabComplete(TabCompleteRequest packet) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// additional tab completion support.
outstandingTabComplete = packet;
}
return false;
}
/**
* Handles additional tab complete for 1.12 and lower clients.
* Handles additional tab complete.
*
* @param response the tab complete response from the backend
*/
public void handleTabCompleteResponse(TabCompleteResponse response) {
if (legacyCommandTabComplete != null) {
String command = legacyCommandTabComplete.getCommand().substring(1);
try {
List<String> offers = server.getCommandManager().offerSuggestions(player, command);
for (String offer : offers) {
response.getOffers().add(new Offer(offer, null));
}
response.getOffers().sort(null);
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'",
player.getUsername(),
command, e);
if (outstandingTabComplete != null) {
if (outstandingTabComplete.isAssumeCommand()) {
return; // used for command blocks which can't run Velocity commands anyway
}
legacyCommandTabComplete = null;
if (outstandingTabComplete.getCommand().startsWith("/")) {
this.finishCommandTabComplete(outstandingTabComplete, response);
} else {
this.finishRegularTabComplete(outstandingTabComplete, response);
}
outstandingTabComplete = null;
}
}
player.getMinecraftConnection().write(response);
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
String command = request.getCommand().substring(1);
try {
List<String> offers = server.getCommandManager().offerSuggestions(player, command);
for (String offer : offers) {
response.getOffers().add(new Offer(offer, null));
}
response.getOffers().sort(null);
player.getConnection().write(response);
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'",
player.getUsername(),
command, e);
}
}
private void finishRegularTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
List<String> offers = new ArrayList<>();
for (Offer offer : response.getOffers()) {
offers.add(offer.getText());
}
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers))
.thenAcceptAsync(e -> {
response.getOffers().clear();
for (String s : e.getSuggestions()) {
response.getOffers().add(new Offer(s));
}
player.getConnection().write(response);
}, player.getConnection().eventLoop());
}
/**

Datei anzeigen

@ -59,7 +59,6 @@ import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
@ -85,7 +84,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
/**
* The actual Minecraft connection. This is actually a wrapper object around the Netty channel.
*/
private final MinecraftConnection minecraftConnection;
private final MinecraftConnection connection;
private final @Nullable InetSocketAddress virtualHost;
private GameProfile profile;
private PermissionFunction permissionFunction;
@ -104,18 +103,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
private @MonotonicNonNull List<String> serversToTry = null;
ConnectedPlayer(VelocityServer server, GameProfile profile,
MinecraftConnection minecraftConnection, @Nullable InetSocketAddress virtualHost) {
MinecraftConnection connection, @Nullable InetSocketAddress virtualHost) {
this.server = server;
if (minecraftConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new VelocityTabList(minecraftConnection);
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new VelocityTabList(connection);
} else {
this.tabList = new VelocityTabListLegacy(minecraftConnection);
this.tabList = new VelocityTabListLegacy(connection);
}
this.profile = profile;
this.minecraftConnection = minecraftConnection;
this.connection = connection;
this.virtualHost = virtualHost;
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
this.connectionPhase = minecraftConnection.getType().getInitialClientPhase();
this.connectionPhase = connection.getType().getInitialClientPhase();
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
}
@ -139,8 +138,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return profile;
}
public MinecraftConnection getMinecraftConnection() {
return minecraftConnection;
public MinecraftConnection getConnection() {
return connection;
}
@Override
@ -175,7 +174,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override
public InetSocketAddress getRemoteAddress() {
return (InetSocketAddress) minecraftConnection.getRemoteAddress();
return (InetSocketAddress) connection.getRemoteAddress();
}
@Override
@ -189,12 +188,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override
public boolean isActive() {
return minecraftConnection.getChannel().isActive();
return connection.getChannel().isActive();
}
@Override
public ProtocolVersion getProtocolVersion() {
return minecraftConnection.getProtocolVersion();
return connection.getProtocolVersion();
}
@Override
@ -210,7 +209,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
TitlePacket pkt = new TitlePacket();
pkt.setAction(TitlePacket.SET_ACTION_BAR);
pkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(component));
minecraftConnection.write(pkt);
connection.write(pkt);
return;
} else {
// Due to issues with action bar packets, we'll need to convert the text message into a
@ -226,7 +225,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Chat chat = new Chat();
chat.setType(pos);
chat.setMessage(json);
minecraftConnection.write(chat);
connection.write(chat);
}
@Override
@ -263,23 +262,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
public void disconnect(Component reason) {
logger.info("{} has disconnected: {}", this,
LegacyComponentSerializer.legacy().serialize(reason));
minecraftConnection.closeWith(Disconnect.create(reason));
connection.closeWith(Disconnect.create(reason));
}
@Override
public void sendTitle(Title title) {
Preconditions.checkNotNull(title, "title");
ProtocolVersion protocolVersion = minecraftConnection.getProtocolVersion();
ProtocolVersion protocolVersion = connection.getProtocolVersion();
if (title.equals(Titles.reset())) {
minecraftConnection.write(TitlePacket.resetForProtocolVersion(protocolVersion));
connection.write(TitlePacket.resetForProtocolVersion(protocolVersion));
} else if (title.equals(Titles.hide())) {
minecraftConnection.write(TitlePacket.hideForProtocolVersion(protocolVersion));
connection.write(TitlePacket.hideForProtocolVersion(protocolVersion));
} else if (title instanceof TextTitle) {
TextTitle tt = (TextTitle) title;
if (tt.isResetBeforeSend()) {
minecraftConnection.delayedWrite(TitlePacket.resetForProtocolVersion(protocolVersion));
connection.delayedWrite(TitlePacket.resetForProtocolVersion(protocolVersion));
}
Optional<Component> titleText = tt.getTitle();
@ -287,7 +286,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_TITLE);
titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(titleText.get()));
minecraftConnection.delayedWrite(titlePkt);
connection.delayedWrite(titlePkt);
}
Optional<Component> subtitleText = tt.getSubtitle();
@ -295,7 +294,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_SUBTITLE);
titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(subtitleText.get()));
minecraftConnection.delayedWrite(titlePkt);
connection.delayedWrite(titlePkt);
}
if (tt.areTimesSet()) {
@ -303,9 +302,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
timesPkt.setFadeIn(tt.getFadeIn());
timesPkt.setStay(tt.getStay());
timesPkt.setFadeOut(tt.getFadeOut());
minecraftConnection.delayedWrite(timesPkt);
connection.delayedWrite(timesPkt);
}
minecraftConnection.flush();
connection.flush();
} else {
throw new IllegalArgumentException("Unknown title class " + title.getClass().getName());
}
@ -457,9 +456,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
if (newResult == null || !newResult) {
disconnect(friendlyReason);
} else {
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER);
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason));
}
}, minecraftConnection.eventLoop());
}, connection.eventLoop());
} else if (event.getResult() instanceof Notify) {
Notify res = (Notify) event.getResult();
if (event.kickedDuringServerConnect()) {
@ -471,7 +470,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// In case someone gets creative, assume we want to disconnect the player.
disconnect(friendlyReason);
}
}, minecraftConnection.eventLoop());
}, connection.eventLoop());
}
/**
@ -584,7 +583,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data));
minecraftConnection.write(message);
connection.write(message);
return true;
}
@ -603,7 +602,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ResourcePackRequest request = new ResourcePackRequest();
request.setUrl(url);
request.setHash("");
minecraftConnection.write(request);
connection.write(request);
}
@Override
@ -615,7 +614,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ResourcePackRequest request = new ResourcePackRequest();
request.setUrl(url);
request.setHash(ByteBufUtil.hexDump(hash));
minecraftConnection.write(request);
connection.write(request);
}
/**
@ -624,10 +623,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
* ID last sent by the server.
*/
public void sendKeepAlive() {
if (minecraftConnection.getState() == StateRegistry.PLAY) {
if (connection.getState() == StateRegistry.PLAY) {
KeepAlive keepAlive = new KeepAlive();
keepAlive.setRandomId(ThreadLocalRandom.current().nextLong());
minecraftConnection.write(keepAlive);
connection.write(keepAlive);
}
}
@ -751,8 +750,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
if (status != null && !status.isSafe()) {
// If it's not safe to continue the connection we need to shut it down.
handleConnectionException(status.getAttemptedConnection(), throwable, true);
} else if ((status != null && !status.isSuccessful())) {
resetInFlightConnection();
}
})
}, connection.eventLoop())
.thenApply(x -> x);
}
@ -785,7 +786,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// The only remaining value is successful (no need to do anything!)
break;
}
}, minecraftConnection.eventLoop())
}, connection.eventLoop())
.thenApply(Result::isSuccessful);
}

Datei anzeigen

@ -8,6 +8,8 @@ import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_
import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL;
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
import static org.asynchttpclient.Dsl.asyncHttpClient;
import static org.asynchttpclient.Dsl.config;
import com.google.common.base.Preconditions;
import com.google.common.net.UrlEscapers;
@ -43,10 +45,14 @@ import java.security.KeyPair;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import net.kyori.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public class LoginSessionHandler implements MinecraftSessionHandler {
@ -124,46 +130,50 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
String url = String.format(MOJANG_HASJOINED_URL,
urlFormParameterEscaper().escape(login.getUsername()), serverId,
urlFormParameterEscaper().escape(playerIp));
server.getHttpClient()
.get(new URL(url), mcConnection.eventLoop())
.thenAcceptAsync(profileResponse -> {
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) {
throw new RuntimeException(e);
}
ListenableFuture<Response> hasJoinedResponse = server.getAsyncHttpClient().prepareGet(url)
.execute();
hasJoinedResponse.addListener(() -> {
if (mcConnection.isClosed()) {
// The player disconnected after we authenticated them.
return;
}
if (profileResponse.getCode() == 200) {
// All went well, initialize the session.
initializePlayer(GSON.fromJson(profileResponse.getBody(), GameProfile.class), true);
} else if (profileResponse.getCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(VelocityMessages.ONLINE_MODE_ONLY);
} else {
// Something else went wrong
logger.error(
"Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
profileResponse.getCode(), login.getUsername(), playerIp);
mcConnection.close();
}
}, mcConnection.eventLoop())
.exceptionally(exception -> {
logger.error("Unable to enable encryption", exception);
// Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption
// is enabled.
try {
mcConnection.enableEncryption(decryptedSharedSecret);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
try {
Response profileResponse = hasJoinedResponse.get();
if (profileResponse.getStatusCode() == 200) {
// All went well, initialize the session.
initializePlayer(GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class),
true);
} else if (profileResponse.getStatusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(VelocityMessages.ONLINE_MODE_ONLY);
} else {
// Something else went wrong
logger.error(
"Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
profileResponse.getStatusCode(), login.getUsername(), playerIp);
mcConnection.close();
return null;
});
}
} catch (ExecutionException e) {
logger.error("Unable to authenticate with Mojang", e);
mcConnection.close();
} catch (InterruptedException e) {
// not much we can do usefully
Thread.currentThread().interrupt();
}
}, mcConnection.eventLoop());
} catch (GeneralSecurityException e) {
logger.error("Unable to enable encryption", e);
mcConnection.close();
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return true;
}
@ -180,6 +190,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// The player was disconnected
return;
}
PreLoginComponentResult result = event.getResult();
Optional<Component> disconnectReason = result.getReason();
if (disconnectReason.isPresent()) {
@ -278,7 +289,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
player.disconnect(VelocityMessages.ALREADY_CONNECTED);
return;
}
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
server.getEventManager().fire(new PostLoginEvent(player))
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());

Datei anzeigen

@ -1,12 +1,15 @@
package com.velocitypowered.proxy.connection.client;
import com.google.common.collect.ImmutableList;
import com.spotify.futures.CompletableFutures;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.util.ModInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PingPassthroughMode;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@ -15,27 +18,32 @@ import com.velocitypowered.proxy.protocol.packet.LegacyPing;
import com.velocitypowered.proxy.protocol.packet.StatusPing;
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class StatusSessionHandler implements MinecraftSessionHandler {
private final VelocityServer server;
private final MinecraftConnection connection;
private final InboundConnection inboundWrapper;
private final InboundConnection inbound;
StatusSessionHandler(VelocityServer server, MinecraftConnection connection,
InboundConnection inboundWrapper) {
InboundConnection inbound) {
this.server = server;
this.connection = connection;
this.inboundWrapper = inboundWrapper;
this.inbound = inbound;
}
private ServerPing createInitialPing() {
private ServerPing constructLocalPing(ProtocolVersion version) {
VelocityConfiguration configuration = server.getConfiguration();
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
return new ServerPing(
new ServerPing.Version(shownVersion.getProtocol(),
new ServerPing.Version(version.getProtocol(),
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
ImmutableList.of()),
@ -45,12 +53,78 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
);
}
private CompletableFuture<ServerPing> attemptPingPassthrough(PingPassthroughMode mode,
List<String> servers, ProtocolVersion pingingVersion) {
ServerPing fallback = constructLocalPing(pingingVersion);
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
for (String s : servers) {
Optional<RegisteredServer> rs = server.getServer(s);
if (!rs.isPresent()) {
continue;
}
VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get();
pings.add(vrs.ping(connection.eventLoop(), pingingVersion));
}
if (pings.isEmpty()) {
return CompletableFuture.completedFuture(fallback);
}
CompletableFuture<List<ServerPing>> pingResponses = CompletableFutures.successfulAsList(pings,
(ex) -> fallback);
switch (mode) {
case ALL:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
return response;
}
return fallback;
});
case MODS:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback that contains a mod list
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
Optional<ModInfo> modInfo = response.getModinfo();
if (modInfo.isPresent()) {
return fallback.asBuilder().mods(modInfo.get()).build();
}
}
return fallback;
});
default:
// Not possible, but covered for completeness.
return CompletableFuture.completedFuture(fallback);
}
}
private CompletableFuture<ServerPing> getInitialPing() {
VelocityConfiguration configuration = server.getConfiguration();
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
PingPassthroughMode passthrough = configuration.getPingPassthrough();
if (passthrough == PingPassthroughMode.DISABLED) {
return CompletableFuture.completedFuture(constructLocalPing(shownVersion));
} else {
String virtualHostStr = inbound.getVirtualHost().map(InetSocketAddress::getHostString)
.orElse("");
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
return attemptPingPassthrough(configuration.getPingPassthrough(), serversToTry, shownVersion);
}
}
@Override
public boolean handle(LegacyPing packet) {
ServerPing initialPing = createInitialPing();
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
server.getEventManager().fire(event)
.thenRunAsync(() -> {
getInitialPing()
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
.thenAcceptAsync(event -> {
connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(),
packet.getVersion()));
}, connection.eventLoop());
@ -65,11 +139,10 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(StatusRequest packet) {
ServerPing initialPing = createInitialPing();
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
server.getEventManager().fire(event)
.thenRunAsync(
() -> {
getInitialPing()
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
.thenAcceptAsync(
(event) -> {
StringBuilder json = new StringBuilder();
VelocityServer.GSON.toJson(event.getPing(), json);
connection.write(new StatusResponse(json));

Datei anzeigen

@ -114,7 +114,7 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
serverConnection.setConnectionPhase(newPhase);
// Write the packet to the player, we don't need it now.
player.getMinecraftConnection().write(message.retain());
player.getConnection().write(message.retain());
return true;
}

Datei anzeigen

@ -135,7 +135,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
COMPLETE(null) {
@Override
public void resetConnectionPhase(ConnectedPlayer player) {
player.getMinecraftConnection().write(LegacyForgeUtil.resetPacket());
player.getConnection().write(LegacyForgeUtil.resetPacket());
player.setPhase(LegacyForgeHandshakeClientPhase.NOT_STARTED);
}

Datei anzeigen

@ -1,8 +1,12 @@
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.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.network.netty.DnsAddressResolverGroupNameResolverAdapter;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
@ -11,17 +15,26 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.util.concurrent.EventExecutor;
import java.net.InetSocketAddress;
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.FilterException;
import org.asynchttpclient.filter.RequestFilter;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class ConnectionManager {
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 21,
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
1 << 21);
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
private final Map<InetSocketAddress, Channel> endpoints = new HashMap<>();
@ -35,6 +48,7 @@ public final class ConnectionManager {
public final ServerChannelInitializerHolder serverChannelInitializer;
private final DnsAddressResolverGroup resolverGroup;
private final AsyncHttpClient httpClient;
/**
* Initalizes the {@code ConnectionManager}.
@ -48,12 +62,26 @@ public final class ConnectionManager {
this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER);
this.serverChannelInitializer = new ServerChannelInitializerHolder(
new ServerChannelInitializer(this.server));
this.resolverGroup = new DnsAddressResolverGroup(
new DnsNameResolverBuilder()
.channelType(this.transportType.datagramChannelClass)
.negativeTtl(15)
.ndots(1)
);
this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder()
.channelType(this.transportType.datagramChannelClass)
.negativeTtl(15)
.ndots(1));
this.httpClient = asyncHttpClient(config()
.setEventLoopGroup(this.workerGroup)
.setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion())
.addRequestFilter(new RequestFilter() {
@Override
public <T> FilterContext<T> filter(FilterContext<T> ctx) throws FilterException {
return new FilterContextBuilder<>(ctx)
.request(new RequestBuilder(ctx.getRequest())
.setNameResolver(
new DnsAddressResolverGroupNameResolverAdapter(resolverGroup, workerGroup)
)
.build())
.build();
}
})
.build());
}
public void logChannelInformation() {
@ -75,6 +103,11 @@ public final class ConnectionManager {
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.IP_TOS, 0x18)
.localAddress(address);
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(EpollChannelOption.TCP_FASTOPEN, 3);
}
bootstrap.bind()
.addListener((ChannelFutureListener) future -> {
final Channel channel = future.channel();
@ -112,25 +145,25 @@ public final class ConnectionManager {
});
}
public Bootstrap createWorker() {
return this.createWorker(this.workerGroup);
}
/**
* Creates a TCP {@link Bootstrap} using Velocity's event loops.
*
* @param group the event loop group to use
* @param group the event loop group to use. Use {@code null} for the default worker group.
*
* @return a new {@link Bootstrap}
*/
public Bootstrap createWorker(EventLoopGroup group) {
return new Bootstrap()
public Bootstrap createWorker(@Nullable EventLoopGroup group) {
Bootstrap bootstrap = new Bootstrap()
.channel(this.transportType.socketChannelClass)
.group(group)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
this.server.getConfiguration().getConnectTimeout())
.group(group == null ? this.workerGroup : group)
.resolver(this.resolverGroup);
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(EpollChannelOption.TCP_FASTOPEN_CONNECT, true);
}
return bootstrap;
}
/**
@ -164,11 +197,11 @@ public final class ConnectionManager {
return bossGroup;
}
public EventLoopGroup getWorkerGroup() {
return workerGroup;
}
public ServerChannelInitializerHolder getServerChannelInitializer() {
return this.serverChannelInitializer;
}
public AsyncHttpClient getHttpClient() {
return httpClient;
}
}

Datei anzeigen

@ -7,11 +7,6 @@ import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel;
@ -27,10 +22,7 @@ enum TransportType {
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
EpollDatagramChannel.class,
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
KQueueDatagramChannel.class,
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)));
final String name;
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
@ -64,10 +56,12 @@ enum TransportType {
}
public static TransportType bestType() {
if (Boolean.getBoolean("velocity.disable-native-transport")) {
return NIO;
}
if (Epoll.isAvailable()) {
return EPOLL;
} else if (KQueue.isAvailable()) {
return KQUEUE;
} else {
return NIO;
}

Datei anzeigen

@ -1,153 +0,0 @@
package com.velocitypowered.proxy.network.http;
import com.velocitypowered.proxy.VelocityServer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import javax.net.ssl.SSLEngine;
public class NettyHttpClient {
private final String userAgent;
private final VelocityServer server;
/**
* Initializes the HTTP client.
*
* @param server the Velocity server
*/
public NettyHttpClient(VelocityServer server) {
this.userAgent = server.getVersion().getName() + "/" + server.getVersion().getVersion();
this.server = server;
}
private ChannelFuture establishConnection(URL url, EventLoop loop) {
String host = url.getHost();
int port = url.getPort();
boolean ssl = url.getProtocol().equals("https");
if (port == -1) {
port = ssl ? 443 : 80;
}
InetSocketAddress address = InetSocketAddress.createUnresolved(host, port);
return server.initializeGenericBootstrap(loop)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
if (ssl) {
SslContext context = SslContextBuilder.forClient().protocols("TLSv1.2").build();
// Unbelievably, Java doesn't automatically check the CN to make sure we're talking
// to the right host! Therefore, we provide the intended host name and port, along
// with asking Java very nicely if it could check the hostname in the certificate
// for us.
SSLEngine engine = context.newEngine(ch.alloc(), address.getHostString(),
address.getPort());
engine.getSSLParameters().setEndpointIdentificationAlgorithm("HTTPS");
ch.pipeline().addLast("ssl", new SslHandler(engine));
}
ch.pipeline().addLast("http", new HttpClientCodec());
}
})
.connect(address);
}
/**
* Attempts an HTTP GET request to the specified URL.
* @param url the URL to fetch
* @param loop the event loop to use
* @return a future representing the response
*/
public CompletableFuture<SimpleHttpResponse> get(URL url, EventLoop loop) {
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
establishConnection(url, loop)
.addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
Channel channel = future.channel();
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
String pathAndQuery = url.getPath();
if (url.getQuery() != null && url.getQuery().length() > 0) {
pathAndQuery += "?" + url.getQuery();
}
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, pathAndQuery);
request.headers().add(HttpHeaderNames.HOST, url.getHost());
request.headers().add(HttpHeaderNames.USER_AGENT, userAgent);
channel.writeAndFlush(request, channel.voidPromise());
} else {
reply.completeExceptionally(future.cause());
}
});
return reply;
}
/**
* Attempts an HTTP POST request to the specified URL.
* @param url the URL to fetch
* @param body the body to post
* @param decorator a consumer that can modify the request as required
* @return a future representing the response
*/
public CompletableFuture<SimpleHttpResponse> post(URL url, ByteBuf body,
Consumer<HttpRequest> decorator) {
return post(url, server.getWorkerGroup().next(), body, decorator);
}
/**
* Attempts an HTTP POST request to the specified URL.
* @param url the URL to fetch
* @param loop the event loop to use
* @param body the body to post
* @param decorator a consumer that can modify the request as required
* @return a future representing the response
*/
public CompletableFuture<SimpleHttpResponse> post(URL url, EventLoop loop, ByteBuf body,
Consumer<HttpRequest> decorator) {
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
establishConnection(url, loop)
.addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
Channel channel = future.channel();
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
String pathAndQuery = url.getPath();
if (url.getQuery() != null && url.getQuery().length() > 0) {
pathAndQuery += "?" + url.getQuery();
}
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.POST, pathAndQuery, body);
request.headers().add(HttpHeaderNames.HOST, url.getHost());
request.headers().add(HttpHeaderNames.USER_AGENT, userAgent);
request.headers().add(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());
decorator.accept(request);
channel.writeAndFlush(request, channel.voidPromise());
} else {
body.release();
reply.completeExceptionally(future.cause());
}
});
return reply;
}
}

Datei anzeigen

@ -1,28 +0,0 @@
package com.velocitypowered.proxy.network.http;
public class SimpleHttpResponse {
private final int code;
private final String body;
SimpleHttpResponse(int code, String body) {
this.code = code;
this.body = body;
}
public int getCode() {
return code;
}
public String getBody() {
return body;
}
@Override
public String toString() {
return "SimpleHttpResponse{"
+ "code=" + code
+ ", body='" + body + '\''
+ '}';
}
}

Datei anzeigen

@ -1,51 +0,0 @@
package com.velocitypowered.proxy.network.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.ReferenceCountUtil;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
private final StringBuilder buffer = new StringBuilder();
private final CompletableFuture<SimpleHttpResponse> reply;
private int httpCode;
SimpleHttpResponseCollector(CompletableFuture<SimpleHttpResponse> reply) {
this.reply = reply;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
HttpResponseStatus status = response.status();
this.httpCode = status.code();
}
if (msg instanceof HttpContent) {
buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8));
if (msg instanceof LastHttpContent) {
ctx.close();
reply.complete(new SimpleHttpResponse(httpCode, buffer.toString()));
}
}
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
reply.completeExceptionally(cause);
}
}

Datei anzeigen

@ -0,0 +1,73 @@
package com.velocitypowered.proxy.network.netty;
import io.netty.channel.EventLoopGroup;
import io.netty.resolver.InetNameResolver;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
public class DnsAddressResolverGroupNameResolverAdapter extends InetNameResolver {
private final DnsAddressResolverGroup resolverGroup;
private final EventLoopGroup group;
/**
* Creates a DnsAddressResolverGroupNameResolverAdapter.
* @param resolverGroup the resolver group to use
* @param group the event loop group
*/
public DnsAddressResolverGroupNameResolverAdapter(
DnsAddressResolverGroup resolverGroup, EventLoopGroup group) {
super(ImmediateEventExecutor.INSTANCE);
this.resolverGroup = resolverGroup;
this.group = group;
}
@Override
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
EventExecutor executor = this.findExecutor();
resolverGroup.getResolver(executor).resolve(InetSocketAddress.createUnresolved(inetHost, 17))
.addListener((FutureListener<InetSocketAddress>) future -> {
if (future.isSuccess()) {
promise.trySuccess(future.getNow().getAddress());
} else {
promise.tryFailure(future.cause());
}
});
}
@Override
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
throws Exception {
EventExecutor executor = this.findExecutor();
resolverGroup.getResolver(executor).resolveAll(InetSocketAddress.createUnresolved(inetHost, 17))
.addListener((FutureListener<List<InetSocketAddress>>) future -> {
if (future.isSuccess()) {
List<InetAddress> addresses = new ArrayList<>(future.getNow().size());
for (InetSocketAddress address : future.getNow()) {
addresses.add(address.getAddress());
}
promise.trySuccess(addresses);
} else {
promise.tryFailure(future.cause());
}
});
}
private EventExecutor findExecutor() {
for (EventExecutor executor : group) {
if (executor.inEventLoop()) {
return executor;
}
}
// otherwise, pick one
return group.next();
}
}

Datei anzeigen

@ -35,10 +35,9 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
checkFrame(expectedSize >= threshold, "Uncompressed size %s is greater than threshold %s",
expectedSize, threshold);
checkFrame(expectedSize <= MAXIMUM_UNCOMPRESSED_SIZE, "Expected uncompressed size"
+ "%s is larger than protocol maximum of %s", expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, expectedSize);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
try {
compressor.inflate(compatibleIn, uncompressed, expectedSize);
out.add(uncompressed);

Datei anzeigen

@ -133,5 +133,9 @@ public class TabCompleteResponse implements MinecraftPacket {
public int compareTo(Offer o) {
return this.text.compareTo(o.text);
}
public String getText() {
return text;
}
}
}

Datei anzeigen

@ -18,13 +18,15 @@ public class PingSessionHandler implements MinecraftSessionHandler {
private final CompletableFuture<ServerPing> result;
private final RegisteredServer server;
private final MinecraftConnection connection;
private final ProtocolVersion version;
private boolean completed = false;
PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server,
MinecraftConnection connection) {
MinecraftConnection connection, ProtocolVersion version) {
this.result = result;
this.server = server;
this.connection = connection;
this.version = version;
}
@Override
@ -33,11 +35,13 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setNextStatus(StateRegistry.STATUS_ID);
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(ProtocolVersion.MINIMUM_VERSION);
connection.write(handshake);
handshake.setProtocolVersion(version);
connection.delayedWrite(handshake);
connection.setState(StateRegistry.STATUS);
connection.write(StatusRequest.INSTANCE);
connection.delayedWrite(StatusRequest.INSTANCE);
connection.flush();
}
@Override

Datei anzeigen

@ -9,6 +9,7 @@ import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
@ -19,7 +20,6 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -28,6 +28,7 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.Collection;
import java.util.Set;
@ -59,11 +60,22 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override
public CompletableFuture<ServerPing> ping() {
return ping(null, ProtocolVersion.UNKNOWN);
}
/**
* Pings the specified server using the specified event {@code loop}, claiming to be
* {@code version}.
* @param loop the event loop to use
* @param version the version to report
* @return the server list ping response
*/
public CompletableFuture<ServerPing> ping(@Nullable EventLoop loop, ProtocolVersion version) {
if (server == null) {
throw new IllegalStateException("No Velocity proxy instance available");
}
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.initializeGenericBootstrap()
server.createBootstrap(loop)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
@ -87,8 +99,8 @@ public class VelocityRegisteredServer implements RegisteredServer {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler(
new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn));
conn.setSessionHandler(new PingSessionHandler(
pingFuture, VelocityRegisteredServer.this, conn, version));
} else {
pingFuture.completeExceptionally(future.cause());
}

Datei anzeigen

@ -1,6 +1,8 @@
package com.velocitypowered.proxy.util;
import com.google.common.base.Preconditions;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
@ -19,7 +21,12 @@ public class AddressUtil {
public static InetSocketAddress parseAddress(String ip) {
Preconditions.checkNotNull(ip, "ip");
URI uri = URI.create("tcp://" + ip);
return InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort());
try {
InetAddress ia = InetAddresses.forUriString(uri.getHost());
return new InetSocketAddress(ia, uri.getPort());
} catch (IllegalArgumentException e) {
return InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort());
}
}
/**

Datei anzeigen

@ -21,7 +21,7 @@ public class VelocityMessages {
public static final Component ALREADY_CONNECTED = TextComponent
.of("You are already connected to this proxy!", TextColor.RED);
public static final Component MOVED_TO_NEW_SERVER = TextComponent
.of("You were moved from the server you were on because you were kicked", TextColor.RED);
.of("The server you were on kicked you: ", TextColor.RED);
private VelocityMessages() {
throw new AssertionError();

Datei anzeigen

@ -271,6 +271,6 @@ public class VelocityBossBar implements com.velocitypowered.api.util.bossbar.Bos
private void sendPacket(Player player, MinecraftPacket packet) {
ConnectedPlayer connected = (ConnectedPlayer) player;
connected.getMinecraftConnection().write(packet);
connected.getConnection().write(packet);
}
}

Datei anzeigen

@ -1 +1,2 @@
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
log4j.skipJansi=true