/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.protocols.dns; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; /** * <PRE> * * The DNSAddressRequest holds a DNS request to lookup the IP address of a host - * provides for transmitting and receiving the response for this lookup. * * NOTES: A DNS request and response has the following fileds header questions * answers authorities additional information * * The header has the following format: id - unique id sent by the client and * returned by the server in its response 16 bits of flags - * Query(0)/response(1) flag opcode - that has type of query AA - set if the * response is an authoritative answer TC - set if response is truncated RD - * set if recursion is desired RA - set if recursion is available Z - reserved * bits RCODE - response code * * This class checks only for the received response to have the answer(which * will hold the IP address) - ignores the authorities and additional info * * </PRE> * * @author <A HREF="mailto:sowmya@opennms.org">Sowmya </A> * @author <A HREF="http://www.opennms.org/">OpenNMS </A> * @author <A HREF="mailto:sowmya@opennms.org">Sowmya </A> * @author <A HREF="http://www.opennms.org/">OpenNMS </A> * @version $Id: $ */ public class DNSAddressRequest { /** * <P> * Defines the class internet in the domain name system. * </P> */ public final int CLASS_IN = 1; // internet /** * <P> * Defines the address type. * </P> */ public final int TYPE_ADDR = 1; // address /** * <P> * The offset of the query bit in the header. * </P> */ public final int SHIFT_QUERY = 15; /** * <P> * The offset of the opcode bits in the header. * </P> */ public final int SHIFT_OPCODE = 11; /** * <P> * The offset of the authoritative bit in the header. * </P> */ public final int SHIFT_AUTHORITATIVE = 10; /** * <P> * The offset of the truncated bit in the header. * </P> */ public final int SHIFT_TRUNCATED = 9; /** * <P> * The offset of the recurse req bit in the header. * </P> */ public final int SHIFT_RECURSE_PLEASE = 8; /** * <P> * The offset of the requrse avail bit in the header. * </P> */ public final int SHIFT_RECURSE_AVAILABLE = 7; /** * <P> * The offset of the reserved bits in the header. * </P> */ public final int SHIFT_RESERVED = 4; /** * <P> * The offset of the response code bits in the header. * </P> */ public final int SHIFT_RESPONSE_CODE = 0; /** * <P> * The op code for a query in the header. * </P> */ public final int OPCODE_QUERY = 0; /** * <P> * The host to request information from. This would be the nameserver if it * supports DNS. * </P> */ public String m_reqHost; /** * <P> * The id used to seralize the request. This allows the client (us) and the * server (host) to match exchanges. * </P> */ public int m_reqID; /** * <P> * True if the answer is authoratitve. * </P> */ public boolean m_authoritative; /** * <P> * True if the message is truncated. * </P> */ public boolean m_truncated; /** * <P> * True if the message is recursive. */ public boolean m_recursive; /** * <P> * The list of answers. * </P> */ public List<DNSAddressRR> m_answers; /** * <P> * The global id, used to get the request id. * </P> */ private static int globalID = 1; /** * <P> * The list of response codes to be considered fatal * </P> */ private List<Integer> m_fatalResponseCodes; /** * <P> * Decodes the integer to get the flags - refer header for more info on the * flags. * </P> */ private void decodeFlags(int flags) throws IOException { // // check the response flag // boolean isResponse = ((flags >> SHIFT_QUERY) & 1) != 0; if (!isResponse) throw new IOException("Response flag not set"); // // check if error free // int code = (flags >> SHIFT_RESPONSE_CODE) & 15; if (isResponseCodeFatal(code)) { throw new IOException(codeName(code) + " (" + code + ")"); } // // set the members of the instance. // m_authoritative = ((flags >> SHIFT_AUTHORITATIVE) & 1) != 0; m_truncated = ((flags >> SHIFT_TRUNCATED) & 1) != 0; m_recursive = ((flags >> SHIFT_RECURSE_AVAILABLE) & 1) != 0; } /** * <P> * Constructs a DNSAddressRequest for the hostname passed. The host string * that is passed to the address string should be a hostname in "x.y.z" * where x, y, and z are strings. This is not suppose to be a dotted decimal * address. * </P> * * @param host * hostname for which address is to be constructed */ public DNSAddressRequest(String host) { // // Imitate the original behavior of only a ServFail being fatal // m_fatalResponseCodes = new ArrayList<Integer>(); m_fatalResponseCodes.add(2); // // Split the host into its component // parts. // StringTokenizer labels = new StringTokenizer(host, "."); while (labels.hasMoreTokens()) { // // if any section is longer than // 63 characters then it's illegal // if (labels.nextToken().length() > 63) throw new IllegalArgumentException("Invalid hostname: " + host); } // // The requested host // m_reqHost = host; // // Synchronize on the class, not // the instance. // synchronized (getClass()) { m_reqID = globalID % 65536; globalID = m_reqID + 1; // prevents negative numbers. } m_answers = new ArrayList<DNSAddressRR>(); } /** * <P> * Builds the address request. * </P> * * @return A byte array containing the request. * @throws java.io.IOException if any. */ public byte[] buildRequest() throws IOException { ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); DataOutputStream dataOut = new DataOutputStream(byteArrayOut); dataOut.writeShort(m_reqID); dataOut.writeShort((0 << SHIFT_QUERY) | (OPCODE_QUERY << SHIFT_OPCODE) | (1 << SHIFT_RECURSE_PLEASE)); dataOut.writeShort(1); // # queries dataOut.writeShort(0); // # answers dataOut.writeShort(0); // # authorities dataOut.writeShort(0); // # additional StringTokenizer labels = new StringTokenizer(m_reqHost, "."); while (labels.hasMoreTokens()) { String label = labels.nextToken(); dataOut.writeByte(label.length()); dataOut.writeBytes(label); } dataOut.writeByte(0); dataOut.writeShort(TYPE_ADDR); dataOut.writeShort(CLASS_IN); return byteArrayOut.toByteArray(); } /** * <P> * Extracts the response from the bytearray. * </P> * * @param data * The byte array containing the response. * @param length * The length of the byte array. * @exception IOException * Thrown if there is an error while reading the received * packet * @throws java.io.IOException if any. */ public void receiveResponse(byte[] data, int length) throws IOException { /* * Decode the input stream. */ DNSInputStream dnsIn = new DNSInputStream(data, 0, length); int id = dnsIn.readShort(); if (id != m_reqID) throw new IOException("ID does not match request"); // // read in the flags // int flags = dnsIn.readShort(); decodeFlags(flags); int numQueries = dnsIn.readShort(); int numAnswers = dnsIn.readShort(); @SuppressWarnings("unused") int numAuthorities = dnsIn.readShort(); @SuppressWarnings("unused") int numAdditional = dnsIn.readShort(); while (numQueries-- > 0) { // // discard questions // @SuppressWarnings("unused") String rname = dnsIn.readDomainName(); @SuppressWarnings("unused") int rtype = dnsIn.readShort(); @SuppressWarnings("unused") int rclass = dnsIn.readShort(); } try { while (numAnswers-- > 0) m_answers.add(dnsIn.readRR()); // ignore the authorities and additional information /** * while (numAuthorities -- > 0) dnsIn.readRR (); while * (numAdditional -- > 0) dnsIn.readRR (); */ } catch (IOException ex) { if (!m_truncated) throw ex; } } /** * <P> * This method only goes so far as to decode the flags in the response byte * array to verify that a DNS server sent the response. * </P> * * <P> * NOTE: This is really a hack to get around the fact that the * receiveResponse() method is not robust enough to handle all possible DNS * server responses. * * @param data * The byte array containing the response. * @param length * The length of the byte array. * @exception IOException * Thrown if there is an error while reading the received * packet * @throws java.io.IOException if any. */ public void verifyResponse(byte[] data, int length) throws IOException { /* * Decode the input stream. */ DNSInputStream dnsIn = new DNSInputStream(data, 0, length); int id = dnsIn.readShort(); if (id != m_reqID) throw new IOException("ID in received packet (" + id + ") does not match ID from request (" + m_reqID + ")"); // // read in the flags // int flags = dnsIn.readShort(); decodeFlags(flags); } /** * <P> * Return an enumeration of the received answers. * </P> * * @return The list of received answers. */ public List<DNSAddressRR> getAnswers() { return m_answers; } /** * <P> * The request id for this particular instance. * </P> * * @return a int. */ public int getRequestID() { return m_reqID; } /** * <P> * The hostname that will be request from the DNS box. * </P> * * @return a {@link java.lang.String} object. */ public String getHost() { return m_reqHost; } /** * <P> * Returns true if the answer is truncated. * </P> * * @return a boolean. */ public boolean isTruncated() { return m_truncated; } /** * <P> * Returns true if the answer is recursive. * </P> * * @return a boolean. */ public boolean isRecursive() { return m_recursive; } /** * <P> * Returns true if the answer is authoritative. * </P> * * @return a boolean. */ public boolean isAuthoritative() { return m_authoritative; } /** * <P> * Returns the code string for the error code received. * </P> * * @param code * The error code. * @return The error string corresponding to the error code */ public static String codeName(int code) { String[] codeNames = { "Format Error", "Server Failure", "Non-Existent Domain", "Not Implemented", "Query Refused" }; return ((code >= 1) && (code <= 5)) ? codeNames[code - 1] : "Unknown error"; } /** * <p>getFatalResponseCodes</p> * * @return a {@link java.util.List} object. */ public List<Integer> getFatalResponseCodes() { return m_fatalResponseCodes; } /** * <p>setFatalResponseCodes</p> * * @param codes a {@link java.util.List} object. */ public void setFatalResponseCodes(List<Integer> codes) { m_fatalResponseCodes = codes; } private boolean isResponseCodeFatal(int code) { if (m_fatalResponseCodes.contains(code)) return true; return false; } }