]>
Commit | Line | Data |
---|---|---|
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 | 27 | class PacketCache : public boost::noncopyable |
12c86877 | 28 | { |
08b02366 | 29 | public: |
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 | }; |