001: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.util.*;
006: import java.io.*;
007: import java.net.*;
008:
009: /**
010: * An implementation of Resolver that sends one query to one server.
011: * SimpleResolver handles TCP retries, transaction security (TSIG), and
012: * EDNS 0.
013: * @see Resolver
014: * @see TSIG
015: * @see OPTRecord
016: *
017: * @author Brian Wellington
018: */
019:
020: public class SimpleResolver implements Resolver {
021:
022: /** The default port to send queries to */
023: public static final int DEFAULT_PORT = 53;
024:
025: /** The default EDNS payload size */
026: public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280;
027:
028: private InetSocketAddress address;
029: private InetSocketAddress localAddress;
030: private boolean useTCP, ignoreTruncation;
031: private OPTRecord queryOPT;
032: private TSIG tsig;
033: private long timeoutValue = 10 * 1000;
034:
035: private static final short DEFAULT_UDPSIZE = 512;
036:
037: private static String defaultResolver = "localhost";
038: private static int uniqueID = 0;
039:
040: /**
041: * Creates a SimpleResolver that will query the specified host
042: * @exception UnknownHostException Failure occurred while finding the host
043: */
044: public SimpleResolver(String hostname) throws UnknownHostException {
045: if (hostname == null) {
046: hostname = ResolverConfig.getCurrentConfig().server();
047: if (hostname == null)
048: hostname = defaultResolver;
049: }
050: InetAddress addr;
051: if (hostname.equals("0"))
052: addr = InetAddress.getLocalHost();
053: else
054: addr = InetAddress.getByName(hostname);
055: address = new InetSocketAddress(addr, DEFAULT_PORT);
056: }
057:
058: /**
059: * Creates a SimpleResolver. The host to query is either found by using
060: * ResolverConfig, or the default host is used.
061: * @see ResolverConfig
062: * @exception UnknownHostException Failure occurred while finding the host
063: */
064: public SimpleResolver() throws UnknownHostException {
065: this (null);
066: }
067:
068: InetSocketAddress getAddress() {
069: return address;
070: }
071:
072: /** Sets the default host (initially localhost) to query */
073: public static void setDefaultResolver(String hostname) {
074: defaultResolver = hostname;
075: }
076:
077: public void setPort(int port) {
078: address = new InetSocketAddress(address.getAddress(), port);
079: }
080:
081: /**
082: * Sets the address of the server to communicate with.
083: * @param addr The address of the DNS server
084: */
085: public void setAddress(InetSocketAddress addr) {
086: address = addr;
087: }
088:
089: /**
090: * Sets the address of the server to communicate with (on the default
091: * DNS port)
092: * @param addr The address of the DNS server
093: */
094: public void setAddress(InetAddress addr) {
095: address = new InetSocketAddress(addr, address.getPort());
096: }
097:
098: /**
099: * Sets the local address to bind to when sending messages.
100: * @param addr The local address to send messages from.
101: */
102: public void setLocalAddress(InetSocketAddress addr) {
103: localAddress = addr;
104: }
105:
106: /**
107: * Sets the local address to bind to when sending messages. A random port
108: * will be used.
109: * @param addr The local address to send messages from.
110: */
111: public void setLocalAddress(InetAddress addr) {
112: localAddress = new InetSocketAddress(addr, 0);
113: }
114:
115: public void setTCP(boolean flag) {
116: this .useTCP = flag;
117: }
118:
119: public void setIgnoreTruncation(boolean flag) {
120: this .ignoreTruncation = flag;
121: }
122:
123: public void setEDNS(int level, int payloadSize, int flags,
124: List options) {
125: if (level != 0 && level != -1)
126: throw new IllegalArgumentException("invalid EDNS level - "
127: + "must be 0 or -1");
128: if (payloadSize == 0)
129: payloadSize = DEFAULT_EDNS_PAYLOADSIZE;
130: queryOPT = new OPTRecord(payloadSize, 0, level, flags, options);
131: }
132:
133: public void setEDNS(int level) {
134: setEDNS(level, 0, 0, null);
135: }
136:
137: public void setTSIGKey(TSIG key) {
138: tsig = key;
139: }
140:
141: TSIG getTSIGKey() {
142: return tsig;
143: }
144:
145: public void setTimeout(int secs, int msecs) {
146: timeoutValue = (long) secs * 1000 + msecs;
147: }
148:
149: public void setTimeout(int secs) {
150: setTimeout(secs, 0);
151: }
152:
153: long getTimeout() {
154: return timeoutValue;
155: }
156:
157: private Message parseMessage(byte[] b) throws WireParseException {
158: try {
159: return (new Message(b));
160: } catch (IOException e) {
161: if (Options.check("verbose"))
162: e.printStackTrace();
163: if (!(e instanceof WireParseException))
164: e = new WireParseException("Error parsing message");
165: throw (WireParseException) e;
166: }
167: }
168:
169: private void verifyTSIG(Message query, Message response, byte[] b,
170: TSIG tsig) {
171: if (tsig == null)
172: return;
173: int error = tsig.verify(response, b, query.getTSIG());
174: if (error == Rcode.NOERROR)
175: response.tsigState = Message.TSIG_VERIFIED;
176: else
177: response.tsigState = Message.TSIG_FAILED;
178: if (Options.check("verbose"))
179: System.err.println("TSIG verify: " + Rcode.string(error));
180: }
181:
182: private void applyEDNS(Message query) {
183: if (queryOPT == null || query.getOPT() != null)
184: return;
185: query.addRecord(queryOPT, Section.ADDITIONAL);
186: }
187:
188: private int maxUDPSize(Message query) {
189: OPTRecord opt = query.getOPT();
190: if (opt == null)
191: return DEFAULT_UDPSIZE;
192: else
193: return opt.getPayloadSize();
194: }
195:
196: /**
197: * Sends a message to a single server and waits for a response. No checking
198: * is done to ensure that the response is associated with the query.
199: * @param query The query to send.
200: * @return The response.
201: * @throws IOException An error occurred while sending or receiving.
202: */
203: public Message send(Message query) throws IOException {
204: if (Options.check("verbose"))
205: System.err.println("Sending to "
206: + address.getAddress().getHostAddress() + ":"
207: + address.getPort());
208:
209: if (query.getHeader().getOpcode() == Opcode.QUERY) {
210: Record question = query.getQuestion();
211: if (question != null && question.getType() == Type.AXFR)
212: return sendAXFR(query);
213: }
214:
215: query = (Message) query.clone();
216: applyEDNS(query);
217: if (tsig != null)
218: tsig.apply(query, null);
219:
220: byte[] out = query.toWire(Message.MAXLENGTH);
221: int udpSize = maxUDPSize(query);
222: boolean tcp = false;
223: long endTime = System.currentTimeMillis() + timeoutValue;
224: do {
225: byte[] in;
226:
227: if (useTCP || out.length > udpSize)
228: tcp = true;
229: if (tcp)
230: in = TCPClient.sendrecv(localAddress, address, out,
231: endTime);
232: else
233: in = UDPClient.sendrecv(localAddress, address, out,
234: udpSize, endTime);
235:
236: /*
237: * Check that the response is long enough.
238: */
239: if (in.length < Header.LENGTH) {
240: throw new WireParseException("invalid DNS header - "
241: + "too short");
242: }
243: /*
244: * Check that the response ID matches the query ID. We want
245: * to check this before actually parsing the message, so that
246: * if there's a malformed response that's not ours, it
247: * doesn't confuse us.
248: */
249: int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF);
250: int qid = query.getHeader().getID();
251: if (id != qid) {
252: String error = "invalid message id: expected " + qid
253: + "; got id " + id;
254: if (tcp) {
255: throw new WireParseException(error);
256: } else {
257: if (Options.check("verbose")) {
258: System.err.println(error);
259: }
260: continue;
261: }
262: }
263: Message response = parseMessage(in);
264: verifyTSIG(query, response, in, tsig);
265: if (!tcp && !ignoreTruncation
266: && response.getHeader().getFlag(Flags.TC)) {
267: tcp = true;
268: continue;
269: }
270: return response;
271: } while (true);
272: }
273:
274: /**
275: * Asynchronously sends a message to a single server, registering a listener
276: * to receive a callback on success or exception. Multiple asynchronous
277: * lookups can be performed in parallel. Since the callback may be invoked
278: * before the function returns, external synchronization is necessary.
279: * @param query The query to send
280: * @param listener The object containing the callbacks.
281: * @return An identifier, which is also a parameter in the callback
282: */
283: public Object sendAsync(final Message query,
284: final ResolverListener listener) {
285: final Object id;
286: synchronized (this ) {
287: id = new Integer(uniqueID++);
288: }
289: Record question = query.getQuestion();
290: String qname;
291: if (question != null)
292: qname = question.getName().toString();
293: else
294: qname = "(none)";
295: String name = this .getClass() + ": " + qname;
296: Thread thread = new ResolveThread(this , query, id, listener);
297: thread.setName(name);
298: thread.setDaemon(true);
299: thread.start();
300: return id;
301: }
302:
303: private Message sendAXFR(Message query) throws IOException {
304: Name qname = query.getQuestion().getName();
305: ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address,
306: tsig);
307: xfrin.setTimeout((int) (getTimeout() / 1000));
308: xfrin.setLocalAddress(localAddress);
309: try {
310: xfrin.run();
311: } catch (ZoneTransferException e) {
312: throw new WireParseException(e.getMessage());
313: }
314: List records = xfrin.getAXFR();
315: Message response = new Message(query.getHeader().getID());
316: response.getHeader().setFlag(Flags.AA);
317: response.getHeader().setFlag(Flags.QR);
318: response.addRecord(query.getQuestion(), Section.QUESTION);
319: Iterator it = records.iterator();
320: while (it.hasNext())
321: response.addRecord((Record) it.next(), Section.ANSWER);
322: return response;
323: }
324:
325: }
|