Home | History | Annotate | Download | only in DNS
      1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) xbill.org)
      2 
      3 package org.xbill.DNS;
      4 
      5 import java.util.*;
      6 import java.io.*;
      7 import java.net.*;
      8 
      9 /**
     10  * An implementation of Resolver that can send queries to multiple servers,
     11  * sending the queries multiple times if necessary.
     12  * @see Resolver
     13  *
     14  * @author Brian Wellington
     15  */
     16 
     17 public class ExtendedResolver implements Resolver {
     18 
     19 private static class Resolution implements ResolverListener {
     20 	Resolver [] resolvers;
     21 	int [] sent;
     22 	Object [] inprogress;
     23 	int retries;
     24 	int outstanding;
     25 	boolean done;
     26 	Message query;
     27 	Message response;
     28 	Throwable thrown;
     29 	ResolverListener listener;
     30 
     31 	public
     32 	Resolution(ExtendedResolver eres, Message query) {
     33 		List l = eres.resolvers;
     34 		resolvers = (Resolver []) l.toArray (new Resolver[l.size()]);
     35 		if (eres.loadBalance) {
     36 			int nresolvers = resolvers.length;
     37 			/*
     38 			 * Note: this is not synchronized, since the
     39 			 * worst thing that can happen is a random
     40 			 * ordering, which is ok.
     41 			 */
     42 			int start = eres.lbStart++ % nresolvers;
     43 			if (eres.lbStart > nresolvers)
     44 				eres.lbStart %= nresolvers;
     45 			if (start > 0) {
     46 				Resolver [] shuffle = new Resolver[nresolvers];
     47 				for (int i = 0; i < nresolvers; i++) {
     48 					int pos = (i + start) % nresolvers;
     49 					shuffle[i] = resolvers[pos];
     50 				}
     51 				resolvers = shuffle;
     52 			}
     53 		}
     54 		sent = new int[resolvers.length];
     55 		inprogress = new Object[resolvers.length];
     56 		retries = eres.retries;
     57 		this.query = query;
     58 	}
     59 
     60 	/* Asynchronously sends a message. */
     61 	public void
     62 	send(int n) {
     63 		sent[n]++;
     64 		outstanding++;
     65 		try {
     66 			inprogress[n] = resolvers[n].sendAsync(query, this);
     67 		}
     68 		catch (Throwable t) {
     69 			synchronized (this) {
     70 				thrown = t;
     71 				done = true;
     72 				if (listener == null) {
     73 					notifyAll();
     74 					return;
     75 				}
     76 			}
     77 		}
     78 	}
     79 
     80 	/* Start a synchronous resolution */
     81 	public Message
     82 	start() throws IOException {
     83 		try {
     84 			/*
     85 			 * First, try sending synchronously.  If this works,
     86 			 * we're done.  Otherwise, we'll get an exception
     87 			 * and continue.  It would be easier to call send(0),
     88 			 * but this avoids a thread creation.  If and when
     89 			 * SimpleResolver.sendAsync() can be made to not
     90 			 * create a thread, this could be changed.
     91 			 */
     92 			sent[0]++;
     93 			outstanding++;
     94 			inprogress[0] = new Object();
     95 			return resolvers[0].send(query);
     96 		}
     97 		catch (Exception e) {
     98 			/*
     99 			 * This will either cause more queries to be sent
    100 			 * asynchronously or will set the 'done' flag.
    101 			 */
    102 			handleException(inprogress[0], e);
    103 		}
    104 		/*
    105 		 * Wait for a successful response or for each
    106 		 * subresolver to fail.
    107 		 */
    108 		synchronized (this) {
    109 			while (!done) {
    110 				try {
    111 					wait();
    112 				}
    113 				catch (InterruptedException e) {
    114 				}
    115 			}
    116 		}
    117 		/* Return the response or throw an exception */
    118 		if (response != null)
    119 			return response;
    120 		else if (thrown instanceof IOException)
    121 			throw (IOException) thrown;
    122 		else if (thrown instanceof RuntimeException)
    123 			throw (RuntimeException) thrown;
    124 		else if (thrown instanceof Error)
    125 			throw (Error) thrown;
    126 		else
    127 			throw new IllegalStateException
    128 				("ExtendedResolver failure");
    129 	}
    130 
    131 	/* Start an asynchronous resolution */
    132 	public void
    133 	startAsync(ResolverListener listener) {
    134 		this.listener = listener;
    135 		send(0);
    136 	}
    137 
    138 	/*
    139 	 * Receive a response.  If the resolution hasn't been completed,
    140 	 * either wake up the blocking thread or call the callback.
    141 	 */
    142 	public void
    143 	receiveMessage(Object id, Message m) {
    144 		if (Options.check("verbose"))
    145 			System.err.println("ExtendedResolver: " +
    146 					   "received message");
    147 		synchronized (this) {
    148 			if (done)
    149 				return;
    150 			response = m;
    151 			done = true;
    152 			if (listener == null) {
    153 				notifyAll();
    154 				return;
    155 			}
    156 		}
    157 		listener.receiveMessage(this, response);
    158 	}
    159 
    160 	/*
    161 	 * Receive an exception.  If the resolution has been completed,
    162 	 * do nothing.  Otherwise make progress.
    163 	 */
    164 	public void
    165 	handleException(Object id, Exception e) {
    166 		if (Options.check("verbose"))
    167 			System.err.println("ExtendedResolver: got " + e);
    168 		synchronized (this) {
    169 			outstanding--;
    170 			if (done)
    171 				return;
    172 			int n;
    173 			for (n = 0; n < inprogress.length; n++)
    174 				if (inprogress[n] == id)
    175 					break;
    176 			/* If we don't know what this is, do nothing. */
    177 			if (n == inprogress.length)
    178 				return;
    179 			boolean startnext = false;
    180 			/*
    181 			 * If this is the first response from server n,
    182 			 * we should start sending queries to server n + 1.
    183 			 */
    184 			if (sent[n] == 1 && n < resolvers.length - 1)
    185 				startnext = true;
    186 			if (e instanceof InterruptedIOException) {
    187 				/* Got a timeout; resend */
    188 				if (sent[n] < retries)
    189 					send(n);
    190 				if (thrown == null)
    191 					thrown = e;
    192 			} else if (e instanceof SocketException) {
    193 				/*
    194 				 * Problem with the socket; don't resend
    195 				 * on it
    196 				 */
    197 				if (thrown == null ||
    198 				    thrown instanceof InterruptedIOException)
    199 					thrown = e;
    200 			} else {
    201 				/*
    202 				 * Problem with the response; don't resend
    203 				 * on the same socket.
    204 				 */
    205 				thrown = e;
    206 			}
    207 			if (done)
    208 				return;
    209 			if (startnext)
    210 				send(n + 1);
    211 			if (done)
    212 				return;
    213 			if (outstanding == 0) {
    214 				/*
    215 				 * If we're done and this is synchronous,
    216 				 * wake up the blocking thread.
    217 				 */
    218 				done = true;
    219 				if (listener == null) {
    220 					notifyAll();
    221 					return;
    222 				}
    223 			}
    224 			if (!done)
    225 				return;
    226 		}
    227 		/* If we're done and this is asynchronous, call the callback. */
    228 		if (!(thrown instanceof Exception))
    229 			thrown = new RuntimeException(thrown.getMessage());
    230 		listener.handleException(this, (Exception) thrown);
    231 	}
    232 }
    233 
    234 private static final int quantum = 5;
    235 
    236 private List resolvers;
    237 private boolean loadBalance = false;
    238 private int lbStart = 0;
    239 private int retries = 3;
    240 
    241 private void
    242 init() {
    243 	resolvers = new ArrayList();
    244 }
    245 
    246 /**
    247  * Creates a new Extended Resolver.  The default ResolverConfig is used to
    248  * determine the servers for which SimpleResolver contexts should be
    249  * initialized.
    250  * @see SimpleResolver
    251  * @see ResolverConfig
    252  * @exception UnknownHostException Failure occured initializing SimpleResolvers
    253  */
    254 public
    255 ExtendedResolver() throws UnknownHostException {
    256 	init();
    257 	String [] servers = ResolverConfig.getCurrentConfig().servers();
    258 	if (servers != null) {
    259 		for (int i = 0; i < servers.length; i++) {
    260 			Resolver r = new SimpleResolver(servers[i]);
    261 			r.setTimeout(quantum);
    262 			resolvers.add(r);
    263 		}
    264 	}
    265 	else
    266 		resolvers.add(new SimpleResolver());
    267 }
    268 
    269 /**
    270  * Creates a new Extended Resolver
    271  * @param servers An array of server names for which SimpleResolver
    272  * contexts should be initialized.
    273  * @see SimpleResolver
    274  * @exception UnknownHostException Failure occured initializing SimpleResolvers
    275  */
    276 public
    277 ExtendedResolver(String [] servers) throws UnknownHostException {
    278 	init();
    279 	for (int i = 0; i < servers.length; i++) {
    280 		Resolver r = new SimpleResolver(servers[i]);
    281 		r.setTimeout(quantum);
    282 		resolvers.add(r);
    283 	}
    284 }
    285 
    286 /**
    287  * Creates a new Extended Resolver
    288  * @param res An array of pre-initialized Resolvers is provided.
    289  * @see SimpleResolver
    290  * @exception UnknownHostException Failure occured initializing SimpleResolvers
    291  */
    292 public
    293 ExtendedResolver(Resolver [] res) throws UnknownHostException {
    294 	init();
    295 	for (int i = 0; i < res.length; i++)
    296 		resolvers.add(res[i]);
    297 }
    298 
    299 public void
    300 setPort(int port) {
    301 	for (int i = 0; i < resolvers.size(); i++)
    302 		((Resolver)resolvers.get(i)).setPort(port);
    303 }
    304 
    305 public void
    306 setTCP(boolean flag) {
    307 	for (int i = 0; i < resolvers.size(); i++)
    308 		((Resolver)resolvers.get(i)).setTCP(flag);
    309 }
    310 
    311 public void
    312 setIgnoreTruncation(boolean flag) {
    313 	for (int i = 0; i < resolvers.size(); i++)
    314 		((Resolver)resolvers.get(i)).setIgnoreTruncation(flag);
    315 }
    316 
    317 public void
    318 setEDNS(int level) {
    319 	for (int i = 0; i < resolvers.size(); i++)
    320 		((Resolver)resolvers.get(i)).setEDNS(level);
    321 }
    322 
    323 public void
    324 setEDNS(int level, int payloadSize, int flags, List options) {
    325 	for (int i = 0; i < resolvers.size(); i++)
    326 		((Resolver)resolvers.get(i)).setEDNS(level, payloadSize,
    327 						     flags, options);
    328 }
    329 
    330 public void
    331 setTSIGKey(TSIG key) {
    332 	for (int i = 0; i < resolvers.size(); i++)
    333 		((Resolver)resolvers.get(i)).setTSIGKey(key);
    334 }
    335 
    336 public void
    337 setTimeout(int secs, int msecs) {
    338 	for (int i = 0; i < resolvers.size(); i++)
    339 		((Resolver)resolvers.get(i)).setTimeout(secs, msecs);
    340 }
    341 
    342 public void
    343 setTimeout(int secs) {
    344 	setTimeout(secs, 0);
    345 }
    346 
    347 /**
    348  * Sends a message and waits for a response.  Multiple servers are queried,
    349  * and queries are sent multiple times until either a successful response
    350  * is received, or it is clear that there is no successful response.
    351  * @param query The query to send.
    352  * @return The response.
    353  * @throws IOException An error occurred while sending or receiving.
    354  */
    355 public Message
    356 send(Message query) throws IOException {
    357 	Resolution res = new Resolution(this, query);
    358 	return res.start();
    359 }
    360 
    361 /**
    362  * Asynchronously sends a message to multiple servers, potentially multiple
    363  * times, registering a listener to receive a callback on success or exception.
    364  * Multiple asynchronous lookups can be performed in parallel.  Since the
    365  * callback may be invoked before the function returns, external
    366  * synchronization is necessary.
    367  * @param query The query to send
    368  * @param listener The object containing the callbacks.
    369  * @return An identifier, which is also a parameter in the callback
    370  */
    371 public Object
    372 sendAsync(final Message query, final ResolverListener listener) {
    373 	Resolution res = new Resolution(this, query);
    374 	res.startAsync(listener);
    375 	return res;
    376 }
    377 
    378 /** Returns the nth resolver used by this ExtendedResolver */
    379 public Resolver
    380 getResolver(int n) {
    381 	if (n < resolvers.size())
    382 		return (Resolver)resolvers.get(n);
    383 	return null;
    384 }
    385 
    386 /** Returns all resolvers used by this ExtendedResolver */
    387 public Resolver []
    388 getResolvers() {
    389 	return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]);
    390 }
    391 
    392 /** Adds a new resolver to be used by this ExtendedResolver */
    393 public void
    394 addResolver(Resolver r) {
    395 	resolvers.add(r);
    396 }
    397 
    398 /** Deletes a resolver used by this ExtendedResolver */
    399 public void
    400 deleteResolver(Resolver r) {
    401 	resolvers.remove(r);
    402 }
    403 
    404 /** Sets whether the servers should be load balanced.
    405  * @param flag If true, servers will be tried in round-robin order.  If false,
    406  * servers will always be queried in the same order.
    407  */
    408 public void
    409 setLoadBalance(boolean flag) {
    410 	loadBalance = flag;
    411 }
    412 
    413 /** Sets the number of retries sent to each server per query */
    414 public void
    415 setRetries(int retries) {
    416 	this.retries = retries;
    417 }
    418 
    419 }
    420