diff --git a/src/dorkbox/network/Broadcast.java b/src/dorkbox/network/Broadcast.java index 1e36a9c1..20a509de 100644 --- a/src/dorkbox/network/Broadcast.java +++ b/src/dorkbox/network/Broadcast.java @@ -15,12 +15,7 @@ */ package dorkbox.network; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.InterfaceAddress; -import java.net.NetworkInterface; -import java.net.SocketException; +import java.net.*; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -31,15 +26,19 @@ import org.slf4j.Logger; import dorkbox.network.pipeline.discovery.ClientDiscoverHostHandler; import dorkbox.network.pipeline.discovery.ClientDiscoverHostInitializer; +import dorkbox.util.OS; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.oio.OioEventLoopGroup; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.oio.OioDatagramChannel; @SuppressWarnings({"unused", "AutoBoxing"}) public final @@ -154,9 +153,21 @@ class Broadcast { logger2.info("Searching for host on {} : {}", address, udpPort); } - NioEventLoopGroup group = new NioEventLoopGroup(); + EventLoopGroup group; + Class channelClass; + + if (OS.isAndroid()) { + // android ONLY supports OIO (not NIO) + group = new OioEventLoopGroup(1); + channelClass = OioDatagramChannel.class; + } else { + group = new NioEventLoopGroup(1); + channelClass = NioDatagramChannel.class; + } + + Bootstrap udpBootstrap = new Bootstrap().group(group) - .channel(NioDatagramChannel.class) + .channel(channelClass) .option(ChannelOption.SO_BROADCAST, true) .handler(new ClientDiscoverHostInitializer()) .localAddress(new InetSocketAddress(address, diff --git a/src/dorkbox/network/Client.java b/src/dorkbox/network/Client.java index 11d4f964..840536cf 100644 --- a/src/dorkbox/network/Client.java +++ b/src/dorkbox/network/Client.java @@ -53,7 +53,6 @@ import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioSocketChannel; -import io.netty.util.internal.PlatformDependent; /** * The client is both SYNC and ASYNC. It starts off SYNC (blocks thread until it's done), then once it's connected to the server, it's @@ -113,22 +112,19 @@ class Client extends EndPointClient implements Connection localChannelName = config.localChannelName; hostName = config.host; - boolean isAndroid = PlatformDependent.isAndroid(); - - final EventLoopGroup boss; - if (isAndroid) { + if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) - boss = new OioEventLoopGroup(0, new NamedThreadFactory(threadName, threadGroup)); + boss = new OioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) boss = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } - else if (OS.isMacOsX()) { + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { // KQueue network stack is MUCH faster (but only on macosx) - boss = new KQueueEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); + boss = new KQueueEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } else { boss = new NioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); @@ -164,16 +160,16 @@ class Client extends EndPointClient implements Connection Bootstrap tcpBootstrap = new Bootstrap(); bootstraps.add(new BootstrapWrapper("TCP", config.host, config.tcpPort, tcpBootstrap)); - if (isAndroid) { + if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) tcpBootstrap.channel(OioSocketChannel.class); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) tcpBootstrap.channel(EpollSocketChannel.class); } - else if (OS.isMacOsX()) { - // JNI network stack is MUCH faster (but only on macosx) + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { + // KQueue network stack is MUCH faster (but only on macosx) tcpBootstrap.channel(KQueueSocketChannel.class); } else { @@ -189,7 +185,7 @@ class Client extends EndPointClient implements Connection serializationManager)); // android screws up on this!! - tcpBootstrap.option(ChannelOption.TCP_NODELAY, !isAndroid) + tcpBootstrap.option(ChannelOption.TCP_NODELAY, !OS.isAndroid()) .option(ChannelOption.SO_KEEPALIVE, true); } @@ -198,20 +194,19 @@ class Client extends EndPointClient implements Connection Bootstrap udpBootstrap = new Bootstrap(); bootstraps.add(new BootstrapWrapper("UDP", config.host, config.udpPort, udpBootstrap)); - if (isAndroid) { + if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) udpBootstrap.channel(OioDatagramChannel.class); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) udpBootstrap.channel(EpollDatagramChannel.class); } - else if (OS.isMacOsX()) { - // JNI network stack is MUCH faster (but only on macosx) + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { + // KQueue network stack is MUCH faster (but only on macosx) udpBootstrap.channel(KQueueDatagramChannel.class); } else { - // windows udpBootstrap.channel(NioDatagramChannel.class); } diff --git a/src/dorkbox/network/DnsClient.java b/src/dorkbox/network/DnsClient.java index 885cae82..2f9de72c 100644 --- a/src/dorkbox/network/DnsClient.java +++ b/src/dorkbox/network/DnsClient.java @@ -50,6 +50,8 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.ReflectiveChannelFactory; import io.netty.channel.epoll.EpollDatagramChannel; import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.kqueue.KQueueDatagramChannel; +import io.netty.channel.kqueue.KQueueEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.oio.OioEventLoopGroup; import io.netty.channel.socket.DatagramChannel; @@ -59,7 +61,6 @@ import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.resolver.HostsFileEntriesResolver; import io.netty.resolver.ResolvedAddressTypes; import io.netty.util.concurrent.Future; -import io.netty.util.internal.PlatformDependent; /** * A DnsClient for resolving DNS name, with reasonably good defaults. @@ -232,16 +233,21 @@ class DnsClient extends Shutdownable { DnsClient(Collection nameServerAddresses) { super(DnsClient.class); - if (PlatformDependent.isAndroid()) { + if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) eventLoopGroup = new OioEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup)); channelType = OioDatagramChannel.class; } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) eventLoopGroup = new EpollEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup)); channelType = EpollDatagramChannel.class; } + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { + // KQueue network stack is MUCH faster (but only on macosx) + eventLoopGroup = new KQueueEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup)); + channelType = KQueueDatagramChannel.class; + } else { eventLoopGroup = new NioEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup)); channelType = NioDatagramChannel.class; diff --git a/src/dorkbox/network/DnsServer.java b/src/dorkbox/network/DnsServer.java index de2cfdd6..9df4c9f9 100644 --- a/src/dorkbox/network/DnsServer.java +++ b/src/dorkbox/network/DnsServer.java @@ -30,7 +30,6 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.WriteBufferWaterMark; -import io.netty.channel.epoll.EpollChannelOption; import io.netty.channel.epoll.EpollDatagramChannel; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; @@ -43,7 +42,6 @@ import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioServerSocketChannel; -import io.netty.channel.unix.UnixChannelOption; /** * from: https://blog.cloudflare.com/how-the-consumer-product-safety-commission-is-inadvertently-behind-the-internets-largest-ddos-attacks/ @@ -117,20 +115,21 @@ class DnsServer extends Shutdownable { if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) - boss = new OioEventLoopGroup(0, new NamedThreadFactory(threadName + "-boss", threadGroup)); - worker = new OioEventLoopGroup(0, new NamedThreadFactory(threadName, threadGroup)); + boss = new OioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); + worker = new OioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) boss = new EpollEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); worker = new EpollEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } - else if (OS.isMacOsX()) { + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { // KQueue network stack is MUCH faster (but only on macosx) boss = new KQueueEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); worker = new KQueueEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } else { + // sometimes the native libraries cannot be loaded, so fall back to NIO boss = new NioEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); worker = new NioEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } @@ -148,11 +147,11 @@ class DnsServer extends Shutdownable { // android ONLY supports OIO (not NIO) tcpBootstrap.channel(OioServerSocketChannel.class); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) tcpBootstrap.channel(EpollServerSocketChannel.class); } - else if (OS.isMacOsX()) { + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { // KQueue network stack is MUCH faster (but only on macosx) tcpBootstrap.channel(KQueueServerSocketChannel.class); } @@ -165,7 +164,6 @@ class DnsServer extends Shutdownable { tcpBootstrap.group(boss, worker) .option(ChannelOption.SO_BACKLOG, backlogConnectionCount) - .option(ChannelOption.SO_REUSEADDR, true) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childOption(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(EndPoint.WRITE_BUFF_LOW, EndPoint.WRITE_BUFF_HIGH)) @@ -191,13 +189,11 @@ class DnsServer extends Shutdownable { } else if (OS.isLinux()) { // JNI network stack is MUCH faster (but only on linux) - udpBootstrap.channel(EpollDatagramChannel.class) - .option(EpollChannelOption.SO_REUSEPORT, true); + udpBootstrap.channel(EpollDatagramChannel.class); } else if (OS.isMacOsX()) { // JNI network stack is MUCH faster (but only on macosx) - udpBootstrap.channel(KQueueDatagramChannel.class) - .option(UnixChannelOption.SO_REUSEPORT, true); + udpBootstrap.channel(KQueueDatagramChannel.class); } else { udpBootstrap.channel(NioDatagramChannel.class); diff --git a/src/dorkbox/network/NativeLibrary.java b/src/dorkbox/network/NativeLibrary.java new file mode 100644 index 00000000..505c5535 --- /dev/null +++ b/src/dorkbox/network/NativeLibrary.java @@ -0,0 +1,144 @@ +/* + * Copyright 2018 dorkbox, llc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.network; + +import java.io.File; +import java.lang.reflect.Field; + +import dorkbox.util.NativeLoader; +import dorkbox.util.OS; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.kqueue.KQueue; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SystemPropertyUtil; + +/** + * + */ +public +class NativeLibrary { + + /** + * Tries to extract the native transport libraries for Linux/MacOsX into a "semi-permanent" location. If this is unsuccessful for any + * reason, netty will fall back to it's own logic. + */ + static { + if (OS.isLinux() || OS.isMacOsX()) { + // try to load the native libraries for Linux/MacOsX... + String originalLibraryPath = SystemPropertyUtil.get("java.library.path"); + File outputDirectory; + + String workdir = SystemPropertyUtil.get("io.netty.native.workdir"); + if (workdir != null) { + File f = new File(workdir); + if (!f.isDirectory()) { + f.mkdirs(); + } + + try { + f = f.getAbsoluteFile(); + } catch (Exception ignored) { + // Good to have an absolute path, but it's OK. + } + + outputDirectory = f; + // logger.debug("-Dio.netty.native.workdir: " + WORKDIR); + } + else { + outputDirectory = PlatformDependent.tmpdir(); + // logger.debug("-Dio.netty.native.workdir: " + WORKDIR + " (io.netty.tmpdir)"); + } + + try { + System.setProperty("java.library.path", originalLibraryPath + File.pathSeparator + outputDirectory.getAbsolutePath()); + + // reset the classloader library path. + Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); + fieldSysPath.setAccessible(true); + fieldSysPath.set(null, null); + } catch (Exception ignored) { + } + + + String staticLibName; + if (OS.isLinux()) { + staticLibName = "netty_transport_native_epoll"; + } + else { + staticLibName = "netty_transport_native_kqueue"; + } + + staticLibName = "lib" + staticLibName + '_' + PlatformDependent.normalizedArch(); + + + + String jarLibName = "META-INF/native/" + staticLibName; + if (OS.isLinux()) { + jarLibName += ".so"; + } + else { + jarLibName += ".jnilib"; + } + + + try { + NativeLoader.extractLibrary(jarLibName, outputDirectory.getAbsolutePath(), staticLibName, null); + + // we have to try to load the native library HERE, while the java.library.path has it + if (OS.isLinux()) { + //noinspection ResultOfMethodCallIgnored + Epoll.isAvailable(); + } + else if (OS.isMacOsX()) { + //noinspection ResultOfMethodCallIgnored + KQueue.isAvailable(); + } + } catch (Exception ignored) { + + } finally { + System.setProperty("java.library.path", originalLibraryPath); + + try { + // reset the classloader library path. + Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); + fieldSysPath.setAccessible(true); + fieldSysPath.set(null, null); + } catch (Exception ignored) { + } + } + } + + // either not Linux/MacOsX, or loading the library failed. + } + + + /** + * @return true if the (possibly) required native libraries have been loaded + */ + public static + boolean isAvailable() { + if (OS.isLinux()) { + return Epoll.isAvailable(); + } + else if (OS.isMacOsX()) { + return KQueue.isAvailable(); + } + + // not Linux/MacOsX + return true; + } + +} diff --git a/src/dorkbox/network/Server.java b/src/dorkbox/network/Server.java index 1337a334..a543610c 100644 --- a/src/dorkbox/network/Server.java +++ b/src/dorkbox/network/Server.java @@ -46,7 +46,6 @@ import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioServerSocketChannel; -import io.netty.channel.unix.UnixChannelOption; /** * The server can only be accessed in an ASYNC manner. This means that the server can only be used in RESPONSE to events. If you access the @@ -151,15 +150,15 @@ class Server extends EndPointServer { if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) - boss = new OioEventLoopGroup(0, new NamedThreadFactory(threadName + "-boss", threadGroup)); - worker = new OioEventLoopGroup(0, new NamedThreadFactory(threadName, threadGroup)); + boss = new OioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); + worker = new OioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) boss = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); worker = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); } - else if (OS.isMacOsX()) { + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { // KQueue network stack is MUCH faster (but only on macosx) boss = new KQueueEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); worker = new KQueueEventLoopGroup(EndPoint.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); @@ -200,12 +199,12 @@ class Server extends EndPointServer { // android ONLY supports OIO (not NIO) tcpBootstrap.channel(OioServerSocketChannel.class); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) tcpBootstrap.channel(EpollServerSocketChannel.class); } - else if (OS.isMacOsX()) { - // JNI network stack is MUCH faster (but only on macosx) + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { + // KQueue network stack is MUCH faster (but only on macosx) tcpBootstrap.channel(KQueueServerSocketChannel.class); } else { @@ -243,18 +242,15 @@ class Server extends EndPointServer { if (udpBootstrap != null) { if (OS.isAndroid()) { // android ONLY supports OIO (not NIO) - udpBootstrap.channel(OioDatagramChannel.class) - .option(UnixChannelOption.SO_REUSEPORT, true); + udpBootstrap.channel(OioDatagramChannel.class); } - else if (OS.isLinux()) { + else if (OS.isLinux() && NativeLibrary.isAvailable()) { // JNI network stack is MUCH faster (but only on linux) - udpBootstrap.channel(EpollDatagramChannel.class) - .option(UnixChannelOption.SO_REUSEPORT, true); + udpBootstrap.channel(EpollDatagramChannel.class); } - else if (OS.isMacOsX()) { - // JNI network stack is MUCH faster (but only on macosx) - udpBootstrap.channel(KQueueDatagramChannel.class) - .option(UnixChannelOption.SO_REUSEPORT, true); + else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { + // KQueue network stack is MUCH faster (but only on macosx) + udpBootstrap.channel(KQueueDatagramChannel.class); } else { // windows