Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement session restructure mcpl PR #5196

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

package org.geysermc.geyser.platform.spigot;

import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
Expand Down Expand Up @@ -176,9 +177,9 @@ private ChannelInitializer<Channel> getChildHandler(GeyserBootstrap bootstrap, C
*/
private void workAroundWeirdBug(GeyserBootstrap bootstrap) {
MinecraftProtocol protocol = new MinecraftProtocol();
LocalSession session = new LocalSession(bootstrap.getGeyserConfig().getRemote().address(),
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
InetAddress.getLoopbackAddress().getHostAddress(), protocol, Runnable::run);
LocalSession session = new LocalSession(this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, Runnable::run);
session.setFlag(MinecraftConstants.CLIENT_HOST, bootstrap.getGeyserConfig().getRemote().address());
session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.getGeyserConfig().getRemote().port());
session.connect();
}

Expand Down
1 change: 0 additions & 1 deletion core/src/main/java/org/geysermc/geyser/GeyserImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
import org.geysermc.geyser.util.NewsHandler;
import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils;
import org.geysermc.mcprotocollib.network.tcp.TcpSession;

import java.io.File;
import java.io.FileWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,19 @@
import io.netty.channel.local.LocalChannel;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
* Client -> server storing the spoofed remote address.
*/
public class LocalChannelWithRemoteAddress extends LocalChannel {
private InetSocketAddress spoofedAddress;
private SocketAddress spoofedAddress;

public InetSocketAddress spoofedRemoteAddress() {
public SocketAddress spoofedRemoteAddress() {
return spoofedAddress;
}

public void spoofedRemoteAddress(InetSocketAddress socketAddress) {
public void spoofedRemoteAddress(SocketAddress socketAddress) {
this.spoofedAddress = socketAddress;
}
}
149 changes: 32 additions & 117 deletions core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,151 +27,66 @@

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.unix.PreferredDirectByteBufAllocator;
import io.netty.handler.codec.haproxy.HAProxyCommand;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.haproxy.HAProxyMessageEncoder;
import io.netty.handler.codec.haproxy.HAProxyProtocolVersion;
import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.mcprotocollib.network.BuiltinFlags;
import org.geysermc.mcprotocollib.network.codec.PacketCodecHelper;
import org.geysermc.mcprotocollib.network.helper.NettyHelper;
import org.geysermc.mcprotocollib.network.net.MinecraftChannelInitializer;
import org.geysermc.mcprotocollib.network.net.NetClientSession;
import org.geysermc.mcprotocollib.network.packet.PacketProtocol;
import org.geysermc.mcprotocollib.network.tcp.FlushHandler;
import org.geysermc.mcprotocollib.network.tcp.TcpFlowControlHandler;
import org.geysermc.mcprotocollib.network.tcp.TcpPacketCodec;
import org.geysermc.mcprotocollib.network.tcp.TcpPacketCompression;
import org.geysermc.mcprotocollib.network.tcp.TcpPacketEncryptor;
import org.geysermc.mcprotocollib.network.tcp.TcpPacketSizer;
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;

import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

/**
* Manages a Minecraft Java session over our LocalChannel implementations.
*/
public final class LocalSession extends TcpSession {
private static DefaultEventLoopGroup DEFAULT_EVENT_LOOP_GROUP;
public final class LocalSession extends NetClientSession {
private static PreferredDirectByteBufAllocator PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR = null;

private final SocketAddress targetAddress;
private final String clientIp;
private final PacketCodecHelper codecHelper;
private final SocketAddress spoofedRemoteAddress;

public LocalSession(String host, int port, SocketAddress targetAddress, String clientIp, PacketProtocol protocol, Executor packetHandlerExecutor) {
super(host, port, protocol, packetHandlerExecutor);
this.targetAddress = targetAddress;
this.clientIp = clientIp;
this.codecHelper = protocol.createHelper();
public LocalSession(SocketAddress targetAddress, String clientIp, PacketProtocol protocol, Executor packetHandlerExecutor) {
super(targetAddress, null, protocol, null, packetHandlerExecutor);
this.spoofedRemoteAddress = new InetSocketAddress(clientIp, 0);
}

@Override
public void connect(boolean wait, boolean transferring) {
if (this.disconnected) {
throw new IllegalStateException("Connection has already been disconnected.");
}

if (DEFAULT_EVENT_LOOP_GROUP == null) {
DEFAULT_EVENT_LOOP_GROUP = new DefaultEventLoopGroup(new DefaultThreadFactory(this.getClass(), true));
Runtime.getRuntime().addShutdownHook(new Thread(
() -> DEFAULT_EVENT_LOOP_GROUP.shutdownGracefully(100, 500, TimeUnit.MILLISECONDS)));
}

final Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(LocalChannelWithRemoteAddress.class);
bootstrap.handler(new ChannelInitializer<LocalChannelWithRemoteAddress>() {
@Override
public void initChannel(@NonNull LocalChannelWithRemoteAddress channel) {
channel.spoofedRemoteAddress(new InetSocketAddress(clientIp, 0));
PacketProtocol protocol = getPacketProtocol();
protocol.newClientSession(LocalSession.this, transferring);

ChannelPipeline pipeline = channel.pipeline();

addHAProxySupport(pipeline);

pipeline.addLast("read-timeout", new ReadTimeoutHandler(getFlag(BuiltinFlags.READ_TIMEOUT, 30)));
pipeline.addLast("write-timeout", new WriteTimeoutHandler(getFlag(BuiltinFlags.WRITE_TIMEOUT, 0)));

pipeline.addLast("encryption", new TcpPacketEncryptor());
pipeline.addLast("sizer", new TcpPacketSizer(protocol.getPacketHeader(), getCodecHelper()));
pipeline.addLast("compression", new TcpPacketCompression(getCodecHelper()));

pipeline.addLast("flow-control", new TcpFlowControlHandler());
pipeline.addLast("codec", new TcpPacketCodec(LocalSession.this, true));
pipeline.addLast("flush-handler", new FlushHandler());
pipeline.addLast("manager", LocalSession.this);
}
}).group(DEFAULT_EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getFlag(BuiltinFlags.CLIENT_CONNECT_TIMEOUT, 30) * 1000);
protected ChannelFactory<? extends Channel> getChannelFactory() {
return new ReflectiveChannelFactory<>(LocalChannelWithRemoteAddress.class);
}

@Override
protected void setOptions(Bootstrap bootstrap) {
if (PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR != null) {
bootstrap.option(ChannelOption.ALLOCATOR, PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR);
}
}

bootstrap.remoteAddress(targetAddress);

CompletableFuture<Void> handleFuture = new CompletableFuture<>();
bootstrap.connect().addListener((futureListener) -> {
if (!futureListener.isSuccess()) {
exceptionCaught(null, futureListener.cause());
}
@Override
protected ChannelHandler getChannelHandler() {
return new MinecraftChannelInitializer<>(channel -> {
PacketProtocol protocol = getPacketProtocol();
protocol.newClientSession(LocalSession.this);

handleFuture.complete(null);
});
return LocalSession.this;
}, true) {
@Override
public void initChannel(@NonNull Channel channel) throws Exception {
((LocalChannelWithRemoteAddress) channel).spoofedRemoteAddress(spoofedRemoteAddress);

if (wait) {
handleFuture.join();
}
}
NettyHelper.initializeHAProxySupport(LocalSession.this, channel);

@Override
public MinecraftCodecHelper getCodecHelper() {
return (MinecraftCodecHelper) this.codecHelper;
super.initChannel(channel);
}
};
}

// TODO duplicate code
private void addHAProxySupport(ChannelPipeline pipeline) {
InetSocketAddress clientAddress = getFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS);
if (clientAddress != null) {
pipeline.addFirst("proxy-protocol-packet-sender", new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(@NonNull ChannelHandlerContext ctx) throws Exception {
HAProxyProxiedProtocol proxiedProtocol = clientAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6;
InetSocketAddress remoteAddress;
if (ctx.channel().remoteAddress() instanceof InetSocketAddress) {
remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
} else {
remoteAddress = new InetSocketAddress(host, port);
}
ctx.channel().writeAndFlush(new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol,
clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(),
clientAddress.getPort(), remoteAddress.getPort()
));
ctx.pipeline().remove(this);
ctx.pipeline().remove("proxy-protocol-encoder");
super.channelActive(ctx);
}
});
pipeline.addFirst("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE);
}
}

/**
* Should only be called when direct ByteBufs should be preferred. At this moment, this should only be called on BungeeCord.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.mcprotocollib.network.ClientSession;
import org.geysermc.mcprotocollib.network.packet.Packet;
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;

@Getter
@RequiredArgsConstructor
public class DownstreamSession {
private final TcpSession session;
private final ClientSession session;

public void sendPacket(@NonNull Packet packet) {
this.session.send(packet);
Expand Down
21 changes: 13 additions & 8 deletions core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,17 @@
import org.geysermc.geyser.util.MinecraftAuthLogger;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.geysermc.mcprotocollib.network.BuiltinFlags;
import org.geysermc.mcprotocollib.network.ClientSession;
import org.geysermc.mcprotocollib.network.NetworkConstants;
import org.geysermc.mcprotocollib.network.Session;
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
import org.geysermc.mcprotocollib.network.event.session.PacketErrorEvent;
import org.geysermc.mcprotocollib.network.event.session.PacketSendingEvent;
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
import org.geysermc.mcprotocollib.network.net.NetClientSession;
import org.geysermc.mcprotocollib.network.net.NetSession;
import org.geysermc.mcprotocollib.network.packet.Packet;
import org.geysermc.mcprotocollib.network.tcp.TcpClientSession;
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import org.geysermc.mcprotocollib.protocol.data.ProtocolState;
Expand Down Expand Up @@ -949,15 +951,17 @@ private void connectDownstream() {
// Start ticking
tickThread = tickEventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);

TcpSession downstream;
ClientSession downstream;
if (geyser.getBootstrap().getSocketAddress() != null) {
// We're going to connect through the JVM and not through TCP
downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(),
geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(),
this.protocol, tickEventLoop);
downstream = new LocalSession(geyser.getBootstrap().getSocketAddress(),
upstream.getAddress().getAddress().getHostAddress(),
this.protocol, tickEventLoop);
downstream.setFlag(MinecraftConstants.CLIENT_HOST, this.remoteServer.address());
downstream.setFlag(MinecraftConstants.CLIENT_PORT, this.remoteServer.port());
this.downstream = new DownstreamSession(downstream);
} else {
downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), "0.0.0.0", 0, this.protocol, null, tickEventLoop);
downstream = new NetClientSession(new InetSocketAddress(this.remoteServer.address(), this.remoteServer.port()), null, this.protocol, null, tickEventLoop);
this.downstream = new DownstreamSession(downstream);

boolean resolveSrv = false;
Expand Down Expand Up @@ -1159,7 +1163,8 @@ public void packetError(PacketErrorEvent event) {
setDaylightCycle(true);
}

downstream.connect(false, loginEvent.transferring());
downstream.setFlag(BuiltinFlags.CLIENT_TRANSFERRING, loginEvent.transferring());
downstream.connect(false);
}

public void disconnect(String reason) {
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ protocol-common = "3.0.0.Beta5-20241203.200249-19"
protocol-codec = "3.0.0.Beta5-20241203.200249-19"
raknet = "1.0.0.CR3-20240416.144209-1"
minecraftauth = "4.1.1"
mcprotocollib = "1.21.2-20241127.160724-5"
mcprotocollib = "5c2249d"
adventure = "4.14.0"
adventure-platform = "4.3.0"
junit = "5.9.2"
Expand Down Expand Up @@ -123,7 +123,7 @@ guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = "minecraftauth" }
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" }
mcprotocollib = { group = "com.github.AlexProgrammerDE", name = "MCProtocolLib", version.ref = "mcprotocollib" }
raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" }
terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" }
velocity-api = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" }
Expand Down
Loading