Coverage Report

Created: 2023-03-26 07:17

/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})
37
1.14k
  {
38
1.14k
    const size_t packetSize = packet.size();
39
1.14k
    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
0
    const dnsheader_aligned dnsheaderdata(packet.data());
48
1.14k
    const struct dnsheader *dh = dnsheaderdata.get();
49
1.14k
    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= packetSize) {
50
186
      if (packetSize > pos) {
51
186
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
52
186
      }
53
186
      return currentHash;
54
186
    }
55
56
962
    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
962
    pos += 13;
61
62
962
    const uint16_t rdLen = ((static_cast<uint16_t>(packet.at(pos)) * 256) + static_cast<uint16_t>(packet.at(pos + 1)));
63
    /* skip the rd length */
64
    /* already hashed above */
65
962
    pos += 2;
66
67
962
    if (rdLen > (packetSize - pos)) {
68
202
      if (pos < packetSize) {
69
202
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
70
202
      }
71
202
      return currentHash;
72
202
    }
73
74
760
    uint16_t rdataRead = 0;
75
760
    uint16_t optionCode;
76
760
    uint16_t optionLen;
77
78
24.5k
    while (pos < packetSize && rdataRead < rdLen && getNextEDNSOption(&packet.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
79
24.1k
      if (optionLen > (rdLen - rdataRead - 4)) {
80
356
        if (packetSize > pos) {
81
356
          currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
82
356
        }
83
356
        return currentHash;
84
356
      }
85
86
23.7k
      if (optionsToSkip.count(optionCode) == 0) {
87
        /* hash the option code, length and content */
88
22.5k
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4 + optionLen, currentHash);
89
22.5k
      }
90
1.16k
      else {
91
        /* skip option: hash only its code and length */
92
1.16k
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
93
1.16k
      }
94
95
23.7k
      pos += 4 + optionLen;
96
23.7k
      rdataRead += 4 + optionLen;
97
23.7k
    }
98
99
404
    if (pos < packetSize) {
100
282
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
101
282
    }
102
103
404
    return currentHash;
104
760
  }
105
106
  static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
107
1.79k
  {
108
1.79k
    const size_t packetSize = packet.size();
109
1.79k
    assert(packetSize >= sizeof(dnsheader));
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.
114
0
    uint32_t currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, 0); // rest of dnsheader, skip id
115
116
79.5k
    for (pos = sizeof(dnsheader); pos < packetSize; ) {
117
78.9k
      const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
118
78.9k
      ++pos;
119
78.9k
      if (labelLen == 0) {
120
1.22k
        break;
121
1.22k
      }
122
77.7k
      pos = std::min(pos + labelLen, packetSize);
123
77.7k
    }
124
1.79k
    return burtleCI(reinterpret_cast<const unsigned char*>(&packet.at(sizeof(dnsheader))), pos - sizeof(dnsheader), currentHash);
125
1.79k
  }
126
127
  /* hash the packet from the beginning, including the qname. This skips:
128
     - the query ID ;
129
     - EDNS Cookie options, if any ;
130
     - Any given option code present in optionsToSkip
131
  */
132
  static uint32_t canHashPacket(const std::string& packet, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
133
1.79k
  {
134
1.79k
    size_t pos = 0;
135
1.79k
    uint32_t currentHash = hashHeaderAndQName(packet, pos);
136
137
1.79k
    if (pos >= packet.size()) {
138
646
      return currentHash;
139
646
    }
140
141
1.15k
    return hashAfterQname(packet, currentHash, pos, optionsToSkip);
142
1.79k
  }
143
144
  static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
145
1.25k
  {
146
1.25k
    if (cachedQuery.size() != query.size()) {
147
0
      return false;
148
0
    }
149
150
1.25k
    return (cachedQuery.compare(/* skip the ID */ 2, sizeof(dnsheader) - 2, query, 2, sizeof(dnsheader) - 2) == 0);
151
1.25k
  }
152
153
  static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, const std::unordered_set<uint16_t>& optionsToIgnore)
154
1.25k
  {
155
1.25k
    const size_t querySize = query.size();
156
1.25k
    const size_t cachedQuerySize = cachedQuery.size();
157
1.25k
    if (querySize != cachedQuerySize) {
158
0
      return false;
159
0
    }
160
161
1.25k
    if (!queryHeaderMatches(cachedQuery, query)) {
162
0
      return false;
163
0
    }
164
165
1.25k
    size_t pos = sizeof(dnsheader) + qname.wirelength();
166
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
    */
172
1.25k
    const dnsheader_aligned dnsheaderdata(query.data());
173
1.25k
    const struct dnsheader* dh = dnsheaderdata.get();
174
1.25k
    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
175
418
      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
176
418
    }
177
178
    /* compare up to the first option, if any */
179
836
    if (cachedQuery.compare(pos, 15, query, pos, 15) != 0) {
180
0
      return false;
181
0
    }
182
183
    /* skip the qtype (2), qclass (2) */
184
    /* root label (1), type (2), class (2) and ttl (4) */
185
    /* already compared above */
186
836
    pos += 13;
187
188
836
    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
836
    pos += sizeof(uint16_t);
192
193
836
    if (rdLen > (querySize - pos)) {
194
      /* something is wrong, let's just compare everything */
195
206
      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
196
206
    }
197
198
630
    uint16_t rdataRead = 0;
199
630
    uint16_t optionCode;
200
630
    uint16_t optionLen;
201
202
17.3k
    while (pos < querySize && rdataRead < rdLen && getNextEDNSOption(&query.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
203
16.9k
      if (optionLen > (rdLen - rdataRead)) {
204
204
        return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
205
204
      }
206
207
      /* compare the option code and length */
208
16.7k
      if (cachedQuery.compare(pos, 4, query, pos, 4) != 0) {
209
0
        return false;
210
0
      }
211
16.7k
      pos += 4;
212
16.7k
      rdataRead += 4;
213
214
16.7k
      if (optionLen > 0 && optionsToIgnore.count(optionCode) == 0) {
215
7.14k
        if (cachedQuery.compare(pos, optionLen, query, pos, optionLen) != 0) {
216
0
          return false;
217
0
        }
218
7.14k
      }
219
16.7k
      pos += optionLen;
220
16.7k
      rdataRead += optionLen;
221
16.7k
    }
222
223
426
    if (pos >= querySize) {
224
192
        return true;
225
192
    }
226
227
234
    return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
228
426
  }
229
230
};