diff options
Diffstat (limited to 'src/org/xbill/DNS/SimpleResolver.java')
-rw-r--r-- | src/org/xbill/DNS/SimpleResolver.java | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/SimpleResolver.java b/src/org/xbill/DNS/SimpleResolver.java new file mode 100644 index 0000000..7436133 --- /dev/null +++ b/src/org/xbill/DNS/SimpleResolver.java @@ -0,0 +1,351 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * An implementation of Resolver that sends one query to one server. + * SimpleResolver handles TCP retries, transaction security (TSIG), and + * EDNS 0. + * @see Resolver + * @see TSIG + * @see OPTRecord + * + * @author Brian Wellington + */ + + +public class SimpleResolver implements Resolver { + +/** The default port to send queries to */ +public static final int DEFAULT_PORT = 53; + +/** The default EDNS payload size */ +public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280; + +private InetSocketAddress address; +private InetSocketAddress localAddress; +private boolean useTCP, ignoreTruncation; +private OPTRecord queryOPT; +private TSIG tsig; +private long timeoutValue = 10 * 1000; + +private static final short DEFAULT_UDPSIZE = 512; + +private static String defaultResolver = "localhost"; +private static int uniqueID = 0; + +/** + * Creates a SimpleResolver that will query the specified host + * @exception UnknownHostException Failure occurred while finding the host + */ +public +SimpleResolver(String hostname) throws UnknownHostException { + if (hostname == null) { + hostname = ResolverConfig.getCurrentConfig().server(); + if (hostname == null) + hostname = defaultResolver; + } + InetAddress addr; + if (hostname.equals("0")) + addr = InetAddress.getLocalHost(); + else + addr = InetAddress.getByName(hostname); + address = new InetSocketAddress(addr, DEFAULT_PORT); +} + +/** + * Creates a SimpleResolver. The host to query is either found by using + * ResolverConfig, or the default host is used. + * @see ResolverConfig + * @exception UnknownHostException Failure occurred while finding the host + */ +public +SimpleResolver() throws UnknownHostException { + this(null); +} + +/** + * Gets the destination address associated with this SimpleResolver. + * Messages sent using this SimpleResolver will be sent to this address. + * @return The destination address associated with this SimpleResolver. + */ +InetSocketAddress +getAddress() { + return address; +} + +/** Sets the default host (initially localhost) to query */ +public static void +setDefaultResolver(String hostname) { + defaultResolver = hostname; +} + +public void +setPort(int port) { + address = new InetSocketAddress(address.getAddress(), port); +} + +/** + * Sets the address of the server to communicate with. + * @param addr The address of the DNS server + */ +public void +setAddress(InetSocketAddress addr) { + address = addr; +} + +/** + * Sets the address of the server to communicate with (on the default + * DNS port) + * @param addr The address of the DNS server + */ +public void +setAddress(InetAddress addr) { + address = new InetSocketAddress(addr, address.getPort()); +} + +/** + * Sets the local address to bind to when sending messages. + * @param addr The local address to send messages from. + */ +public void +setLocalAddress(InetSocketAddress addr) { + localAddress = addr; +} + +/** + * Sets the local address to bind to when sending messages. A random port + * will be used. + * @param addr The local address to send messages from. + */ +public void +setLocalAddress(InetAddress addr) { + localAddress = new InetSocketAddress(addr, 0); +} + +public void +setTCP(boolean flag) { + this.useTCP = flag; +} + +public void +setIgnoreTruncation(boolean flag) { + this.ignoreTruncation = flag; +} + +public void +setEDNS(int level, int payloadSize, int flags, List options) { + if (level != 0 && level != -1) + throw new IllegalArgumentException("invalid EDNS level - " + + "must be 0 or -1"); + if (payloadSize == 0) + payloadSize = DEFAULT_EDNS_PAYLOADSIZE; + queryOPT = new OPTRecord(payloadSize, 0, level, flags, options); +} + +public void +setEDNS(int level) { + setEDNS(level, 0, 0, null); +} + +public void +setTSIGKey(TSIG key) { + tsig = key; +} + +TSIG +getTSIGKey() { + return tsig; +} + +public void +setTimeout(int secs, int msecs) { + timeoutValue = (long)secs * 1000 + msecs; +} + +public void +setTimeout(int secs) { + setTimeout(secs, 0); +} + +long +getTimeout() { + return timeoutValue; +} + +private Message +parseMessage(byte [] b) throws WireParseException { + try { + return (new Message(b)); + } + catch (IOException e) { + if (Options.check("verbose")) + e.printStackTrace(); + if (!(e instanceof WireParseException)) + e = new WireParseException("Error parsing message"); + throw (WireParseException) e; + } +} + +private void +verifyTSIG(Message query, Message response, byte [] b, TSIG tsig) { + if (tsig == null) + return; + int error = tsig.verify(response, b, query.getTSIG()); + if (Options.check("verbose")) + System.err.println("TSIG verify: " + Rcode.TSIGstring(error)); +} + +private void +applyEDNS(Message query) { + if (queryOPT == null || query.getOPT() != null) + return; + query.addRecord(queryOPT, Section.ADDITIONAL); +} + +private int +maxUDPSize(Message query) { + OPTRecord opt = query.getOPT(); + if (opt == null) + return DEFAULT_UDPSIZE; + else + return opt.getPayloadSize(); +} + +/** + * Sends a message to a single server and waits for a response. No checking + * is done to ensure that the response is associated with the query. + * @param query The query to send. + * @return The response. + * @throws IOException An error occurred while sending or receiving. + */ +public Message +send(Message query) throws IOException { + if (Options.check("verbose")) + System.err.println("Sending to " + + address.getAddress().getHostAddress() + + ":" + address.getPort()); + + if (query.getHeader().getOpcode() == Opcode.QUERY) { + Record question = query.getQuestion(); + if (question != null && question.getType() == Type.AXFR) + return sendAXFR(query); + } + + query = (Message) query.clone(); + applyEDNS(query); + if (tsig != null) + tsig.apply(query, null); + + byte [] out = query.toWire(Message.MAXLENGTH); + int udpSize = maxUDPSize(query); + boolean tcp = false; + long endTime = System.currentTimeMillis() + timeoutValue; + do { + byte [] in; + + if (useTCP || out.length > udpSize) + tcp = true; + if (tcp) + in = TCPClient.sendrecv(localAddress, address, out, + endTime); + else + in = UDPClient.sendrecv(localAddress, address, out, + udpSize, endTime); + + /* + * Check that the response is long enough. + */ + if (in.length < Header.LENGTH) { + throw new WireParseException("invalid DNS header - " + + "too short"); + } + /* + * Check that the response ID matches the query ID. We want + * to check this before actually parsing the message, so that + * if there's a malformed response that's not ours, it + * doesn't confuse us. + */ + int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF); + int qid = query.getHeader().getID(); + if (id != qid) { + String error = "invalid message id: expected " + qid + + "; got id " + id; + if (tcp) { + throw new WireParseException(error); + } else { + if (Options.check("verbose")) { + System.err.println(error); + } + continue; + } + } + Message response = parseMessage(in); + verifyTSIG(query, response, in, tsig); + if (!tcp && !ignoreTruncation && + response.getHeader().getFlag(Flags.TC)) + { + tcp = true; + continue; + } + return response; + } while (true); +} + +/** + * Asynchronously sends a message to a single server, registering a listener + * to receive a callback on success or exception. Multiple asynchronous + * lookups can be performed in parallel. Since the callback may be invoked + * before the function returns, external synchronization is necessary. + * @param query The query to send + * @param listener The object containing the callbacks. + * @return An identifier, which is also a parameter in the callback + */ +public Object +sendAsync(final Message query, final ResolverListener listener) { + final Object id; + synchronized (this) { + id = new Integer(uniqueID++); + } + Record question = query.getQuestion(); + String qname; + if (question != null) + qname = question.getName().toString(); + else + qname = "(none)"; + String name = this.getClass() + ": " + qname; + Thread thread = new ResolveThread(this, query, id, listener); + thread.setName(name); + thread.setDaemon(true); + thread.start(); + return id; +} + +private Message +sendAXFR(Message query) throws IOException { + Name qname = query.getQuestion().getName(); + ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address, tsig); + xfrin.setTimeout((int)(getTimeout() / 1000)); + xfrin.setLocalAddress(localAddress); + try { + xfrin.run(); + } + catch (ZoneTransferException e) { + throw new WireParseException(e.getMessage()); + } + List records = xfrin.getAXFR(); + Message response = new Message(query.getHeader().getID()); + response.getHeader().setFlag(Flags.AA); + response.getHeader().setFlag(Flags.QR); + response.addRecord(query.getQuestion(), Section.QUESTION); + Iterator it = records.iterator(); + while (it.hasNext()) + response.addRecord((Record)it.next(), Section.ANSWER); + return response; +} + +} |