Fix: Properly check whether the so_reuseport socket option is available (#4579)

* Try to properly check if so_reuseport is available

* io_uring "support"

* comment out io_uring, for now

* Make IO_uring opt-in via `-DGeyser.io_uring=true` flag

* dont include io_uring

* oops - bungee 

editing on mobile is hard

* oops - spigot

* oops - velocity

* properly exclude all io_uring on all platforms except standalone

---------

Co-authored-by: Kas-tle <26531652+Kas-tle@users.noreply.github.com>
This commit is contained in:
chris 2024-04-19 11:50:40 +02:00 committed by GitHub
parent c3ffd65f48
commit 94f664ad8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 56 additions and 19 deletions

View file

@ -22,6 +22,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies { dependencies {
exclude(dependency("com.google.*:.*")) exclude(dependency("com.google.*:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-handler:.*")) exclude(dependency("io.netty:netty-handler:.*"))

View file

@ -41,6 +41,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// We cannot shade Netty, or else native libraries will not load // We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module // Needed because older Spigot builds do not provide the haproxy module
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-classes-epoll:.*")) exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))

View file

@ -13,6 +13,7 @@ platformRelocate("org.yaml")
exclude("com.google.*:*") exclude("com.google.*:*")
// Needed because Velocity provides every dependency except netty-resolver-dns // Needed because Velocity provides every dependency except netty-resolver-dns
exclude("io.netty.incubator:.*")
exclude("io.netty:netty-transport-native-epoll:*") exclude("io.netty:netty-transport-native-epoll:*")
exclude("io.netty:netty-transport-native-unix-common:*") exclude("io.netty:netty-transport-native-unix-common:*")
exclude("io.netty:netty-transport-native-kqueue:*") exclude("io.netty:netty-transport-native-kqueue:*")
@ -57,6 +58,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-transport:.*")) exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*")) exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-codec-haproxy:.*")) exclude(dependency("io.netty:netty-codec-haproxy:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("org.slf4j:.*")) exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*")) exclude(dependency("org.ow2.asm:.*"))
// Exclude all Kyori dependencies except the legacy NBT serializer // Exclude all Kyori dependencies except the legacy NBT serializer

View file

@ -22,6 +22,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies { dependencies {
exclude(dependency("com.google.*:.*")) exclude(dependency("com.google.*:.*"))
exclude(dependency("io.netty:.*")) exclude(dependency("io.netty:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("org.slf4j:.*")) exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*")) exclude(dependency("org.ow2.asm:.*"))
} }

View file

@ -26,6 +26,8 @@ provided("io.netty", "netty-transport-native-epoll")
provided("io.netty", "netty-transport-native-unix-common") provided("io.netty", "netty-transport-native-unix-common")
provided("io.netty", "netty-transport-classes-kqueue") provided("io.netty", "netty-transport-classes-kqueue")
provided("io.netty", "netty-transport-native-kqueue") provided("io.netty", "netty-transport-native-kqueue")
provided("io.netty.incubator", "netty-incubator-transport-native-io_uring")
provided("io.netty.incubator", "netty-incubator-transport-classes-io_uring")
provided("io.netty", "netty-handler") provided("io.netty", "netty-handler")
provided("io.netty", "netty-common") provided("io.netty", "netty-common")
provided("io.netty", "netty-buffer") provided("io.netty", "netty-buffer")

View file

@ -47,6 +47,8 @@ dependencies {
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } } implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } } implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } }
implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } } implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } }
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
// Adventure text serialization // Adventure text serialization
api(libs.bundles.adventure) api(libs.bundles.adventure)
@ -66,11 +68,6 @@ dependencies {
api(libs.events) api(libs.events)
} }
configurations.api {
// This is still experimental - additionally, it could only really benefit standalone
exclude(group = "io.netty.incubator", module = "netty-incubator-transport-native-io_uring")
}
tasks.processResources { tasks.processResources {
// This is solely for backwards compatibility for other programs that used this file before the switch to gradle. // This is solely for backwards compatibility for other programs that used this file before the switch to gradle.
// It used to be generated by the maven Git-Commit-Id-Plugin // It used to be generated by the maven Git-Commit-Id-Plugin

View file

@ -49,6 +49,7 @@ public final class Bootstraps {
String kernelVersion; String kernelVersion;
try { try {
kernelVersion = Native.KERNEL_VERSION; kernelVersion = Native.KERNEL_VERSION;
GeyserImpl.getInstance().getLogger().debug("Kernel version: " + kernelVersion);
} catch (Throwable e) { } catch (Throwable e) {
GeyserImpl.getInstance().getLogger().debug("Could not determine kernel version! " + e.getMessage()); GeyserImpl.getInstance().getLogger().debug("Could not determine kernel version! " + e.getMessage());
kernelVersion = null; kernelVersion = null;
@ -67,10 +68,22 @@ public final class Bootstraps {
} }
@SuppressWarnings({"rawtypes, unchecked"}) @SuppressWarnings({"rawtypes, unchecked"})
public static void setupBootstrap(AbstractBootstrap bootstrap) { public static boolean setupBootstrap(AbstractBootstrap bootstrap) {
boolean success = true;
if (REUSEPORT_AVAILABLE) { if (REUSEPORT_AVAILABLE) {
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true); // Guessing whether so_reuseport is available based on kernel version is cool, but unreliable.
Channel channel = bootstrap.register().channel();
if (channel.config().setOption(UnixChannelOption.SO_REUSEPORT, true)) {
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true);
} else {
// If this occurs, we guessed wrong and reuseport is not available
GeyserImpl.getInstance().getLogger().debug("so_reuseport is not available despite version being " + Native.KERNEL_VERSION);
success = false;
}
// Now yeet that channel
channel.close();
} }
return success;
} }
private static int[] fromString(String input) { private static int[] fromString(String input) {

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.network.netty; package org.geysermc.geyser.network.netty;
import com.github.steveice10.packetlib.helper.TransportHelper;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
@ -39,6 +38,9 @@ import io.netty.channel.kqueue.KQueueEventLoopGroup;
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.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.incubator.channel.uring.IOUring;
import io.netty.incubator.channel.uring.IOUringDatagramChannel;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import lombok.Getter; import lombok.Getter;
import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpirationPolicy;
@ -106,7 +108,7 @@ public final class GeyserServer {
@Getter @Getter
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses; private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses;
private final int listenCount; private int listenCount;
private ChannelFuture[] bootstrapFutures; private ChannelFuture[] bootstrapFutures;
@ -127,8 +129,11 @@ public final class GeyserServer {
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount); this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
this.bootstrap = this.createBootstrap(); this.bootstrap = this.createBootstrap();
// setup SO_REUSEPORT if exists // setup SO_REUSEPORT if exists - or, if the option does not actually exist, reset listen count
Bootstraps.setupBootstrap(this.bootstrap); // otherwise, we try to bind multiple times which wont work if so_reuseport is not valid
if (!Bootstraps.setupBootstrap(this.bootstrap)) {
this.listenCount = 1;
}
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) { if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
this.proxiedAddresses = ExpiringMap.builder() this.proxiedAddresses = ExpiringMap.builder()
@ -415,22 +420,35 @@ public final class GeyserServer {
} }
private static Transport compatibleTransport() { private static Transport compatibleTransport() {
TransportHelper.TransportMethod transportMethod = TransportHelper.determineTransportMethod(); if (isClassAvailable("io.netty.incubator.channel.uring.IOUring")
if (transportMethod == TransportHelper.TransportMethod.EPOLL) { && IOUring.isAvailable()
&& Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) {
return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
}
if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) {
return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new); return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new);
} }
if (transportMethod == TransportHelper.TransportMethod.KQUEUE) { if (isClassAvailable("io.netty.channel.kqueue.KQueue") && KQueue.isAvailable()) {
return new Transport(KQueueDatagramChannel.class, KQueueEventLoopGroup::new); return new Transport(KQueueDatagramChannel.class, KQueueEventLoopGroup::new);
} }
// if (transportMethod == TransportHelper.TransportMethod.IO_URING) {
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
// }
return new Transport(NioDatagramChannel.class, NioEventLoopGroup::new); return new Transport(NioDatagramChannel.class, NioEventLoopGroup::new);
} }
private record Transport(Class<? extends DatagramChannel> datagramChannel, IntFunction<EventLoopGroup> eventLoopGroupFactory) { private record Transport(Class<? extends DatagramChannel> datagramChannel, IntFunction<EventLoopGroup> eventLoopGroupFactory) {
} }
/**
* Used so implementations can opt to remove these dependencies if so desired
*/
private static boolean isClassAvailable(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
} }

View file

@ -6,6 +6,7 @@ events = "1.1-SNAPSHOT"
jackson = "2.17.0" jackson = "2.17.0"
fastutil = "8.5.2" fastutil = "8.5.2"
netty = "4.1.107.Final" netty = "4.1.107.Final"
netty-io-uring = "0.0.25.Final-SNAPSHOT"
guava = "29.0-jre" guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8 gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1" websocket = "1.5.1"
@ -75,6 +76,7 @@ netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", versio
netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" } netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" } netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" }
netty-transport-native-kqueue = { group = "io.netty", name = "netty-transport-native-kqueue", version.ref = "netty" } netty-transport-native-kqueue = { group = "io.netty", name = "netty-transport-native-kqueue", version.ref = "netty" }
netty-transport-native-io_uring = { group = "io.netty.incubator", name = "netty-incubator-transport-native-io_uring", version.ref = "netty-io-uring" }
log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" }
log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" } log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" }