D2UpdateMessage::D2UpdateMessage(const bool parse)
: message_(parse ? dns::Message::PARSE : dns::Message::RENDER) {
+ // If this object is to create an outgoing message, we have to
+ // set the proper Opcode field and QR flag here.
if (!parse) {
message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+
}
}
RESPONSE : REQUEST);
}
-void
-D2UpdateMessage::setQRFlag(const QRFlag flag) {
- bool on = (flag == RESPONSE ? true : false);
- message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, on);
-}
-
uint16_t
-D2UpdateMessage::getQid() const {
+D2UpdateMessage::getId() const {
return (message_.getQid());
}
void
-D2UpdateMessage::setQid(const uint16_t qid) {
- message_.setQid(qid);
+D2UpdateMessage::setId(const uint16_t id) {
+ message_.setQid(id);
}
void
D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+ // The Zone data is kept in the underlying Question class. If there
+ // is a record stored there already, we need to remove it, because
+ // we may have at most one Zone record in the DNS Update message.
if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
message_.clearSection(dns::Message::SECTION_QUESTION);
}
-
+ // Add the new record...
Question question(zone, rrclass, RRType::SOA());
message_.addQuestion(question);
+ // ... and update the local class member holding the D2Zone object.
zone_.reset(new D2Zone(question.getName(), question.getClass()));
}
D2UpdateMessage::addRRset(const UpdateMsgSection section,
const dns::RRsetPtr& rrset) {
message_.addRRset(ddnsToDnsSection(section), rrset);
-
-}
-
-bool
-D2UpdateMessage::hasRRset(const UpdateMsgSection section, const dns::Name& name,
- const dns::RRClass& rrclass, const dns::RRType& rrtype) {
- return (message_.hasRRset(ddnsToDnsSection(section), name, rrclass, rrtype));
-}
-
-bool
-D2UpdateMessage::hasRRset(const UpdateMsgSection section, const dns::RRsetPtr &rrset) {
- return (message_.hasRRset(ddnsToDnsSection(section), rrset));
}
void
D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
+ // We are preparing the wire format of the message, meaning
+ // that this message will be sent as a request to the DNS.
+ // Therefore, we expect that this message is a REQUEST.
+ if (getQRFlag() != REQUEST) {
+ isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+ " DNS Update message");
+ }
+ // According to RFC2136, the ZONE section may contain exactly one
+ // record.
if (getRRCount(SECTION_ZONE) != 1) {
isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
" must comprise exactly one record (RFC2136, section 2.3)");
void
D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
+ // First, use the underlying dns::Message implementation to get the
+ // contents of the DNS response message. Note that it may or may
+ // not be the message that we are interested in, but needs to be
+ // parsed so as we can check its ID, Opcode etc.
message_.fromWire(buffer);
+ // This class implements exposes the getZone() function. This function
+ // will return pointer to the D2Zone object if non-empty Zone
+ // section exists in the received message. It will return NULL pointer
+ // if it doesn't exist. The pointer is held in the D2UpdateMessage class
+ // member. We need to update this pointer every time we parse the
+ // message.
if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+ // There is a Zone section in the received message. Replace
+ // Zone pointer with the new value.
QuestionPtr question = *message_.beginQuestion();
assert(question);
zone_.reset(new D2Zone(question->getName(), question->getClass()));
} else {
+ // Zone section doesn't hold any pointers, so set the pointer to NULL.
zone_.reset();
}
+ // Check that the content of the received message is sane.
+ // One of the basic checks to do is to verify that we have
+ // received the DNS update message. If not, it can be dropped
+ // or an error message can be printed. Other than that, we
+ // will check that there is at most one Zone record and QR flag
+ // is set.
+ validate();
}
dns::Message::Section
"unknown message section " << section);
}
+void
+D2UpdateMessage::validate() const {
+ // Verify that we are dealing with the DNS Update message. According to
+ // RFC 2136, section 3.8 server will copy the Opcode from the query.
+ // If we are dealing with a different type of message, we may simply
+ // stop further processing, because it is likely that the message was
+ // directed to someone else.
+ if (message_.getOpcode() != Opcode::UPDATE()) {
+ isc_throw(NotUpdateMessage, "received message is not a DDNS update, received"
+ " message code is " << message_.getOpcode().getCode());
+ }
+ // Received message should have QR flag set, which indicates that it is
+ // a RESPONSE.
+ if (getQRFlag() == REQUEST) {
+ isc_throw(NotUpdateMessage, "received message should should have QR flag set,"
+ " to indicate that it is a RESPONSE message, the QR flag is unset");
+ }
+ // DNS server may copy a Zone record from the query message. Since query must
+ // comprise exactly one Zone record (RFC 2136, section 2.3), the response message
+ // may contain 1 record at most. It may also contain no records if a server
+ // chooses not to copy Zone section.
+ if (getRRCount(SECTION_ZONE) > 1) {
+ isc_throw(InvalidZoneSection, "received message contains " << getRRCount(SECTION_ZONE)
+ << " Zone records, it should contain at most 1 record");
+ }
+}
+
} // namespace d2
} // namespace isc
isc::Exception(file, line, what) {}
};
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+ InvalidQRFlag(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+ NotUpdateMessage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
///
QRFlag getQRFlag() const;
- void setQRFlag(const QRFlag flag);
+ uint16_t getId() const;
- uint16_t getQid() const;
-
- void setQid(const uint16_t qid);
+ void setId(const uint16_t qid);
const dns::Rcode& getRcode() const;
void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
- bool hasRRset(const UpdateMsgSection section, const dns::Name& name,
- const dns::RRClass& rrclass, const dns::RRType& rrtype);
-
- bool hasRRset(const UpdateMsgSection section, const dns::RRsetPtr &rrset);
-
- void clearSection(const UpdateMsgSection section);
-
- void clear(const bool parse_mode);
-
void toWire(dns::AbstractMessageRenderer& renderer);
void fromWire(isc::util::InputBuffer& buffer);
private:
static dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+ void validate() const;
dns::Message message_;
D2ZonePtr zone_;
}
};
+// This test verifies that DNS Update message ID can be set using
+// setId function.
+TEST_F(D2UpdateMessageTest, setId) {
+ D2UpdateMessage msg;
+ EXPECT_EQ(0, msg.getId());
+ msg.setId(0x1234);
+ EXPECT_EQ(0x1234, msg.getId());
+}
+
+// This test verifies that the DNS Update message RCODE can be set
+// using setRcode function.
+TEST_F(D2UpdateMessageTest, setRcode) {
+ D2UpdateMessage msg;
+ msg.setRcode(Rcode::NOERROR());
+ EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode());
+
+ msg.setRcode(Rcode::NOTIMP());
+ EXPECT_EQ(Rcode::NOTIMP().getCode(), msg.getRcode().getCode());
+}
+
+// This test verifies that the Zone section in the DNS Update message
+// can be set.
+TEST_F(D2UpdateMessageTest, setZone) {
+ D2UpdateMessage msg;
+ D2ZonePtr zone = msg.getZone();
+ EXPECT_FALSE(zone);
+ msg.setZone(Name("example.com"), RRClass::ANY());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone->getClass().getCode());
+
+ msg.setZone(Name("foo.example.com"), RRClass::NONE());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("foo.example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::NONE().getCode(), zone->getClass().getCode());
+}
+
// This test verifies that the DNS message is properly decoded from the
// wire format.
TEST_F(D2UpdateMessageTest, fromWire) {
ASSERT_NO_THROW(msg.fromWire(buf));
// Check that the message header is valid.
- EXPECT_EQ(0x05AF, msg.getQid());
+ EXPECT_EQ(0x05AF, msg.getId());
EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode());
TEST_F(D2UpdateMessageTest, toWire) {
D2UpdateMessage msg;
// Set message ID.
- msg.setQid(0x1234);
- // Make it a Request message by setting the QR flag to 0.
- msg.setQRFlag(D2UpdateMessage::REQUEST);
+ msg.setId(0x1234);
// Rcode to NOERROR.
msg.setRcode(Rcode(Rcode::NOERROR_CODE));