Commit d908fc0e authored by Stephen Morris's avatar Stephen Morris

Merge branch 'trac1605'

parents b33929ed 5b7155e4
......@@ -21,6 +21,7 @@ libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
libdatasrc_la_SOURCES += query.h query.cc
libdatasrc_la_SOURCES += cache.h cache.cc
libdatasrc_la_SOURCES += rbnode_rrset.h
libdatasrc_la_SOURCES += rbtree.h
libdatasrc_la_SOURCES += zonetable.h zonetable.cc
libdatasrc_la_SOURCES += zone.h
......
......@@ -34,6 +34,7 @@
#include <datasrc/memory_datasrc.h>
#include <datasrc/rbtree.h>
#include <datasrc/rbnode_rrset.h>
#include <datasrc/logger.h>
#include <datasrc/iterator.h>
#include <datasrc/data_source.h>
......@@ -419,14 +420,19 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
* access is without the impl_-> and it will get inlined anyway.
*/
// Implementation of InMemoryZoneFinder::add
result::Result add(const ConstRRsetPtr& rrset, ZoneData& zone_data) {
result::Result add(const ConstRRsetPtr& rawrrset, ZoneData& zone_data) {
// Sanitize input. This will cause an exception to be thrown
// if the input RRset is empty.
addValidation(rrset);
addValidation(rawrrset);
// OK, can add the RRset.
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
arg(rawrrset->getName()).arg(rawrrset->getType()).arg(origin_);
// ... although instead of loading the RRset directly, we encapsulate
// it within an RBNodeRRset. This contains additional information that
// speeds up queries.
ConstRRsetPtr rrset(new internal::RBNodeRRset(rawrrset));
if (rrset->getType() == RRType::NSEC3()) {
return (addNSEC3(rrset, zone_data));
......
// Copyright (C) 2012 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.
#ifndef __RBNODE_RRSET_H
#define __RBNODE_RRSET_H
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrset.h>
#include <dns/rrttl.h>
#include <dns/rrtype.h>
#include <util/buffer.h>
#include <string>
namespace isc {
namespace datasrc {
namespace internal {
/// \brief Special RRset for optimizing memory datasource requirement
///
/// To speed up the performance of the in-memory data source, at load time
/// associate relevant "additional section" data with each RRset in the
/// data source.
///
/// This class, derived from AbstractRRset, holds a "const" pointer to the
/// underlying RRset object. All calls to methods on the class are passed to
/// the underlying object. However, there are some restrictions:
///
/// - Calls to methods that change attributes of the underlying RRset (such as
/// TTL or Name) cause an exception to be thrown. The in-memory data source
/// does not allow modification of these attributes. In theory, it is a bad
/// practice in that it doesn't preserve the assumed behavior of the base
/// class. In practice, however, it should be acceptable because this
/// class is effectively hidden from applications and will only be given
/// to them as a const pointer to the base class via find() variants.
/// So the application cannot call non const methods anyway unless it
/// intentionally breaks the constness.
///
/// - Calls that add the pointer to the associated RRSIG to the RRset are
/// allowed (even though the pointer is to a "const" RRset). The reason here
/// is that RRSIGs are added to the in-memory data source after the
/// RBNodeRRset objects have been created. Thus there has to be the
/// capability of modifying this information.
///
/// The class is not derived from RRset itself to simplify coding: part of the
/// loading of the memory data source is handled in the BIND 10 "libdns++"
/// code, which creates RRsets and passes them to the data source code. This
/// does not have to be altered if encapsulation, rather than inheritance, is
/// used.
///
/// \note This class is exposed in this separate header file so that test code
/// can refer to its definition, and only for that purpose. Otherwise this is
/// essentially a private class of the in-memory data source implementation,
/// and an application shouldn't directly refer to this class.
///
// Note: non-Doxygen-documented methods are documented in the base class.
class RBNodeRRset : public isc::dns::AbstractRRset {
private:
// Note: The copy constructor and the assignment operator are intentionally
// defined as private as we would normally not duplicate a RBNodeRRset.
// (We use the "private" method instead of inheriting from
// boost::noncopyable so as to avoid multiple inheritance.)
RBNodeRRset(const RBNodeRRset& source);
RBNodeRRset& operator=(const RBNodeRRset& source);
public:
/// \brief Usual Constructor
///
/// Creates an RBNodeRRset from the pointer to the RRset passed to it.
///
/// \param rrset Pointer to underlying RRset encapsulated by this object.
explicit RBNodeRRset(const isc::dns::ConstRRsetPtr& rrset) : rrset_(rrset)
{}
/// \brief Destructor
virtual ~RBNodeRRset() {}
// Getter and Setter Methods
//
// The getter methods pass the call through to the underlying RRset. The
// setter methods thrown an exception - this specialisation of the RRset
// object does not expect the underlying RRset to be modified.
virtual unsigned int getRdataCount() const {
return (rrset_->getRdataCount());
}
virtual const isc::dns::Name& getName() const {
return (rrset_->getName());
}
virtual const isc::dns::RRClass& getClass() const {
return (rrset_->getClass());
}
virtual const isc::dns::RRType& getType() const {
return (rrset_->getType());
}
virtual const isc::dns::RRTTL& getTTL() const {
return (rrset_->getTTL());
}
virtual void setName(const isc::dns::Name&) {
isc_throw(isc::NotImplemented, "RBNodeRRset::setName() not supported");
}
virtual void setTTL(const isc::dns::RRTTL&) {
isc_throw(isc::NotImplemented, "RBNodeRRset::setTTL() not supported");
}
virtual std::string toText() const {
return (rrset_->toText());
}
virtual unsigned int toWire(
isc::dns::AbstractMessageRenderer& renderer) const {
return (rrset_->toWire(renderer));
}
virtual unsigned int toWire(isc::util::OutputBuffer& buffer) const {
return (rrset_->toWire(buffer));
}
virtual void addRdata(isc::dns::rdata::ConstRdataPtr) {
isc_throw(isc::NotImplemented,
"RBNodeRRset::addRdata() not supported");
}
virtual void addRdata(const isc::dns::rdata::Rdata&) {
isc_throw(isc::NotImplemented,
"RBNodeRRset::addRdata() not supported");
}
virtual isc::dns::RdataIteratorPtr getRdataIterator() const {
return (rrset_->getRdataIterator());
}
virtual isc::dns::RRsetPtr getRRsig() const {
return (rrset_->getRRsig());
}
// With all the RRsig methods, we have the problem that we store the
// underlying RRset using a ConstRRsetPtr - a pointer to a "const" RRset -
// but we need to modify it by adding or removing an RRSIG. We overcome
// this by temporarily violating the "const" nature of the RRset to add the
// data.
virtual void addRRsig(const isc::dns::rdata::ConstRdataPtr& rdata) {
AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
p->addRRsig(rdata);
}
virtual void addRRsig(const isc::dns::rdata::RdataPtr& rdata) {
AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
p->addRRsig(rdata);
}
virtual void addRRsig(const AbstractRRset& sigs) {
AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
p->addRRsig(sigs);
}
virtual void addRRsig(const isc::dns::ConstRRsetPtr& sigs) {
AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
p->addRRsig(sigs);
}
virtual void addRRsig(const isc::dns::RRsetPtr& sigs) {
AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
p->addRRsig(sigs);
}
virtual void removeRRsig() {
AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
p->removeRRsig();
}
/// \brief Return underlying RRset pointer
///
/// ... mainly for testing.
isc::dns::ConstRRsetPtr getUnderlyingRRset() const {
return (rrset_);
}
private:
isc::dns::ConstRRsetPtr rrset_; ///< Underlying RRset
};
} // namespace internal
} // namespace datasrc
} // namespace isc
#endif // __RBNODE_RRSET_H
......@@ -79,6 +79,7 @@ run_unittests_sqlite3_LDADD = $(common_ldadd)
# In-memory datasource tests
run_unittests_memory_SOURCES = $(common_sources)
run_unittests_memory_SOURCES += memory_datasrc_unittest.cc
run_unittests_memory_SOURCES += rbnode_rrset_unittest.cc
run_unittests_memory_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
run_unittests_memory_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
......@@ -109,19 +110,21 @@ endif
endif
EXTRA_DIST = testdata/brokendb.sqlite3
EXTRA_DIST += testdata/diffs.sqlite3
EXTRA_DIST += testdata/example2.com
EXTRA_DIST += testdata/example2.com.sqlite3
EXTRA_DIST += testdata/example.com.signed
EXTRA_DIST += testdata/example.org
EXTRA_DIST += testdata/example.org.nsec3-signed
EXTRA_DIST += testdata/example.org.nsec3-signed-noparam
EXTRA_DIST += testdata/example.org.sqlite3
EXTRA_DIST += testdata/example2.com
EXTRA_DIST += testdata/example2.com.sqlite3
EXTRA_DIST += testdata/mkbrokendb.c
EXTRA_DIST += testdata/root.zone
EXTRA_DIST += testdata/rrset_toWire1
EXTRA_DIST += testdata/rrset_toWire2
EXTRA_DIST += testdata/rwtest.sqlite3
EXTRA_DIST += testdata/sql1.example.com.signed
EXTRA_DIST += testdata/sql2.example.com.signed
EXTRA_DIST += testdata/test-root.sqlite3
EXTRA_DIST += testdata/test.sqlite3
EXTRA_DIST += testdata/test.sqlite3.nodiffs
EXTRA_DIST += testdata/rwtest.sqlite3
EXTRA_DIST += testdata/diffs.sqlite3
......@@ -172,14 +172,23 @@ TEST_F(InMemoryClientTest, iterator) {
EXPECT_EQ(result::SUCCESS, zone->add(aRRsetA));
EXPECT_EQ(result::SUCCESS, zone->add(aRRsetAAAA));
EXPECT_EQ(result::SUCCESS, zone->add(subRRsetA));
// Check it with full zone, one by one.
// It should be in ascending order in case of InMemory data source
// (isn't guaranteed in general)
// Check it with full zone.
vector<ConstRRsetPtr> expected_rrsets;
expected_rrsets.push_back(aRRsetA);
expected_rrsets.push_back(aRRsetAAAA);
expected_rrsets.push_back(subRRsetA);
iterator = memory_client.getIterator(Name("a"));
EXPECT_EQ(aRRsetA, iterator->getNextRRset());
EXPECT_EQ(aRRsetAAAA, iterator->getNextRRset());
EXPECT_EQ(subRRsetA, iterator->getNextRRset());
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
vector<ConstRRsetPtr> actual_rrsets;
ConstRRsetPtr actual;
while ((actual = iterator->getNextRRset()) != NULL) {
actual_rrsets.push_back(actual);
}
rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
actual_rrsets.begin(), actual_rrsets.end());
}
TEST_F(InMemoryClientTest, iterator_separate_rrs) {
......
// Copyright (C) 2012 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.
#include <stdexcept>
#include <exceptions/exceptions.h>
#include <dns/rdataclass.h>
#include <datasrc/rbnode_rrset.h>
#include <testutils/dnsmessage_test.h>
#include <gtest/gtest.h>
#include <dns/tests/unittest_util.h>
using isc::UnitTestUtil;
using namespace isc;
using namespace isc::datasrc;
using namespace isc::datasrc::internal;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::testutils;
using namespace isc::util;
using namespace std;
// These tests are very similar to those for RRset - indeed, this file was
// created from those tests. However, the significant difference in behaviour
// between RRset and RBNodeRRset - that the "set" methods in the latter mostly
// result in exceptions being thrown - preclude use of full type
// parameterisation of the tests.
namespace {
const char* const RRSIG_TXT =
"A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
"f49t+sXKPzbipN9g+s1ZPiIyofc=";
class RBNodeRRsetTest : public ::testing::Test {
protected:
RBNodeRRsetTest() :
test_name("test.example.com"),
test_domain("example.com"),
test_nsname("ns.example.com"),
rrset_a(ConstRRsetPtr(new RRset(
test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
rrset_a_empty(ConstRRsetPtr(new RRset(
test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
rrset_ns(ConstRRsetPtr(new RRset(
test_domain, RRClass::IN(), RRType::NS(), RRTTL(86400)))),
rrset_ch_txt(ConstRRsetPtr(new RRset(
test_domain, RRClass::CH(), RRType::TXT(), RRTTL(0)))),
rrset_siga(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
RRTTL(3600)))
{
// Add a couple of Rdata elements to the A RRset. The easiest way to
// do this is to override the "const" restrictions. As this is a test,
// we don't feel too bad about doing so.
AbstractRRset* a_rrset =
const_cast<AbstractRRset*>(rrset_a.getUnderlyingRRset().get());
a_rrset->addRdata(in::A("192.0.2.1"));
a_rrset->addRdata(in::A("192.0.2.2"));
// Create the RRSIG corresponding to the rrset_a record. The RDATA
// won't match the A record it covers, although it is internally
// self-consistent.
AbstractRRset* sig_rrset =
const_cast<AbstractRRset*>(rrset_siga.get());
sig_rrset->addRdata(generic::RRSIG(RRSIG_TXT));
}
const Name test_name;
const Name test_domain;
const Name test_nsname;
RBNodeRRset rrset_a;
RBNodeRRset rrset_a_empty;
const RBNodeRRset rrset_ns;
const RBNodeRRset rrset_ch_txt;
ConstRRsetPtr rrset_siga;
};
TEST_F(RBNodeRRsetTest, getRdataCount) {
EXPECT_EQ(0, rrset_a_empty.getRdataCount());
EXPECT_EQ(2, rrset_a.getRdataCount());
}
TEST_F(RBNodeRRsetTest, getName) {
EXPECT_EQ(test_name, rrset_a.getName());
EXPECT_EQ(test_domain, rrset_ns.getName());
}
TEST_F(RBNodeRRsetTest, getClass) {
EXPECT_EQ(RRClass("IN"), rrset_a.getClass());
EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass());
}
TEST_F(RBNodeRRsetTest, getType) {
EXPECT_EQ(RRType("A"), rrset_a.getType());
EXPECT_EQ(RRType("NS"), rrset_ns.getType());
EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType());
}
TEST_F(RBNodeRRsetTest, getTTL) {
EXPECT_EQ(RRTTL(3600), rrset_a.getTTL());
EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL());
EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL());
}
TEST_F(RBNodeRRsetTest, setName) {
EXPECT_THROW(rrset_a.setName(test_nsname), NotImplemented);
}
TEST_F(RBNodeRRsetTest, setTTL) {
EXPECT_THROW(rrset_a.setTTL(RRTTL(86400)), NotImplemented);
}
TEST_F(RBNodeRRsetTest, toText) {
EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
"test.example.com. 3600 IN A 192.0.2.2\n",
rrset_a.toText());
// toText() cannot be performed for an empty RRset.
EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset);
}
// Note: although the next two tests are essentially the same and used common
// test code, they use different test data: the MessageRenderer produces
// compressed wire data whereas the OutputBuffer does not.
template <typename T>
void
performToWireTest(T& dataHolder, const RBNodeRRset& rrset,
const RBNodeRRset& rrset_empty, const char* testdata)
{
rrset.toWire(dataHolder);
std::vector<unsigned char> wiredata;
UnitTestUtil::readWireData(testdata, wiredata);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, dataHolder.getData(),
dataHolder.getLength(), &wiredata[0], wiredata.size());
// toWire() cannot be performed for an empty RRset.
dataHolder.clear();
EXPECT_THROW(rrset_empty.toWire(dataHolder), EmptyRRset);
}
TEST_F(RBNodeRRsetTest, toWireRenderer) {
MessageRenderer renderer;
performToWireTest(renderer, rrset_a, rrset_a_empty, "rrset_toWire2");
}
TEST_F(RBNodeRRsetTest, toWireBuffer) {
OutputBuffer buffer(0);
performToWireTest(buffer, rrset_a, rrset_a_empty, "rrset_toWire1");
}
TEST_F(RBNodeRRsetTest, addRdata) {
EXPECT_THROW(rrset_a.addRdata(in::A("192.0.2.3")), NotImplemented);
// Check the same goes for trying to add the wrong type of data
EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), NotImplemented);
}
TEST_F(RBNodeRRsetTest, addRdataPtr) {
EXPECT_THROW(rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
rrset_a_empty.getClass(),
"192.0.2.1")),
NotImplemented);
}
TEST_F(RBNodeRRsetTest, getRDataIterator) {
RdataIteratorPtr it = rrset_a.getRdataIterator();
for (int i = 0; i < 2; ++i) {
ASSERT_FALSE(it->isLast());
ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
it->next();
ASSERT_FALSE(it->isLast());
ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2")));
it->next();
ASSERT_TRUE(it->isLast());
// Should be able repeat the iteration by calling first().
it->first();
}
}
// test operator<<. We simply confirm it appends the result of toText().
TEST_F(RBNodeRRsetTest, LeftShiftOperator) {
ostringstream oss;
oss << rrset_a;
EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
"test.example.com. 3600 IN A 192.0.2.2\n", oss.str());
}
// addRRSIG tests.
TEST_F(RBNodeRRsetTest, addRRsigConstRdataPointer) {
EXPECT_FALSE(rrset_a.getRRsig());
ConstRdataPtr data = createRdata(rrset_siga->getType(),
rrset_siga->getClass(), RRSIG_TXT);
rrset_a.addRRsig(data);
rrsetCheck(rrset_siga, rrset_a.getRRsig());
}
TEST_F(RBNodeRRsetTest, addRRsigRdataPointer) {
EXPECT_FALSE(rrset_a.getRRsig());
RdataPtr data = createRdata(rrset_siga->getType(), rrset_siga->getClass(),
RRSIG_TXT);
rrset_a.addRRsig(data);
rrsetCheck(rrset_siga, rrset_a.getRRsig());
}
TEST_F(RBNodeRRsetTest, addRRsigAbstractRRset) {
EXPECT_FALSE(rrset_a.getRRsig());
rrset_a.addRRsig(*(rrset_siga.get()));
rrsetCheck(rrset_siga, rrset_a.getRRsig());
}
TEST_F(RBNodeRRsetTest, addRRsigConstantRRsetPointer) {
EXPECT_FALSE(rrset_a.getRRsig());
rrset_a.addRRsig(rrset_siga);
rrsetCheck(rrset_siga, rrset_a.getRRsig());
}
TEST_F(RBNodeRRsetTest, addRRsigRRsetPointer) {
EXPECT_FALSE(rrset_a.getRRsig());
RRsetPtr rrsig(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
RRTTL(3600)));
rrsig->addRdata(generic::RRSIG(RRSIG_TXT));
rrset_a.addRRsig(rrsig);
rrsetCheck(rrset_siga, rrset_a.getRRsig());
}
TEST_F(RBNodeRRsetTest, removeRRsig) {
EXPECT_FALSE(rrset_a.getRRsig());
rrset_a.addRRsig(*(rrset_siga.get()));
EXPECT_TRUE(rrset_a.getRRsig());
rrset_a.removeRRsig();
EXPECT_FALSE(rrset_a.getRRsig());
}
} // Anonymous namespace
#
# Rendering an IN/A RRset containing 2 RRs:
# test.example.com. 3600 IN A 192.0.2.1
# test.example.com. 3600 IN A 192.0.2.2
#
#(4) t e s t (7) e x a m p l e (3) c o m .
04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
# type/class: A = 1, IN = 1
00 01 00 01
# TTL: 3600
00 00 0e 10
#6 7
# RDLENGTH: 4
00 04
# RDATA: 192.0.2.1
c0 00 02 01
#
# 2nd RR: mostly the same except the RDATA
04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
00 01 00 01
00 00 0e 10
00 04
c0 00 02 02
#
# Rendering an IN/A RRset and NS RRset as follows:
# test.example.com. 3600 IN A 192.0.2.1
# test.example.com. 3600 IN A 192.0.2.2
# example.com. 1D IN NS ns.example.com.
# Names will be compressed when possible.
#
# 0 1 2 3 4 5
#(4) t e s t (7) e x a m p l e (3) c o m .
04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
# type/class: A = 1, IN = 1
00 01 00 01
# TTL: 3600
00 00 0e 10
#6 7
# RDLENGTH: 4
00 04
# RDATA: 192.0.2.1
c0 00 02 01
#
# 2nd RR: the owner name is compresed
c0 00
00 01 00 01
00 00 0e 10
00 04
c0 00 02 02
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment