summaryrefslogtreecommitdiffstats
path: root/src/lib/d2srv/d2_update_message.cc
blob: 1917d6e33959287626713aee8fea444e31e6bd06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// 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/.

#include <config.h>

#include <d2srv/d2_update_message.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/opcode.h>
#include <dns/question.h>

namespace isc {
namespace d2 {

using namespace isc::dns;

D2UpdateMessage::D2UpdateMessage(const Direction direction)
    : message_(direction == INBOUND ?
               dns::Message::PARSE : dns::Message::RENDER) {
    // If this object is to create an outgoing message, we have to
    // set the proper Opcode field and QR flag here.
    if (direction == OUTBOUND) {
        message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
        message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
        message_.setRcode(Rcode::NOERROR());
    }
}

D2UpdateMessage::QRFlag
D2UpdateMessage::getQRFlag() const {
    return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
            RESPONSE : REQUEST);
}

uint16_t
D2UpdateMessage::getId() const {
    return (message_.getQid());
}

void
D2UpdateMessage::setId(const uint16_t id) {
    message_.setQid(id);
}


const dns::Rcode&
D2UpdateMessage::getRcode() const {
    return (message_.getRcode());
}

void
D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
    message_.setRcode(rcode);
}

unsigned int
D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
    return (message_.getRRCount(ddnsToDnsSection(section)));
}

const dns::RRsetIterator
D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
    return (message_.beginSection(ddnsToDnsSection(section)));
}

const dns::RRsetIterator
D2UpdateMessage::endSection(const UpdateMsgSection section) const {
    return (message_.endSection(ddnsToDnsSection(section)));
}

void
D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
    // The Zone data is kept in the underlying Question class. If there
    // is a record stored there already, we need to remove it, because
    // we may have at most one Zone record in the DNS Update message.
    if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
        message_.clearSection(dns::Message::SECTION_QUESTION);
    }
    // Add the new record...
    Question question(zone, rrclass, RRType::SOA());
    message_.addQuestion(question);
    // ... and update the local class member holding the D2Zone object.
    zone_.reset(new D2Zone(question.getName(), question.getClass()));
}

D2ZonePtr
D2UpdateMessage::getZone() const {
    return (zone_);
}

void
D2UpdateMessage::addRRset(const UpdateMsgSection section,
                          const dns::RRsetPtr& rrset) {
    if (section == SECTION_ZONE) {
        isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
                  " of the DNS Update message, use setZone instead");
    }
    message_.addRRset(ddnsToDnsSection(section), rrset);
}

void
D2UpdateMessage::toWire(AbstractMessageRenderer& renderer,
                        TSIGContext* const tsig_context) {
    // We are preparing the wire format of the message, meaning
    // that this message will be sent as a request to the DNS.
    // Therefore, we expect that this message is a REQUEST.
    if (getQRFlag() != REQUEST) {
        isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
                  " DNS Update message");
    }
    // According to RFC2136, the ZONE section may contain exactly one
    // record.
    if (getRRCount(SECTION_ZONE) != 1) {
        isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
                  " must comprise exactly one record (RFC2136, section 2.3)");
    }
    message_.toWire(renderer, tsig_context);
}

void
D2UpdateMessage::fromWire(const void* received_data, size_t bytes_received,
                          dns::TSIGContext* const tsig_context) {
    // First, use the underlying dns::Message implementation to get the
    // contents of the DNS response message. Note that it may or may
    // not be the message that we are interested in, but needs to be
    // parsed so as we can check its ID, Opcode etc.
    isc::util::InputBuffer received_data_buffer(received_data, bytes_received);
    message_.fromWire(received_data_buffer);

    // If tsig_context is not NULL, then we need to verify the message.
    if (tsig_context) {
        TSIGError error = tsig_context->verify(message_.getTSIGRecord(),
                                               received_data, bytes_received);
        if (error != TSIGError::NOERROR()) {
            isc_throw(TSIGVerifyError, "TSIG verification failed: "
                      << error.toText());
        }
    }

    // This class exposes the getZone() function. This function will return
    // pointer to the D2Zone object if non-empty Zone section exists in the
    // received message. It will return NULL pointer if it doesn't exist.
    // The pointer is held in the D2UpdateMessage class member. We need to
    // update this pointer every time we parse the message.
    if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
        // There is a Zone section in the received message. Replace
        // Zone pointer with the new value.
        QuestionPtr question = *message_.beginQuestion();
        // If the Zone counter is greater than 0 (which we have checked)
        // there must be a valid Question pointer stored in the message_
        // object. If there isn't, it is a programming error.
        if (!question) {
            isc_throw(isc::Unexpected, "question is null?!");
        }
        zone_.reset(new D2Zone(question->getName(), question->getClass()));

    } else {
        // Zone section doesn't hold any pointers, so set the pointer to NULL.
        zone_.reset();

    }
    // Check that the content of the received message is sane.
    // One of the basic checks to do is to verify that we have
    // received the DNS update message. If not, it can be dropped
    // or an error message can be printed. Other than that, we
    // will check that there is at most one Zone record and QR flag
    // is set.
    validateResponse();
}

dns::Message::Section
D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
    /// The following switch maps the enumerator values from the
    /// DNS Update message to the corresponding enumerator values
    /// representing fields of the DNS message.
    switch(section) {
    case SECTION_ZONE :
        return (dns::Message::SECTION_QUESTION);

    case SECTION_PREREQUISITE:
        return (dns::Message::SECTION_ANSWER);

    case SECTION_UPDATE:
        return (dns::Message::SECTION_AUTHORITY);

    case SECTION_ADDITIONAL:
        return (dns::Message::SECTION_ADDITIONAL);

    default:
        ;
    }
    isc_throw(dns::InvalidMessageSection,
              "unknown message section " << section);
}

void
D2UpdateMessage::validateResponse() const {
    // Verify that we are dealing with the DNS Update message. According to
    // RFC 2136, section 3.8 server will copy the Opcode from the query.
    // If we are dealing with a different type of message, we may simply
    // stop further processing, because it is likely that the message was
    // directed to someone else.
    if (message_.getOpcode() != Opcode::UPDATE()) {
        isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
                  << " received message code is "
                  << message_.getOpcode().getCode());
    }
    // Received message should have QR flag set, which indicates that it is
    // a RESPONSE.
    if (getQRFlag() == REQUEST) {
        isc_throw(InvalidQRFlag, "received message should have QR flag set,"
                  " to indicate that it is a RESPONSE message; the QR"
                  << " flag in received message is unset");
    }
    // DNS server may copy a Zone record from the query message. Since query
    // must comprise exactly one Zone record (RFC 2136, section 2.3), the
    // response message may contain 1 record at most. It may also contain no
    // records if a server chooses not to copy Zone section.
    if (getRRCount(SECTION_ZONE) > 1) {
        isc_throw(InvalidZoneSection, "received message contains "
                  << getRRCount(SECTION_ZONE) << " Zone records,"
                  << " it should contain at most 1 record");
    }
}

} // namespace d2
} // namespace isc