All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vertx.java.core.dns.impl.DefaultDnsClient Maven / Gradle / Ivy

Go to download

vert.x - Effortless asynchronous application development for the modern web and enterprise

There is a newer version: 4.5.9
Show newest version
/*
 * Copyright (c) 2011-2013 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package org.vertx.java.core.dns.impl;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import org.vertx.java.core.AsyncResult;
import org.vertx.java.core.Handler;
import org.vertx.java.core.dns.*;
import org.vertx.java.core.dns.DnsResponseCode;
import org.vertx.java.core.dns.impl.netty.*;
import org.vertx.java.core.dns.impl.netty.decoder.RecordDecoderFactory;
import org.vertx.java.core.dns.impl.netty.decoder.record.MailExchangerRecord;
import org.vertx.java.core.dns.impl.netty.decoder.record.ServiceRecord;
import org.vertx.java.core.impl.DefaultContext;
import org.vertx.java.core.impl.DefaultFutureResult;
import org.vertx.java.core.impl.VertxInternal;
import org.vertx.java.core.net.impl.PartialPooledByteBufAllocator;

import java.net.*;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author Norman Maurer
 */
public final class DefaultDnsClient implements DnsClient {

  private final Bootstrap bootstrap;
  private final List dnsServers;
  private static final char[] HEX_TABLE = "0123456789abcdef".toCharArray();
  private final DefaultContext actualCtx;
  private final VertxInternal vertx;

  public DefaultDnsClient(VertxInternal vertx, InetSocketAddress... dnsServers) {
    if (dnsServers == null || dnsServers.length == 0) {
      throw new IllegalArgumentException("Need at least one default DNS Server");
    }

    // use LinkedList as we will traverse it all the time
    this.dnsServers = new LinkedList<>(Arrays.asList(dnsServers));
    actualCtx = vertx.getOrCreateContext();
    this.vertx = vertx;
    bootstrap = new Bootstrap();
    bootstrap.group(actualCtx.getEventLoop());
    bootstrap.channel(NioDatagramChannel.class);
    bootstrap.option(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE);
    bootstrap.handler(new ChannelInitializer() {
      @Override
      protected void initChannel(DatagramChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new DnsQueryEncoder());
        pipeline.addLast(new DnsResponseDecoder());
      }
    });
  }

  @Override
  public DnsClient lookup4(String name, final Handler> handler) {
    lookup(name, new HandlerAdapter(handler), DnsEntry.TYPE_A);
    return this;
  }

  @Override
  public DnsClient lookup6(String name, final Handler> handler) {
    lookup(name, new HandlerAdapter(handler), DnsEntry.TYPE_AAAA);
    return this;
  }

  @Override
  public DnsClient lookup(String name, final Handler> handler) {
    lookup(name, new HandlerAdapter(handler), DnsEntry.TYPE_A, DnsEntry.TYPE_AAAA);
    return this;
  }

  @Override
  public DnsClient resolveA(String name, final Handler>> handler) {
    lookup(name, handler, DnsEntry.TYPE_A);
    return this;
  }

  @Override
  public DnsClient resolveCNAME(String name, Handler >> handler) {
    lookup(name, handler, DnsEntry.TYPE_CNAME);
    return this;
  }

  @Override
  public DnsClient resolveMX(String name, final Handler>> handler) {
    lookup(name, new ConvertingHandler(handler, DefaultMxRecordComparator.INSTANCE) {
      @Override
      protected MxRecord convert(MailExchangerRecord entry) {
        return new DefaultMxRecord(entry);
      }
    }, DnsEntry.TYPE_MX);
    return this;
  }

  @Override
  public DnsClient resolveTXT(String name, final Handler>> handler) {
    lookup(name, new Handler() {
      @SuppressWarnings("unchecked")
      @Override
      public void handle(AsyncResult event) {
        if (event.failed()) {
          handler.handle(event);
        } else {
          List txts = new ArrayList<>();
          List> records = (List>) event.result();
          for (List txt: records) {
            txts.addAll(txt);
          }
          handler.handle(new DefaultFutureResult(txts));
        }
      }
    }, DnsEntry.TYPE_TXT);
    return this;
  }

  @Override
  public DnsClient resolvePTR(String name, final Handler> handler) {
    lookup(name, new HandlerAdapter(handler), DnsEntry.TYPE_PTR);
    return this;
  }

  @Override
  public DnsClient resolveAAAA(String name, Handler>> handler) {
    lookup(name, handler, DnsEntry.TYPE_AAAA);
    return this;
  }

  @Override
  public DnsClient resolveNS(String name, Handler>> handler) {
    lookup(name, handler, DnsEntry.TYPE_NS);
    return this;
  }

  @Override
  public DnsClient resolveSRV(String name, final Handler>> handler) {
    lookup(name, new ConvertingHandler(handler, DefaultSrvRecordComparator.INSTANCE) {
      @Override
      protected SrvRecord convert(ServiceRecord entry) {
        return new DefaultSrvRecord(entry);
      }
    }, DnsEntry.TYPE_SRV);
    return this;
  }

  @Override
  public DnsClient reverseLookup(String address, final Handler> handler) {
    // TODO:  Check if the given address is a valid ip address before pass it to InetAddres.getByName(..)
    //        This is need as otherwise it may try to perform a DNS lookup.
    //        An other option would be to change address to be of type InetAddress.
    try {
      final InetAddress inetAddress = InetAddress.getByName(address);
      byte[] addr = inetAddress.getAddress();

      StringBuilder reverseName = new StringBuilder(64);
      if (inetAddress instanceof Inet4Address) {
        // reverse ipv4 address
        reverseName.append(addr[3] & 0xff).append(".")
                .append(addr[2]& 0xff).append(".")
                .append(addr[1]& 0xff).append(".")
                .append(addr[0]& 0xff);
      } else {
        // It is an ipv 6 address time to reverse it
        for (int i = 0; i < 16; i++) {
          reverseName.append(HEX_TABLE[(addr[15 - i] & 0xf)]);
          reverseName.append(".");
          reverseName.append(HEX_TABLE[(addr[15 - i] >> 4) & 0xf]);
          if (i != 15) {
            reverseName.append(".");
          }
        }
      }
      reverseName.append(".in-addr.arpa");

      return resolvePTR(reverseName.toString(), new Handler>() {
        @Override
        public void handle(AsyncResult event) {
          if (event.failed()) {
            handler.handle(event);
          } else {
            String result = (String) event.result();
            try {
              handler.handle(new DefaultFutureResult<>(InetAddress.getByAddress(result, inetAddress.getAddress())));
            } catch (UnknownHostException e) {
              // Should never happen
              handler.handle(new DefaultFutureResult(e));
            }
          }
        }
      });
    } catch (final UnknownHostException e) {
      // Should never happen as we work with ip addresses as input
      // anyway just in case notify the handler
      actualCtx.execute(new Runnable() {
        public void run() {
          handler.handle(new DefaultFutureResult(e));

        }
      });
    }
    return this;
  }

  @SuppressWarnings("unchecked")
  private void lookup(final String name, final Handler handler, final int... types) {
    final DefaultFutureResult result = new DefaultFutureResult<>();
    result.setHandler(handler);
    lookup(dnsServers.iterator(), name, result, types);
  }

  @SuppressWarnings("unchecked")
  private void lookup(final Iterator dns, final String name, final DefaultFutureResult result, final int... types) {
    bootstrap.connect(dns.next()).addListener(new RetryChannelFutureListener(dns, name, result, types) {
      @Override
      public void onSuccess(ChannelFuture future) throws Exception {
        DnsQuery query = new DnsQuery(ThreadLocalRandom.current().nextInt());
        for (int type: types) {
          query.addQuestion(new DnsQuestion(name, type));
        }
        future.channel().writeAndFlush(query).addListener(new RetryChannelFutureListener(dns, name, result, types) {
          @Override
          public void onSuccess(ChannelFuture future) throws Exception {
            future.channel().pipeline().addLast(new SimpleChannelInboundHandler() {
              @Override
              protected void channelRead0(ChannelHandlerContext ctx, DnsResponse msg) throws Exception {
                DnsResponseCode code = DnsResponseCode.valueOf(msg.getHeader().getResponseCode());

                if (code == DnsResponseCode.NOERROR) {
                  List resources = msg.getAnswers();
                  List records = new ArrayList<>(resources.size());
                  for (DnsResource resource : msg.getAnswers()) {
                    Object record = RecordDecoderFactory.getFactory().decode(resource.type(), msg, resource);
                    records.add(record);
                  }

                  setResult(result, ctx.channel().eventLoop(), records);
                } else {
                  setResult(result, ctx.channel().eventLoop(), new DnsException(code));
                }
                ctx.close();
              }

              @Override
              public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                setResult(result, ctx.channel().eventLoop(), cause);
                ctx.close();
              }
            });
          }
        });
      }
    });
  }

  @SuppressWarnings("unchecked")
  private void setResult(final DefaultFutureResult r, EventLoop loop, final Object result) {
    if (r.complete()) {
      return;
    }
    if (actualCtx.isOnCorrectWorker(loop)) {
      try {
        vertx.setContext(actualCtx);
        if (result instanceof Throwable) {
          r.setFailure((Throwable) result);
        } else {
          r.setResult(result);
        }
      } catch (Throwable t) {
        actualCtx.reportException(t);
      }
    } else {
      actualCtx.execute(new Runnable() {
        public void run() {
          if (result instanceof Throwable) {
            r.setFailure((Throwable) result);
          } else {
            r.setResult(result);
          }
        }
      });
    }
  }

  private static final class HandlerAdapter implements Handler>> {
    private final Handler handler;

    HandlerAdapter(Handler handler) {
      this.handler = handler;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void handle(AsyncResult> event) {
      if (event.failed()) {
        handler.handle(event);
      } else {
        List result = event.result();
        if (result.isEmpty()) {
          handler.handle(new DefaultFutureResult<>((T)null));
        } else {
          handler.handle(new DefaultFutureResult<>(result.get(0)));
        }
      }
    }
  }

  protected abstract class ConvertingHandler implements Handler>> {
    private final Handler handler;
    private final Comparator comparator;

    ConvertingHandler(Handler>> handler, Comparator comparator) {
      this.handler = handler;
      this.comparator = comparator;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void handle(AsyncResult> event) {
      if (event.failed()) {
        handler.handle(event);
      } else {
        List records = (List) event.result();
        for (int i = 0; i < records.size(); i++) {
          F record = (F) records.get(i);
          records.set(i, convert(record));
        }

        Collections.sort(records, comparator);
        handler.handle(new DefaultFutureResult(records));
      }
    }

    protected abstract T convert(F entry);
  }

  private abstract class RetryChannelFutureListener implements ChannelFutureListener {
    private final Iterator dns;
    private final String name;
    private final DefaultFutureResult result;
    private final int[] types;

    RetryChannelFutureListener(final Iterator dns, final String name, final DefaultFutureResult result, final int... types) {
      this.dns = dns;
      this.name = name;
      this.result = result;
      this.types = types;
    }

    @Override
    public final void operationComplete(ChannelFuture future) throws Exception {
      if (!future.isSuccess()) {
        if (!result.complete()) {
          if (dns.hasNext()) {
            // try next dns server
            lookup(dns, name, result, types);
          } else {
            // TODO: maybe use a special exception ?
            result.setFailure(future.cause());
          }
        }
      } else {
        onSuccess(future);
      }
    }

    protected abstract void onSuccess(ChannelFuture future) throws Exception;
  }


}