Coverage Report

Created: 2024-02-25 06:29

/src/PcapPlusPlus/Packet++/header/DnsLayer.h
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#include "DnsLayerEnums.h"
4
#include "DnsResource.h"
5
#include "DnsResourceData.h"
6
#include "Layer.h"
7
8
/// @file
9
10
/**
11
 * \namespace pcpp
12
 * \brief The main namespace for the PcapPlusPlus lib
13
 */
14
namespace pcpp
15
{
16
17
  /**
18
   * @struct dnshdr
19
   * Represents the fixed part of the DNS header, meaning the part that doesn't include the DNS data (queries, answers, authorities
20
   * and additional records)
21
   */
22
#pragma pack(push, 1)
23
  struct dnshdr
24
  {
25
    /** DNS query identification */
26
    uint16_t transactionID;
27
#if (BYTE_ORDER == LITTLE_ENDIAN)
28
    uint16_t
29
    /** Recursion desired flag */
30
      recursionDesired:1,
31
    /** Truncated flag */
32
      truncation:1,
33
    /** Authoritative answer flag */
34
      authoritativeAnswer:1,
35
    /** Operation Code */
36
      opcode:4,
37
    /** Query/Response flag */
38
      queryOrResponse:1,
39
    /** Return Code */
40
      responseCode:4,
41
    /** Checking disabled flag */
42
      checkingDisabled:1,
43
    /** Authenticated data flag */
44
      authenticData:1,
45
    /** Zero flag (Reserved) */
46
      zero:1,
47
    /** Recursion available flag */
48
      recursionAvailable:1;
49
#elif (BYTE_ORDER == BIG_ENDIAN)
50
    uint16_t
51
    /** Query/Response flag */
52
      queryOrResponse:1,
53
    /** Operation Code */
54
      opcode:4,
55
    /** Authoritative answer flag */
56
      authoritativeAnswer:1,
57
    /** Truncated flag */
58
      truncation:1,
59
    /** Recursion desired flag */
60
      recursionDesired:1,
61
    /** Recursion available flag */
62
      recursionAvailable:1,
63
    /** Zero flag (Reserved) */
64
      zero:1,
65
    /** Authenticated data flag */
66
      authenticData:1,
67
    /** Checking disabled flag */
68
      checkingDisabled:1,
69
    /** Return Code */
70
      responseCode:4;
71
#endif
72
    /** Number of DNS query records in packet */
73
    uint16_t numberOfQuestions;
74
    /** Number of DNS answer records in packet */
75
    uint16_t numberOfAnswers;
76
    /** Number of authority records in packet */
77
    uint16_t numberOfAuthority;
78
    /** Number of additional records in packet */
79
    uint16_t numberOfAdditional;
80
  };
81
#pragma pack(pop)
82
83
84
  // forward declarations
85
  class DnsQuery;
86
  class IDnsResource;
87
  class DnsResource;
88
  class IDnsResourceData;
89
90
91
  /**
92
   * @class DnsLayer
93
   * Represents the DNS protocol layer
94
   */
95
  class DnsLayer : public Layer
96
  {
97
    friend class IDnsResource;
98
    friend class DnsQuery;
99
    friend class DnsResource;
100
101
  public:
102
103
    /**
104
     * A constructor that creates the layer from an existing packet raw data
105
     * @param[in] data A pointer to the raw data
106
     * @param[in] dataLen Size of the data in bytes
107
     * @param[in] prevLayer A pointer to the previous layer
108
     * @param[in] packet A pointer to the Packet instance where layer will be stored in
109
     */
110
    DnsLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);
111
112
    /**
113
     * A constructor that creates an empty DNS layer: all members of dnshdr are set to 0 and layer will contain no records
114
     */
115
    DnsLayer();
116
117
    /**
118
     * A copy constructor for this layer
119
     * @param[in] other The DNS layer to copy from
120
     */
121
    DnsLayer(const DnsLayer& other);
122
123
    /**
124
     * An assignment operator for this layer
125
     * @param[in] other The DNS layer to assign
126
     * @return A reference to the assignee
127
     */
128
    DnsLayer& operator=(const DnsLayer& other);
129
130
    virtual ~DnsLayer();
131
132
    /**
133
     * Get a pointer to the DNS header (as opposed to the DNS data which is the queries, answers, etc. Data can be retrieved through the
134
     * other methods of this layer. Notice the return value points directly to the data, so every change will change the actual packet data
135
     * @return A pointer to the @ref dnshdr
136
     */
137
    dnshdr* getDnsHeader() const;
138
139
    /**
140
     * Searches for a DNS query by its name field. Notice this method returns only a query which its name equals to the requested name. If
141
     * several queries match the requested name, the first one will be returned. If no queries match the requested name, NULL will be returned
142
     * @param[in] name The name of the query to search
143
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
144
     * @return The first matching DNS query or NULL if no queries were found
145
     */
146
    DnsQuery* getQuery(const std::string& name, bool exactMatch) const;
147
148
    /**
149
     * @return The first DNS query in the packet or NULL if packet doesn't contain any queries
150
     */
151
    DnsQuery* getFirstQuery() const;
152
153
    /**
154
     * Get the DNS query following a certain query
155
     * @param[in] query A pointer to a DNS query that exist in the packet
156
     * @return The DNS query following 'query'. If 'query' is NULL or 'query' is the last query in the packet NULL will be returned
157
     */
158
    DnsQuery* getNextQuery(DnsQuery* query) const;
159
160
    /**
161
     * @return The number of DNS queries in the packet
162
     */
163
    size_t getQueryCount() const;
164
165
    /**
166
     * Add a new DNS query to the layer
167
     * @param[in] name The value that shall be set in the name field of the query
168
     * @param[in] dnsType The value that shall be set in the DNS type field of the query
169
     * @param[in] dnsClass The value that shall be set in the DNS class field of the query
170
     * @return A pointer to the newly created DNS query or NULL if query could not be created (an appropriate error log message will be
171
     * printed in this case)
172
     */
173
    DnsQuery* addQuery(const std::string& name, DnsType dnsType, DnsClass dnsClass);
174
175
    /**
176
     * Add a new DNS query similar to an already existing DNS query. All query fields will be copied from the existing query
177
     * @param[in] copyQuery The record to create the new record from. copyQuery won't be changed in any way
178
     * @return A pointer to the newly created DNS query or NULL if query could not be created (an appropriate error log message will be
179
     * printed in this case)
180
     */
181
    DnsQuery* addQuery(DnsQuery* const copyQuery);
182
183
    /**
184
     * Remove an existing query by name. If several queries matches the name, the first match will be removed
185
     * @param[in] queryNameToRemove The name of the query to remove
186
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
187
     * @return True if query was found and successfully removed or false if query was not found or couldn't be removed
188
     */
189
    bool removeQuery(const std::string& queryNameToRemove, bool exactMatch);
190
191
    /**
192
     * Remove an existing query
193
     * @param[in] queryToRemove A pointer to the query to remove
194
     * @return True if query was found and successfully removed or false if query was not found or couldn't be removed
195
     */
196
    bool removeQuery(DnsQuery* queryToRemove);
197
198
    /**
199
     * Searches for a DNS answer by its name field. Notice this method returns only an answer which its name equals to the requested name. If
200
     * several answers match the requested name, the first one will be returned. If no answers match the requested name, NULL will be returned
201
     * @param[in] name The name of the answer to search
202
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
203
     * @return The first matching DNS answer or NULL if no answers were found
204
     */
205
    DnsResource* getAnswer(const std::string& name, bool exactMatch) const;
206
207
    /**
208
     * @return The first DNS answer in the packet or NULL if packet doesn't contain any answers
209
     */
210
    DnsResource* getFirstAnswer() const;
211
212
    /**
213
     * Get the DNS answer following a certain answer
214
     * @param[in] answer A pointer to a DNS answer that exist in the packet
215
     * @return The DNS answer following 'answer'. If 'answer' is NULL or 'answer' is the last answer in the packet NULL will be returned
216
     */
217
    DnsResource* getNextAnswer(DnsResource* answer) const;
218
219
    /**
220
     * @return The number of DNS answers in the packet
221
     */
222
    size_t getAnswerCount() const;
223
224
    /**
225
     * Add a new DNS answer to the layer
226
     * @param[in] name The value that shall be set in the name field of the answer
227
     * @param[in] dnsType The value that shall be set in the DNS type field of the answer
228
     * @param[in] dnsClass The value that shall be set in the DNS class field of the answer
229
     * @param[in] ttl The value that shall be set in the 'time-to-leave' field of the answer
230
     * @param[in] data The answer data to be set. The type of the data should match the type of the DNS record
231
     * (for example: DNS record of type A should have data of type IPv4DnsResourceData. Please see DnsResource#setData()
232
     * for more info on this
233
     * @return A pointer to the newly created DNS answer or NULL if answer could not be created (an appropriate error log message will be
234
     * printed in this case)
235
     */
236
    DnsResource* addAnswer(const std::string& name, DnsType dnsType, DnsClass dnsClass, uint32_t ttl, IDnsResourceData* data);
237
238
    /**
239
     * Add a new DNS answer similar to an already existing DNS answer. All answer fields will be copied from the existing answer
240
     * @param[in] copyAnswer The record to create the new record from. copyAnswer won't be changed in any way
241
     * @return A pointer to the newly created DNS answer or NULL if query could not be created (an appropriate error log message will be
242
     * printed in this case)
243
     */
244
    DnsResource* addAnswer(DnsResource* const copyAnswer);
245
246
    /**
247
     * Remove an existing answer by name. If several answers matches the name, the first match will be removed
248
     * @param[in] answerNameToRemove The name of the answer to remove
249
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
250
     * @return True if answer was found and successfully removed or false if answer was not found or couldn't be removed
251
     */
252
    bool removeAnswer(const std::string& answerNameToRemove, bool exactMatch);
253
254
    /**
255
     * Remove an existing answer
256
     * @param[in] answerToRemove A pointer to the answer to remove
257
     * @return True if answer was found and successfully removed or false if answer was not found or couldn't be removed
258
     */
259
    bool removeAnswer(DnsResource* answerToRemove);
260
261
262
    /**
263
     * Searches for a DNS authority by its name field. Notice this method returns only an authority which its name equals to the requested name. If
264
     * several authorities match the requested name, the first one will be returned. If no authorities match the requested name, NULL will be returned
265
     * @param[in] name The name of the authority to search
266
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
267
     * @return The first matching DNS authority or NULL if no authorities were found
268
     */
269
    DnsResource* getAuthority(const std::string& name, bool exactMatch) const;
270
271
    /**
272
     * @return The first DNS authority in the packet or NULL if packet doesn't contain any authorities
273
     */
274
    DnsResource* getFirstAuthority() const;
275
276
    /**
277
     * Get the DNS authority following a certain authority
278
     * @param[in] authority A pointer to a DNS authority that exist in the packet
279
     * @return The DNS authority following 'authority'. If 'authority' is NULL or 'authority' is the last authority in the packet NULL will be returned
280
     */
281
    DnsResource* getNextAuthority(DnsResource* authority) const;
282
283
    /**
284
     * @return The number of DNS authorities in the packet
285
     */
286
    size_t getAuthorityCount() const;
287
288
    /**
289
     * Add a new DNS authority to the layer
290
     * @param[in] name The value that shall be set in the name field of the authority
291
     * @param[in] dnsType The value that shall be set in the DNS type field of the authority
292
     * @param[in] dnsClass The value that shall be set in the DNS class field of the authority
293
     * @param[in] ttl The value that shall be set in the 'time-to-leave' field of the authority
294
     * @param[in] data The authority data to be set. The type of the data should match the type of the DNS record
295
     * (for example: DNS record of type A should have data of type IPv4DnsResourceData. Please see DnsResource#setData()
296
     * for more info on this
297
     * @return A pointer to the newly created DNS authority or NULL if authority could not be created (an appropriate error log message will be
298
     * printed in this case)
299
     */
300
    DnsResource* addAuthority(const std::string& name, DnsType dnsType, DnsClass dnsClass, uint32_t ttl, IDnsResourceData* data);
301
302
    /**
303
     * Add a new DNS authority similar to an already existing DNS authority. All authority fields will be copied from the existing authority
304
     * @param[in] copyAuthority The record to create the new record from. copyAuthority won't be changed in any way
305
     * @return A pointer to the newly created DNS authority or NULL if query could not be created (an appropriate error log message will be
306
     * printed in this case)
307
     */
308
    DnsResource* addAuthority(DnsResource* const copyAuthority);
309
310
    /**
311
     * Remove an existing authority by name. If several authorities matches the name, the first match will be removed
312
     * @param[in] authorityNameToRemove The name of the authority to remove
313
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
314
     * @return True if authority was found and successfully removed or false if authority was not found or couldn't be removed
315
     */
316
    bool removeAuthority(const std::string& authorityNameToRemove, bool exactMatch);
317
318
    /**
319
     * Remove an existing authority
320
     * @param[in] authorityToRemove A pointer to the authority to remove
321
     * @return True if authority was found and successfully removed or false if authority was not found or couldn't be removed
322
     */
323
    bool removeAuthority(DnsResource* authorityToRemove);
324
325
326
    /**
327
     * Searches for a DNS additional record by its name field. Notice this method returns only an additional record which its name equals to
328
     * the requested name. If several additional records match the requested name, the first one will be returned. If no additional records
329
     * match the requested name, NULL will be returned
330
     * @param[in] name The name of the additional record to search
331
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
332
     * @return The first matching DNS additional record or NULL if no additional records were found
333
     */
334
    DnsResource* getAdditionalRecord(const std::string& name, bool exactMatch) const;
335
336
    /**
337
     * @return The first DNS additional record in the packet or NULL if packet doesn't contain any additional records
338
     */
339
    DnsResource* getFirstAdditionalRecord() const;
340
341
    /**
342
     * Get the DNS additional record following a certain additional record
343
     * @param[in] additionalRecord A pointer to a DNS additional record that exist in the packet
344
     * @return The DNS additional record following 'additionalRecord'. If 'additionalRecord' is NULL or 'additionalRecord' is the
345
     * last additional record in the packet NULL will be returned
346
     */
347
    DnsResource* getNextAdditionalRecord(DnsResource* additionalRecord) const;
348
349
    /**
350
     * @return The number of DNS additional records in the packet
351
     */
352
    size_t getAdditionalRecordCount() const;
353
354
    /**
355
     * Add a new DNS additional record to the layer
356
     * @param[in] name The value that shall be set in the name field of the additional record
357
     * @param[in] dnsType The value that shall be set in the DNS type field of the additional record
358
     * @param[in] dnsClass The value that shall be set in the DNS class field of the additional record
359
     * @param[in] ttl The value that shall be set in the 'time-to-leave' field of the additional record
360
     * @param[in] data The additional record data to be set. The type of the data should match the type of the DNS record
361
     * (for example: DNS record of type A should have data of type IPv4DnsResourceData. Please see DnsResource#setData()
362
     * for more info on this
363
     * @return A pointer to the newly created DNS additional record or NULL if additional record could not be created (an appropriate error
364
     * log message will be printed in this case)
365
     */
366
    DnsResource* addAdditionalRecord(const std::string& name, DnsType dnsType, DnsClass dnsClass, uint32_t ttl, IDnsResourceData* data);
367
368
    /**
369
     * Add a new DNS additional record to the layer that doesn't have DNS class and TTL. Instead these bytes may contains some arbitrary
370
     * data. In the future I may add support for these kinds of additional data records. For now, these bytes are set as raw
371
     * @param[in] name The value that shall be set in the name field of the additional record
372
     * @param[in] dnsType The value that shall be set in the DNS type field of the additional record
373
     * @param[in] customData1 Two bytes of the arbitrary data that will be set in the offset usually used for the DNS class
374
     * @param[in] customData2 Four bytes of the arbitrary data that will be set in the offset usually used for the TTL
375
     * @param[in] data The additional record data to be set. The type of the data should match the type of the DNS record.
376
     * (for example: DNS record of type A should have data of type IPv4DnsResourceData. Please see DnsResource#setData()
377
     * for more info on this
378
     * @return A pointer to the newly created DNS additional record or NULL if additional record could not be created (an appropriate error
379
     * log message will be printed in this case)
380
     */
381
    DnsResource* addAdditionalRecord(const std::string& name, DnsType dnsType, uint16_t customData1, uint32_t customData2, IDnsResourceData* data);
382
383
    /**
384
     * Add a new DNS additional record similar to an already existing DNS additional record. All additional record fields will be copied from the
385
     * existing additional record
386
     * @param[in] copyAdditionalRecord The record to create the new record from. copyAdditionalRecord won't be changed in any way
387
     * @return A pointer to the newly created DNS additional record or NULL if query could not be created (an appropriate error log message will
388
     * be printed in this case)
389
     */
390
    DnsResource* addAdditionalRecord(DnsResource* const copyAdditionalRecord);
391
392
    /**
393
     * Remove an existing additional record by name. If several additional records matches the name, the first match will be removed
394
     * @param[in] additionalRecordNameToRemove The name of the additional record to remove
395
     * @param[in] exactMatch Indicate whether to match the whole name or just a part of it
396
     * @return True if additional record was found and successfully removed or false if additional record was not found or couldn't be removed
397
     */
398
    bool removeAdditionalRecord(const std::string& additionalRecordNameToRemove, bool exactMatch);
399
400
    /**
401
     * Remove an existing additional record
402
     * @param[in] additionalRecordToRemove A pointer to the additional record to remove
403
     * @return True if additional record was found and successfully removed or false if additional record was not found or couldn't be removed
404
     */
405
    bool removeAdditionalRecord(DnsResource* additionalRecordToRemove);
406
407
    // implement abstract methods
408
409
    /**
410
     * Does nothing for this layer (DnsLayer is always last)
411
     */
412
125k
    void parseNextLayer() {}
413
414
    /**
415
     * @return The size of the DNS data in the packet including he DNS header and size of all queries, answers, authorities and additional
416
     * records
417
     */
418
298k
    size_t getHeaderLen() const { return m_DataLen; } //No layer above DNS
419
420
    /**
421
     * Does nothing for this layer
422
     */
423
15.4k
    virtual void computeCalculateFields() {}
424
425
    std::string toString() const;
426
427
24.9k
    OsiModelLayer getOsiModelLayer() const { return OsiModelApplicationLayer; }
428
429
    /**
430
     * A static method that checks whether the port is considered as DNS
431
     * @param[in] port The port number to be checked
432
     * @return True if the port is associated with the DNS protocol
433
     */
434
    static inline bool isDnsPort(uint16_t port);
435
436
    /**
437
     * A static method that validates the input data
438
     * @param[in] data The pointer to the beginning of a byte stream of a DNS packet
439
     * @param[in] dataLen The length of the byte stream
440
     * @param[in] dnsOverTcp Should be set to "true" if this is DNS is over TCP, otherwise set to "false"
441
     * (which is also the default value)
442
     * @return True if the data is valid and can represent a DNS packet
443
     */
444
    static inline bool isDataValid(const uint8_t* data, size_t dataLen, bool dnsOverTcp = false);
445
446
  protected:
447
    DnsLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, size_t offsetAdjustment);
448
    explicit DnsLayer(size_t offsetAdjustment);
449
450
  private:
451
    IDnsResource* m_ResourceList;
452
    DnsQuery*     m_FirstQuery;
453
    DnsResource*  m_FirstAnswer;
454
    DnsResource*  m_FirstAuthority;
455
    DnsResource*  m_FirstAdditional;
456
    uint16_t      m_OffsetAdjustment;
457
458
    size_t getBasicHeaderSize();
459
    void init(size_t offsetAdjustment, bool callParseResource);
460
    void initNewLayer(size_t offsetAdjustment);
461
462
    IDnsResource* getFirstResource(DnsResourceType resType) const;
463
    void setFirstResource(DnsResourceType resType, IDnsResource* resource);
464
465
    using Layer::extendLayer;
466
    bool extendLayer(int offsetInLayer, size_t numOfBytesToExtend, IDnsResource* resource);
467
468
    using Layer::shortenLayer;
469
    bool shortenLayer(int offsetInLayer, size_t numOfBytesToShorten, IDnsResource* resource);
470
471
    IDnsResource* getResourceByName(IDnsResource* startFrom, size_t resourceCount, const std::string& name, bool exactMatch) const;
472
473
    void parseResources();
474
475
    DnsResource* addResource(DnsResourceType resType, const std::string& name, DnsType dnsType, DnsClass dnsClass,
476
        uint32_t ttl, IDnsResourceData* data);
477
478
    bool removeResource(IDnsResource* resourceToRemove);
479
480
  };
481
482
483
484
  /**
485
   * @class DnsOverTcpLayer
486
   * Represents the DNS over TCP layer.
487
   * DNS over TCP is described here: https://tools.ietf.org/html/rfc7766 .
488
   * It is very similar to DNS over UDP, except for one field: TCP message length which is added in the beginning of the message
489
   * before the other DNS data properties. The rest of the data is similar.
490
   *
491
   * Note: DNS over TCP can spread over more than one packet, but this implementation doesn't support this use-case and assumes
492
   * the whole message fits in a single packet.
493
   */
494
  class DnsOverTcpLayer : public DnsLayer
495
  {
496
  public:
497
498
    /**
499
     * A constructor that creates the layer from an existing packet raw data
500
     * @param[in] data A pointer to the raw data
501
     * @param[in] dataLen Size of the data in bytes
502
     * @param[in] prevLayer A pointer to the previous layer
503
     * @param[in] packet A pointer to the Packet instance where layer will be stored in
504
     */
505
    DnsOverTcpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
506
48.3k
      : DnsLayer(data, dataLen, prevLayer, packet, sizeof(uint16_t)) {}
507
508
    /**
509
     * A constructor that creates an empty DNS layer: all members of dnshdr are set to 0 and layer will contain no records
510
     */
511
0
    DnsOverTcpLayer() : DnsLayer(sizeof(uint16_t)) {}
512
513
    /**
514
     * A copy constructor for this layer
515
     * @param[in] other The DNS over TCP layer to copy from
516
     */
517
0
    DnsOverTcpLayer(const DnsOverTcpLayer& other) : DnsLayer(other) {}
518
519
    /**
520
     * @return The value of the TCP message length as described in https://tools.ietf.org/html/rfc7766#section-8
521
     */
522
    uint16_t getTcpMessageLength();
523
524
    /**
525
     * Set the TCP message length value as described in https://tools.ietf.org/html/rfc7766#section-8
526
     * @param[in] value The value to set
527
     */
528
    void setTcpMessageLength(uint16_t value);
529
530
531
    // overridden methods
532
533
    /**
534
     * Calculate the TCP message length field
535
     */
536
    void computeCalculateFields();
537
  };
538
539
540
  // implementation of inline methods
541
542
  bool DnsLayer::isDnsPort(uint16_t port)
543
757k
  {
544
757k
    switch (port)
545
757k
    {
546
105k
    case 53:
547
107k
    case 5353:
548
125k
    case 5355:
549
125k
      return true;
550
632k
    default:
551
632k
      return false;
552
757k
    }
553
757k
  }
554
555
  bool DnsLayer::isDataValid(const uint8_t* data, size_t dataLen, bool dnsOverTcp)
556
435k
  {
557
435k
    size_t minSize = sizeof(dnshdr) + (dnsOverTcp ? sizeof(uint16_t) : 0);
558
435k
    return data && dataLen >= minSize;
559
435k
  }
560
561
} // namespace pcpp