Home | History | Annotate | Download | only in DNS
      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