/src/pdns/pdns/dnsname.hh
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of PowerDNS or dnsdist. |
3 | | * Copyright -- PowerDNS.COM B.V. and its contributors |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of version 2 of the GNU General Public License as |
7 | | * published by the Free Software Foundation. |
8 | | * |
9 | | * In addition, for the avoidance of any doubt, permission is granted to |
10 | | * link this program with OpenSSL and to (re)distribute the binaries |
11 | | * produced as the result of such linking. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | | */ |
22 | | #pragma once |
23 | | #include <array> |
24 | | #include <cstring> |
25 | | #include <optional> |
26 | | #include <string> |
27 | | #include <utility> |
28 | | #include <vector> |
29 | | #include <set> |
30 | | #include <strings.h> |
31 | | #include <stdexcept> |
32 | | #include <sstream> |
33 | | #include <iterator> |
34 | | #include <unordered_set> |
35 | | #include <string_view> |
36 | | |
37 | | using namespace std::string_view_literals; |
38 | | |
39 | | #include <boost/version.hpp> |
40 | | #include <boost/container/string.hpp> |
41 | | |
42 | | inline bool dns_isspace(char chr) __attribute__((const)); |
43 | | inline bool dns_isspace(char chr) |
44 | 191k | { |
45 | 191k | return chr == ' ' || chr == '\t' || chr == '\r' || chr == '\n'; |
46 | 191k | } |
47 | | |
48 | | extern const unsigned char dns_toupper_table[256], dns_tolower_table[256]; |
49 | | |
50 | | inline unsigned char dns_toupper(unsigned char chr) __attribute__((pure)); |
51 | | inline unsigned char dns_toupper(unsigned char chr) |
52 | 339M | { |
53 | 339M | return dns_toupper_table[chr]; |
54 | 339M | } |
55 | | |
56 | | inline unsigned char dns_tolower(unsigned char chr) __attribute__((pure)); |
57 | | inline unsigned char dns_tolower(unsigned char chr) |
58 | 4.90M | { |
59 | 4.90M | return dns_tolower_table[chr]; |
60 | 4.90M | } |
61 | | |
62 | | #include "burtle.hh" |
63 | | #include "views.hh" |
64 | | |
65 | | /* Quest in life: |
66 | | accept escaped ascii presentations of DNS names and store them "natively" |
67 | | accept a DNS packet with an offset, and extract a DNS name from it |
68 | | build up DNSNames with prepend and append of 'raw' unescaped labels |
69 | | |
70 | | Be able to turn them into ASCII and "DNS name in a packet" again on request |
71 | | |
72 | | Provide some common operators for comparison, detection of being part of another domain |
73 | | |
74 | | NOTE: For now, everything MUST be . terminated, otherwise it is an error |
75 | | */ |
76 | | |
77 | | // DNSName: represents a case-insensitive string, allowing for non-printable |
78 | | // characters. It is used for all kinds of name (of hosts, domains, keys, |
79 | | // algorithm...) overall the PowerDNS codebase. |
80 | | // |
81 | | // The following type traits are provided: |
82 | | // - EqualityComparable |
83 | | // - LessThanComparable |
84 | | // - Hash |
85 | | #if defined(PDNS_AUTH) |
86 | | class ZoneName; |
87 | | #endif |
88 | | class DNSName |
89 | | { |
90 | | public: |
91 | | static const size_t s_maxDNSNameLength = 255; |
92 | | |
93 | 245k | DNSName() = default; //!< Constructs an *empty* DNSName, NOT the root! |
94 | | // Work around assertion in some boost versions that do not like self-assignment of boost::container::string |
95 | | DNSName& operator=(const DNSName& rhs) |
96 | 197k | { |
97 | 197k | if (this != &rhs) { |
98 | 197k | d_storage = rhs.d_storage; |
99 | 197k | } |
100 | 197k | return *this; |
101 | 197k | } |
102 | | DNSName& operator=(DNSName&& rhs) noexcept |
103 | 448k | { |
104 | 448k | if (this != &rhs) { |
105 | 448k | d_storage = std::move(rhs.d_storage); |
106 | 448k | } |
107 | 448k | return *this; |
108 | 448k | } |
109 | 305k | DNSName(const DNSName& a) = default; |
110 | 144k | DNSName(DNSName&& a) = default; |
111 | | |
112 | | explicit DNSName(std::string_view sw); //!< Constructs from a human formatted, escaped presentation |
113 | | DNSName(const char* p, size_t len, size_t offset, bool uncompress, uint16_t* qtype = nullptr, uint16_t* qclass = nullptr, unsigned int* consumed = nullptr, uint16_t minOffset = 0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression. |
114 | | |
115 | | bool isPartOf(const DNSName& rhs) const; //!< Are we part of the rhs name? Note that name.isPartOf(name). |
116 | | inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty |
117 | 0 | bool operator!=(const DNSName& other) const { return !(*this == other); } |
118 | | |
119 | | std::string toString(const std::string& separator=".", const bool trailing=true) const; //!< Our human-friendly, escaped, representation |
120 | | void toString(std::string& output, const std::string& separator=".", const bool trailing=true) const; |
121 | | std::string toLogString() const; //!< like plain toString, but returns (empty) on empty names |
122 | 0 | std::string toStringNoDot() const { return toString(".", false); } |
123 | 35.1k | std::string toStringRootDot() const { if(isRoot()) return "."; else return toString(".", false); } |
124 | | std::string toDNSString() const; //!< Our representation in DNS native format |
125 | | std::string toDNSStringLC() const; //!< Our representation in DNS native format, lower cased |
126 | | void appendRawLabel(const std::string& str); //!< Append this unescaped label |
127 | | void appendRawLabel(const char* start, unsigned int length); //!< Append this unescaped label |
128 | | void prependRawLabel(const std::string& str); //!< Prepend this unescaped label |
129 | | std::vector<std::string> getRawLabels() const; //!< Individual raw unescaped labels |
130 | | std::string getRawLabel(unsigned int pos) const; //!< Get the specified raw unescaped label |
131 | | DNSName getLastLabel() const; //!< Get the DNSName of the last label |
132 | | bool chopOff(); //!< Turn www.powerdns.com. into powerdns.com., returns false for . |
133 | | DNSName makeRelative(const DNSName& zone) const; |
134 | | DNSName makeLowerCase() const |
135 | 0 | { |
136 | 0 | DNSName ret(*this); |
137 | 0 | ret.makeUsLowerCase(); |
138 | 0 | return ret; |
139 | 0 | } |
140 | | void makeUsLowerCase() |
141 | 0 | { |
142 | 0 | for(auto & c : d_storage) { |
143 | 0 | c=dns_tolower(c); |
144 | 0 | } |
145 | 0 | } |
146 | | void makeUsRelative(const DNSName& zone); |
147 | | DNSName getCommonLabels(const DNSName& other) const; //!< Return the list of common labels from the top, for example 'c.d' for 'a.b.c.d' and 'x.y.c.d' |
148 | | DNSName labelReverse() const; |
149 | | bool isWildcard() const; |
150 | | bool isHostname() const; |
151 | | unsigned int countLabels() const; |
152 | | size_t wirelength() const; //!< Number of total bytes in the name |
153 | 221k | bool empty() const { return d_storage.empty(); } |
154 | 69.0k | bool isRoot() const { return d_storage.size()==1 && d_storage[0]==0; } |
155 | 0 | bool hasLabels() const { return !empty() && !isRoot(); } |
156 | 0 | void clear() { d_storage.clear(); } |
157 | | void trimToLabels(unsigned int); |
158 | | size_t hash(size_t init=0) const |
159 | 0 | { |
160 | 0 | return burtleCI(d_storage, init); |
161 | 0 | } |
162 | | DNSName& operator+=(const DNSName& rhs) |
163 | 0 | { |
164 | 0 | if(d_storage.size() + rhs.d_storage.size() > s_maxDNSNameLength + 1) // one extra byte for the second root label |
165 | 0 | throwSafeRangeError("resulting name too long", rhs.d_storage.data(), rhs.d_storage.size()); |
166 | 0 | if(rhs.empty()) |
167 | 0 | return *this; |
168 | 0 |
|
169 | 0 | if(d_storage.empty()) |
170 | 0 | d_storage+=rhs.d_storage; |
171 | 0 | else |
172 | 0 | d_storage.replace(d_storage.length()-1, rhs.d_storage.length(), rhs.d_storage); |
173 | 0 |
|
174 | 0 | return *this; |
175 | 0 | } |
176 | | |
177 | | bool operator<(const DNSName& rhs) const // this delivers _some_ kind of ordering, but not one useful in a DNS context. Really fast though. |
178 | 0 | { |
179 | 0 | struct DNSNameCompare |
180 | 0 | { |
181 | 0 | bool operator()(const unsigned char& lhs, const unsigned char& rhs) const |
182 | 0 | { |
183 | 0 | return dns_tolower(lhs) < dns_tolower(rhs); |
184 | 0 | } |
185 | 0 | }; |
186 | | |
187 | | // note that this is case insensitive, including on the label lengths |
188 | 0 | return std::lexicographical_compare(d_storage.rbegin(), d_storage.rend(), |
189 | 0 | rhs.d_storage.rbegin(), rhs.d_storage.rend(), DNSNameCompare()); |
190 | 0 | } |
191 | | |
192 | | int slowCanonCompare_three_way(const DNSName& rhs) const; |
193 | | int canonCompare_three_way(const DNSName& rhs) const; |
194 | 0 | inline bool canonCompare(const DNSName& rhs) const { return canonCompare_three_way(rhs) < 0; } |
195 | | |
196 | | typedef boost::container::string string_t; |
197 | | |
198 | 1.97k | const string_t& getStorage() const { |
199 | 1.97k | return d_storage; |
200 | 1.97k | } |
201 | | |
202 | | [[nodiscard]] size_t sizeEstimate() const |
203 | 0 | { |
204 | 0 | return d_storage.size(); // knowingly overestimating small strings as most string |
205 | 0 | // implementations have internal capacity and we always include |
206 | 0 | // sizeof(*this) |
207 | 0 | } |
208 | | |
209 | | bool has8bitBytes() const; /* returns true if at least one byte of the labels forming the name is not included in [A-Za-z0-9_*./@ \\:-] */ |
210 | | |
211 | | class RawLabelsVisitor |
212 | | { |
213 | | public: |
214 | | /* Zero-copy, zero-allocation raw labels visitor. |
215 | | The general idea is that we walk the labels in the constructor, |
216 | | filling up our array of labels position and setting the initial |
217 | | value of d_position at the number of labels. |
218 | | We then can easily provide string_view into the first and last label. |
219 | | pop_back() moves d_position one label closer to the start, so we |
220 | | can also easily walk back the labels in reverse order. |
221 | | There is no copy because we use a reference into the DNSName storage, |
222 | | so it is absolutely forbidden to alter the DNSName for as long as we |
223 | | exist, and no allocation because we use a static array (there cannot |
224 | | be more than 128 labels in a DNSName). |
225 | | */ |
226 | | RawLabelsVisitor(const string_t& storage); |
227 | | std::string_view front() const; |
228 | | std::string_view back() const; |
229 | | bool pop_back(); |
230 | | bool empty() const; |
231 | | private: |
232 | | std::array<uint8_t, 128> d_labelPositions; |
233 | | const string_t& d_storage; |
234 | | size_t d_position{0}; |
235 | | }; |
236 | | RawLabelsVisitor getRawLabelsVisitor() const; |
237 | | |
238 | | #if defined(PDNS_AUTH) // [ |
239 | | // Sugar while ZoneName::operator DNSName are made explicit |
240 | | bool isPartOf(const ZoneName& rhs) const; |
241 | | DNSName makeRelative(const ZoneName& zone) const; |
242 | | void makeUsRelative(const ZoneName& zone); |
243 | | #endif // ] |
244 | | |
245 | | private: |
246 | | string_t d_storage; |
247 | | |
248 | | void packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset); |
249 | | size_t parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t position, bool uncompress); |
250 | | static void appendEscapedLabel(std::string& appendTo, const char* orig, size_t len); |
251 | | static std::string unescapeLabel(const std::string& orig); |
252 | | static void throwSafeRangeError(const std::string& msg, const char* buf, size_t length); |
253 | | }; |
254 | | |
255 | | size_t hash_value(DNSName const& d); |
256 | | |
257 | | struct CanonDNSNameCompare |
258 | | { |
259 | | bool operator()(const DNSName&a, const DNSName& b) const |
260 | 0 | { |
261 | 0 | return a.canonCompare(b); |
262 | 0 | } |
263 | | }; |
264 | | |
265 | | inline DNSName operator+(const DNSName& lhs, const DNSName& rhs) |
266 | 0 | { |
267 | 0 | DNSName ret=lhs; |
268 | 0 | ret += rhs; |
269 | 0 | return ret; |
270 | 0 | } |
271 | | |
272 | | extern const DNSName g_rootdnsname; // . |
273 | | extern const DNSName g_wildcarddnsname; // * |
274 | | |
275 | | extern const DNSName g_coodnsname; // coo |
276 | | extern const DNSName g_groupdnsname; // group |
277 | | extern const DNSName g_versiondnsname; // version |
278 | | extern const DNSName g_zonesdnsname; // zones |
279 | | |
280 | | extern const DNSName g_gsstsigdnsname; // gss-tsig |
281 | | extern const DNSName g_hmacmd5dnsname; // hmac-md5 |
282 | | extern const DNSName g_hmacmd5dnsname_long; // hmac-md5.sig-alg.reg.int |
283 | | extern const DNSName g_hmacsha1dnsname; // hmac-sha1 |
284 | | extern const DNSName g_hmacsha224dnsname; // hmac-sha224 |
285 | | extern const DNSName g_hmacsha256dnsname; // hmac-sha256 |
286 | | extern const DNSName g_hmacsha384dnsname; // hmac-sha384 |
287 | | extern const DNSName g_hmacsha512dnsname; // hmac-sha512 |
288 | | |
289 | | #if defined(PDNS_AUTH) // [ |
290 | | // ZoneName: this is equivalent to DNSName, but intended to only store zone |
291 | | // names. In addition to the name, an optional variant is allowed. The |
292 | | // variant is never part of a DNS packet; it can only be used by backends to |
293 | | // perform specific extra processing. |
294 | | // Variant names are limited to [a-z0-9_-]. |
295 | | // Conversions between DNSName and ZoneName are allowed, but must be explicit; |
296 | | // conversions to DNSName lose the variant part. |
297 | | class ZoneName |
298 | | { |
299 | | public: |
300 | 0 | ZoneName() = default; //!< Constructs an *empty* ZoneName, NOT the root! |
301 | | // Work around assertion in some boost versions that do not like self-assignment of boost::container::string |
302 | | ZoneName& operator=(const ZoneName& rhs) |
303 | 0 | { |
304 | 0 | if (this != &rhs) { |
305 | 0 | d_name = rhs.d_name; |
306 | 0 | d_variant = rhs.d_variant; |
307 | 0 | } |
308 | 0 | return *this; |
309 | 0 | } |
310 | | ZoneName& operator=(ZoneName&& rhs) noexcept |
311 | 25.1k | { |
312 | 25.1k | if (this != &rhs) { |
313 | 25.1k | d_name = std::move(rhs.d_name); |
314 | 25.1k | d_variant = std::move(rhs.d_variant); |
315 | 25.1k | } |
316 | 25.1k | return *this; |
317 | 25.1k | } |
318 | 5.89k | ZoneName(const ZoneName& a) = default; |
319 | 5.89k | ZoneName(ZoneName&& a) = default; |
320 | | |
321 | | explicit ZoneName(std::string_view name); |
322 | 0 | explicit ZoneName(std::string_view name, std::string_view variant) : d_name(name), d_variant(variant) {} |
323 | 0 | explicit ZoneName(const DNSName& name, std::string_view variant = ""sv) : d_name(name), d_variant(variant) {} |
324 | | explicit ZoneName(std::string_view name, std::string_view::size_type sep); |
325 | | |
326 | 0 | bool isPartOf(const ZoneName& rhs) const { return d_name.isPartOf(rhs.d_name); } |
327 | 0 | bool isPartOf(const DNSName& rhs) const { return d_name.isPartOf(rhs); } |
328 | 0 | bool operator==(const ZoneName& rhs) const { return d_name == rhs.d_name && d_variant == rhs.d_variant; } |
329 | 0 | bool operator!=(const ZoneName& rhs) const { return !operator==(rhs); } |
330 | | |
331 | | std::string toString(const std::string& separator=".", const bool trailing=true) const; |
332 | 0 | void toString(std::string& output, const std::string& separator=".", const bool trailing=true) const { output = toString(separator, trailing); } |
333 | | std::string toLogString() const; |
334 | | std::string toStringNoDot() const; |
335 | | std::string toStringRootDot() const; |
336 | | |
337 | 0 | bool chopOff() { return d_name.chopOff(); } |
338 | | ZoneName makeLowerCase() const |
339 | 0 | { |
340 | 0 | ZoneName ret(*this); |
341 | 0 | ret.d_name.makeUsLowerCase(); |
342 | 0 | return ret; |
343 | 0 | } |
344 | 0 | void makeUsLowerCase() { d_name.makeUsLowerCase(); } |
345 | 0 | bool empty() const { return d_name.empty(); } |
346 | 0 | void clear() { d_name.clear(); d_variant.clear(); } |
347 | 0 | void trimToLabels(unsigned int trim) { d_name.trimToLabels(trim); } |
348 | | size_t hash(size_t init=0) const; |
349 | | |
350 | | bool operator<(const ZoneName& rhs) const; |
351 | | |
352 | | int canonCompare_three_way(const ZoneName& rhs) const; |
353 | 0 | inline bool canonCompare(const ZoneName& rhs) const { return canonCompare_three_way(rhs) < 0; } |
354 | | |
355 | | // Conversion from ZoneName to DNSName |
356 | 33.0k | explicit operator const DNSName&() const { return d_name; } |
357 | 161k | explicit operator DNSName&() { return d_name; } |
358 | | |
359 | 0 | bool hasVariant() const { return !d_variant.empty(); } |
360 | 0 | std::string getVariant() const { return d_variant; } |
361 | | void setVariant(std::string_view); |
362 | | |
363 | | // Search for a variant separator: mandatory (when variants are used) trailing |
364 | | // dot followed by another dot and the variant name, and return the length of |
365 | | // the zone name without its variant part, or npos if there is no variant |
366 | | // present. |
367 | | static std::string_view::size_type findVariantSeparator(std::string_view name); |
368 | | |
369 | | private: |
370 | | DNSName d_name; |
371 | | std::string d_variant{}; |
372 | | }; |
373 | | |
374 | | size_t hash_value(ZoneName const& zone); |
375 | | |
376 | | std::ostream & operator<<(std::ostream &ostr, const ZoneName& zone); |
377 | | namespace std { |
378 | | template <> |
379 | | struct hash<ZoneName> { |
380 | 0 | size_t operator () (const ZoneName& dn) const { return dn.hash(0); } |
381 | | }; |
382 | | } |
383 | | |
384 | | struct CanonZoneNameCompare |
385 | | { |
386 | | bool operator()(const ZoneName& a, const ZoneName& b) const |
387 | 0 | { |
388 | 0 | return a.canonCompare(b); |
389 | 0 | } |
390 | | }; |
391 | | #else // ] [ |
392 | | using ZoneName = DNSName; |
393 | | using CanonZoneNameCompare = CanonDNSNameCompare; |
394 | | #endif // ] |
395 | | |
396 | | extern const ZoneName g_rootzonename; |
397 | | |
398 | | template<typename T> |
399 | | struct SuffixMatchTree |
400 | | { |
401 | | SuffixMatchTree(std::string name = "", bool endNode_ = false) : |
402 | | d_name(std::move(name)), endNode(endNode_) |
403 | | {} |
404 | | |
405 | | SuffixMatchTree(const SuffixMatchTree& rhs): d_name(rhs.d_name), children(rhs.children), endNode(rhs.endNode) |
406 | | { |
407 | | if (endNode) { |
408 | | d_value = rhs.d_value; |
409 | | } |
410 | | } |
411 | | SuffixMatchTree & operator=(const SuffixMatchTree &rhs) |
412 | | { |
413 | | d_name = rhs.d_name; |
414 | | children = rhs.children; |
415 | | endNode = rhs.endNode; |
416 | | if (endNode) { |
417 | | d_value = rhs.d_value; |
418 | | } |
419 | | return *this; |
420 | | } |
421 | | bool operator<(const SuffixMatchTree& rhs) const |
422 | 0 | { |
423 | 0 | return strcasecmp(d_name.c_str(), rhs.d_name.c_str()) < 0; |
424 | 0 | } |
425 | | |
426 | | std::string d_name; |
427 | | mutable std::set<SuffixMatchTree, std::less<>> children; |
428 | | mutable bool endNode; |
429 | | mutable T d_value{}; |
430 | | |
431 | | /* this structure is used to do a lookup without allocating and |
432 | | copying a string, using C++14's heterogeneous lookups in ordered |
433 | | containers */ |
434 | | struct LightKey |
435 | | { |
436 | | std::string_view d_name; |
437 | | bool operator<(const SuffixMatchTree& smt) const |
438 | 0 | { |
439 | 0 | auto compareUpTo = std::min(this->d_name.size(), smt.d_name.size()); |
440 | 0 | auto ret = strncasecmp(this->d_name.data(), smt.d_name.data(), compareUpTo); |
441 | 0 | if (ret != 0) { |
442 | 0 | return ret < 0; |
443 | 0 | } |
444 | 0 | if (this->d_name.size() == smt.d_name.size()) { |
445 | 0 | return ret < 0; |
446 | 0 | } |
447 | 0 | return this->d_name.size() < smt.d_name.size(); |
448 | 0 | } |
449 | | }; |
450 | | |
451 | | bool operator<(const LightKey& lk) const |
452 | 0 | { |
453 | 0 | auto compareUpTo = std::min(this->d_name.size(), lk.d_name.size()); |
454 | 0 | auto ret = strncasecmp(this->d_name.data(), lk.d_name.data(), compareUpTo); |
455 | 0 | if (ret != 0) { |
456 | 0 | return ret < 0; |
457 | 0 | } |
458 | 0 | if (this->d_name.size() == lk.d_name.size()) { |
459 | 0 | return ret < 0; |
460 | 0 | } |
461 | 0 | return this->d_name.size() < lk.d_name.size(); |
462 | 0 | } |
463 | | |
464 | | template<typename V> |
465 | | void visit(const V& v) const { |
466 | | for(const auto& c : children) { |
467 | | c.visit(v); |
468 | | } |
469 | | |
470 | | if (endNode) { |
471 | | v(*this); |
472 | | } |
473 | | } |
474 | | |
475 | | void add(const DNSName& name, T&& t) |
476 | 0 | { |
477 | 0 | auto labels = name.getRawLabels(); |
478 | 0 | add(labels, std::move(t)); |
479 | 0 | } |
480 | | |
481 | | void add(std::vector<std::string>& labels, T&& value) const |
482 | 0 | { |
483 | 0 | if (labels.empty()) { // this allows insertion of the root |
484 | 0 | endNode = true; |
485 | 0 | d_value = std::move(value); |
486 | 0 | } |
487 | 0 | else if(labels.size()==1) { |
488 | 0 | auto res = children.emplace(*labels.begin(), true); |
489 | 0 | if (!res.second) { |
490 | 0 | // we might already have had the node as an |
491 | 0 | // intermediary one, but it's now an end node |
492 | 0 | if (!res.first->endNode) { |
493 | 0 | res.first->endNode = true; |
494 | 0 | } |
495 | 0 | } |
496 | 0 | res.first->d_value = std::move(value); |
497 | 0 | } |
498 | 0 | else { |
499 | 0 | auto res = children.emplace(*labels.rbegin(), false); |
500 | 0 | labels.pop_back(); |
501 | 0 | res.first->add(labels, std::move(value)); |
502 | 0 | } |
503 | 0 | } |
504 | | |
505 | | void remove(const DNSName &name, bool subtree=false) const |
506 | 0 | { |
507 | 0 | auto labels = name.getRawLabels(); |
508 | 0 | remove(labels, subtree); |
509 | 0 | } |
510 | | |
511 | | /* Removes the node at `labels`, also make sure that no empty |
512 | | * children will be left behind in memory |
513 | | */ |
514 | | void remove(std::vector<std::string>& labels, bool subtree = false) const |
515 | 0 | { |
516 | 0 | if (labels.empty()) { // this allows removal of the root |
517 | 0 | endNode = false; |
518 | 0 | if (subtree) { |
519 | 0 | children.clear(); |
520 | 0 | } |
521 | 0 | return; |
522 | 0 | } |
523 | 0 |
|
524 | 0 | SuffixMatchTree smt(*labels.rbegin()); |
525 | 0 | auto child = children.find(smt); |
526 | 0 | if (child == children.end()) { |
527 | 0 | // No subnode found, we're done |
528 | 0 | return; |
529 | 0 | } |
530 | 0 |
|
531 | 0 | // We have found a child |
532 | 0 | labels.pop_back(); |
533 | 0 | if (labels.empty()) { |
534 | 0 | // The child is no longer an endnode |
535 | 0 | child->endNode = false; |
536 | 0 |
|
537 | 0 | if (subtree) { |
538 | 0 | child->children.clear(); |
539 | 0 | } |
540 | 0 |
|
541 | 0 | // If the child has no further children, just remove it from the set. |
542 | 0 | if (child->children.empty()) { |
543 | 0 | children.erase(child); |
544 | 0 | } |
545 | 0 | return; |
546 | 0 | } |
547 | 0 |
|
548 | 0 | // We are not at the end, let the child figure out what to do |
549 | 0 | child->remove(labels); |
550 | 0 | } |
551 | | |
552 | | T* lookup(const DNSName& name) const |
553 | 0 | { |
554 | 0 | auto bestNode = getBestNode(name); |
555 | 0 | if (bestNode) { |
556 | 0 | return &bestNode->d_value; |
557 | 0 | } |
558 | 0 | return nullptr; |
559 | 0 | } |
560 | | |
561 | | std::optional<DNSName> getBestMatch(const DNSName& name) const |
562 | 0 | { |
563 | 0 | if (children.empty()) { // speed up empty set |
564 | 0 | return endNode ? std::optional<DNSName>(g_rootdnsname) : std::nullopt; |
565 | 0 | } |
566 | 0 |
|
567 | 0 | auto visitor = name.getRawLabelsVisitor(); |
568 | 0 | return getBestMatch(visitor); |
569 | 0 | } |
570 | | |
571 | | // Returns all end-nodes, fully qualified (not as separate labels) |
572 | | std::vector<DNSName> getNodes() const { |
573 | | std::vector<DNSName> ret; |
574 | | if (endNode) { |
575 | | ret.push_back(DNSName(d_name)); |
576 | | } |
577 | | for (const auto& child : children) { |
578 | | auto nodes = child.getNodes(); |
579 | | ret.reserve(ret.size() + nodes.size()); |
580 | | for (const auto &node: nodes) { |
581 | | ret.push_back(node + DNSName(d_name)); |
582 | | } |
583 | | } |
584 | | return ret; |
585 | | } |
586 | | |
587 | | private: |
588 | | const SuffixMatchTree* getBestNode(const DNSName& name) const |
589 | 0 | { |
590 | 0 | if (children.empty()) { // speed up empty set |
591 | 0 | if (endNode) { |
592 | 0 | return this; |
593 | 0 | } |
594 | 0 | return nullptr; |
595 | 0 | } |
596 | 0 |
|
597 | 0 | auto visitor = name.getRawLabelsVisitor(); |
598 | 0 | return getBestNode(visitor); |
599 | 0 | } |
600 | | |
601 | | const SuffixMatchTree* getBestNode(DNSName::RawLabelsVisitor& visitor) const |
602 | 0 | { |
603 | 0 | if (visitor.empty()) { // optimization |
604 | 0 | if (endNode) { |
605 | 0 | return this; |
606 | 0 | } |
607 | 0 | return nullptr; |
608 | 0 | } |
609 | 0 |
|
610 | 0 | const LightKey lk{visitor.back()}; |
611 | 0 | auto child = children.find(lk); |
612 | 0 | if (child == children.end()) { |
613 | 0 | if (endNode) { |
614 | 0 | return this; |
615 | 0 | } |
616 | 0 | return nullptr; |
617 | 0 | } |
618 | 0 | visitor.pop_back(); |
619 | 0 | auto result = child->getBestNode(visitor); |
620 | 0 | if (result) { |
621 | 0 | return result; |
622 | 0 | } |
623 | 0 | return endNode ? this : nullptr; |
624 | 0 | } |
625 | | |
626 | | std::optional<DNSName> getBestMatch(DNSName::RawLabelsVisitor& visitor) const |
627 | 0 | { |
628 | 0 | if (visitor.empty()) { // optimization |
629 | 0 | if (endNode) { |
630 | 0 | return std::optional<DNSName>(d_name); |
631 | 0 | } |
632 | 0 | return std::nullopt; |
633 | 0 | } |
634 | 0 |
|
635 | 0 | const LightKey lk{visitor.back()}; |
636 | 0 | auto child = children.find(lk); |
637 | 0 | if (child == children.end()) { |
638 | 0 | if (endNode) { |
639 | 0 | return std::optional<DNSName>(d_name); |
640 | 0 | } |
641 | 0 | return std::nullopt; |
642 | 0 | } |
643 | 0 | visitor.pop_back(); |
644 | 0 | auto result = child->getBestMatch(visitor); |
645 | 0 | if (result) { |
646 | 0 | if (!d_name.empty()) { |
647 | 0 | result->appendRawLabel(d_name); |
648 | 0 | } |
649 | 0 | return result; |
650 | 0 | } |
651 | 0 | return endNode ? std::optional<DNSName>(d_name) : std::nullopt; |
652 | 0 | } |
653 | | }; |
654 | | |
655 | | /* Quest in life: serve as a rapid block list. If you add a DNSName to a root SuffixMatchNode, |
656 | | anything part of that domain will return 'true' in check */ |
657 | | struct SuffixMatchNode |
658 | | { |
659 | | public: |
660 | | SuffixMatchNode() = default; |
661 | | SuffixMatchTree<bool> d_tree; |
662 | | |
663 | | void add(const DNSName& dnsname) |
664 | 0 | { |
665 | 0 | d_tree.add(dnsname, true); |
666 | 0 | d_nodes.insert(dnsname); |
667 | 0 | } |
668 | | |
669 | | void add(const std::string& name) |
670 | 0 | { |
671 | 0 | add(DNSName(name)); |
672 | 0 | } |
673 | | |
674 | | void add(std::vector<std::string> labels) |
675 | 0 | { |
676 | 0 | d_tree.add(labels, true); |
677 | 0 | DNSName tmp; |
678 | 0 | while (!labels.empty()) { |
679 | 0 | tmp.appendRawLabel(labels.back()); |
680 | 0 | labels.pop_back(); // This is safe because we have a copy of labels |
681 | 0 | } |
682 | 0 | d_nodes.insert(tmp); |
683 | 0 | } |
684 | | |
685 | | void remove(const DNSName& name) |
686 | 0 | { |
687 | 0 | d_tree.remove(name); |
688 | 0 | d_nodes.erase(name); |
689 | 0 | } |
690 | | |
691 | | void remove(std::vector<std::string> labels) |
692 | 0 | { |
693 | 0 | d_tree.remove(labels); |
694 | 0 | DNSName tmp; |
695 | 0 | while (!labels.empty()) { |
696 | 0 | tmp.appendRawLabel(labels.back()); |
697 | 0 | labels.pop_back(); // This is safe because we have a copy of labels |
698 | 0 | } |
699 | 0 | d_nodes.erase(tmp); |
700 | 0 | } |
701 | | |
702 | | bool check(const DNSName& dnsname) const |
703 | 0 | { |
704 | 0 | return d_tree.lookup(dnsname) != nullptr; |
705 | 0 | } |
706 | | |
707 | | std::optional<DNSName> getBestMatch(const DNSName& name) const |
708 | 0 | { |
709 | 0 | return d_tree.getBestMatch(name); |
710 | 0 | } |
711 | | |
712 | | std::string toString() const |
713 | 0 | { |
714 | 0 | std::string ret; |
715 | 0 | bool first = true; |
716 | 0 | for (const auto& n : d_nodes) { |
717 | 0 | if (!first) { |
718 | 0 | ret += ", "; |
719 | 0 | } |
720 | 0 | first = false; |
721 | 0 | ret += n.toString(); |
722 | 0 | } |
723 | 0 | return ret; |
724 | 0 | } |
725 | | |
726 | | private: |
727 | | mutable std::set<DNSName> d_nodes; // Only used for string generation |
728 | | }; |
729 | | |
730 | | std::ostream & operator<<(std::ostream &os, const DNSName& d); |
731 | | namespace std { |
732 | | template <> |
733 | | struct hash<DNSName> { |
734 | 0 | size_t operator () (const DNSName& dn) const { return dn.hash(0); } |
735 | | }; |
736 | | } |
737 | | |
738 | | DNSName::string_t segmentDNSNameRaw(const char* input, size_t inputlen); // from ragel |
739 | | |
740 | | bool DNSName::operator==(const DNSName& rhs) const |
741 | 0 | { |
742 | 0 | if (rhs.empty() != empty() || rhs.d_storage.size() != d_storage.size()) { |
743 | 0 | return false; |
744 | 0 | } |
745 | | |
746 | 0 | const auto* us = d_storage.cbegin(); |
747 | 0 | const auto* p = rhs.d_storage.cbegin(); |
748 | 0 | for (; us != d_storage.cend() && p != rhs.d_storage.cend(); ++us, ++p) { |
749 | 0 | if (dns_tolower(*p) != dns_tolower(*us)) { |
750 | 0 | return false; |
751 | 0 | } |
752 | 0 | } |
753 | 0 | return true; |
754 | 0 | } |
755 | | |
756 | | struct DNSNameSet: public std::unordered_set<DNSName> { |
757 | 0 | std::string toString() const { |
758 | 0 | std::ostringstream oss; |
759 | 0 | std::copy(begin(), end(), std::ostream_iterator<DNSName>(oss, "\n")); |
760 | 0 | return oss.str(); |
761 | 0 | } |
762 | | }; |