blob: 74361335451756962ea7460028eba8e85fc2dfd5 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001// 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}