• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@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
SimpleResolver(String hostname)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
SimpleResolver()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
getAddress()77 getAddress() {
78 	return address;
79 }
80 
81 /** Sets the default host (initially localhost) to query */
82 public static void
setDefaultResolver(String hostname)83 setDefaultResolver(String hostname) {
84 	defaultResolver = hostname;
85 }
86 
87 public void
setPort(int port)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
setAddress(InetSocketAddress addr)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
setAddress(InetAddress addr)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
setLocalAddress(InetSocketAddress addr)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
setLocalAddress(InetAddress addr)126 setLocalAddress(InetAddress addr) {
127 	localAddress = new InetSocketAddress(addr, 0);
128 }
129 
130 public void
setTCP(boolean flag)131 setTCP(boolean flag) {
132 	this.useTCP = flag;
133 }
134 
135 public void
setIgnoreTruncation(boolean flag)136 setIgnoreTruncation(boolean flag) {
137 	this.ignoreTruncation = flag;
138 }
139 
140 public void
setEDNS(int level, int payloadSize, int flags, List options)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
setEDNS(int level)151 setEDNS(int level) {
152 	setEDNS(level, 0, 0, null);
153 }
154 
155 public void
setTSIGKey(TSIG key)156 setTSIGKey(TSIG key) {
157 	tsig = key;
158 }
159 
160 TSIG
getTSIGKey()161 getTSIGKey() {
162 	return tsig;
163 }
164 
165 public void
setTimeout(int secs, int msecs)166 setTimeout(int secs, int msecs) {
167 	timeoutValue = (long)secs * 1000 + msecs;
168 }
169 
170 public void
setTimeout(int secs)171 setTimeout(int secs) {
172 	setTimeout(secs, 0);
173 }
174 
175 long
getTimeout()176 getTimeout() {
177 	return timeoutValue;
178 }
179 
180 private Message
parseMessage(byte [] b)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
verifyTSIG(Message query, Message response, byte [] b, TSIG tsig)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
applyEDNS(Message query)204 applyEDNS(Message query) {
205 	if (queryOPT == null || query.getOPT() != null)
206 		return;
207 	query.addRecord(queryOPT, Section.ADDITIONAL);
208 }
209 
210 private int
maxUDPSize(Message query)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
send(Message query)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
sendAsync(final Message query, final ResolverListener listener)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
sendAXFR(Message query)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