From 28c6e7612119f8929740c40d53a8d810978c1e3b Mon Sep 17 00:00:00 2001 From: ishland Date: Sun, 10 Jul 2022 15:55:42 +0800 Subject: [PATCH] Velocity fixes Workarounds #8 --- .../bungee/init/BungeeRaknetifyServer.java | 25 ++-- .../common/util/NetworkInterfaceListener.java | 32 ++--- .../mixin/server/MixinServerNetworkIo.java | 48 ++++--- .../RaknetifyVelocityLaunchWrapper.java | 48 +++++++ .../velocity/RaknetifyVelocityPlugin.java | 126 +++++++++++++----- .../init/VelocityRaknetifyServer.java | 72 +++++++++- 6 files changed, 257 insertions(+), 94 deletions(-) create mode 100644 velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityLaunchWrapper.java diff --git a/bungee/src/main/java/com/ishland/raknetify/bungee/init/BungeeRaknetifyServer.java b/bungee/src/main/java/com/ishland/raknetify/bungee/init/BungeeRaknetifyServer.java index fb8217a..7c81aab 100644 --- a/bungee/src/main/java/com/ishland/raknetify/bungee/init/BungeeRaknetifyServer.java +++ b/bungee/src/main/java/com/ishland/raknetify/bungee/init/BungeeRaknetifyServer.java @@ -14,7 +14,6 @@ import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.ReflectiveChannelFactory; import io.netty.channel.socket.DatagramChannel; -import io.netty.channel.unix.DomainSocketAddress; import io.netty.util.AttributeKey; import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; @@ -66,10 +65,11 @@ public class BungeeRaknetifyServer { private static final Reference2ReferenceOpenHashMap> channels = new Reference2ReferenceOpenHashMap<>(); private static final ReferenceOpenHashSet nonWildcardChannels = new ReferenceOpenHashSet<>(); - private static boolean active = false; + private static volatile boolean active = false; + private static volatile int activeIndex = 0; private static boolean injected = false; - private static Consumer listener = null; + private static volatile Consumer listener = null; public static void inject() { if (active) return; @@ -96,11 +96,14 @@ public static void inject() { } } + int currentActiveIndex = ++activeIndex; listener = event -> { if (!active) { NetworkInterfaceListener.removeListener(listener); } + if (currentActiveIndex != activeIndex) return; // we can't remove ourselves now, is plugin reloaded? + if (event.added()) { for (Channel channel : channels.keySet()) { injectChannel(instance, channel, false); @@ -109,20 +112,12 @@ public static void inject() { for (ReferenceOpenHashSet futures : channels.values()) { for (ObjectIterator iterator = futures.iterator(); iterator.hasNext(); ) { ChannelFuture future = iterator.next(); - final Iterator iterator1 = event.networkInterface().getInetAddresses().asIterator(); - - __loop0: - while (iterator1.hasNext()) { - final InetAddress address = iterator1.next(); - if (((InetSocketAddress) future.channel().localAddress()).getAddress().equals(address)) { - RaknetifyBungeePlugin.LOGGER.info("Closing Raknetify server %s".formatted(future.channel().localAddress())); - future.channel().close(); - iterator.remove(); - break __loop0; - } + if (((InetSocketAddress) future.channel().localAddress()).getAddress().equals(event.address())) { + RaknetifyBungeePlugin.LOGGER.info("Closing Raknetify server %s".formatted(future.channel().localAddress())); + future.channel().close(); + iterator.remove(); } } - } } }; diff --git a/common/src/main/java/com/ishland/raknetify/common/util/NetworkInterfaceListener.java b/common/src/main/java/com/ishland/raknetify/common/util/NetworkInterfaceListener.java index b3d6e49..adcc622 100644 --- a/common/src/main/java/com/ishland/raknetify/common/util/NetworkInterfaceListener.java +++ b/common/src/main/java/com/ishland/raknetify/common/util/NetworkInterfaceListener.java @@ -1,15 +1,14 @@ package com.ishland.raknetify.common.util; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import it.unimi.dsi.fastutil.objects.ReferenceSets; +import java.net.InetAddress; import java.net.NetworkInterface; import java.util.List; -import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -39,28 +38,29 @@ public class NetworkInterfaceListener { public static void init() { } - private static final Object2ObjectOpenHashMap knownInterfaces = new Object2ObjectOpenHashMap<>(); - private static final ReferenceSet> listeners = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); + private static final ObjectOpenHashSet knownAddresses = new ObjectOpenHashSet<>(); + private static final ReferenceSet> listeners = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); private static void pollChanges() { try { final List networkInterfaces = NetworkInterface.networkInterfaces().toList(); - ObjectOpenHashSet currentInterfaces = new ObjectOpenHashSet<>(); + ObjectOpenHashSet currentAddresses = new ObjectOpenHashSet<>(); for (NetworkInterface networkInterface : networkInterfaces) { if (networkInterface.isUp()) { - currentInterfaces.add(networkInterface.getName()); - if (knownInterfaces.put(networkInterface.getName(), networkInterface) == null) { - listeners.forEach(consumer -> consumer.accept(new InterfaceChangeEvent(true, networkInterface))); + for (InetAddress address : networkInterface.inetAddresses().toList()) { + currentAddresses.add(address); + if (knownAddresses.add(address)) { + listeners.forEach(consumer -> consumer.accept(new InterfaceAddressChangeEvent(true, address))); + } } } } - final ObjectIterator> iterator = knownInterfaces.entrySet().iterator(); + final ObjectIterator iterator = knownAddresses.iterator(); while (iterator.hasNext()) { - final Map.Entry entry = iterator.next(); - if (!currentInterfaces.contains(entry.getKey())) { - final NetworkInterface networkInterface = entry.getValue(); + final InetAddress address = iterator.next(); + if (!currentAddresses.contains(address)) { iterator.remove(); - listeners.forEach(consumer -> consumer.accept(new InterfaceChangeEvent(false, networkInterface))); + listeners.forEach(consumer -> consumer.accept(new InterfaceAddressChangeEvent(false, address))); } } } catch (Throwable t) { @@ -68,15 +68,15 @@ private static void pollChanges() { } } - public static void addListener(Consumer consumer) { + public static void addListener(Consumer consumer) { listeners.add(consumer); } - public static void removeListener(Consumer consumer) { + public static void removeListener(Consumer consumer) { listeners.remove(consumer); } - public record InterfaceChangeEvent(boolean added, NetworkInterface networkInterface) { + public record InterfaceAddressChangeEvent(boolean added, InetAddress address) { } } diff --git a/fabric/src/main/java/com/ishland/raknetify/fabric/mixin/server/MixinServerNetworkIo.java b/fabric/src/main/java/com/ishland/raknetify/fabric/mixin/server/MixinServerNetworkIo.java index 9b411af..df1a8e1 100644 --- a/fabric/src/main/java/com/ishland/raknetify/fabric/mixin/server/MixinServerNetworkIo.java +++ b/fabric/src/main/java/com/ishland/raknetify/fabric/mixin/server/MixinServerNetworkIo.java @@ -52,7 +52,7 @@ public abstract class MixinServerNetworkIo { @Shadow @Final private List channels; @Unique - private Consumer raknetify$eventListener = null; + private Consumer raknetify$eventListener = null; @Inject(method = "bind", at = @At("HEAD")) private void bindUdp(InetAddress address, int port, CallbackInfo ci) throws IOException { @@ -65,7 +65,7 @@ private void bindUdp(InetAddress address, int port, CallbackInfo ci) throws IOEx final Iterator iterator = networkInterface.getInetAddresses().asIterator(); while (iterator.hasNext()) { final InetAddress inetAddress = iterator.next(); - System.out.println("Starting raknetify server on interface %s address %s".formatted(networkInterface.getName(), inetAddress)); + System.out.println("Starting raknetify server on %s".formatted(inetAddress)); bind(inetAddress, hasPortOverride ? raknetify$portOverride : port); } } @@ -78,29 +78,26 @@ private void bindUdp(InetAddress address, int port, CallbackInfo ci) throws IOEx } try { ThreadLocalUtil.setInitializingRaknet(true); - final Iterator iterator = event.networkInterface().getInetAddresses().asIterator(); - while (iterator.hasNext()) { - final InetAddress inetAddress = iterator.next(); - if (event.added()) { - System.out.println("Starting raknetify server on interface %s address %s".formatted(event.networkInterface().getName(), inetAddress)); - try { - bind(inetAddress, hasPortOverride ? raknetify$portOverride : port); - } catch (IOException t) { - System.out.println("**** FAILED TO BIND TO PORT! %s".formatted(t.getMessage())); - } catch (Throwable t) { - t.printStackTrace(); - } - } else { - synchronized (this.channels) { - for (Iterator iter = this.channels.iterator(); iter.hasNext(); ) { - ChannelFuture channel = iter.next(); - final SocketAddress socketAddress = channel.channel().localAddress(); - if (socketAddress instanceof InetSocketAddress channelAddress) { - if (inetAddress.equals(channelAddress.getAddress())) { - System.out.println("Stopping raknetify server on interface %s address %s".formatted(event.networkInterface().getName(), inetAddress)); - channel.channel().close(); - iter.remove(); - } + final InetAddress inetAddress = event.address(); + if (event.added()) { + System.out.println("Starting raknetify server on %s".formatted(inetAddress)); + try { + bind(inetAddress, hasPortOverride ? raknetify$portOverride : port); + } catch (IOException t) { + System.out.println("**** FAILED TO BIND TO PORT! %s".formatted(t.getMessage())); + } catch (Throwable t) { + t.printStackTrace(); + } + } else { + synchronized (this.channels) { + for (Iterator iter = this.channels.iterator(); iter.hasNext(); ) { + ChannelFuture channel = iter.next(); + final SocketAddress socketAddress = channel.channel().localAddress(); + if (socketAddress instanceof InetSocketAddress channelAddress) { + if (inetAddress.equals(channelAddress.getAddress())) { + System.out.println("Stopping raknetify server on %s".formatted(inetAddress)); + channel.channel().close(); + iter.remove(); } } } @@ -115,6 +112,7 @@ private void bindUdp(InetAddress address, int port, CallbackInfo ci) throws IOEx NetworkInterfaceListener.addListener(event -> this.server.submit(() -> raknetify$eventListener.accept(event))); } } else { + System.out.println("Starting raknetify server on %s".formatted(address)); bind(address, hasPortOverride ? raknetify$portOverride : port); } } finally { diff --git a/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityLaunchWrapper.java b/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityLaunchWrapper.java new file mode 100644 index 0000000..e4be19e --- /dev/null +++ b/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityLaunchWrapper.java @@ -0,0 +1,48 @@ +package com.ishland.raknetify.velocity; + +import com.ishland.raknetify.common.data.ProtocolMultiChannelMappings; +import com.ishland.raknetify.velocity.connection.RakNetVelocityConnectionUtil; +import com.ishland.raknetify.velocity.init.VelocityPacketRegistryInjector; +import com.ishland.raknetify.velocity.init.VelocityRaknetifyServer; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.player.ServerPostConnectEvent; +import com.velocitypowered.api.event.proxy.ListenerBoundEvent; +import com.velocitypowered.api.event.proxy.ListenerCloseEvent; + +import static com.ishland.raknetify.velocity.RaknetifyVelocityPlugin.INSTANCE; +import static com.ishland.raknetify.velocity.RaknetifyVelocityPlugin.LOGGER; +import static com.ishland.raknetify.velocity.RaknetifyVelocityPlugin.PROXY; + +public class RaknetifyVelocityLaunchWrapper { + + public static void launch() { + if (!isCompatible()) { + Runnable runnable = () -> { + LOGGER.error("This version of Raknetify is NOT compatible with your version of Velocity"); + LOGGER.error("Please update your Velocity at https://papermc.io/downloads#Velocity"); + }; + runnable.run(); + PROXY.getEventManager().register(INSTANCE, ListenerBoundEvent.class, PostOrder.LAST, ignored -> runnable.run()); + return; + } + + ProtocolMultiChannelMappings.init(); + VelocityPacketRegistryInjector.inject(); + + PROXY.getEventManager().register(INSTANCE, LoginEvent.class, PostOrder.LAST, RakNetVelocityConnectionUtil::onPlayerLogin); + PROXY.getEventManager().register(INSTANCE, ListenerBoundEvent.class, PostOrder.LAST, VelocityRaknetifyServer::start); + PROXY.getEventManager().register(INSTANCE, ListenerCloseEvent.class, PostOrder.LAST, VelocityRaknetifyServer::stop); + PROXY.getEventManager().register(INSTANCE, ServerPostConnectEvent.class, PostOrder.LAST, RakNetVelocityConnectionUtil::onServerSwitch); + } + + private static boolean isCompatible() { + try { + Class.forName("com.velocitypowered.proxy.crypto.EncryptionUtils"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityPlugin.java b/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityPlugin.java index 1a4b2f4..2a3c42e 100644 --- a/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityPlugin.java +++ b/velocity/src/main/java/com/ishland/raknetify/velocity/RaknetifyVelocityPlugin.java @@ -1,30 +1,54 @@ package com.ishland.raknetify.velocity; +import com.google.common.base.Preconditions; import com.google.inject.Inject; -import com.ishland.raknetify.common.data.ProtocolMultiChannelMappings; -import com.ishland.raknetify.velocity.connection.RakNetVelocityConnectionUtil; -import com.ishland.raknetify.velocity.init.VelocityPacketRegistryInjector; -import com.ishland.raknetify.velocity.init.VelocityRaknetifyServer; -import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.LoginEvent; -import com.velocitypowered.api.event.player.ServerPostConnectEvent; -import com.velocitypowered.api.event.proxy.ListenerBoundEvent; -import com.velocitypowered.api.event.proxy.ListenerCloseEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import org.slf4j.Logger; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandlerFactory; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.regex.Pattern; + @Plugin( id = "raknetify" ) public class RaknetifyVelocityPlugin { + private static final ArrayList excludeRegex = new ArrayList<>(); + + static { + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.*Int2ObjectLinked*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.*Int2ObjectSorted*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.*Int2ObjectAVL*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.*Int2ObjectRB*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.*Int2ObjectArray*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.IntList*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.AbstractIntList*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.IntSorted*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.AbstractIntSorted*")); + excludeRegex.add(Pattern.compile("it.unimi.dsi.fastutil.ints.IntRB*")); + } + + private static boolean isExcluded(String name) { + for (Pattern regex : excludeRegex) { + if (regex.matcher(name).find()) return true; + } + return false; + } + public static ProxyServer PROXY; public static Logger LOGGER; public static RaknetifyVelocityPlugin INSTANCE; + public static URLClassLoader WRAPPER; + @Inject private ProxyServer proxy; @Inject @@ -36,32 +60,72 @@ public void onProxyInit(ProxyInitializeEvent e) { PROXY = this.proxy; LOGGER = this.logger; - if (!isCompatible()) { - Runnable runnable = () -> { - LOGGER.error("This version of Raknetify is NOT compatible with your version of Velocity"); - LOGGER.error("Please update your Velocity at https://papermc.io/downloads#Velocity"); - }; - runnable.run(); - PROXY.getEventManager().register(this, ListenerBoundEvent.class, PostOrder.LAST, ignored -> runnable.run()); - return; + final CodeSource codeSource = RaknetifyVelocityPlugin.class.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + try { + LOGGER.info("Bootstrapping raknetify in wrapped environment"); + final URLClassLoader urlClassLoader = new RaknetifyURLClassLoader("raknetify wrapper", new URL[]{codeSource.getLocation()}, RaknetifyVelocityPlugin.class.getClassLoader()); + final Class launchWrapper = urlClassLoader.loadClass("com.ishland.raknetify.velocity.RaknetifyVelocityLaunchWrapper"); + Preconditions.checkState(launchWrapper.getClassLoader() == urlClassLoader, "Not launched in wrapper"); + WRAPPER = urlClassLoader; + launchWrapper.getMethod("launch").invoke(null); + return; + } catch (Throwable t) { + LOGGER.error("Error bootstrapping raknetify inside wrapped environment, running in normal environment instead", t); + } } - ProtocolMultiChannelMappings.init(); - VelocityPacketRegistryInjector.inject(); - - PROXY.getEventManager().register(this, LoginEvent.class, PostOrder.LAST, RakNetVelocityConnectionUtil::onPlayerLogin); - PROXY.getEventManager().register(this, ListenerBoundEvent.class, PostOrder.LAST, VelocityRaknetifyServer::start); - PROXY.getEventManager().register(this, ListenerCloseEvent.class, PostOrder.LAST, VelocityRaknetifyServer::stop); - PROXY.getEventManager().register(this, ServerPostConnectEvent.class, PostOrder.LAST, RakNetVelocityConnectionUtil::onServerSwitch); - } - - private static boolean isCompatible() { try { - Class.forName("com.velocitypowered.proxy.crypto.EncryptionUtils"); - return true; - } catch (ClassNotFoundException e) { - return false; + Class.forName("com.ishland.raknetify.velocity.RaknetifyVelocityLaunchWrapper").getMethod("launch").invoke(null); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException ex) { + throw new RuntimeException(ex); } } + private static class RaknetifyURLClassLoader extends URLClassLoader { + + public RaknetifyURLClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + public RaknetifyURLClassLoader(URL[] urls) { + super(urls); + } + + public RaknetifyURLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { + super(urls, parent, factory); + } + + public RaknetifyURLClassLoader(String name, URL[] urls, ClassLoader parent) { + super(name, urls, parent); + } + + public RaknetifyURLClassLoader(String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { + super(name, urls, parent, factory); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (!(name.startsWith("com.ishland.raknetify") && !name.equals("com.ishland.raknetify.velocity.RaknetifyVelocityPlugin")) + && !name.startsWith("network.ycc.raknet") + && !isExcluded(name)) { + try { + return Class.forName(name, true, this.getParent()); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + // ignored, try our own loader + } + } + + synchronized (getClassLoadingLock(name)) { + try { + final Class clazz = this.findClass(name); + if (resolve) this.resolveClass(clazz); + return clazz; + } catch (ClassNotFoundException e1) { + // then fail here, there's nothing more we can do + throw new ClassNotFoundException(name, e1); + } + } + } + } } diff --git a/velocity/src/main/java/com/ishland/raknetify/velocity/init/VelocityRaknetifyServer.java b/velocity/src/main/java/com/ishland/raknetify/velocity/init/VelocityRaknetifyServer.java index 3cc88e4..ab03c1a 100644 --- a/velocity/src/main/java/com/ishland/raknetify/velocity/init/VelocityRaknetifyServer.java +++ b/velocity/src/main/java/com/ishland/raknetify/velocity/init/VelocityRaknetifyServer.java @@ -2,6 +2,7 @@ import com.google.common.base.Preconditions; import com.ishland.raknetify.common.Constants; +import com.ishland.raknetify.common.util.NetworkInterfaceListener; import com.ishland.raknetify.velocity.RaknetifyVelocityPlugin; import com.ishland.raknetify.velocity.connection.RakNetVelocityConnectionUtil; import com.velocitypowered.api.event.proxy.ListenerBoundEvent; @@ -18,9 +19,14 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.socket.DatagramChannel; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import network.ycc.raknet.server.channel.RakNetServerChannel; import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.util.function.Consumer; import static com.ishland.raknetify.common.util.ReflectionUtil.accessible; @@ -36,12 +42,55 @@ public class VelocityRaknetifyServer { } } - private static ChannelFuture channel = null; + private static final ReferenceOpenHashSet channels = new ReferenceOpenHashSet<>(); + private static volatile InetSocketAddress currentServerAddress = null; + private static volatile boolean active = false; + + static { + NetworkInterfaceListener.addListener(event -> { + if (!active) return; + final InetSocketAddress address = currentServerAddress; + if (address != null && address.getAddress().isAnyLocalAddress()) { + if (event.added()) { + startServer(new InetSocketAddress(event.address(), address.getPort())); + } else { + for (ChannelFuture future : channels) { + if (((InetSocketAddress) future.channel().localAddress()).getAddress().equals(event.address())) { + closeServer(future); + } + } + } + } + }); + } public static void start(ListenerBoundEvent evt) { if (evt.getListenerType() != ListenerType.MINECRAFT) return; Preconditions.checkArgument(RaknetifyVelocityPlugin.PROXY instanceof VelocityServer); - Preconditions.checkState(channel == null, "Raknetify Server is already running"); + Preconditions.checkState(channels.isEmpty(), "Raknetify Server is already running"); + final InetSocketAddress address = evt.getAddress(); + currentServerAddress = address; + + if (address.getAddress().isAnyLocalAddress()) { + try { + for (NetworkInterface networkInterface : NetworkInterface.networkInterfaces().toList()) { + for (InetAddress inetAddress : networkInterface.inetAddresses().toList()) { + try { + startServer(new InetSocketAddress(inetAddress, address.getPort())); + } catch (Throwable t) { + RaknetifyVelocityPlugin.LOGGER.error("Failed to start raknetify server", t); + } + } + } + } catch (Throwable t) { + throw new RuntimeException("Failed to start raknetify server", t); + } + } else { + startServer(address); + } + } + + private static void startServer(InetSocketAddress address) { try { final ConnectionManager cm = (ConnectionManager) accessible(VelocityServer.class.getDeclaredField("cm")).get(RaknetifyVelocityPlugin.PROXY); final Object transportType = accessible(ConnectionManager.class.getDeclaredField("transportType")).get(cm); @@ -50,7 +99,7 @@ public static void start(ListenerBoundEvent evt) { final EventLoopGroup bossGroup = (EventLoopGroup) accessible(ConnectionManager.class.getDeclaredField("bossGroup")).get(cm); final EventLoopGroup workerGroup = (EventLoopGroup) accessible(ConnectionManager.class.getDeclaredField("workerGroup")).get(cm); - channel = new ServerBootstrap() + ChannelFuture future = new ServerBootstrap() .channelFactory(() -> new RakNetServerChannel(() -> { final DatagramChannel channel = datagramChannelFactory.newChannel(); channel.config().setOption(ChannelOption.IP_TOS, 0x18); @@ -66,11 +115,12 @@ protected void initChannel(Channel channel) throws Exception { RakNetVelocityConnectionUtil.postInitChannel(channel, false); } }) - .localAddress(evt.getAddress()) + .localAddress(address) .bind() .syncUninterruptibly(); - RaknetifyVelocityPlugin.LOGGER.info("Raknetify server started on {}", channel.channel().localAddress()); + RaknetifyVelocityPlugin.LOGGER.info("Raknetify server started on {}", future.channel().localAddress()); + channels.add(future); } catch (Throwable t) { throw new RuntimeException("Failed to start Raknetify server", t); } @@ -79,14 +129,22 @@ protected void initChannel(Channel channel) throws Exception { public static void stop(ListenerCloseEvent evt) { if (evt.getListenerType() != ListenerType.MINECRAFT) return; Preconditions.checkArgument(RaknetifyVelocityPlugin.PROXY instanceof VelocityServer); - Preconditions.checkState(channel != null, "Raknetify Server is not running"); + Preconditions.checkState(!channels.isEmpty(), "Raknetify Server is not running"); + for (ChannelFuture channel : channels) { + closeServer(channel); + } + + + channels.clear(); + } + + private static void closeServer(ChannelFuture channel) { RaknetifyVelocityPlugin.LOGGER.info("Closing Raknetify server {}", channel.channel().localAddress()); try { channel.channel().close().sync(); } catch (InterruptedException e) { RaknetifyVelocityPlugin.LOGGER.error("Interrupted whilst closing raknetify server"); } - channel = null; } }