org.vertx.java.core.dns.impl.DefaultDnsClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vertx-core Show documentation
Show all versions of vertx-core Show documentation
vert.x - Effortless asynchronous application development for the modern web and enterprise
/*
* 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