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 sends one query to one server. 11 * SimpleResolver handles TCP retries, transaction security (TSIG), and 12 * EDNS 0. 13 * @see Resolver 14 * @see TSIG 15 * @see OPTRecord 16 * 17 * @author Brian Wellington 18 */ 19 20 21 public class SimpleResolver implements Resolver { 22 23 /** The default port to send queries to */ 24 public static final int DEFAULT_PORT = 53; 25 26 /** The default EDNS payload size */ 27 public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280; 28 29 private InetSocketAddress address; 30 private InetSocketAddress localAddress; 31 private boolean useTCP, ignoreTruncation; 32 private OPTRecord queryOPT; 33 private TSIG tsig; 34 private long timeoutValue = 10 * 1000; 35 36 private static final short DEFAULT_UDPSIZE = 512; 37 38 private static String defaultResolver = "localhost"; 39 private static int uniqueID = 0; 40 41 /** 42 * Creates a SimpleResolver that will query the specified host 43 * @exception UnknownHostException Failure occurred while finding the host 44 */ 45 public 46 SimpleResolver(String hostname) throws UnknownHostException { 47 if (hostname == null) { 48 hostname = ResolverConfig.getCurrentConfig().server(); 49 if (hostname == null) 50 hostname = defaultResolver; 51 } 52 InetAddress addr; 53 if (hostname.equals("0")) 54 addr = InetAddress.getLocalHost(); 55 else 56 addr = InetAddress.getByName(hostname); 57 address = new InetSocketAddress(addr, DEFAULT_PORT); 58 } 59 60 /** 61 * Creates a SimpleResolver. The host to query is either found by using 62 * ResolverConfig, or the default host is used. 63 * @see ResolverConfig 64 * @exception UnknownHostException Failure occurred while finding the host 65 */ 66 public 67 SimpleResolver() throws UnknownHostException { 68 this(null); 69 } 70 71 /** 72 * Gets the destination address associated with this SimpleResolver. 73 * Messages sent using this SimpleResolver will be sent to this address. 74 * @return The destination address associated with this SimpleResolver. 75 */ 76 InetSocketAddress 77 getAddress() { 78 return address; 79 } 80 81 /** Sets the default host (initially localhost) to query */ 82 public static void 83 setDefaultResolver(String hostname) { 84 defaultResolver = hostname; 85 } 86 87 public void 88 setPort(int port) { 89 address = new InetSocketAddress(address.getAddress(), port); 90 } 91 92 /** 93 * Sets the address of the server to communicate with. 94 * @param addr The address of the DNS server 95 */ 96 public void 97 setAddress(InetSocketAddress addr) { 98 address = addr; 99 } 100 101 /** 102 * Sets the address of the server to communicate with (on the default 103 * DNS port) 104 * @param addr The address of the DNS server 105 */ 106 public void 107 setAddress(InetAddress addr) { 108 address = new InetSocketAddress(addr, address.getPort()); 109 } 110 111 /** 112 * Sets the local address to bind to when sending messages. 113 * @param addr The local address to send messages from. 114 */ 115 public void 116 setLocalAddress(InetSocketAddress addr) { 117 localAddress = addr; 118 } 119 120 /** 121 * Sets the local address to bind to when sending messages. A random port 122 * will be used. 123 * @param addr The local address to send messages from. 124 */ 125 public void 126 setLocalAddress(InetAddress addr) { 127 localAddress = new InetSocketAddress(addr, 0); 128 } 129 130 public void 131 setTCP(boolean flag) { 132 this.useTCP = flag; 133 } 134 135 public void 136 setIgnoreTruncation(boolean flag) { 137 this.ignoreTruncation = flag; 138 } 139 140 public void 141 setEDNS(int level, int payloadSize, int flags, List options) { 142 if (level != 0 && level != -1) 143 throw new IllegalArgumentException("invalid EDNS level - " + 144 "must be 0 or -1"); 145 if (payloadSize == 0) 146 payloadSize = DEFAULT_EDNS_PAYLOADSIZE; 147 queryOPT = new OPTRecord(payloadSize, 0, level, flags, options); 148 } 149 150 public void 151 setEDNS(int level) { 152 setEDNS(level, 0, 0, null); 153 } 154 155 public void 156 setTSIGKey(TSIG key) { 157 tsig = key; 158 } 159 160 TSIG 161 getTSIGKey() { 162 return tsig; 163 } 164 165 public void 166 setTimeout(int secs, int msecs) { 167 timeoutValue = (long)secs * 1000 + msecs; 168 } 169 170 public void 171 setTimeout(int secs) { 172 setTimeout(secs, 0); 173 } 174 175 long 176 getTimeout() { 177 return timeoutValue; 178 } 179 180 private Message 181 parseMessage(byte [] b) throws WireParseException { 182 try { 183 return (new Message(b)); 184 } 185 catch (IOException e) { 186 if (Options.check("verbose")) 187 e.printStackTrace(); 188 if (!(e instanceof WireParseException)) 189 e = new WireParseException("Error parsing message"); 190 throw (WireParseException) e; 191 } 192 } 193 194 private void 195 verifyTSIG(Message query, Message response, byte [] b, TSIG tsig) { 196 if (tsig == null) 197 return; 198 int error = tsig.verify(response, b, query.getTSIG()); 199 if (Options.check("verbose")) 200 System.err.println("TSIG verify: " + Rcode.TSIGstring(error)); 201 } 202 203 private void 204 applyEDNS(Message query) { 205 if (queryOPT == null || query.getOPT() != null) 206 return; 207 query.addRecord(queryOPT, Section.ADDITIONAL); 208 } 209 210 private int 211 maxUDPSize(Message query) { 212 OPTRecord opt = query.getOPT(); 213 if (opt == null) 214 return DEFAULT_UDPSIZE; 215 else 216 return opt.getPayloadSize(); 217 } 218 219 /** 220 * Sends a message to a single server and waits for a response. No checking 221 * is done to ensure that the response is associated with the query. 222 * @param query The query to send. 223 * @return The response. 224 * @throws IOException An error occurred while sending or receiving. 225 */ 226 public Message 227 send(Message query) throws IOException { 228 if (Options.check("verbose")) 229 System.err.println("Sending to " + 230 address.getAddress().getHostAddress() + 231 ":" + address.getPort()); 232 233 if (query.getHeader().getOpcode() == Opcode.QUERY) { 234 Record question = query.getQuestion(); 235 if (question != null && question.getType() == Type.AXFR) 236 return sendAXFR(query); 237 } 238 239 query = (Message) query.clone(); 240 applyEDNS(query); 241 if (tsig != null) 242 tsig.apply(query, null); 243 244 byte [] out = query.toWire(Message.MAXLENGTH); 245 int udpSize = maxUDPSize(query); 246 boolean tcp = false; 247 long endTime = System.currentTimeMillis() + timeoutValue; 248 do { 249 byte [] in; 250 251 if (useTCP || out.length > udpSize) 252 tcp = true; 253 if (tcp) 254 in = TCPClient.sendrecv(localAddress, address, out, 255 endTime); 256 else 257 in = UDPClient.sendrecv(localAddress, address, out, 258 udpSize, endTime); 259 260 /* 261 * Check that the response is long enough. 262 */ 263 if (in.length < Header.LENGTH) { 264 throw new WireParseException("invalid DNS header - " + 265 "too short"); 266 } 267 /* 268 * Check that the response ID matches the query ID. We want 269 * to check this before actually parsing the message, so that 270 * if there's a malformed response that's not ours, it 271 * doesn't confuse us. 272 */ 273 int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF); 274 int qid = query.getHeader().getID(); 275 if (id != qid) { 276 String error = "invalid message id: expected " + qid + 277 "; got id " + id; 278 if (tcp) { 279 throw new WireParseException(error); 280 } else { 281 if (Options.check("verbose")) { 282 System.err.println(error); 283 } 284 continue; 285 } 286 } 287 Message response = parseMessage(in); 288 verifyTSIG(query, response, in, tsig); 289 if (!tcp && !ignoreTruncation && 290 response.getHeader().getFlag(Flags.TC)) 291 { 292 tcp = true; 293 continue; 294 } 295 return response; 296 } while (true); 297 } 298 299 /** 300 * Asynchronously sends a message to a single server, registering a listener 301 * to receive a callback on success or exception. Multiple asynchronous 302 * lookups can be performed in parallel. Since the callback may be invoked 303 * before the function returns, external synchronization is necessary. 304 * @param query The query to send 305 * @param listener The object containing the callbacks. 306 * @return An identifier, which is also a parameter in the callback 307 */ 308 public Object 309 sendAsync(final Message query, final ResolverListener listener) { 310 final Object id; 311 synchronized (this) { 312 id = new Integer(uniqueID++); 313 } 314 Record question = query.getQuestion(); 315 String qname; 316 if (question != null) 317 qname = question.getName().toString(); 318 else 319 qname = "(none)"; 320 String name = this.getClass() + ": " + qname; 321 Thread thread = new ResolveThread(this, query, id, listener); 322 thread.setName(name); 323 thread.setDaemon(true); 324 thread.start(); 325 return id; 326 } 327 328 private Message 329 sendAXFR(Message query) throws IOException { 330 Name qname = query.getQuestion().getName(); 331 ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address, tsig); 332 xfrin.setTimeout((int)(getTimeout() / 1000)); 333 xfrin.setLocalAddress(localAddress); 334 try { 335 xfrin.run(); 336 } 337 catch (ZoneTransferException e) { 338 throw new WireParseException(e.getMessage()); 339 } 340 List records = xfrin.getAXFR(); 341 Message response = new Message(query.getHeader().getID()); 342 response.getHeader().setFlag(Flags.AA); 343 response.getHeader().setFlag(Flags.QR); 344 response.addRecord(query.getQuestion(), Section.QUESTION); 345 Iterator it = records.iterator(); 346 while (it.hasNext()) 347 response.addRecord((Record)it.next(), Section.ANSWER); 348 return response; 349 } 350 351 } 352