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

Convert Velocity to use ByteBuf-derived plugin message packets

Only tested with 1.12.2, not 1.13 or Forge
Dieser Commit ist enthalten in:
Andrew Steinborn 2019-07-03 09:20:32 -04:00
Ursprung 39c505f6fd
Commit 11a86e9bb9
13 geänderte Dateien mit 264 neuen und 74 gelöschten Zeilen

Datei anzeigen

@ -1,5 +1,7 @@
package com.velocitypowered.proxy;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetector.Level;
import java.text.DecimalFormat;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -35,6 +37,8 @@ public class Velocity {
return;
}
ResourceLeakDetector.setLevel(Level.PARANOID);
long startTime = System.currentTimeMillis();
VelocityServer server = new VelocityServer(options);

Datei anzeigen

@ -131,8 +131,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
try {
sessionHandler.exception(cause);
} catch (Exception ex) {
logger.error("{}: exception handling exception", (association != null ? association :
channel.remoteAddress()), cause);
logger.error("{}: exception handling exception in {}",
(association != null ? association : channel.remoteAddress()), sessionHandler, cause);
}
}
@ -140,7 +140,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (cause instanceof ReadTimeoutException) {
logger.error("{}: read timed out", association);
} else {
logger.error("{}: exception encountered", association, cause);
logger.error("{}: exception encountered in {}", association, sessionHandler, cause);
}
}

Datei anzeigen

@ -25,6 +25,8 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@ -119,12 +121,15 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false;
}
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id,
packet.getData());
copy);
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
playerConnection.write(packet);
PluginMessage copied = new PluginMessage(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
playerConnection.write(copied);
}
}, playerConnection.eventLoop());
return true;
@ -163,6 +168,9 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void handleGeneric(MinecraftPacket packet) {
if (packet instanceof PluginMessage) {
((PluginMessage) packet).retain();
}
playerConnection.write(packet);
}

Datei anzeigen

@ -33,6 +33,7 @@ import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
@ -212,9 +213,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
MinecraftConnection mc = ensureConnected();
PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId());
message.setData(data);
PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data));
mc.write(message);
return true;
}

Datei anzeigen

@ -29,6 +29,9 @@ import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer;
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@ -80,6 +83,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
}
@Override
public void deactivated() {
for (PluginMessage message : loginPluginMessages) {
ReferenceCountUtil.release(message);
}
}
@Override
public boolean handle(KeepAlive packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
@ -213,10 +223,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) {
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet);
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet);
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isMcBrand(packet)) {
backendConn.write(PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion()));
} else {
@ -236,16 +246,23 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// but further aggravated by Velocity. To work around these issues, we will queue any
// non-FML handshake messages to be sent once the FML handshake has completed or the
// JoinGame packet has been received by the proxy, whichever comes first.
loginPluginMessages.add(packet);
//
// We also need to make sure to retain these packets so they can be flushed
// appropriately.
loginPluginMessages.add(packet.retain());
} else {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
backendConn.write(packet);
backendConn.write(packet.retain());
} else {
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
packet.getData());
server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet),
backendConn.eventLoop());
ByteBufUtil.getBytes(packet.content()));
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
PluginMessage message = new PluginMessage(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
backendConn.write(message);
}, backendConn.eventLoop());
}
}
}
@ -272,6 +289,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && serverConnection.getPhase().consideredComplete()) {
if (packet instanceof PluginMessage) {
((PluginMessage) packet).retain();
}
smc.write(packet);
}
}

Datei anzeigen

@ -49,6 +49,7 @@ import com.velocitypowered.proxy.tablist.VelocityTabList;
import com.velocitypowered.proxy.util.VelocityMessages;
import com.velocitypowered.proxy.util.collect.CappedSet;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Collections;
@ -570,9 +571,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId());
message.setData(data);
PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data));
minecraftConnection.write(message);
return true;
}

Datei anzeigen

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

Datei anzeigen

@ -212,7 +212,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
PluginMessage message,
MinecraftConnection backendConn) {
// Send the packet on to the server.
backendConn.write(message);
backendConn.write(message.retain());
// We handled the packet. No need to continue processing.
return true;

Datei anzeigen

@ -27,9 +27,8 @@ class LegacyForgeUtil {
*/
static byte getHandshakePacketDiscriminator(PluginMessage message) {
Preconditions.checkArgument(message.getChannel().equals(FORGE_LEGACY_HANDSHAKE_CHANNEL));
byte[] data = message.getData();
Preconditions.checkArgument(data.length >= 1);
return data[0];
Preconditions.checkArgument(message.content().isReadable());
return message.content().getByte(0);
}
/**
@ -44,7 +43,7 @@ class LegacyForgeUtil {
.checkArgument(message.getChannel().equals(FORGE_LEGACY_HANDSHAKE_CHANNEL),
"message is not a FML HS plugin message");
ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData());
ByteBuf byteBuf = message.content().retainedSlice();
try {
byte discriminator = byteBuf.readByte();
@ -75,7 +74,7 @@ class LegacyForgeUtil {
static PluginMessage resetPacket() {
PluginMessage msg = new PluginMessage();
msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL);
msg.setData(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone());
msg.replace(Unpooled.wrappedBuffer(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()));
return msg;
}
}

Datei anzeigen

@ -4,24 +4,24 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LoginPluginMessage implements MinecraftPacket {
public class LoginPluginMessage extends DeferredByteBufHolder implements MinecraftPacket {
private int id;
private @Nullable String channel;
private ByteBuf data = Unpooled.EMPTY_BUFFER;
public LoginPluginMessage() {
super(null);
}
public LoginPluginMessage(int id, @Nullable String channel, ByteBuf data) {
super(data);
this.id = id;
this.channel = channel;
this.data = data;
}
public int getId() {
@ -35,16 +35,12 @@ public class LoginPluginMessage implements MinecraftPacket {
return channel;
}
public ByteBuf getData() {
return data;
}
@Override
public String toString() {
return "LoginPluginMessage{"
+ "id=" + id
+ ", channel='" + channel + '\''
+ ", data=" + data
+ ", data=" + super.toString()
+ '}';
}
@ -53,9 +49,9 @@ public class LoginPluginMessage implements MinecraftPacket {
this.id = ProtocolUtils.readVarInt(buf);
this.channel = ProtocolUtils.readString(buf);
if (buf.isReadable()) {
this.data = buf.readSlice(buf.readableBytes());
this.replace(buf.readSlice(buf.readableBytes()));
} else {
this.data = Unpooled.EMPTY_BUFFER;
this.replace(Unpooled.EMPTY_BUFFER);
}
}
@ -66,7 +62,7 @@ public class LoginPluginMessage implements MinecraftPacket {
throw new IllegalStateException("Channel is not specified!");
}
ProtocolUtils.writeString(buf, channel);
buf.writeBytes(data);
buf.writeBytes(content());
}
@Override

Datei anzeigen

@ -1,21 +1,29 @@
package com.velocitypowered.proxy.protocol.packet;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.transformLegacyToModernChannel;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class PluginMessage implements MinecraftPacket {
public class PluginMessage extends DeferredByteBufHolder implements MinecraftPacket {
private @Nullable String channel;
private byte[] data = EMPTY_BYTE_ARRAY;
public PluginMessage() {
super(null);
}
public PluginMessage(String channel,
@MonotonicNonNull ByteBuf backing) {
super(backing);
this.channel = channel;
}
public String getChannel() {
if (channel == null) {
@ -28,19 +36,11 @@ public class PluginMessage implements MinecraftPacket {
this.channel = channel;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
@Override
public String toString() {
return "PluginMessage{"
+ "channel='" + channel + '\''
+ ", data=<removed>"
+ ", data=" + super.toString()
+ '}';
}
@ -50,8 +50,7 @@ public class PluginMessage implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) {
this.channel = transformLegacyToModernChannel(this.channel);
}
this.data = new byte[buf.readableBytes()];
buf.readBytes(data);
this.replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
@ -64,11 +63,51 @@ public class PluginMessage implements MinecraftPacket {
} else {
ProtocolUtils.writeString(buf, this.channel);
}
buf.writeBytes(data);
buf.writeBytes(content());
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public PluginMessage copy() {
return (PluginMessage) super.copy();
}
@Override
public PluginMessage duplicate() {
return (PluginMessage) super.duplicate();
}
@Override
public PluginMessage retainedDuplicate() {
return (PluginMessage) super.retainedDuplicate();
}
@Override
public PluginMessage replace(ByteBuf content) {
return (PluginMessage) super.replace(content);
}
@Override
public PluginMessage retain() {
return (PluginMessage) super.retain();
}
@Override
public PluginMessage retain(int increment) {
return (PluginMessage) super.retain(increment);
}
@Override
public PluginMessage touch() {
return (PluginMessage) super.touch();
}
@Override
public PluginMessage touch(Object hint) {
return (PluginMessage) super.touch(hint);
}
}

Datei anzeigen

@ -0,0 +1,136 @@
package com.velocitypowered.proxy.protocol.util;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.util.IllegalReferenceCountException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A special-purpose implementation of {@code ByteBufHolder} that can defer accepting its buffer.
* This is required because Velocity packets are, for better or worse, mutable.
*/
public class DeferredByteBufHolder implements ByteBufHolder {
@MonotonicNonNull
private ByteBuf backing;
public DeferredByteBufHolder(
@MonotonicNonNull ByteBuf backing) {
this.backing = backing;
}
@Override
public ByteBuf content() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
if (backing.refCnt() <= 0) {
throw new IllegalReferenceCountException(backing.refCnt());
}
return backing;
}
@Override
public ByteBufHolder copy() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return new DeferredByteBufHolder(backing.copy());
}
@Override
public ByteBufHolder duplicate() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return new DeferredByteBufHolder(backing.duplicate());
}
@Override
public ByteBufHolder retainedDuplicate() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return new DeferredByteBufHolder(backing.retainedDuplicate());
}
@Override
public ByteBufHolder replace(ByteBuf content) {
if (content == null) {
throw new NullPointerException("content");
}
this.backing = content;
return this;
}
@Override
public int refCnt() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return backing.refCnt();
}
@Override
public ByteBufHolder retain() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.retain();
return this;
}
@Override
public ByteBufHolder retain(int increment) {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.retain(increment);
return this;
}
@Override
public ByteBufHolder touch() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.touch();
return this;
}
@Override
public ByteBufHolder touch(Object hint) {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.touch(hint);
return this;
}
@Override
public boolean release() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return backing.release();
}
@Override
public boolean release(int decrement) {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return backing.release(decrement);
}
@Override
public String toString() {
String str = "DeferredByteBufHolder[";
if (backing == null) {
str += "null";
} else {
str += backing.toString();
}
return str + "]";
}
}

Datei anzeigen

@ -11,7 +11,9 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.ByteProcessor;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
@ -93,12 +95,12 @@ public class PluginMessageUtil {
checkNotNull(message, "message");
checkArgument(isRegister(message) || isUnregister(message), "Unknown channel type %s",
message.getChannel());
if (message.getData().length == 0) {
if (!message.content().isReadable()) {
// If we try to split this, we will get an one-element array with the empty string, which
// has caused issues with 1.13+ compatibility. Just return an empty list.
return ImmutableList.of();
}
String channels = new String(message.getData(), StandardCharsets.UTF_8);
String channels = message.content().toString(StandardCharsets.UTF_8);
return ImmutableList.copyOf(channels.split("\0"));
}
@ -114,10 +116,9 @@ public class PluginMessageUtil {
Preconditions.checkArgument(channels.size() > 0, "no channels specified");
String channelName = protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0
? REGISTER_CHANNEL : REGISTER_CHANNEL_LEGACY;
PluginMessage message = new PluginMessage();
message.setChannel(channelName);
message.setData(String.join("\0", channels).getBytes(StandardCharsets.UTF_8));
return message;
ByteBuf contents = Unpooled.buffer();
contents.writeCharSequence(String.join("\0", channels), StandardCharsets.UTF_8);
return new PluginMessage(channelName, contents);
}
/**
@ -133,21 +134,11 @@ public class PluginMessageUtil {
String toAppend = " (" + version.getName() + ")";
byte[] rewrittenData;
ByteBuf rewrittenBuf = Unpooled.buffer();
try {
String currentBrand = ProtocolUtils.readString(Unpooled.wrappedBuffer(message.getData()));
ProtocolUtils.writeString(rewrittenBuf, currentBrand + toAppend);
rewrittenData = new byte[rewrittenBuf.readableBytes()];
rewrittenBuf.readBytes(rewrittenData);
} finally {
rewrittenBuf.release();
}
String currentBrand = ProtocolUtils.readString(message.content().slice());
ProtocolUtils.writeString(rewrittenBuf, currentBrand + toAppend);
PluginMessage newMsg = new PluginMessage();
newMsg.setChannel(message.getChannel());
newMsg.setData(rewrittenData);
return newMsg;
return new PluginMessage(message.getChannel(), rewrittenBuf);
}
private static final Pattern INVALID_IDENTIFIER_REGEX = Pattern.compile("[^a-z0-9\\-_]*");
@ -185,5 +176,4 @@ public class PluginMessageUtil {
return "legacy:" + INVALID_IDENTIFIER_REGEX.matcher(lower).replaceAll("");
}
}
}