Skip to content

Commit

Permalink
Connection migration
Browse files Browse the repository at this point in the history
Temporarily disable Epoll and bundle all natives in build artifact
  • Loading branch information
ramidzkh committed Aug 18, 2023
1 parent 6e1b181 commit a1d3956
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 13 deletions.
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ dependencies {
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:${loader_version}")

implementation("io.netty.incubator:netty-incubator-codec-classes-quic:0.0.49.Final")
include(implementation("io.netty.incubator:netty-incubator-codec-classes-quic:0.0.49.Final"))

for (def classifier : ["linux-aarch_64", "linux-x86_64", "osx-aarch_64", "osx-x86_64", "windows-x86_64"]) {
include("io.netty.incubator:netty-incubator-codec-native-quic:0.0.49.Final:${classifier}")
}

runtimeOnly("io.netty.incubator:netty-incubator-codec-native-quic:0.0.49.Final:linux-x86_64")

testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")

testImplementation("com.google.code.gson:gson:2.10.1")
}

java {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/me/ramidzkh/qc/QuicConnect.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
public interface QuicConnect {

String APPLICATION_NAME = "minecraft quic connect/0.0.1";

long IDLE_TIMEOUT_SECONDS = 10;
}
6 changes: 3 additions & 3 deletions src/main/java/me/ramidzkh/qc/client/QuicConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class QuicConnection {

public static ChannelFuture connect(InetSocketAddress address, boolean useNativeTransport, Connection connection)
throws ExecutionException, InterruptedException {
useNativeTransport &= Epoll.isAvailable();
useNativeTransport &= false && Epoll.isAvailable();

var context = QuicSslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
Expand All @@ -33,7 +33,7 @@ public static ChannelFuture connect(InetSocketAddress address, boolean useNative

var codec = new QuicClientCodecBuilder()
.sslContext(context)
.maxIdleTimeout(5 /* 30 */, TimeUnit.SECONDS)
.maxIdleTimeout(QuicConnect.IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.initialMaxData(10000000)
// As we don't want to support remote initiated streams just setup the limit for local initiated streams
.initialMaxStreamDataBidirectionalLocal(1000000)
Expand Down Expand Up @@ -63,7 +63,7 @@ public void channelActive(@NotNull ChannelHandlerContext ctx) {
@Override
protected void initChannel(@NotNull Channel channel) {
((ConnectionAccessor) connection).setEncrypted(true);
ChannelPipeline pipeline = channel.pipeline();
var pipeline = channel.pipeline();
Connection.configureSerialization(pipeline, PacketFlow.CLIENTBOUND);
pipeline.addLast("packet_handler", connection);
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/me/ramidzkh/qc/mixin/ConnectionAccessor.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package me.ramidzkh.qc.mixin;

import io.netty.channel.Channel;
import net.minecraft.network.Connection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

import java.net.SocketAddress;

@Mixin(Connection.class)
public interface ConnectionAccessor {

@Accessor
Channel getChannel();

@Accessor
void setAddress(SocketAddress address);

@Accessor
void setEncrypted(boolean encrypted);
}
5 changes: 5 additions & 0 deletions src/main/java/me/ramidzkh/qc/mixin/ConnectionMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.ExecutionException;

@Mixin(Connection.class)
Expand All @@ -39,6 +40,10 @@ private static void onConnect(InetSocketAddress address, boolean useNativeTransp
}
}

@Redirect(method = "channelActive", at = @At(value = "FIELD", target = "Lnet/minecraft/network/Connection;address:Ljava/net/SocketAddress;"))
private void dropSetAddress(Connection instance, SocketAddress value) {
}

@Redirect(method = "disconnect", at = @At(value = "FIELD", target = "Lnet/minecraft/network/Connection;channel:Lio/netty/channel/Channel;"))
private Channel getChannelForDisconnect(Connection self) {
if (channel instanceof QuicStreamChannel quic) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public DedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess

@Inject(method = "initServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConnectionListener;startTcpServerListener(Ljava/net/InetAddress;I)V", shift = At.Shift.AFTER))
private void openQuic(CallbackInfoReturnable<Boolean> callbackInfoReturnable) throws IOException {
int port = ((ExtraServerProperties) getProperties()).getQuicPort();
var port = ((ExtraServerProperties) getProperties()).getQuicPort();

if (port != -1) {
LOGGER.info("Starting Quic bind on {}:{}", getLocalIp().isEmpty() ? "*" : getLocalIp(), port);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package me.ramidzkh.qc.server;

import com.mojang.logging.LogUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.incubator.codec.quic.QuicConnectionEvent;
import io.netty.incubator.codec.quic.QuicServerCodecBuilder;
import io.netty.incubator.codec.quic.QuicSslContextBuilder;
import me.ramidzkh.qc.QuicConnect;
Expand All @@ -19,18 +21,23 @@
import net.minecraft.server.network.ServerHandshakePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.WeakHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class QuicServerConnectionListener {

private static final Logger LOGGER = LogUtils.getLogger();

public static void startQuicServerListener(MinecraftServer server, List<ChannelFuture> channels,
List<Connection> connections, @Nullable InetAddress address, int port) {
var useNativeTransport = Epoll.isAvailable() && server.isEpollEnabled();
var useNativeTransport = false && Epoll.isAvailable() && server.isEpollEnabled();

var config = FabricLoader.getInstance().getConfigDir().resolve("quic-connect");
var keyFile = config.resolve("key.pem");
Expand All @@ -40,28 +47,69 @@ public static void startQuicServerListener(MinecraftServer server, List<ChannelF
.applicationProtocols(QuicConnect.APPLICATION_NAME)
.build();

var inheritAddresses = new WeakHashMap<Channel, SocketAddress>();

var codec = new QuicServerCodecBuilder()
.sslContext(context)
.maxIdleTimeout(5, TimeUnit.SECONDS)
.maxIdleTimeout(QuicConnect.IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
// Configure some limits for the maximal number of streams (and the data) that we want to handle.
.initialMaxData(10000000)
.initialMaxStreamDataBidirectionalLocal(1000000)
.initialMaxStreamDataBidirectionalRemote(1000000)
.initialMaxStreamsBidirectional(100)
.initialMaxStreamsUnidirectional(100)
.tokenHandler(new KeyedTokenHandler(Util.make(new byte[32], ThreadLocalRandom.current()::nextBytes)))
.handler(new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof QuicConnectionEvent event) {
var newAddress = event.newAddress();
inheritAddresses.put(ctx.channel(), newAddress);

for (var connection : connections) {
var currentAddress = connection.getRemoteAddress();
var accessor = (ConnectionAccessor) connection;

if (currentAddress.equals(newAddress)
&& accessor.getChannel().parent() == ctx.channel()) {
accessor.setAddress(newAddress);
LOGGER.info("{}[{}] was migrated to {}", connection, currentAddress, newAddress);
}
}
}

ctx.fireUserEventTriggered(evt);
}

@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) {
inheritAddresses.remove(ctx.channel());
ctx.fireChannelInactive();
}

@Override
public boolean isSharable() {
return true;
}
})
.streamHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(@NotNull Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
var pipeline = channel.pipeline();
Connection.configureSerialization(pipeline, PacketFlow.SERVERBOUND);
int pps = server.getRateLimitPacketsPerSecond();
Connection connection = pps > 0 ? new RateKickingConnection(pps)
var pps = server.getRateLimitPacketsPerSecond();
var connection = pps > 0 ? new RateKickingConnection(pps)
: new Connection(PacketFlow.SERVERBOUND);
((ConnectionAccessor) connection).setEncrypted(true);
connections.add(connection);
pipeline.addLast("packet_handler", connection);
connection.setListener(new ServerHandshakePacketListenerImpl(server, connection));

var address = inheritAddresses.get(channel.parent());

if (address != null) {
((ConnectionAccessor) connection).setAddress(address);
}
}
})
.build();
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "${version}",
"mixins": ["quic-connect.mixins.json"],
"depends": {
"minecraft": ">=1.19.4"
"minecraft": ">=1.20.1"
},
"name": "Quic Connect",
"description": "Connect to Minecraft servers using QUIC",
Expand Down

0 comments on commit a1d3956

Please sign in to comment.