package com.netifera.platform.net.dns.tools; import java.io.IOException; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; import org.xbill.DNS.DClass; import org.xbill.DNS.Name; import org.xbill.DNS.PTRRecord; import org.xbill.DNS.Record; import org.xbill.DNS.ReverseMap; import org.xbill.DNS.Type; import com.netifera.platform.api.iterables.IndexedIterable; import com.netifera.platform.api.probe.IProbe; import com.netifera.platform.api.tools.ITool; import com.netifera.platform.api.tools.IToolContext; import com.netifera.platform.api.tools.ToolException; import com.netifera.platform.net.dns.internal.tools.Activator; import com.netifera.platform.net.dns.service.DNS; import com.netifera.platform.net.dns.service.client.AsynchronousLookup; import com.netifera.platform.net.dns.service.nameresolver.INameResolver; import com.netifera.platform.net.sockets.CompletionHandler; import com.netifera.platform.tools.RequiredOptionMissingException; import com.netifera.platform.util.addresses.AddressFormatException; import com.netifera.platform.util.addresses.inet.InternetAddress; public class DNSReverseLookup implements ITool { private static final boolean DEBUG = false; private static final int DEFAULT_DNS_INTERVAL = 200; // 200 milliseconds between requests private DNS dns; private IndexedIterable<InternetAddress> addresses; private INameResolver resolver; private IToolContext context; private long realm; private AtomicInteger activeRequests; private Queue<Runnable> retryQueue = new LinkedList<Runnable>(); private int successCount = 0; public void toolRun(IToolContext context) throws ToolException { this.context = context; final int sendDelay = getSendDelay(); // XXX hardcode local probe as realm IProbe probe = Activator.getInstance().getProbeManager().getLocalProbe(); realm = probe.getEntity().getId(); context.setTitle("Reverse lookup"); setupToolOptions(); context.setTitle("Reverse lookup "+addresses); try { if (dns != null) { resolver = dns.createNameResolver(Activator.getInstance().getSocketEngine()); } else { resolver = Activator.getInstance().getNameResolver(); } if (resolver == null) { throw new ToolException("No Name Resolver available on " + probe.getName()); } activeRequests = new AtomicInteger(0); context.setTotalWork(addresses.itemCount()); for (InternetAddress address: addresses) { if(DEBUG) context.debug("Looking up " + address + " outstanding = " + activeRequests.get()); if (Thread.currentThread().isInterrupted()) break; try { Thread.sleep(sendDelay); } catch(InterruptedException e) { context.warning("Interrupted"); Thread.currentThread().interrupt(); break; } reverseLookup(address, context); } while (activeRequests.get() > 0) { Runnable retry = nextRetryOrNull(); while (retry != null && !(Thread.currentThread().isInterrupted())) { Thread.sleep(sendDelay * 2); retry.run(); retry = nextRetryOrNull(); } Thread.sleep(500); } } catch (IOException e) { context.exception("I/O Exception", e); } catch (InterruptedException e) { context.warning("Interrupted"); } finally { try { if (dns != null) resolver.shutdown(); } catch (IOException e) { context.exception("I/O Exception", e); }; if (successCount == 0) context.error("No records found"); context.done(); } } private int getSendDelay() { final String property = System.getProperty("netifera.dns.delay"); if(property == null) { return DEFAULT_DNS_INTERVAL; } try { final int delay = Integer.parseInt(property); if(delay < 0 || delay > 10000) { return DEFAULT_DNS_INTERVAL; } return delay; } catch(NumberFormatException e) { return DEFAULT_DNS_INTERVAL; } } private void reverseLookup(final InternetAddress address, final IToolContext toolContext) { Name name = ReverseMap.fromAddress(address.toBytes()); final AsynchronousLookup lookup = new AsynchronousLookup(name, Type.PTR, DClass.IN); lookup.setResolver(resolver.getExtendedResolver()); CompletionHandler<Record[],Void> handler = new CompletionHandler<Record[],Void>() { int retry = 0; public void cancelled(Void attachment) { activeRequests.decrementAndGet(); context.warning("Reverse lookup of "+address+" was cancelled"); toolContext.worked(1); } public void completed(Record[] result, final Void attachment) { if (lookup.getResult() == AsynchronousLookup.TRY_AGAIN && retry < 3) { retry = retry + 1; final CompletionHandler<Record[],Void> handler = this; enqueueRetry(new Runnable() { public void run() { context.info("Retrying reverse lookup of "+address+" ("+retry+")"); lookup.run(attachment, handler); } }); return; } if(DEBUG) { context.info("completed to " + address + " outstanding = " + activeRequests.get()); } if (result == null) { toolContext.worked(1); activeRequests.decrementAndGet(); context.error(address+" Reverse lookup failed: "+lookup.getErrorString()); return; } for (Record record: result) processRecord(record, context); toolContext.worked(1); activeRequests.decrementAndGet(); } public void failed(Throwable exc, Void attachment) { activeRequests.decrementAndGet(); toolContext.worked(1); if(lookup.getResult() == AsynchronousLookup.HOST_NOT_FOUND) { return; } // TRY_AGAIN set for SERVFAIL, retry one time only if(lookup.getResult() == AsynchronousLookup.TRY_AGAIN && retry == 0) { retry++; final CompletionHandler<Record[], Void> handler = this; enqueueRetry(new Runnable() { public void run() { context.info("Retrying reverse lookup of "+address+" ("+retry+") on SERVFAIL"); lookup.run(null, handler); } }); return; } context.error("Reverse lookup of "+address+" failed: "+ lookup.getErrorString() ); /* Test for SERVFAIL */ if(lookup.getResult() != AsynchronousLookup.TRY_AGAIN) { context.exception("Unexpected exception in reverse lookup of " + address, exc); } }}; activeRequests.incrementAndGet(); lookup.run(null,handler); } private void processRecord(Record o, IToolContext context) { if (o instanceof PTRRecord) { PTRRecord ptr = (PTRRecord) o; if (ptr.getTarget().toString().endsWith(".arpa.")) { context.warning("Malformed PTR entry: " + ptr.toString()); return; } String reverseName = ptr.getName().toString(); if (!reverseName.endsWith(".arpa.")) { warnUnknownFormat(reverseName); return; } context.info(ptr.toString()); try { InternetAddress address = InternetAddress.fromARPA(reverseName); Activator.getInstance().getDomainEntityFactory().createPTRRecord(realm, context.getSpaceId(), address, ptr.getTarget().toString()); successCount += successCount + 1; } catch(AddressFormatException e) { warnUnknownFormat(reverseName); } } } private void warnUnknownFormat(String name) { context.warning("Unknown reverse address format: "+ name); } private void setupToolOptions() throws RequiredOptionMissingException { dns = (DNS) context.getConfiguration().get("dns"); addresses = (IndexedIterable<InternetAddress>) context.getConfiguration().get("target"); } private synchronized void enqueueRetry(Runnable runnable) { retryQueue.add(runnable); } private synchronized Runnable nextRetryOrNull() { return retryQueue.poll(); } }