// 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 #include #include #include #include #include #include #include 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 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. /// /// Design choice: 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