diff options
Diffstat (limited to 'src/lib/d2srv/tests/d2_update_message_unittests.cc')
-rw-r--r-- | src/lib/d2srv/tests/d2_update_message_unittests.cc | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/src/lib/d2srv/tests/d2_update_message_unittests.cc b/src/lib/d2srv/tests/d2_update_message_unittests.cc new file mode 100644 index 0000000..6f0cfca --- /dev/null +++ b/src/lib/d2srv/tests/d2_update_message_unittests.cc @@ -0,0 +1,696 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <d2srv/d2_config.h> +#include <d2srv/d2_update_message.h> +#include <d2srv/d2_zone.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrttl.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::d2; +using namespace isc::dns; +using namespace isc::dns::rdata; +using namespace isc::util; + +namespace { + /// @brief Test fixture class for testing D2UpdateMessage object +class D2UpdateMessageTest : public ::testing::Test { +public: + /// @brief Constructor + // + // Does nothing. + D2UpdateMessageTest() { } + + /// @brief Destructor + // + // Does nothing. + ~D2UpdateMessageTest() { }; + + /// @brief Returns string representation of the name encoded in wire format. + // + // This function reads the number of bytes specified in the second + // argument from the buffer. It doesn't check if buffer has sufficient + // length for reading given number of bytes. Caller should verify it + // prior to calling this function. + // + // @param buf input buffer, its internal pointer will be moved to + // the position after a name being read from it. + // @param name_length length of the name stored in the buffer + // @param no_zero_byte if true it indicates that the given buffer does not + // comprise the zero byte, which signals end of the name. This is + // the case, when dealing with compressed messages which don't have + // this byte. + // + // @return string representation of the name. + std::string readNameFromWire(InputBuffer& buf, size_t name_length, + const bool no_zero_byte = false) { + std::vector<uint8_t> name_data; + // Create another InputBuffer which holds only the name in the wire + // format. + buf.readVector(name_data, name_length); + if (no_zero_byte) { + ++name_length; + name_data.push_back(0); + } + InputBuffer name_buf(&name_data[0], name_length); + // Parse the name and return its textual representation. + Name name(name_buf); + return (name.toText()); + } +}; + +// This test verifies that DNS Update message ID can be set using +// setId function. +TEST_F(D2UpdateMessageTest, setId) { + // Message ID is initialized to 0. + D2UpdateMessage msg; + EXPECT_EQ(0, msg.getId()); + // Override the default value and verify that it has been set. + 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; + // Rcode must be explicitly set before it is accessed. + msg.setRcode(Rcode::NOERROR()); + EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode()); + // Let's override current value to make sure that getter does + // not return fixed value. + 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; + // The zone pointer is initialized to NULL. + D2ZonePtr zone = msg.getZone(); + EXPECT_FALSE(zone); + // Let's create a new Zone and check that it is returned + // via getter. + 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()); + + // Now, let's check that the existing Zone object can be + // overridden with a new one. + 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) { + // The following table holds the DNS response in on-wire format. + // This message comprises the following sections: + // - HEADER + // - PREREQUISITE section with one RR + // - UPDATE section with 1 RR. + // Such a response may be generated by the DNS server as a result + // of copying the contents of the REQUEST message sent by DDNS client. + const uint8_t bin_msg[] = { + // HEADER section starts here (see RFC 2136, section 2). + 0x05, 0xAF, // ID=0x05AF + 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN + 0x0, 0x1, // ZOCOUNT=1 + 0x0, 0x2, // PRCOUNT=2 + 0x0, 0x1, // UPCOUNT=1 + 0x0, 0x0, // ADCOUNT=0 + + // Zone section starts here. The The first field comprises + // the Zone name encoded as a set of labels, each preceded + // by a length of the following label. The whole Zone name is + // terminated with a NULL char. + // For Zone section format see (RFC 2136, section 2.3). + 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length) + 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length) + 0x0, // NULL character terminates the Zone name. + 0x0, 0x6, // ZTYPE='SOA' + 0x0, 0x1, // ZCLASS='IN' + + // Prerequisite section starts here. This section comprises two + // prerequisites: + // - 'Name is not in use' + // - 'Name is in use' + // See RFC 2136, section 2.4 for the format of Prerequisite section. + // Each prerequisite RR starts with its name. It is expressed in the + // compressed format as described in RFC 1035, section 4.1.4. The first + // label is expressed as in case of non-compressed name. It is preceded + // by the length value. The following two bytes are the pointer to the + // offset in the message where 'example.com' was used. That is, in the + // Zone name at offset 12. Pointer starts with two bits set - they + // mark start of the pointer. + + // First prerequisite. NONE class indicates that the update requires + // that the name 'foo.example.com' is not use/ + 0x03, 0x66, 0x6F, 0x6F, // foo. + 0xC0, 0x0C, // pointer to example.com. + 0x0, 0x1C, // TYPE=AAAA + 0x0, 0xFE, // CLASS=NONE + 0x0, 0x0, 0x0, 0x0, // TTL=0 + 0x0, 0x0, // RDLENGTH=0 + + // Second prerequisite. ANY class indicates tha the update requires + // that the name 'bar.example.com' exists. + 0x03, 0x62, 0x61, 0x72, // bar. + 0xC0, 0x0C, // pointer to example.com. + 0x0, 0x1C, // TYPE=AAAA + 0x0, 0xFF, // CLASS=ANY + 0x0, 0x0, 0x0, 0x0, // TTL=0 + 0x0, 0x0, // RDLENGTH=0 + + // Update section starts here. The format of this section conforms to + // RFC 2136, section 2.5. The name of the RR is again expressed in + // compressed format. The two pointer bytes point to the offset in the + // message where 'foo.example.com' was used already - 29. + 0xC0, 0x1D, // pointer to foo.example.com. + 0x0, 0x1C, // TYPE=AAAA + 0x0, 0x1, // CLASS=IN + 0xAA, 0xBB, 0xCC, 0xDD, // TTL=0xAABBCCDD + 0x0, 0x10, // RDLENGTH=16 + // The following 16 bytes of RDATA hold IPv6 address: 2001:db8:1::1. + 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + + // Create an object to be used to decode the message from the wire format. + D2UpdateMessage msg(D2UpdateMessage::INBOUND); + + // Decode the message. + ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg))); + + // Check that the message header is valid. + EXPECT_EQ(0x05AF, msg.getId()); + EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag()); + EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode()); + + // The ZOCOUNT must contain exactly one zone. If it does, we should get + // the name, class and type of the zone and verify they are valid. + ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_ZONE)); + D2ZonePtr zone = msg.getZone(); + ASSERT_TRUE(zone); + EXPECT_EQ("example.com.", zone->getName().toText()); + EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode()); + + // Check the Prerequisite section. It should contain two records. + ASSERT_EQ(2, msg.getRRCount(D2UpdateMessage::SECTION_PREREQUISITE)); + + // Proceed to the first prerequisite. + RRsetIterator rrset_it = + msg.beginSection(D2UpdateMessage::SECTION_PREREQUISITE); + RRsetPtr prereq1 = *rrset_it; + ASSERT_TRUE(prereq1); + // Check record fields. + EXPECT_EQ("foo.example.com.", prereq1->getName().toText()); // NAME + EXPECT_EQ(RRType::AAAA().getCode(), prereq1->getType().getCode()); // TYPE + EXPECT_EQ(RRClass::NONE().getCode(), + prereq1->getClass().getCode()); // CLASS + EXPECT_EQ(0, prereq1->getTTL().getValue()); // TTL + EXPECT_EQ(0, prereq1->getRdataCount()); // RDLENGTH + + // Move to next prerequisite section. + ++rrset_it; + RRsetPtr prereq2 = *rrset_it; + ASSERT_TRUE(prereq2); + // Check record fields. + EXPECT_EQ("bar.example.com.", prereq2->getName().toText()); // NAME + EXPECT_EQ(RRType::AAAA().getCode(), prereq2->getType().getCode()); // TYPE + EXPECT_EQ(RRClass::ANY().getCode(), prereq2->getClass().getCode()); // CLASS + EXPECT_EQ(0, prereq2->getTTL().getValue()); // TTL + EXPECT_EQ(0, prereq2->getRdataCount()); // RDLENGTH + + // Check the Update section. There is only one record, so beginSection() + // should return the pointer to this sole record. + ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_UPDATE)); + rrset_it = msg.beginSection(D2UpdateMessage::SECTION_UPDATE); + RRsetPtr update = *rrset_it; + ASSERT_TRUE(update); + // Check the record fields. + EXPECT_EQ("foo.example.com.", update->getName().toText()); // NAME + EXPECT_EQ(RRType::AAAA().getCode(), update->getType().getCode()); // TYPE + EXPECT_EQ(RRClass::IN().getCode(), update->getClass().getCode()); // CLASS + EXPECT_EQ(0xAABBCCDD, update->getTTL().getValue()); // TTL + // There should be exactly one record holding the IPv6 address. + // This record can be accessed using RdataIterator. This record + // can be compared with the reference record, holding expected IPv6 + // address using compare function. + ASSERT_EQ(1, update->getRdataCount()); + RdataIteratorPtr rdata_it = update->getRdataIterator(); + ASSERT_TRUE(rdata_it); + in::AAAA rdata_ref("2001:db8:1::1"); + EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent())); + + // @todo: at this point we don't test Additional Data records. We may + // consider implementing tests for it in the future. +} + +// This test verifies that the fromWire function throws appropriate exception +// if the message being parsed comprises invalid Opcode (is not a DNS Update). +TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) { + // This is a binary representation of the DNS message. + // It comprises invalid Opcode=3, expected value is 6 + // (Update). + const uint8_t bin_msg[] = { + 0x05, 0xAF, // ID=0x05AF + 0x98, 0x6, // QR=1, Opcode=3, RCODE=YXDOMAIN + 0x0, 0x0, // ZOCOUNT=0 + 0x0, 0x0, // PRCOUNT=0 + 0x0, 0x0, // UPCOUNT=0 + 0x0, 0x0 // ADCOUNT=0 + }; + // The 'true' argument passed to the constructor turns the + // message into the parse mode in which the fromWire function + // can be used to decode the binary message data. + D2UpdateMessage msg(D2UpdateMessage::INBOUND); + // When using invalid Opcode, the fromWire function should + // throw NotUpdateMessage exception. + EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)), + isc::d2::NotUpdateMessage); +} + +// This test verifies that the fromWire function throws appropriate exception +// if the message being parsed comprises invalid QR flag. The QR bit is +// expected to be set to indicate that the message is a RESPONSE. +TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) { + // This is a binary representation of the DNS message. + // It comprises invalid QR flag = 0. + const uint8_t bin_msg[] = { + 0x05, 0xAF, // ID=0x05AF + 0x28, 0x6, // QR=0, Opcode=6, RCODE=YXDOMAIN + 0x0, 0x0, // ZOCOUNT=0 + 0x0, 0x0, // PRCOUNT=0 + 0x0, 0x0, // UPCOUNT=0 + 0x0, 0x0 // ADCOUNT=0 + }; + // The 'true' argument passed to the constructor turns the + // message into the parse mode in which the fromWire function + // can be used to decode the binary message data. + D2UpdateMessage msg(D2UpdateMessage::INBOUND); + // When using invalid QR flag, the fromWire function should + // throw InvalidQRFlag exception. + EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)), + isc::d2::InvalidQRFlag); +} + +// This test verifies that the fromWire function throws appropriate exception +// if the message being parsed comprises more than one (two in this case) +// Zone records. +TEST_F(D2UpdateMessageTest, fromWireTooManyZones) { + // This is a binary representation of the DNS message. This message + // comprises two Zone records. + const uint8_t bin_msg[] = { + 0x05, 0xAF, // ID=0x05AF + 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN + 0x0, 0x2, // ZOCOUNT=2 + 0x0, 0x0, // PRCOUNT=0 + 0x0, 0x0, // UPCOUNT=0 + 0x0, 0x0, // ADCOUNT=0 + + // Start first Zone record. + 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length) + 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length) + 0x0, // NULL character terminates the Zone name. + 0x0, 0x6, // ZTYPE='SOA' + 0x0, 0x1, // ZCLASS='IN' + + // Start second Zone record. Presence of this record should result + // in error when parsing this message. + 0x3, 0x63, 0x6F, 0x6D, // com. (0x3 is a length) + 0x0, // NULL character terminates the Zone name. + 0x0, 0x6, // ZTYPE='SOA' + 0x0, 0x1 // ZCLASS='IN' + }; + + // The 'true' argument passed to the constructor turns the + // message into the parse mode in which the fromWire function + // can be used to decode the binary message data. + D2UpdateMessage msg(D2UpdateMessage::INBOUND); + // When parsing a message with more than one Zone record, + // exception should be thrown. + EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)), + isc::d2::InvalidZoneSection); +} + +// This test verifies that the wire format of the message is produced +// in the render mode. +TEST_F(D2UpdateMessageTest, toWire) { + D2UpdateMessage msg; + // Set message ID. + msg.setId(0x1234); + // Rcode to NOERROR. + msg.setRcode(Rcode(Rcode::NOERROR_CODE)); + + // Set Zone section. This section must comprise exactly + // one Zone. toWire function would fail if Zone is not set. + msg.setZone(Name("example.com"), RRClass::IN()); + + // Set prerequisites. + + // 'Name Is Not In Use' prerequisite (RFC 2136, section 2.4.5) + RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(), + RRType::ANY(), RRTTL(0))); + msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1); + + // 'Name is In Use' prerequisite (RFC 2136, section 2.4.4) + RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(), + RRType::ANY(), RRTTL(0))); + msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2); + + // Set Update Section. + + // Create RR holding a name being added. This RR is constructed + // in conformance to RFC 2136, section 2.5.1. + RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(), + RRType::A(), RRTTL(10))); + // RR record is of the type A, thus RDATA holds 4 octet Internet + // address. This address is 10.10.1.1. + char rdata1[] = { + 0xA, 0xA , 0x1, 0x1 + }; + InputBuffer buf_rdata1(rdata1, 4); + updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1, + buf_rdata1.getLength())); + // Add the RR to the message. + msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1); + + // Render message into the wire format. + MessageRenderer renderer; + ASSERT_NO_THROW(msg.toWire(renderer)); + + // Make sure that created packet is not truncated. + ASSERT_EQ(77, renderer.getLength()); + + // Create input buffer from the rendered data. InputBuffer + // is handy to validate the byte contents of the rendered + // message. + InputBuffer buf(renderer.getData(), renderer.getLength()); + + // Start validating the message header. + + // Verify message ID. + EXPECT_EQ(0x1234, buf.readUint16()); + // The 2-bytes following message ID comprise the following fields: + // - QR - 1 bit indicating that it is REQUEST. Should be 0. + // - Opcode - 4 bits which should hold value of 5 indicating this is + // an Update message. Binary form is "0101". + // - Z - These bits are unused for Update Message and should be 0. + // - RCODE - Response code, set to NOERROR for REQUEST. It is 0. + //8706391835 + // The binary value is: + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // | QR| Opcode | Z | RCODE | + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // | 0 | 0 1 0 1 | 0 0 0 0 0 0 0 | 0 0 0 0 | + // +---+---+---+-------+---+---+---+---+---+---+---+---+---+---+---+ + // and the hexadecimal representation is 0x2800. + EXPECT_EQ(0x2800, buf.readUint16()); + + // ZOCOUNT - holds the number of zones for the update. For Request + // message it must be exactly one record (RFC2136, section 2.3). + EXPECT_EQ(1, buf.readUint16()); + + // PRCOUNT - holds the number of prerequisites. Earlier we have added + // two prerequisites. Thus, expect that this counter is 2. + EXPECT_EQ(2, buf.readUint16()); + + // UPCOUNT - holds the number of RRs in the Update Section. We have + // added 1 RR, which adds the name foo.example.com to the Zone. + EXPECT_EQ(1, buf.readUint16()); + + // ADCOUNT - holds the number of RRs in the Additional Data Section. + EXPECT_EQ(0, buf.readUint16()); + + // Start validating the Zone section. This section comprises the + // following data: + // - ZNAME + // - ZTYPE + // - ZCLASS + + // ZNAME holds 'example.com.' encoded as set of labels. Each label + // is preceded by its length. The name is ended with the byte holding + // zero value. This yields the total size of the name in wire format + // of 13 bytes. + + // The simplest way to convert the name from wire format to a string + // is to use dns::Name class. It should be ok to rely on the Name class + // to decode the name, because it is unit tested elsewhere. + std::string zone_name = readNameFromWire(buf, 13); + EXPECT_EQ("example.com.", zone_name); + + // ZTYPE of the Zone section must be SOA according to RFC 2136, + // section 2.3. + EXPECT_EQ(RRType::SOA().getCode(), buf.readUint16()); + + // ZCLASS of the Zone section is IN. + EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16()); + + // Start checks on Prerequisite section. Each prerequisite comprises + // the following fields: + // - NAME - name of the RR in wire format + // - TYPE - two octets with one of the RR TYPE codes + // - CLASS - two octets with one of the RR CLASS codes + // - TTL - a 32-bit signed integer specifying Time-To-Live + // - RDLENGTH - length of the RDATA field + // - RDATA - a variable length string of octets containing + // resource data. + // In case of this message, we expect to have two prerequisite RRs. + // Their structure is checked below. + + // First prerequisite should comprise the 'Name is not in use prerequisite' + // for 'foo.example.com'. + + // Check the name first. Message renderer is using compression for domain + // names as described in RFC 1035, section 4.1.4. The name in this RR is + // foo.example.com. The name of the zone is example.com and it has occurred + // in this message already at offset 12 (the size of the header is 12). + // Therefore, name of this RR is encoded as 'foo', followed by a pointer + // to offset in this message where the remainder of this name was used. + // This pointer has the following format: + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // | 1 1| OFFSET | + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // | 1 1| 0 0 0 0 0 0 0 0 0 0 1 1 0 0| + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // which has a following hexadecimal representation: 0xC00C + + // Let's read the non-compressed part first - 'foo.' + std::string name_prereq1 = readNameFromWire(buf, 4, true); + EXPECT_EQ("foo.", name_prereq1); + // The remaining two bytes hold the pointer to 'example.com'. + EXPECT_EQ(0xC00C, buf.readUint16()); + // TYPE is ANY + EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16()); + // CLASS is NONE + EXPECT_EQ(RRClass::NONE().getCode(), buf.readUint16()); + // TTL is a 32-but value, expecting 0 + EXPECT_EQ(0, buf.readUint32()); + // There is no RDATA, so RDLENGTH is 0 + EXPECT_EQ(0, buf.readUint16()); + + // Start checking second prerequisite. + + std::string name_prereq2 = readNameFromWire(buf, 4, true); + EXPECT_EQ("bar.", name_prereq2); + // The remaining two bytes hold the pointer to 'example.com'. + EXPECT_EQ(0xC00C, buf.readUint16()); + // TYPE is ANY + EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16()); + // CLASS is ANY + EXPECT_EQ(RRClass::ANY().getCode(), buf.readUint16()); + // TTL is a 32-but value, expecting 0 + EXPECT_EQ(0, buf.readUint32()); + // There is no RDATA, so RDLENGTH is 0 + EXPECT_EQ(0, buf.readUint16()); + + // Start checking Update section. This section contains RRset with + // one A RR. + + // The name of the RR is 'foo.example.com'. It is encoded in the + // compressed format - as a pointer to the name of prerequisite 1. + // This name is in offset 0x1D in this message. + EXPECT_EQ(0xC01D, buf.readUint16()); + // TYPE is A + EXPECT_EQ(RRType::A().getCode(), buf.readUint16()); + // CLASS is IN (same as zone class) + EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16()); + // TTL is a 32-but value, set here to 10. + EXPECT_EQ(10, buf.readUint32()); + // For A records, the RDATA comprises the 4-byte Internet address. + // So, RDLENGTH is 4. + EXPECT_EQ(4, buf.readUint16()); + // We have stored the following address in RDATA field: 10.10.1.1 + // (which is 0A 0A 01 01) in hexadecimal format. + EXPECT_EQ(0x0A0A0101, buf.readUint32()); + + // @todo: consider extending this test to verify Additional Data + // section. +} + +// This test verifies that an attempt to call toWire function on the +// received message will result in an exception. +TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) { + // This is a binary representation of the DNS message. + // This message is valid and should be parsed with no + // error. + const uint8_t bin_msg[] = { + 0x05, 0xAF, // ID=0x05AF + 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN + 0x0, 0x0, // ZOCOUNT=0 + 0x0, 0x0, // PRCOUNT=0 + 0x0, 0x0, // UPCOUNT=0 + 0x0, 0x0 // ADCOUNT=0 + }; + + // The 'true' argument passed to the constructor turns the + // message into the parse mode in which the fromWire function + // can be used to decode the binary message data. + D2UpdateMessage msg(D2UpdateMessage::INBOUND); + ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg))); + + // The message is parsed. The QR Flag should now indicate that + // it is a Response message. + ASSERT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag()); + + // An attempt to call toWire on the Response message should + // result in the InvalidQRFlag exception. + MessageRenderer renderer; + EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag); +} + +// TSIG test +TEST_F(D2UpdateMessageTest, validTSIG) { + // Create a TSIG Key and context + std::string secret("this key will match"); + D2TsigKeyPtr right_key; + ASSERT_NO_THROW(right_key.reset(new + D2TsigKey(Name("right.com"), + TSIGKey::HMACMD5_NAME(), + secret.c_str(), secret.size()))); + + D2TsigKeyPtr wrong_key; + secret = "this key will not match"; + ASSERT_NO_THROW(wrong_key.reset(new + D2TsigKey(Name("wrong.com"), + TSIGKey::HMACMD5_NAME(), + secret.c_str(), secret.size()))); + + + // Build a request message + D2UpdateMessage msg; + msg.setId(0x1234); + msg.setRcode(Rcode(Rcode::NOERROR_CODE)); + msg.setZone(Name("example.com"), RRClass::IN()); + RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(), + RRType::ANY(), RRTTL(0))); + msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1); + RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(), + RRType::ANY(), RRTTL(0))); + msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2); + RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(), + RRType::A(), RRTTL(10))); + char rdata1[] = { + 0xA, 0xA , 0x1, 0x1 + }; + InputBuffer buf_rdata1(rdata1, 4); + updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1, + buf_rdata1.getLength())); + msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1); + + // Make a context to send the message with and use it to render + // the message into the wire format. + TSIGContextPtr context; + ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key))); + MessageRenderer renderer; + ASSERT_NO_THROW(msg.toWire(renderer, context.get())); + + // Grab the wire data from the signed message. + const void* wire_data = renderer.getData(); + const size_t wire_size = renderer.getLength(); + + // Make a context with the wrong key and use it to convert the wired data. + // Verification should fail. + D2UpdateMessage msg2(D2UpdateMessage::INBOUND); + ASSERT_NO_THROW(context.reset(new TSIGContext(*wrong_key))); + ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()), + TSIGVerifyError); + + // Now make a context with the correct key and try again. + // If the message passes TSIG verification, then the QR Flag test in + // the subsequent call to D2UpdateMessage::validateResponse should + // fail because this isn't really received message. + ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key))); + ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()), + InvalidQRFlag); +} + +// Tests message signing and verification for all supported algorithms. +TEST_F(D2UpdateMessageTest, allValidTSIG) { + std::vector<std::string>algorithms; + algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR); + algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR); + + dns::Name key_name("test_key"); + std::string secret("random text for secret"); + for (int i = 0; i < algorithms.size(); ++i) { + D2TsigKey key(key_name, + TSIGKeyInfo::stringToAlgorithmName(algorithms[i]), + secret.c_str(), secret.size()); + + // Build a request message + D2UpdateMessage msg; + msg.setId(0x1234); + msg.setRcode(Rcode(Rcode::NOERROR_CODE)); + msg.setZone(Name("example.com"), RRClass::IN()); + + // Make a context to send the message with and use it to render + // the message into the wire format. + TSIGContextPtr context; + ASSERT_NO_THROW(context.reset(new TSIGContext(key))); + MessageRenderer renderer; + ASSERT_NO_THROW(msg.toWire(renderer, context.get())); + + // Grab the wire data from the signed message. + const void* wire_data = renderer.getData(); + const size_t wire_size = renderer.getLength(); + + // Create a fresh context to "receive" the message. (We can't use the + // one we signed it with, as its expecting a signed response to its + // request. Here we are acting like the server). + // If the message passes TSIG verification, then the QR Flag test in + // the subsequent call to D2UpdateMessage::validateResponse should + // fail because this isn't really received message. + ASSERT_NO_THROW(context.reset(new TSIGContext(key))); + D2UpdateMessage msg2(D2UpdateMessage::INBOUND); + ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()), + InvalidQRFlag); + } +} + + +} // End of anonymous namespace |