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