diff --git a/include/tins/dns.h b/include/tins/dns.h
index 7b52553..83f1279 100644
--- a/include/tins/dns.h
+++ b/include/tins/dns.h
@@ -221,8 +221,176 @@ public:
QueryClass qclass_;
};
+ class Resource;
+
/**
- * \brief Struct that represent DNS resource records.
+ * \brief Class that represents a Start Of Authority record
+ */
+ class soa_record {
+ public:
+
+ friend class DNSTest;
+ /**
+ * \brief Default constructor
+ */
+ soa_record();
+
+ /**
+ * \brief Constructs a SOA record from a DNS::resource
+ * \param resource The resource from which to construct this record
+ */
+ soa_record(const DNS::Resource& resource);
+
+ /**
+ * \brief Constructs a SOA record from a buffer
+ * \param buffer The buffer from which to construct this SOA record
+ * \param total_sz The size of the buffer
+ */
+ soa_record(const uint8_t* buffer, uint32_t total_sz);
+
+ /**
+ * \brief Constructs a SOA record
+ *
+ * \param mname The primary source name
+ * \param rname The responsible person name
+ * \param serial The serial number
+ * \param refresh The refresh value
+ * \param retry The retry value
+ * \param expire The expire value
+ * \param minimum_ttl The minimum TTL value
+ */
+ soa_record(const std::string& mname,
+ const std::string& rname,
+ uint32_t serial,
+ uint32_t refresh,
+ uint32_t retry,
+ uint32_t expire,
+ uint32_t minimum_ttl);
+
+ /**
+ * \brief Getter for the primary source name field
+ *
+ * The returned domain name is already decoded.
+ *
+ * \return mname The primary source name field
+ */
+ const std::string& mname() const {
+ return mname_;
+ }
+
+ /**
+ * \brief Getter for the responsible person name field
+ *
+ * The returned domain name is already decoded.
+ *
+ * \return mname The responsible person name field
+ */
+ const std::string& rname() const {
+ return rname_;
+ }
+
+ /**
+ * \brief Getter for the serial number field
+ * \return The serial number field
+ */
+ uint32_t serial() const {
+ return serial_;
+ }
+
+ /**
+ * \brief Getter for the refresh field
+ * \return The refresh field
+ */
+ uint32_t refresh() const {
+ return refresh_;
+ }
+
+ /**
+ * \brief Getter for the retry field
+ * \return The retry field
+ */
+ uint32_t retry() const {
+ return retry_;
+ }
+
+ /**
+ * \brief Getter for the expire field
+ * \return The expire field
+ */
+ uint32_t expire() const {
+ return expire_;
+ }
+
+ /**
+ * \brief Getter for the minimum TTL field
+ * \return The minimum TTL field
+ */
+ uint32_t minimum_ttl() const {
+ return minimum_ttl_;
+ }
+
+ /**
+ * \brief Getter for the primary source name field
+ * \param value The new primary source name field value
+ */
+ void mname(const std::string& value);
+
+ /**
+ * \brief Getter for the responsible person name field
+ * \param value The new responsible person name field value
+ */
+ void rname(const std::string& value);
+
+ /**
+ * \brief Getter for the serial number field
+ * \param value The new serial number field value
+ */
+ void serial(uint32_t value);
+
+ /**
+ * \brief Getter for the refresh field
+ * \param value The new refresh field value
+ */
+ void refresh(uint32_t value);
+
+ /**
+ * \brief Getter for the retry field
+ * \param value The new retry field value
+ */
+ void retry(uint32_t value);
+
+ /**
+ * \brief Getter for the expire field
+ * \param value The new expire field value
+ */
+ void expire(uint32_t value);
+
+ /**
+ * \brief Getter for the minimum TTL field
+ * \param value The new minimum TTL field value
+ */
+ void minimum_ttl(uint32_t value);
+
+ /**
+ * \brief Serialize this SOA record
+ * \return The serialized SOA record
+ */
+ PDU::serialization_type serialize() const;
+ private:
+ friend class DNS;
+ void init(const uint8_t* buffer, uint32_t total_sz);
+
+ std::string mname_;
+ std::string rname_;
+ uint32_t serial_;
+ uint32_t refresh_;
+ uint32_t retry_;
+ uint32_t expire_;
+ uint32_t minimum_ttl_;
+ };
+
+ /**
+ * \brief Class that represent DNS resource records.
*/
class Resource {
public:
@@ -317,6 +485,15 @@ public:
data_ = data;
}
+ /**
+ * \brief Sets the contents of this resource to the provided SOA record
+ * \param data The SOA record that will be stored in this resource
+ */
+ void data(const soa_record& data) {
+ serialization_type buffer = data.serialize();
+ data_.assign(buffer.begin(), buffer.end());
+ }
+
/**
* Setter for the type field.
*/
@@ -693,6 +870,20 @@ public:
*/
static std::string encode_domain_name(const std::string& domain_name);
+ /**
+ * \brief Decodes a domain name
+ *
+ * This method processes an encoded domain name and returns the decoded
+ * version. This can't handle offset labels.
+ *
+ * For example, given the input "\x03www\x07example\x03com\x00",
+ * the output would be www.example.com".
+ *
+ * \param domain_name The domain name to decode.
+ * \return The decoded domain name.
+ */
+ static std::string decode_domain_name(const std::string& domain_name);
+
/**
* \brief Check wether ptr points to a valid response for this PDU.
*
@@ -709,6 +900,8 @@ public:
return new DNS(*this);
}
private:
+ friend class soa_record;
+
TINS_BEGIN_PACK
struct dns_header {
uint16_t id;
diff --git a/include/tins/exceptions.h b/include/tins/exceptions.h
index 318fe8f..cc0c9cb 100644
--- a/include/tins/exceptions.h
+++ b/include/tins/exceptions.h
@@ -263,6 +263,16 @@ public:
}
};
+/**
+ * \brief Exception thrown when an invalid domain name is parsed
+ */
+class invalid_domain_name : public exception_base {
+public:
+ const char* what() const throw() {
+ return "Invalid domain name";
+ }
+};
+
namespace Crypto {
namespace WPA2 {
/**
diff --git a/src/dns.cpp b/src/dns.cpp
index 95e539a..5a66ac2 100644
--- a/src/dns.cpp
+++ b/src/dns.cpp
@@ -294,6 +294,39 @@ string DNS::encode_domain_name(const string& dn) {
return output;
}
+string DNS::decode_domain_name(const string& domain_name) {
+ string output;
+ if (domain_name.empty()) {
+ return output;
+ }
+ const uint8_t* ptr = (const uint8_t*)&domain_name[0];
+ const uint8_t* end = ptr + domain_name.size();
+ while (*ptr) {
+ // We can't handle offsets
+ if ((*ptr & 0xc0)) {
+ throw invalid_domain_name();
+ }
+ else {
+ // It's a label, grab its size.
+ uint8_t size = *ptr;
+ ptr++;
+ if (ptr + size > end) {
+ throw malformed_packet();
+ }
+ // Append a dot if it's not the first one.
+ if (!output.empty()) {
+ output.push_back('.');
+ }
+ output.insert(output.end(), ptr, ptr + size);
+ ptr += size;
+ }
+ if (output.size() > 256) {
+ throw invalid_domain_name();
+ }
+ }
+ return output;
+}
+
// The output buffer should be at least 256 bytes long. This used to use
// a std::string but it worked about 50% slower, so this is somehow
// unsafe but a lot faster.
@@ -380,7 +413,7 @@ void DNS::convert_records(const uint8_t* ptr,
InputMemoryStream stream(ptr, end - ptr);
char dname[256], small_addr_buf[256];
while (stream) {
- string addr;
+ string data;
bool used_small_buffer = false;
// Retrieve the record's domain name.
stream.skip(compose_name(stream.pointer(), dname));
@@ -402,7 +435,7 @@ void DNS::convert_records(const uint8_t* ptr,
switch (type) {
case AAAA:
- addr = stream.read().to_string();
+ data = stream.read().to_string();
break;
case A:
inline_convert_v4(stream.read(), small_addr_buf);
@@ -417,23 +450,29 @@ void DNS::convert_records(const uint8_t* ptr,
stream.skip(data_size);
used_small_buffer = true;
break;
+ case SOA:
+ {
+ stream.skip(compose_name(stream.pointer(), small_addr_buf));
+ data = encode_domain_name(small_addr_buf);
+ stream.skip(compose_name(stream.pointer(), small_addr_buf));
+ data += encode_domain_name(small_addr_buf);
+ const uint32_t size_left = sizeof(uint32_t) * 5;
+ if (!stream.can_read(size_left)) {
+ throw malformed_packet();
+ }
+ data.insert(data.end(), stream.pointer(), stream.pointer() + size_left);
+ stream.skip(size_left);
+ }
+ break;
default:
- if (data_size < sizeof(small_addr_buf) - 1) {
- stream.read(small_addr_buf, data_size);
- // null terminator
- small_addr_buf[data_size] = 0;
- used_small_buffer = true;
- }
- else {
- addr.assign(stream.pointer(), stream.pointer() + data_size);
- stream.skip(data_size);
- }
+ data.assign(stream.pointer(), stream.pointer() + data_size);
+ stream.skip(data_size);
break;
}
res.push_back(
Resource(
dname,
- (used_small_buffer) ? small_addr_buf : addr,
+ (used_small_buffer) ? small_addr_buf : data,
type,
qclass,
ttl,
@@ -556,4 +595,91 @@ bool DNS::matches_response(const uint8_t* ptr, uint32_t total_sz) const {
return hdr->id == header_.id;
}
+// SOA record
+
+DNS::soa_record::soa_record()
+: serial_(0), refresh_(0), retry_(0), expire_(0) {
+
+}
+
+DNS::soa_record::soa_record(const string& mname,
+ const string& rname,
+ uint32_t serial,
+ uint32_t refresh,
+ uint32_t retry,
+ uint32_t expire,
+ uint32_t minimum_ttl)
+: mname_(mname), rname_(rname), serial_(serial), refresh_(refresh), retry_(retry),
+ expire_(expire), minimum_ttl_(minimum_ttl) {
+
+}
+
+DNS::soa_record::soa_record(const uint8_t* buffer, uint32_t total_sz) {
+ init(buffer, total_sz);
+}
+
+DNS::soa_record::soa_record(const DNS::Resource& resource) {
+ init((const uint8_t*)&resource.data()[0], resource.data().size());
+}
+
+void DNS::soa_record::mname(const string& value) {
+ mname_ = value;
+}
+
+void DNS::soa_record::rname(const string& value) {
+ rname_ = value;
+}
+
+void DNS::soa_record::serial(uint32_t value) {
+ serial_ = value;
+}
+
+void DNS::soa_record::refresh(uint32_t value) {
+ refresh_ = value;
+}
+
+void DNS::soa_record::retry(uint32_t value) {
+ retry_ = value;
+}
+
+void DNS::soa_record::expire(uint32_t value) {
+ expire_ = value;
+}
+
+void DNS::soa_record::minimum_ttl(uint32_t value) {
+ minimum_ttl_ = value;
+}
+
+PDU::serialization_type DNS::soa_record::serialize() const {
+ string encoded_mname = DNS::encode_domain_name(mname_);
+ string encoded_rname = DNS::encode_domain_name(rname_);
+ PDU::serialization_type output(
+ encoded_mname.size() + encoded_rname.size() + sizeof(uint32_t) * 5
+ );
+ OutputMemoryStream stream(output);
+ stream.write(encoded_mname.begin(), encoded_mname.end());
+ stream.write(encoded_rname.begin(), encoded_rname.end());
+ stream.write_be(serial_);
+ stream.write_be(refresh_);
+ stream.write_be(retry_);
+ stream.write_be(expire_);
+ stream.write_be(minimum_ttl_);
+ return output;
+}
+
+void DNS::soa_record::init(const uint8_t* buffer, uint32_t total_sz) {
+ InputMemoryStream stream(buffer, total_sz);
+ string domain = (const char*)stream.pointer();
+ mname_ = DNS::decode_domain_name(domain);
+ stream.skip(domain.size() + 1);
+ domain = (const char*)stream.pointer();
+ stream.skip(domain.size() + 1);
+ rname_ = DNS::decode_domain_name(domain);
+ serial_ = stream.read_be();
+ refresh_ = stream.read_be();
+ retry_ = stream.read_be();
+ expire_ = stream.read_be();
+ minimum_ttl_ = stream.read_be();
+}
+
} // Tins
diff --git a/tests/src/dns.cpp b/tests/src/dns.cpp
index 22c0527..fbe4840 100644
--- a/tests/src/dns.cpp
+++ b/tests/src/dns.cpp
@@ -473,3 +473,83 @@ TEST_F(DNSTest, MXPreferenceField) {
EXPECT_EQ(42, resource.preference());
EXPECT_EQ("example.com", resource.dname());
}
+
+TEST_F(DNSTest, SOARecordConstructor) {
+ DNS::soa_record r(
+ "hehehehe.example.com",
+ "john.example.com",
+ 0x9823ade9,
+ 0x918273aa,
+ 0x827361ad,
+ 0x8ad71928,
+ 0x1ad92871
+ );
+ EXPECT_EQ("hehehehe.example.com", r.mname());
+ EXPECT_EQ("john.example.com", r.rname());
+ EXPECT_EQ(0x9823ade9, r.serial());
+ EXPECT_EQ(0x918273aa, r.refresh());
+ EXPECT_EQ(0x827361ad, r.retry());
+ EXPECT_EQ(0x8ad71928, r.expire());
+ EXPECT_EQ(0x1ad92871, r.minimum_ttl());
+}
+
+TEST_F(DNSTest, SOARecordGettersAndSetters) {
+ DNS::soa_record r;
+ r.mname("hehehehe.example.com");
+ r.rname("john.example.com");
+ r.serial(0x9823ade9);
+ r.refresh(0x918273aa);
+ r.retry(0x827361ad);
+ r.expire(0x8ad71928);
+ r.minimum_ttl(0x1ad92871);
+ EXPECT_EQ("hehehehe.example.com", r.mname());
+ EXPECT_EQ("john.example.com", r.rname());
+ EXPECT_EQ(0x9823ade9, r.serial());
+ EXPECT_EQ(0x918273aa, r.refresh());
+ EXPECT_EQ(0x827361ad, r.retry());
+ EXPECT_EQ(0x8ad71928, r.expire());
+ EXPECT_EQ(0x1ad92871, r.minimum_ttl());
+}
+
+TEST_F(DNSTest, SOARecordFromBuffer) {
+ const uint8_t raw[] = {
+ 232, 101, 129, 128, 0, 1, 0, 1, 0, 0, 0, 0, 6, 103, 111, 111, 103, 108,
+ 101, 3, 99, 111, 109, 0, 0, 6, 0, 1, 192, 12, 0, 6, 0, 1, 0, 0, 0, 59,
+ 0, 38, 3, 110, 115, 50, 192, 12, 9, 100, 110, 115, 45, 97, 100, 109, 105,
+ 110, 192, 12, 6, 174, 163, 84, 0, 0, 3, 132, 0, 0, 3, 132, 0, 0, 7, 8, 0,
+ 0, 0, 60
+ };
+
+ DNS dns(raw, sizeof(raw));
+ ASSERT_EQ(1, dns.answers().size());
+ DNS::Resource r(dns.answers().front());
+ DNS::soa_record soa(r);
+ EXPECT_EQ("ns2.google.com", soa.mname());
+ EXPECT_EQ("dns-admin.google.com", soa.rname());
+ EXPECT_EQ(112108372, soa.serial());
+ EXPECT_EQ(900, soa.refresh());
+ EXPECT_EQ(900, soa.retry());
+ EXPECT_EQ(1800, soa.expire());
+ EXPECT_EQ(60, soa.minimum_ttl());
+}
+
+TEST_F(DNSTest, SOARecordSerialize) {
+ DNS::soa_record r1;
+ r1.mname("hehehehe.example.com");
+ r1.rname("john.example.com");
+ r1.serial(0x9823ade9);
+ r1.refresh(0x918273aa);
+ r1.retry(0x827361ad);
+ r1.expire(0x8ad71928);
+ r1.minimum_ttl(0x1ad92871);
+
+ DNS::serialization_type buffer = r1.serialize();
+ DNS::soa_record r2(&buffer[0], buffer.size());
+ EXPECT_EQ("hehehehe.example.com", r2.mname());
+ EXPECT_EQ("john.example.com", r2.rname());
+ EXPECT_EQ(0x9823ade9, r2.serial());
+ EXPECT_EQ(0x918273aa, r2.refresh());
+ EXPECT_EQ(0x827361ad, r2.retry());
+ EXPECT_EQ(0x8ad71928, r2.expire());
+ EXPECT_EQ(0x1ad92871, r2.minimum_ttl());
+}