Browse Source

an initial cut of the EDNS class (code + tests), mainly for backup.

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac311@2794 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
4915b040ed

+ 1 - 0
configure.ac

@@ -452,6 +452,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());
     }
@@ -292,14 +292,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->isDNSSECSupported();
+    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->setDNSSECSupported(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

@@ -444,13 +444,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()->isDNSSECSupported());
 }
 
 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

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

@@ -0,0 +1,176 @@
+// 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 {
+//           0             7               15                              31
+//          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//          | EXTENDED-RCODE|    VERSION     |D|               Z             |
+//          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//                                          <= VERSION_SHIFT (16 bits)
+//EXTFLAG_DO:0 0 0 ....................... 0 1 0 0 0 0.....................0
+const uint32_t VERSION_MASK = 0x00ff0000;
+const unsigned int VERSION_SHIFT = 16;
+const uint32_t EXTFLAG_DO = 0x00008000;
+
+//     0             7       11                                      31
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    | EXTENDED-RCODE| RCODE  |                                       |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//                    <= EXTRCODE_SHIFT
+const uint32_t EXTRCODE_MASK = 0xff000000;
+const unsigned int EXTRCODE_SHIFT = 24;
+}
+
+EDNS::EDNS(const uint8_t version) :
+    version_(version),
+    udp_size_(Message::DEFAULT_MAX_UDPSIZE),
+    dnssec_ok_(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_ok_ = ((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 (isDNSSECSupported()) {
+        ret += " do";
+    }
+    ret += "; udp: " + lexical_cast<string>(getUDPSize());
+    
+    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_ok_) {
+        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 = RRsetPtr(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

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

@@ -0,0 +1,149 @@
+// 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;
+typedef boost::shared_ptr<const EDNS> ConstEDNSPtr;
+
+/// Document why this is separated from \c Message.
+///
+/// Stores normalized information: unknown flags will be ignored on
+/// construction.
+///
+/// Decision: separate the knowledge about the relationship with rcode and
+/// the wire format.  EDNS is only responsible for the 8-bit part of the
+/// 12-bit extended RCODE.
+///
+/// note: extended Rcode is handled in the Message class.  We may want to
+/// generalize this by passing the whole Message and letting the \c EDNS
+/// object modify or refer to it based on the EDNS protocol.  But it would
+/// couple the \c Message and \c EDNS more tightly.  Right now, our decision
+/// is to consider \c Rcode is a special case; 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.
+///
+/// MEMO: performance consideration: toWire() can be optimized by caching
+/// the rendered image and reuse EDNS
+///
+///TBD: how to manage options is an open issue.
+class EDNS {
+public:
+    explicit EDNS(const uint8_t version = SUPPORTED_VERSION);
+
+    EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+         const RRTTL& ttl, const rdata::Rdata& rdata);
+
+    /// \brief Returns the version of EDNS.
+    uint8_t getVersion() const { return (version_); }
+
+    /// \brief Return the maximum buffer size of UDP messages for the sender
+    /// of the message.
+    uint16_t getUDPSize() const { return (udp_size_); }
+
+    /// \brief Specify the maximum buffer size of UDP messages that use
+    /// this EDNS.
+    ///
+    /// 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(const uint16_t udp_size) { udp_size_ = udp_size; }
+
+    /// \brief Return whether the message sender indicates DNSSEC is supported.
+    ///
+    /// \return TBD
+    bool isDNSSECSupported() const { return (dnssec_ok_); }
+
+    /// \brief Specify whether DNSSEC is supported in the message.
+    void setDNSSECSupported(const bool on) { dnssec_ok_ = on; }
+
+    // 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
+    // RRset::toWire().  In any case the caller shouldn't assume these are
+    // only possible return values from this method.
+    unsigned int toWire(MessageRenderer& renderer,
+                        const uint8_t extended_rcode) const;
+    unsigned int toWire(OutputBuffer& buffer,
+                        const uint8_t extended_rcode) const;
+
+    /// TBD
+    std::string toText() const;
+
+    // TBD: currently not implemented.
+    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 want to use pimpl, especially when we support EDNS options.
+    const uint8_t version_;
+    uint16_t udp_size_;
+    bool dnssec_ok_;
+};
+
+/// \brief Create a new EDNS object from a set of RR parameters, also providing
+/// the extended RCODE value.
+///
+/// Document why this function isn't a constructor.
+/// Document why this function does two things: create an EDNS and adjust
+/// Rcode.
+///
+/// This function provides the strong exception guarantee: Unless an
+/// exception is thrown \c extended_code won't be modified.
+EDNS* createEDNSFromRR(const Name& name, const RRClass& rrclass,
+                       const RRType& rrtype, const RRTTL& ttl,
+                       const rdata::Rdata& rdata,
+                       uint8_t& extended_rcode);
+
+/// Should we define this?
+std::ostream& operator<<(std::ostream& os, const EDNS& edns);
+}
+}
+#endif  // __EDNS_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 107 - 168
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,36 @@ Opcode::toText() const {
     return (opcodetext[code_]);
 }
 
-Rcode::Rcode(uint16_t code) : code_(code) {
+namespace {
+//     0     3               11      15
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    |UNUSED | EXTENDED-RCODE | RCODE |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//                            <= EXTRCODE_SHIFT
+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 +225,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 +255,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 +299,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 +341,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 +437,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 +476,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 = this->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 +613,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 +662,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 +746,13 @@ 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) {
-        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";
+    if (impl_->edns_ != NULL) {
+        s += impl_->edns_->toText();
     }
 
     if (!impl_->questions_.empty()) {
@@ -860,11 +803,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());
 

+ 12 - 40
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;
 
@@ -697,6 +661,14 @@ public:
     /// Only allowed in the \c RENDER mode.
     void setOpcode(const Opcode& opcode);
 
+    /// \brief TBD
+    ConstEDNSPtr getEDNS() const;
+
+    /// \brief TBD
+    ///
+    /// Only allowed in the \c RENDER mode.
+    void setEDNS(ConstEDNSPtr edns);
+
     /// \brief Returns the number of RRs contained in the given section.
     unsigned int getRRCount(const Section& section) const;
 

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

@@ -1,8 +1,11 @@
 SUBDIRS = tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
 if USE_GXX
-AM_CPPFLAGS += -Wno-write-strings
+AM_CXXFLAGS += -Wno-write-strings
 endif
 
 #lib_LTLIBRARIES = libdns_python_name.la libdns_python_rrset.la
@@ -19,6 +22,7 @@ libdns_python_la_LDFLAGS = $(PYTHON_LDFLAGS)
 # directly included from source files, so these don't have their own
 # rules
 EXTRA_DIST = libdns_python_common.h
+EXTRA_DIST += edns_python.h
 EXTRA_DIST += messagerenderer_python.cc
 EXTRA_DIST += message_python.cc
 EXTRA_DIST += rrclass_python.cc

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

@@ -0,0 +1,374 @@
+// 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_isDNSSECSupported(const s_EDNS* self);
+PyObject* EDNS_setDNSSECSupported(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." },
+    { "is_dnssec_supported",
+      reinterpret_cast<PyCFunction>(EDNS_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>(EDNS_setDNSSECSupported), METH_VARARGS,
+      "Specify whether DNSSEC is supported in the message." },
+    { "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_isDNSSECSupported(const s_EDNS* const self) {
+    if (self->edns->isDNSSECSupported()) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+
+PyObject*
+EDNS_setDNSSECSupported(s_EDNS* self, PyObject* args) {
+    const PyObject *b;
+    if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &b)) {
+        return (NULL);
+    }
+    self->edns->setDNSSECSupported(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);
+}

+ 14 - 2
src/lib/dns/python/libdns_python.cc

@@ -40,8 +40,9 @@
 
 #include <dns/python/libdns_python_common.h>
 
-// For our 'general' isc::Exception
+// For our 'general' isc::Exceptions
 static PyObject* po_IscException;
+static PyObject* po_InvalidParameter;
 
 // order is important here!
 #include <dns/python/messagerenderer_python.cc>
@@ -54,6 +55,7 @@ static PyObject* po_IscException;
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
                                                // Name
 #include <dns/python/message_python.cc>        // needs RRset, Question
+#include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
 
 //
 // Definition of the module
@@ -81,9 +83,15 @@ PyInit_libdns_python(void) {
         return (NULL);
     }
 
-    po_IscException = PyErr_NewException("libdns_python.IscException", NULL, NULL);
+    po_IscException = PyErr_NewException("libdns_python.IscException", NULL,
+                                         NULL);
     PyModule_AddObject(mod, "IscException", po_IscException);
 
+    // Add the exceptions to the class
+    po_InvalidParameter = PyErr_NewException("libdns_python.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 +130,10 @@ PyInit_libdns_python(void) {
         return (NULL);
     }
 
+    if (!initModulePart_EDNS(mod)) {
+        return (NULL);
+    }
+
     return (mod);
 }
 

+ 9 - 85
src/lib/dns/python/message_python.cc

@@ -120,7 +120,6 @@ static PyTypeObject messageflag_type = {
     0                                   // tp_version_tag
 };
 
-
 static int
 MessageFlag_init(s_MessageFlag* self UNUSED_PARAM,
                  PyObject* args UNUSED_PARAM)
@@ -486,6 +485,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 +509,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 +620,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,10 +981,6 @@ 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);
@@ -1019,35 +1023,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,
@@ -1248,57 +1223,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()));
 }

+ 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
 
 

+ 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);

+ 5 - 4
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 ; \
+	$(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 libdns_python 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.is_dnssec_supported())
+
+        edns = EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                    self.rrttl_do_off, self.opt_rdata)
+        self.assertFalse(edns.is_dnssec_supported())
+
+        edns = EDNS()
+        self.assertFalse(edns.is_dnssec_supported())
+        edns.set_dnssec_supported(True)
+        self.assertTrue(edns.is_dnssec_supported())
+        edns.set_dnssec_supported(False);
+        self.assertFalse(edns.is_dnssec_supported())
+
+        self.assertRaises(TypeError, edns.set_dnssec_supported, "wrong")
+        self.assertRaises(TypeError, edns.set_dnssec_supported, 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"
+        self.assertEqual(expected_str, edns.to_text())
+        self.assertEqual(expected_str, str(edns))
+
+        edns.set_dnssec_supported(True)
+        self.assertEqual("; EDNS: version: 0, flags: do; udp: 4096",
+                         edns.to_text())
+
+        self.assertEqual("; EDNS: version: 0, flags: do; udp: 4096",
+                         EDNS(Name.ROOT_NAME, self.rrclass, self.rrtype,
+                              RRTTL(0x01008000), self.opt_rdata).to_text())
+
+        self.assertEqual("; EDNS: version: 0, flags: do; udp: 4096",
+                         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_supported(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_supported(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_supported(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_supported(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.is_dnssec_supported())
+        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()

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

@@ -20,7 +20,7 @@
 import unittest
 import os
 from libdns_python 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,
@@ -487,84 +452,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 libdns_python 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()

+ 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).isDNSSECSupported());
+
+    // DO bit is off.  DNSSEC should be considered to be unsupported.
+    EXPECT_FALSE(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                      rrttl_do_off, *opt_rdata).isDNSSECSupported());
+
+    // tests for EDNS constructed by hand
+    EXPECT_FALSE(edns_base.isDNSSECSupported()); // false by default
+    edns_base.setDNSSECSupported(true);          // enable by hand
+    EXPECT_TRUE(edns_base.isDNSSECSupported());
+    edns_base.setDNSSECSupported(false); // disable by hand
+    EXPECT_FALSE(edns_base.isDNSSECSupported());
+}
+
+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",
+              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",
+              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",
+              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",
+              EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+                   RRTTL(0x00008001), *opt_rdata).toText());
+}
+
+TEST_F(EDNSTest, toWireRenderer) {
+    // Typical case, (explicitly) disabling DNSSEC
+    edns_base.setDNSSECSupported(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.setDNSSECSupported(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.setDNSSECSupported(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.setDNSSECSupported(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.setDNSSECSupported(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->isDNSSECSupported());
+    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());
+}
+}

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

@@ -127,82 +127,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 +146,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());
 }

+ 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) :