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