blob: f762b84d2d96c1ffe2fa247d1ce1bab2153131ce [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.util.*;
6import java.io.*;
7import 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
17public class ExtendedResolver implements Resolver {
18
19private 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
234private static final int quantum = 5;
235
236private List resolvers;
237private boolean loadBalance = false;
238private int lbStart = 0;
239private int retries = 3;
240
241private void
242init() {
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 */
254public
255ExtendedResolver() 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 */
276public
277ExtendedResolver(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 */
292public
293ExtendedResolver(Resolver [] res) throws UnknownHostException {
294 init();
295 for (int i = 0; i < res.length; i++)
296 resolvers.add(res[i]);
297}
298
299public void
300setPort(int port) {
301 for (int i = 0; i < resolvers.size(); i++)
302 ((Resolver)resolvers.get(i)).setPort(port);
303}
304
305public void
306setTCP(boolean flag) {
307 for (int i = 0; i < resolvers.size(); i++)
308 ((Resolver)resolvers.get(i)).setTCP(flag);
309}
310
311public void
312setIgnoreTruncation(boolean flag) {
313 for (int i = 0; i < resolvers.size(); i++)
314 ((Resolver)resolvers.get(i)).setIgnoreTruncation(flag);
315}
316
317public void
318setEDNS(int level) {
319 for (int i = 0; i < resolvers.size(); i++)
320 ((Resolver)resolvers.get(i)).setEDNS(level);
321}
322
323public void
324setEDNS(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
330public void
331setTSIGKey(TSIG key) {
332 for (int i = 0; i < resolvers.size(); i++)
333 ((Resolver)resolvers.get(i)).setTSIGKey(key);
334}
335
336public void
337setTimeout(int secs, int msecs) {
338 for (int i = 0; i < resolvers.size(); i++)
339 ((Resolver)resolvers.get(i)).setTimeout(secs, msecs);
340}
341
342public void
343setTimeout(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 */
355public Message
356send(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 */
371public Object
372sendAsync(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 */
379public Resolver
380getResolver(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 */
387public Resolver []
388getResolvers() {
389 return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]);
390}
391
392/** Adds a new resolver to be used by this ExtendedResolver */
393public void
394addResolver(Resolver r) {
395 resolvers.add(r);
396}
397
398/** Deletes a resolver used by this ExtendedResolver */
399public void
400deleteResolver(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 */
408public void
409setLoadBalance(boolean flag) {
410 loadBalance = flag;
411}
412
413/** Sets the number of retries sent to each server per query */
414public void
415setRetries(int retries) {
416 this.retries = retries;
417}
418
419}