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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
|
// Copyright (C) 2023 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 <dhcp/option4_dnr.h>
#include <dns/labelsequence.h>
#include <util/strutil.h>
using namespace isc::asiolink;
using namespace isc::util;
namespace isc {
namespace dhcp {
Option4Dnr::Option4Dnr(OptionBufferConstIter begin, OptionBufferConstIter end)
: Option(V4, DHO_V4_DNR) {
unpack(begin, end);
}
OptionPtr
Option4Dnr::clone() const {
return (cloneInternal<Option4Dnr>());
}
void
Option4Dnr::pack(OutputBuffer& buf, bool check) const {
packHeader(buf, check);
for (const DnrInstance& dnr_instance : dnr_instances_) {
buf.writeUint16(dnr_instance.getDnrInstanceDataLength());
buf.writeUint16(dnr_instance.getServicePriority());
buf.writeUint8(dnr_instance.getAdnLength());
dnr_instance.packAdn(buf);
if (dnr_instance.isAdnOnlyMode()) {
continue;
}
buf.writeUint8(dnr_instance.getAddrLength());
dnr_instance.packAddresses(buf);
dnr_instance.packSvcParams(buf);
}
}
void
Option4Dnr::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
setData(begin, end);
while (begin != end) {
DnrInstance dnr_instance(V4);
if (std::distance(begin, end) < dnr_instance.getMinimalLength()) {
isc_throw(OutOfRange, dnr_instance.getLogPrefix()
<< "DNR instance data truncated to size "
<< std::distance(begin, end));
}
// Unpack DnrInstanceDataLength.
dnr_instance.unpackDnrInstanceDataLength(begin, end);
const OptionBufferConstIter dnr_instance_end = begin +
dnr_instance.getDnrInstanceDataLength();
// Unpack Service priority.
dnr_instance.unpackServicePriority(begin);
// Unpack ADN len + ADN.
dnr_instance.unpackAdn(begin, dnr_instance_end);
if (begin == dnr_instance_end) {
// ADN only mode, other fields are not included.
addDnrInstance(dnr_instance);
continue;
}
dnr_instance.setAdnOnlyMode(false);
// Unpack Addr Len + IPv4 Address(es).
dnr_instance.unpackAddresses(begin, dnr_instance_end);
// SvcParams (variable length) field is last.
dnr_instance.unpackSvcParams(begin, dnr_instance_end);
addDnrInstance(dnr_instance);
}
}
std::string
Option4Dnr::toText(int indent) const {
std::ostringstream stream;
std::string in(indent, ' '); // base indentation
stream << in << "type=" << type_ << "(V4_DNR), "
<< "len=" << (len() - getHeaderLen());
int i = 0;
for (const DnrInstance& dnr_instance : dnr_instances_) {
stream << ", DNR Instance " << ++i
<< "(Instance len=" << dnr_instance.getDnrInstanceDataLength() << ", "
<< dnr_instance.getDnrInstanceAsText() << ")";
}
return (stream.str());
}
uint16_t
Option4Dnr::len() const {
uint16_t len = OPTION4_HDR_LEN;
for (const DnrInstance& dnr_instance : dnr_instances_) {
len += dnr_instance.getDnrInstanceDataLength() +
dnr_instance.getDnrInstanceDataLengthSize();
}
return (len);
}
void
Option4Dnr::addDnrInstance(DnrInstance& dnr_instance) {
dnr_instances_.push_back(dnr_instance);
}
const std::unordered_set<std::string> DnrInstance::FORBIDDEN_SVC_PARAMS = {"ipv4hint", "ipv6hint"};
DnrInstance::DnrInstance(Option::Universe universe)
: universe_(universe), dnr_instance_data_length_(0), service_priority_(0),
adn_length_(0), addr_length_(0), svc_params_length_(0),
adn_only_mode_(true), dnr_instance_data_length_size_(0),
adn_length_size_(0), addr_length_size_(0), minimal_length_(0) {
initMembers();
}
DnrInstance::DnrInstance(Option::Universe universe,
const uint16_t service_priority,
const std::string& adn,
const DnrInstance::AddressContainer& ip_addresses,
const std::string& svc_params)
: universe_(universe), dnr_instance_data_length_(0),
service_priority_(service_priority), adn_length_(0),
addr_length_(0), ip_addresses_(ip_addresses), svc_params_length_(0),
adn_only_mode_(true), svc_params_(svc_params),
dnr_instance_data_length_size_(0), adn_length_size_(0),
addr_length_size_(0), minimal_length_(0) {
initMembers();
setAdn(adn);
checkFields();
}
DnrInstance::DnrInstance(Option::Universe universe,
const uint16_t service_priority,
const std::string& adn)
: universe_(universe), dnr_instance_data_length_(0),
service_priority_(service_priority), adn_length_(0),
addr_length_(0), svc_params_length_(0), adn_only_mode_(true),
dnr_instance_data_length_size_(0), adn_length_size_(0),
addr_length_size_(0), minimal_length_(0) {
initMembers();
setAdn(adn);
}
void
DnrInstance::packAdn(OutputBuffer& buf) const {
if (!adn_) {
// This should not happen since Encrypted DNS options are designed
// to always include an authentication domain name.
isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
<< "Mandatory Authentication Domain Name fully "
"qualified domain-name is missing");
}
isc::dns::LabelSequence label_sequence(*adn_);
if (label_sequence.getDataLength() > 0) {
size_t data_length = 0;
const uint8_t* data = label_sequence.getData(&data_length);
buf.writeData(data, data_length);
}
}
void
DnrInstance::packAddresses(OutputBuffer& buf) const {
AddressContainer::const_iterator address = ip_addresses_.begin();
while (address != ip_addresses_.end()) {
buf.writeUint32(address->toUint32());
++address;
}
}
void
DnrInstance::packSvcParams(OutputBuffer& buf) const {
if (svc_params_length_ > 0) {
buf.writeData(&(*svc_params_.begin()), svc_params_length_);
}
}
std::string
DnrInstance::getAdnAsText() const {
return (adn_) ? (adn_->toText()) : ("");
}
void
DnrInstance::setAdn(const std::string& adn) {
std::string trimmed_adn = str::trim(adn);
if (trimmed_adn.empty()) {
isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
<< "Mandatory Authentication Domain Name fully "
"qualified domain-name must not be empty");
}
try {
adn_.reset(new isc::dns::Name(trimmed_adn, true));
} catch (const Exception& ex) {
isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
<< "Failed to parse "
"fully qualified domain-name from string - "
<< ex.what());
}
size_t adn_len = 0;
isc::dns::LabelSequence label_sequence(*adn_);
label_sequence.getData(&adn_len);
if (adn_len > std::numeric_limits<uint16_t>::max()) {
isc_throw(InvalidOptionDnrDomainName, getLogPrefix() << "Given ADN FQDN length " << adn_len
<< " is bigger than uint_16 MAX");
}
adn_length_ = adn_len;
if (universe_ == Option::V4) {
dnr_instance_data_length_ = dnrInstanceLen();
}
}
void
DnrInstance::unpackAdn(OptionBufferConstIter& begin, OptionBufferConstIter end) {
OpaqueDataTuple::LengthFieldType lft = OptionDataTypeUtil::getTupleLenFieldType(universe_);
OpaqueDataTuple adn_tuple(lft);
try {
adn_tuple.unpack(begin, end);
} catch (const Exception& ex) {
isc_throw(BadValue, getLogPrefix() << "failed to unpack ADN data"
<< " - " << ex.what());
}
adn_length_ = adn_tuple.getLength();
// Encrypted DNS options are designed to always include an authentication domain name,
// so when there is no FQDN included, we shall throw an exception.
if (adn_length_ == 0) {
isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
<< "Mandatory Authentication Domain Name fully "
"qualified domain-name is missing");
}
InputBuffer name_buf(adn_tuple.getData().data(), adn_length_);
try {
adn_.reset(new isc::dns::Name(name_buf, true));
} catch (const Exception& ex) {
isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
<< "Failed to parse "
"fully qualified domain-name from wire format "
"- " << ex.what());
}
begin += adn_length_ + getAdnLengthSize();
}
void
DnrInstance::checkSvcParams(bool from_wire_data) {
std::string svc_params = str::trim(svc_params_);
if (svc_params.empty()) {
isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
<< "Provided Svc Params field is empty");
}
if (!from_wire_data) {
// If Service Params field was not parsed from on-wire data,
// but actually was provided with ctor, let's calculate svc_params_length_.
auto svc_params_len = svc_params.length();
if (svc_params_len > std::numeric_limits<uint16_t>::max()) {
isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
<< "Given Svc Params length " << svc_params_len
<< " is bigger than uint_16 MAX");
}
svc_params_length_ = svc_params_len;
// If Service Params field was not parsed from on-wire data,
// but actually was provided with ctor, let's replace it with trimmed value.
svc_params_ = svc_params;
}
// SvcParams are a whitespace-separated list, with each SvcParam
// consisting of a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey.
// SvcParams in presentation format MAY appear in any order, but keys MUST NOT be repeated.
// Let's put all elements of a whitespace-separated list into a vector.
std::vector<std::string> tokens = str::tokens(svc_params, " ");
// Set of keys used to check if a key is not repeated.
std::unordered_set<std::string> keys;
// String sanitizer is used to check keys syntax.
str::StringSanitizerPtr sanitizer;
// SvcParamKeys are lower-case alphanumeric strings. Key names
// contain 1-63 characters from the ranges "a"-"z", "0"-"9", and "-".
std::string regex = "[^a-z0-9-]";
sanitizer.reset(new str::StringSanitizer(regex, ""));
// Now let's check each SvcParamKey=SvcParamValue pair.
for (const std::string& token : tokens) {
std::vector<std::string> key_val = str::tokens(token, "=");
if (key_val.size() > 2) {
isc_throw(InvalidOptionDnrSvcParams,
getLogPrefix() << "Wrong Svc Params syntax - more than one "
"equals sign found in SvcParamKey=SvcParamValue pair");
}
// SvcParam Key related checks come below.
std::string key = key_val[0];
if (key.length() > 63) {
isc_throw(InvalidOptionDnrSvcParams,
getLogPrefix() << "Wrong Svc Params syntax - key had more than 63 "
"characters - " << key);
}
if (FORBIDDEN_SVC_PARAMS.find(key) != FORBIDDEN_SVC_PARAMS.end()) {
isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key "
<< key << " must not be used");
}
auto insert_res = keys.insert(key);
if (!insert_res.second) {
isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key "
<< key << " was duplicated");
}
std::string sanitized_key = sanitizer->scrub(key);
if (sanitized_key.size() < key.size()) {
isc_throw(InvalidOptionDnrSvcParams,
getLogPrefix()
<< "Wrong Svc Params syntax - invalid character used in key - " << key);
}
}
}
void
DnrInstance::checkFields() {
if (svc_params_.empty() && ip_addresses_.empty()) {
// ADN only mode, nothing more to do.
return;
}
if (!svc_params_.empty() && ip_addresses_.empty()) {
// As per draft-ietf-add-dnr 3.1.8:
// If additional data is supplied (i.e. not ADN only mode),
// the option includes at least one valid IP address.
isc_throw(OutOfRange, getLogPrefix() << "No IP address given. Since this is not ADN only "
"mode, at least one valid IP address must "
"be included");
}
if (!svc_params_.empty()) {
checkSvcParams(false);
}
adn_only_mode_ = false;
const uint8_t addr_field_len = (universe_ == Option::V4) ? V4ADDRESS_LEN : V6ADDRESS_LEN;
const uint16_t max_addr_len = (universe_ == Option::V4) ? std::numeric_limits<uint8_t>::max() :
std::numeric_limits<uint16_t>::max();
auto addr_len = ip_addresses_.size() * addr_field_len;
if (addr_len > max_addr_len) {
isc_throw(OutOfRange, getLogPrefix() << "Given IP addresses length " << addr_len
<< " is bigger than MAX " << max_addr_len);
}
addr_length_ = addr_len;
if (universe_ == Option::V4) {
dnr_instance_data_length_ = dnrInstanceLen();
}
}
std::string
DnrInstance::getDnrInstanceAsText() const {
std::ostringstream stream;
stream << "service_priority=" << service_priority_ << ", adn_length=" << adn_length_ << ", "
<< "adn='" << getAdnAsText() << "'";
if (!adn_only_mode_) {
stream << ", addr_length=" << addr_length_ << ", address(es):";
for (const auto& address : ip_addresses_) {
stream << " " << address.toText();
}
if (svc_params_length_ > 0) {
stream << ", svc_params='" + svc_params_ + "'";
}
}
return (stream.str());
}
uint16_t
DnrInstance::dnrInstanceLen() const {
uint16_t len = SERVICE_PRIORITY_SIZE + adn_length_ + getAdnLengthSize();
if (!adn_only_mode_) {
len += addr_length_ + getAddrLengthSize() + svc_params_length_;
}
return (len);
}
void
DnrInstance::addIpAddress(const IOAddress& ip_address) {
ip_addresses_.push_back(ip_address);
}
void
DnrInstance::unpackDnrInstanceDataLength(OptionBufferConstIter& begin, OptionBufferConstIter end) {
dnr_instance_data_length_ = readUint16(&*begin, getDnrInstanceDataLengthSize());
begin += getDnrInstanceDataLengthSize();
if (std::distance(begin, end) < dnr_instance_data_length_) {
isc_throw(OutOfRange, getLogPrefix()
<< "DNR instance data truncated to size "
<< std::distance(begin, end) << " but it was supposed to be "
<< dnr_instance_data_length_);
}
}
void
DnrInstance::unpackServicePriority(OptionBufferConstIter& begin) {
service_priority_ = readUint16(&*begin, SERVICE_PRIORITY_SIZE);
begin += SERVICE_PRIORITY_SIZE;
}
void
DnrInstance::unpackAddresses(OptionBufferConstIter& begin, const OptionBufferConstIter end) {
OpaqueDataTuple addr_tuple(OpaqueDataTuple::LENGTH_1_BYTE);
try {
addr_tuple.unpack(begin, end);
} catch (const Exception& ex) {
isc_throw(BadValue, getLogPrefix() << "failed to unpack IP Addresses data"
<< " - " << ex.what());
}
addr_length_ = addr_tuple.getLength();
// It MUST be a multiple of 4.
if ((addr_length_ % V4ADDRESS_LEN) != 0) {
isc_throw(OutOfRange, getLogPrefix()
<< "Addr Len=" << addr_length_ << " is not divisible by 4");
}
// As per draft-ietf-add-dnr 3.1.8:
// If additional data is supplied (i.e. not ADN only mode),
// the option includes at least one valid IP address.
if (addr_length_ == 0) {
isc_throw(OutOfRange, getLogPrefix()
<< "Addr Len=" << addr_length_
<< " but it must contain at least one valid IP address");
}
begin += getAddrLengthSize();
OptionBufferConstIter addr_begin = begin;
OptionBufferConstIter addr_end = addr_begin + addr_length_;
while (addr_begin != addr_end) {
const uint8_t* ptr = &(*addr_begin);
addIpAddress(IOAddress(readUint32(ptr, std::distance(addr_begin, addr_end))));
addr_begin += V4ADDRESS_LEN;
begin += V4ADDRESS_LEN;
}
}
void
DnrInstance::unpackSvcParams(OptionBufferConstIter& begin, OptionBufferConstIter end) {
svc_params_length_ = std::distance(begin, end);
if (svc_params_length_ > 0) {
svc_params_.assign(begin, end);
checkSvcParams();
begin += svc_params_length_;
}
}
void
DnrInstance::initMembers() {
dnr_instance_data_length_size_ = (universe_ == Option::V6) ? 0 : 2;
adn_length_size_ = (universe_ == Option::V6) ? 2 : 1;
addr_length_size_ = (universe_ == Option::V6) ? 2 : 1;
minimal_length_ = dnr_instance_data_length_size_ + SERVICE_PRIORITY_SIZE + adn_length_size_;
log_prefix_ =
(universe_ == Option::V4) ?
("DHCPv4 Encrypted DNS Option (" + std::to_string(DHO_V4_DNR) + ") malformed: ") :
("DHCPv6 Encrypted DNS Option (" + std::to_string(D6O_V6_DNR) + ") malformed: ");
}
} // namespace dhcp
} // namespace isc
|