diff --git a/src/dorkbox/network/DnsServer.java b/src/dorkbox/network/DnsServer.java new file mode 100644 index 00000000..094a8d40 --- /dev/null +++ b/src/dorkbox/network/DnsServer.java @@ -0,0 +1,245 @@ +package dorkbox.network; + +import org.slf4j.Logger; + +import dorkbox.network.connection.EndPoint; +import dorkbox.network.connection.EndPointBase; +import dorkbox.network.dns.serverHandlers.DnsServerHandler; +import dorkbox.util.NamedThreadFactory; +import dorkbox.util.OS; +import dorkbox.util.Property; +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.PooledByteBufAllocator; +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; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.oio.OioEventLoopGroup; +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; + +/** + * + */ +public +class DnsServer extends EndPoint { + + /** + * The maximum queue length for incoming connection indications (a request to connect). If a connection indication arrives when the + * queue is full, the connection is refused. + */ + @Property + public static int backlogConnectionCount = 50; + + private final ServerBootstrap tcpBootstrap; + private final Bootstrap udpBootstrap; + + private final int tcpPort; + private final int udpPort; + private final String hostName; + + + public + DnsServer(String host, int port) { + super(DnsServer.class); + + tcpPort = port; + udpPort = port; + + if (host == null) { + hostName = "0.0.0.0"; + } + else { + hostName = host; + } + + String threadName = DnsServer.class.getSimpleName(); + + + final EventLoopGroup boss; + final EventLoopGroup worker; + + 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)); + } + else if (OS.isLinux()) { + // JNI network stack is MUCH faster (but only on linux) + boss = new EpollEventLoopGroup(EndPointBase.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); + worker = new EpollEventLoopGroup(EndPointBase.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); + } + else { + boss = new NioEventLoopGroup(EndPointBase.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-boss", threadGroup)); + worker = new NioEventLoopGroup(EndPointBase.DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup)); + } + + + manageForShutdown(boss); + manageForShutdown(worker); + + + tcpBootstrap = new ServerBootstrap(); + udpBootstrap = new Bootstrap(); + + + if (OS.isAndroid()) { + // android ONLY supports OIO (not NIO) + tcpBootstrap.channel(OioServerSocketChannel.class); + } + else if (OS.isLinux()) { + // JNI network stack is MUCH faster (but only on linux) + tcpBootstrap.channel(EpollServerSocketChannel.class); + } + else { + tcpBootstrap.channel(NioServerSocketChannel.class); + } + + // TODO: If we use netty for an HTTP server, + // Beside the usual ChannelOptions the Native Transport allows to enable TCP_CORK which may come in handy if you implement a HTTP Server. + + 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(EndPointBase.WRITE_BUFF_LOW, EndPointBase.WRITE_BUFF_HIGH)) + .childHandler(new DnsServerHandler()); + + // have to check options.host for null. we don't bind to 0.0.0.0, we bind to "null" to get the "any" address! + if (hostName != null) { + tcpBootstrap.localAddress(hostName, tcpPort); + } + else { + tcpBootstrap.localAddress(tcpPort); + } + + + // android screws up on this!! + tcpBootstrap.option(ChannelOption.TCP_NODELAY, !OS.isAndroid()) + .childOption(ChannelOption.TCP_NODELAY, !OS.isAndroid()); + + + if (OS.isAndroid()) { + // android ONLY supports OIO (not NIO) + udpBootstrap.channel(OioDatagramChannel.class); + } + else if (OS.isLinux()) { + // JNI network stack is MUCH faster (but only on linux) + udpBootstrap.channel(EpollDatagramChannel.class) + .option(EpollChannelOption.SO_REUSEPORT, true); + } + else { + udpBootstrap.channel(NioDatagramChannel.class); + } + + udpBootstrap.group(worker) + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(EndPointBase.WRITE_BUFF_LOW, EndPointBase.WRITE_BUFF_HIGH)) + + // not binding to specific address, since it's driven by TCP, and that can be bound to a specific address + .localAddress(udpPort) // if you bind to a specific interface, Linux will be unable to receive broadcast packets! + .handler(new DnsServerHandler()); + } + + /** + * Binds the server to the configured, underlying protocols. + *
+ * This method will also BLOCK until the stop method is called, and if you want to continue running code after this method invocation, + * bind should be called in a separate, non-daemon thread. + */ + public + void bind() { + bind(true); + } + + /** + * Binds the server to the configured, underlying protocols. + * + * This is a more advanced method, and you should consider callingbind()
instead.
+ *
+ * @param blockUntilTerminate will BLOCK until the server stop method is called, and if you want to continue running code after this method
+ * invocation, bind should be called in a separate, non-daemon thread - or with false as the parameter.
+ */
+ @SuppressWarnings("AutoBoxing")
+ public
+ void bind(boolean blockUntilTerminate) {
+ // make sure we are not trying to connect during a close or stop event.
+ // This will wait until we have finished starting up/shutting down.
+ synchronized (shutdownInProgress) {
+ }
+
+
+ // The bootstraps will be accessed ONE AT A TIME, in this order!
+ ChannelFuture future;
+
+ Logger logger2 = logger;
+
+
+ // TCP
+ // Wait until the connection attempt succeeds or fails.
+ // try {
+ // future = tcpBootstrap.bind();
+ // future.await();
+ // } catch (Exception e) {
+ // // String errorMessage = stopWithErrorMessage(logger2,
+ // // "Could not bind to address " + hostName + " TCP port " + tcpPort +
+ // // " on the server.",
+ // // e);
+ // // throw new IllegalArgumentException(errorMessage);
+ // throw new RuntimeException();
+ // }
+ //
+ // if (!future.isSuccess()) {
+ // // String errorMessage = stopWithErrorMessage(logger2,
+ // // "Could not bind to address " + hostName + " TCP port " + tcpPort +
+ // // " on the server.",
+ // // future.cause());
+ // // throw new IllegalArgumentException(errorMessage);
+ // throw new RuntimeException();
+ // }
+ //
+ // // logger2.info("Listening on address {} at TCP port: {}", hostName, tcpPort);
+ //
+ // manageForShutdown(future);
+
+
+ // UDP
+ // Wait until the connection attempt succeeds or fails.
+ try {
+ future = udpBootstrap.bind();
+ future.await();
+ } catch (Exception e) {
+ String errorMessage = stopWithErrorMessage(logger2,
+ "Could not bind to address " + hostName + " UDP port " + udpPort +
+ " on the server.",
+ e);
+ throw new IllegalArgumentException(errorMessage);
+ }
+
+ if (!future.isSuccess()) {
+ String errorMessage = stopWithErrorMessage(logger2,
+ "Could not bind to address " + hostName + " UDP port " + udpPort +
+ " on the server.",
+ future.cause());
+ throw new IllegalArgumentException(errorMessage);
+ }
+
+ // logger2.info("Listening on address {} at UDP port: {}", hostName, udpPort);
+ manageForShutdown(future);
+
+ // we now BLOCK until the stop method is called.
+ // if we want to continue running code in the server, bind should be called in a separate, non-daemon thread.
+ if (blockUntilTerminate) {
+ waitForShutdown();
+ }
+ }
+}
diff --git a/src/dorkbox/network/dns/handlers/DnsHandler.java b/src/dorkbox/network/dns/handlers/DnsHandler.java
deleted file mode 100644
index 40a60508..00000000
--- a/src/dorkbox/network/dns/handlers/DnsHandler.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package dorkbox.network.dns.handlers;
-
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.socket.DatagramChannel;
-
-public
-class DnsHandler extends ChannelInitializer
+ * Sub-classes may override this method to change behavior.
+ */
+ @Override
+ public
+ void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ if (msg instanceof DatagramPacket) {
+ ByteBuf content = ((DatagramPacket) msg).content();
+
+ if (content.readableBytes() == 0) {
+ // we can't read this message, there's nothing there!
+ System.err.println("NO CONTENT ");
+ ctx.fireChannelRead(msg);
+ return;
+ }
+
+ DnsMessage msg1 = new DnsMessage(content);
+
+ // should get one from a pool!
+
+ Bootstrap dnsBootstrap = new Bootstrap();
+
+ // setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
+ SecurityManager s = System.getSecurityManager();
+ ThreadGroup nettyGroup = new ThreadGroup(s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(),
+ "DnsClient (Netty)");
+
+ EventLoopGroup group;
+ if (PlatformDependent.isAndroid()) {
+ group = new OioEventLoopGroup(0, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
+ dnsBootstrap.channel(OioDatagramChannel.class);
+ }
+ else {
+ group = new NioEventLoopGroup(2, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
+ dnsBootstrap.channel(NioDatagramChannel.class);
+ }
+
+ dnsBootstrap.group(group);
+ dnsBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
+ // dnsBootstrap.handler(new DnsHandler());
+
+
+
+ // sending the question
+ final ChannelFuture future = dnsBootstrap.connect(new InetSocketAddress("8.8.8.8", 53));
+ try {
+ future.await();
+
+ if (future.isSuccess()) {
+ // woo, connected!
+ System.err.println("CONNECTED");
+ // this.dnsServer = dnsServer;
+ }
+ else {
+ System.err.println("CANNOT CONNECT!");
+ // this.dnsServer = null;
+ // Logger logger2 = this.logger;
+ // if (logger2.isDebugEnabled()) {
+ // logger2.error("Could not connect to the DNS server.", this.future.cause());
+ // }
+ // else {
+ // logger2.error("Could not connect to the DNS server.");
+ // }
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ // Logger logger2 = this.logger;
+ // if (logger2.isDebugEnabled()) {
+ // logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort(), e.getCause());
+ // }
+ // else {
+ // logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort());
+ // }
+ }
+
+
+
+
+
+
+
+ //
+ // ClientBootstrap cb = new ClientBootstrap(this.clientChannelFactory);
+ // cb.setOption("broadcast", "false");
+ //
+ // cb.setPipelineFactory(new ChannelPipelineFactory() {
+ // @Override
+ // public
+ // ChannelPipeline getPipeline() throws Exception {
+ // return Channels.pipeline(new ClientHanler(original, e.getChannel(), e.getRemoteAddress()));
+ // }
+ // });
+ //
+ // List