]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/packetcache.hh
dnsdist: Fix DNS over plain HTTP broken by `reloadAllCertificates()`
[thirdparty/pdns.git] / pdns / packetcache.hh
CommitLineData
12c86877 1/*
12471842
PL
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 */
e8c59f2d 22#pragma once
bf269e28
RG
23#include "ednsoptions.hh"
24#include "misc.hh"
25#include "iputils.hh"
ba45c866 26
3dbfa51e 27class PacketCache : public boost::noncopyable
12c86877 28{
08b02366 29public:
fa980c59
RG
30
31 /* hash the packet from the provided position, which should point right after tje qname. This skips:
32 - the query ID ;
33 - EDNS Cookie options, if any ;
56f16502 34 - Any given option code present in optionsToSkip
fa980c59 35 */
fa5a722b 36 static uint32_t hashAfterQname(const std::string_view& packet, uint32_t currentHash, size_t pos, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
12c86877 37 {
fa980c59
RG
38 const size_t packetSize = packet.size();
39 assert(packetSize >= sizeof(dnsheader));
40
41 /* we need at least 2 (QTYPE) + 2 (QCLASS)
42
bf269e28
RG
43 + OPT root label (1), type (2), class (2) and ttl (4)
44 + the OPT RR rdlen (2)
fa980c59 45 = 15
bf269e28 46 */
325a18bf
OM
47 const dnsheader_aligned dnsheaderdata(packet.data());
48 const struct dnsheader *dh = dnsheaderdata.get();
fa980c59
RG
49 if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= packetSize) {
50 if (packetSize > pos) {
51 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
bf269e28 52 }
fa980c59 53 return currentHash;
bf269e28 54 }
fa980c59
RG
55
56 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
57 /* skip the qtype (2), qclass (2) */
58 /* root label (1), type (2), class (2) and ttl (4) */
59 /* already hashed above */
60 pos += 13;
61
3e35ea9e 62 const uint16_t rdLen = ((static_cast<uint16_t>(packet.at(pos)) * 256) + static_cast<uint16_t>(packet.at(pos + 1)));
fa980c59
RG
63 /* skip the rd length */
64 /* already hashed above */
65 pos += 2;
66
67 if (rdLen > (packetSize - pos)) {
68 if (pos < packetSize) {
69 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
70 }
71 return currentHash;
bf269e28 72 }
fa980c59
RG
73
74 uint16_t rdataRead = 0;
75 uint16_t optionCode;
76 uint16_t optionLen;
77
78 while (pos < packetSize && rdataRead < rdLen && getNextEDNSOption(&packet.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
745b1626 79 if (optionLen > (rdLen - rdataRead - 4)) {
fa980c59
RG
80 if (packetSize > pos) {
81 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
82 }
83 return currentHash;
84 }
85
56f16502 86 if (optionsToSkip.count(optionCode) == 0) {
fa980c59
RG
87 /* hash the option code, length and content */
88 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4 + optionLen, currentHash);
89 }
90 else {
56f16502 91 /* skip option: hash only its code and length */
fa980c59
RG
92 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
93 }
94
95 pos += 4 + optionLen;
96 rdataRead += 4 + optionLen;
97 }
98
99 if (pos < packetSize) {
100 currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
bf269e28
RG
101 }
102
fa980c59 103 return currentHash;
908c1491 104 }
08b02366 105
fa980c59 106 static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
08b02366 107 {
3e35ea9e 108 const size_t packetSize = packet.size();
fa980c59 109 assert(packetSize >= sizeof(dnsheader));
3c2c5503
OM
110 // Quite some bits in the header are actually irrelevant for
111 // incoming queries. If we ever change that and ignore them for
112 // hashing, don't forget to also adapt the `queryHeaderMatches`
113 // code, as it should be consistent with the hash function.
b2c3da47 114 uint32_t currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, 0); // rest of dnsheader, skip id
fa980c59 115
b2c3da47 116 for (pos = sizeof(dnsheader); pos < packetSize; ) {
fa980c59 117 const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
fa980c59
RG
118 ++pos;
119 if (labelLen == 0) {
120 break;
121 }
b2c3da47 122 pos = std::min(pos + labelLen, packetSize);
fa980c59 123 }
b2c3da47 124 return burtleCI(reinterpret_cast<const unsigned char*>(&packet.at(sizeof(dnsheader))), pos - sizeof(dnsheader), currentHash);
fa980c59 125 }
08b02366 126
fa980c59
RG
127 /* hash the packet from the beginning, including the qname. This skips:
128 - the query ID ;
129 - EDNS Cookie options, if any ;
56f16502 130 - Any given option code present in optionsToSkip
fa980c59 131 */
56f16502 132 static uint32_t canHashPacket(const std::string& packet, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
fa980c59
RG
133 {
134 size_t pos = 0;
135 uint32_t currentHash = hashHeaderAndQName(packet, pos);
fa980c59 136
7459cfc5 137 if (pos >= packet.size()) {
fa980c59 138 return currentHash;
08b02366
RG
139 }
140
56f16502 141 return hashAfterQname(packet, currentHash, pos, optionsToSkip);
08b02366
RG
142 }
143
144 static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
145 {
146 if (cachedQuery.size() != query.size()) {
147 return false;
148 }
149
150 return (cachedQuery.compare(/* skip the ID */ 2, sizeof(dnsheader) - 2, query, 2, sizeof(dnsheader) - 2) == 0);
151 }
152
fa980c59 153 static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, const std::unordered_set<uint16_t>& optionsToIgnore)
08b02366 154 {
fa980c59
RG
155 const size_t querySize = query.size();
156 const size_t cachedQuerySize = cachedQuery.size();
157 if (querySize != cachedQuerySize) {
158 return false;
159 }
160
08b02366
RG
161 if (!queryHeaderMatches(cachedQuery, query)) {
162 return false;
163 }
164
165 size_t pos = sizeof(dnsheader) + qname.wirelength();
166
fa980c59
RG
167 /* we need at least 2 (QTYPE) + 2 (QCLASS)
168 + OPT root label (1), type (2), class (2) and ttl (4)
169 + the OPT RR rdlen (2)
170 = 15
171 */
17c011cd 172 const dnsheader_aligned dnsheaderdata(query.data());
84d4747e 173 const struct dnsheader* dh = dnsheaderdata.get();
fa980c59
RG
174 if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
175 return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
176 }
08b02366 177
fa980c59
RG
178 /* compare up to the first option, if any */
179 if (cachedQuery.compare(pos, 15, query, pos, 15) != 0) {
08b02366
RG
180 return false;
181 }
182
fa980c59
RG
183 /* skip the qtype (2), qclass (2) */
184 /* root label (1), type (2), class (2) and ttl (4) */
185 /* already compared above */
186 pos += 13;
08b02366 187
fa980c59
RG
188 const uint16_t rdLen = ((static_cast<unsigned char>(query.at(pos)) * 256) + static_cast<unsigned char>(query.at(pos + 1)));
189 /* skip the rd length */
190 /* already compared above */
191 pos += sizeof(uint16_t);
192
193 if (rdLen > (querySize - pos)) {
194 /* something is wrong, let's just compare everything */
195 return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
196 }
197
198 uint16_t rdataRead = 0;
199 uint16_t optionCode;
200 uint16_t optionLen;
201
202 while (pos < querySize && rdataRead < rdLen && getNextEDNSOption(&query.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
203 if (optionLen > (rdLen - rdataRead)) {
204 return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
08b02366
RG
205 }
206
fa980c59
RG
207 /* compare the option code and length */
208 if (cachedQuery.compare(pos, 4, query, pos, 4) != 0) {
08b02366
RG
209 return false;
210 }
fa980c59
RG
211 pos += 4;
212 rdataRead += 4;
213
214 if (optionLen > 0 && optionsToIgnore.count(optionCode) == 0) {
215 if (cachedQuery.compare(pos, optionLen, query, pos, optionLen) != 0) {
216 return false;
217 }
08b02366 218 }
fa980c59
RG
219 pos += optionLen;
220 rdataRead += optionLen;
221 }
222
223 if (pos >= querySize) {
224 return true;
08b02366
RG
225 }
226
fa980c59 227 return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
08b02366
RG
228 }
229
12c86877 230};