Browse Source

[2000] Update OPT RDATA parser to handle EDNS options

Mukund Sivaraman 11 years ago
parent
commit
b6875c7848

+ 136 - 20
src/lib/dns/rdata/generic/opt_41.cc

@@ -14,25 +14,68 @@
 
 #include <config.h>
 
-#include <string>
-
 #include <util/buffer.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
+#include <boost/foreach.hpp>
+
+#include <string>
+#include <string.h>
+
 using namespace std;
 using namespace isc::util;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
+/// \brief Constructor.
+OPT::PseudoRR::PseudoRR(uint16_t code,
+                        boost::shared_ptr<std::vector<uint8_t> >& data) :
+    code_(code),
+    data_(data)
+{
+}
+
+uint16_t
+OPT::PseudoRR::getCode() const {
+    return (code_);
+}
+
+const uint8_t*
+OPT::PseudoRR::getData() const {
+    return (&(*data_)[0]);
+}
+
+uint16_t
+OPT::PseudoRR::getLength() const {
+    return (data_->size());
+}
+
+struct OPTImpl {
+    OPTImpl() :
+        rdlength_(0)
+    {}
+
+    uint16_t rdlength_;
+    std::vector<OPT::PseudoRR> pseudo_rrs_;
+};
+
+/// \brief Default constructor.
+OPT::OPT() :
+    impl_(new OPTImpl)
+{
+}
+
 /// \brief Constructor from string.
 ///
 /// This constructor cannot be used, and always throws an exception.
 ///
 /// \throw InvalidRdataText OPT RR cannot be constructed from text.
-OPT::OPT(const std::string&) {
+OPT::OPT(const std::string&) :
+    impl_(NULL)
+{
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
 }
 
@@ -42,30 +85,80 @@ OPT::OPT(const std::string&) {
 ///
 /// \throw InvalidRdataText OPT RR cannot be constructed from text.
 OPT::OPT(MasterLexer&, const Name*,
-       MasterLoader::Options, MasterLoaderCallbacks&)
+         MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(NULL)
 {
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
 }
 
-OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
-    // setPosition() will throw against a short buffer anyway, but it's safer
-    // to check it explicitly here.
-    if (buffer.getLength() - buffer.getPosition() < rdata_len) {
-        isc_throw(InvalidRdataLength, "RDLEN of OPT is too large");
+OPT::OPT(InputBuffer& buffer, size_t rdata_len) :
+    impl_(NULL)
+{
+    std::auto_ptr<OPTImpl> impl_ptr(new OPTImpl);
+
+    while (true) {
+        if (rdata_len == 0) {
+            break;
+        }
+
+        if (rdata_len < 4) {
+            isc_throw(InvalidRdataLength,
+                      "Pseudo OPT RR record too short: "
+                      << rdata_len << " bytes");
+        }
+
+        const uint16_t option_code = buffer.readUint16();
+        const uint16_t option_length = buffer.readUint16();
+        rdata_len -= 4;
+
+        if ((impl_ptr->rdlength_ + option_length) < impl_ptr->rdlength_) {
+            isc_throw(InvalidRdataText,
+                      "Option length " << option_length
+                      << " would overflow OPT RR RDLEN (currently "
+                      << impl_ptr->rdlength_ << ").");
+        }
+
+        if (rdata_len < option_length) {
+            isc_throw(InvalidRdataLength, "Corrupt Pseudo OPT RR record");
+        }
+
+        boost::shared_ptr<std::vector<uint8_t> >
+            option_data(new std::vector<uint8_t>(option_length));
+        buffer.readData(&(*option_data)[0], option_length);
+        impl_ptr->pseudo_rrs_.push_back(PseudoRR(option_code, option_data));
+        impl_ptr->rdlength_ += option_length;
+        rdata_len -= option_length;
+    }
+
+    impl_ = impl_ptr.release();
+}
+
+OPT::OPT(const OPT& other) :
+    Rdata(), impl_(new OPTImpl(*other.impl_))
+{
+}
+
+OPT&
+OPT::operator=(const OPT& source) {
+    if (this == &source) {
+        return (*this);
     }
 
-    // This simple implementation ignores any options
-    buffer.setPosition(buffer.getPosition() + rdata_len);
+    OPTImpl* newimpl = new OPTImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
 }
 
-OPT::OPT(const OPT&) : Rdata() {
-    // there's nothing to copy in this simple implementation.
+OPT::~OPT() {
+    delete impl_;
 }
 
 std::string
 OPT::toText() const {
-    // OPT records do not have a text format.
-    return ("");
+    isc_throw(isc::InvalidOperation,
+              "OPT RRs do not have a presentation format");
 }
 
 void
@@ -79,13 +172,36 @@ OPT::toWire(AbstractMessageRenderer&) const {
 }
 
 int
-OPT::compare(const Rdata& other) const {
-    //const OPT& other_opt = dynamic_cast<const OPT&>(other);
-    // right now we don't need other_opt:
-    static_cast<void>(dynamic_cast<const OPT&>(other));
-
+OPT::compare(const Rdata&) const {
+    isc_throw(isc::InvalidOperation,
+              "It is meaningless to compare a set of OPT pseudo RRs; "
+              "they have unspecified order");
     return (0);
 }
 
+void
+OPT::appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length) {
+    // See if it overflows 16-bit length field. We only worry about the
+    // pseudo-RR length here, not the whole message length (which should
+    // be checked and enforced elsewhere).
+    if ((impl_->rdlength_ + length) < impl_->rdlength_) {
+        isc_throw(isc::InvalidParameter,
+                  "Option length " << length
+                  << " would overflow OPT RR RDLEN (currently "
+                  << impl_->rdlength_ << ").");
+    }
+
+    boost::shared_ptr<std::vector<uint8_t> >
+        option_data(new std::vector<uint8_t>(length));
+    std::memcpy(&(*option_data)[0], data, length);
+    impl_->pseudo_rrs_.push_back(PseudoRR(code, option_data));
+    impl_->rdlength_ += length;
+}
+
+const std::vector<OPT::PseudoRR>&
+OPT::getPseudoRRs() const {
+    return (impl_->pseudo_rrs_);
+}
+
 // END_RDATA_NAMESPACE
 // END_ISC_NAMESPACE

+ 28 - 2
src/lib/dns/rdata/generic/opt_41.h

@@ -18,6 +18,10 @@
 
 #include <dns/rdata.h>
 
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
 // BEGIN_ISC_NAMESPACE
 
 // BEGIN_COMMON_DECLARATIONS
@@ -25,15 +29,37 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+struct OPTImpl;
+
 class OPT : public Rdata {
 public:
     // BEGIN_COMMON_MEMBERS
     // END_COMMON_MEMBERS
 
     // The default constructor makes sense for OPT as it can be empty.
-    OPT() {}
+    OPT();
+    OPT& operator=(const OPT& source);
+    ~OPT();
+
+    class PseudoRR {
+    public:
+        PseudoRR(uint16_t code,
+                 boost::shared_ptr<std::vector<uint8_t> >& data);
+
+        uint16_t getCode() const;
+        const uint8_t* getData() const;
+        uint16_t getLength() const;
+
+    private:
+        uint16_t code_;
+        boost::shared_ptr<std::vector<uint8_t> > data_;
+    };
+
+    void appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length);
+    const std::vector<PseudoRR>& getPseudoRRs() const;
+
 private:
-    // RR-type specific members are here.
+    OPTImpl* impl_;
 };
 
 // END_RDATA_NAMESPACE

+ 33 - 8
src/lib/dns/tests/rdata_opt_unittest.cc

@@ -49,11 +49,10 @@ TEST_F(Rdata_OPT_Test, createFromWire) {
                                          "rdata_opt_fromWire"));
     EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::CH(),
                                          "rdata_opt_fromWire", 2));
-
     // short buffer case.
     EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
                                       "rdata_opt_fromWire", 11),
-                 InvalidRdataLength);
+                 InvalidBufferPosition);
 }
 
 TEST_F(Rdata_OPT_Test, createFromLexer) {
@@ -74,16 +73,42 @@ TEST_F(Rdata_OPT_Test, toWireRenderer) {
 }
 
 TEST_F(Rdata_OPT_Test, toText) {
-    EXPECT_EQ("", rdata_opt.toText());
+    EXPECT_THROW(rdata_opt.toText(),
+                 isc::InvalidOperation);
 }
 
 TEST_F(Rdata_OPT_Test, compare) {
-    // This simple implementation always returns "true"
-    EXPECT_EQ(0, rdata_opt.compare(
+    EXPECT_THROW(rdata_opt.compare(
                   *rdataFactoryFromFile(RRType::OPT(), RRClass::CH(),
-                                        "rdata_opt_fromWire", 2)));
+                                        "rdata_opt_fromWire", 2)),
+                 isc::InvalidOperation);
+
+    // comparison attempt between incompatible RR types also results in
+    // isc::InvalidOperation.
+    EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch),
+                 isc::InvalidOperation);
+}
+
+TEST_F(Rdata_OPT_Test, append) {
+    EXPECT_THROW(rdata_opt.toText(),
+                 isc::InvalidOperation);
+}
+
+TEST_F(Rdata_OPT_Test, getPseudoRRs) {
+    const generic::OPT rdf =
+        dynamic_cast<const generic::OPT&>
+        (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+                               "rdata_opt_fromWire", 2));
+
+    const std::vector<generic::OPT::PseudoRR>& rrs = rdf.getPseudoRRs();
+    ASSERT_FALSE(rrs.empty());
+    EXPECT_EQ(1, rrs.size());
+    EXPECT_EQ(3, rrs.at(0).getCode());
+    EXPECT_EQ(3, rrs.at(0).getLength());
 
-    // comparison attempt between incompatible RR types should be rejected
-    EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
+    const uint8_t expected_data[] = {0x00, 0x01, 0x02};
+    const uint8_t* actual_data = rrs.at(0).getData();
+    EXPECT_EQ(0, std::memcmp(expected_data, actual_data,
+                             sizeof(expected_data)));
 }
 }