• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@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
Resolution(ExtendedResolver eres, Message query)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
send(int n)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
start()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
startAsync(ResolverListener listener)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
receiveMessage(Object id, Message m)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
handleException(Object id, Exception e)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
init()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
ExtendedResolver()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
ExtendedResolver(String [] servers)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
ExtendedResolver(Resolver [] res)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
setPort(int port)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
setTCP(boolean flag)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
setIgnoreTruncation(boolean flag)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
setEDNS(int level)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
setEDNS(int level, int payloadSize, int flags, List options)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
setTSIGKey(TSIG key)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
setTimeout(int secs, int msecs)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
setTimeout(int secs)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
send(Message query)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
sendAsync(final Message query, final ResolverListener listener)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
getResolver(int n)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 []
getResolvers()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
addResolver(Resolver r)394 addResolver(Resolver r) {
395 	resolvers.add(r);
396 }
397 
398 /** Deletes a resolver used by this ExtendedResolver */
399 public void
deleteResolver(Resolver r)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
setLoadBalance(boolean flag)409 setLoadBalance(boolean flag) {
410 	loadBalance = flag;
411 }
412 
413 /** Sets the number of retries sent to each server per query */
414 public void
setRetries(int retries)415 setRetries(int retries) {
416 	this.retries = retries;
417 }
418 
419 }
420