// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) package com.netifera.platform.net.dns.service.client; import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import org.xbill.DNS.ARecord; import org.xbill.DNS.DClass; import org.xbill.DNS.Lookup; import org.xbill.DNS.Message; import org.xbill.DNS.Name; import org.xbill.DNS.Options; import org.xbill.DNS.PTRRecord; import org.xbill.DNS.Record; import org.xbill.DNS.Resolver; import org.xbill.DNS.ResolverListener; import org.xbill.DNS.ReverseMap; import org.xbill.DNS.TSIG; import org.xbill.DNS.TextParseException; import org.xbill.DNS.Type; import com.netifera.platform.util.addresses.inet.InternetAddress; /** * An implementation of Resolver that can send queries to multiple servers, * sending the queries multiple times if necessary. * * @see Resolver * * @author Brian Wellington * @author lee */ public class ExtendedResolver implements Resolver { private static class Resolution implements ResolverListener { Resolver[] resolvers; int[] sent; Object[] inprogress; int retries; int outstanding; boolean done; Message query; Message response; Throwable thrown; ResolverListener listener; public Resolution(ExtendedResolver eres, Message query) { assert eres.resolvers.size() != 0; /* * Note: an empty eres.resolvers will generates a * ArrayIndexOutOfBoundsException in all the Resolution methods * * sent = new int[resolvers.length]; * inprogress = new Object[resolvers.length]; * * sent[0]++; // ArrayIndexOutOfBoundsException */ List<Resolver> l = eres.resolvers; resolvers = l.toArray(new Resolver[l.size()]); if (eres.loadBalance) { int nresolvers = resolvers.length; /* * Note: this is not synchronized, since the worst thing that * can happen is a random ordering, which is ok. */ int start = eres.lbStart++ % nresolvers; if (eres.lbStart > nresolvers) eres.lbStart %= nresolvers; if (start > 0) { Resolver[] shuffle = new Resolver[nresolvers]; for (int i = 0; i < nresolvers; i++) { int pos = (i + start) % nresolvers; shuffle[i] = resolvers[pos]; } resolvers = shuffle; } } sent = new int[resolvers.length]; inprogress = new Object[resolvers.length]; retries = eres.retries; this.query = query; } /* Asynchronously sends a message. */ public void send(int n) { sent[n]++; outstanding++; try { inprogress[n] = resolvers[n].sendAsync(query, this); } catch (Throwable t) { thrown = t; done = true; if (listener == null) { notifyAll(); return; } } } /* Start a synchronous resolution */ public Message start() throws IOException { try { /* * First, try sending synchronously. If this works, we're done. * Otherwise, we'll get an exception and continue. It would be * easier to call send(0), but this avoids a thread creation. If * and when SimpleResolver.sendAsync() can be made to not create * a thread, this could be changed. */ sent[0]++; outstanding++; inprogress[0] = new Object(); return resolvers[0].send(query); } catch (Exception e) { /* * This will either cause more queries to be sent asynchronously * or will set the 'done' flag. */ handleException(inprogress[0], e); } if (!done) { /* * Wait for a successful response or for each subresolver to * fail. */ synchronized (this) { while (!done) { try { wait(); } catch (InterruptedException e) { } } } } /* Return the response or throw an exception */ if (response != null) return response; else if (thrown instanceof IOException) throw (IOException) thrown; else if (thrown instanceof RuntimeException) throw (RuntimeException) thrown; else if (thrown instanceof Error) throw (Error) thrown; else throw new IllegalStateException("ExtendedResolver failure"); } /* Start an asynchronous resolution */ public void startAsync(ResolverListener listener) { this.listener = listener; send(0); } /* * Receive a response. If the resolution hasn't been completed, either * wake up the blocking thread or call the callback. */ public void receiveMessage(Object id, Message m) { if (Options.check("verbose")) System.err.println("ExtendedResolver: " + "received message"); synchronized (this) { if (done) return; response = m; done = true; if (listener == null) { notifyAll(); return; } } listener.receiveMessage(this, response); } /* * Receive an exception. If the resolution has been completed, do * nothing. Otherwise make progress. */ public void handleException(Object id, Exception e) { if (Options.check("verbose")) System.err.println("ExtendedResolver: got " + e); synchronized (this) { outstanding--; if (done) return; int n; for (n = 0; n < inprogress.length; n++) if (inprogress[n] == id) break; /* If we don't know what this is, do nothing. */ if (n == inprogress.length) return; boolean startnext = false; // boolean waiting = false; /* * If this is the first response from server n, we should start * sending queries to server n + 1. */ if (sent[n] == 1 && n < resolvers.length - 1) startnext = true; if (e instanceof InterruptedIOException) { /* Got a timeout; resend */ if (sent[n] < retries) send(n); if (thrown == null) thrown = e; } else if (e instanceof SocketException) { /* * Problem with the socket; don't resend on it */ if (thrown == null || thrown instanceof InterruptedIOException) thrown = e; } else { /* * Problem with the response; don't resend on the same * socket. */ thrown = e; } if (done) return; if (startnext) send(n + 1); if (done) return; if (outstanding == 0) { /* * If we're done and this is synchronous, wake up the * blocking thread. */ done = true; if (listener == null) { notifyAll(); return; } } if (!done) return; } /* If we're done and this is asynchronous, call the callback. */ if (!(thrown instanceof Exception)) thrown = new RuntimeException(thrown.getMessage()); listener.handleException(this, (Exception) thrown); } } private List<Resolver> resolvers = new ArrayList<Resolver>();; private boolean loadBalance = false; private int lbStart = 0; private int retries = 3; /** * Creates a new Extended Resolver. The default ResolverConfig is used to * determine the servers for which SimpleResolver contexts should be * initialized. * * @see SimpleResolver * @see ResolverConfig * @exception UnknownHostException * Failure occured initializing SimpleResolvers */ /* * public ExtendedResolver() throws UnknownHostException { init(); // XXX * ResolverConfig is deprecated String [] servers = * ResolverConfig.getCurrentConfig().servers(); if (servers != null) { for * (int i = 0; i < servers.length; i++) { Resolver r = new * SimpleResolver(servers[i]); r.setTimeout(quantum); resolvers.add(r); } } * else resolvers.add(new SimpleResolver()); } */ /** * Creates a new Extended Resolver * * @param servers * An array of server names for which SimpleResolver contexts * should be initialized. * @see SimpleResolver * @exception UnknownHostException * Failure occured initializing SimpleResolvers */ /* * public ExtendedResolver(UDPSocketLocator[] servers) throws * UnknownHostException { init(); for (int i = 0; i < servers.length; i++) { * Resolver r = new SimpleResolver(servers[i]); r.setTimeout(quantum); * resolvers.add(r); } } */ public ExtendedResolver() { } /** * Creates a new Extended Resolver * * @param res * An array of pre-initialized Resolvers is provided. * @see SimpleResolver * @exception UnknownHostException * Failure occured initializing SimpleResolvers */ /* public ExtendedResolver(Resolver[] res) { for (int i = 0; i < res.length; i++) resolvers.add(res[i]); } */ @Deprecated public void setPort(int port) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setPort(port); } @Deprecated public void setTCP(boolean flag) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setTCP(flag); } public void setIgnoreTruncation(boolean flag) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setIgnoreTruncation(flag); } public void setEDNS(int level) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setEDNS(level); } @SuppressWarnings("unchecked") public void setEDNS(int level, int payloadSize, int flags, List options) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setEDNS(level, payloadSize, flags, options); } public void setTSIGKey(TSIG key) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setTSIGKey(key); } public void setTimeout(int secs, int msecs) { for (int i = 0; i < resolvers.size(); i++) resolvers.get(i).setTimeout(secs, msecs); } public void setTimeout(int secs) { setTimeout(secs, 0); } /** * Sends a message and waits for a response. Multiple servers are queried, * and queries are sent multiple times until either a successful response is * received, or it is clear that there is no successful response. * * @param query * The query to send. * @return The response. * @throws IOException * An error occurred while sending or receiving. */ public Message send(Message query) throws IOException { Resolution res = new Resolution(this, query); return res.start(); } /** * Asynchronously sends a message to multiple servers, potentially multiple * times, registering a listener to receive a callback on success or * exception. Multiple asynchronous lookups can be performed in parallel. * Since the callback may be invoked before the function returns, external * synchronization is necessary. * * @param query * The query to send * @param listener * The object containing the callbacks. * @return An identifier, which is also a parameter in the callback */ public Object sendAsync(final Message query, final ResolverListener listener) { Resolution res = new Resolution(this, query); res.startAsync(listener); return res; } /** Returns the nth resolver used by this ExtendedResolver */ public Resolver getResolver(int n) { if (n < resolvers.size()) return resolvers.get(n); return null; } /** Returns all resolvers used by this ExtendedResolver */ public Resolver[] getResolvers() { return resolvers.toArray(new Resolver[resolvers.size()]); } /** Adds a new resolver to be used by this ExtendedResolver */ public void addResolver(Resolver r) { resolvers.add(r); } /** Deletes a resolver used by this ExtendedResolver */ public void deleteResolver(Resolver r) { resolvers.remove(r); } /** * Sets whether the servers should be load balanced. * * @param flag * If true, servers will be tried in round-robin order. If false, * servers will always be queried in the same order. */ public void setLoadBalance(boolean flag) { loadBalance = flag; } /** Sets the number of retries sent to each server per query */ public void setRetries(int retries) { this.retries = retries; } public InternetAddress getAddressByName(String name) throws TextParseException, UnknownHostException { Lookup lookup = new Lookup(name); lookup.setResolver(this); lookup.setSearchPath((Name[])null); for (Record record: lookup.run()) if (record instanceof ARecord) return InternetAddress.fromInetAddress(((ARecord)record).getAddress()); throw new UnknownHostException(name); } public List<InternetAddress> getAddressesByName(String name) throws UnknownHostException { Lookup lookup; try { lookup = new Lookup(name); } catch (TextParseException e) { throw new UnknownHostException("Malformed host name: "+ name); } lookup.setResolver(this); lookup.setSearchPath((Name[])null); List<InternetAddress> answer = new ArrayList<InternetAddress>(); for (Record record: lookup.run()) if (record instanceof ARecord) answer.add(InternetAddress.fromInetAddress(((ARecord)record).getAddress())); return answer; } public String getNameByAddress(InternetAddress address) throws UnknownHostException { Name name = ReverseMap.fromAddress(address.toString()); Lookup lookup = new Lookup(name, Type.PTR, DClass.IN); lookup.setResolver(this); lookup.setSearchPath((Name[])null); for (Record record: lookup.run()) if (record instanceof PTRRecord) return ((PTRRecord)record).getName().toString(); throw new UnknownHostException(address.toString()); } public List<String> getNamesByAddress(InternetAddress address) throws UnknownHostException { Name name = ReverseMap.fromAddress(address.toString()); Lookup lookup = new Lookup(name, Type.PTR, DClass.IN); lookup.setResolver(this); lookup.setSearchPath((Name[])null); List<String> answer = new ArrayList<String>(); for (Record record: lookup.run()) if (record instanceof PTRRecord) answer.add(((PTRRecord)record).getName().toString()); return answer; } public synchronized void shutdown() throws IOException { for (Resolver resolver: resolvers) { if (resolver instanceof SimpleResolver) { ((SimpleResolver)resolver).shutdown(); } else if (resolver instanceof ExtendedResolver) { ((ExtendedResolver)resolver).shutdown(); } } } }