Coverage Report

Created: 2025-07-04 06:59

/src/pdns/pdns/packetcache.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 "ednsoptions.hh"
24
#include "misc.hh"
25
#include "iputils.hh"
26
27
class PacketCache : public boost::noncopyable
28
{
29
public:
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 ;
34
     - Any given option code present in optionsToSkip
35
  */
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}, const std::vector<uint16_t>& payloadRanks = {})
37
2.46k
  {
38
2.46k
    const size_t packetSize = packet.size();
39
2.46k
    assert(packetSize >= sizeof(dnsheader));
40
41
    /* we need at least 2 (QTYPE) + 2 (QCLASS)
42
43
       + OPT root label (1), type (2), class (2) and ttl (4)
44
       + the OPT RR rdlen (2)
45
       = 15
46
    */
47
2.46k
    const dnsheader_aligned dnsheaderdata(packet.data());
48
2.46k
    const struct dnsheader *dh = dnsheaderdata.get();
49
2.46k
    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) > packetSize) {
50
605
      if (packetSize > pos) {
51
605
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
52
605
      }
53
605
      return currentHash;
54
605
    }
55
56
1.86k
    if (payloadRanks.empty()) {
57
1.86k
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
58
1.86k
    }
59
0
    else {
60
0
      std::vector<unsigned char> optrr(packet.begin() + pos, packet.begin() + pos + 15);
61
0
      uint16_t bufSize = optrr.at(7) * 256 + optrr.at(8);
62
0
      auto it = std::upper_bound(payloadRanks.begin(), payloadRanks.end(), bufSize);
63
0
      if (it != payloadRanks.begin()) {
64
0
        it--;
65
0
        optrr[7] = (*it) >> 8;
66
0
        optrr[8] = (*it) & 0xff;
67
0
      }
68
0
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&optrr.at(0)), 15, currentHash);
69
0
    }
70
1.86k
    if ( (pos + 15) == packetSize ) {
71
56
      return currentHash;
72
56
    }
73
74
    /* skip the qtype (2), qclass (2) */
75
    /* root label (1), type (2), class (2) and ttl (4) */
76
    /* already hashed above */
77
1.80k
    pos += 13;
78
79
1.80k
    const uint16_t rdLen = ((static_cast<uint16_t>(packet.at(pos)) * 256) + static_cast<uint16_t>(packet.at(pos + 1)));
80
    /* skip the rd length */
81
    /* already hashed above */
82
1.80k
    pos += 2;
83
84
1.80k
    if (rdLen > (packetSize - pos)) {
85
339
      if (pos < packetSize) {
86
339
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
87
339
      }
88
339
      return currentHash;
89
339
    }
90
91
1.46k
    uint16_t rdataRead = 0;
92
1.46k
    uint16_t optionCode;
93
1.46k
    uint16_t optionLen;
94
95
39.3k
    while (pos < packetSize && rdataRead < rdLen && getNextEDNSOption(&packet.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
96
38.6k
      if (optionLen > (rdLen - rdataRead - 4)) {
97
696
        if (packetSize > pos) {
98
696
          currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
99
696
        }
100
696
        return currentHash;
101
696
      }
102
103
37.9k
      if (optionsToSkip.count(optionCode) == 0) {
104
        /* hash the option code, length and content */
105
35.9k
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4 + optionLen, currentHash);
106
35.9k
      }
107
1.94k
      else {
108
        /* skip option: hash only its code and length */
109
1.94k
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
110
1.94k
      }
111
112
37.9k
      pos += 4 + optionLen;
113
37.9k
      rdataRead += 4 + optionLen;
114
37.9k
    }
115
116
770
    if (pos < packetSize) {
117
464
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
118
464
    }
119
120
770
    return currentHash;
121
1.46k
  }
122
123
  static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
124
2.05k
  {
125
2.05k
    const size_t packetSize = packet.size();
126
2.05k
    assert(packetSize >= sizeof(dnsheader));
127
    // Quite some bits in the header are actually irrelevant for
128
    // incoming queries.  If we ever change that and ignore them for
129
    // hashing, don't forget to also adapt the `queryHeaderMatches`
130
    // code, as it should be consistent with the hash function.
131
2.05k
    uint32_t currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, 0); // rest of dnsheader, skip id
132
133
114k
    for (pos = sizeof(dnsheader); pos < packetSize; ) {
134
114k
      const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
135
114k
      ++pos;
136
114k
      if (labelLen == 0) {
137
1.68k
        break;
138
1.68k
      }
139
112k
      pos = std::min(pos + labelLen, packetSize);
140
112k
    }
141
2.05k
    return burtleCI(reinterpret_cast<const unsigned char*>(&packet.at(sizeof(dnsheader))), pos - sizeof(dnsheader), currentHash);
142
2.05k
  }
143
144
  /* hash the packet from the beginning, including the qname. This skips:
145
     - the query ID ;
146
     - EDNS Cookie options, if any ;
147
     - Any given option code present in optionsToSkip
148
  */
149
  static uint32_t canHashPacket(const std::string& packet, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
150
2.05k
  {
151
2.05k
    size_t pos = 0;
152
2.05k
    uint32_t currentHash = hashHeaderAndQName(packet, pos);
153
154
2.05k
    if (pos >= packet.size()) {
155
670
      return currentHash;
156
670
    }
157
158
1.38k
    return hashAfterQname(packet, currentHash, pos, optionsToSkip);
159
2.05k
  }
160
161
  static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
162
1.50k
  {
163
1.50k
    if (cachedQuery.size() != query.size()) {
164
0
      return false;
165
0
    }
166
167
1.50k
    return (cachedQuery.compare(/* skip the ID */ 2, sizeof(dnsheader) - 2, query, 2, sizeof(dnsheader) - 2) == 0);
168
1.50k
  }
169
170
  static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, const std::unordered_set<uint16_t>& optionsToIgnore)
171
1.50k
  {
172
1.50k
    const size_t querySize = query.size();
173
1.50k
    const size_t cachedQuerySize = cachedQuery.size();
174
1.50k
    if (querySize != cachedQuerySize) {
175
0
      return false;
176
0
    }
177
178
1.50k
    if (!queryHeaderMatches(cachedQuery, query)) {
179
0
      return false;
180
0
    }
181
182
1.50k
    size_t pos = sizeof(dnsheader) + qname.wirelength();
183
184
    /* we need at least 2 (QTYPE) + 2 (QCLASS)
185
       + OPT root label (1), type (2), class (2) and ttl (4)
186
       + the OPT RR rdlen (2)
187
       = 15
188
    */
189
1.50k
    const dnsheader_aligned dnsheaderdata(query.data());
190
1.50k
    const struct dnsheader* dh = dnsheaderdata.get();
191
1.50k
    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
192
618
      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
193
618
    }
194
195
    /* compare up to the first option, if any */
196
890
    if (cachedQuery.compare(pos, 15, query, pos, 15) != 0) {
197
0
      return false;
198
0
    }
199
200
    /* skip the qtype (2), qclass (2) */
201
    /* root label (1), type (2), class (2) and ttl (4) */
202
    /* already compared above */
203
890
    pos += 13;
204
205
890
    const uint16_t rdLen = ((static_cast<unsigned char>(query.at(pos)) * 256) + static_cast<unsigned char>(query.at(pos + 1)));
206
    /* skip the rd length */
207
    /* already compared above */
208
890
    pos += sizeof(uint16_t);
209
210
890
    if (rdLen > (querySize - pos)) {
211
      /* something is wrong, let's just compare everything */
212
188
      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
213
188
    }
214
215
702
    uint16_t rdataRead = 0;
216
702
    uint16_t optionCode;
217
702
    uint16_t optionLen;
218
219
13.5k
    while (pos < querySize && rdataRead < rdLen && getNextEDNSOption(&query.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
220
13.1k
      if (optionLen > (rdLen - rdataRead)) {
221
258
        return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
222
258
      }
223
224
      /* compare the option code and length */
225
12.8k
      if (cachedQuery.compare(pos, 4, query, pos, 4) != 0) {
226
0
        return false;
227
0
      }
228
12.8k
      pos += 4;
229
12.8k
      rdataRead += 4;
230
231
12.8k
      if (optionLen > 0 && optionsToIgnore.count(optionCode) == 0) {
232
5.60k
        if (cachedQuery.compare(pos, optionLen, query, pos, optionLen) != 0) {
233
0
          return false;
234
0
        }
235
5.60k
      }
236
12.8k
      pos += optionLen;
237
12.8k
      rdataRead += optionLen;
238
12.8k
    }
239
240
444
    if (pos >= querySize) {
241
226
        return true;
242
226
    }
243
244
218
    return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
245
444
  }
246
247
};