1// 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 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 21public class SimpleResolver implements Resolver { 22 23/** The default port to send queries to */ 24public static final int DEFAULT_PORT = 53; 25 26/** The default EDNS payload size */ 27public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280; 28 29private InetSocketAddress address; 30private InetSocketAddress localAddress; 31private boolean useTCP, ignoreTruncation; 32private OPTRecord queryOPT; 33private TSIG tsig; 34private long timeoutValue = 10 * 1000; 35 36private static final short DEFAULT_UDPSIZE = 512; 37 38private static String defaultResolver = "localhost"; 39private 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 */ 45public 46SimpleResolver(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 */ 66public 67SimpleResolver() 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 */ 76InetSocketAddress 77getAddress() { 78 return address; 79} 80 81/** Sets the default host (initially localhost) to query */ 82public static void 83setDefaultResolver(String hostname) { 84 defaultResolver = hostname; 85} 86 87public void 88setPort(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 */ 96public void 97setAddress(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 */ 106public void 107setAddress(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 */ 115public void 116setLocalAddress(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 */ 125public void 126setLocalAddress(InetAddress addr) { 127 localAddress = new InetSocketAddress(addr, 0); 128} 129 130public void 131setTCP(boolean flag) { 132 this.useTCP = flag; 133} 134 135public void 136setIgnoreTruncation(boolean flag) { 137 this.ignoreTruncation = flag; 138} 139 140public void 141setEDNS(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 150public void 151setEDNS(int level) { 152 setEDNS(level, 0, 0, null); 153} 154 155public void 156setTSIGKey(TSIG key) { 157 tsig = key; 158} 159 160TSIG 161getTSIGKey() { 162 return tsig; 163} 164 165public void 166setTimeout(int secs, int msecs) { 167 timeoutValue = (long)secs * 1000 + msecs; 168} 169 170public void 171setTimeout(int secs) { 172 setTimeout(secs, 0); 173} 174 175long 176getTimeout() { 177 return timeoutValue; 178} 179 180private Message 181parseMessage(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 194private void 195verifyTSIG(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 203private void 204applyEDNS(Message query) { 205 if (queryOPT == null || query.getOPT() != null) 206 return; 207 query.addRecord(queryOPT, Section.ADDITIONAL); 208} 209 210private int 211maxUDPSize(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 */ 226public Message 227send(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 */ 308public Object 309sendAsync(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 328private Message 329sendAXFR(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