Kea 2.7.5
option4_dnr.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <dhcp/option4_dnr.h>
10#include <dns/labelsequence.h>
11#include <util/io.h>
12#include <util/str.h>
13
14using namespace isc::asiolink;
15using namespace isc::util;
16
17namespace isc {
18namespace dhcp {
19
22 bool convenient_notation)
23 : Option(V4, DHO_V4_DNR), convenient_notation_(convenient_notation) {
24 unpack(begin, end);
25}
26
29 return (cloneInternal<Option4Dnr>());
30}
31
32void
33Option4Dnr::pack(OutputBuffer& buf, bool check) const {
34 packHeader(buf, check);
35 for (const DnrInstance& dnr_instance : dnr_instances_) {
36 buf.writeUint16(dnr_instance.getDnrInstanceDataLength());
37 buf.writeUint16(dnr_instance.getServicePriority());
38 buf.writeUint8(dnr_instance.getAdnLength());
39 dnr_instance.packAdn(buf);
40 if (dnr_instance.isAdnOnlyMode()) {
41 continue;
42 }
43
44 buf.writeUint8(dnr_instance.getAddrLength());
45 dnr_instance.packAddresses(buf);
46 dnr_instance.packSvcParams(buf);
47 }
48}
49
50void
52 if (convenient_notation_) {
53 // parse convenient notation
54 std::string config_txt = std::string(begin, end);
55 parseConfigData(config_txt);
56 } else {
57 setData(begin, end);
58 while (begin != end) {
59 DnrInstance dnr_instance(V4);
60 if (std::distance(begin, end) < dnr_instance.getMinimalLength()) {
61 isc_throw(OutOfRange, dnr_instance.getLogPrefix()
62 << "DNR instance data truncated to size "
63 << std::distance(begin, end));
64 }
65
66 // Unpack DnrInstanceDataLength.
67 dnr_instance.unpackDnrInstanceDataLength(begin, end);
68
69 const OptionBufferConstIter dnr_instance_end = begin +
70 dnr_instance.getDnrInstanceDataLength();
71
72 // Unpack Service priority.
73 dnr_instance.unpackServicePriority(begin);
74
75 // Unpack ADN len + ADN.
76 dnr_instance.unpackAdn(begin, dnr_instance_end);
77
78 if (begin == dnr_instance_end) {
79 // ADN only mode, other fields are not included.
80 addDnrInstance(dnr_instance);
81 continue;
82 }
83
84 dnr_instance.setAdnOnlyMode(false);
85
86 // Unpack Addr Len + IPv4 Address(es).
87 dnr_instance.unpackAddresses(begin, dnr_instance_end);
88
89 // SvcParams (variable length) field is last.
90 dnr_instance.unpackSvcParams(begin, dnr_instance_end);
91
92 addDnrInstance(dnr_instance);
93 }
94 }
95}
96
97std::string
98Option4Dnr::toText(int indent) const {
99 std::ostringstream stream;
100 std::string in(indent, ' '); // base indentation
101 stream << in << "type=" << type_ << "(V4_DNR), "
102 << "len=" << (len() - getHeaderLen());
103 int i = 0;
104 for (const DnrInstance& dnr_instance : dnr_instances_) {
105 stream << ", DNR Instance " << ++i
106 << "(Instance len=" << dnr_instance.getDnrInstanceDataLength() << ", "
107 << dnr_instance.getDnrInstanceAsText() << ")";
108 }
109
110 return (stream.str());
111}
112
113uint16_t
115 uint16_t len = OPTION4_HDR_LEN;
116 for (const DnrInstance& dnr_instance : dnr_instances_) {
117 len += dnr_instance.getDnrInstanceDataLength() +
118 dnr_instance.getDnrInstanceDataLengthSize();
119 }
120
121 return (len);
122}
123
124void
126 dnr_instances_.push_back(dnr_instance);
127}
128
129void
130Option4Dnr::parseConfigData(const std::string& config_txt) {
131 // This parses convenient option config notation.
132 // The config to be parsed may contain escaped characters like "\\," or "\\|".
133 // Example configs are below (first contains two DNR instances in one option with recommended
134 // resolvers' IP addresses, and SvcParams - DNR instances are separated with pipe "|" char;
135 // second is an example of ADN-only mode;
136 // third is like the first example, but for single DNR instance):
137 //
138 // "name": "v4-dnr",
139 // "data": "10, dot1.example.org., 10.0.2.3 10.3.4.5, alpn=dot\\,doq | 20, dot2.example.org., 10.0.2.3 10.3.4.5, alpn=dot"
140 //
141 // "name": "v4-dnr",
142 // "data": "200, resolver.example."
143 //
144 // "name": "v4-dnr",
145 // "data": "100, dot1.example.org., 10.0.3.4 10.1.5.6, alpn=dot\\,doq\\,h2\\,h3 port=8530 dohpath=/q{?dns}"
146
147 // Get Dnr Instance tokens using pipe separator with double backslash escaping enabled.
148 std::vector<std::string> tokens = str::tokens(config_txt, std::string("|"), true);
149
150 for (auto const& txt_dnr_instance : tokens) {
151 DnrInstance dnr_instance(V4);
152 dnr_instance.parseDnrInstanceConfigData(txt_dnr_instance);
153 dnr_instance.setDnrInstanceDataLength();
154 addDnrInstance(dnr_instance);
155 }
156}
157
158const std::unordered_set<std::string> DnrInstance::FORBIDDEN_SVC_PARAMS = {"ipv4hint", "ipv6hint"};
159
161 boost::assign::list_of<DnrInstance::SvcParamsMap::relation>
162 ("mandatory", 0) // RFC 9460, Section 14.3.2, not used in DNR
163 ("alpn", 1) // RFC 9460, Section 14.3.2, mandatory in DNR
164 ("no-default-alpn", 2) // RFC 9460, Section 14.3.2, not used in DNR
165 ("port", 3) // RFC 9460, Section 14.3.2, optional in DNR
166 ("ipv4hint", 4) // RFC 9460, Section 14.3.2, forbidden in DNR
167 ("ech", 5) // RFC 9460, Section 14.3.2, not used in DNR
168 ("ipv6hint", 6) // RFC 9460, Section 14.3.2, forbidden in DNR
169 ("dohpath", 7) // RFC 9461, optional in DNR
170 ("ohttp", 8) // https://datatracker.ietf.org/doc/draft-ietf-ohai-svcb-config,
171 // not used in DNR
172 ;
173
174const std::set<uint8_t> DnrInstance::SUPPORTED_SVC_PARAMS = {1, 3, 7};
175
176const std::unordered_set<std::string> DnrInstance::ALPN_IDS = {
177 "http/0.9", // HTTP/0.9
178 "http/1.0", // HTTP/1.0
179 "http/1.1", // HTTP/1.1
180 "spdy/1", // SPDY/1
181 "spdy/2", // SPDY/2
182 "spdy/3", // SPDY/3
183 "stun.turn", // Traversal Using Relays around NAT (TURN)
184 "stun.nat-discovery", // NAT discovery using Session Traversal Utilities for NAT (STUN)
185 "h2", // HTTP/2 over TLS
186 "h2c", // HTTP/2 over TCP
187 "webrtc", // WebRTC Media and Data
188 "c-webrtc", // Confidential WebRTC Media and Data
189 "ftp", // FTP
190 "imap", // IMAP
191 "pop3", // POP3
192 "managesieve", // ManageSieve
193 "coap", // CoAP
194 "xmpp-client", // XMPP jabber:client namespace
195 "xmpp-server", // XMPP jabber:server namespace
196 "acme-tls/1", // acme-tls/1
197 "mqtt", // OASIS Message Queuing Telemetry Transport (MQTT)
198 "dot", // DNS-over-TLS
199 "ntske/1", // Network Time Security Key Establishment, version 1
200 "sunrpc", // SunRPC
201 "h3", // HTTP/3
202 "smb", // SMB2
203 "irc", // IRC
204 "nntp", // NNTP (reading)
205 "nnsp", // NNTP (transit)
206 "doq", // DoQ
207 "sip/2", // SIP
208 "tds/8.0", // TDS/8.0
209 "dicom" // DICOM
210};
211
213 : universe_(universe), dnr_instance_data_length_(0), service_priority_(0), adn_length_(0),
214 addr_length_(0), svc_params_length_(0), adn_only_mode_(true), alpn_http_(false),
215 dnr_instance_data_length_size_(0), adn_length_size_(0), addr_length_size_(0),
216 minimal_length_(0) {
217 initMembers();
218}
219
220void
222 if (!adn_) {
223 // This should not happen since Encrypted DNS options are designed
224 // to always include an authentication domain name.
226 << "Mandatory Authentication Domain Name fully "
227 "qualified domain-name is missing");
228 }
229
230 isc::dns::LabelSequence label_sequence(*adn_);
231 if (label_sequence.getDataLength() > 0) {
232 size_t data_length = 0;
233 const uint8_t* data = label_sequence.getData(&data_length);
234 buf.writeData(data, data_length);
235 }
236}
237
238void
240 AddressContainer::const_iterator address = ip_addresses_.begin();
241 while (address != ip_addresses_.end()) {
242 buf.writeUint32(address->toUint32());
243 ++address;
244 }
245}
246
247void
249 if (svc_params_length_ > 0 && !svc_params_buf_.empty()) {
250 buf.writeData(svc_params_buf_.data(), svc_params_length_);
251 }
252}
253
254std::string
256 return (adn_) ? (adn_->toText()) : ("");
257}
258
259void
262 OpaqueDataTuple adn_tuple(lft);
263 try {
264 adn_tuple.unpack(begin, end);
265 } catch (const Exception& ex) {
266 isc_throw(BadValue, getLogPrefix() << "failed to unpack ADN data"
267 << " - " << ex.what());
268 }
269
270 adn_length_ = adn_tuple.getLength();
271
272 // Encrypted DNS options are designed to always include an authentication domain name,
273 // so when there is no FQDN included, we shall throw an exception.
274 if (adn_length_ == 0) {
276 << "Mandatory Authentication Domain Name fully "
277 "qualified domain-name is missing");
278 }
279
280 InputBuffer name_buf(adn_tuple.getData().data(), adn_length_);
281 try {
282 adn_.reset(new isc::dns::Name(name_buf, true));
283 } catch (const Exception& ex) {
285 << "Failed to parse fully qualified domain-name "
286 << "from wire format - " << ex.what());
287 }
288
289 begin += adn_length_ + getAdnLengthSize();
290}
291
292std::string
293DnrInstance::svcParamValAsText(const std::pair<uint16_t, OpaqueDataTuple>& svc_param) const {
294 OptionBufferConstIter alpn_begin;
295 OptionBufferConstIter alpn_end;
296 std::ostringstream stream;
298 bool first = true;
299 std::string ret;
300
301 switch (svc_param.first) {
302 case 1:
303 // alpn
304 // read all protocols and concatenate them with comma
305 alpn_begin = svc_param.second.getData().begin();
306 alpn_end = svc_param.second.getData().end();
307 while (alpn_begin != alpn_end) {
308 try {
309 alpn_id_tuple.unpack(alpn_begin, alpn_end);
310 } catch (const Exception& e) {
312 << "Exception happened when tried to parse ALPN IDs"
313 << ". Error: " << e.what());
314 }
315
316 if (first) {
317 first = false;
318 } else {
319 stream << ",";
320 }
321
322 stream << alpn_id_tuple.getText();
323 alpn_begin += alpn_id_tuple.getTotalLength();
324 }
325
326 ret = stream.str();
327 break;
328 case 3:
329 // port
330 // read uint16 from data buffer and return as string
331 ret = std::to_string(
332 readUint16(svc_param.second.getData().data(), svc_param.second.getLength()));
333 break;
334 case 7:
335 // dohpath
336 // conversion not needed, let's return data as string
337 ret = svc_param.second.getText();
338 break;
339 }
340
341 return (ret);
342}
343
344std::string
346 std::ostringstream stream;
347 stream << "service_priority=" << service_priority_ << ", adn_length=" << adn_length_ << ", "
348 << "adn='" << getAdnAsText() << "'";
349 if (!adn_only_mode_) {
350 stream << ", addr_length=" << addr_length_ << ", address(es):";
351 for (auto const& address : ip_addresses_) {
352 stream << " " << address.toText();
353 }
354
355 if (svc_params_length_ > 0) {
356 stream << ", svc_params='";
357 bool first = true;
358 for (auto const& it : svc_params_map_) {
359 auto const& k = SVC_PARAMS.right.at(it.first);
360 if (first) {
361 first = false;
362 } else {
363 stream << " ";
364 }
365
366 stream << k << "=" << svcParamValAsText(it);
367 }
368
369 stream << "'";
370 }
371 }
372
373 return (stream.str());
374}
375
376uint16_t
379 if (!adn_only_mode_) {
381 }
382
383 return (len);
384}
385
386void
388 ip_addresses_.push_back(ip_address);
389}
390
391void
395 if (std::distance(begin, end) < dnr_instance_data_length_) {
397 << "DNR instance data truncated to size "
398 << std::distance(begin, end) << " but it was supposed to be "
400 }
401}
402
403void
408
409void
412 try {
413 addr_tuple.unpack(begin, end);
414 } catch (const Exception& ex) {
415 isc_throw(BadValue, getLogPrefix() << "failed to unpack IP Addresses data"
416 << " - " << ex.what());
417 }
418
419 addr_length_ = addr_tuple.getLength();
420 // It MUST be a multiple of 4.
421 if ((addr_length_ % V4ADDRESS_LEN) != 0) {
423 << "Addr Len=" << addr_length_ << " is not divisible by 4");
424 }
425
426 // As per RFC9463 Section 3.1.8:
427 // If additional data is supplied (i.e. not ADN only mode),
428 // the option includes at least one valid IP address.
429 if (addr_length_ == 0) {
431 << "Addr Len=" << addr_length_
432 << " but it must contain at least one valid IP address");
433 }
434
435 begin += getAddrLengthSize();
436 OptionBufferConstIter addr_begin = begin;
437 OptionBufferConstIter addr_end = addr_begin + addr_length_;
438
439 while (addr_begin != addr_end) {
440 const uint8_t* ptr = &(*addr_begin);
441 addIpAddress(IOAddress(readUint32(ptr, std::distance(addr_begin, addr_end))));
442 addr_begin += V4ADDRESS_LEN;
443 begin += V4ADDRESS_LEN;
444 }
445}
446
447void
449 svc_params_length_ = std::distance(begin, end);
450 if (svc_params_length_ > 0) {
451 svc_params_buf_.assign(begin, end);
452
453 // used to check correct order of SvcParams
454 int prev_svc_param_key = -1;
455
456 // When the list of SvcParams is non-empty, it contains a series of
457 // SvcParamKey=SvcParamValue pairs, represented as:
458 // - a 2-octet field containing the SvcParamKey as an integer in network byte order.
459 // - a 2-octet field containing the length of the SvcParamValue as an integer
460 // between 0 and 65535 in network byte order. (uint16)
461 // - an octet string of this length whose contents are the SvcParamValue in a format
462 // determined by the SvcParamKey.
463 while (begin != end) {
464 // Minimum SvcParams len shall be 4:
465 // 2 octets SvcParamKey + 2 octets SvcParamValue Len
466 if (std::distance(begin, end) < 4) {
467 isc_throw(OutOfRange, getLogPrefix() << "DNR SvcParams data truncated to size "
468 << std::distance(begin, end));
469 }
470
471 uint16_t num_svc_param_key = readUint16(&*begin, 2);
472 begin += 2;
473
474 // Check if SvcParamKey is known in
475 // https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
476 auto it = SVC_PARAMS.right.find(num_svc_param_key);
477 if (it == SVC_PARAMS.right.end()) {
479 getLogPrefix() << "Wrong Svc Params syntax - key " << num_svc_param_key
480 << " not found in SvcParamKeys registry");
481 }
482
483 std::string svc_param_key = it->second;
484
485 // As per RFC9463 Section 3.1.8:
486 // The service parameters do not include "ipv4hint" or "ipv6hint" parameters.
487 if (FORBIDDEN_SVC_PARAMS.find(svc_param_key) != FORBIDDEN_SVC_PARAMS.end()) {
489 << "Wrong Svc Params syntax - key "
490 << svc_param_key << " must not be used");
491 }
492
493 // Check if SvcParamKey usage is supported by DNR DHCP option.
494 // Note that SUPPORTED_SVC_PARAMS set may expand in the future.
495 if (SUPPORTED_SVC_PARAMS.find(num_svc_param_key) == SUPPORTED_SVC_PARAMS.end()) {
497 getLogPrefix() << "Wrong Svc Params syntax - key " << svc_param_key
498 << " not supported in DNR option SvcParams");
499 }
500
501 // As per RFC9460 Section 2.2:
502 // SvcParamKeys SHALL appear in increasing numeric order. (...)
503 // There are no duplicate SvcParamKeys.
504 //
505 // We check for duplicates here.
506 if (svc_params_map_.find(num_svc_param_key) != svc_params_map_.end()) {
508 << "Wrong Svc Params syntax - key "
509 << svc_param_key << " is duplicated.");
510 }
511
512 // And we check correct order here.
513 if (num_svc_param_key <= prev_svc_param_key) {
515 getLogPrefix() << "Wrong Svc Params syntax - SvcParamKeys"
516 << " SHALL appear in increasing numeric order.");
517 }
518
519 prev_svc_param_key = num_svc_param_key;
520
521 // Let's try to unpack SvcParamVal into a tuple.
523 try {
524 svc_param_tuple.unpack(begin, end);
525 } catch (const Exception& e) {
528 << "Wrong Svc Params syntax - failed to unpack SvcParamVal for "
529 << "SvcParamKey " << svc_param_key << ". Error: " << e.what());
530 }
531
532 svc_params_map_.insert(std::make_pair(num_svc_param_key, svc_param_tuple));
533 begin += svc_param_tuple.getTotalLength();
534 }
535 }
536}
537
538void
539DnrInstance::initMembers() {
540 dnr_instance_data_length_size_ = (universe_ == Option::V6) ? 0 : 2;
541 adn_length_size_ = (universe_ == Option::V6) ? 2 : 1;
542 addr_length_size_ = (universe_ == Option::V6) ? 2 : 1;
543 minimal_length_ = dnr_instance_data_length_size_ + SERVICE_PRIORITY_SIZE + adn_length_size_;
544 log_prefix_ =
545 (universe_ == Option::V4) ?
546 ("DHCPv4 Encrypted DNS Option (" + std::to_string(DHO_V4_DNR) + ") malformed: ") :
547 ("DHCPv6 Encrypted DNS Option (" + std::to_string(D6O_V6_DNR) + ") malformed: ");
548}
549
550void
551DnrInstance::parseDnrInstanceConfigData(const std::string& config_txt) {
552 // This parses convenient option config notation.
553 // The config to be parsed may contain escaped characters like "\\," or "\\|".
554 // Example configs are below (first contains recommended resolvers' IP addresses, and SvcParams;
555 // second is an example of ADN-only mode;
556 // third is like the first example, but for DNRv4 - single DNR instance):
557 //
558 // "name": "v6-dnr",
559 // "data": "100, dot1.example.org., 2001:db8::1 2001:db8::2, alpn=dot\\,doq\\,h2\\,h3 port=8530 dohpath=/q{?dns}"
560 //
561 // "name": "v6-dnr",
562 // "data": "200, resolver.example."
563 //
564 // "name": "v4-dnr",
565 // "data": "100, dot1.example.org., 10.0.3.4 10.1.5.6, alpn=dot\\,doq\\,h2\\,h3 port=8530 dohpath=/q{?dns}"
566
567 // get tokens using comma separator with double backslash escaping enabled
568 std::vector<std::string> tokens = str::tokens(config_txt, std::string(","), true);
569
570 if (tokens.size() < 2) {
571 isc_throw(BadValue, getLogPrefix() << "Option config requires at least comma separated "
572 << "Service Priority and ADN");
573 }
574
575 if (tokens.size() > 4) {
576 isc_throw(BadValue, getLogPrefix() << "Option config supports maximum 4 comma separated "
577 << "fields: Service Priority, ADN, resolver IP "
578 << "address(es) and SvcParams");
579 }
580
581 // parse Service Priority
582 std::string txt_svc_priority = str::trim(tokens[0]);
583 try {
584 service_priority_ = boost::lexical_cast<uint16_t>(txt_svc_priority);
585 } catch (const std::exception& e) {
586 isc_throw(BadValue, getLogPrefix() << "Cannot parse uint_16 integer Service priority "
587 << "from given value: " << txt_svc_priority
588 << ". Error: " << e.what());
589 }
590
591 // parse ADN
592 std::string txt_adn = str::trim(tokens[1]);
593 try {
594 adn_.reset(new isc::dns::Name(txt_adn, true));
595 } catch (const std::exception& e) {
596 isc_throw(InvalidOptionDnrDomainName, getLogPrefix() << "Cannot parse ADN FQDN "
597 << "from given value: " << txt_adn
598 << ". Error: " << e.what());
599 }
600
601 adn_length_ = adn_->getLength();
602 if (adn_length_ == 0) {
604 << "Mandatory Authentication Domain Name fully "
605 << "qualified domain-name is missing");
606 }
607
608 if (tokens.size() > 2) {
609 setAdnOnlyMode(false);
610
611 // parse resolver IP address(es)
612 std::string txt_addresses = str::trim(tokens[2]);
613
614 parseIpAddresses(txt_addresses);
615 }
616
617 if (tokens.size() == 4) {
618 // parse Service Parameters
619 std::string txt_svc_params = str::trim(tokens[3]);
620
621 parseSvcParams(txt_svc_params);
622 }
623}
624
625void
626DnrInstance::parseIpAddresses(const std::string& txt_addresses) {
627 // determine v4/v6 universe
628 std::string ip_version = (universe_ == Option::V6) ? "IPv6" : "IPv4";
629 const size_t addr_len = (universe_ == Option::V6) ? V6ADDRESS_LEN : V4ADDRESS_LEN;
630
631 // IP addresses are separated with space
632 std::vector<std::string> addresses = str::tokens(txt_addresses, std::string(" "));
633 for (auto const& txt_addr : addresses) {
634 try {
635 const IOAddress address = IOAddress(str::trim(txt_addr));
636 if ((address.isV4() && universe_ == Option::V6) ||
637 (address.isV6() && universe_ == Option::V4)) {
638 isc_throw(BadValue, "Given address is not " << ip_version << " address.");
639 }
640
641 addIpAddress(address);
642 } catch (const Exception& e) {
644 << "Cannot parse " << ip_version << " address "
645 << "from given value: " << txt_addr << ". Error: " << e.what());
646 }
647 }
648
649 // As per RFC9463 section 3.1.8:
650 // (If ADN-only mode is not used)
651 // The option includes at least one valid IP address.
652 if (ip_addresses_.empty()) {
653 isc_throw(BadValue, getLogPrefix() << "Option config requires at least one valid IP "
654 << "address.");
655 }
656
657 addr_length_ = ip_addresses_.size() * addr_len;
658}
659
660void
661DnrInstance::parseSvcParams(const std::string& txt_svc_params) {
662 // SvcParamKey=SvcParamValue pairs are separated with space
663 std::vector<std::string> svc_params_pairs = str::tokens(txt_svc_params, std::string(" "));
664
665 for (auto const& svc_param_pair : svc_params_pairs) {
666 std::vector<std::string> key_val_tokens = str::tokens(str::trim(svc_param_pair), "=");
667 if (key_val_tokens.size() != 2) {
668 isc_throw(InvalidOptionDnrSvcParams,
669 getLogPrefix() << "Wrong Svc Params syntax - SvcParamKey=SvcParamValue "
670 << "pair syntax must be used");
671 }
672
673 // SvcParam Key related checks come below.
674 std::string svc_param_key = str::trim(key_val_tokens[0]);
675
676 // As per RFC9463 Section 3.1.8:
677 // The service parameters do not include "ipv4hint" or "ipv6hint" parameters.
678 if (FORBIDDEN_SVC_PARAMS.find(svc_param_key) != FORBIDDEN_SVC_PARAMS.end()) {
679 isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
680 << "Wrong Svc Params syntax - key "
681 << svc_param_key << " must not be used");
682 }
683
684 // Check if SvcParamKey is known in
685 // https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
686 auto svc_params_iterator = SVC_PARAMS.left.find(svc_param_key);
687 if (svc_params_iterator == SVC_PARAMS.left.end()) {
688 isc_throw(InvalidOptionDnrSvcParams,
689 getLogPrefix() << "Wrong Svc Params syntax - key " << svc_param_key
690 << " not found in SvcParamKeys registry");
691 }
692
693 // Check if SvcParamKey usage is supported by DNR DHCP option.
694 // Note that SUPPORTED_SVC_PARAMS set may expand in the future.
695 uint16_t num_svc_param_key = svc_params_iterator->second;
696 if (SUPPORTED_SVC_PARAMS.find(num_svc_param_key) == SUPPORTED_SVC_PARAMS.end()) {
697 isc_throw(InvalidOptionDnrSvcParams,
698 getLogPrefix() << "Wrong Svc Params syntax - key " << svc_param_key
699 << " not supported in DNR option SvcParams");
700 }
701
702 // As per RFC9460 Section 2.2:
703 // SvcParamKeys SHALL appear in increasing numeric order. (...)
704 // There are no duplicate SvcParamKeys.
705 //
706 // We check for duplicates here. Correct ordering is done when option gets packed.
707 if (svc_params_map_.find(num_svc_param_key) != svc_params_map_.end()) {
708 isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
709 << "Wrong Svc Params syntax - key "
710 << svc_param_key << " is duplicated.");
711 }
712
713 // SvcParam Val check.
714 std::string svc_param_val = str::trim(key_val_tokens[1]);
715 if (svc_param_val.empty()) {
716 isc_throw(InvalidOptionDnrSvcParams,
717 getLogPrefix() << "Wrong Svc Params syntax - empty SvcParamValue for key "
718 << svc_param_key);
719 }
720
721 switch (num_svc_param_key) {
722 case 1:
723 parseAlpnSvcParam(svc_param_val);
724 break;
725 case 3:
726 parsePortSvcParam(svc_param_val);
727 break;
728 case 7:
729 parseDohpathSvcParam(svc_param_val);
730 break;
731 default:
732 // This should not happen because we check if num_svc_param_key is
733 // in SUPPORTED_SVC_PARAMS before. But in case new SvcParam appears in Supported,
734 // and is not handled here...
735 isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
736 << "Wrong Svc Params syntax - key "
737 << num_svc_param_key << " not supported yet.");
738 }
739 }
740
741 // If the "alpn" SvcParam indicates support for HTTP, "dohpath" MUST be present.
742 if (alpn_http_ && svc_params_map_.find(7) == svc_params_map_.end()) {
743 isc_throw(InvalidOptionDnrSvcParams,
744 getLogPrefix() << "Wrong Svc Params syntax - dohpath SvcParam missing. "
745 << "When alpn SvcParam indicates "
746 << "support for HTTP, dohpath must be present.");
747 }
748
749 // At this step all given SvcParams should be fine. We can pack everything to data
750 // buffer according to RFC9460 Section 2.2.
751 //
752 // When the list of SvcParams is non-empty, it contains a series of
753 // SvcParamKey=SvcParamValue pairs, represented as:
754 // - a 2-octet field containing the SvcParamKey as an integer in network byte order.
755 // - a 2-octet field containing the length of the SvcParamValue as an integer
756 // between 0 and 65535 in network byte order. (uint16)
757 // - an octet string of this length whose contents are the SvcParamValue in a format
758 // determined by the SvcParamKey.
759 // (...)
760 // SvcParamKeys SHALL appear in increasing numeric order.
761 // Note that (...) there are no duplicate SvcParamKeys.
762 OutputBuffer out_buf(2);
763
764 for (auto const& svc_param_key : SUPPORTED_SVC_PARAMS) {
765 auto it = svc_params_map_.find(svc_param_key);
766 if (it != svc_params_map_.end()) {
767 // Write 2-octet field containing the SvcParamKey as an integer
768 // in network byte order.
769 out_buf.writeUint16(it->first);
770 // Write 2-octet field containing the length of the SvcParamValue
771 // and an octet string of this length whose contents are the SvcParamValue.
772 // We use OpaqueDataTuple#pack(&buf) here that will write correct len-data
773 // tuple to the buffer.
774 (it->second).pack(out_buf);
775 }
776 }
777
778 // Copy SvcParams buffer from OutputBuffer to OptionBuffer.
779 const uint8_t* ptr = static_cast<const uint8_t*>(out_buf.getData());
780 OptionBuffer temp_buf(ptr, ptr + out_buf.getLength());
781 svc_params_buf_ = temp_buf;
782 svc_params_length_ = out_buf.getLength();
783 out_buf.clear();
784}
785
786void
787DnrInstance::parseAlpnSvcParam(const std::string& svc_param_val) {
788 // The wire-format value for "alpn" consists of at least one alpn-id prefixed by its
789 // length as a single octet, and these length-value pairs are concatenated to form
790 // the SvcParamValue.
791 OutputBuffer out_buf(2);
792 OpaqueDataTuple svc_param_val_tuple(OpaqueDataTuple::LENGTH_2_BYTES);
793 std::vector<std::string> alpn_ids_tokens = str::tokens(svc_param_val, std::string(","));
794 for (auto const& alpn_id : alpn_ids_tokens) {
795 // Check if alpn-id is known in
796 // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
797 if (ALPN_IDS.find(alpn_id) == ALPN_IDS.end()) {
798 isc_throw(InvalidOptionDnrSvcParams,
799 getLogPrefix() << "Wrong Svc Params syntax - alpn-id " << alpn_id
800 << " not found in ALPN-IDs registry");
801 }
802
803 // Make notice if this is any of http alpn-ids.
804 if (alpn_id[0] == 'h') {
805 alpn_http_ = true;
806 }
807
808 OpaqueDataTuple alpn_id_tuple(OpaqueDataTuple::LENGTH_1_BYTE);
809 alpn_id_tuple.append(alpn_id);
810 alpn_id_tuple.pack(out_buf);
811 }
812
813 svc_param_val_tuple.append(out_buf.getData(), out_buf.getLength());
814 svc_params_map_.insert(std::make_pair(1, svc_param_val_tuple));
815 out_buf.clear();
816}
817
818void
819DnrInstance::parsePortSvcParam(const std::string& svc_param_val) {
820 // The wire format of the SvcParamValue is the corresponding 2-octet numeric value
821 // in network byte order.
822 OutputBuffer out_buf(2);
823 OpaqueDataTuple svc_param_val_tuple(OpaqueDataTuple::LENGTH_2_BYTES);
824 uint16_t port;
825 try {
826 port = boost::lexical_cast<uint16_t>(svc_param_val);
827 } catch (const std::exception& e) {
828 isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
829 << "Cannot parse uint_16 integer port nr "
830 << "from given value: " << svc_param_val
831 << ". Error: " << e.what());
832 }
833
834 out_buf.writeUint16(port);
835 svc_param_val_tuple.append(out_buf.getData(), out_buf.getLength());
836 out_buf.clear();
837 svc_params_map_.insert(std::make_pair(3, svc_param_val_tuple));
838}
839
840void
841DnrInstance::parseDohpathSvcParam(const std::string& svc_param_val) {
842 // RFC9461 Section 5
843 // single-valued SvcParamKey whose value (in both presentation format and wire
844 // format) MUST be a URI Template in relative form ([RFC6570], Section 1.1) encoded
845 // in UTF-8 [RFC3629]. If the "alpn" SvcParam indicates support for HTTP,
846 // "dohpath" MUST be present. The URI Template MUST contain a "dns" variable,
847 // and MUST be chosen such that the result after DoH URI Template expansion
848 // (Section 6 of [RFC8484]) is always a valid and functional ":path" value
849 // ([RFC9113], Section 8.3.1).
850 std::vector<uint8_t> utf8_encoded;
851 OpaqueDataTuple svc_param_val_tuple(OpaqueDataTuple::LENGTH_2_BYTES);
852
853 // Check that "dns" variable is there
854 if (svc_param_val.find("{?dns}") == std::string::npos) {
855 isc_throw(InvalidOptionDnrSvcParams,
856 getLogPrefix() << "Wrong Svc Params syntax - dohpath SvcParamValue URI"
857 << " Template MUST contain a 'dns' variable.");
858 }
859
860 // We hope to have URI containing < 0x80 ASCII chars, however to be sure
861 // and to be inline with RFC9461 Section 5, let's encode the dohpath with utf8.
862 utf8_encoded = encode::encodeUtf8(svc_param_val);
863 svc_param_val_tuple.append(utf8_encoded.begin(), utf8_encoded.size());
864 svc_params_map_.insert(std::make_pair(7, svc_param_val_tuple));
865}
866
867} // namespace dhcp
868} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
Represents DNR Instance which is used both in DHCPv4 and DHCPv6 Encrypted DNS Option.
Definition option4_dnr.h:55
Option::Universe universe_
Either V4 or V6 Option universe.
static const std::set< uint8_t > SUPPORTED_SVC_PARAMS
Ordered set of supported SvcParamKeys.
Definition option4_dnr.h:91
std::string getDnrInstanceAsText() const
Returns string representation of the DNR instance.
void unpackSvcParams(OptionBufferConstIter &begin, OptionBufferConstIter end)
Unpacks Service Parameters from wire data buffer and stores it in svc_params_buf_.
void setAdnOnlyMode(bool adn_only_mode)
Setter of the adn_only_mode_ field.
std::map< uint16_t, OpaqueDataTuple > svc_params_map_
Service Parameters stored in a map.
uint16_t dnr_instance_data_length_
Length of all following data inside this DNR instance in octets.
OptionBuffer svc_params_buf_
Service Parameters (SvcParams) (variable length) as on-wire data buffer.
AddressContainer ip_addresses_
Vector container holding one or more IP addresses.
uint8_t getAdnLengthSize() const
Returns size in octets of ADN Length field.
void unpackDnrInstanceDataLength(OptionBufferConstIter &begin, OptionBufferConstIter end)
Unpacks DNR Instance Data Length from wire data buffer and stores it in dnr_instance_data_length_.
void packAdn(isc::util::OutputBuffer &buf) const
Writes the ADN FQDN in the wire format into a buffer.
uint16_t addr_length_
Length of included IP addresses in octets.
virtual void unpackAddresses(OptionBufferConstIter &begin, OptionBufferConstIter end)
Unpacks IP address(es) from wire data and stores it/them in ip_addresses_.
uint8_t getAddrLengthSize() const
Returns size in octets of Addr Length field.
uint16_t svc_params_length_
Length of Service Parameters field in octets.
void parseDnrInstanceConfigData(const std::string &config_txt)
Parses a convenient notation of the option data, which may be used in config.
void unpackServicePriority(OptionBufferConstIter &begin)
Unpacks Service Priority from wire data buffer and stores it in service_priority_.
uint16_t service_priority_
The priority of this instance compared to other DNR instances.
void packSvcParams(isc::util::OutputBuffer &buf) const
Writes the Service Parameters in the wire format into a buffer.
boost::bimap< std::string, uint16_t > SvcParamsMap
A Type defined for boost Bimap holding SvcParamKeys.
Definition option4_dnr.h:61
virtual void packAddresses(isc::util::OutputBuffer &buf) const
Writes the IP address(es) in the wire format into a buffer.
bool alpn_http_
Indicates whether the "alpn" SvcParam contains support for HTTP.
std::string getAdnAsText() const
Returns the Authentication domain name in the text format.
std::string getLogPrefix() const
Returns Log prefix depending on V4/V6 Option universe.
static const SvcParamsMap SVC_PARAMS
Service parameters, used in DNR options in DHCPv4 and DHCPv6, but also in RA and DNS.
Definition option4_dnr.h:76
static const std::unordered_set< std::string > ALPN_IDS
Possible ALPN protocol IDs.
Definition option4_dnr.h:97
static const std::unordered_set< std::string > FORBIDDEN_SVC_PARAMS
Set of forbidden SvcParams.
Definition option4_dnr.h:71
bool adn_only_mode_
Flag stating whether ADN only mode is used or not.
uint16_t adn_length_
Length of the authentication-domain-name data in octets.
uint16_t dnrInstanceLen() const
Calculates and returns length of DNR Instance data in octets.
void addIpAddress(const asiolink::IOAddress &ip_address)
Adds IP address to ip_addresses_ container.
void unpackAdn(OptionBufferConstIter &begin, OptionBufferConstIter end)
Unpacks the ADN from given wire data buffer and stores it in adn_ field.
static const uint8_t SERVICE_PRIORITY_SIZE
Size in octets of Service Priority field.
Definition option4_dnr.h:64
uint8_t getDnrInstanceDataLengthSize() const
Returns size in octets of DNR Instance Data Length field.
boost::shared_ptr< isc::dns::Name > adn_
Authentication domain name field of variable length.
DnrInstance(Option::Universe universe)
Constructor of the empty DNR Instance.
Exception thrown when invalid domain name is specified.
Definition option4_dnr.h:29
Exception thrown when Service parameters have wrong format.
Definition option4_dnr.h:37
Represents a single instance of the opaque data preceded by length.
LengthFieldType
Size of the length field in the tuple.
OptionPtr clone() const override
Copies this option and returns a pointer to the copy.
void addDnrInstance(DnrInstance &dnr_instance)
Adds given DNR instance to Option's DNR Instance container.
std::string toText(int indent=0) const override
Returns string representation of the option.
Option4Dnr(OptionBufferConstIter begin, OptionBufferConstIter end, bool convenient_notation=false)
Constructor of the Option from on-wire data.
void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) override
Parses received wire data buffer.
DnrInstanceContainer dnr_instances_
Container holding DNR Instances.
uint16_t len() const override
Returns length of the complete option (data length + DHCPv4/DHCPv6 option header)
void pack(util::OutputBuffer &buf, bool check=true) const override
Writes option in wire-format to a buffer.
static OpaqueDataTuple::LengthFieldType getTupleLenFieldType(Option::Universe u)
Returns Length Field Type for a tuple.
uint16_t type_
option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
Definition option.h:597
virtual uint16_t getHeaderLen() const
Returns length of header (2 for v4, 4 for v6)
Definition option.cc:327
Universe
defines option universe DHCPv4 or DHCPv6
Definition option.h:90
void setData(InputIterator first, InputIterator last)
Sets content of this option from buffer.
Definition option.h:434
void packHeader(isc::util::OutputBuffer &buf, bool check=true) const
Store option's header in a buffer.
Definition option.cc:119
void check() const
A protected method used for option correctness.
Definition option.cc:90
static const size_t OPTION4_HDR_LEN
length of the usual DHCPv4 option header (there are exceptions)
Definition option.h:84
Light-weight Accessor to Name data.
The Name class encapsulates DNS names.
Definition name.h:219
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition buffer.h:81
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:343
@ D6O_V6_DNR
Definition dhcp6.h:159
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
@ DHO_V4_DNR
Definition dhcp4.h:217
OptionBuffer::const_iterator OptionBufferConstIter
const_iterator for walking over OptionBuffer
Definition option.h:30
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition option.h:24
boost::shared_ptr< Option > OptionPtr
Definition option.h:37
std::vector< uint8_t > encodeUtf8(const std::string &value)
Encode value string into UTF-8.
Definition utf8.cc:15
vector< string > tokens(const string &text, const string &delim, bool escape)
Split string into tokens.
Definition str.cc:52
string trim(const string &input)
Trim leading and trailing spaces.
Definition str.cc:32
uint16_t readUint16(void const *const buffer, size_t const length)
uint16_t wrapper over readUint.
Definition io.h:76
uint32_t readUint32(void const *const buffer, size_t const length)
uint32_t wrapper over readUint.
Definition io.h:82
Defines the logger used by the top-level component of kea-lfc.