package io.vertx.core.impl.resolver;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoop;
import io.netty.channel.socket.DatagramChannel;
import io.netty.resolver.AddressResolverGroup;
import io.netty.resolver.HostsFileEntries;
import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.HostsFileParser;
import io.netty.resolver.NameResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.resolver.dns.*;
import io.netty.util.NetUtil;
import io.netty.util.concurrent.EventExecutor;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.VertxException;
import io.vertx.core.dns.AddressResolverOptions;
import io.vertx.core.impl.AddressResolver;
import io.vertx.core.impl.VertxImpl;
import io.vertx.core.spi.resolver.ResolverProvider;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static io.netty.util.internal.ObjectUtil.intValue;
public class DnsResolverProvider implements ResolverProvider {
private final Vertx vertx;
private final List<ResolverRegistration> resolvers = Collections.synchronizedList(new ArrayList<>());
private AddressResolverGroup<InetSocketAddress> resolverGroup;
private final List<InetSocketAddress> serverList = new ArrayList<>();
public List<InetSocketAddress> nameServerAddresses() {
return serverList;
}
public DnsResolverProvider(VertxImpl vertx, AddressResolverOptions options) {
List<String> dnsServers = options.getServers();
if (dnsServers != null && dnsServers.size() > 0) {
for (String dnsServer : dnsServers) {
int sep = dnsServer.indexOf(':');
String ipAddress;
int port;
if (sep != -1) {
ipAddress = dnsServer.substring(0, sep);
port = Integer.parseInt(dnsServer.substring(sep + 1));
} else {
ipAddress = dnsServer;
port = 53;
}
try {
serverList.add(new InetSocketAddress(InetAddress.getByAddress(NetUtil.createByteArrayFromIpAddressString(ipAddress)), port));
} catch (UnknownHostException e) {
throw new VertxException(e);
}
}
} else {
DnsServerAddressStream stream = DefaultDnsServerAddressStreamProvider.defaultAddresses().stream();
Set<InetSocketAddress> all = new HashSet<>();
while (true) {
InetSocketAddress address = stream.next();
if (all.contains(address)) {
break;
}
serverList.add(address);
all.add(address);
}
}
DnsServerAddresses nameServerAddresses = options.isRotateServers() ? DnsServerAddresses.rotational(serverList) : DnsServerAddresses.sequential(serverList);
DnsServerAddressStreamProvider nameServerAddressProvider = hostname -> nameServerAddresses.stream();
HostsFileEntries entries;
if (options.getHostsPath() != null) {
File file = vertx.resolveFile(options.getHostsPath()).getAbsoluteFile();
try {
if (!file.exists() || !file.isFile()) {
throw new IOException();
}
entries = HostsFileParser.parse(file);
} catch (IOException e) {
throw new VertxException("Cannot read hosts file " + file.getAbsolutePath());
}
} else if (options.getHostsValue() != null) {
try {
entries = HostsFileParser.parse(new StringReader(options.getHostsValue().toString()));
} catch (IOException e) {
throw new VertxException("Cannot read hosts config ", e);
}
} else {
entries = HostsFileParser.parseSilently();
}
int minTtl = intValue(options.getCacheMinTimeToLive(), 0);
int maxTtl = intValue(options.getCacheMaxTimeToLive(), Integer.MAX_VALUE);
int negativeTtl = intValue(options.getCacheNegativeTimeToLive(), 0);
DnsCache resolveCache = new DefaultDnsCache(minTtl, maxTtl, negativeTtl);
DnsCache authoritativeDnsServerCache = new DefaultDnsCache(minTtl, maxTtl, negativeTtl);
this.vertx = vertx;
this.resolverGroup = new AddressResolverGroup<InetSocketAddress>() {
@Override
protected io.netty.resolver.AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) throws Exception {
ChannelFactory<DatagramChannel> channelFactory = () -> vertx.transport().datagramChannel();
DnsAddressResolverGroup group = new DnsAddressResolverGroup(channelFactory, nameServerAddressProvider) {
@Override
protected NameResolver<InetAddress> newNameResolver(EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory, DnsServerAddressStreamProvider nameServerProvider) throws Exception {
DnsNameResolverBuilder builder = new DnsNameResolverBuilder((EventLoop) executor);
builder.hostsFileEntriesResolver(new HostsFileEntriesResolver() {
@Override
public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
if (inetHost.endsWith(".")) {
inetHost = inetHost.substring(0, inetHost.length() - 1);
}
InetAddress address = lookup(inetHost, resolvedAddressTypes);
if (address == null) {
address = lookup(inetHost.toLowerCase(Locale.ENGLISH), resolvedAddressTypes);
}
return address;
}
InetAddress lookup(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
switch (resolvedAddressTypes) {
case IPV4_ONLY:
return entries.inet4Entries().get(inetHost);
case IPV6_ONLY:
return entries.inet6Entries().get(inetHost);
case IPV4_PREFERRED:
Inet4Address inet4Address = entries.inet4Entries().get(inetHost);
return inet4Address != null? inet4Address : entries.inet6Entries().get(inetHost);
case IPV6_PREFERRED:
Inet6Address inet6Address = entries.inet6Entries().get(inetHost);
return inet6Address != null? inet6Address : entries.inet4Entries().get(inetHost);
default:
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
}
}
});
builder.channelFactory(channelFactory);
builder.nameServerProvider(nameServerAddressProvider);
builder.optResourceEnabled(options.isOptResourceEnabled());
builder.resolveCache(resolveCache);
builder.authoritativeDnsServerCache(authoritativeDnsServerCache);
builder.queryTimeoutMillis(options.getQueryTimeout());
builder.maxQueriesPerResolve(options.getMaxQueries());
builder.recursionDesired(options.getRdFlag());
if (options.getSearchDomains() != null) {
builder.searchDomains(options.getSearchDomains());
int ndots = options.getNdots();
if (ndots == -1) {
ndots = AddressResolver.DEFAULT_NDOTS_RESOLV_OPTION;
}
builder.ndots(ndots);
}
return builder.build();
}
};
io.netty.resolver.AddressResolver<InetSocketAddress> resolver = group.getResolver(executor);
resolvers.add(new ResolverRegistration(resolver, (EventLoop) executor));
return resolver;
}
};
}
private static class ResolverRegistration {
private final io.netty.resolver.AddressResolver<InetSocketAddress> resolver;
private final EventLoop executor;
ResolverRegistration(io.netty.resolver.AddressResolver<InetSocketAddress> resolver, EventLoop executor) {
this.resolver = resolver;
this.executor = executor;
}
}
@Override
public AddressResolverGroup<InetSocketAddress> resolver(AddressResolverOptions options) {
return resolverGroup;
}
@Override
public void close(Handler<Void> doneHandler) {
Context context = vertx.getOrCreateContext();
ResolverRegistration[] registrations = this.resolvers.toArray(new ResolverRegistration[0]);
if (registrations.length == 0) {
context.runOnContext(doneHandler);
return;
}
AtomicInteger count = new AtomicInteger(registrations.length);
for (ResolverRegistration registration : registrations) {
Runnable task = () -> {
registration.resolver.close();
if (count.decrementAndGet() == 0) {
context.runOnContext(doneHandler);
}
};
if (registration.executor.inEventLoop()) {
task.run();
} else {
registration.executor.execute(task);
}
}
}
}