diff options
Diffstat (limited to 'src/lib/d2srv/d2_update_message.h')
-rw-r--r-- | src/lib/d2srv/d2_update_message.h | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/src/lib/d2srv/d2_update_message.h b/src/lib/d2srv/d2_update_message.h new file mode 100644 index 0000000..8365128 --- /dev/null +++ b/src/lib/d2srv/d2_update_message.h @@ -0,0 +1,357 @@ +// 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/. + +#ifndef D2_UPDATE_MESSAGE_H +#define D2_UPDATE_MESSAGE_H + +#include <d2srv/d2_zone.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rcode.h> +#include <dns/rrclass.h> +#include <dns/rrset.h> +#include <dns/tsig.h> + +#include <map> + +namespace isc { +namespace d2 { + +/// @brief Exception indicating that Zone section contains invalid content. +/// +/// This exception is thrown when ZONE section of the DNS Update message +/// is invalid. According to RFC2136, section 2.3, the zone section is +/// allowed to contain exactly one record. When Request message contains +/// more records or is empty, this exception is thrown. +class InvalidZoneSection : public Exception { +public: + InvalidZoneSection(const char* file, size_t line, const char* what) : + 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 Exception indicating that a signed, inbound message failed to verify +/// +/// This exception is thrown when TSIG verification of a DNS server's response +/// fails. +class TSIGVerifyError : public Exception { +public: + TSIGVerifyError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + + +class D2UpdateMessage; + +/// @brief Pointer to the DNS Update Message. +typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr; + +/// @brief The @c D2UpdateMessage encapsulates a DNS Update message. +/// +/// This class represents the DNS Update message. Functions exposed by this +/// class allow to specify the data sections carried by the message and create +/// an on-wire format of this message. This class is also used to decode +/// messages received from the DNS server in the on-wire format. +/// +/// <b>Design choice:</b> A dedicated class has been created to encapsulate +/// DNS Update message because existing @c isc::dns::Message is designed to +/// support regular DNS messages (described in RFC 1035) only. Although DNS +/// Update has the same format, particular sections serve different purposes. +/// In order to avoid rewrite of significant portions of @c isc::dns::Message +/// class, this class is implemented in-terms-of @c isc::dns::Message class +/// to reuse its functionality where possible. +class D2UpdateMessage { +public: + + /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound + /// or Outbound message. + enum Direction { + INBOUND, + OUTBOUND + }; + + /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE. + enum QRFlag { + REQUEST, + RESPONSE + }; + + /// @brief Identifies sections in the DNS Update Message. + /// + /// Each message comprises message Header and may contain the following + /// sections: + /// - ZONE + /// - PREREQUISITE + /// - UPDATE + /// - ADDITIONAL + /// + /// The enum elements are used by functions such as @c getRRCount (to get + /// the number of records in a corresponding section) and @c beginSection + /// and @c endSection (to access data in the corresponding section). + enum UpdateMsgSection { + SECTION_ZONE, + SECTION_PREREQUISITE, + SECTION_UPDATE, + SECTION_ADDITIONAL + }; + +public: + /// @brief Constructor used to create an instance of the DNS Update Message + /// (either outgoing or incoming). + /// + /// This constructor is used to create an instance of either incoming or + /// outgoing DNS Update message. The boolean argument indicates whether it + /// is incoming (true) or outgoing (false) message. For incoming messages + /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data. + /// For outgoing messages, modifier functions should be used to set the + /// message contents and @c D2UpdateMessage::toWire function to create + /// on-wire data. + /// + /// @param direction indicates if this is an inbound or outbound message. + D2UpdateMessage(const Direction direction = OUTBOUND); + + /// + /// @name Copy constructor and assignment operator + /// + /// Copy constructor and assignment operator are private because we assume + /// there will be no need to copy messages on the client side. + //@{ +private: + D2UpdateMessage(const D2UpdateMessage& source); + D2UpdateMessage& operator=(const D2UpdateMessage& source); + //@} + +public: + + /// @brief Returns enum value indicating if the message is a + /// REQUEST or RESPONSE + /// + /// The returned value is REQUEST if the message is created as an outgoing + /// message. In such case the QR flag bit in the message header is cleared. + /// The returned value is RESPONSE if the message is created as an incoming + /// message and the QR flag bit was set in the received message header. + /// + /// @return An enum value indicating whether the message is a + /// REQUEST or RESPONSE. + QRFlag getQRFlag() const; + + /// @brief Returns message ID. + /// + /// @return message ID. + uint16_t getId() const; + + /// @brief Sets message ID. + /// + /// @param id 16-bit value of the message id. + void setId(const uint16_t id); + + /// @brief Returns an object representing message RCode. + /// + /// @return An object representing message RCode. + const dns::Rcode& getRcode() const; + + /// @brief Sets message RCode. + /// + /// @param rcode An object representing message RCode. + void setRcode(const dns::Rcode& rcode); + + /// @brief Returns number of RRsets in the specified message section. + /// + /// @param section An @c UpdateMsgSection enum specifying a message section + /// for which the number of RRsets is to be returned. + /// + /// @return A number of RRsets in the specified message section. + unsigned int getRRCount(const UpdateMsgSection section) const; + + /// @name Functions returning iterators to RRsets in message sections. + /// + //@{ + /// @brief Return iterators pointing to the beginning of the list of RRsets, + /// which belong to the specified section. + /// + /// @param section An @c UpdateMsgSection enum specifying a message section + /// for which the iterator should be returned. + /// + /// @return An iterator pointing to the beginning of the list of the + /// RRsets, which belong to the specified section. + const dns::RRsetIterator beginSection(const UpdateMsgSection section) const; + + /// @brief Return iterators pointing to the end of the list of RRsets, + /// which belong to the specified section. + /// + /// @param section An @c UpdateMsgSection enum specifying a message section + /// for which the iterator should be returned. + /// + /// @return An iterator pointing to the end of the list of the + /// RRsets, which belong to the specified section. + const dns::RRsetIterator endSection(const UpdateMsgSection section) const; + //@} + + /// @brief Sets the Zone record. + /// + /// This function creates the @c D2Zone object, representing a Zone record + /// for the outgoing message. If the Zone record is already set, it is + /// replaced by the new record being set by this function. The RRType for + /// the record is always SOA. + /// + /// @param zone A name of the zone being updated. + /// @param rrclass A class of the zone record. + void setZone(const dns::Name& zone, const dns::RRClass& rrclass); + + /// @brief Returns a pointer to the object representing Zone record. + /// + /// @return A pointer to the object representing Zone record. + D2ZonePtr getZone() const; + + /// @brief Adds an RRset to the specified section. + /// + /// This function may throw exception if the specified section is + /// out of bounds or Zone section update is attempted. For Zone + /// section @c D2UpdateMessage::setZone function should be used instead. + /// Also, this function expects that @c rrset argument is non-NULL. + /// + /// @param section A message section where the RRset should be added. + /// @param rrset A reference to a RRset which should be added. + void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset); + + /// @name Functions to handle message encoding and decoding. + /// + //@{ + /// @brief Encode outgoing message into wire format. + /// + /// This function encodes the DNS Update into the wire format. The format of + /// such a message is described in the RFC2136, section 2. Some of the + /// sections which belong to encoded message may be empty. If a particular + /// message section is empty (does not comprise any RRs), the corresponding + /// counter in the message header is set to 0. These counters are: PRCOUNT, + /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data + /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136 + /// requires that the message comprises exactly one Zone record. + /// + /// If given a TSIG context, this method will pass the context down into + /// dns::Message.toWire() method which signs the message using the context. + /// + /// This function does not guarantee exception safety. However, exceptions + /// should be rare because @c D2UpdateMessage class API prevents invalid + /// use of the class. The typical case, when this function may throw an + /// exception is when this it is called on the object representing + /// incoming (instead of outgoing) message. In such case, the QR field + /// will be set to RESPONSE, which is invalid setting when calling this + /// function. + /// + /// @param renderer A renderer object used to generate the message wire + /// format. + /// @param tsig_ctx A TSIG context that is to be used for signing the + /// message. If NULL the message will not be signed. + void toWire(dns::AbstractMessageRenderer& renderer, + dns::TSIGContext* const tsig_ctx = NULL); + + /// @brief Decode incoming message from the wire format. + /// + /// This function decodes the DNS Update message stored in the buffer + /// specified by the function argument. If given a TSIG context, then + /// the function will first attempt to use that context to verify the + /// message signature. If verification fails a TSIGVerifyError exception + /// will be thrown. The function then parses message header and extracts + /// the section counters: ZOCOUNT, PRCOUNT, UPCOUNT and ADCOUNT. Using + /// these counters, function identifies message sections, which follow + /// message header. These sections can be later accessed using: + /// @c D2UpdateMessage::getZone, @c D2UpdateMessage::beginSection and + /// @c D2UpdateMessage::endSection functions. + /// + /// This function is NOT exception safe. It signals message decoding errors + /// through exceptions. Message decoding error may occur if the received + /// message does not conform to the general DNS Message format, specified in + /// RFC 1035. Errors which are specific to DNS Update messages include: + /// - Invalid Opcode - not an UPDATE. + /// - Invalid QR flag - the QR bit should be set to indicate that the + /// message is the server response. + /// - The number of records in the Zone section is greater than 1. + /// + /// @param received_data buffer holding DNS Update message to be parsed. + /// @param bytes_received the number of bytes in received_data + /// @param tsig_context A TSIG context that is to be used to verify the + /// message. If NULL TSIG verification will not be attempted. + void fromWire(const void* received_data, size_t bytes_received, + dns::TSIGContext* const tsig_context = NULL); + //@} + +private: + /// Maps the values of the @c UpdateMessageSection field to the + /// corresponding values in the @c isc::dns::Message class. This + /// mapping is required here because this class uses @c isc::dns::Message + /// class to do the actual processing of the DNS Update message. + /// + /// @param section An enum indicating the section for which the + /// corresponding enum value from @c isc::dns::Message will be returned. + /// + /// @return The enum value indicating the section in the DNS message + /// represented by the @c isc::dns::Message class. + static + dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section); + + /// @brief Checks received response message for correctness. + /// + /// This function verifies that the received response from a server is + /// correct. Currently this function checks the following: + /// - Opcode is 'DNS Update', + /// - QR flag is RESPONSE (flag bit is set), + /// - Zone section comprises at most one record. + /// + /// The function will throw exception if any of the conditions above are + /// not met. + /// + /// @throw isc::d2::NotUpdateMessage if invalid Opcode. + /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE + /// @throw isc::d2::InvalidZone section, if Zone section comprises more + /// than one record. + void validateResponse() const; + + /// @brief An object representing DNS Message which is used by the + /// implementation of @c D2UpdateMessage to perform low level. + /// + /// Declaration of this object pollutes the header with the details + /// of @c D2UpdateMessage implementation. It might be cleaner to use + /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However, + /// it would bring additional complications to the implementation + /// while the benefit would low - this header is not a part of any + /// common library. Therefore, if implementation is changed, modification of + /// private members of this class in the header has low impact. + dns::Message message_; + + /// @brief Holds a pointer to the object, representing Zone in the DNS + /// Update. + D2ZonePtr zone_; + +}; + +} // namespace d2 +} // namespace isc + +#endif // D2_UPDATE_MESSAGE_H |