• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.28
/pdns/packetcache.hh
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
  {
6,056,117✔
38
    const size_t packetSize = packet.size();
6,056,117✔
39
    assert(packetSize >= sizeof(dnsheader));
6,056,117✔
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
    const dnsheader_aligned dnsheaderdata(packet.data());
5,325,564✔
48
    const struct dnsheader *dh = dnsheaderdata.get();
6,143,142✔
49
    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) > packetSize) {
6,189,342✔
50
      if (packetSize > pos) {
6,161,448✔
51
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
6,149,054✔
52
      }
6,149,054✔
53
      return currentHash;
6,161,405✔
54
    }
6,161,405✔
55

56
    if (payloadRanks.empty()) {
8,589,961,147✔
57
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
26,586✔
58
    }
26,586✔
59
    else {
8,589,934,595✔
60
      std::vector<unsigned char> optrr(packet.begin() + pos, packet.begin() + pos + 15);
8,589,934,595✔
61
      uint16_t bufSize = optrr.at(7) * 256 + optrr.at(8);
8,589,934,595✔
62
      auto it = std::upper_bound(payloadRanks.begin(), payloadRanks.end(), bufSize);
8,589,934,595✔
63
      if (it != payloadRanks.begin()) {
8,589,934,595✔
64
        it--;
7✔
65
        optrr[7] = (*it) >> 8;
7✔
66
        optrr[8] = (*it) & 0xff;
7✔
67
      }
7✔
68
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&optrr.at(0)), 15, currentHash);
8,589,934,595✔
69
    }
8,589,934,595✔
70
    if ( (pos + 15) == packetSize ) {
8,589,961,147✔
71
      return currentHash;
25,899✔
72
    }
25,899✔
73

74
    /* skip the qtype (2), qclass (2) */
75
    /* root label (1), type (2), class (2) and ttl (4) */
76
    /* already hashed above */
77
    pos += 13;
8,589,935,260✔
78

79
    const uint16_t rdLen = ((static_cast<uint16_t>(packet.at(pos)) * 256) + static_cast<uint16_t>(packet.at(pos + 1)));
8,589,935,260✔
80
    /* skip the rd length */
81
    /* already hashed above */
82
    pos += 2;
8,589,935,260✔
83

84
    if (rdLen > (packetSize - pos)) {
8,589,935,260!
85
      if (pos < packetSize) {
×
86
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
×
87
      }
×
88
      return currentHash;
×
89
    }
×
90

91
    uint16_t rdataRead = 0;
8,589,935,260✔
92
    uint16_t optionCode;
8,589,935,260✔
93
    uint16_t optionLen;
8,589,935,260✔
94

95
    while (pos < packetSize && rdataRead < rdLen && getNextEDNSOption(&packet.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
8,589,935,954!
96
      if (optionLen > (rdLen - rdataRead - 4)) {
720!
97
        if (packetSize > pos) {
×
98
          currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
×
99
        }
×
100
        return currentHash;
×
101
      }
×
102

103
      if (optionsToSkip.count(optionCode) == 0) {
720✔
104
        /* hash the option code, length and content */
105
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4 + optionLen, currentHash);
100✔
106
      }
100✔
107
      else {
620✔
108
        /* skip option: hash only its code and length */
109
        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
620✔
110
      }
620✔
111

112
      pos += 4 + optionLen;
720✔
113
      rdataRead += 4 + optionLen;
720✔
114
    }
720✔
115

116
    if (pos < packetSize) {
8,589,935,260!
117
      currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
×
118
    }
×
119

120
    return currentHash;
8,589,935,260✔
121
  }
8,589,935,260✔
122

123
  static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
124
  {
820,622✔
125
    const size_t packetSize = packet.size();
820,622✔
126
    assert(packetSize >= sizeof(dnsheader));
820,622✔
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
    uint32_t currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, 0); // rest of dnsheader, skip id
×
132

133
    for (pos = sizeof(dnsheader); pos < packetSize; ) {
2,464,746✔
134
      const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
2,464,744✔
135
      ++pos;
2,464,744✔
136
      if (labelLen == 0) {
2,464,744✔
137
        break;
822,354✔
138
      }
822,354✔
139
      pos = std::min(pos + labelLen, packetSize);
1,642,390✔
140
    }
1,642,390✔
141
    return burtleCI(reinterpret_cast<const unsigned char*>(&packet.at(sizeof(dnsheader))), pos - sizeof(dnsheader), currentHash);
820,622✔
142
  }
820,622✔
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
  {
829,468✔
151
    size_t pos = 0;
829,468✔
152
    uint32_t currentHash = hashHeaderAndQName(packet, pos);
829,468✔
153

154
    if (pos >= packet.size()) {
829,468!
155
      return currentHash;
×
156
    }
×
157

158
    return hashAfterQname(packet, currentHash, pos, optionsToSkip);
829,468✔
159
  }
829,468✔
160

161
  static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
162
  {
410,457✔
163
    if (cachedQuery.size() != query.size()) {
410,457!
164
      return false;
×
165
    }
×
166

167
    return (cachedQuery.compare(/* skip the ID */ 2, sizeof(dnsheader) - 2, query, 2, sizeof(dnsheader) - 2) == 0);
410,457✔
168
  }
410,457✔
169

170
  static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, const std::unordered_set<uint16_t>& optionsToIgnore)
171
  {
411,372✔
172
    const size_t querySize = query.size();
411,372✔
173
    const size_t cachedQuerySize = cachedQuery.size();
411,372✔
174
    if (querySize != cachedQuerySize) {
411,372!
175
      return false;
×
176
    }
×
177

178
    if (!queryHeaderMatches(cachedQuery, query)) {
411,372!
179
      return false;
×
180
    }
×
181

182
    size_t pos = sizeof(dnsheader) + qname.wirelength();
411,372✔
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
    const dnsheader_aligned dnsheaderdata(query.data());
411,372✔
190
    const struct dnsheader* dh = dnsheaderdata.get();
411,372✔
191
    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
411,376✔
192
      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
409,394✔
193
    }
409,394✔
194

195
    /* compare up to the first option, if any */
196
    if (cachedQuery.compare(pos, 15, query, pos, 15) != 0) {
2,147,485,625✔
197
      return false;
3✔
198
    }
3✔
199

200
    /* skip the qtype (2), qclass (2) */
201
    /* root label (1), type (2), class (2) and ttl (4) */
202
    /* already compared above */
203
    pos += 13;
2,147,485,622✔
204

205
    const uint16_t rdLen = ((static_cast<unsigned char>(query.at(pos)) * 256) + static_cast<unsigned char>(query.at(pos + 1)));
2,147,485,622✔
206
    /* skip the rd length */
207
    /* already compared above */
208
    pos += sizeof(uint16_t);
2,147,485,622✔
209

210
    if (rdLen > (querySize - pos)) {
2,147,485,622!
211
      /* something is wrong, let's just compare everything */
212
      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
×
213
    }
×
214

215
    uint16_t rdataRead = 0;
2,147,485,622✔
216
    uint16_t optionCode;
2,147,485,622✔
217
    uint16_t optionLen;
2,147,485,622✔
218

219
    while (pos < querySize && rdataRead < rdLen && getNextEDNSOption(&query.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
2,147,485,724!
220
      if (optionLen > (rdLen - rdataRead)) {
108!
221
        return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
×
222
      }
×
223

224
      /* compare the option code and length */
225
      if (cachedQuery.compare(pos, 4, query, pos, 4) != 0) {
108!
226
        return false;
×
227
      }
×
228
      pos += 4;
108✔
229
      rdataRead += 4;
108✔
230

231
      if (optionLen > 0 && optionsToIgnore.count(optionCode) == 0) {
108!
232
        if (cachedQuery.compare(pos, optionLen, query, pos, optionLen) != 0) {
10✔
233
          return false;
6✔
234
        }
6✔
235
      }
10✔
236
      pos += optionLen;
102✔
237
      rdataRead += optionLen;
102✔
238
    }
102✔
239

240
    if (pos >= querySize) {
2,147,485,616✔
241
        return true;
91✔
242
    }
91✔
243

244
    return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
2,147,485,525✔
245
  }
2,147,485,616✔
246

247
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc