Browse Source

merge trac #311 (EDNS class)

git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@3020 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
0caf682710

+ 1 - 0
configure.ac

@@ -473,6 +473,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/config/testdata/Makefile
                  src/lib/dns/Makefile
                  src/lib/dns/tests/Makefile
+                 src/lib/dns/tests/testdata/Makefile
                  src/lib/dns/python/Makefile
                  src/lib/dns/python/tests/Makefile
                  src/lib/exceptions/Makefile

+ 12 - 5
src/bin/auth/auth_srv.cc

@@ -24,6 +24,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/buffer.h>
+#include <dns/edns.h>
 #include <dns/exceptions.h>
 #include <dns/messagerenderer.h>
 #include <dns/name.h>
@@ -164,7 +165,6 @@ makeErrorMessage(Message& message, MessageRenderer& renderer,
     message.setQid(qid);
     message.setOpcode(opcode);
     message.setHeaderFlag(MessageFlag::QR());
-    message.setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
     if (rd) {
         message.setHeaderFlag(MessageFlag::RD());
     }
@@ -302,14 +302,21 @@ bool
 AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
                                 MessageRenderer& response_renderer)
 {
-    const bool dnssec_ok = message.isDNSSECSupported();
-    const uint16_t remote_bufsize = message.getUDPSize();
+    ConstEDNSPtr remote_edns = message.getEDNS();
+    const bool dnssec_ok = remote_edns && remote_edns->getDNSSECAwareness();
+    const uint16_t remote_bufsize = remote_edns ? remote_edns->getUDPSize() :
+        Message::DEFAULT_MAX_UDPSIZE; 
 
     message.makeResponse();
     message.setHeaderFlag(MessageFlag::AA());
     message.setRcode(Rcode::NOERROR());
-    message.setDNSSECSupported(dnssec_ok);
-    message.setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
+
+    if (remote_edns) {
+        EDNSPtr local_edns = EDNSPtr(new EDNS());
+        local_edns->setDNSSECAwareness(dnssec_ok);
+        local_edns->setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
+        message.setEDNS(local_edns);
+    }
 
     try {
         Query query(message, cache_, dnssec_ok);

+ 10 - 3
src/bin/auth/tests/auth_srv_unittest.cc

@@ -445,13 +445,20 @@ TEST_F(AuthSrvTest, ednsBadVers) {
     EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                           response_renderer));
 
-    // The response must have an EDNS OPT RR in the additional section.
+    // The response must have an EDNS OPT RR in the additional section, but
+    // it will be added automatically at the render time.
     // Note that the DNSSEC DO bit is cleared even if this bit in the query
     // is set.  This is a limitation of the current implementation.
     headerCheck(parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(),
                 QR_FLAG, 1, 0, 0, 1);
-    EXPECT_EQ(4096, parse_message.getUDPSize());
-    EXPECT_FALSE(parse_message.isDNSSECSupported());
+    EXPECT_FALSE(parse_message.getEDNS()); // EDNS isn't added at this point
+
+    parse_message.clear(Message::PARSE);
+    InputBuffer ib(response_renderer.getData(), response_renderer.getLength());
+    parse_message.fromWire(ib);
+    EXPECT_EQ(Rcode::BADVERS(), parse_message.getRcode());
+    EXPECT_TRUE(parse_message.getEDNS());
+    EXPECT_FALSE(parse_message.getEDNS()->getDNSSECAwareness());
 }
 
 TEST_F(AuthSrvTest, AXFROverUDP) {

+ 1 - 0
src/lib/dns/Makefile.am

@@ -63,6 +63,7 @@ libdns___la_SOURCES += util/binary_from_base32hex.h
 libdns___la_SOURCES += util/base16_from_binary.h util/binary_from_base16.h
 libdns___la_SOURCES += buffer.h
 libdns___la_SOURCES += dnssectime.h dnssectime.cc
+libdns___la_SOURCES += edns.h edns.cc
 libdns___la_SOURCES += exceptions.h exceptions.cc
 libdns___la_SOURCES += util/hex.h
 libdns___la_SOURCES += message.h message.cc

+ 174 - 0
src/lib/dns/edns.cc

@@ -0,0 +1,174 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <config.h>
+
+#include <stdint.h>
+
+#include <cassert>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+
+namespace {
+// This diagram shows the wire-format representation of the TTL field of
+// OPT RR and its relationship with implementation specific parameters.
+//
+//           0             7               15                              31
+//          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//          | EXTENDED-RCODE|    VERSION     |D|               Z             |
+//          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//                                          <= VERSION_SHIFT (16 bits)
+//                          <= EXTRCODE_SHIFT (24 bits)
+//EXTFLAG_DO:0 0 0 ....................... 0 1 0 0 0 0.....................0
+//VER_MASK:  0 0 0 ........0 1 1 1 1 1 1 1 1 0 0 ..........................0
+
+const unsigned int VERSION_SHIFT = 16;
+const unsigned int EXTRCODE_SHIFT = 24;
+const uint32_t VERSION_MASK = 0x00ff0000;
+const uint32_t EXTFLAG_DO = 0x00008000;
+}
+
+EDNS::EDNS(const uint8_t version) :
+    version_(version),
+    udp_size_(Message::DEFAULT_MAX_UDPSIZE),
+    dnssec_aware_(false)
+{
+    if (version_ > SUPPORTED_VERSION) {
+        isc_throw(isc::InvalidParameter,
+                  "failed to construct EDNS: unsupported version: " <<
+                  static_cast<unsigned int>(version_));
+    }
+}
+
+EDNS::EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+           const RRTTL& ttl, const Rdata& rdata UNUSED_PARAM) :
+    version_((ttl.getValue() & VERSION_MASK) >> VERSION_SHIFT)
+{
+    if (rrtype != RRType::OPT()) {
+        isc_throw(isc::InvalidParameter,
+                  "EDNS is being created with incompatible RR type: "
+                  << rrtype);
+    }
+    
+    if (version_ > EDNS::SUPPORTED_VERSION) {
+        isc_throw(DNSMessageBADVERS, "unsupported EDNS version: " <<
+                  static_cast<unsigned int>(version_));
+    }
+
+    if (name != Name::ROOT_NAME()) {
+        isc_throw(DNSMessageFORMERR, "invalid owner name for EDNS OPT RR: " <<
+                  name);
+    }
+
+    dnssec_aware_ = ((ttl.getValue() & EXTFLAG_DO) != 0);
+    udp_size_ = rrclass.getCode();
+}
+
+string
+EDNS::toText() const {
+    string ret = "; EDNS: version: ";
+
+    ret += lexical_cast<string>(static_cast<int>(getVersion()));
+    ret += ", flags:";
+    if (getDNSSECAwareness()) {
+        ret += " do";
+    }
+    ret += "; udp: " + lexical_cast<string>(getUDPSize()) + "\n";
+
+    return (ret);
+}
+
+template <typename Output>
+int
+EDNS::toWire(Output& output, const uint8_t extended_rcode) const {
+    // Render EDNS OPT RR
+    uint32_t extrcode_flags = extended_rcode << EXTRCODE_SHIFT;
+    extrcode_flags |= (version_ << VERSION_SHIFT) & VERSION_MASK;
+    if (dnssec_aware_) {
+        extrcode_flags |= EXTFLAG_DO;
+    }
+
+    // Construct an RRset corresponding to the EDNS.
+    // We don't support any options for now, so the OPT RR can be empty.
+    RRsetPtr edns_rrset(new RRset(Name::ROOT_NAME(), RRClass(udp_size_),
+                                  RRType::OPT(), RRTTL(extrcode_flags)));
+    edns_rrset->addRdata(ConstRdataPtr(new generic::OPT()));
+
+    edns_rrset->toWire(output);
+
+    return (1);
+}
+
+unsigned int
+EDNS::toWire(MessageRenderer& renderer, const uint8_t extended_rcode) const {
+    // If adding the OPT RR would exceed the size limit, don't do it.
+    // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
+    // (RDATA is empty in this simple implementation)
+    if (renderer.getLength() + 11 > renderer.getLengthLimit()) {
+        return (0);
+    }
+
+    return (toWire<MessageRenderer>(renderer, extended_rcode));
+}
+
+unsigned int
+EDNS::toWire(OutputBuffer& buffer, const uint8_t extended_rcode) const {
+    return (toWire<OutputBuffer>(buffer, extended_rcode));
+}
+
+EDNS*
+createEDNSFromRR(const Name& name, const RRClass& rrclass,
+                 const RRType& rrtype, const RRTTL& ttl,
+                 const Rdata& rdata UNUSED_PARAM,
+                 uint8_t& extended_rcode)
+{
+    // Create a new EDNS object first for exception guarantee.
+    EDNS* edns = new EDNS(name, rrclass, rrtype, ttl, rdata);
+
+    // At this point we can update extended_rcode safely.
+    extended_rcode = ttl.getValue() >> EXTRCODE_SHIFT;
+
+    return (edns);
+}
+
+ostream&
+operator<<(std::ostream& os, const EDNS& edns) {
+    os << edns.toText();
+    return (os);
+}
+
+} // end of namespace dns
+} // end of namespace isc

+ 449 - 0
src/lib/dns/edns.h

@@ -0,0 +1,449 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __EDNS_H
+#define __EDNS_H 1
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <ostream>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MessageRenderer;
+class RRClass;
+class RRTTL;
+class RRType;
+class Rcode;
+
+/// \brief A pointer-like type pointing to an \c EDNS object.
+typedef boost::shared_ptr<EDNS> EDNSPtr;
+
+/// \brief A pointer-like type pointing to an immutable \c EDNS object.
+typedef boost::shared_ptr<const EDNS> ConstEDNSPtr;
+
+/// The \c EDNS class represents the %EDNS OPT RR defined in RFC2671.
+///
+/// This class encapsulates various optional features of %EDNS such as
+/// the UDP payload size or the DNSSEC DO bit, and provides interfaces
+/// to manage these features.  It is also responsible for conversion
+/// to and from wire-format OPT RR.
+/// One important exception is about the extended RCODE:
+/// The \c EDNS class is only responsible for extracting the 8-bit part
+/// of the 12-bit extended RCODE from the OPT RR's TTL field of an
+/// incoming message, and for setting the 8-bit part into the OPT RR TTL
+/// of an outgoing message.  It's not supposed to know how to construct the
+/// complete RCODE, much less maintain the RCODE in it.
+/// It is the caller's responsibility (typically the \c Message class).
+///
+/// When converting wire-format OPT RR into an \c EDNS object, it normalizes
+/// the information, i.e., unknown flags will be ignored on construction.
+///
+/// This class is also supposed to support %EDNS options such as NSID,
+/// but the initial implementation does not include it.  This is a near term
+/// TODO item.
+///
+/// <b>Notes to developers</b>
+///
+/// The rest of the description is for developers who need to or want to
+/// understand the design of this API.
+///
+/// Representing %EDNS is tricky.  An OPT RR is no different from other RRs
+/// in terms of the wire format syntax, and in that sense we could use the
+/// generic \c RRset class to represent an OPT RR (BIND 9 adopts this
+/// approach).  But the resulting interface would be inconvenient for
+/// developers.  For example, the developer would need to know that the
+/// UDP size is encoded in the RR Class field.  It's better to provide
+/// a more abstract interface along with the special semantics of OPT RR.
+///
+/// Another approach would be to realize each optional feature of EDNS
+/// as an attribute of the DNS message.
+/// NLnet Labs' ldns takes this approach.
+/// This way an operation for specifying the UDP size would be written
+/// like this:
+/// \code message->setUDPSize(4096); \endcode
+/// which should be more intuitive.
+/// A drawback of this approach is that OPT RR is itself optional and the
+/// separate parameters may not necessarily indicate whether to include an
+/// OPT RR per se.
+/// For example, consider what should be done with this code:
+/// \code message->setUDPSize(512); \endcode
+/// Since the payload size of 512 is the default, it may mean the OPT RR
+/// should be skipped.  But it might also mean the caller intentionally
+/// (for some reason) wants to insert an OPT RR specifying the default UDP
+/// size explicitly.
+///
+/// So, we use a separate class that encapsulates the EDNS semantics and
+/// knows the mapping between the semantics and the wire format representation.
+/// This way the interface can be semantics-based and is intuitive:
+/// \code edns->setUDPSize(4096); \endcode
+/// while we can explicitly specify whether to include an OPT RR by setting
+/// (or not setting) an \c EDNS object in a message:
+/// \code message->setEDNS(edns); // unless we do this OPT RR is skipped
+/// \endcode
+///
+/// There is still a non trivial point: How to manage extended RCODEs.
+/// An OPT RR encodes the upper 8 bits of extended 12-bit RCODE.
+/// In general, it would be better to provide a unified interface to get
+/// access to RCODEs whether or not they are traditional 4 bit codes or
+/// extended ones that have non 0 upper bits.
+/// However, since an OPT RR may not appear in a message the RCODE cannot be
+/// maintained in the \c EDNS class.
+/// But it would not be desirable to maintain the extended RCODEs completely
+/// in the \c Message class, either, because we wanted to hide the mapping
+/// between %EDNS semantics and its wire format representation within the
+///  \c EDNS class; if we moved the responsibility about RCODEs to the
+/// \c Message class, it would have to parse and render the upper 8 bits of
+/// the RCODEs, dealing with wire representation of OPT RR.
+/// This is suboptimal in the sense of encapsulation.
+///
+/// As a compromise, our decision is to separate the knowledge about the
+/// relationship with RCODE from the knowledge about the wire format as
+/// noted in the beginning of this description.
+///
+/// This decoupling is based on the observation that the extended RCODE
+/// is a very special case where %EDNS only has partial information.
+/// If a future version of the %EDNS protocol introduces further relationship
+/// between the message and the %EDNS, we might reconsider the interface,
+/// probably with higher abstraction.
+class EDNS {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// We use the default copy constructor, default copy assignment operator,
+    /// and default destructors intentionally.
+    ///
+    /// Note about copyability: This version of this class is copyable,
+    /// but we may want to change it once we support EDNS options, when
+    /// we want to revise this class using the pimpl idiom.
+    /// But we should be careful about that: the python binding currently
+    /// assumes this class is copyable.
+    //@{
+    /// Constructor with the EDNS version.
+    /// An application would use this constructor to specify EDNS parameters
+    /// and/or options for outgoing DNS messages.
+    ///
+    /// All other parameters than the version number will be initialized to
+    /// reasonable defaults.
+    /// Specifically, the UDP payload size is set to
+    /// \c Message::DEFAULT_MAX_UDPSIZE, and DNSSEC is assumed to be not
+    /// supported.
+    /// These parameters can be altered via setter methods of this class.
+    /// Note, however, that the version number cannot be changed once
+    /// constructed.
+    ///
+    /// The version number parameter can be omitted, in which case the highest
+    /// supported version in this implementation will be assumed.
+    /// When specified, if it is larger than the highest supported version,
+    /// an exception of class \c isc::InvalidParameter will be thrown.
+    ///
+    /// This constructor throws no other exception.
+    ///
+    /// \param version The version number of the EDNS to be constructed.
+    explicit EDNS(const uint8_t version = SUPPORTED_VERSION);
+
+    /// \brief Constructor from resource record (RR) parameters.
+    ///
+    /// This constructor is intended to be used to construct an EDNS object
+    /// from an OPT RR contained in an incoming DNS message.
+    ///
+    /// Unlike many other constructors for this purpose, this constructor
+    /// does not take the bare wire-format %data in the form of an
+    /// \c InputBuffer object.  This is because parsing incoming EDNS is
+    /// highly context dependent and it's not feasible to handle it in a
+    /// completely polymorphic way.  For example, a DNS message parser would
+    /// have to check an OPT RR appears at most once in the message, and if
+    /// it appears it should be in the additional section.  So, the parser
+    /// needs to have an explicit check to see if an RR is of type OPT, and
+    /// then (if other conditions are met) construct a corresponding \c EDNS
+    /// object.  At that point the parser would have already converted the
+    /// wire %data into corresponding objects of \c Name, \c RRClass,
+    /// \c RRType, etc, and it makes more sense to pass them directly to the
+    /// constructor.
+    ///
+    /// In practice, top level applications rarely need to use this
+    /// constructor directly.  It should normally suffice to have a higher
+    /// level class such as \c Message do that job.
+    ///
+    /// This constructor checks the passed parameters to see if they are
+    /// valid in terms of the EDNS protocol specification.
+    /// \c name must be the root name ("."); otherwise, an exception of
+    /// class \c DNSMessageFORMERR will be thrown.
+    /// \c rrtype must specify the OPT RR type; otherwise, an exception of
+    /// class \c isc::InvalidParameter will be thrown.
+    /// The ENDS version number is extracted from \c rrttl.  If it is larger
+    /// than the higher supported version, an exception of class
+    /// \c DNSMessageBADVERS will be thrown.  Note that this is different from
+    /// the case of the same error in the other constructor.
+    /// This is intentional, so that the application can transparently convert
+    /// the exception to a response RCODE according to the protocol
+    /// specification.
+    ///
+    /// This initial implementation does not support EDNS options at all,
+    /// and \c rdata is simply ignored.  Future versions will support
+    /// options, and may throw exceptions while validating the given parameter.
+    ///
+    /// \b Note: since no other type than OPT for \c rrtype is allowed, this
+    /// parameter could actually have been omitted.  But it is intentionally
+    /// included as a parameter so that invalid usage of the construction
+    /// can be detected.  As noted above the caller should normally have
+    /// the corresponding \c RRType object at the time of call to this
+    /// constructor, so the overhead of having the additional parameter
+    /// should be marginal.
+    ///
+    /// \param name The owner name of the OPT RR.  This must be the root name.
+    /// \param rrclass The RR class of the OPT RR.
+    /// \param rrtype This must specify the OPT RR type.
+    /// \param rrttl The TTL of the OPT RR.
+    /// \param rdata The RDATA of the OPT RR.
+    EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+         const RRTTL& ttl, const rdata::Rdata& rdata);
+    //@}
+
+    ///
+    /// \name Getter and Setter Methods
+    ///
+    //@{
+    /// \brief Returns the version of EDNS.
+    ///
+    /// This method never throws an exception.
+    uint8_t getVersion() const { return (version_); }
+
+    /// \brief Returns the maximum payload size of UDP messages for the sender
+    /// of the message containing this \c EDNS.
+    ///
+    /// This method never throws an exception.
+    uint16_t getUDPSize() const { return (udp_size_); }
+
+    /// \brief Specify the maximum payload size of UDP messages that use
+    /// this EDNS.
+    ///
+    /// Unless explicitly specified, \c DEFAULT_MAX_UDPSIZE will be assumed
+    /// for the maximum payload size, regardless of whether EDNS OPT RR is
+    /// included or not.  This means if an application wants to send a message
+    /// with an EDNS OPT RR for specifying a larger UDP size, it must
+    /// explicitly specify the value using this method.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param udp_size The maximum payload size of UDP messages for the sender
+    /// of the message containing this \c EDNS.
+    void setUDPSize(const uint16_t udp_size) { udp_size_ = udp_size; }
+
+    /// \brief Returns whether the message sender is DNSSEC aware.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return true if DNSSEC is supported; otherwise false.
+    bool getDNSSECAwareness() const { return (dnssec_aware_); }
+
+    /// \brief Specifies whether the sender of the message containing this
+    /// \c EDNS is DNSSEC aware.
+    ///
+    /// If the parameter is true, a subsequent call to \c toWire() will
+    /// set the DNSSEC DO bit on for the corresponding OPT RR.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param is_aware \c true if DNSSEC is supported; \c false otherwise.
+    void setDNSSECAwareness(const bool is_aware) { dnssec_aware_ = is_aware; }
+    //@}
+
+    ///
+    /// \name Converter Methods
+    ///
+    //@{
+    /// \brief Render the \c EDNS in the wire format.
+    ///
+    /// This method renders the \c EDNS object as a form of DNS OPT RR
+    /// via \c renderer, which encapsulates output buffer and other rendering
+    /// contexts.
+    /// Since the \c EDNS object does not maintain the extended RCODE
+    /// information, a separate parameter \c extended_rcode must be passed to
+    /// this method.
+    ///
+    /// If by adding the OPT RR the message size would exceed the limit
+    /// maintained in \c renderer, this method skips rendering the RR
+    /// and returns 0; otherwise it returns 1, which is the number of RR
+    /// rendered.
+    ///
+    /// In the current implementation the return value is either 0 or 1, but
+    /// the return type is <code>unsigned int</code> to be consistent with
+    /// \c RRset::toWire().  In any case the caller shouldn't assume these are
+    /// only possible return values from this method.
+    ///
+    /// This method is mostly exception free, but it requires memory
+    /// allocation and if it fails a corresponding standard exception will be
+    /// thrown.
+    ///
+    /// In practice, top level applications rarely need to use this
+    /// method directly.  It should normally suffice to have a higher
+    /// level class such as \c Message do that job.
+    ///
+    /// <b>Note to developer:</b> the current implementation constructs an
+    /// \c RRset object for the OPT RR and calls its \c toWire() method,
+    /// which is inefficient.  In future, we may want to optimize this method
+    /// by caching the rendered image and having the application reuse the
+    /// same \c EDNS object when possible.
+    ///
+    /// \param renderer DNS message rendering context that encapsulates the
+    /// output buffer and name compression information.
+    /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as
+    /// part of the EDNS OPT RR.
+    /// \return 1 if the OPT RR fits in the message size limit; otherwise 0.
+    unsigned int toWire(MessageRenderer& renderer,
+                        const uint8_t extended_rcode) const;
+
+    /// \brief Render the \c EDNS in the wire format.
+    ///
+    /// This method is same as \c toWire(MessageRenderer&,uint8_t)const
+    /// except it renders the OPT RR in an \c OutputBuffer and therefore
+    /// does not care about message size limit.
+    /// As a consequence it always returns 1.
+    unsigned int toWire(OutputBuffer& buffer,
+                        const uint8_t extended_rcode) const;
+
+    /// \brief Convert the EDNS to a string.
+    ///
+    /// The format of the resulting string is as follows:
+    /// \code ; EDNS: version: <version>, flags: <edns flags>; udp: <udp size>
+    /// \endcode
+    /// where
+    ///  - \em version is the EDNS version number (integer).
+    ///  - <em>edns flags</em> is a sequence of EDNS flag bits.  The only
+    ///    possible flag is the "DNSSEC OK", which is represented as "do".
+    ///  - <em>udp size</em> is sender's UDP payload size in bytes.
+    ///
+    /// The string will be terminated with a trailing newline character.
+    ///
+    /// When EDNS options are supported the output of this method will be
+    /// extended.
+    ///
+    /// This method is mostly exception free, but it may require memory
+    /// allocation and if it fails a corresponding standard exception will be
+    /// thrown.
+    ///
+    /// \return A string representation of \c EDNS.  See above for the format.
+    std::string toText() const;
+    //@}
+
+    // TBD: This method is currently not implemented.  We'll eventually need
+    // something like this.
+    //void addOption();
+
+private:
+    /// Helper method to define unified implementation for the public versions
+    /// of toWire().
+    template <typename Output>
+    int toWire(Output& output, const uint8_t extended_rcode) const;
+
+public:
+    /// \brief The highest EDNS version this implementation supports.
+    static const uint8_t SUPPORTED_VERSION = 0;
+private:
+    // We may eventually want to migrate to pimpl, especially when we support
+    // EDNS options.  In this initial implementation, we keep it simple.
+    const uint8_t version_;
+    uint16_t udp_size_;
+    bool dnssec_aware_;
+};
+
+/// \brief Create a new \c EDNS object from a set of RR parameters, also
+/// providing the extended RCODE value.
+///
+/// This function is similar to the EDNS class constructor
+/// \c EDNS::EDNS(const Name&, const RRClass&, const RRType&, const RRTTL&, const rdata::Rdata&)
+/// but is different in that
+/// - It dynamically creates a new object
+/// - It returns (via a reference argument) the topmost 8 bits of the extended
+/// RCODE encoded in the \c ttl.
+///
+/// On success, \c extended_rcode will be updated with the 8-bit part of
+/// the extended RCODE encoded in the TTL of the OPT RR.
+///
+/// The intended usage of this function is to parse an OPT RR of an incoming
+/// DNS message, while updating the RCODE of the message.
+/// One common usage patter is as follows:
+///
+/// \code Message msg;
+/// ...
+/// uint8_t extended_rcode;
+/// ConstEDNSPtr edns = ConstEDNSPtr(createEDNSFromRR(..., extended_rcode));
+/// rcode = Rcode(msg.getRcode().getCode(), extended_rcode);
+/// \endcode
+/// (although, like the \c EDNS constructor, normal applications wouldn't have
+/// to use this function directly).
+///
+/// This function provides the strong exception guarantee: Unless an
+/// exception is thrown \c extended_code won't be modified.
+///
+/// This function validates the given parameters and throws exceptions on
+/// failure in the same way as the \c EDNS class constructor.
+/// In addition, if memory allocation for the new object fails it throws the
+/// corresponding standard exception.
+///
+/// Note that this function returns a bare pointer to the newly allocated
+/// object, not a shared pointer object enclosing the pointer.
+/// The caller is responsible for deleting the object after the use of it
+/// (typically, the caller would immediately encapsulate the returned pointer
+/// in a shared pointer object, \c EDNSPtr or \c ConstEDNSPtr).
+/// It returns a bare pointer so that it can be used where the use of a shared
+/// pointer is impossible or not desirable.
+///
+/// Note to developers: there is no strong technical reason why this function
+/// cannot be a constructor of the \c EDNS class or even integrated into the
+/// constructor.  But we decided to make it a separate free function so that
+/// constructors will be free from side effects (which is in itself a matter
+/// of preference).
+///
+/// \param name The owner name of the OPT RR.  This must be the root name.
+/// \param rrclass The RR class of the OPT RR.
+/// \param rrtype This must specify the OPT RR type.
+/// \param rrttl The TTL of the OPT RR.
+/// \param rdata The RDATA of the OPT RR.
+/// \param extended_rcode A placeholder to store the topmost 8 bits of the
+/// extended Rcode.
+/// \return A pointer to the created \c EDNS object.
+EDNS* createEDNSFromRR(const Name& name, const RRClass& rrclass,
+                       const RRType& rrtype, const RRTTL& ttl,
+                       const rdata::Rdata& rdata, uint8_t& extended_rcode);
+
+/// \brief Insert the \c EDNS as a string into stream.
+///
+/// This method convert \c edns into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param edns A reference to an \c EDNS object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const EDNS& edns);
+}
+}
+#endif  // __EDNS_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 110 - 167
src/lib/dns/message.cc

@@ -28,6 +28,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/buffer.h>
+#include <dns/edns.h>
 #include <dns/exceptions.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -64,10 +65,7 @@ const flags_t FLAG_CD = 0x0010;
 //
 // EDNS related constants
 //
-const flags_t EXTFLAG_MASK = 0xffff;
-const flags_t EXTFLAG_DO = 0x8000;
 const uint32_t EXTRCODE_MASK = 0xff000000; 
-const uint32_t EDNSVERSION_MASK = 0x00ff0000;
 
 const unsigned int OPCODE_MASK = 0x7800;
 const unsigned int OPCODE_SHIFT = 11;
@@ -167,12 +165,39 @@ Opcode::toText() const {
     return (opcodetext[code_]);
 }
 
-Rcode::Rcode(uint16_t code) : code_(code) {
+namespace {
+// This diagram shows the wire-format representation of the 12-bit extended
+// form RCODEs and its relationship with implementation specific parameters.
+//
+//     0     3               11      15
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    |UNUSED | EXTENDED-RCODE | RCODE |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//                            <= EXTRCODE_SHIFT (4 bits)
+const unsigned int EXTRCODE_SHIFT = 4;
+}
+
+Rcode::Rcode(const uint16_t code) : code_(code) {
     if (code_ > MAX_RCODE) {
-        isc_throw(OutOfRange, "Rcode is too large to construct");
+        isc_throw(OutOfRange, "Rcode is too large to construct: " << code_);
     }
 }
 
+Rcode::Rcode(const uint8_t code, const uint8_t extended_code) :
+    code_((extended_code << EXTRCODE_SHIFT) | (code & RCODE_MASK))
+{
+    if (code > RCODE_MASK) {
+        isc_throw(OutOfRange,
+                  "Base Rcode is too large to construct: "
+                  << static_cast<unsigned int>(code));
+    }
+}
+
+uint8_t
+Rcode::getExtendedCode() const {
+    return (code_ >> EXTRCODE_SHIFT);
+}
+
 string
 Rcode::toText() const {
     if (code_ < sizeof(rcodetext) / sizeof (const char *)) {
@@ -203,17 +228,13 @@ public:
     Rcode rcode_;
     const Opcode* opcode_;
     flags_t flags_;
-    bool dnssec_ok_;
 
     bool header_parsed_;
     static const unsigned int SECTION_MAX = 4; // TODO: revisit this design
     int counts_[SECTION_MAX];   // TODO: revisit this definition
     vector<QuestionPtr> questions_;
     vector<RRsetPtr> rrsets_[SECTION_MAX];
-    RRsetPtr remote_edns_;
-    uint16_t remote_udpsize_;
-    RRsetPtr local_edns_;
-    uint16_t udpsize_;
+    ConstEDNSPtr edns_;
 
 #ifdef notyet
     // tsig/sig0: TODO
@@ -237,11 +258,7 @@ MessageImpl::init() {
     qid_ = 0;
     rcode_ = Rcode::NOERROR();  // XXX
     opcode_ = NULL;
-    dnssec_ok_ = false;
-    remote_edns_ = RRsetPtr();
-    remote_udpsize_ = Message::DEFAULT_MAX_UDPSIZE;
-    local_edns_ = RRsetPtr();
-    udpsize_ = Message::DEFAULT_MAX_UDPSIZE;
+    edns_ = EDNSPtr();
 
     for (int i = 0; i < SECTION_MAX; ++i) {
         counts_[i] = 0;
@@ -285,38 +302,6 @@ Message::clearHeaderFlag(const MessageFlag& flag) {
     impl_->flags_ &= ~flag.getBit();
 }
 
-bool
-Message::isDNSSECSupported() const {
-    return (impl_->dnssec_ok_);
-}
-
-void
-Message::setDNSSECSupported(bool on) {
-    if (impl_->mode_ != Message::RENDER) {
-        isc_throw(InvalidMessageOperation,
-                  "setDNSSECSupported performed in non-render mode");
-    }
-    impl_->dnssec_ok_ = on;
-}
-
-uint16_t
-Message::getUDPSize() const {
-    return (impl_->udpsize_);
-}
-
-void
-Message::setUDPSize(uint16_t size) {
-    if (impl_->mode_ != Message::RENDER) {
-        isc_throw(InvalidMessageOperation,
-                  "setUDPSize performed in non-render mode");
-    }
-    if (size < DEFAULT_MAX_UDPSIZE) {
-        isc_throw(InvalidMessageUDPSize,
-                  "Specified UDP message size is too small");
-    }
-    impl_->udpsize_ = size;
-}
-
 qid_t
 Message::getQid() const {
     return (impl_->qid_);
@@ -359,6 +344,20 @@ Message::setOpcode(const Opcode& opcode) {
     impl_->opcode_ = &opcode;
 }
 
+ConstEDNSPtr
+Message::getEDNS() const {
+    return (impl_->edns_);
+}
+
+void
+Message::setEDNS(ConstEDNSPtr edns) {
+    if (impl_->mode_ != Message::RENDER) {
+        isc_throw(InvalidMessageOperation,
+                  "setEDNS performed in non-render mode");
+    }
+    impl_->edns_ = edns;
+}
+
 unsigned int
 Message::getRRCount(const Section& section) const {
     return (impl_->counts_[section.getCode()]);
@@ -441,54 +440,6 @@ struct RenderSection {
 };
 }
 
-namespace {
-bool
-addEDNS(MessageImpl* mimpl, MessageRenderer& renderer) {
-    const bool is_query = ((mimpl->flags_ & MessageFlag::QR().getBit()) == 0); 
-
-    // If this is a reply, add EDNS either when the request had it, or
-    // if the Rcode is BADVERS, which is EDNS specific.
-    // XXX: this logic is tricky.  We should revisit this later.
-    if (!is_query) {
-        if (mimpl->remote_edns_ == NULL && mimpl->rcode_ != Rcode::BADVERS()) {
-            return (false);
-        }
-    } else {
-        // For queries, we add EDNS only when necessary:
-        // Local UDP size is not the default value, or
-        // DNSSEC DO bit is to be set, or
-        // Extended Rcode is to be specified.
-        if (mimpl->udpsize_ == Message::DEFAULT_MAX_UDPSIZE &&
-            !mimpl->dnssec_ok_ &&
-            mimpl->rcode_.getCode() < 0x10) {
-            return (false);
-        }
-    }
-
-    // If adding the OPT RR would exceed the size limit, don't do it.
-    // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
-    // (RDATA is empty in this simple implementation)
-    if (renderer.getLength() + 11 > renderer.getLengthLimit()) {
-        return (false);
-    }
-
-    // Render EDNS OPT RR
-    uint32_t extrcode_flags = ((mimpl->rcode_.getCode() & 0xff0) << 24);
-    if (mimpl->dnssec_ok_) {
-        extrcode_flags |= 0x8000; // set DO bit
-    }
-    mimpl->local_edns_ = RRsetPtr(new RRset(Name::ROOT_NAME(),
-                                            RRClass(mimpl->udpsize_),
-                                            RRType::OPT(),
-                                            RRTTL(extrcode_flags)));
-    // We don't support any options in this simple implementation
-    mimpl->local_edns_->addRdata(ConstRdataPtr(new generic::OPT()));
-    mimpl->local_edns_->toWire(renderer);
-
-    return (true);
-}
-}
-
 void
 Message::toWire(MessageRenderer& renderer) {
     if (impl_->mode_ != Message::RENDER) {
@@ -528,12 +479,20 @@ Message::toWire(MessageRenderer& renderer) {
                      RenderSection<RRsetPtr>(renderer, false)).getTotalCount();
     }
 
-    // Added EDNS OPT RR if necessary (we want to avoid hardcoding specialized
-    // logic, see the parser case)
-    if (!renderer.isTruncated() && addEDNS(this->impl_, renderer)) {
-        ++arcount;
+    // Add EDNS OPT RR if necessary.  Basically, we add it only when EDNS
+    // has been explicitly set.  However, if the RCODE would require it and
+    // no EDNS has been set we generate a temporary local EDNS and use it.
+    if (!renderer.isTruncated()) {
+        ConstEDNSPtr local_edns = impl_->edns_;
+        if (!local_edns && impl_->rcode_.getExtendedCode() != 0) {
+            local_edns = ConstEDNSPtr(new EDNS());
+        }
+        if (local_edns) {
+            arcount += local_edns->toWire(renderer,
+                                          impl_->rcode_.getExtendedCode());
+        }
     }
-
+ 
     // Adjust the counter buffer.
     // XXX: these may not be equal to the number of corresponding entries
     // in rrsets_[] or questions_ if truncation occurred or an EDNS OPT RR
@@ -657,6 +616,34 @@ struct MatchRR : public unary_function<RRsetPtr, bool> {
 };
 }
 
+// Note about design decision:
+// we need some type specific processing here, including EDNS and TSIG.
+// how much we should generalize/hardcode the special logic is subject
+// to discussion.  In terms of modularity it would be ideal to introduce
+// an abstract class (say "MessageAttribute") and let other such
+// concrete notions as EDNS or TSIG inherit from it.  Then we would
+// just do:
+// message->addAttribute(rrtype, rrclass, buffer);
+// to create and attach type-specific concrete object to the message.
+//
+// A major downside of this approach is, as usual, complexity due to
+// indirection and performance penalty.  Also, it may not be so easy
+// to separate the processing logic because in many cases we'll need
+// parse context for which the message class is responsible (e.g.
+// to check the EDNS OPT RR only appears in the additional section,
+// and appears only once).
+//
+// Another point to consider is that we may not need so many special
+// types other than EDNS and TSIG (and when and if we implement it,
+// SIG(0)); newer optional attributes of the message would more likely
+// be standardized as new flags or options of EDNS.  If that's the case,
+// introducing an abstract class with all the overhead and complexity
+// may not make much sense.
+//
+// Conclusion: don't over-generalize type-specific logic for now.
+// introduce separate concrete classes, and move context-independent
+// logic to that class; processing logic dependent on parse context
+// is hardcoded here.
 int
 MessageImpl::parseSection(const Section& section, InputBuffer& buffer) {
     unsigned int added = 0;
@@ -678,60 +665,36 @@ MessageImpl::parseSection(const Section& section, InputBuffer& buffer) {
         const size_t rdlen = buffer.readUint16();
         ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
 
-        // XXX: we wanted to avoid hardcoding type-specific logic here,
-        // but this would be the fastest way for a proof-of-concept
-        // implementation.  We'll revisit this part later.
         if (rrtype == RRType::OPT()) {
             if (section != Section::ADDITIONAL()) {
                 isc_throw(DNSMessageFORMERR,
                           "EDNS OPT RR found in an invalid section");
             }
-            if (remote_edns_ != NULL) {
+            if (edns_) {
                 isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found");
             }
-            if (((ttl.getValue() & EDNSVERSION_MASK) >> 16) >
-                Message::EDNS_SUPPORTED_VERSION) {
-                // XXX: we should probably not reject the message yet, because
-                // it's better to let the requestor know the responder-side
-                // highest version as indicated in Section 4.6 of RFC2671.
-                // This is probably because why BIND 9 does the version check
-                // in the client code.
-                // This is a TODO item.  Right now we simply reject it.
-                const unsigned int ver =
-                    (ttl.getValue() & EDNSVERSION_MASK) >> 16;
-                isc_throw(DNSMessageBADVERS, "unsupported EDNS version: " <<
-                          ver);
-            }
-            if (name != Name::ROOT_NAME()) {
-                isc_throw(DNSMessageFORMERR,
-                          "invalid owner name for EDNS OPT RR");
-            }
 
-            remote_edns_ = RRsetPtr(new RRset(name, rrclass, rrtype, ttl));
-            remote_edns_->addRdata(rdata);
-
-            dnssec_ok_ = (((ttl.getValue() & EXTFLAG_MASK) & EXTFLAG_DO) != 0);
-            if (rrclass.getCode() > Message::DEFAULT_MAX_UDPSIZE) {
-                udpsize_ = rrclass.getCode();
-            }
-            rcode_ = Rcode(((ttl.getValue() & EXTRCODE_MASK) >> 20) |
-                           rcode_.getCode());
+            uint8_t extended_rcode;
+            edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl,
+                                                  *rdata, extended_rcode));
+            rcode_ = Rcode(rcode_.getCode(), extended_rcode);
             continue;
-        }
-
-        vector<RRsetPtr>::iterator it =
-            find_if(rrsets_[sectionCodeToId(section)].begin(),
-                    rrsets_[sectionCodeToId(section)].end(),
-                    MatchRR(name, rrtype, rrclass));
-        if (it != rrsets_[sectionCodeToId(section)].end()) {
-            (*it)->setTTL(min((*it)->getTTL(), ttl));
-            (*it)->addRdata(rdata);
         } else {
-            RRsetPtr rrset = RRsetPtr(new RRset(name, rrclass, rrtype, ttl)); 
-            rrset->addRdata(rdata);
-            rrsets_[sectionCodeToId(section)].push_back(rrset);
+            vector<RRsetPtr>::iterator it =
+                find_if(rrsets_[sectionCodeToId(section)].begin(),
+                        rrsets_[sectionCodeToId(section)].end(),
+                        MatchRR(name, rrtype, rrclass));
+            if (it != rrsets_[sectionCodeToId(section)].end()) {
+                (*it)->setTTL(min((*it)->getTTL(), ttl));
+                (*it)->addRdata(rdata);
+            } else {
+                RRsetPtr rrset =
+                    RRsetPtr(new RRset(name, rrclass, rrtype, ttl)); 
+                rrset->addRdata(rdata);
+                rrsets_[sectionCodeToId(section)].push_back(rrset);
+            }
+            ++added;
         }
-        ++added;
     }
 
     return (added);
@@ -786,30 +749,14 @@ Message::toText() const {
         lexical_cast<string>(impl_->counts_[Section::AUTHORITY().getCode()]);
 
     unsigned int arcount = impl_->counts_[Section::ADDITIONAL().getCode()];
-    RRsetPtr edns_rrset;
-    if (!getHeaderFlag(MessageFlag::QR()) && impl_->remote_edns_ != NULL) {
-        edns_rrset = impl_->remote_edns_;
+    if (impl_->edns_ != NULL) {
         ++arcount;
     }
     s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
 
-    if (edns_rrset != NULL) {
+    if (impl_->edns_ != NULL) {
         s += "\n;; OPT PSEUDOSECTION:\n";
-        s += "; EDNS: version: ";
-        s += lexical_cast<string>(
-            (edns_rrset->getTTL().getValue() & 0x00ff0000) >> 16);
-        s += ", flags:";
-        if ((edns_rrset->getTTL().getValue() & 0x8000) != 0) {
-            s += " do";
-        }
-        const uint32_t mbz = edns_rrset->getTTL().getValue() & ~0x8000 & 0xffff;
-        if (mbz != 0) {
-            s += "; MBZ: " + lexical_cast<string>(mbz) + ", udp: ";
-        } else {
-            s += "; udp: " +
-                lexical_cast<string>(edns_rrset->getClass().getCode());
-        }
-        s += "\n";
+        s += impl_->edns_->toText();
     }
 
     if (!impl_->questions_.empty()) {
@@ -860,11 +807,7 @@ Message::makeResponse()
 
     impl_->mode_ = Message::RENDER;
 
-    impl_->dnssec_ok_ = false;
-    impl_->remote_udpsize_ = impl_->udpsize_;
-    impl_->local_edns_ = RRsetPtr();
-    impl_->udpsize_ = DEFAULT_MAX_UDPSIZE;
-
+    impl_->edns_ = EDNSPtr();
     impl_->flags_ &= MESSAGE_REPLYPRESERVE;
     setHeaderFlag(MessageFlag::QR());
 

+ 23 - 41
src/lib/dns/message.h

@@ -25,6 +25,7 @@
 
 #include <exceptions/exceptions.h>
 
+#include <dns/edns.h>
 #include <dns/question.h>
 #include <dns/rrset.h>
 
@@ -314,8 +315,10 @@ Opcode::RESERVED15()
 /// Constant objects are defined for standard flags.
 class Rcode {
 public:
-    Rcode(uint16_t code);
+    Rcode(const uint16_t code);
+    Rcode(const uint8_t code, const uint8_t extended_code);
     uint16_t getCode() const { return (code_); }
+    uint8_t getExtendedCode() const;
     bool operator==(const Rcode& other) const { return (code_ == other.code_); }
     bool operator!=(const Rcode& other) const { return (code_ != other.code_); }
     std::string toText() const;
@@ -627,45 +630,6 @@ public:
     /// \c setHeaderFlag() with an additional argument.
     void clearHeaderFlag(const MessageFlag& flag);
 
-    /// \brief Return whether the message sender indicates DNSSEC is supported.
-    /// If EDNS is included, this corresponds to the value of the DO bit.
-    /// Otherwise, DNSSEC is considered not supported.
-    bool isDNSSECSupported() const;
-
-    /// \brief Specify whether DNSSEC is supported in the message.
-    ///
-    /// Only allowed in the \c RENDER mode.
-    /// If EDNS is included in the message, the DO bit is set or cleared
-    /// according to the specified value of this method.
-    void setDNSSECSupported(bool on);
-
-    /// \brief Return the maximum buffer size of UDP messages for the sender
-    /// of the message.
-    ///
-    /// The semantics of this value is different based on the mode:
-    /// In the \c PARSE mode, it means the buffer size of the remote node;
-    /// in the \c RENDER mode, it means the buffer size of the local node.
-    ///
-    /// In either case, its value is the value of the UDP payload size field
-    /// of EDNS (when it's included) or \c DEFAULT_MAX_UDPSIZE.
-    ///
-    /// Note: this interface may be confusing and may have to be revisited.
-    uint16_t getUDPSize() const;
-
-    /// \brief Specify the maximum buffer size of UDP messages of the local
-    /// node.
-    ///
-    /// Only allowed in the \c RENDER mode.
-    /// If EDNS OPT RR is included in the message, its UDP payload size field
-    /// will be set to the specified value.
-    ///
-    /// Unless explicitly specified, \c DEFAULT_MAX_UDPSIZE will be assumed
-    /// for the maximum buffer size, regardless of whether EDNS OPT RR is
-    /// included or not.  This means if an application wants to send a message
-    /// with an EDNS OPT RR for specifying a larger UDP size, it must explicitly
-    /// specify the value using this method.
-    void setUDPSize(uint16_t size);
-
     /// \brief Return the query ID given in the header section of the message.
     qid_t getQid() const;
 
@@ -682,9 +646,10 @@ public:
     /// returned.
     const Rcode& getRcode() const;
 
-    /// \brief Return the Response Code of the message.
+    /// \brief Set the Response Code of the message.
     ///
     /// Only allowed in the \c RENDER mode.
+    ///
     /// If the specified code is an EDNS extended RCODE, an EDNS OPT RR will be
     /// included in the message.
     void setRcode(const Rcode& rcode);
@@ -697,6 +662,23 @@ public:
     /// Only allowed in the \c RENDER mode.
     void setOpcode(const Opcode& opcode);
 
+    /// \brief Return, if any, the EDNS associated with the message.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A shared pointer to the EDNS.  This will be a null shared
+    /// pointer if the message is not associated with EDNS.
+    ConstEDNSPtr getEDNS() const;
+
+    /// \brief Set EDNS for the message.
+    ///
+    /// Only allowed in the \c RENDER mode; otherwise an exception of class
+    /// \c InvalidMessageOperation will be thrown.
+    ///
+    /// \param edns A shared pointer to an \c EDNS object to be set in
+    /// \c Message.
+    void setEDNS(ConstEDNSPtr edns);
+
     /// \brief Returns the number of RRs contained in the given section.
     unsigned int getRRCount(const Section& section) const;
 

+ 1 - 0
src/lib/dns/python/Makefile.am

@@ -11,6 +11,7 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 # directly included from source files, so these don't have their own
 # rules
 EXTRA_DIST = pydnspp_common.h
+EXTRA_DIST += edns_python.cc
 EXTRA_DIST += messagerenderer_python.cc
 EXTRA_DIST += message_python.cc
 EXTRA_DIST += rrclass_python.cc

+ 373 - 0
src/lib/dns/python/edns_python.cc

@@ -0,0 +1,373 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <cassert>
+
+#include <dns/edns.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+namespace {
+//
+// EDNS
+//
+
+// The s_* Class simply covers one instantiation of the object
+
+class s_EDNS : public PyObject {
+public:
+    EDNS* edns;
+};
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int EDNS_init(s_EDNS* self, PyObject* args);
+void EDNS_destroy(s_EDNS* self);
+
+// These are the functions we export
+PyObject* EDNS_toText(const s_EDNS* self);
+// This is a second version of toText, we need one where the argument
+// is a PyObject*, for the str() function in python.
+PyObject* EDNS_str(PyObject* self);
+PyObject* EDNS_toWire(const s_EDNS* self, PyObject* args);
+PyObject* EDNS_getVersion(const s_EDNS* self);
+PyObject* EDNS_getDNSSECAwareness(const s_EDNS* self);
+PyObject* EDNS_setDNSSECAwareness(s_EDNS* self, PyObject* args);
+PyObject* EDNS_getUDPSize(const s_EDNS* self);
+PyObject* EDNS_setUDPSize(s_EDNS* self, PyObject* args);
+PyObject* EDNS_createFromRR(const s_EDNS* null_self, PyObject* args);
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef EDNS_methods[] = {
+    { "to_text", reinterpret_cast<PyCFunction>(EDNS_toText), METH_NOARGS,
+      "Returns the string representation" },
+    { "to_wire", reinterpret_cast<PyCFunction>(EDNS_toWire), METH_VARARGS,
+      "Converts the EDNS object to wire format.\n"
+      "The argument can be either a MessageRenderer or an object that "
+      "implements the sequence interface. If the object is mutable "
+      "(for instance a bytearray()), the wire data is added in-place.\n"
+      "If it is not (for instance a bytes() object), a new object is "
+      "returned" },
+    { "get_version",
+      reinterpret_cast<PyCFunction>(EDNS_getVersion), METH_NOARGS,
+      "Returns the version of EDNS." },
+    { "get_dnssec_awareness",
+      reinterpret_cast<PyCFunction>(EDNS_getDNSSECAwareness), METH_NOARGS,
+      "Returns whether the message sender is DNSSEC aware." },
+    { "set_dnssec_awareness",
+      reinterpret_cast<PyCFunction>(EDNS_setDNSSECAwareness), METH_VARARGS,
+      "Specifies whether the sender of the message containing this "
+      "EDNS is DNSSEC aware." },
+    { "get_udp_size",
+      reinterpret_cast<PyCFunction>(EDNS_getUDPSize), METH_NOARGS,
+      "Return the maximum buffer size of UDP messages for the sender "
+      "of the message." },
+    { "set_udp_size",
+      reinterpret_cast<PyCFunction>(EDNS_setUDPSize), METH_VARARGS,
+      "Specify the maximum buffer size of UDP messages that use this EDNS." },
+    { "create_from_rr",
+      reinterpret_cast<PyCFunction>(EDNS_createFromRR),
+      METH_VARARGS | METH_STATIC,
+      "Create a new EDNS object from a set of RR parameters, also providing "
+      "the extended RCODE value." },
+    { NULL, NULL, 0, NULL }
+};
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_EDNS
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject edns_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.EDNS",
+    sizeof(s_EDNS),                     // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)EDNS_destroy,           // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    EDNS_str,                           // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The EDNS class encapsulates DNS extensions "
+    "provided by the EDNSx protocol.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    EDNS_methods,                       // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    (initproc)EDNS_init,                // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+EDNS*
+createFromRR(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+             const RRTTL& rrttl, const Rdata& rdata, uint8_t& extended_rcode)
+{
+    try {
+        return (createEDNSFromRR(name, rrclass, rrtype, rrttl, rdata,
+                                 extended_rcode));
+    } catch (const isc::InvalidParameter& ex) {
+        PyErr_SetString(po_InvalidParameter, ex.what());
+    } catch (const DNSMessageFORMERR& ex) {
+        PyErr_SetString(po_DNSMessageFORMERR, ex.what());
+    } catch (const DNSMessageBADVERS& ex) {
+        PyErr_SetString(po_DNSMessageBADVERS, ex.what());
+    } catch (...) {
+        PyErr_SetString(po_IscException, "Unexpected exception");
+    }
+
+    return (NULL);
+}
+int
+EDNS_init(s_EDNS* self, PyObject* args) {
+    uint8_t version = EDNS::SUPPORTED_VERSION;
+    const s_Name* name;
+    const s_RRClass* rrclass;
+    const s_RRType* rrtype;
+    const s_RRTTL* rrttl;
+    const s_Rdata* rdata;
+
+    if (PyArg_ParseTuple(args, "|b", &version)) {
+        try {
+            self->edns = new EDNS(version);
+        } catch (const isc::InvalidParameter& ex) {
+            PyErr_SetString(po_InvalidParameter, ex.what());
+            return (-1);
+        } catch (...) {
+            PyErr_SetString(po_IscException, "Unexpected exception");
+            return (-1);
+        }
+        return (0);
+    } else if (PyArg_ParseTuple(args, "O!O!O!O!O!", &name_type, &name,
+                                &rrclass_type, &rrclass, &rrtype_type, &rrtype,
+                                &rrttl_type, &rrttl, &rdata_type, &rdata)) {
+        // We use createFromRR() even if we don't need to know extended_rcode
+        // in this context so that we can share the try-catch logic with
+        // EDNS_createFromRR() (see below).
+        uint8_t extended_rcode;
+        self->edns = createFromRR(*name->name, *rrclass->rrclass,
+                                  *rrtype->rrtype, *rrttl->rrttl,
+                                  *rdata->rdata, extended_rcode);
+        return (self->edns != NULL ? 0 : -1);
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError, "Invalid arguments to EDNS constructor");
+
+    return (-1);
+}
+
+void
+EDNS_destroy(s_EDNS* const self) {
+    delete self->edns;
+    self->edns = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+EDNS_toText(const s_EDNS* const self) {
+    // Py_BuildValue makes python objects from native data
+    return (Py_BuildValue("s", self->edns->toText().c_str()));
+}
+
+PyObject*
+EDNS_str(PyObject* const self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self,
+                                const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject*
+EDNS_toWire(const s_EDNS* const self, PyObject* args) {
+    PyObject* bytes;
+    uint8_t extended_rcode;
+    s_MessageRenderer* renderer;
+
+    if (PyArg_ParseTuple(args, "Ob", &bytes, &extended_rcode) &&
+        PySequence_Check(bytes)) {
+        PyObject* bytes_o = bytes;
+        
+        OutputBuffer buffer(0);
+        self->edns->toWire(buffer, extended_rcode);
+        PyObject* rd_bytes = PyBytes_FromStringAndSize(
+            static_cast<const char*>(buffer.getData()), buffer.getLength());
+        PyObject* result = PySequence_InPlaceConcat(bytes_o, rd_bytes);
+        // We need to release the object we temporarily created here
+        // to prevent memory leak
+        Py_DECREF(rd_bytes);
+        return (result);
+    } else if (PyArg_ParseTuple(args, "O!b", &messagerenderer_type,
+                                &renderer, &extended_rcode)) {
+        const unsigned int n = self->edns->toWire(*renderer->messagerenderer,
+                                                  extended_rcode);
+
+        return (Py_BuildValue("I", n));
+    }
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError, "Incorrect arguments for EDNS.to_wire()");
+    return (NULL);
+}
+
+PyObject*
+EDNS_getVersion(const s_EDNS* const self) {
+    return (Py_BuildValue("B", self->edns->getVersion()));
+}
+
+PyObject*
+EDNS_getDNSSECAwareness(const s_EDNS* const self) {
+    if (self->edns->getDNSSECAwareness()) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+
+PyObject*
+EDNS_setDNSSECAwareness(s_EDNS* self, PyObject* args) {
+    const PyObject *b;
+    if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &b)) {
+        return (NULL);
+    }
+    self->edns->setDNSSECAwareness(b == Py_True);
+    Py_RETURN_NONE;
+}
+
+PyObject*
+EDNS_getUDPSize(const s_EDNS* const self) {
+    return (Py_BuildValue("I", self->edns->getUDPSize()));
+}
+
+PyObject*
+EDNS_setUDPSize(s_EDNS* self, PyObject* args) {
+    unsigned int size;
+    if (!PyArg_ParseTuple(args, "I", &size)) {
+        return (NULL);
+    }
+    if (size > 65535) {
+        PyErr_SetString(PyExc_OverflowError,
+                        "UDP size is not an unsigned 16-bit integer");
+        return (NULL);
+    }
+    self->edns->setUDPSize(size);
+    Py_RETURN_NONE;
+}
+
+PyObject*
+EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
+    const s_Name* name;
+    const s_RRClass* rrclass;
+    const s_RRType* rrtype;
+    const s_RRTTL* rrttl;
+    const s_Rdata* rdata;
+    s_EDNS* edns_obj = NULL;
+
+    assert(null_self == NULL);
+
+    if (PyArg_ParseTuple(args, "O!O!O!O!O!", &name_type, &name,
+                         &rrclass_type, &rrclass, &rrtype_type, &rrtype,
+                         &rrttl_type, &rrttl, &rdata_type, &rdata)) {
+        uint8_t extended_rcode;
+        edns_obj = PyObject_New(s_EDNS, &edns_type);
+        if (edns_obj == NULL) {
+            return (NULL);
+        }
+
+        edns_obj->edns = createFromRR(*name->name, *rrclass->rrclass,
+                                      *rrtype->rrtype, *rrttl->rrttl,
+                                      *rdata->rdata, extended_rcode);
+        if (edns_obj->edns != NULL) {
+            PyObject* extrcode_obj = Py_BuildValue("B", extended_rcode);
+            return (Py_BuildValue("OO", edns_obj, extrcode_obj));
+        }
+        
+        Py_DECREF(edns_obj);
+        return (NULL);
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "Incorrect arguments for EDNS.create_from_rr()");
+    return (NULL);
+}
+
+} // end of anonymous namespace
+// end of EDNS
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_EDNS(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&edns_type) < 0) {
+        return (false);
+    }
+    Py_INCREF(&edns_type);
+    void* p = &edns_type;
+    PyModule_AddObject(mod, "EDNS", static_cast<PyObject*>(p));
+
+    addClassVariable(edns_type, "SUPPORTED_VERSION",
+                     Py_BuildValue("B", EDNS::SUPPORTED_VERSION));
+
+    return (true);
+}

+ 52 - 86
src/lib/dns/python/message_python.cc

@@ -26,7 +26,6 @@ static PyObject* po_MessageTooShort;
 static PyObject* po_InvalidMessageSection;
 static PyObject* po_InvalidMessageOperation;
 static PyObject* po_InvalidMessageUDPSize;
-static PyObject* po_DNSMessageBADVERS;
 
 //
 // Definition of the classes
@@ -120,7 +119,6 @@ static PyTypeObject messageflag_type = {
     0                                   // tp_version_tag
 };
 
-
 static int
 MessageFlag_init(s_MessageFlag* self UNUSED_PARAM,
                  PyObject* args UNUSED_PARAM)
@@ -486,6 +484,7 @@ static int Rcode_init(s_Rcode* self, PyObject* args);
 static void Rcode_destroy(s_Rcode* self);
 
 static PyObject* Rcode_getCode(s_Rcode* self);
+static PyObject* Rcode_getExtendedCode(const s_Rcode* self);
 static PyObject* Rcode_toText(s_Rcode* self);
 static PyObject* Rcode_str(PyObject* self);
 static PyObject* Rcode_NOERROR(s_Rcode* self);
@@ -509,6 +508,9 @@ static PyObject* Rcode_richcmp(s_Rcode* self, s_Rcode* other, int op);
 
 static PyMethodDef Rcode_methods[] = {
     { "get_code", reinterpret_cast<PyCFunction>(Rcode_getCode), METH_NOARGS, "Returns the code value" },
+    { "get_extended_code",
+      reinterpret_cast<PyCFunction>(Rcode_getExtendedCode),
+      METH_NOARGS, "Returns the extended code value." },
     { "to_text", reinterpret_cast<PyCFunction>(Rcode_toText), METH_NOARGS, "Returns the text representation" },
     { "NOERROR", reinterpret_cast<PyCFunction>(Rcode_NOERROR), METH_NOARGS | METH_STATIC, "Creates a NOERROR Rcode" },
     { "FORMERR", reinterpret_cast<PyCFunction>(Rcode_FORMERR), METH_NOARGS | METH_STATIC, "Creates a FORMERR Rcode" },
@@ -617,6 +619,11 @@ Rcode_getCode(s_Rcode* self) {
 }
 
 static PyObject*
+Rcode_getExtendedCode(const s_Rcode* self) {
+    return (Py_BuildValue("B", self->rcode->getExtendedCode()));
+}
+
+static PyObject*
 Rcode_toText(s_Rcode* self) {
     return (Py_BuildValue("s", self->rcode->toText().c_str()));
 }
@@ -973,16 +980,14 @@ static void Message_destroy(s_Message* self);
 static PyObject* Message_getHeaderFlag(s_Message* self, PyObject* args);
 static PyObject* Message_setHeaderFlag(s_Message* self, PyObject* args);
 static PyObject* Message_clearHeaderFlag(s_Message* self, PyObject* args);
-static PyObject* Message_isDNSSECSupported(s_Message* self);
-static PyObject* Message_setDNSSECSupported(s_Message* self, PyObject* args);
-static PyObject* Message_getUDPSize(s_Message* self);
-static PyObject* Message_setUDPSize(s_Message* self, PyObject* args);
 static PyObject* Message_getQid(s_Message* self);
 static PyObject* Message_setQid(s_Message* self, PyObject* args);
 static PyObject* Message_getRcode(s_Message* self);
 static PyObject* Message_setRcode(s_Message* self, PyObject* args);
 static PyObject* Message_getOpcode(s_Message* self);
 static PyObject* Message_setOpcode(s_Message* self, PyObject* args);
+static PyObject* Message_getEDNS(s_Message* self);
+static PyObject* Message_setEDNS(s_Message* self, PyObject* args);
 static PyObject* Message_getRRCount(s_Message* self, PyObject* args);
 // use direct iterators for these? (or simply lists for now?)
 static PyObject* Message_getQuestion(s_Message* self);
@@ -1019,35 +1024,6 @@ static PyMethodDef Message_methods[] = {
       "Sets the specified header flag bit to 0. The message must be in "
       "RENDER mode. If not, an InvalidMessageOperation is raised. "
       "Takes a MessageFlag object as the only argument." },
-    { "is_dnssec_supported", reinterpret_cast<PyCFunction>(Message_isDNSSECSupported), METH_NOARGS,
-      "Returns True if the message sender indicates DNSSEC is supported. "
-      "If EDNS is included, this corresponds to the value of the DO bit. "
-      "Otherwise, DNSSEC is considered not supported." },
-    { "set_dnssec_supported", reinterpret_cast<PyCFunction>(Message_setDNSSECSupported), METH_VARARGS,
-      "Specify whether DNSSEC is supported in the message. "
-      "The message must be in RENDER mode. If not, an "
-      "InvalidMessageOperation is raised."
-      "If EDNS is included in the message, the DO bit is set or cleared "
-      "according to given argument (True or False) of this method."},
-    { "get_udp_size", reinterpret_cast<PyCFunction>(Message_getUDPSize), METH_NOARGS,
-      "Return the maximum buffer size of UDP messages for the sender "
-      "of the message.\n\n"
-      "The semantics of this value is different based on the mode:\n"
-      "In the PARSE mode, it means the buffer size of the remote node;\n"
-      "in the RENDER mode, it means the buffer size of the local node.\n\n"
-      "In either case, its value is the value of the UDP payload size field "
-      "of EDNS (when it's included) or DEFAULT_MAX_UDPSIZE." },
-    { "set_udp_size", reinterpret_cast<PyCFunction>(Message_setUDPSize), METH_VARARGS,
-      "Specify the maximum buffer size of UDP messages of the local "
-      "node. If the message is not in RENDER mode, an "
-      "InvalidMessageOperation is raised.\n\n"
-      "If EDNS OPT RR is included in the message, its UDP payload size field "
-      "will be set to the specified value.\n"
-      "Unless explicitly specified, DEFAULT_MAX_UDPSIZE will be assumed "
-      "for the maximum buffer size, regardless of whether EDNS OPT RR is "
-      "included or not.  This means if an application wants to send a message "
-      "with an EDNS OPT RR for specifying a larger UDP size, it must explicitly "
-      "specify the value using this method. "},
     { "get_qid", reinterpret_cast<PyCFunction>(Message_getQid), METH_NOARGS,
       "Returns the query id" },
     { "set_qid", reinterpret_cast<PyCFunction>(Message_setQid), METH_VARARGS,
@@ -1066,6 +1042,12 @@ static PyMethodDef Message_methods[] = {
       "Sets the message opcode (an Opcode object).\n"
       "If the message is not in RENDER mode, an "
       "InvalidMessageOperation is raised."},
+    { "get_edns", reinterpret_cast<PyCFunction>(Message_getEDNS), METH_NOARGS,
+      "Return, if any, the EDNS associated with the message."
+    },
+    { "set_edns", reinterpret_cast<PyCFunction>(Message_setEDNS), METH_VARARGS,
+      "Set EDNS for the message."
+    },
     { "get_rr_count", reinterpret_cast<PyCFunction>(Message_getRRCount), METH_VARARGS,
       "Returns the number of RRs contained in the given section." },
     { "get_question", reinterpret_cast<PyCFunction>(Message_getQuestion), METH_NOARGS,
@@ -1248,57 +1230,6 @@ Message_clearHeaderFlag(s_Message* self, PyObject* args) {
 }
 
 static PyObject*
-Message_isDNSSECSupported(s_Message* self) {
-    if (self->message->isDNSSECSupported()) {
-        Py_RETURN_TRUE;
-    } else {
-        Py_RETURN_FALSE;
-    }
-}
-
-static PyObject*
-Message_setDNSSECSupported(s_Message* self, PyObject* args) {
-    PyObject *b;
-    if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &b)) {
-        return (NULL);
-    }
-    try {
-        if (b == Py_True) {
-            self->message->setDNSSECSupported(true);
-        } else {
-            self->message->setDNSSECSupported(false);
-        }
-        Py_RETURN_NONE;
-    } catch (const InvalidMessageOperation& imo) {
-        PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
-    }
-}
-
-static PyObject*
-Message_getUDPSize(s_Message* self) {
-    return (Py_BuildValue("I", self->message->getUDPSize()));
-}
-
-static PyObject*
-Message_setUDPSize(s_Message* self, PyObject* args) {
-    uint16_t size;
-    if (!PyArg_ParseTuple(args, "H", &size)) {
-        return (NULL);
-    }
-    try {
-        self->message->setUDPSize(size);
-        Py_RETURN_NONE;
-    } catch (const InvalidMessageUDPSize& imus) {
-        PyErr_SetString(po_InvalidMessageUDPSize, imus.what());
-        return (NULL);
-    } catch (const InvalidMessageOperation& imo) {
-        PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
-    }
-}
-
-static PyObject*
 Message_getQid(s_Message* self) {
     return (Py_BuildValue("I", self->message->getQid()));
 }
@@ -1386,6 +1317,41 @@ Message_setOpcode(s_Message* self, PyObject* args) {
 }
 
 static PyObject*
+Message_getEDNS(s_Message* self) {
+    s_EDNS* edns;
+    EDNS* edns_body;
+    ConstEDNSPtr src = self->message->getEDNS();
+
+    if (!src) {
+        Py_RETURN_NONE;
+    }
+    if ((edns_body = new(nothrow) EDNS(*src)) == NULL) {
+        return (PyErr_NoMemory());
+    }
+    edns = static_cast<s_EDNS*>(opcode_type.tp_alloc(&edns_type, 0));
+    if (edns != NULL) {
+        edns->edns = edns_body;
+    }
+
+    return (edns);
+}
+
+static PyObject*
+Message_setEDNS(s_Message* self, PyObject* args) {
+    s_EDNS* edns;
+    if (!PyArg_ParseTuple(args, "O!", &edns_type, &edns)) {
+        return (NULL);
+    }
+    try {
+        self->message->setEDNS(EDNSPtr(new EDNS(*edns->edns)));
+        Py_RETURN_NONE;
+    } catch (const InvalidMessageOperation& imo) {
+        PyErr_SetString(po_InvalidMessageOperation, imo.what());
+        return (NULL);
+    }
+}
+
+static PyObject*
 Message_getRRCount(s_Message* self, PyObject* args) {
     s_Section *section;
     if (!PyArg_ParseTuple(args, "O!", &section_type, &section)) {

+ 10 - 1
src/lib/dns/python/messagerenderer_python.cc

@@ -42,7 +42,7 @@ static PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
 // TODO: set/get compressmode
 static PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
 static PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
-
+static PyObject* MessageRenderer_clear(s_MessageRenderer* self);
 
 static PyMethodDef MessageRenderer_methods[] = {
     { "get_data", reinterpret_cast<PyCFunction>(MessageRenderer_getData), METH_NOARGS,
@@ -57,6 +57,9 @@ static PyMethodDef MessageRenderer_methods[] = {
       "Sets truncated to true" },
     { "set_length_limit", reinterpret_cast<PyCFunction>(MessageRenderer_setLengthLimit), METH_VARARGS,
       "Sets the length limit of the data to the given number" },
+    { "clear", reinterpret_cast<PyCFunction>(MessageRenderer_clear),
+      METH_NOARGS,
+      "Clear the internal buffer and other internal resources." },
     { NULL, NULL, 0, NULL }
 };
 
@@ -175,6 +178,12 @@ MessageRenderer_setLengthLimit(s_MessageRenderer* self,
     Py_RETURN_NONE;
 }
 
+static PyObject*
+MessageRenderer_clear(s_MessageRenderer* self) {
+    self->messagerenderer->clear();
+    Py_RETURN_NONE;
+}
+
 // end of MessageRenderer
 
 

+ 15 - 1
src/lib/dns/python/pydnspp.cc

@@ -40,8 +40,12 @@
 
 #include <dns/python/pydnspp_common.h>
 
-// For our 'general' isc::Exception
+// For our 'general' isc::Exceptions
 static PyObject* po_IscException;
+static PyObject* po_InvalidParameter;
+
+// For our own isc::dns::Exception
+static PyObject* po_DNSMessageBADVERS;
 
 // order is important here!
 #include <dns/python/messagerenderer_python.cc>
@@ -53,6 +57,7 @@ static PyObject* po_IscException;
 #include <dns/python/rrset_python.cc>          // needs Rdata, RRTTL
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
                                                // Name
+#include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
 #include <dns/python/message_python.cc>        // needs RRset, Question
 
 //
@@ -81,9 +86,14 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    // Add the exceptions to the class
     po_IscException = PyErr_NewException("pydnspp.IscException", NULL, NULL);
     PyModule_AddObject(mod, "IscException", po_IscException);
 
+    po_InvalidParameter = PyErr_NewException("pydnspp.InvalidParameter",
+                                             NULL, NULL);
+    PyModule_AddObject(mod, "InvalidParameter", po_InvalidParameter);
+
     // for each part included above, we call its specific initializer
 
     if (!initModulePart_Name(mod)) {
@@ -122,6 +132,10 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_EDNS(mod)) {
+        return (NULL);
+    }
+
     return (mod);
 }
 

+ 10 - 1
src/lib/dns/python/rdata_python.cc

@@ -146,12 +146,21 @@ Rdata_init(s_Rdata* self, PyObject* args) {
     s_RRType* rrtype;
     s_RRClass* rrclass;
     const char* s;
-    
+    const char* data;
+    Py_ssize_t len;
+
+    // Create from string
     if (PyArg_ParseTuple(args, "O!O!s", &rrtype_type, &rrtype,
                                         &rrclass_type, &rrclass,
                                         &s)) {
         self->rdata = createRdata(*rrtype->rrtype, *rrclass->rrclass, s);
         return (0);
+    } else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
+                                &rrclass_type, &rrclass, &data, &len)) {
+        InputBuffer input_buffer(data, len);
+        self->rdata = createRdata(*rrtype->rrtype, *rrclass->rrclass,
+                                  input_buffer, len);
+        return (0);
     }
 
     return (-1);

+ 4 - 3
src/lib/dns/python/tests/Makefile.am

@@ -1,4 +1,5 @@
-PYTESTS = message_python_test.py
+PYTESTS = edns_python_test.py
+PYTESTS += message_python_test.py
 PYTESTS += messagerenderer_python_test.py
 PYTESTS += name_python_test.py
 PYTESTS += question_python_test.py
@@ -23,8 +24,8 @@ PYCOVERAGE = $(PYTHON)
 check-local:
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
-	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
-	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
 	done

+ 180 - 0
src/lib/dns/python/tests/edns_python_test.py

@@ -0,0 +1,180 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+
+import unittest
+import os
+from pydnspp import *
+from testutil import *
+
+class EDNSTest(unittest.TestCase):
+
+    def setUp(self):
+        self.rrtype = RRType("OPT")
+        self.rrclass = RRClass(4096)
+        self.rrttl_do_on = RRTTL(0x00008000)
+        self.rrttl_do_off = RRTTL(0)
+        self.rrttl_badver = RRTTL(0x00018000)
+        self.opt_rdata = Rdata(self.rrtype, self.rrclass, bytes())
+        self.edns_base = EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                              self.rrttl_do_off, self.opt_rdata)
+
+    def test_badver_construct(self):
+        self.assertRaises(InvalidParameter, EDNS, 1)
+        self.assertRaises(TypeError, EDNS, 1, 2) # signature mismatch
+        self.assertRaises(TypeError, EDNS, 256) # invalid arguments
+
+    def test_dnssec_dobit(self):
+        edns = EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                    self.rrttl_do_on, self.opt_rdata)
+        self.assertTrue(edns.get_dnssec_awareness())
+
+        edns = EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                    self.rrttl_do_off, self.opt_rdata)
+        self.assertFalse(edns.get_dnssec_awareness())
+
+        edns = EDNS()
+        self.assertFalse(edns.get_dnssec_awareness())
+        edns.set_dnssec_awareness(True)
+        self.assertTrue(edns.get_dnssec_awareness())
+        edns.set_dnssec_awareness(False);
+        self.assertFalse(edns.get_dnssec_awareness())
+
+        self.assertRaises(TypeError, edns.set_dnssec_awareness, "wrong")
+        self.assertRaises(TypeError, edns.set_dnssec_awareness, 1)
+
+    def test_udpsize(self):
+        edns = EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                    self.rrttl_do_on, self.opt_rdata)
+        self.assertEqual(4096, edns.get_udp_size())
+
+        edns = EDNS()
+        edns.set_udp_size(511)
+        self.assertEqual(511, edns.get_udp_size())
+        edns.set_udp_size(0)
+        self.assertEqual(0, edns.get_udp_size())
+        edns.set_udp_size(65535)
+        self.assertEqual(65535, edns.get_udp_size())
+
+        self.assertRaises(TypeError, edns.set_udp_size, "wrong")
+
+        # Range check.  We need to do this at the binding level, so we need
+        # explicit tests for it.
+        self.assertRaises(OverflowError, edns.set_udp_size, 65536)
+        self.assertRaises(OverflowError, edns.set_udp_size, -1)
+
+    def test_get_version(self):
+        self.assertEqual(EDNS.SUPPORTED_VERSION, EDNS().get_version())
+
+    def test_bad_wiredata(self):
+        self.assertRaises(InvalidParameter, EDNS, Name.ROOT_NAME,
+                          self.rrclass, RRType("A"),
+                          self.rrttl_do_on, self.opt_rdata)
+        self.assertRaises(DNSMessageFORMERR, EDNS, Name("example.com"),
+                          self.rrclass, self.rrtype, self.rrttl_do_on,
+                          self.opt_rdata)
+        self.assertRaises(DNSMessageBADVERS, EDNS, Name.ROOT_NAME,
+                          self.rrclass, self.rrtype, self.rrttl_badver,
+                          self.opt_rdata)
+
+    def test_to_text(self):
+        edns = EDNS()
+        edns.set_udp_size(4096)
+        expected_str = "; EDNS: version: 0, flags:; udp: 4096\n"
+        self.assertEqual(expected_str, edns.to_text())
+        self.assertEqual(expected_str, str(edns))
+
+        edns.set_dnssec_awareness(True)
+        self.assertEqual("; EDNS: version: 0, flags: do; udp: 4096\n",
+                         edns.to_text())
+
+        self.assertEqual("; EDNS: version: 0, flags: do; udp: 4096\n",
+                         EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                              RRTTL(0x01008000), self.opt_rdata).to_text())
+
+        self.assertEqual("; EDNS: version: 0, flags: do; udp: 4096\n",
+                         EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                              RRTTL(0x00008001), self.opt_rdata).to_text())
+
+    def test_towire_renderer(self):
+        renderer = MessageRenderer()
+        extrcode_noerror = Rcode.NOERROR().get_extended_code()
+        extrcode_badvers = Rcode.BADVERS().get_extended_code()
+
+        self.assertEqual(1, self.edns_base.to_wire(renderer, extrcode_noerror))
+        wiredata = read_wire_data("edns_toWire1.wire")
+        self.assertEqual(wiredata, renderer.get_data())
+
+        renderer.clear()
+        self.edns_base.set_dnssec_awareness(True)
+        self.assertEqual(1, self.edns_base.to_wire(renderer, extrcode_noerror))
+        wiredata = read_wire_data("edns_toWire2.wire")
+        self.assertEqual(wiredata, renderer.get_data())
+
+        renderer.clear()
+        self.edns_base.set_dnssec_awareness(True)
+        self.assertEqual(1, self.edns_base.to_wire(renderer, extrcode_badvers))
+        wiredata = read_wire_data("edns_toWire3.wire")
+        self.assertEqual(wiredata, renderer.get_data())
+
+        renderer.clear()
+        self.edns_base.set_dnssec_awareness(True)
+        self.edns_base.set_udp_size(511)
+        self.assertEqual(1, self.edns_base.to_wire(renderer, extrcode_noerror))
+        wiredata = read_wire_data("edns_toWire4.wire")
+        self.assertEqual(wiredata, renderer.get_data())
+
+        renderer.clear()
+        edns = EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                    RRTTL(0x00008001), self.opt_rdata)
+        self.assertEqual(1, edns.to_wire(renderer, extrcode_noerror))
+        wiredata = read_wire_data("edns_toWire2.wire")
+        self.assertEqual(wiredata, renderer.get_data())
+
+        renderer.clear()
+        renderer.set_length_limit(10)
+        self.edns_base.set_dnssec_awareness(True)
+        self.assertEqual(0, self.edns_base.to_wire(renderer, extrcode_noerror))
+        self.assertEqual(0, renderer.get_length())
+
+    def test_towire_buffer(self):
+        extrcode_noerror = Rcode.NOERROR().get_extended_code()
+
+        obuffer = bytes()
+        obuffer = self.edns_base.to_wire(obuffer, extrcode_noerror)
+        wiredata = read_wire_data("edns_toWire1.wire")
+        self.assertEqual(wiredata, obuffer)
+
+    def test_create_from_rr(self):
+        (edns, extrcode) = EDNS.create_from_rr(Name.ROOT_NAME, self.rrclass,
+                                               self.rrtype, self.rrttl_do_on,
+                                               self.opt_rdata)
+        self.assertEqual(EDNS.SUPPORTED_VERSION, edns.get_version())
+        self.assertTrue(edns.get_dnssec_awareness())
+        self.assertEqual(4096, edns.get_udp_size())
+        self.assertEqual(0, extrcode)
+
+        (edns, extrcode) = EDNS.create_from_rr(Name.ROOT_NAME, self.rrclass,
+                                               self.rrtype, RRTTL(0x01008000),
+                                               self.opt_rdata)
+        self.assertEqual(1, extrcode)
+
+        self.assertRaises(DNSMessageBADVERS, EDNS.create_from_rr,
+                          Name.ROOT_NAME, self.rrclass, self.rrtype,
+                          self.rrttl_badver, self.opt_rdata)
+
+if __name__ == '__main__':
+    unittest.main()

+ 19 - 114
src/lib/dns/python/tests/message_python_test.py

@@ -20,7 +20,7 @@
 import unittest
 import os
 from pydnspp import *
-
+from testutil import *
 
 class MessageFlagTest(unittest.TestCase):
     def test_init(self):
@@ -240,19 +240,6 @@ if "TESTDATA_PATH" in os.environ:
 else:
     testdata_path = "../tests/testdata"
 
-def read_wire_data(filename):
-    data = bytes()
-    file = open(testdata_path + os.sep + filename, "r")
-    for line in file:
-        line = line.strip()
-        if line == "" or line.startswith("#"):
-            pass
-        else:
-            cur_data = bytes.fromhex(line)
-            data += cur_data
-
-    return data
-
 def factoryFromFile(message, file):
     data = read_wire_data(file)
     message.from_wire(data)
@@ -316,28 +303,6 @@ class MessageTest(unittest.TestCase):
         self.assertRaises(InvalidMessageOperation,
                           self.p.clear_header_flag, MessageFlag.AA())
 
-    def test_set_DNSSEC_supported(self):
-        self.assertRaises(TypeError, self.r.set_dnssec_supported, "wrong")
-
-        self.assertFalse(self.r.is_dnssec_supported())
-        self.r.set_dnssec_supported(True)
-        self.assertTrue(self.r.is_dnssec_supported())
-        self.r.set_dnssec_supported(False)
-        self.assertFalse(self.r.is_dnssec_supported())
-
-        self.assertRaises(InvalidMessageOperation,
-                          self.p.set_dnssec_supported, True)
-        self.assertRaises(InvalidMessageOperation,
-                          self.p.set_dnssec_supported, False)
-
-    def test_set_udp_size(self):
-        self.assertRaises(TypeError, self.r.set_udp_size, "wrong")
-        self.assertRaises(InvalidMessageUDPSize, self.r.set_udp_size, 0)
-        self.assertRaises(InvalidMessageUDPSize, self.r.set_udp_size, 65536)
-        self.assertRaises(InvalidMessageOperation, self.p.set_udp_size, 1024)
-        self.r.set_udp_size(2048)
-        self.assertEqual(2048, self.r.get_udp_size())
-
     def test_set_qid(self):
         self.assertRaises(TypeError, self.r.set_qid, "wrong")
         self.assertRaises(InvalidMessageOperation,
@@ -366,6 +331,24 @@ class MessageTest(unittest.TestCase):
         self.assertRaises(InvalidMessageOperation,
                           self.p.set_opcode, opcode)
 
+    def test_get_edns(self):
+        self.assertEqual(None, self.p.get_edns())
+
+        message_parse = Message(Message.PARSE)
+        factoryFromFile(message_parse, "message_fromWire10")
+        edns = message_parse.get_edns()
+        self.assertEqual(0, edns.get_version())
+        self.assertEqual(4096, edns.get_udp_size())
+        self.assertTrue(edns.get_dnssec_awareness())
+
+    def test_set_edns(self):
+        self.assertRaises(InvalidMessageOperation, self.p.set_edns, EDNS())
+
+        edns = EDNS()
+        edns.set_udp_size(1024)
+        self.r.set_edns(edns)
+        self.assertEqual(1024, self.r.get_edns().get_udp_size())
+
     def test_get_section(self):
         self.assertRaises(TypeError, self.r.get_section, "wrong")
 
@@ -487,84 +470,6 @@ test.example.com. 3600 IN A 192.0.2.2
         self.assertEqual("192.0.2.2", rdata[1].to_text())
         self.assertEqual(2, len(rdata))
 
-    def test_GetEDNS0DOBit(self):
-        message_parse = Message(Message.PARSE)
-        ## Without EDNS0, DNSSEC is considered to be unsupported.
-        factoryFromFile(message_parse, "message_fromWire1")
-        self.assertFalse(message_parse.is_dnssec_supported())
-    
-        ## If DO bit is on, DNSSEC is considered to be supported.
-        message_parse.clear(Message.PARSE)
-        factoryFromFile(message_parse, "message_fromWire2")
-        self.assertTrue(message_parse.is_dnssec_supported())
-    
-        ## If DO bit is off, DNSSEC is considered to be unsupported.
-        message_parse.clear(Message.PARSE)
-        factoryFromFile(message_parse, "message_fromWire3")
-        self.assertFalse(message_parse.is_dnssec_supported())
-    
-    def test_SetEDNS0DOBit(self):
-        # By default, it's false, and we can enable/disable it.
-        message_parse = Message(Message.PARSE)
-        message_render = Message(Message.RENDER)
-        self.assertFalse(message_render.is_dnssec_supported())
-        message_render.set_dnssec_supported(True)
-        self.assertTrue(message_render.is_dnssec_supported())
-        message_render.set_dnssec_supported(False)
-        self.assertFalse(message_render.is_dnssec_supported())
-    
-        ## A message in the parse mode doesn't allow this flag to be set.
-        self.assertRaises(InvalidMessageOperation,
-                          message_parse.set_dnssec_supported,
-                          True)
-        ## Once converted to the render mode, it works as above
-        message_parse.make_response()
-        self.assertFalse(message_parse.is_dnssec_supported())
-        message_parse.set_dnssec_supported(True)
-        self.assertTrue(message_parse.is_dnssec_supported())
-        message_parse.set_dnssec_supported(False)
-        self.assertFalse(message_parse.is_dnssec_supported())
-    
-    def test_GetEDNS0UDPSize(self):
-        # Without EDNS0, the default max UDP size is used.
-        message_parse = Message(Message.PARSE)
-        factoryFromFile(message_parse, "message_fromWire1")
-        self.assertEqual(Message.DEFAULT_MAX_UDPSIZE, message_parse.get_udp_size())
-    
-        ## If the size specified in EDNS0 > default max, use it.
-        message_parse.clear(Message.PARSE)
-        factoryFromFile(message_parse, "message_fromWire2")
-        self.assertEqual(4096, message_parse.get_udp_size())
-    
-        ## If the size specified in EDNS0 < default max, keep using the default.
-        message_parse.clear(Message.PARSE)
-        factoryFromFile(message_parse, "message_fromWire8")
-        self.assertEqual(Message.DEFAULT_MAX_UDPSIZE, message_parse.get_udp_size())
-    
-    def test_SetEDNS0UDPSize(self):
-        # The default size if unspecified
-        message_render = Message(Message.RENDER)
-        message_parse = Message(Message.PARSE)
-        self.assertEqual(Message.DEFAULT_MAX_UDPSIZE, message_render.get_udp_size())
-        # A common buffer size with EDNS, should succeed
-        message_render.set_udp_size(4096)
-        self.assertEqual(4096, message_render.get_udp_size())
-        # Unusual large value, but accepted
-        message_render.set_udp_size(0xffff)
-        self.assertEqual(0xffff, message_render.get_udp_size())
-        # Too small is value is rejected
-        self.assertRaises(InvalidMessageUDPSize, message_render.set_udp_size, 511)
-    
-        # A message in the parse mode doesn't allow the set operation.
-        self.assertRaises(InvalidMessageOperation, message_parse.set_udp_size, 4096)
-        ## Once converted to the render mode, it works as above.
-        message_parse.make_response()
-        message_parse.set_udp_size(4096)
-        self.assertEqual(4096, message_parse.get_udp_size())
-        message_parse.set_udp_size(0xffff)
-        self.assertEqual(0xffff, message_parse.get_udp_size())
-        self.assertRaises(InvalidMessageUDPSize, message_parse.set_udp_size, 511)
-    
     def test_EDNS0ExtCode(self):
         # Extended Rcode = BADVERS
         message_parse = Message(Message.PARSE)

+ 1 - 14
src/lib/dns/python/tests/question_python_test.py

@@ -20,25 +20,13 @@
 import unittest
 import os
 from pydnspp import *
+from testutil import *
 
 if "TESTDATA_PATH" in os.environ:
     testdata_path = os.environ["TESTDATA_PATH"]
 else:
     testdata_path = "../tests/testdata"
 
-def read_wire_data(filename):
-    data = bytes()
-    file = open(testdata_path + os.sep + filename, "r")
-    for line in file:
-        line = line.strip()
-        if line == "" or line.startswith("#"):
-            pass
-        else:
-            cur_data = bytes.fromhex(line)
-            data += cur_data
-
-    return data
-
 def question_from_wire(file, position = 0):
     data = read_wire_data(file)
     return Question(data, position)
@@ -102,7 +90,6 @@ class QuestionTest(unittest.TestCase):
         wiredata = read_wire_data("question_toWire2")
         self.assertEqual(renderer.get_data(), wiredata)
         self.assertRaises(TypeError, self.test_question1.to_wire, 1)
-    
 
 if __name__ == '__main__':
     unittest.main()

+ 45 - 0
src/lib/dns/python/tests/testutil.py

@@ -0,0 +1,45 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+
+#
+# helper functions for tests taken from C++ unittests
+#
+
+import os
+
+if "TESTDATA_PATH" in os.environ:
+    testdata_path = os.environ["TESTDATA_PATH"]
+else:
+    testdata_path = "../tests/testdata"
+
+def read_wire_data(filename):
+    data = bytes()
+    for path in testdata_path.split(":"):
+        try:
+            file = open(path + os.sep + filename, "r")
+            for line in file:
+                line = line.strip()
+                if line == "" or line.startswith("#"):
+                    pass
+                else:
+                    cur_data = bytes.fromhex(line)
+                    data += cur_data
+
+            return data
+        except IOError:
+            pass
+    raise ValueError("Data file " + filename + "cannot be found")

+ 6 - 1
src/lib/dns/tests/Makefile.am

@@ -1,6 +1,9 @@
+SUBDIRS = testdata .
+
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(top_builddir)/src/lib/dns/tests/testdata\"
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 if USE_STATIC_LINK
@@ -14,6 +17,7 @@ if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES = unittest_util.h unittest_util.cc
 run_unittests_SOURCES += buffer_unittest.cc name_unittest.cc
+run_unittests_SOURCES += edns_unittest.cc
 run_unittests_SOURCES += messagerenderer_unittest.cc
 run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
 run_unittests_SOURCES += rrttl_unittest.cc
@@ -53,6 +57,7 @@ noinst_PROGRAMS = $(TESTS)
 # NOTE: keep this in sync with real file listing
 # so is included in tarball
 EXTRA_DIST = testdata/gen-wiredata.py.in
+EXTRA_DIST += testdata/edns_toWire1.spec
 EXTRA_DIST += testdata/message_fromWire1
 EXTRA_DIST += testdata/message_fromWire10
 EXTRA_DIST += testdata/message_fromWire10.spec

+ 264 - 0
src/lib/dns/tests/edns_unittest.cc

@@ -0,0 +1,264 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <sstream>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include "unittest_util.h"
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+const uint8_t EDNS::SUPPORTED_VERSION;
+
+namespace {
+class EDNSTest : public ::testing::Test {
+protected:
+    EDNSTest() : rrtype(RRType::OPT()), buffer(NULL, 0), obuffer(0),
+                 renderer(obuffer), rcode(0)
+    {
+        opt_rdata = ConstRdataPtr(new generic::OPT());
+        edns_base.setUDPSize(4096);
+    }
+    RRType rrtype;
+    EDNS edns_base;
+    ConstEDNSPtr edns;
+    InputBuffer buffer;
+    OutputBuffer obuffer;
+    MessageRenderer renderer;
+    ConstRdataPtr opt_rdata;
+    Rcode rcode;
+    vector<unsigned char> wiredata;
+};
+
+// RRClass of EDNS OPT means UDP buffer size
+const RRClass rrclass(4096);
+// RRTTL of EDNS OPT encodes extended-rcode, version, and DO bit
+const RRTTL rrttl_do_on(0x00008000); // DO=on
+const RRTTL rrttl_do_off(0);         // DO=off
+const RRTTL rrttl_badver(0x00018000); // version=1, DO=on
+
+TEST_F(EDNSTest, badVerConstruct) {
+    EXPECT_THROW(EDNS(1), isc::InvalidParameter);
+}
+
+TEST_F(EDNSTest, DNSSECDOBit) {
+    // tests for EDNS from RR
+
+    // DO bit is on, so DNSSEC should be considered to be supported.
+    EXPECT_TRUE(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                     rrttl_do_on, *opt_rdata).getDNSSECAwareness());
+
+    // DO bit is off.  DNSSEC should be considered to be unsupported.
+    EXPECT_FALSE(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                      rrttl_do_off, *opt_rdata).getDNSSECAwareness());
+
+    // tests for EDNS constructed by hand
+    EXPECT_FALSE(edns_base.getDNSSECAwareness()); // false by default
+    edns_base.setDNSSECAwareness(true);          // enable by hand
+    EXPECT_TRUE(edns_base.getDNSSECAwareness());
+    edns_base.setDNSSECAwareness(false); // disable by hand
+    EXPECT_FALSE(edns_base.getDNSSECAwareness());
+}
+
+TEST_F(EDNSTest, UDPSize) {
+    EXPECT_EQ(4096, EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on,
+                         *opt_rdata).getUDPSize());
+
+    // EDNS UDP size smaller than the traditional max, 512.  Unusual, but
+    // not prohibited.
+    edns_base.setUDPSize(511);
+    EXPECT_EQ(511, edns_base.getUDPSize());
+
+    // Even 0 is okay.
+    edns_base.setUDPSize(0);
+    EXPECT_EQ(0, edns_base.getUDPSize());
+
+    // Possible max value is also okay, although the higher layer app may
+    // adjust it to a reasonably lower value
+    edns_base.setUDPSize(65535);
+    EXPECT_EQ(65535, edns_base.getUDPSize());
+}
+
+TEST_F(EDNSTest, getVersion) {
+    // Constructed by hand
+    EXPECT_EQ(EDNS::SUPPORTED_VERSION, EDNS().getVersion());
+
+    // From RR
+    EXPECT_EQ(EDNS::SUPPORTED_VERSION,
+              EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on,
+                   *opt_rdata).getVersion());
+}
+
+TEST_F(EDNSTest, BadWireData) {
+    // Incompatible RR type
+    EXPECT_THROW(EDNS(Name::ROOT_NAME(), rrclass, RRType::A(),
+                      rrttl_do_on, *opt_rdata), isc::InvalidParameter);
+
+    // OPT RR of a non root name
+    EXPECT_THROW(EDNS(Name("example.com"), rrclass, rrtype,
+                      rrttl_do_on, *opt_rdata), DNSMessageFORMERR);
+                 
+    // Unsupported Version
+    EXPECT_THROW(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                      rrttl_badver, *opt_rdata), DNSMessageBADVERS);
+}
+
+TEST_F(EDNSTest, toText) {
+    // Typical case, disabling DNSSEC
+    EXPECT_EQ("; EDNS: version: 0, flags:; udp: 4096\n",
+              EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_off,
+                   *opt_rdata).toText());
+
+    // Typical case, enabling DNSSEC
+    EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n",
+              EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on,
+                   *opt_rdata).toText());
+
+    // Non-0 extended Rcode: ignored in the toText() output.
+    EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n",
+              EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                   RRTTL(0x01008000), *opt_rdata).toText());
+
+    // Unknown flag: ignored in the toText() output.
+    EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n",
+              EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                   RRTTL(0x00008001), *opt_rdata).toText());
+}
+
+TEST_F(EDNSTest, toWireRenderer) {
+    // Typical case, (explicitly) disabling DNSSEC
+    edns_base.setDNSSECAwareness(false);
+    EXPECT_EQ(1, edns_base.toWire(renderer,
+                                  Rcode::NOERROR().getExtendedCode()));
+    UnitTestUtil::readWireData("edns_toWire1.wire", wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &wiredata[0], wiredata.size());
+
+    // Typical case, enabling DNSSEC
+    renderer.clear();
+    wiredata.clear();
+    edns_base.setDNSSECAwareness(true);
+    EXPECT_EQ(1, edns_base.toWire(renderer,
+                                  Rcode::NOERROR().getExtendedCode()));
+    UnitTestUtil::readWireData("edns_toWire2.wire", wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &wiredata[0], wiredata.size());
+
+    // Non-0 extended Rcode
+    renderer.clear();
+    wiredata.clear();
+    edns_base.setDNSSECAwareness(true);
+    EXPECT_EQ(1, edns_base.toWire(renderer,
+                                  Rcode::BADVERS().getExtendedCode()));
+    UnitTestUtil::readWireData("edns_toWire3.wire", wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &wiredata[0], wiredata.size());
+
+    // Uncommon UDP buffer size
+    renderer.clear();
+    wiredata.clear();
+    edns_base.setDNSSECAwareness(true);
+    edns_base.setUDPSize(511);
+    EXPECT_EQ(1, edns_base.toWire(renderer,
+                                  Rcode::NOERROR().getExtendedCode()));
+    UnitTestUtil::readWireData("edns_toWire4.wire", wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &wiredata[0], wiredata.size());
+
+    // From RR with unknown flag.  If used for toWire(), the unknown flag
+    // should disappear.
+    renderer.clear();
+    wiredata.clear();
+    EXPECT_EQ(1, EDNS(Name::ROOT_NAME(), rrclass, rrtype, RRTTL(0x00008001),
+                      *opt_rdata).toWire(renderer,
+                                         Rcode::NOERROR().getExtendedCode()));
+    UnitTestUtil::readWireData("edns_toWire2.wire", wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &wiredata[0], wiredata.size());
+
+    // If the available length in the renderer is not sufficient for the OPT
+    // RR, it shouldn't be inserted.
+    renderer.clear();
+    renderer.setLengthLimit(10); // 10 = minimum length of OPT RR - 1
+    edns_base.setDNSSECAwareness(true);
+    edns_base.setUDPSize(4096);
+    EXPECT_EQ(0, edns_base.toWire(renderer,
+                                  Rcode::NOERROR().getExtendedCode()));
+    // renderer should be intact
+    EXPECT_EQ(0, renderer.getLength());
+}
+
+TEST_F(EDNSTest, toWireBuffer) {
+    // "to renderer" and "to buffer" should generally produce the same result.
+    // for simplicity we only check one typical case to confirm that.
+    EXPECT_EQ(1, edns_base.toWire(obuffer,
+                                  Rcode::NOERROR().getExtendedCode()));
+    UnitTestUtil::readWireData("edns_toWire1.wire", wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
+                        obuffer.getLength(), &wiredata[0], wiredata.size());
+}
+
+TEST_F(EDNSTest, createFromRR) {
+    uint8_t extended_rcode;
+
+    // normal case
+    edns = ConstEDNSPtr(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype,
+                                         rrttl_do_on, *opt_rdata,
+                                         extended_rcode));
+    EXPECT_EQ(EDNS::SUPPORTED_VERSION, edns->getVersion());
+    EXPECT_TRUE(edns->getDNSSECAwareness());
+    EXPECT_EQ(4096, edns->getUDPSize());
+    EXPECT_EQ(0, static_cast<int>(extended_rcode));
+
+    // non-0 extended rcode
+    extended_rcode = 0;
+    ConstEDNSPtr(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype,
+                                  RRTTL(0x01008000), *opt_rdata,
+                                  extended_rcode));
+    EXPECT_EQ(1, static_cast<int>(extended_rcode));
+
+    // creation triggers an exception.  extended_rcode must be intact.
+    extended_rcode = 0;
+    EXPECT_THROW(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype,
+                                  rrttl_badver, *opt_rdata, extended_rcode),
+                 DNSMessageBADVERS);
+    EXPECT_EQ(0, static_cast<int>(extended_rcode));
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST_F(EDNSTest, LeftShiftOperator) {
+    ostringstream oss;
+    oss << edns_base;
+    EXPECT_EQ(edns_base.toText(), oss.str());
+}
+}

+ 21 - 89
src/lib/dns/tests/message_unittest.cc

@@ -17,6 +17,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/buffer.h>
+#include <dns/edns.h>
 #include <dns/exceptions.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -93,6 +94,25 @@ TEST_F(MessageTest, RcodeToText) {
     EXPECT_EQ("4095", Rcode(Rcode(0xfff)).toText());
 }
 
+TEST_F(MessageTest, getEDNS) {
+    EXPECT_FALSE(message_parse.getEDNS()); // by default EDNS isn't set
+
+    factoryFromFile(message_parse, "message_fromWire10");
+    EXPECT_TRUE(message_parse.getEDNS());
+    EXPECT_EQ(0, message_parse.getEDNS()->getVersion());
+    EXPECT_EQ(4096, message_parse.getEDNS()->getUDPSize());
+    EXPECT_TRUE(message_parse.getEDNS()->getDNSSECAwareness());
+}
+
+TEST_F(MessageTest, setEDNS) {
+    // setEDNS() isn't allowed in the parse mode
+    EXPECT_THROW(message_parse.setEDNS(EDNSPtr(new EDNS())),
+                 InvalidMessageOperation);
+
+    EDNSPtr edns = EDNSPtr(new EDNS());
+    message_render.setEDNS(edns);
+    EXPECT_EQ(edns, message_render.getEDNS());
+}
 
 TEST_F(MessageTest, fromWire) {
     factoryFromFile(message_parse, "message_fromWire1");
@@ -127,82 +147,7 @@ TEST_F(MessageTest, fromWire) {
     EXPECT_TRUE(it->isLast());
 }
 
-TEST_F(MessageTest, GetEDNS0DOBit) {
-    // Without EDNS0, DNSSEC is considered to be unsupported.
-    factoryFromFile(message_parse, "message_fromWire1");
-    EXPECT_FALSE(message_parse.isDNSSECSupported());
-
-    // If DO bit is on, DNSSEC is considered to be supported.
-    message_parse.clear(Message::PARSE);
-    factoryFromFile(message_parse, "message_fromWire2");
-    EXPECT_TRUE(message_parse.isDNSSECSupported());
-
-    // If DO bit is off, DNSSEC is considered to be unsupported.
-    message_parse.clear(Message::PARSE);
-    factoryFromFile(message_parse, "message_fromWire3");
-    EXPECT_FALSE(message_parse.isDNSSECSupported());
-}
-
-TEST_F(MessageTest, SetEDNS0DOBit) {
-    // By default, it's false, and we can enable/disable it.
-    EXPECT_FALSE(message_render.isDNSSECSupported());
-    message_render.setDNSSECSupported(true);
-    EXPECT_TRUE(message_render.isDNSSECSupported());
-    message_render.setDNSSECSupported(false);
-    EXPECT_FALSE(message_render.isDNSSECSupported());
-
-    // A message in the parse mode doesn't allow this flag to be set.
-    EXPECT_THROW(message_parse.setDNSSECSupported(true),
-                 InvalidMessageOperation);
-    // Once converted to the render mode, it works as above
-    message_parse.makeResponse();
-    EXPECT_FALSE(message_parse.isDNSSECSupported());
-    message_parse.setDNSSECSupported(true);
-    EXPECT_TRUE(message_parse.isDNSSECSupported());
-    message_parse.setDNSSECSupported(false);
-    EXPECT_FALSE(message_parse.isDNSSECSupported());
-}
-
-TEST_F(MessageTest, GetEDNS0UDPSize) {
-    // Without EDNS0, the default max UDP size is used.
-    factoryFromFile(message_parse, "message_fromWire1");
-    EXPECT_EQ(Message::DEFAULT_MAX_UDPSIZE, message_parse.getUDPSize());
-
-    // If the size specified in EDNS0 > default max, use it.
-    message_parse.clear(Message::PARSE);
-    factoryFromFile(message_parse, "message_fromWire2");
-    EXPECT_EQ(4096, message_parse.getUDPSize());
-
-    // If the size specified in EDNS0 < default max, keep using the default.
-    message_parse.clear(Message::PARSE);
-    factoryFromFile(message_parse, "message_fromWire8");
-    EXPECT_EQ(Message::DEFAULT_MAX_UDPSIZE, message_parse.getUDPSize());
-}
-
-TEST_F(MessageTest, SetEDNS0UDPSize) {
-    // The default size if unspecified
-    EXPECT_EQ(Message::DEFAULT_MAX_UDPSIZE, message_render.getUDPSize());
-    // A common buffer size with EDNS, should succeed
-    message_render.setUDPSize(4096);
-    EXPECT_EQ(4096, message_render.getUDPSize());
-    // Unusual large value, but accepted
-    message_render.setUDPSize(0xffff);
-    EXPECT_EQ(0xffff, message_render.getUDPSize());
-    // Too small is value is rejected
-    EXPECT_THROW(message_render.setUDPSize(511), InvalidMessageUDPSize);
-
-    // A message in the parse mode doesn't allow the set operation.
-    EXPECT_THROW(message_parse.setUDPSize(4096), InvalidMessageOperation);
-    // Once converted to the render mode, it works as above.
-    message_parse.makeResponse();
-    message_parse.setUDPSize(4096);
-    EXPECT_EQ(4096, message_parse.getUDPSize());
-    message_parse.setUDPSize(0xffff);
-    EXPECT_EQ(0xffff, message_parse.getUDPSize());
-    EXPECT_THROW(message_parse.setUDPSize(511), InvalidMessageUDPSize);
-}
-
-TEST_F(MessageTest, EDNS0ExtCode) {
+TEST_F(MessageTest, EDNS0ExtRcode) {
     // Extended Rcode = BADVERS
     factoryFromFile(message_parse, "message_fromWire10");
     EXPECT_EQ(Rcode::BADVERS(), message_parse.getRcode());
@@ -221,19 +166,6 @@ TEST_F(MessageTest, BadEDNS0) {
     message_parse.clear(Message::PARSE);
     EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire5"),
                  DNSMessageFORMERR);
-    // OPT RR of a non root name
-    message_parse.clear(Message::PARSE);
-    EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire6"),
-                 DNSMessageFORMERR);
-    // Compressed owner name of OPT RR points to a root name.
-    // Not necessarily bogus, but very unusual and mostly pathological.
-    // We accept it, but is it okay?
-    message_parse.clear(Message::PARSE);
-    EXPECT_NO_THROW(factoryFromFile(message_parse, "message_fromWire7"));
-    // Unsupported Version
-    message_parse.clear(Message::PARSE);
-    EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire9"),
-                 DNSMessageBADVERS);
 }
 
 TEST_F(MessageTest, toWire) {

+ 2 - 1
src/lib/dns/tests/run_unittests.cc

@@ -21,7 +21,8 @@
 int
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
-    isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+    isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+    isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
 
     return (RUN_ALL_TESTS());
 }

+ 7 - 0
src/lib/dns/tests/testdata/Makefile.am

@@ -0,0 +1,7 @@
+CLEANFILES = *.wire
+
+BUILT_SOURCES = edns_toWire1.wire edns_toWire2.wire edns_toWire3.wire
+BUILT_SOURCES += edns_toWire4.wire
+
+.spec.wire:
+	./gen-wiredata.py -o $@ $<

+ 5 - 0
src/lib/dns/tests/testdata/edns_toWire1.spec

@@ -0,0 +1,5 @@
+#
+# A simplest form of EDNS: all default parameters
+#
+[edns]
+

+ 5 - 0
src/lib/dns/tests/testdata/edns_toWire2.spec

@@ -0,0 +1,5 @@
+#
+# Same as edns_toWire1 but setting the DO bit
+#
+[edns]
+do: 1

+ 7 - 0
src/lib/dns/tests/testdata/edns_toWire3.spec

@@ -0,0 +1,7 @@
+#
+# Same as edns_toWire1 but setting the DO bit, and extended Rcode being non 0
+# (for BADVER)
+#
+[edns]
+do: 1
+extrcode: 0x1

+ 6 - 0
src/lib/dns/tests/testdata/edns_toWire4.spec

@@ -0,0 +1,6 @@
+#
+# Same as edns_toWire1 but setting the DO bit
+#
+[edns]
+do: 1
+udpsize = 511

+ 9 - 4
src/lib/exceptions/exceptions.h

@@ -103,20 +103,25 @@ private:
     const std::string what_;
 };
 
-///
 /// \brief A generic exception that is thrown if a parameter given
 /// to a method would refer to or modify out-of-range data.
-///
 class OutOfRange : public Exception {
 public:
     OutOfRange(const char* file, size_t line, const char* what) :
         isc::Exception(file, line, what) {}
 };
 
-///
+/// \brief A generic exception that is thrown if a parameter given
+/// to a method or function is considered invalid and no other specific
+/// exceptions are suitable to describe the error.
+class InvalidParameter : public Exception {
+public:
+    InvalidParameter(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// \brief A generic exception that is thrown if a parameter given
 /// to a method is considered invalid in that context.
-///
 class BadValue : public Exception {
 public:
     BadValue(const char* file, size_t line, const char* what) :