/* * Copyright (c) 2017, Matias Fontanini * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef TINS_DNS_H #define TINS_DNS_H #include #include #include #include #include #include #include #include // Undefining some macros that conflict with some symbols here. // Eventually, the conflicting names will be removed, but until then // this is the best we can do. // - IN is defined by winsock2.h // - CERT is defined by openssl #undef IN #undef CERT namespace Tins { namespace Memory { class InputMemoryStream; } // Memory class IPv4Address; class IPv6Address; /** * \class DNS * \brief Represents a DNS PDU. * * This class represents the DNS PDU, and allows easy access * to queries and answer records. * * The DNS PDU is not parsed automatically while sniffing, so you will * have to parse it manually from an UDP packet's payload, for example: * * \code * // Assume we get an udp packet from somewhere. * UDP udp = get_udp_packet(); * * // Now: * // 1 - Get the RawPDU layer (contains the payload). * // 2 - Construct a DNS object over its contents. * DNS dns = udp.rfind_pdu().to(); * * // Now use the DNS object! * for(const auto& query : dns.queries()) { * // Process a query * } * \endcode */ class TINS_API DNS : public PDU { public: /** * \brief This PDU's flag. */ static const PDU::PDUType pdu_flag = PDU::DNS; /** * The DNS type. */ enum QRType { QUERY = 0, RESPONSE = 1 }; /** * \brief Query types enum. */ enum QueryType { A = 1, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL_R, WKS, PTR, HINFO, MINFO, MX, TXT, RP, AFSDB, X25, ISDN, RT, NSAP, NSAP_PTR, SIG, KEY, PX, GPOS, AAAA, LOC, NXT, EID, NIMLOC, SRV, ATMA, NAPTR, KX, CERTIFICATE, A6, DNAM, SINK, OPT, APL, DS, SSHFP, IPSECKEY, RRSIG, NSEC, DNSKEY, DHCID, NSEC3, NSEC3PARAM, CERT = CERTIFICATE }; enum QueryClass { INTERNET = 1, CHAOS = 3, HESIOD = 4, /** * \cond */ IN = INTERNET, CH = CHAOS, HS = HESIOD, /** * \endcond */ ANY = 255 }; /** * \brief Struct that represent DNS queries. */ class query { public: /** * \brief Constructs a DNS query. * * \param nm The name of the domain being resolved. * \param tp The query type. * \param cl The query class. */ #if TINS_IS_CXX11 query(std::string nm, QueryType tp, QueryClass cl) : name_(std::move(nm)), type_(tp), qclass_(cl) {} #else query(const std::string& nm, QueryType tp, QueryClass cl) : name_(nm), type_(tp), qclass_(cl) {} #endif /** * \brief Default constructs this Query. */ query() : type_(), qclass_() {} /** * \brief Setter for the name field. * * \param nm The name to be set. */ void dname(const std::string& nm) { name_ = nm; } /** * \brief Setter for the query type field. * * \param tp The query type to be set. */ void query_type(QueryType tp) { type_ = tp; } /** * \brief Setter for the query type field. * * This method is deprecated. Use query::query_type * * \deprecated * \sa query::query_type */ TINS_DEPRECATED(void type(QueryType tp)) { type_ = tp; } /** * \brief Setter for the query class field. * * \param cl The query class to be set. */ void query_class(QueryClass cl) { qclass_ = cl; } /** * \brief Getter for the name field. */ const std::string& dname() const { return name_; } /** * \brief Getter for the query type field. */ QueryType query_type() const { return type_; } /** * \brief Getter for the query type field. * * This method is deprecated. Use query::query_type * * \deprecated * \sa query::query_type */ TINS_DEPRECATED(QueryType type() const) { return type_; } /** * \brief Getter for the query class field. */ QueryClass query_class() const { return qclass_; } private: std::string name_; QueryType type_; QueryClass qclass_; }; class resource; /** * \brief Class that represents a Start Of Authority record */ class soa_record { public: /** * \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: 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: /** * Constructs a Resource object. * * \param dname The domain name for which this records * provides an answer. * \param data The resource's payload. * \param type The type of this record. * \param rclass The class of this record. * \param ttl The time-to-live of this record. */ #if TINS_IS_CXX11 resource(std::string dname, std::string data, uint16_t type, uint16_t rclass, uint32_t ttl, uint16_t preference = 0) : dname_(std::move(dname)), data_(std::move(data)), type_(type), qclass_(rclass), ttl_(ttl), preference_(preference) {} #else resource(const std::string& dname, const std::string& data, uint16_t type, uint16_t rclass, uint32_t ttl, uint16_t preference = 0) : dname_(dname), data_(data), type_(type), qclass_(rclass), ttl_(ttl), preference_(preference) {} #endif resource() : type_(), qclass_(), ttl_(), preference_() {} /** * \brief Getter for the domain name field. * * This returns the domain name for which this record * provides an answer. */ const std::string& dname() const { return dname_; } /** * Getter for the data field. */ const std::string& data() const { return data_; } /** * Getter for the query type field. */ uint16_t query_type() const { return type_; } /** * \brief Getter for the query type field. * * This method is deprecated. Use resource::query_type * * \deprecated * \sa resource::query_type */ TINS_DEPRECATED(uint16_t type() const) { return type_; } /** * Getter for the query class field. */ uint16_t query_class() const { return qclass_; } /** * Getter for the time-to-live field. */ uint32_t ttl() const { return ttl_; } /** * \brief Getter for the preferece field. * * This field is only valid for MX resources. */ uint16_t preference() const { return preference_; } /** * Setter for the domain name field. */ void dname(const std::string& data) { dname_ = data; } /** * \brief Setter for the data field. * * The data will be encoded properly by the DNS class before * being added to this packet. That means that if the type is * A or AAAA, it will be properly encoded as an IPv4 or * IPv6 address. * * The same happens for records that contain domain names, * such as NS or CNAME. This data will be encoded using * DNS domain name encoding. */ void data(const std::string& data) { 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 query type field. */ void query_type(uint16_t data) { type_ = data; } /** * \brief Setter for the query type field. * * This method is deprecated. Use query::query_type * * \deprecated * \sa resource::query_type */ TINS_DEPRECATED(void type(uint16_t data)) { type_ = data; } /** * Setter for the query class field. */ void query_class(uint16_t data) { qclass_ = data; } /** * Setter for the time-to-live field. */ void ttl(uint32_t data) { ttl_ = data; } /** * \brief Setter for the preference field. * * This field is only valid for MX resources. */ void preference(uint16_t data) { preference_ = data; } private: std::string dname_, data_; uint16_t type_, qclass_; uint32_t ttl_; uint16_t preference_; }; TINS_DEPRECATED(typedef query Query); TINS_DEPRECATED(typedef resource Resource); typedef std::vector queries_type; typedef std::vector resources_type; typedef IPv4Address address_type; typedef IPv6Address address_v6_type; /** * \brief Extracts metadata for this protocol based on the buffer provided * * \param buffer Pointer to a buffer * \param total_sz Size of the buffer pointed by buffer */ static metadata extract_metadata(const uint8_t *buffer, uint32_t total_sz); /** * \brief Default constructor. * * This constructor initializes every field to 0. */ DNS(); /** * \brief Constructs a DNS object from a buffer. * * If there's not enough size for the DNS header, or any of the * records are malformed, a malformed_packet is be thrown. * * \param buffer The buffer from which this PDU will be * constructed. * \param total_sz The total size of the buffer. */ DNS(const uint8_t* buffer, uint32_t total_sz); // Getters /** * \brief Getter for the id field. * * \return uint16_t containing the value of the id field. */ uint16_t id() const { return Endian::be_to_host(header_.id); } /** * \brief Getter for the query response field. * * \return QRType containing the value of the query response * field. */ QRType type() const { return static_cast(header_.qr); } /** * \brief Getter for the opcode field. * * \return uint8_t containing the value of the opcode field. */ uint8_t opcode() const { return header_.opcode; } /** * \brief Getter for the authoritative answer field. * * \return uint8_t containing the value of the authoritative * answer field. */ uint8_t authoritative_answer() const { return header_.aa; } /** * \brief Getter for the truncated field. * * \return uint8_t containing the value of the truncated field. */ uint8_t truncated() const { return header_.tc; } /** * \brief Getter for the recursion desired field. * * \return uint8_t containing the value of the recursion * desired field. */ uint8_t recursion_desired() const { return header_.rd; } /** * \brief Getter for the recursion available field. * * \return uint8_t containing the value of the recursion * available field. */ uint8_t recursion_available() const { return header_.ra; } /** * \brief Getter for the z desired field. * * \return uint8_t containing the value of the z field. */ uint8_t z() const { return header_.z; } /** * \brief Getter for the authenticated data field. * * \return uint8_t containing the value of the authenticated * data field. */ uint8_t authenticated_data() const { return header_.ad; } /** * \brief Getter for the checking disabled field. * * \return uint8_t containing the value of the checking * disabled field. */ uint8_t checking_disabled() const { return header_.cd; } /** * \brief Getter for the rcode field. * * \return uint8_t containing the value of the rcode field. */ uint8_t rcode() const { return header_.rcode; } /** * \brief Getter for the questions field. * * \return uint16_t containing the value of the questions field. */ uint16_t questions_count() const { return Endian::be_to_host(header_.questions); } /** * \brief Getter for the answers field. * * \return uint16_t containing the value of the answers field. */ uint16_t answers_count() const { return Endian::be_to_host(header_.answers); } /** * \brief Getter for the authority field. * * \return uint16_t containing the value of the authority field. */ uint16_t authority_count() const { return Endian::be_to_host(header_.authority); } /** * \brief Getter for the additional field. * * \return uint16_t containing the value of the additional field. */ uint16_t additional_count() const { return Endian::be_to_host(header_.additional); } /** * \brief Getter for the PDU's type. * * \return Returns the PDUType corresponding to the PDU. */ PDUType pdu_type() const { return pdu_flag; } /** * \brief The header's size */ uint32_t header_size() const; // Setters /** * \brief Setter for the id field. * * \param new_id The new id to be set. */ void id(uint16_t new_id); /** * \brief Setter for the query response field. * * \param new_qr The new qr to be set. */ void type(QRType new_qr); /** * \brief Setter for the opcode field. * * \param new_opcode The new opcode to be set. */ void opcode(uint8_t new_opcode); /** * \brief Setter for the authoritative answer field. * * \param new_aa The new authoritative answer field value to * be set. */ void authoritative_answer(uint8_t new_aa); /** * \brief Setter for the truncated field. * * \param new_tc The new truncated field value to * be set. */ void truncated(uint8_t new_tc); /** * \brief Setter for the recursion desired field. * * \param new_rd The new recursion desired value to * be set. */ void recursion_desired(uint8_t new_rd); /** * \brief Setter for the recursion available field. * * \param new_ra The new recursion available value to * be set. */ void recursion_available(uint8_t new_ra); /** * \brief Setter for the z(reserved) field. * * \param new_z The new z value to be set. */ void z(uint8_t new_z); /** * \brief Setter for the authenticated data field. * * \param new_ad The new authenticated data value to * be set. */ void authenticated_data(uint8_t new_ad); /** * \brief Setter for the checking disabled field. * * \param new_z The new checking disabled value to be set. */ void checking_disabled(uint8_t new_cd); /** * \brief Setter for the rcode field. * * \param new_rcode The new rcode value to be set. */ void rcode(uint8_t new_rcode); // Methods /** * \brief Add a query to perform. * * \param query The query to be added. */ void add_query(const query& query); /** * \brief Add an answer resource record. * * \param resource The resource to be added. */ void add_answer(const resource& resource); /** * \brief Add an authority resource record. * * \param resource The resource to be added. */ void add_authority(const resource& resource); /** * \brief Add an additional resource record. * * \param resource The resource to be added. */ void add_additional(const resource& resource); /** * \brief Getter for this PDU's DNS queries. * * \return The query records in this PDU. */ queries_type queries() const; /** * \brief Getter for this PDU's DNS answers * * \return The answer records in this PDU. */ resources_type answers() const; /** * \brief Getter for this PDU's DNS authority records. * * \return The authority records in this PDU. */ resources_type authority() const; /** * \brief Getter for this PDU's DNS additional records. * * \return The additional records in this PDU. */ resources_type additional() const; /** * \brief Encodes a domain name. * * This processes the input domain name and returns the encoded * version. Each label in the original domain name will be * prefixed with a byte that indicates the label's length. * The null-terminator byte will be included in the encoded * string. No compression is performed. * * For example, given the input "www.example.com", the output would * be "\x03www\x07example\x03com\x00". * * \param domain_name The domain name to encode. * \return The encoded domain name. */ 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 whether ptr points to a valid response for this PDU. * * \sa PDU::matches_response * \param ptr The pointer to the buffer. * \param total_sz The size of the buffer. */ bool matches_response(const uint8_t* ptr, uint32_t total_sz) const; /** * \sa PDU::clone */ DNS* clone() const { return new DNS(*this); } private: friend class soa_record; TINS_BEGIN_PACK struct dns_header { uint16_t id; #if TINS_IS_LITTLE_ENDIAN uint16_t rd:1, tc:1, aa:1, opcode:4, qr:1, rcode:4, cd:1, ad:1, z:1, ra:1; #elif TINS_IS_BIG_ENDIAN uint16_t qr:1, opcode:4, aa:1, tc:1, rd:1, ra:1, z:1, ad:1, cd:1, rcode:4; #endif uint16_t questions, answers, authority, additional; } TINS_END_PACK; typedef std::vector > sections_type; uint32_t compose_name(const uint8_t* ptr, char* out_ptr) const; void convert_records(const uint8_t* ptr, const uint8_t* end, resources_type& res, const uint16_t rr_count) const; void skip_to_section_end(Memory::InputMemoryStream& stream, const uint32_t num_records) const; void skip_to_dname_end(Memory::InputMemoryStream& stream) const; void update_records(uint32_t& section_start, uint32_t num_records, uint32_t threshold, uint32_t offset); uint8_t* update_dname(uint8_t* ptr, uint32_t threshold, uint32_t offset); static void inline_convert_v4(uint32_t value, char* output); static bool contains_dname(uint16_t type); void write_serialization(uint8_t* buffer, uint32_t total_sz); void add_record(const resource& resource, const sections_type& sections); dns_header header_; byte_array records_data_; uint32_t answers_idx_, authority_idx_, additional_idx_; }; } // Tins #endif // TINS_DNS_H