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

PowerDNS / pdns / 17611467588

10 Sep 2025 10:53AM UTC coverage: 66.01% (+0.03%) from 65.978%
17611467588

Pull #16108

github

web-flow
Merge b6eb1a724 into 29382c4af
Pull Request #16108: dnsdist: implement simple packet shuffle in cache

42255 of 92634 branches covered (45.62%)

Branch coverage included in aggregate %.

9 of 120 new or added lines in 6 files covered. (7.5%)

12 existing lines in 5 files now uncovered.

128490 of 166031 relevant lines covered (77.39%)

5519579.39 hits per line

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

90.2
/pdns/rcpgenerator.cc
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
#ifdef HAVE_CONFIG_H
23
#include "config.h"
24
#endif
25
#include "rcpgenerator.hh"
26
#include "dnsparser.hh"
27
#include "misc.hh"
28
#include "utility.hh"
29
#include <boost/algorithm/string.hpp>
30
#include <boost/algorithm/string/classification.hpp>
31
#include <boost/algorithm/string/replace.hpp>
32
#include <boost/format.hpp>
33

34
#include <iostream>
35
#include "base32.hh"
36
#include "base64.hh"
37
#include "namespaces.hh"
38

39
RecordTextReader::RecordTextReader(string str, ZoneName zone) :
40
  d_string(std::move(str)), d_zone(std::move(zone))
41
{
2,021,826✔
42
   /* remove whitespace */
43
   if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
2,021,827✔
44
     boost::trim_if(d_string, dns_isspace);
3✔
45
   d_end = d_string.size();
2,021,826✔
46
}
2,021,826✔
47

48
void RecordTextReader::xfr48BitInt(uint64_t &val)
49
{
6✔
50
  xfr64BitInt(val);
6✔
51
  if (val > 281474976710655LL)
6!
52
    throw RecordTextException("Overflow reading 48 bit integer from record content"); // fixme improve
×
53
}
6✔
54

55
void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID& val) {
12✔
56
  skipSpaces();
12✔
57
  size_t len;
12✔
58
  for(len=0;
12✔
59
      d_pos+len < d_string.length() && (isxdigit(d_string.at(d_pos+len)) || d_string.at(d_pos+len) == ':');
240!
60
      len++) ;   // find length of ID
228✔
61

62
  // Parse as v6, and then strip the final 64 zero bytes
63
  struct in6_addr tmpbuf;
12✔
64
  string to_parse = d_string.substr(d_pos, len) + ":0:0:0:0";
12✔
65

66
  if (inet_pton(AF_INET6, to_parse.c_str(), &tmpbuf) != 1) {
12!
67
    throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string.substr(d_pos, len) + "' is invalid");
×
68
  }
×
69

70
  std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content));
12✔
71
  d_pos += len;
12✔
72
}
12✔
73

74
void RecordTextReader::xfr64BitInt(uint64_t &val)
75
{
625,986✔
76
  skipSpaces();
625,986✔
77

78
  if(!isdigit(d_string.at(d_pos)))
625,986!
79
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
80

81
  size_t pos;
625,986✔
82
  val=std::stoull(d_string.substr(d_pos), &pos);
625,986✔
83

84
  d_pos += pos;
625,986✔
85
}
625,986✔
86

87

88
void RecordTextReader::xfr32BitInt(uint32_t &val)
89
{
2,360,954✔
90
  skipSpaces();
2,360,954✔
91

92
  if(!isdigit(d_string.at(d_pos)))
2,360,954!
93
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
94

95
  size_t pos;
2,360,954✔
96
  val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos);
2,360,954✔
97

98
  d_pos += pos;
2,360,954✔
99
}
2,360,954✔
100

101
void RecordTextReader::xfrTime(uint32_t &val)
102
{
625,980✔
103
  struct tm tm;
625,980✔
104
  memset(&tm, 0, sizeof(tm));
625,980✔
105

106
  uint64_t itmp;
625,980✔
107
  xfr64BitInt(itmp);
625,980✔
108

109
  if (itmp <= (uint32_t)~0) {
625,980✔
110
    // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
111
    val = (uint32_t) itmp;
6✔
112
    return;
6✔
113
  }
6✔
114

115
  ostringstream tmp;
625,974✔
116

117
  tmp<<itmp;
625,974✔
118

119
  if (sscanf(tmp.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
625,974!
120
             &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
625,974✔
121
             &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
625,974✔
122
    throw RecordTextException("unable to parse '"+std::to_string(itmp)+"' into a valid time at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
123
  }
×
124

125
  tm.tm_year-=1900;
625,974✔
126
  tm.tm_mon-=1;
625,974✔
127
  // coverity[store_truncates_time_t]
128
  val=(uint32_t)Utility::timegm(&tm);
625,974✔
129
}
625,974✔
130

131
void RecordTextReader::xfrIP(uint32_t &val)
132
{
1,291,876✔
133
  skipSpaces();
1,291,876✔
134

135
  if(!isdigit(d_string.at(d_pos)))
1,291,876✔
136
    throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
4✔
137

138
  uint32_t octet=0;
1,291,872✔
139
  val=0;
1,291,872✔
140
  char count=0;
1,291,872✔
141
  bool last_was_digit = false;
1,291,872✔
142

143
  for(;;) {
16,176,482✔
144
    if(d_string.at(d_pos)=='.') {
16,176,482✔
145
      if (!last_was_digit)
3,875,592✔
146
        throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
3✔
147
      last_was_digit = false;
3,875,589✔
148
      val<<=8;
3,875,589✔
149
      val+=octet;
3,875,589✔
150
      octet=0;
3,875,589✔
151
      count++;
3,875,589✔
152
      if(count > 3)
3,875,589✔
153
        throw RecordTextException(string("unable to parse IP address, too many dots"));
3✔
154
    }
3,875,589✔
155
    else if(isdigit(d_string.at(d_pos))) {
12,300,890✔
156
      last_was_digit = true;
12,300,884✔
157
      octet*=10;
12,300,884✔
158
      octet+=d_string.at(d_pos) - '0';
12,300,884✔
159
      if(octet > 255)
12,300,884✔
160
        throw RecordTextException("unable to parse IP address");
6✔
161
    }
12,300,884✔
162
    else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
6!
163
      break;
6✔
164
    else {
×
165
      throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
×
166
    }
×
167
    d_pos++;
16,176,464✔
168
    if(d_pos == d_string.length())
16,176,464✔
169
      break;
1,291,854✔
170
  }
16,176,464✔
171
  if (count != 3)
1,291,860✔
172
    throw RecordTextException(string("unable to parse IP address, not enough dots"));
3✔
173
  if (!last_was_digit)
1,291,857✔
174
    throw RecordTextException(string("unable to parse IP address, trailing dot"));
3✔
175
  val<<=8;
1,291,854✔
176
  val+=octet;
1,291,854✔
177
  val=ntohl(val);
1,291,854✔
178
}
1,291,854✔
179

180

181
void RecordTextReader::xfrIP6(std::string &val)
182
{
6,286✔
183
  struct in6_addr tmpbuf;
6,286✔
184

185
  skipSpaces();
6,286✔
186

187
  size_t len;
6,286✔
188
  // lookup end of value - think of ::ffff encoding too, has dots in it!
189
  for(len=0;
6,286✔
190
      d_pos+len < d_string.length() && (isxdigit(d_string.at(d_pos+len)) || d_string.at(d_pos+len) == ':' || d_string.at(d_pos+len)=='.');
106,400✔
191
    len++);
100,114✔
192

193
  if(!len)
6,286!
194
    throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
195

196
  // end of value is here, try parse as IPv6
197
  string address=d_string.substr(d_pos, len);
6,286✔
198

199
  if (inet_pton(AF_INET6, address.c_str(), &tmpbuf) != 1) {
6,286✔
200
    throw RecordTextException("while parsing IPv6 address: '" + address + "' is invalid");
9✔
201
  }
9✔
202

203
  val = std::string((char*)tmpbuf.s6_addr, 16);
6,277✔
204

205
  d_pos += len;
6,277✔
206
}
6,277✔
207

208
void RecordTextReader::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
209
{
×
210
  if (version == 4) {
×
211
    uint32_t ip;
×
212
    xfrIP(ip);
×
213
    val = makeComboAddressFromRaw(4, string((const char*) &ip, 4));
×
214
  }
×
215
  else if (version == 6) {
×
216
    string ip;
×
217
    xfrIP6(ip);
×
218
    val = makeComboAddressFromRaw(6, ip);
×
219
  }
×
220
  else throw RecordTextException("invalid address family");
×
221
}
×
222

223
void RecordTextReader::xfrCAPort(ComboAddress &val)
224
{
×
225
  uint16_t port;
×
226
  xfr16BitInt(port);
×
227
  val.sin4.sin_port = port;
×
228
}
×
229

230
bool RecordTextReader::eof()
231
{
2,594,389✔
232
  return d_pos==d_end;
2,594,389✔
233
}
2,594,389✔
234

235
void RecordTextReader::xfr16BitInt(uint16_t &val)
236
{
656,018✔
237
  uint32_t tmp;
656,018✔
238
  xfr32BitInt(tmp);
656,018✔
239
  val=tmp;
656,018✔
240
  if(val!=tmp)
656,018!
241
    throw RecordTextException("Overflow reading 16 bit integer from record content"); // fixme improve
×
242
}
656,018✔
243

244
void RecordTextReader::xfr8BitInt(uint8_t &val)
245
{
1,287,689✔
246
  uint32_t tmp;
1,287,689✔
247
  xfr32BitInt(tmp);
1,287,689✔
248
  val=tmp;
1,287,689✔
249
  if(val!=tmp)
1,287,689!
250
    throw RecordTextException("Overflow reading 8 bit integer from record content"); // fixme improve
×
251
}
1,287,689✔
252

253
// this code should leave all the escapes around
254
void RecordTextReader::xfrName(DNSName& val, [[maybe_unused]] bool compress)
255
{
402,588✔
256
  skipSpaces();
402,588✔
257
  DNSName sval;
402,588✔
258

259
  string::size_type begin_pos = d_pos;
402,588✔
260
  while (d_pos < d_end) {
4,763,580✔
261
    if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
4,718,152✔
262
      break;
357,156✔
263
    }
357,156✔
264

265
    d_pos++;
4,360,992✔
266
  }
4,360,992✔
267

268
  {
402,588✔
269
    std::string_view view(d_string);
402,588✔
270
    sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
402,588✔
271
  }
402,588✔
272

273
  if (sval.empty()) {
402,588!
274
    sval = DNSName(d_zone);
×
275
  }
×
276
  else if (!d_zone.empty()) {
402,588✔
277
    sval += DNSName(d_zone);
393,702✔
278
  }
393,702✔
279
  val = std::move(sval);
402,588✔
280
}
402,588✔
281

282
static bool isbase64(char c, bool acceptspace)
283
{
23,940,098✔
284
  if(dns_isspace(c))
23,940,098✔
285
    return acceptspace;
175✔
286
  if(c >= '0' && c <= '9')
23,939,923✔
287
    return true;
3,584,103✔
288
  if(c >= 'a' && c <= 'z')
20,355,820✔
289
    return true;
9,670,768✔
290
  if(c >= 'A' && c <= 'Z')
10,685,052✔
291
    return true;
9,450,441✔
292
  if(c=='+' || c=='/' || c=='=')
1,234,611✔
293
    return true;
1,234,657✔
294
  return false;
2,147,483,668✔
295
}
1,234,611✔
296

297
void RecordTextReader::xfrBlobNoSpaces(string& val, int len) {
39✔
298
  skipSpaces();
39✔
299
  int pos=(int)d_pos;
39✔
300
  const char* strptr=d_string.c_str();
39✔
301
  while(d_pos < d_end && isbase64(strptr[d_pos], false))
951✔
302
    d_pos++;
912✔
303

304
  string tmp;
39✔
305
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
39✔
306
  boost::erase_all(tmp," ");
39✔
307
  val.clear();
39✔
308
  B64Decode(tmp, val);
39✔
309

310
  if (len>-1 && val.size() != static_cast<size_t>(len))
39!
311
    throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len));
×
312
}
39✔
313

314
void RecordTextReader::xfrBlob(string& val, int)
315
{
314,309✔
316
  skipSpaces();
314,309✔
317
  int pos=(int)d_pos;
314,309✔
318
  const char* strptr=d_string.c_str();
314,309✔
319
  while(d_pos < d_end && isbase64(strptr[d_pos], true))
24,253,459✔
320
    d_pos++;
23,939,150✔
321

322
  string tmp;
314,309✔
323
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
314,309✔
324
  boost::erase_all(tmp," ");
314,309✔
325
  val.clear();
314,309✔
326
  B64Decode(tmp, val);
314,309✔
327
}
314,309✔
328

329
void RecordTextReader::xfrRFC1035CharString(string &val) {
353✔
330
  auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
353✔
331
  d_pos += ctr;
353✔
332
}
353✔
333

334
void RecordTextReader::xfrSVCBValueList(vector<string> &val) {
682✔
335
  auto ctr = parseSVCBValueList(d_string.substr(d_pos, d_end - d_pos), val);
682✔
336
  d_pos += ctr;
682✔
337
}
682✔
338

339
void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readability-function-cognitive-complexity)
340
{
778✔
341
  while (d_pos != d_end) {
1,795✔
342
    skipSpaces();
1,116✔
343
    if (d_pos == d_end)
1,116!
344
      return;
×
345

346
    // Find the SvcParamKey
347
    size_t pos = d_pos;
1,116✔
348
    while (d_pos != d_end) {
6,468✔
349
      if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') {
6,432✔
350
        break;
1,080✔
351
      }
1,080✔
352
      d_pos++;
5,352✔
353
    }
5,352✔
354

355
    // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
356
    string k = d_string.substr(pos, d_pos - pos);
1,116✔
357
    SvcParam::SvcParamKey key;
1,116✔
358
    bool generic;
1,116✔
359
    try {
1,116✔
360
      key = SvcParam::keyFromString(k, generic);
1,116✔
361
    } catch (const std::invalid_argument &e) {
1,116✔
362
      throw RecordTextException(e.what());
3✔
363
    }
3✔
364

365
    if (d_pos != d_end && d_string.at(d_pos) == '=') {
1,113✔
366
      d_pos++; // Now on the first character after '='
1,074✔
367
      if (d_pos == d_end || d_string.at(d_pos) == ' ') {
1,074✔
368
        throw RecordTextException("expected value after " + k + "=");
33✔
369
      }
33✔
370
    }
1,074✔
371

372
    switch (key) {
1,080✔
373
    case SvcParam::no_default_alpn:
21✔
374
      if (d_pos != d_end && d_string.at(d_pos) != ' ') {
21✔
375
        throw RecordTextException(k + " key can not have values");
6✔
376
      }
6✔
377
      val.insert(SvcParam(key));
15✔
378
      break;
15✔
379
    case SvcParam::ipv4hint: /* fall-through */
81✔
380
    case SvcParam::ipv6hint: {
138✔
381
      vector<ComboAddress> hints;
138✔
382
      bool doAuto{false};
138✔
383
      if (generic) {
138✔
384
        string value;
18✔
385
        xfrRFC1035CharString(value);
18✔
386
        size_t len = key == SvcParam::ipv4hint ? 4 : 16;
18✔
387
        if (value.size() % len != 0) {
18✔
388
          throw RecordTextException(k + " in generic format has wrong number of bytes");
6✔
389
        }
6✔
390
        for (size_t i=0; i<value.size(); i += len) {
30✔
391
          auto hint = makeComboAddressFromRaw(static_cast<uint8_t>(key), &value.at(i), len);
18✔
392
          hints.push_back(hint);
18✔
393
        }
18✔
394
      } else {
120✔
395
        vector<string> value;
120✔
396
        xfrSVCBValueList(value);
120✔
397
        for (auto const &v: value) {
159✔
398
          if (v == "auto") {
159✔
399
            doAuto = true;
35✔
400
            hints.clear();
35✔
401
            break;
35✔
402
          }
35✔
403
          hints.push_back(ComboAddress(v));
124✔
404
        }
124✔
405
      }
120✔
406
      if (!doAuto && hints.empty()) {
132!
407
        throw RecordTextException("value is required for SVC Param " + k);
×
408
      }
×
409
      try {
132✔
410
        auto p = SvcParam(key, std::move(hints));
132✔
411
        p.setAutoHint(doAuto);
132✔
412
        val.insert(std::move(p));
132✔
413
      }
132✔
414
      catch (const std::invalid_argument& e) {
132✔
415
        throw RecordTextException(e.what());
12✔
416
      }
12✔
417
      break;
120✔
418
    }
132✔
419
    case SvcParam::alpn: {
538✔
420
      vector<string> value;
538✔
421
      if (generic) {
538✔
422
        string v;
15✔
423
        xfrRFC1035CharString(v);
15✔
424
        size_t spos{0}, len;
15✔
425
        while (spos < v.length()) {
30✔
426
          len = v.at(spos);
24✔
427
          spos += 1;
24✔
428
          if (len == 0) {
24✔
429
            throw RecordTextException("ALPN values cannot be empty strings");
3✔
430
          }
3✔
431
          if (len > v.length() - spos) {
21✔
432
            throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
6✔
433
          }
6✔
434
          value.push_back(v.substr(spos, len));
15✔
435
          spos += len;
15✔
436
        }
15✔
437
      } else {
523✔
438
        xfrSVCBValueList(value);
523✔
439
      }
523✔
440
      if (value.empty()) {
529✔
441
        throw RecordTextException("value is required for SVC Param " + k);
3✔
442
      }
3✔
443
      for (const auto &alpn_value : value) {
689✔
444
        if (alpn_value.empty()) {
689!
445
          throw RecordTextException("ALPN values cannot be empty strings");
×
446
        }
×
447
      }
689✔
448
      val.insert(SvcParam(key, std::move(value)));
526✔
449
      break;
526✔
450
    }
526✔
451
    case SvcParam::mandatory: {
48✔
452
      if (generic) {
48✔
453
        string v;
9✔
454
        xfrRFC1035CharString(v);
9✔
455
        if (v.empty()) {
9✔
456
          throw RecordTextException("value is required for SVC Param " + k);
3✔
457
        }
3✔
458
        if (v.length() % 2 != 0) {
6✔
459
          throw RecordTextException("Wrong number of bytes in SVC Param " + k);
3✔
460
        }
3✔
461
        std::set<SvcParam::SvcParamKey> keys;
3✔
462
        for (size_t i=0; i < v.length(); i += 2) {
9✔
463
          uint16_t mand = (v.at(i) << 8);
6✔
464
          mand += v.at(i+1);
6✔
465
          keys.insert(SvcParam::SvcParamKey(mand));
6✔
466
        }
6✔
467
        val.insert(SvcParam(key, std::move(keys)));
3✔
468
        break;
3✔
469
      }
6✔
470
      vector<string> parts;
39✔
471
      xfrSVCBValueList(parts);
39✔
472
      if (parts.empty()) {
39!
473
        throw RecordTextException("value is required for SVC Param " + k);
×
474
      }
×
475
      set<string> values(parts.begin(), parts.end());
39✔
476
      val.insert(SvcParam(key, std::move(values)));
39✔
477
      break;
39✔
478
    }
39✔
479
    case SvcParam::port: {
260✔
480
      uint16_t port;
260✔
481
      if (generic) {
260✔
482
        string v;
6✔
483
        xfrRFC1035CharString(v);
6✔
484
        if (v.length() != 2) {
6✔
485
          throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v.length()));
3✔
486
        }
3✔
487
        port = (v.at(0) << 8);
3✔
488
        port += v.at(1);
3✔
489
      } else {
254✔
490
        string portstring;
254✔
491
        xfrRFC1035CharString(portstring);
254✔
492
        if (portstring.empty()) {
254✔
493
          throw RecordTextException("value is required for SVC Param " + k);
3✔
494
        }
3✔
495
        try {
251✔
496
          pdns::checked_stoi_into(port, portstring);
251✔
497
        } catch (const std::exception &e) {
251✔
498
          throw RecordTextException(e.what());
6✔
499
        }
6✔
500
      }
251✔
501
      val.insert(SvcParam(key, port));
248✔
502
      break;
248✔
503
    }
260✔
504
    case SvcParam::ech: {
30✔
505
      string value;
30✔
506
      if (generic) {
30✔
507
        xfrRFC1035CharString(value);
6✔
508
      } else {
24✔
509
        bool haveQuote = d_string.at(d_pos) == '"';
24✔
510
        if (haveQuote) {
24✔
511
          d_pos++;
21✔
512
        }
21✔
513
        xfrBlobNoSpaces(value);
24✔
514
        if (haveQuote) {
24✔
515
          if (d_string.at(d_pos) != '"') {
21!
516
            throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
×
517
          }
×
518
          d_pos++;
21✔
519
        }
21✔
520
      }
24✔
521
      if (value.empty()) {
30✔
522
        throw RecordTextException("value is required for SVC Param " + k);
6✔
523
      }
6✔
524
      val.insert(SvcParam(key, value));
24✔
525
      break;
24✔
526
    }
30✔
527
    default: {
45✔
528
      string value;
45✔
529
      xfrRFC1035CharString(value);
45✔
530
      if (!generic && value.empty()) {
45!
531
        // for generic format, we do not know.
532
        // Known keys which forbid having a value need to implement a switch case, above.
533
        throw RecordTextException("value is required for SVC Param " + k);
×
534
      }
×
535
      val.insert(SvcParam(key, value));
45✔
536
      break;
45✔
537
    }
45✔
538
    }
1,080✔
539
  }
1,080✔
540
}
778✔
541

542
static inline uint8_t hextodec(uint8_t val)
543
{
1,695,134✔
544
  if(val >= '0' && val<='9')
1,695,134!
545
    return val-'0';
324,298✔
546
  else if(val >= 'A' && val<='F')
1,370,837✔
547
    return 10+(val-'A');
9,166✔
548
  else if(val >= 'a' && val<='f')
1,361,671✔
549
    return 10+(val-'a');
1,361,670✔
UNCOV
550
  else
×
UNCOV
551
    throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
×
552
}
1,695,134✔
553

554

555
static void HEXDecode(const char* begin, const char* end, string& out)
556
{
329,489✔
557
  if(end - begin == 1 && *begin=='-') {
329,489!
558
    out.clear();
35✔
559
    return;
35✔
560
  }
35✔
561
  out.clear();
329,454✔
562
  out.reserve((end-begin)/2);
329,454✔
563
  uint8_t mode=0, val=0;
329,454✔
564
  for(; begin != end; ++begin) {
2,024,758✔
565
    if(!isalnum(*begin))
1,695,304✔
566
      continue;
170✔
567
    if(mode==0) {
1,695,134✔
568
      val = 16*hextodec(*begin);
847,569✔
569
      mode=1;
847,569✔
570
    } else {
847,569✔
571
      val += hextodec(*begin);
847,565✔
572
      out.append(1, (char) val);
847,565✔
573
      mode = 0;
847,565✔
574
      val = 0;
847,565✔
575
    }
847,565✔
576
  }
1,695,134✔
577
  if(mode)
329,454✔
578
    throw RecordTextException("Hexadecimal blob with odd number of characters");
3✔
579
}
329,454✔
580

581
void RecordTextReader::xfrHexBlob(string& val, bool keepReading)
582
{
329,489✔
583
  skipSpaces();
329,489✔
584
  int pos=(int)d_pos;
329,489✔
585
  while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos])))
2,024,829✔
586
    d_pos++;
1,695,340✔
587

588
  HEXDecode(d_string.c_str()+pos, d_string.c_str() + d_pos, val);
329,489✔
589
}
329,489✔
590

591
void RecordTextReader::xfrBase32HexBlob(string& val)
592
{
282,589✔
593
  skipSpaces();
282,589✔
594
  int pos=(int)d_pos;
282,589✔
595
  while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
8,365,293✔
596
    d_pos++;
8,082,704✔
597

598
  val=fromBase32Hex(string(d_string.c_str()+pos, d_pos-pos));
282,589✔
599
}
282,589✔
600

601

602
void RecordTextWriter::xfrBase32HexBlob(const string& val)
603
{
261,790✔
604
  if(!d_string.empty())
261,790!
605
    d_string.append(1,' ');
261,790✔
606

607
  d_string.append(toUpper(toBase32Hex(val)));
261,790✔
608
}
261,790✔
609

610

611
void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
612
{
11,201✔
613
  val.clear();
11,201✔
614
  val.reserve(d_end - d_pos);
11,201✔
615

616
  while (d_pos != d_end) {
18,628✔
617
    if (!val.empty()) {
12,552✔
618
      val.append(1, ' ');
1,353✔
619
    }
1,353✔
620

621
    skipSpaces();
12,552✔
622
    char delimiter{'"'};
12,552✔
623
    bool quoted = d_string[d_pos] == '"';
12,552✔
624
    // If the word is quoted, process up to the next quote; otherwise,
625
    // process up to the next whitespace (but output it in quotes).
626
    val.append(1, '"');
12,552✔
627
    if (quoted) {
12,552✔
628
      ++d_pos;
12,484✔
629
    }
12,484✔
630
    else {
68✔
631
      // RFC1035: ``a contiguous set of characters without interior spaces''
632
      delimiter = ' ';
68✔
633
    }
68✔
634
    while (d_pos != d_end && d_string[d_pos] != delimiter) {
425,006✔
635
      if (d_string[d_pos] == '\\' && d_pos + 1 != d_end) {
412,464!
636
        val.append(1, d_string[d_pos++]); // copy escape slash
2,078✔
637
        char chr = d_string[d_pos];
2,078✔
638
        if (chr >= '0' && chr <= '9') {
2,078✔
639
          bool valid{false};
1,274✔
640
          // Must be a three-digit character escape sequence
641
          if (d_end - d_pos >= 3) {
1,274!
642
            char chr2 = d_string[d_pos + 1];
1,274✔
643
            char chr3 = d_string[d_pos + 2];
1,274✔
644
            if (chr2 >= '0' && chr2 <= '9' && chr3 >= '0' && chr3 <= '9') {
1,274!
645
              valid = 100 * (chr - '0') + 10 * (chr2 - '0') + chr3 - '0' < 256;
1,271✔
646
            }
1,271✔
647
          }
1,274✔
648
          if (!valid) {
1,274✔
649
            throw RecordTextException("Data field in DNS contains an invalid escape at position "+std::to_string(d_pos)+" of '"+d_string+"'");
6✔
650
          }
6✔
651
        }
1,274✔
652
        // Not advancing d_pos, we'll append the next 1 or 3 characters as
653
        // part of the regular case.
654
      }
2,078✔
655
      if (!quoted && d_string[d_pos] == '"') {
412,458✔
656
        // Bind allows a non-quoted text to be immediately followed by a
657
        // quoted text, without any whitespace in between, so handle this
658
        // as a delimiter.
659
        break;
4✔
660
      }
4✔
661
      val.append(1, d_string[d_pos]);
412,454✔
662
      ++d_pos;
412,454✔
663
    }
412,454✔
664
    val.append(1,'"');
12,546✔
665
    if (quoted) {
12,546✔
666
      // If we reached the end in a quoted section, the closing quote is missing.
667
      if (d_pos == d_end) {
12,484!
668
        throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'");
×
669
      }
×
670
      // Skip closing quote
671
      ++d_pos;
12,484✔
672
    }
12,484✔
673
    if (!multi) {
12,546✔
674
      break;
5,119✔
675
    }
5,119✔
676
  }
12,546✔
677
}
11,201✔
678

679
void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
680
{
21✔
681
  val.clear();
21✔
682
  val.reserve(d_end - d_pos);
21✔
683

684
  if(!val.empty())
21!
685
    val.append(1, ' ');
×
686

687
  skipSpaces();
21✔
688
  val.append(1, d_string[d_pos]);
21✔
689
  while(++d_pos < d_end && d_string[d_pos] != ' '){
105!
690
    val.append(1, d_string[d_pos]);
84✔
691
  }
84✔
692
}
21✔
693

694
void RecordTextReader::xfrType(uint16_t& val)
695
{
925,488✔
696
  skipSpaces();
925,488✔
697
  int pos=(int)d_pos;
925,488✔
698
  while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
3,830,782✔
699
    d_pos++;
2,905,294✔
700

701
  string tmp;
925,488✔
702
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
925,488✔
703

704
  val=DNSRecordContent::TypeToNumber(tmp);
925,488✔
705
}
925,488✔
706

707

708
void RecordTextReader::skipSpaces()
709
{
6,553,299✔
710
  const char* strptr = d_string.c_str();
6,553,299✔
711
  while(d_pos < d_end && dns_isspace(strptr[d_pos]))
11,091,423✔
712
    d_pos++;
4,538,124✔
713
  if(d_pos == d_end)
6,553,299!
714
    throw RecordTextException("missing field at the end of record content '"+d_string+"'");
×
715
}
6,553,299✔
716

717

718
RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
719
{
2,734,793✔
720
  d_string.clear();
2,734,793✔
721
  d_nodot=noDot;
2,734,793✔
722
}
2,734,793✔
723

724
void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
725
{
21✔
726
  if(!d_string.empty()) {
21✔
727
    d_string.append(1,' ');
18✔
728
  }
18✔
729

730
  size_t ctr = 0;
21✔
731
  char tmp[5];
21✔
732
  for (auto const &c : val.content) {
168✔
733
    snprintf(tmp, sizeof(tmp), "%02X", c);
168✔
734
    d_string+=tmp;
168✔
735
    ctr++;
168✔
736
    if (ctr % 2 == 0 && ctr != 8) {
168✔
737
      d_string+=':';
63✔
738
    }
63✔
739
  }
168✔
740
}
21✔
741

742
void RecordTextWriter::xfr48BitInt(const uint64_t& val)
743
{
81✔
744
  if(!d_string.empty())
81!
745
    d_string.append(1,' ');
81✔
746
  d_string+=std::to_string(val);
81✔
747
}
81✔
748

749

750
void RecordTextWriter::xfr32BitInt(const uint32_t& val)
751
{
4,457,784✔
752
  if(!d_string.empty())
4,457,784✔
753
    d_string.append(1,' ');
4,167,749✔
754
  d_string+=std::to_string(val);
4,457,784✔
755
}
4,457,784✔
756

757
void RecordTextWriter::xfrType(const uint16_t& val)
758
{
873,874✔
759
  if(!d_string.empty())
873,874!
760
    d_string.append(1,' ');
×
761
  d_string+=DNSRecordContent::NumberToType(val);
873,874✔
762
}
873,874✔
763

764
// this function is on the fast path for the pdns_recursor
765
void RecordTextWriter::xfrIP(const uint32_t& val)
766
{
1,225,506✔
767
  if(!d_string.empty())
1,225,506✔
768
    d_string.append(1,' ');
18✔
769

770
  char tmp[17];
1,225,506✔
771
  uint32_t ip=val;
1,225,506✔
772
  uint8_t vals[4];
1,225,506✔
773

774
  memcpy(&vals[0], &ip, sizeof(ip));
1,225,506✔
775

776
  char *pos=tmp;
1,225,506✔
777

778
  for(int n=0; n < 4; ++n) {
6,127,530✔
779
    if(vals[n]<10) {
4,902,024✔
780
      *(pos++)=vals[n]+'0';
1,308,035✔
781
    } else if(vals[n] < 100) {
3,598,157✔
782
      *(pos++)=(vals[n]/10) +'0';
487,379✔
783
      *(pos++)=(vals[n]%10) +'0';
487,379✔
784
    } else {
3,106,610✔
785
      *(pos++)=(vals[n]/100) +'0';
3,106,610✔
786
      vals[n]%=100;
3,106,610✔
787
      *(pos++)=(vals[n]/10) +'0';
3,106,610✔
788
      *(pos++)=(vals[n]%10) +'0';
3,106,610✔
789
    }
3,106,610✔
790
    if(n!=3)
4,902,024✔
791
      *(pos++)='.';
3,676,518✔
792
  }
4,902,024✔
793
  *pos=0;
1,225,506✔
794
  d_string.append(tmp, pos);
1,225,506✔
795
}
1,225,506✔
796

797
void RecordTextWriter::xfrIP6(const std::string& val)
798
{
2,582✔
799
  char tmpbuf[16];
2,582✔
800
  char addrbuf[40];
2,582✔
801

802
  if(!d_string.empty())
2,582✔
803
   d_string.append(1,' ');
12✔
804

805
  val.copy(tmpbuf,16);
2,582✔
806

807
  if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
2,582!
808
    throw RecordTextException("Unable to convert to ipv6 address");
×
809

810
  d_string += std::string(addrbuf);
2,582✔
811
}
2,582✔
812

813
void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
814
{
×
815
  string ip = val.toString();
×
816

817
  if(!d_string.empty())
×
818
    d_string.append(1,' ');
×
819

820
  d_string += ip;
×
821
}
×
822

823
void RecordTextWriter::xfrCAPort(ComboAddress &val)
824
{
×
825
  xfr16BitInt(val.sin4.sin_port);
×
826
}
×
827

828
void RecordTextWriter::xfrTime(const uint32_t& val)
829
{
1,747,748✔
830
  if(!d_string.empty())
1,747,748!
831
    d_string.append(1,' ');
1,747,748✔
832

833
  struct tm tm;
1,747,748✔
834
  time_t time=val; // Y2038 bug!
1,747,748✔
835
  gmtime_r(&time, &tm);
1,747,748✔
836

837
  static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d");
1,747,748✔
838
  d_string += boost::str(boost::format(fmt) % (tm.tm_year+1900) % (tm.tm_mon+1) % tm.tm_mday % tm.tm_hour % tm.tm_min % tm.tm_sec);
1,747,748✔
839
}
1,747,748✔
840

841

842
void RecordTextWriter::xfr16BitInt(const uint16_t& val)
843
{
1,174,597✔
844
  xfr32BitInt(val);
1,174,597✔
845
}
1,174,597✔
846

847
void RecordTextWriter::xfr8BitInt(const uint8_t& val)
848
{
2,291,786✔
849
  xfr32BitInt(val);
2,291,786✔
850
}
2,291,786✔
851

852
// should not mess with the escapes
853
void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */)
854
{
1,100,440✔
855
  if(!d_string.empty())
1,100,440✔
856
    d_string.append(1,' ');
915,085✔
857

858
  if(d_nodot) {
1,100,440✔
859
    d_string+=val.toStringRootDot();
721,261✔
860
  }
721,261✔
861
  else
379,179✔
862
  {
379,179✔
863
    d_string+=val.toString();
379,179✔
864
  }
379,179✔
865
}
1,100,440✔
866

867
void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
868
{
131✔
869
  xfrBlob(val, size);
131✔
870
}
131✔
871

872
void RecordTextWriter::xfrBlob(const string& val, int)
873
{
901,228✔
874
  if(!d_string.empty())
901,228✔
875
    d_string.append(1,' ');
878,387✔
876

877
  d_string+=Base64Encode(val);
901,228✔
878
}
901,228✔
879

880
void RecordTextWriter::xfrHexBlob(const string& val, bool)
881
{
267,488✔
882
  if(!d_string.empty())
267,488!
883
    d_string.append(1,' ');
267,488✔
884

885
  if(val.empty()) {
267,488✔
886
    d_string.append(1,'-');
7✔
887
    return;
7✔
888
  }
7✔
889

890
  string::size_type limit=val.size();
267,481✔
891
  char tmp[5];
267,481✔
892
  for(string::size_type n = 0; n < limit; ++n) {
930,966✔
893
    snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
663,485✔
894
    d_string+=tmp;
663,485✔
895
  }
663,485✔
896
}
267,481✔
897

898
void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) {
1,135✔
899
  bool shouldQuote{false};
1,135✔
900
  vector<string> escaped;
1,135✔
901
  escaped.reserve(val.size());
1,135✔
902
  for (auto const &v : val) {
1,415✔
903
    if (v.find_first_of(' ') != string::npos) {
1,415✔
904
      shouldQuote = true;
6✔
905
    }
6✔
906
    string tmp = txtEscape(v);
1,415✔
907
    string unescaped;
1,415✔
908
    unescaped.reserve(tmp.size() + 4);
1,415✔
909
    for (auto const &ch : tmp) {
3,040✔
910
      if (ch == '\\') {
3,040✔
911
        unescaped += R"F(\\)F";
36✔
912
        continue;
36✔
913
      }
36✔
914
      if (ch == ',') {
3,004✔
915
        unescaped += R"F(\\,)F";
30✔
916
        continue;
30✔
917
      }
30✔
918
      unescaped += ch;
2,974✔
919
    }
2,974✔
920
    escaped.push_back(std::move(unescaped));
1,415✔
921
  }
1,415✔
922
  if (shouldQuote) {
1,135✔
923
    d_string.append(1, '"');
6✔
924
  }
6✔
925
  d_string.append(boost::join(escaped, ","));
1,135✔
926
  if (shouldQuote) {
1,135✔
927
    d_string.append(1, '"');
6✔
928
  }
6✔
929
}
1,135✔
930

931
void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
1,279✔
932
  for (auto const &param : val) {
1,945✔
933
    if (!d_string.empty())
1,945✔
934
      d_string.append(1, ' ');
1,900✔
935

936
    d_string.append(SvcParam::keyToString(param.getKey()));
1,945✔
937
    if (param.getKey() != SvcParam::no_default_alpn) {
1,945✔
938
      d_string.append(1, '=');
1,936✔
939
    }
1,936✔
940

941
    switch (param.getKey())
1,945✔
942
    {
1,945✔
943
    case SvcParam::no_default_alpn:
9✔
944
      break;
9✔
945
    case SvcParam::ipv4hint: /* fall-through */
53✔
946
    case SvcParam::ipv6hint:
80✔
947
      // TODO use xfrCA and put commas in between?
948
      if (param.getAutoHint()) {
80!
949
        d_string.append("auto");
×
950
        break;
×
951
      }
×
952
      d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
80✔
953
      break;
80✔
954
    case SvcParam::alpn:
1,135✔
955
      xfrSVCBValueList(param.getALPN());
1,135✔
956
      break;
1,135✔
957
    case SvcParam::mandatory:
53✔
958
    {
53✔
959
      bool doComma = false;
53✔
960
      for (auto const &k: param.getMandatory()) {
62✔
961
        if (doComma)
62✔
962
          d_string.append(1, ',');
9✔
963
        d_string.append(SvcParam::keyToString(k));
62✔
964
        doComma = true;
62✔
965
      }
62✔
966
      break;
53✔
967
    }
80✔
968
    case SvcParam::port: {
588✔
969
      auto str = d_string;
588✔
970
      d_string.clear();
588✔
971
      xfr16BitInt(param.getPort());
588✔
972
      d_string = str + d_string;
588✔
973
      break;
588✔
974
    }
80✔
975
    case SvcParam::ech: {
32✔
976
      auto str = d_string;
32✔
977
      d_string.clear();
32✔
978
      xfrBlobNoSpaces(param.getECH());
32✔
979
      d_string = str + '"' + d_string + '"';
32✔
980
      break;
32✔
981
    }
80✔
982
    default:
48✔
983
      auto str = d_string;
48✔
984
      d_string.clear();
48✔
985
      xfrText(param.getValue(), false, false);
48✔
986
      d_string = str + '"' + txtEscape(d_string) + '"';
48✔
987
      break;
48✔
988
    }
1,945✔
989
  }
1,945✔
990
}
1,279✔
991

992
void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
993
{
144,709✔
994
  if(!d_string.empty())
144,709✔
995
    d_string.append(1,' ');
12,170✔
996

997
  if (val.empty()) {
144,709!
998
    d_string.append(2, '"');
×
999
  }
×
1000
  else {
144,709✔
1001
    d_string.append(val);
144,709✔
1002
  }
144,709✔
1003
}
144,709✔
1004

1005
void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
1006
{
42✔
1007
  if(!d_string.empty())
42!
1008
    d_string.append(1,' ');
42✔
1009
  d_string.append(val);
42✔
1010
}
42✔
1011

1012
#ifdef TESTING
1013

1014
int main(int argc, char**argv)
1015
try
1016
{
1017
  RecordTextReader rtr(argv[1], argv[2]);
1018

1019
  unsigned int order, pref;
1020
  string flags, services, regexp, replacement;
1021
  string mx;
1022

1023
  rtr.xfrInt(order);
1024
  rtr.xfrInt(pref);
1025
  rtr.xfrText(flags);
1026
  rtr.xfrText(services);
1027
  rtr.xfrText(regexp);
1028
  rtr.xfrName(replacement);
1029

1030
  cout<<"order: "<<order<<", pref: "<<pref<<"\n";
1031
  cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n";
1032

1033
  string out;
1034
  RecordTextWriter rtw(out);
1035

1036
  rtw.xfrInt(order);
1037
  rtw.xfrInt(pref);
1038
  rtw.xfrText(flags);
1039
  rtw.xfrText(services);
1040
  rtw.xfrText(regexp);
1041
  rtw.xfrName(replacement);
1042

1043
  cout<<"Regenerated: '"<<out<<"'\n";
1044

1045
}
1046
catch(std::exception& e)
1047
{
1048
  cerr<<"Fatal: "<<e.what()<<endl;
1049
}
1050

1051
#endif
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