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