3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-06 00:00:47 +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/caches/
- $HOME/.gradle/wrapper/ - $HOME/.gradle/wrapper/
jdk: jdk:
- oraclejdk8 - openjdk8

Datei anzeigen

@ -35,14 +35,3 @@ and you can configure it from there.
Alternatively, you can get the proxy JAR from the [downloads](https://www.velocitypowered.com/downloads) Alternatively, you can get the proxy JAR from the [downloads](https://www.velocitypowered.com/downloads)
page. 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.

Datei anzeigen

@ -24,7 +24,7 @@ dependencies {
compile "net.kyori:text-serializer-plain:${textVersion}" compile "net.kyori:text-serializer-plain:${textVersion}"
compile 'com.moandjiezana.toml:toml4j:0.7.2' compile 'com.moandjiezana.toml:toml4j:0.7.2'
compile "org.slf4j:slf4j-api:${slf4jVersion}" 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.checkerframework:checker-qual:${checkerFrameworkVersion}"
compile "org.spongepowered:configurate-hocon:${configurateVersion}" 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(477, "1.14"),
MINECRAFT_1_14_1(480, "1.14.1"), MINECRAFT_1_14_1(480, "1.14.1"),
MINECRAFT_1_14_2(485, "1.14.2"), 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 int protocol;
private final String name; private final String name;

Datei anzeigen

@ -182,6 +182,19 @@ public final class ServerPing {
return this; 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() { public Builder clearMods() {
this.mods.clear(); this.mods.clear();
return this; return this;

Datei anzeigen

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

Datei anzeigen

@ -48,7 +48,6 @@ dependencies {
compile "io.netty:netty-handler:${nettyVersion}" compile "io.netty:netty-handler:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${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-epoll:${nettyVersion}:linux-x86_64"
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
compile "io.netty:netty-resolver-dns:${nettyVersion}" compile "io.netty:netty-resolver-dns:${nettyVersion}"
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}" compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
@ -57,15 +56,19 @@ dependencies {
compile "org.apache.logging.log4j:log4j-iostreams:${log4jVersion}" compile "org.apache.logging.log4j:log4j-iostreams:${log4jVersion}"
compile 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options compile 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options
compile 'net.minecrell:terminalconsoleappender:1.1.1' compile 'net.minecrell:terminalconsoleappender:1.2.0'
runtime 'net.java.dev.jna:jna:4.5.2' // Needed for JLine runtime 'org.jline:jline-terminal-jansi:3.12.1' // Needed for JLine
runtime 'com.lmax:disruptor:3.4.2' // Async loggers 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 'net.kyori:event-method-asm:3.0.0'
compile 'com.mojang:brigadier:1.0.15' 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-api:${junitVersion}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${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.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.velocitypowered.proxy.config.VelocityConfiguration; 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 io.netty.handler.codec.http.HttpHeaderNames;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
@ -21,11 +18,14 @@ import java.util.Map;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
/** /**
* bStats collects some data for plugin authors. * bStats collects some data for plugin authors.
@ -185,40 +185,44 @@ public class Metrics {
} }
// Compress the data to save bandwidth // Compress the data to save bandwidth
ByteBuf reqBody = createResponseBody(data); ListenableFuture<Response> future = server.getAsyncHttpClient()
.preparePost(URL)
server.getHttpClient().post(new URL(URL), reqBody, request -> { .addHeader(HttpHeaderNames.CONTENT_ENCODING, "gzip")
request.headers().add(HttpHeaderNames.CONTENT_ENCODING, "gzip"); .addHeader(HttpHeaderNames.ACCEPT, "application/json")
request.headers().add(HttpHeaderNames.ACCEPT, "application/json"); .addHeader(HttpHeaderNames.CONTENT_TYPE, "application/json")
request.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/json"); .setBody(createResponseBody(data))
}) .execute();
.whenCompleteAsync((resp, exc) -> { future.addListener(() -> {
if (logFailedRequests) { if (logFailedRequests) {
if (exc != null) { try {
logger.error("Unable to send metrics to bStats", exc); Response r = future.get();
} else if (resp.getCode() != 429) { if (r.getStatusCode() != 429) {
logger.error("Got HTTP status code {} when sending metrics to bStats", logger.error("Got HTTP status code {} when sending metrics to bStats",
resp.getCode()); 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 { private static byte[] createResponseBody(JsonObject object) throws IOException {
ByteBuf buf = Unpooled.buffer(); ByteArrayOutputStream os = new ByteArrayOutputStream();
try (Writer writer = try (Writer writer =
new BufferedWriter( new BufferedWriter(
new OutputStreamWriter( new OutputStreamWriter(
new GZIPOutputStream(new ByteBufOutputStream(buf)), StandardCharsets.UTF_8 new GZIPOutputStream(os), StandardCharsets.UTF_8
) )
) )
) { ) {
VelocityServer.GSON.toJson(object, writer); VelocityServer.GSON.toJson(object, writer);
} catch (IOException e) { } catch (IOException e) {
buf.release();
throw e; 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.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.plugin.VelocityEventManager;
import com.velocitypowered.proxy.plugin.VelocityPluginManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.protocol.packet.Chat;
@ -70,12 +69,15 @@ import java.util.function.IntFunction;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent;
import net.kyori.text.serializer.gson.GsonComponentSerializer; import net.kyori.text.serializer.gson.GsonComponentSerializer;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.asynchttpclient.AsyncHttpClient;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public class VelocityServer implements ProxyServer { public class VelocityServer implements ProxyServer {
@ -89,7 +91,6 @@ public class VelocityServer implements ProxyServer {
private final ConnectionManager cm; private final ConnectionManager cm;
private final ProxyOptions options; private final ProxyOptions options;
private @MonotonicNonNull VelocityConfiguration configuration; private @MonotonicNonNull VelocityConfiguration configuration;
private @MonotonicNonNull NettyHttpClient httpClient;
private @MonotonicNonNull KeyPair serverKeyPair; private @MonotonicNonNull KeyPair serverKeyPair;
private final ServerMap servers; private final ServerMap servers;
private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final VelocityCommandManager commandManager = new VelocityCommandManager();
@ -201,7 +202,6 @@ public class VelocityServer implements ProxyServer {
} }
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit()); ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
httpClient = new NettyHttpClient(this);
loadPlugins(); loadPlugins();
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance // 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()); logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
} }
public EventLoopGroup getWorkerGroup() { public Bootstrap createBootstrap(@Nullable EventLoopGroup group) {
return this.cm.getWorkerGroup();
}
public Bootstrap initializeGenericBootstrap() {
return this.cm.createWorker();
}
public Bootstrap initializeGenericBootstrap(EventLoopGroup group) {
return this.cm.createWorker(group); return this.cm.createWorker(group);
} }
@ -433,8 +425,8 @@ public class VelocityServer implements ProxyServer {
thread.start(); thread.start();
} }
public NettyHttpClient getHttpClient() { public AsyncHttpClient getAsyncHttpClient() {
return ensureInitialized(httpClient); return ensureInitialized(cm).getHttpClient();
} }
public Ratelimiter getIpAttemptLimiter() { 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 * @return {@code true} if we can register the connection, {@code false} if not
*/ */
public boolean canRegisterConnection(ConnectedPlayer connection) { public boolean canRegisterConnection(ConnectedPlayer connection) {
if (configuration.isOnlineMode() && configuration.isOnlineModeKickExistingPlayers()) {
return true;
}
String lowerName = connection.getUsername().toLowerCase(Locale.US); String lowerName = connection.getUsername().toLowerCase(Locale.US);
return !(connectionsByName.containsKey(lowerName) return !(connectionsByName.containsKey(lowerName)
|| connectionsByUuid.containsKey(connection.getUniqueId())); || connectionsByUuid.containsKey(connection.getUniqueId()));
@ -466,6 +461,8 @@ public class VelocityServer implements ProxyServer {
*/ */
public boolean registerConnection(ConnectedPlayer connection) { public boolean registerConnection(ConnectedPlayer connection) {
String lowerName = connection.getUsername().toLowerCase(Locale.US); String lowerName = connection.getUsername().toLowerCase(Locale.US);
if (!this.configuration.isOnlineModeKickExistingPlayers()) {
if (connectionsByName.putIfAbsent(lowerName, connection) != null) { if (connectionsByName.putIfAbsent(lowerName, connection) != null) {
return false; return false;
} }
@ -473,6 +470,16 @@ public class VelocityServer implements ProxyServer {
connectionsByName.remove(lowerName, connection); connectionsByName.remove(lowerName, connection);
return false; 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; return true;
} }
@ -498,7 +505,7 @@ public class VelocityServer implements ProxyServer {
Preconditions.checkNotNull(component, "component"); Preconditions.checkNotNull(component, "component");
Chat chat = Chat.createClientbound(component); Chat chat = Chat.createClientbound(component);
for (ConnectedPlayer player : connectionsByUuid.values()) { 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") @ConfigKey("forwarding-secret")
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); 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", @Comment({
"suggest turning this on."}) "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") @ConfigKey("announce-forge")
private boolean announceForge = false; 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]") @Table("[servers]")
private final Servers 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, private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, 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.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
@ -117,6 +143,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.announceForge = announceForge; this.announceForge = announceForge;
this.playerInfoForwardingMode = playerInfoForwardingMode; this.playerInfoForwardingMode = playerInfoForwardingMode;
this.forwardingSecret = forwardingSecret; this.forwardingSecret = forwardingSecret;
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
this.pingPassthrough = pingPassthrough;
this.servers = servers; this.servers = servers;
this.forcedHosts = forcedHosts; this.forcedHosts = forcedHosts;
this.advanced = advanced; this.advanced = advanced;
@ -365,10 +393,18 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return advanced.isProxyProtocol(); return advanced.isProxyProtocol();
} }
public boolean useTcpFastOpen() {
return advanced.tcpFastOpen;
}
public Metrics getMetrics() { public Metrics getMetrics() {
return metrics; return metrics;
} }
public PingPassthroughMode getPingPassthrough() {
return pingPassthrough;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@ -416,6 +452,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN") String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
.toUpperCase(Locale.US); .toUpperCase(Locale.US);
String passThroughName = toml.getString("ping-passthrough", "DISABLED")
.toUpperCase(Locale.US);
return new VelocityConfiguration( return new VelocityConfiguration(
toml.getString("bind", "0.0.0.0:25577"), toml.getString("bind", "0.0.0.0:25577"),
@ -425,6 +463,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
toml.getBoolean("announce-forge", false), toml.getBoolean("announce-forge", false),
PlayerInfoForwarding.valueOf(forwardingModeName), PlayerInfoForwarding.valueOf(forwardingModeName),
forwardingSecret, forwardingSecret,
toml.getBoolean("kick-existing-players", false),
PingPassthroughMode.valueOf(passThroughName),
servers, servers,
forcedHosts, forcedHosts,
advanced, advanced,
@ -443,6 +483,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return builder.toString(); return builder.toString();
} }
public boolean isOnlineModeKickExistingPlayers() {
return onlineModeKickExistingPlayers;
}
private static class Servers { private static class Servers {
@IsMap @IsMap
@ -596,6 +640,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("proxy-protocol") @ConfigKey("proxy-protocol")
private boolean proxyProtocol = false; 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() { private Advanced() {
} }
@ -607,6 +655,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue(); this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
this.proxyProtocol = toml.getBoolean("proxy-protocol", false); 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; return proxyProtocol;
} }
public boolean isTcpFastOpen() {
return tcpFastOpen;
}
@Override @Override
public String toString() { public String toString() {
return "Advanced{" return "Advanced{"
@ -643,6 +696,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
+ ", connectionTimeout=" + connectionTimeout + ", connectionTimeout=" + connectionTimeout
+ ", readTimeout=" + readTimeout + ", readTimeout=" + readTimeout
+ ", proxyProtocol=" + proxyProtocol + ", 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() { public EventLoop eventLoop() {
return channel.eventLoop(); return channel.eventLoop();
} }
@ -233,6 +237,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* @param autoReading whether or not we should read data automatically * @param autoReading whether or not we should read data automatically
*/ */
public void setAutoReading(boolean autoReading) { public void setAutoReading(boolean autoReading) {
ensureInEventLoop();
channel.config().setAutoRead(autoReading); channel.config().setAutoRead(autoReading);
if (autoReading) { if (autoReading) {
// For some reason, the channel may not completely read its queued contents once autoread // 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 * @param state the new state
*/ */
public void setState(StateRegistry state) { public void setState(StateRegistry state) {
ensureInEventLoop();
this.state = state; this.state = state;
this.channel.pipeline().get(MinecraftEncoder.class).setState(state); this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
this.channel.pipeline().get(MinecraftDecoder.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 * @param protocolVersion the protocol version to use
*/ */
public void setProtocolVersion(ProtocolVersion protocolVersion) { public void setProtocolVersion(ProtocolVersion protocolVersion) {
ensureInEventLoop();
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.nextProtocolVersion = protocolVersion; this.nextProtocolVersion = protocolVersion;
if (protocolVersion != ProtocolVersion.LEGACY) { if (protocolVersion != ProtocolVersion.LEGACY) {
@ -284,6 +294,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* @param sessionHandler the handler to use * @param sessionHandler the handler to use
*/ */
public void setSessionHandler(MinecraftSessionHandler sessionHandler) { public void setSessionHandler(MinecraftSessionHandler sessionHandler) {
ensureInEventLoop();
if (this.sessionHandler != null) { if (this.sessionHandler != null) {
this.sessionHandler.deactivated(); this.sessionHandler.deactivated();
} }
@ -302,6 +314,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/ */
public void setCompressionThreshold(int threshold) { public void setCompressionThreshold(int threshold) {
ensureOpen(); ensureOpen();
ensureInEventLoop();
if (threshold == -1) { if (threshold == -1) {
channel.pipeline().remove(COMPRESSION_DECODER); channel.pipeline().remove(COMPRESSION_DECODER);
@ -325,6 +338,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/ */
public void enableEncryption(byte[] secret) throws GeneralSecurityException { public void enableEncryption(byte[] secret) throws GeneralSecurityException {
ensureOpen(); ensureOpen();
ensureInEventLoop();
SecretKey key = new SecretKeySpec(secret, "AES"); SecretKey key = new SecretKeySpec(secret, "AES");
@ -342,6 +356,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
public void setAssociation(MinecraftConnectionAssociation association) { public void setAssociation(MinecraftConnectionAssociation association) {
ensureInEventLoop();
this.association = association; this.association = association;
} }

Datei anzeigen

@ -36,7 +36,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) { BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
this.server = server; this.server = server;
this.serverConn = serverConn; this.serverConn = serverConn;
this.playerConnection = serverConn.getPlayer().getMinecraftConnection(); this.playerConnection = serverConn.getPlayer().getConnection();
MinecraftSessionHandler psh = playerConnection.getSessionHandler(); MinecraftSessionHandler psh = playerConnection.getSessionHandler();
if (!(psh instanceof ClientPlaySessionHandler)) { if (!(psh instanceof ClientPlaySessionHandler)) {
@ -190,7 +190,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
public void disconnected() { public void disconnected() {
serverConn.getServer().removePlayer(serverConn.getPlayer()); serverConn.getServer().removePlayer(serverConn.getPlayer());
if (!serverConn.isGracefulDisconnect() && !exceptionTriggered) { 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, private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
GameProfile profile) { GameProfile profile) {
ByteBuf dataToForward = Unpooled.buffer(); ByteBuf forwarded = Unpooled.buffer(2048);
ByteBuf finalData = Unpooled.buffer();
try { try {
ProtocolUtils.writeVarInt(dataToForward, VelocityConstants.FORWARDING_VERSION); ProtocolUtils.writeVarInt(forwarded, VelocityConstants.FORWARDING_VERSION);
ProtocolUtils.writeString(dataToForward, address); ProtocolUtils.writeString(forwarded, address);
ProtocolUtils.writeUuid(dataToForward, profile.getId()); ProtocolUtils.writeUuid(forwarded, profile.getId());
ProtocolUtils.writeString(dataToForward, profile.getName()); ProtocolUtils.writeString(forwarded, profile.getName());
ProtocolUtils.writeProperties(dataToForward, profile.getProperties()); ProtocolUtils.writeProperties(forwarded, profile.getProperties());
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key); mac.init(key);
mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes()); mac.update(forwarded.array(), forwarded.arrayOffset(), forwarded.readableBytes());
byte[] sig = mac.doFinal(); byte[] sig = mac.doFinal();
finalData.writeBytes(sig);
finalData.writeBytes(dataToForward); return Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(sig), forwarded);
return finalData;
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
finalData.release(); forwarded.release();
throw new RuntimeException("Unable to authenticate data", e); throw new RuntimeException("Unable to authenticate data", e);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
// Should never happen // Should never happen
finalData.release(); forwarded.release();
throw new AssertionError(e); throw new AssertionError(e);
} finally {
dataToForward.release();
} }
} }
} }

Datei anzeigen

@ -84,13 +84,13 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
.whenCompleteAsync((x, error) -> { .whenCompleteAsync((x, error) -> {
// Strap on the ClientPlaySessionHandler if required. // Strap on the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler; ClientPlaySessionHandler playHandler;
if (serverConn.getPlayer().getMinecraftConnection().getSessionHandler() if (serverConn.getPlayer().getConnection().getSessionHandler()
instanceof ClientPlaySessionHandler) { instanceof ClientPlaySessionHandler) {
playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getMinecraftConnection() playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getConnection()
.getSessionHandler(); .getSessionHandler();
} else { } else {
playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer()); playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer());
serverConn.getPlayer().getMinecraftConnection().setSessionHandler(playHandler); serverConn.getPlayer().getConnection().setSessionHandler(playHandler);
} }
playHandler.handleBackendJoinGame(packet, serverConn); playHandler.handleBackendJoinGame(packet, serverConn);
@ -167,7 +167,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
serverConn.getPlayer().getMinecraftConnection().write(packet.retain()); serverConn.getPlayer().getConnection().write(packet.retain());
return true; 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.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
@ -77,7 +76,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
CompletableFuture<Impl> result = new CompletableFuture<>(); CompletableFuture<Impl> result = new CompletableFuture<>();
// Note: we use the event loop for the connection the player is on. This reduces context // Note: we use the event loop for the connection the player is on. This reduces context
// switches. // switches.
server.initializeGenericBootstrap(proxyPlayer.getMinecraftConnection().eventLoop()) server.createBootstrap(proxyPlayer.getConnection().eventLoop())
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
@ -139,23 +138,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode(); PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode();
// Initiate the handshake. // Initiate the handshake.
ProtocolVersion protocolVersion = proxyPlayer.getMinecraftConnection().getNextProtocolVersion(); ProtocolVersion protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion();
Handshake handshake = new Handshake(); Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setNextStatus(StateRegistry.LOGIN_ID);
handshake.setProtocolVersion(protocolVersion); handshake.setProtocolVersion(protocolVersion);
if (forwardingMode == PlayerInfoForwarding.LEGACY) { if (forwardingMode == PlayerInfoForwarding.LEGACY) {
handshake.setServerAddress(createLegacyForwardingAddress()); 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); handshake.setServerAddress(handshake.getServerAddress() + HANDSHAKE_HOSTNAME_TOKEN);
} else { } else {
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
} }
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
mc.write(handshake); mc.delayedWrite(handshake);
mc.setProtocolVersion(protocolVersion); mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN); mc.setState(StateRegistry.LOGIN);
mc.write(new ServerLogin(proxyPlayer.getUsername())); mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername()));
mc.flush();
} }
public @Nullable MinecraftConnection getConnection() { 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.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
@ -59,7 +60,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final List<UUID> serverBossBars = new ArrayList<>(); private final List<UUID> serverBossBars = new ArrayList<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>(); private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server; private final VelocityServer server;
private @Nullable TabCompleteRequest legacyCommandTabComplete; private @Nullable TabCompleteRequest outstandingTabComplete;
/** /**
* Constructs a client play session handler. * Constructs a client play session handler.
@ -77,7 +78,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
.getProtocolVersion()); .getProtocolVersion());
if (!channels.isEmpty()) { if (!channels.isEmpty()) {
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels); PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
player.getMinecraftConnection().write(register); player.getConnection().write(register);
player.getKnownChannels().addAll(channels); player.getKnownChannels().addAll(channels);
} }
} }
@ -155,61 +156,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public boolean handle(TabCompleteRequest packet) { public boolean handle(TabCompleteRequest packet) {
boolean isCommand = !packet.isAssumeCommand() && packet.getCommand().startsWith("/"); boolean isCommand = !packet.isAssumeCommand() && packet.getCommand().startsWith("/");
if (!isCommand) { if (isCommand) {
// We can't deal with anything else. return this.handleCommandTabComplete(packet);
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;
} else { } 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 @Override
@ -325,7 +276,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public void writabilityChanged() { public void writabilityChanged() {
VelocityServerConnection serverConn = player.getConnectedServer(); VelocityServerConnection serverConn = player.getConnectedServer();
if (serverConn != null) { if (serverConn != null) {
boolean writable = player.getMinecraftConnection().getChannel().isWritable(); boolean writable = player.getConnection().getChannel().isWritable();
MinecraftConnection smc = serverConn.getConnection(); MinecraftConnection smc = serverConn.getConnection();
if (smc != null) { if (smc != null) {
smc.setAutoReading(writable); smc.setAutoReading(writable);
@ -345,7 +296,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (!spawned) { if (!spawned) {
// Nothing special to do with regards to spawning the player // Nothing special to do with regards to spawning the player
spawned = true; spawned = true;
player.getMinecraftConnection().delayedWrite(joinGame); player.getConnection().delayedWrite(joinGame);
// Required for Legacy Forge // Required for Legacy Forge
player.getPhase().onFirstJoin(player); 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 // 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 // to perform entity ID rewrites, eliminating potential issues from rewriting packets and
// improving compatibility with mods. // improving compatibility with mods.
player.getMinecraftConnection().delayedWrite(joinGame); player.getConnection().delayedWrite(joinGame);
int tempDim = joinGame.getDimension() == 0 ? -1 : 0; int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
player.getMinecraftConnection().delayedWrite( player.getConnection().delayedWrite(
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(),
joinGame.getLevelType())); joinGame.getLevelType()));
player.getMinecraftConnection().delayedWrite( player.getConnection().delayedWrite(
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
joinGame.getLevelType())); joinGame.getLevelType()));
} }
@ -381,7 +332,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
BossBar deletePacket = new BossBar(); BossBar deletePacket = new BossBar();
deletePacket.setUuid(serverBossBar); deletePacket.setUuid(serverBossBar);
deletePacket.setAction(BossBar.REMOVE); deletePacket.setAction(BossBar.REMOVE);
player.getMinecraftConnection().delayedWrite(deletePacket); player.getConnection().delayedWrite(deletePacket);
} }
serverBossBars.clear(); serverBossBars.clear();
@ -399,12 +350,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Clear any title from the previous server. // Clear any title from the previous server.
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
player.getMinecraftConnection() player.getConnection()
.delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); .delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
} }
// Flush everything // Flush everything
player.getMinecraftConnection().flush(); player.getConnection().flush();
serverMc.flush(); serverMc.flush();
destination.completeJoin(); destination.completeJoin();
} }
@ -413,29 +364,116 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return serverBossBars; 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 * @param response the tab complete response from the backend
*/ */
public void handleTabCompleteResponse(TabCompleteResponse response) { public void handleTabCompleteResponse(TabCompleteResponse response) {
if (legacyCommandTabComplete != null) { if (outstandingTabComplete != null) {
String command = legacyCommandTabComplete.getCommand().substring(1); if (outstandingTabComplete.isAssumeCommand()) {
return; // used for command blocks which can't run Velocity commands anyway
}
if (outstandingTabComplete.getCommand().startsWith("/")) {
this.finishCommandTabComplete(outstandingTabComplete, response);
} else {
this.finishRegularTabComplete(outstandingTabComplete, response);
}
outstandingTabComplete = null;
}
}
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
String command = request.getCommand().substring(1);
try { try {
List<String> offers = server.getCommandManager().offerSuggestions(player, command); List<String> offers = server.getCommandManager().offerSuggestions(player, command);
for (String offer : offers) { for (String offer : offers) {
response.getOffers().add(new Offer(offer, null)); response.getOffers().add(new Offer(offer, null));
} }
response.getOffers().sort(null); response.getOffers().sort(null);
player.getConnection().write(response);
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'", logger.error("Unable to provide tab list completions for {} for command '{}'",
player.getUsername(), player.getUsername(),
command, e); command, e);
} }
legacyCommandTabComplete = null;
} }
player.getMinecraftConnection().write(response); 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.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; 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. * 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 final @Nullable InetSocketAddress virtualHost;
private GameProfile profile; private GameProfile profile;
private PermissionFunction permissionFunction; private PermissionFunction permissionFunction;
@ -104,18 +103,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
private @MonotonicNonNull List<String> serversToTry = null; private @MonotonicNonNull List<String> serversToTry = null;
ConnectedPlayer(VelocityServer server, GameProfile profile, ConnectedPlayer(VelocityServer server, GameProfile profile,
MinecraftConnection minecraftConnection, @Nullable InetSocketAddress virtualHost) { MinecraftConnection connection, @Nullable InetSocketAddress virtualHost) {
this.server = server; this.server = server;
if (minecraftConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new VelocityTabList(minecraftConnection); this.tabList = new VelocityTabList(connection);
} else { } else {
this.tabList = new VelocityTabListLegacy(minecraftConnection); this.tabList = new VelocityTabListLegacy(connection);
} }
this.profile = profile; this.profile = profile;
this.minecraftConnection = minecraftConnection; this.connection = connection;
this.virtualHost = virtualHost; this.virtualHost = virtualHost;
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
this.connectionPhase = minecraftConnection.getType().getInitialClientPhase(); this.connectionPhase = connection.getType().getInitialClientPhase();
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS); this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
} }
@ -139,8 +138,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return profile; return profile;
} }
public MinecraftConnection getMinecraftConnection() { public MinecraftConnection getConnection() {
return minecraftConnection; return connection;
} }
@Override @Override
@ -175,7 +174,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override @Override
public InetSocketAddress getRemoteAddress() { public InetSocketAddress getRemoteAddress() {
return (InetSocketAddress) minecraftConnection.getRemoteAddress(); return (InetSocketAddress) connection.getRemoteAddress();
} }
@Override @Override
@ -189,12 +188,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override @Override
public boolean isActive() { public boolean isActive() {
return minecraftConnection.getChannel().isActive(); return connection.getChannel().isActive();
} }
@Override @Override
public ProtocolVersion getProtocolVersion() { public ProtocolVersion getProtocolVersion() {
return minecraftConnection.getProtocolVersion(); return connection.getProtocolVersion();
} }
@Override @Override
@ -210,7 +209,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
TitlePacket pkt = new TitlePacket(); TitlePacket pkt = new TitlePacket();
pkt.setAction(TitlePacket.SET_ACTION_BAR); pkt.setAction(TitlePacket.SET_ACTION_BAR);
pkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(component)); pkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(component));
minecraftConnection.write(pkt); connection.write(pkt);
return; return;
} else { } else {
// Due to issues with action bar packets, we'll need to convert the text message into a // 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 chat = new Chat();
chat.setType(pos); chat.setType(pos);
chat.setMessage(json); chat.setMessage(json);
minecraftConnection.write(chat); connection.write(chat);
} }
@Override @Override
@ -263,23 +262,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
public void disconnect(Component reason) { public void disconnect(Component reason) {
logger.info("{} has disconnected: {}", this, logger.info("{} has disconnected: {}", this,
LegacyComponentSerializer.legacy().serialize(reason)); LegacyComponentSerializer.legacy().serialize(reason));
minecraftConnection.closeWith(Disconnect.create(reason)); connection.closeWith(Disconnect.create(reason));
} }
@Override @Override
public void sendTitle(Title title) { public void sendTitle(Title title) {
Preconditions.checkNotNull(title, "title"); Preconditions.checkNotNull(title, "title");
ProtocolVersion protocolVersion = minecraftConnection.getProtocolVersion(); ProtocolVersion protocolVersion = connection.getProtocolVersion();
if (title.equals(Titles.reset())) { if (title.equals(Titles.reset())) {
minecraftConnection.write(TitlePacket.resetForProtocolVersion(protocolVersion)); connection.write(TitlePacket.resetForProtocolVersion(protocolVersion));
} else if (title.equals(Titles.hide())) { } else if (title.equals(Titles.hide())) {
minecraftConnection.write(TitlePacket.hideForProtocolVersion(protocolVersion)); connection.write(TitlePacket.hideForProtocolVersion(protocolVersion));
} else if (title instanceof TextTitle) { } else if (title instanceof TextTitle) {
TextTitle tt = (TextTitle) title; TextTitle tt = (TextTitle) title;
if (tt.isResetBeforeSend()) { if (tt.isResetBeforeSend()) {
minecraftConnection.delayedWrite(TitlePacket.resetForProtocolVersion(protocolVersion)); connection.delayedWrite(TitlePacket.resetForProtocolVersion(protocolVersion));
} }
Optional<Component> titleText = tt.getTitle(); Optional<Component> titleText = tt.getTitle();
@ -287,7 +286,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
TitlePacket titlePkt = new TitlePacket(); TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_TITLE); titlePkt.setAction(TitlePacket.SET_TITLE);
titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(titleText.get())); titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(titleText.get()));
minecraftConnection.delayedWrite(titlePkt); connection.delayedWrite(titlePkt);
} }
Optional<Component> subtitleText = tt.getSubtitle(); Optional<Component> subtitleText = tt.getSubtitle();
@ -295,7 +294,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
TitlePacket titlePkt = new TitlePacket(); TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_SUBTITLE); titlePkt.setAction(TitlePacket.SET_SUBTITLE);
titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(subtitleText.get())); titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(subtitleText.get()));
minecraftConnection.delayedWrite(titlePkt); connection.delayedWrite(titlePkt);
} }
if (tt.areTimesSet()) { if (tt.areTimesSet()) {
@ -303,9 +302,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
timesPkt.setFadeIn(tt.getFadeIn()); timesPkt.setFadeIn(tt.getFadeIn());
timesPkt.setStay(tt.getStay()); timesPkt.setStay(tt.getStay());
timesPkt.setFadeOut(tt.getFadeOut()); timesPkt.setFadeOut(tt.getFadeOut());
minecraftConnection.delayedWrite(timesPkt); connection.delayedWrite(timesPkt);
} }
minecraftConnection.flush(); connection.flush();
} else { } else {
throw new IllegalArgumentException("Unknown title class " + title.getClass().getName()); throw new IllegalArgumentException("Unknown title class " + title.getClass().getName());
} }
@ -457,9 +456,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
if (newResult == null || !newResult) { if (newResult == null || !newResult) {
disconnect(friendlyReason); disconnect(friendlyReason);
} else { } 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) { } else if (event.getResult() instanceof Notify) {
Notify res = (Notify) event.getResult(); Notify res = (Notify) event.getResult();
if (event.kickedDuringServerConnect()) { 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. // In case someone gets creative, assume we want to disconnect the player.
disconnect(friendlyReason); disconnect(friendlyReason);
} }
}, minecraftConnection.eventLoop()); }, connection.eventLoop());
} }
/** /**
@ -584,7 +583,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data"); Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data)); PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data));
minecraftConnection.write(message); connection.write(message);
return true; return true;
} }
@ -603,7 +602,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ResourcePackRequest request = new ResourcePackRequest(); ResourcePackRequest request = new ResourcePackRequest();
request.setUrl(url); request.setUrl(url);
request.setHash(""); request.setHash("");
minecraftConnection.write(request); connection.write(request);
} }
@Override @Override
@ -615,7 +614,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ResourcePackRequest request = new ResourcePackRequest(); ResourcePackRequest request = new ResourcePackRequest();
request.setUrl(url); request.setUrl(url);
request.setHash(ByteBufUtil.hexDump(hash)); 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. * ID last sent by the server.
*/ */
public void sendKeepAlive() { public void sendKeepAlive() {
if (minecraftConnection.getState() == StateRegistry.PLAY) { if (connection.getState() == StateRegistry.PLAY) {
KeepAlive keepAlive = new KeepAlive(); KeepAlive keepAlive = new KeepAlive();
keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); 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 (status != null && !status.isSafe()) {
// If it's not safe to continue the connection we need to shut it down. // If it's not safe to continue the connection we need to shut it down.
handleConnectionException(status.getAttemptedConnection(), throwable, true); handleConnectionException(status.getAttemptedConnection(), throwable, true);
} else if ((status != null && !status.isSuccessful())) {
resetInFlightConnection();
} }
}) }, connection.eventLoop())
.thenApply(x -> x); .thenApply(x -> x);
} }
@ -785,7 +786,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// The only remaining value is successful (no need to do anything!) // The only remaining value is successful (no need to do anything!)
break; break;
} }
}, minecraftConnection.eventLoop()) }, connection.eventLoop())
.thenApply(Result::isSuccessful); .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.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL;
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa; import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId; 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.base.Preconditions;
import com.google.common.net.UrlEscapers; import com.google.common.net.UrlEscapers;
@ -43,10 +45,14 @@ import java.security.KeyPair;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
@ -124,9 +130,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
String url = String.format(MOJANG_HASJOINED_URL, String url = String.format(MOJANG_HASJOINED_URL,
urlFormParameterEscaper().escape(login.getUsername()), serverId, urlFormParameterEscaper().escape(login.getUsername()), serverId,
urlFormParameterEscaper().escape(playerIp)); urlFormParameterEscaper().escape(playerIp));
server.getHttpClient()
.get(new URL(url), mcConnection.eventLoop()) ListenableFuture<Response> hasJoinedResponse = server.getAsyncHttpClient().prepareGet(url)
.thenAcceptAsync(profileResponse -> { .execute();
hasJoinedResponse.addListener(() -> {
if (mcConnection.isClosed()) { if (mcConnection.isClosed()) {
// The player disconnected after we authenticated them. // The player disconnected after we authenticated them.
return; return;
@ -140,30 +147,33 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if (profileResponse.getCode() == 200) { try {
Response profileResponse = hasJoinedResponse.get();
if (profileResponse.getStatusCode() == 200) {
// All went well, initialize the session. // All went well, initialize the session.
initializePlayer(GSON.fromJson(profileResponse.getBody(), GameProfile.class), true); initializePlayer(GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class),
} else if (profileResponse.getCode() == 204) { true);
} else if (profileResponse.getStatusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy. // Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(VelocityMessages.ONLINE_MODE_ONLY); inbound.disconnect(VelocityMessages.ONLINE_MODE_ONLY);
} else { } else {
// Something else went wrong // Something else went wrong
logger.error( logger.error(
"Got an unexpected error code {} whilst contacting Mojang to log in {} ({})", "Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
profileResponse.getCode(), login.getUsername(), playerIp); profileResponse.getStatusCode(), login.getUsername(), playerIp);
mcConnection.close(); mcConnection.close();
} }
}, mcConnection.eventLoop()) } catch (ExecutionException e) {
.exceptionally(exception -> { logger.error("Unable to authenticate with Mojang", e);
logger.error("Unable to enable encryption", exception);
mcConnection.close(); mcConnection.close();
return null; } catch (InterruptedException e) {
}); // not much we can do usefully
Thread.currentThread().interrupt();
}
}, mcConnection.eventLoop());
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
logger.error("Unable to enable encryption", e); logger.error("Unable to enable encryption", e);
mcConnection.close(); mcConnection.close();
} catch (MalformedURLException e) {
throw new AssertionError(e);
} }
return true; return true;
} }
@ -180,6 +190,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// The player was disconnected // The player was disconnected
return; return;
} }
PreLoginComponentResult result = event.getResult(); PreLoginComponentResult result = event.getResult();
Optional<Component> disconnectReason = result.getReason(); Optional<Component> disconnectReason = result.getReason();
if (disconnectReason.isPresent()) { if (disconnectReason.isPresent()) {

Datei anzeigen

@ -1,12 +1,15 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.spotify.futures.CompletableFutures;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.util.ModInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PingPassthroughMode;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; 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.StatusPing;
import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse; import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.buffer.ByteBuf; 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 { public class StatusSessionHandler implements MinecraftSessionHandler {
private final VelocityServer server; private final VelocityServer server;
private final MinecraftConnection connection; private final MinecraftConnection connection;
private final InboundConnection inboundWrapper; private final InboundConnection inbound;
StatusSessionHandler(VelocityServer server, MinecraftConnection connection, StatusSessionHandler(VelocityServer server, MinecraftConnection connection,
InboundConnection inboundWrapper) { InboundConnection inbound) {
this.server = server; this.server = server;
this.connection = connection; this.connection = connection;
this.inboundWrapper = inboundWrapper; this.inbound = inbound;
} }
private ServerPing createInitialPing() { private ServerPing constructLocalPing(ProtocolVersion version) {
VelocityConfiguration configuration = server.getConfiguration(); VelocityConfiguration configuration = server.getConfiguration();
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
return new ServerPing( return new ServerPing(
new ServerPing.Version(shownVersion.getProtocol(), new ServerPing.Version(version.getProtocol(),
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING), "Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
ImmutableList.of()), 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 @Override
public boolean handle(LegacyPing packet) { public boolean handle(LegacyPing packet) {
ServerPing initialPing = createInitialPing(); getInitialPing()
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
server.getEventManager().fire(event) .thenAcceptAsync(event -> {
.thenRunAsync(() -> {
connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(), connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(),
packet.getVersion())); packet.getVersion()));
}, connection.eventLoop()); }, connection.eventLoop());
@ -65,11 +139,10 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(StatusRequest packet) { public boolean handle(StatusRequest packet) {
ServerPing initialPing = createInitialPing(); getInitialPing()
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
server.getEventManager().fire(event) .thenAcceptAsync(
.thenRunAsync( (event) -> {
() -> {
StringBuilder json = new StringBuilder(); StringBuilder json = new StringBuilder();
VelocityServer.GSON.toJson(event.getPing(), json); VelocityServer.GSON.toJson(event.getPing(), json);
connection.write(new StatusResponse(json)); connection.write(new StatusResponse(json));

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,8 +1,12 @@
package com.velocitypowered.proxy.network; 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.google.common.base.Preconditions;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.network.netty.DnsAddressResolverGroupNameResolverAdapter;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler; import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
@ -11,17 +15,26 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.resolver.dns.DnsAddressResolverGroup; import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsNameResolverBuilder; import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.util.concurrent.EventExecutor;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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 { 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); 1 << 21);
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
private final Map<InetSocketAddress, Channel> endpoints = new HashMap<>(); private final Map<InetSocketAddress, Channel> endpoints = new HashMap<>();
@ -35,6 +48,7 @@ public final class ConnectionManager {
public final ServerChannelInitializerHolder serverChannelInitializer; public final ServerChannelInitializerHolder serverChannelInitializer;
private final DnsAddressResolverGroup resolverGroup; private final DnsAddressResolverGroup resolverGroup;
private final AsyncHttpClient httpClient;
/** /**
* Initalizes the {@code ConnectionManager}. * Initalizes the {@code ConnectionManager}.
@ -48,12 +62,26 @@ public final class ConnectionManager {
this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER); this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER);
this.serverChannelInitializer = new ServerChannelInitializerHolder( this.serverChannelInitializer = new ServerChannelInitializerHolder(
new ServerChannelInitializer(this.server)); new ServerChannelInitializer(this.server));
this.resolverGroup = new DnsAddressResolverGroup( this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder()
new DnsNameResolverBuilder()
.channelType(this.transportType.datagramChannelClass) .channelType(this.transportType.datagramChannelClass)
.negativeTtl(15) .negativeTtl(15)
.ndots(1) .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() { public void logChannelInformation() {
@ -75,6 +103,11 @@ public final class ConnectionManager {
.childOption(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.IP_TOS, 0x18) .childOption(ChannelOption.IP_TOS, 0x18)
.localAddress(address); .localAddress(address);
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(EpollChannelOption.TCP_FASTOPEN, 3);
}
bootstrap.bind() bootstrap.bind()
.addListener((ChannelFutureListener) future -> { .addListener((ChannelFutureListener) future -> {
final Channel channel = future.channel(); 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. * 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} * @return a new {@link Bootstrap}
*/ */
public Bootstrap createWorker(EventLoopGroup group) { public Bootstrap createWorker(@Nullable EventLoopGroup group) {
return new Bootstrap() Bootstrap bootstrap = new Bootstrap()
.channel(this.transportType.socketChannelClass) .channel(this.transportType.socketChannelClass)
.group(group)
.option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
this.server.getConfiguration().getConnectTimeout()) this.server.getConfiguration().getConnectTimeout())
.group(group == null ? this.workerGroup : group)
.resolver(this.resolverGroup); .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; return bossGroup;
} }
public EventLoopGroup getWorkerGroup() {
return workerGroup;
}
public ServerChannelInitializerHolder getServerChannelInitializer() { public ServerChannelInitializerHolder getServerChannelInitializer() {
return this.serverChannelInitializer; 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.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel; 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.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.ServerSocketChannel;
@ -27,10 +22,7 @@ enum TransportType {
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))), (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class, EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
EpollDatagramChannel.class, EpollDatagramChannel.class,
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))), (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)));
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
KQueueDatagramChannel.class,
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
final String name; final String name;
final Class<? extends ServerSocketChannel> serverSocketChannelClass; final Class<? extends ServerSocketChannel> serverSocketChannelClass;
@ -64,10 +56,12 @@ enum TransportType {
} }
public static TransportType bestType() { public static TransportType bestType() {
if (Boolean.getBoolean("velocity.disable-native-transport")) {
return NIO;
}
if (Epoll.isAvailable()) { if (Epoll.isAvailable()) {
return EPOLL; return EPOLL;
} else if (KQueue.isAvailable()) {
return KQUEUE;
} else { } else {
return NIO; 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", checkFrame(expectedSize >= threshold, "Uncompressed size %s is greater than threshold %s",
expectedSize, threshold); expectedSize, threshold);
checkFrame(expectedSize <= MAXIMUM_UNCOMPRESSED_SIZE, "Expected uncompressed size" int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
+ "%s is larger than protocol maximum of %s", expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, expectedSize); ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
try { try {
compressor.inflate(compatibleIn, uncompressed, expectedSize); compressor.inflate(compatibleIn, uncompressed, expectedSize);
out.add(uncompressed); out.add(uncompressed);

Datei anzeigen

@ -133,5 +133,9 @@ public class TabCompleteResponse implements MinecraftPacket {
public int compareTo(Offer o) { public int compareTo(Offer o) {
return this.text.compareTo(o.text); 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 CompletableFuture<ServerPing> result;
private final RegisteredServer server; private final RegisteredServer server;
private final MinecraftConnection connection; private final MinecraftConnection connection;
private final ProtocolVersion version;
private boolean completed = false; private boolean completed = false;
PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server, PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server,
MinecraftConnection connection) { MinecraftConnection connection, ProtocolVersion version) {
this.result = result; this.result = result;
this.server = server; this.server = server;
this.connection = connection; this.connection = connection;
this.version = version;
} }
@Override @Override
@ -33,11 +35,13 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setNextStatus(StateRegistry.STATUS_ID); handshake.setNextStatus(StateRegistry.STATUS_ID);
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
handshake.setPort(server.getServerInfo().getAddress().getPort()); handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(ProtocolVersion.MINIMUM_VERSION); handshake.setProtocolVersion(version);
connection.write(handshake); connection.delayedWrite(handshake);
connection.setState(StateRegistry.STATUS); connection.setState(StateRegistry.STATUS);
connection.write(StatusRequest.INSTANCE); connection.delayedWrite(StatusRequest.INSTANCE);
connection.flush();
} }
@Override @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.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 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.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils; 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.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; 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.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
@ -59,11 +60,22 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override @Override
public CompletableFuture<ServerPing> ping() { 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) { if (server == null) {
throw new IllegalStateException("No Velocity proxy instance available"); throw new IllegalStateException("No Velocity proxy instance available");
} }
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>(); CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.initializeGenericBootstrap() server.createBootstrap(loop)
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
@ -87,8 +99,8 @@ public class VelocityRegisteredServer implements RegisteredServer {
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) { if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler( conn.setSessionHandler(new PingSessionHandler(
new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn)); pingFuture, VelocityRegisteredServer.this, conn, version));
} else { } else {
pingFuture.completeExceptionally(future.cause()); pingFuture.completeExceptionally(future.cause());
} }

Datei anzeigen

@ -1,6 +1,8 @@
package com.velocitypowered.proxy.util; package com.velocitypowered.proxy.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
@ -19,8 +21,13 @@ public class AddressUtil {
public static InetSocketAddress parseAddress(String ip) { public static InetSocketAddress parseAddress(String ip) {
Preconditions.checkNotNull(ip, "ip"); Preconditions.checkNotNull(ip, "ip");
URI uri = URI.create("tcp://" + ip); URI uri = URI.create("tcp://" + ip);
try {
InetAddress ia = InetAddresses.forUriString(uri.getHost());
return new InetSocketAddress(ia, uri.getPort());
} catch (IllegalArgumentException e) {
return InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort()); return InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort());
} }
}
/** /**
* Attempts to parse an IP address of the form <code>127.0.0.1:25565</code>. The returned * Attempts to parse an IP address of the form <code>127.0.0.1:25565</code>. The returned

Datei anzeigen

@ -21,7 +21,7 @@ public class VelocityMessages {
public static final Component ALREADY_CONNECTED = TextComponent public static final Component ALREADY_CONNECTED = TextComponent
.of("You are already connected to this proxy!", TextColor.RED); .of("You are already connected to this proxy!", TextColor.RED);
public static final Component MOVED_TO_NEW_SERVER = TextComponent 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() { private VelocityMessages() {
throw new AssertionError(); 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) { private void sendPacket(Player player, MinecraftPacket packet) {
ConnectedPlayer connected = (ConnectedPlayer) player; 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 log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
log4j.skipJansi=true