E-MailRelay
gdnsmessage.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gdnsmessage.cpp
19///
20
21#include "gdef.h"
22#include "gdnsmessage.h"
23#include "gassert.h"
24#include "gstr.h"
25#include "gstringview.h"
26#include "gstringfield.h"
27#include <array>
28#include <vector>
29#include <iomanip>
30#include <sstream>
31#include <stdexcept>
32
33GNet::DnsMessageRequest::DnsMessageRequest( const std::string & type , const std::string & hostname , unsigned int id )
34{
35 // header section
36 G_ASSERT( id < 0xffffU ) ;
37 addWord( id ) ; // ID=id - arbitrary identifier to link query with response
38 addByte( 0x01U ) ; // flags - QR=0 (ie. query) and RD=1 (ie. recursion desired)
39 addByte( 0x00U ) ; // RA=0 (recursion available) and Z=0 (zero bits, but see RFC-2671) and RCODE=0 (response code)
40 addWord( 1U ) ; // QDCOUNT=1 (ie. one question section)
41 addWord( 0U ) ; // ANCOUNT=0 (ie. no answer sections)
42 addWord( 0U ) ; // NSCOUNT=0 (ie. no authority sections)
43 addWord( 0U ) ; // ARCOUNT=0 (ie. no additional sections)
44
45 // question section
46 addDomainName( hostname , '.' ) ; // QNAME
47 addWord( DnsMessageRecordType::value(type) ) ; // eg. QTYPE=A
48 addWord( 1U ) ; // QCLASS=IN(ternet)
49}
50
51void GNet::DnsMessageRequest::addDomainName( const std::string & domain , char sep )
52{
53 std::string_view domain_sv( domain ) ;
54 for( G::StringFieldView part( domain_sv , sep ) ; part ; ++part )
55 {
56 addLabel( part() ) ;
57 }
58 addLabel( std::string_view() ) ; // zero-length root
59}
60
61void GNet::DnsMessageRequest::addLabel( std::string_view data )
62{
63 if( data.size() > 63U ) throw DnsMessage::Error("overflow") ;
64 addByte( static_cast<unsigned int>(data.size()) ) ;
65 if( !data.empty() )
66 m_data.append( data.data() , data.size() ) ;
67}
68
69void GNet::DnsMessageRequest::addWord( unsigned int n )
70{
71 addByte( (n >> 8) & 0xFFU ) ;
72 addByte( (n >> 0) & 0xFFU ) ;
73}
74
75void GNet::DnsMessageRequest::addByte( unsigned int n )
76{
77 m_data.append( 1U , static_cast<char>(static_cast<unsigned char>(n)) ) ;
78}
79
80const char * GNet::DnsMessageRequest::p() const
81{
82 return m_data.data() ;
83}
84
85std::size_t GNet::DnsMessageRequest::n() const
86{
87 return m_data.size() ;
88}
89
90// ==
91
92GNet::DnsMessage::DnsMessage()
93= default;
94
95GNet::DnsMessage::DnsMessage( const std::vector<char> & buffer ) :
96 m_buffer(buffer)
97{
98}
99
100GNet::DnsMessage::DnsMessage( const char * p , std::size_t n ) :
101 m_buffer(p,p+n)
102{
103}
104
106{
107 return m_buffer.size() >= 12U && !TC() ;
108}
109
110const char * GNet::DnsMessage::p() const noexcept
111{
112 return m_buffer.data() ;
113}
114
115std::size_t GNet::DnsMessage::n() const noexcept
116{
117 return m_buffer.size() ;
118}
119
120#ifndef G_LIB_SMALL
122{
123 return {} ;
124}
125#endif
126
127GNet::DnsMessage GNet::DnsMessage::request( const std::string & type , const std::string & hostname , unsigned int id )
128{
129 DnsMessageRequest r( type , hostname , id ) ;
130 return { r.p() , r.n() } ;
131}
132
133std::vector<GNet::Address> GNet::DnsMessage::addresses() const
134{
135 std::vector<Address> list ;
136 for( unsigned int i = QDCOUNT() ; i < (QDCOUNT()+ANCOUNT()) ; i++ )
137 {
138 list.push_back( rrAddress(i) ) ;
139 }
140 return list ;
141}
142
143unsigned int GNet::DnsMessage::byte( unsigned int i ) const
144{
145 if( i > m_buffer.size() )
146 throw Error( "invalid offset: " + std::to_string(i) + "/" + std::to_string(m_buffer.size()) ) ;
147 char c = m_buffer.at( i ) ;
148 return static_cast<unsigned char>(c) ;
149}
150
151unsigned int GNet::DnsMessage::word( unsigned int i ) const
152{
153 return byte(i) * 256U + byte(i+1U) ;
154}
155
156std::string GNet::DnsMessage::span( unsigned int begin , unsigned int end ) const
157{
158 if( begin >= m_buffer.size() || end > m_buffer.size() || begin > end )
159 throw Error( "invalid span" ) ;
160 return { m_buffer.begin()+begin , m_buffer.begin()+end } ;
161}
162
163unsigned int GNet::DnsMessage::ID() const
164{
165 return word( 0U ) ;
166}
167
169{
170 return !!( byte(2U) & 0x80 ) ;
171}
172
173unsigned int GNet::DnsMessage::OPCODE() const
174{
175 return ( byte(2U) & 0x78 ) >> 3U ;
176}
177
179{
180 return !!( byte(2U) & 0x04 ) ;
181}
182
184{
185 return !!( byte(2U) & 0x02 ) ;
186}
187
188#ifndef G_LIB_SMALL
190{
191 return !!( byte(2U) & 0x01 ) ;
192}
193#endif
194
195#ifndef G_LIB_SMALL
197{
198 return !!( byte(3U) & 0x80 ) ;
199}
200#endif
201
202#ifndef G_LIB_SMALL
203unsigned int GNet::DnsMessage::Z() const
204{
205 return ( byte(3U) & 0x70 ) >> 4 ;
206}
207#endif
208
209unsigned int GNet::DnsMessage::RCODE() const
210{
211 return byte(3U) & 0x0f ;
212}
213
214unsigned int GNet::DnsMessage::QDCOUNT() const
215{
216 return word(4U) ;
217}
218
219unsigned int GNet::DnsMessage::ANCOUNT() const
220{
221 return word(6U) ;
222}
223
224#ifndef G_LIB_SMALL
225unsigned int GNet::DnsMessage::NSCOUNT() const
226{
227 return word(8U) ;
228}
229#endif
230
231#ifndef G_LIB_SMALL
232unsigned int GNet::DnsMessage::ARCOUNT() const
233{
234 return word(10U) ;
235}
236#endif
237
238GNet::DnsMessage GNet::DnsMessage::rejection( const DnsMessage & message , unsigned int rcode )
239{
240 DnsMessage result( message ) ;
241 result.convertToResponse( rcode , false ) ;
242 return result ;
243}
244
245void GNet::DnsMessage::convertToResponse( unsigned int rcode , bool authoritative )
246{
247 if( m_buffer.size() < 10U || QDCOUNT() == 0U || OPCODE() != 0U )
248 throw Error( "cannot convert" ) ;
249
250 // fix up the header
251 unsigned char * buffer = reinterpret_cast<unsigned char*>( m_buffer.data() ) ;
252 buffer[2U] |= 0x80U ; // QR
253 if( authoritative ) buffer[2U] |= 0x04U ; // AA
254 buffer[3U] &= 0xf0U ; buffer[3U] |= ( rcode & 0x0fU ) ; // RCODE
255 buffer[6U] = 0U ; buffer[7U] = 0U ; // ANCOUNT
256 buffer[8U] = 0U ; buffer[9U] = 0U ; // NSCOUNT
257 buffer[10U] = 0U ; buffer[11U] = 0U ; // ARCOUNT
258
259 // step over the question(s)
260 unsigned int new_size = 12U ; // HEADER size
261 for( unsigned int i = 0U ; i < QDCOUNT() ; i++ )
262 new_size += Question(*this,new_size).size() ;
263
264 // chop off RRs
265 m_buffer.resize( new_size ) ;
266}
267
268void GNet::DnsMessage::addWord( unsigned int n )
269{
270 addByte( (n >> 8) & 0xFFU ) ;
271 addByte( (n >> 0) & 0xFFU ) ;
272}
273
274void GNet::DnsMessage::addByte( unsigned int n )
275{
276 m_buffer.push_back( static_cast<char>(static_cast<unsigned char>(n)) ) ;
277}
278
279GNet::DnsMessageQuestion GNet::DnsMessage::question( unsigned int record_index ) const
280{
281 if( record_index >= QDCOUNT() )
282 throw Error( "invalid record number" ) ;
283
284 unsigned int offset = 12U ; // HEADER size
285 for( unsigned int i = 0U ; i < record_index ; i++ )
286 offset += Question(*this,offset).size() ;
287 return { *this , offset } ;
288}
289
290GNet::DnsMessageRR GNet::DnsMessage::rr( unsigned int record_index ) const
291{
292 if( record_index < QDCOUNT() )
293 throw Error( "invalid rr number" ) ; // a question is not a RR
294
295 unsigned int offset = 12U ; // HEADER size
296 for( unsigned int i = 0U ; i < record_index ; i++ )
297 {
298 if( i < QDCOUNT() )
299 offset += Question(*this,offset).size() ;
300 else
301 offset += RR(*this,offset).size() ;
302 }
303 return { *this , offset } ;
304}
305
306GNet::Address GNet::DnsMessage::rrAddress( unsigned int record_index ) const
307{
308 return rr(record_index).address() ;
309}
310
311// ==
312
314{
315 m_qname = DnsMessageNameParser::read( msg , offset ) ;
316 unsigned int qname_size = DnsMessageNameParser::size( msg , offset ) ;
317 m_qtype = msg.word( offset + qname_size ) ;
318 m_qclass = msg.word( offset + qname_size + 2U ) ;
319 m_size = qname_size + 2U + 2U ; // QNAME + QTYPE + QCLASS
320}
321
323{
324 return m_size ;
325}
326
328{
329 return m_qname ;
330}
331
333{
334 return m_qtype ; // eg. AAAA
335}
336
337#ifndef G_LIB_SMALL
339{
340 return m_qclass ; // eg. IN
341}
342#endif
343
344// ==
345
346unsigned int GNet::DnsMessageNameParser::size( const DnsMessage & msg , unsigned int offset_in )
347{
348 unsigned int offset = offset_in ;
349 for(;;)
350 {
351 unsigned int n = msg.byte( offset ) ;
352 if( ( n & 0xC0 ) == 0xC0 ) // compression -- see RFC-1035 4.1.4
353 return offset - offset_in + 2U ;
354 else if( ( n & 0xC0 ) != 0 )
355 throw DnsMessage::Error( "unknown label type" ) ; // "reserved for future use"
356 else if( n == 0U )
357 break ;
358 else
359 offset += (n+1U) ;
360 }
361 return offset - offset_in + 1U ;
362}
363
364std::string GNet::DnsMessageNameParser::read( const DnsMessage & msg , unsigned int offset_in )
365{
366 unsigned int offset = offset_in ;
367 std::string result ;
368 for(;;)
369 {
370 unsigned int n = msg.byte( offset ) ;
371 if( ( n & 0xC0U ) == 0xC0U )
372 {
373 unsigned int m = msg.byte(offset+1U) ;
374 offset = (n&0x3FU)*256U + m ;
375 if( offset > offset_in )
376 throw DnsMessage::Error( "invalid label" ) ;
377 }
378 else if( ( n & 0xC0U ) != 0U )
379 {
380 throw DnsMessage::Error( "unknown label type" ) ; // "reserved for future use"
381 }
382 else if( n == 0U )
383 {
384 break ;
385 }
386 else
387 {
388 if( n > 63U )
389 throw DnsMessage::Error( "name overflow" ) ;
390
391 result.append( result.empty()?0U:1U , '.' ) ;
392 result.append( msg.span(offset+1U,offset+n+1U) ) ;
393 offset += (n+1U) ;
394 }
395 }
396 return result ;
397}
398
399// ==
400
401GNet::DnsMessageRR::DnsMessageRR( const DnsMessage & msg , unsigned int offset ) :
402 m_msg(msg) ,
403 m_offset(offset)
404{
405 m_name = DnsMessageNameParser::read( msg , offset ) ; // NAME
407
408 m_type = msg.word( offset ) ; offset += 2U ; // TYPE // NOLINT
409 m_class = msg.word( offset ) ; offset += 2U ; // CLASS // NOLINT
410 offset += 4U ; // TTL
411 m_rdata_size = msg.word( offset ) ; offset += 2U ; // RDLENGTH // NOLINT
412
413 m_rdata_offset = offset ; // NOLINT
414 m_size = offset - m_offset + m_rdata_size ;
415
416 if( m_class != 1U ) // "IN" (internet)
417 throw DnsMessage::Error( "invalid rr class" ) ;
418}
419
420#ifndef G_LIB_SMALL
421unsigned int GNet::DnsMessageRR::type() const
422{
423 return m_type ;
424}
425#endif
426
427#ifndef G_LIB_SMALL
428unsigned int GNet::DnsMessageRR::class_() const
429{
430 return m_class ;
431}
432#endif
433
434bool GNet::DnsMessageRR::isa( std::string_view type_name ) const noexcept
435{
436 return m_type == DnsMessageRecordType::value( type_name , std::nothrow ) ;
437}
438
439unsigned int GNet::DnsMessageRR::size() const
440{
441 return m_size ;
442}
443
444#ifndef G_LIB_SMALL
445std::string GNet::DnsMessageRR::name() const
446{
447 return m_name ;
448}
449#endif
450
451std::string GNet::DnsMessageRR::rdataDname( unsigned int rdata_offset ) const
452{
453 return DnsMessageNameParser::read( m_msg , m_rdata_offset + rdata_offset ) ;
454}
455
456#ifndef G_LIB_SMALL
457std::string GNet::DnsMessageRR::rdataDname( unsigned int * rdata_offset_p ) const
458{
459 std::string dname = DnsMessageNameParser::read( m_msg , m_rdata_offset + *rdata_offset_p ) ;
460 *rdata_offset_p += DnsMessageNameParser::size( m_msg , m_rdata_offset + *rdata_offset_p ) ;
461 return dname ;
462}
463#endif
464
465#ifndef G_LIB_SMALL
466std::string GNet::DnsMessageRR::rdataSpan( unsigned int rdata_begin ) const
467{
468 return rdataSpan( rdata_begin , rdataSize() ) ;
469}
470#endif
471
472std::string GNet::DnsMessageRR::rdataSpan( unsigned int rdata_begin , unsigned int rdata_end ) const
473{
474 return m_msg.span( m_rdata_offset + rdata_begin , m_rdata_offset + rdata_end ) ;
475}
476
477#ifndef G_LIB_SMALL
478unsigned int GNet::DnsMessageRR::rdataOffset() const
479{
480 return m_rdata_offset ;
481}
482#endif
483
484unsigned int GNet::DnsMessageRR::rdataSize() const
485{
486 return m_rdata_size ;
487}
488
489unsigned int GNet::DnsMessageRR::rdataByte( unsigned int i ) const
490{
491 return m_msg.byte( m_rdata_offset + i ) ;
492}
493
494unsigned int GNet::DnsMessageRR::rdataWord( unsigned int i ) const
495{
496 return m_msg.word( m_rdata_offset + i ) ;
497}
498
499GNet::Address GNet::DnsMessageRR::address( unsigned int port , std::nothrow_t ) const
500{
501 bool ok = false ;
502 return addressImp( port , ok ) ;
503}
504
506{
507 bool ok = false ;
508 auto a = addressImp( port , ok ) ;
509 if( !ok )
510 throw DnsMessage::Error( "not an address" ) ;
511 return a ;
512}
513
514GNet::Address GNet::DnsMessageRR::addressImp( unsigned int port , bool & ok ) const
515{
516 std::ostringstream ss ;
517 if( isa("A") && rdataSize() == 4U )
518 {
519 ss << rdataByte(0U) << "." << rdataByte(1U) << "." << rdataByte(2U) << "." << rdataByte(3U) << ":" << port ;
520 }
521 else if( isa("AAAA") && rdataSize() == 16U )
522 {
523 const char * sep = "" ;
524 for( unsigned int i = 0 ; i < 8U ; i++ , sep = ":" )
525 ss << sep << std::hex << rdataWord(i*2U) ;
526 ss << "." << port ;
527 }
528 ok = Address::validString( ss.str() , Address::NotLocal() ) ;
529 return ok ? Address::parse( ss.str() , Address::NotLocal() ) : Address::defaultAddress() ;
530}
531
532// ==
533
534namespace GNet
535{
536 namespace DnsMessageRecordTypeImp
537 {
538 struct Pair /// A std::pair-like structure used in GNet::DnsMessage, needed for gcc 4.2.1
539 {
540 unsigned int first ;
541 const char * second ;
542 } ;
543 constexpr std::array<Pair,23U> map = {{
544 { 1 , "A" } , // a host address
545 { 2 , "NS" } , // an authoritative name server
546 { 3 , "MD" } , // a mail destination (Obsolete - use MX)
547 { 4 , "MF" } , // a mail forwarder (Obsolete - use MX)
548 { 5 , "CNAME" } , // the canonical name for an alias
549 { 6 , "SOA" } , // marks the start of a zone of authority
550 { 7 , "MB" } , // a mailbox domain name (EXPERIMENTAL)
551 { 8 , "MG" } , // a mail group member (EXPERIMENTAL)
552 { 9 , "MR" } , // a mail rename domain name (EXPERIMENTAL)
553 { 10 , "NULL_" } , // a null RR (EXPERIMENTAL)
554 { 11 , "WKS" } , // a well known service description
555 { 12 , "PTR" } , // a domain name pointer
556 { 13 , "HINFO" } , // host information
557 { 14 , "MINFO" } , // mailbox or mail list information
558 { 15 , "MX" } , // mail exchange
559 { 16 , "TXT" } , // text strings
560 { 28 , "AAAA" } , // IPv6 -- RFC-3596
561 { 33 , "SRV" } , // service pointer -- RFC-2782
562 { 41 , "OPT" } , // extended options -- EDNS0 -- RFC-2671
563 { 43 , "DS" } , // delegation signer -- DNSSEC -- RFC-4034
564 { 46 , "RRSIG" } , // resource record signature -- DNSSEC -- RFC-4034
565 { 47 , "NSEC" } , // next secure -- DNSSEC -- RFC-4034
566 { 48 , "DNSKEY" } // dns public key -- DNSSEC -- RFC-4034
567 }} ;
568 }
569}
570
571unsigned int GNet::DnsMessageRecordType::value( std::string_view type_name , std::nothrow_t ) noexcept
572{
573 namespace imp = DnsMessageRecordTypeImp ;
574 for( const auto & item : imp::map )
575 {
576 if( G::Str::match( type_name , item.second ) )
577 return item.first ;
578 }
579 return 0U ;
580}
581
582unsigned int GNet::DnsMessageRecordType::value( std::string_view type_name )
583{
584 unsigned int v = value( type_name , std::nothrow ) ;
585 if( v == 0U )
586 throw DnsMessage::Error( "invalid rr type name" ) ;
587 return v ;
588}
589
590std::string GNet::DnsMessageRecordType::name( unsigned int type_value )
591{
592 namespace imp = DnsMessageRecordTypeImp ;
593 for( const auto & item : imp::map )
594 {
595 if( item.first == type_value )
596 return item.second ;
597 }
598 throw DnsMessage::Error( "invalid rr type value" ) ;
599}
600
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:63
static bool validString(std::string_view display_string, std::string *reason=nullptr)
Returns true if the transport-address display string is valid.
Definition: gaddress.cpp:383
static Address parse(std::string_view display_string)
Factory function for any address family.
Definition: gaddress.cpp:178
const sockaddr * address() const
Returns the sockaddr address.
Definition: gaddress.cpp:417
static unsigned int size(const DnsMessage &msg, unsigned int)
Returns the size of the compressed name at the given offset.
static std::string read(const DnsMessage &msg, unsigned int)
Returns the decompressed domain name at the given offset, made up of the labels with dots inbetween.
Represents DNS question record.
Definition: gdnsmessage.h:312
DnsMessageQuestion(const DnsMessage &, unsigned int offset)
Constructor.
unsigned int qtype() const
Returns the question QTYPE value.
std::string qname() const
Returns the question domain name (QNAME).
unsigned int size() const
Returns the record size.
unsigned int qclass() const
Returns the question QCLASS value.
unsigned int offset() const
Calls rdataOffset().
Represents DNS response record.
Definition: gdnsmessage.h:241
unsigned int type() const
Returns the RR TYPE value().
unsigned int class_() const
Returns the RR CLASS value().
DnsMessageRR(const DnsMessage &, unsigned int offset)
Constructor from DnsMessage data.
unsigned int size() const
Returns the size of the RR.
Address address(unsigned int port=0U) const
Returns the Address if isa(A) or isa(AAAA).
bool isa(std::string_view) const noexcept
Returns true if the type() has the given name().
std::string name() const
Returns the RR NAME.
static unsigned int value(std::string_view type_name)
Returns the type value for the given type name.
static std::string name(unsigned int type_value)
Returns the type name for the given type value.
Represents a DNS query message.
Definition: gdnsmessage.h:358
std::size_t n() const
Returns message size.
Definition: gdnsmessage.cpp:85
const char * p() const
Returns a pointer to the message data.
Definition: gdnsmessage.cpp:80
DnsMessageRequest(const std::string &type, const std::string &hostname, unsigned int id=0U)
Constructor.
Definition: gdnsmessage.cpp:33
A DNS message parser, with static factory functions for message composition.
Definition: gdnsmessage.h:56
Address rrAddress(unsigned int n) const
Returns the address in the n'th record.
unsigned int ANCOUNT() const
Returns the header ANCOUNT field, ie.
const char * p() const noexcept
Returns the raw data.
Question question(unsigned int n) const
Returns the n'th record as a Question record.
unsigned int RCODE() const
Returns the header RCODE.
bool QR() const
Returns the header QR (query/response).
bool valid() const
Returns true if the message data is big enough for a header and its TC() flag is false.
unsigned int Z() const
Returns the header Z value (zero).
DnsMessage(const std::vector< char > &buffer)
Constructor. Check with valid().
Definition: gdnsmessage.cpp:95
unsigned int QDCOUNT() const
Returns the header QDCOUNT field, ie.
unsigned int NSCOUNT() const
Returns the header NSCOUNT field, ie.
unsigned int ARCOUNT() const
Returns the header ARCOUNT field, ie.
bool AA() const
Returns the header AA flag (authorative).
unsigned int OPCODE() const
Returns the header OPCODE.
static DnsMessage rejection(const DnsMessage &request, unsigned int rcode)
Factory function for a failure response based on the given request message.
bool TC() const
Returns the header TC flag (truncated).
static DnsMessage request(const std::string &type, const std::string &hostname, unsigned int id=0U)
Factory function for a request message of the give type ("A", "AAAA", etc).
std::string span(unsigned int begin, unsigned int end) const
Returns the data in the given half-open byte range.
unsigned int byte(unsigned int byte_index) const
Returns byte at the given offset.
std::vector< Address > addresses() const
Returns the Answer addresses.
bool RD() const
Returns the header RD (recursion desired).
static DnsMessage empty()
Factory function for an unusable object.
unsigned int ID() const
Returns the header ID.
bool RA() const
Returns the header RA (recursion available).
RR rr(unsigned int n) const
Returns the n'th record as a RR record.
unsigned int word(unsigned int byte_index) const
Returns word at the given byte offset.
std::size_t n() const noexcept
Returns the raw data size.
static bool match(std::string_view, std::string_view) noexcept
Returns true if the two strings are the same.
Definition: gstr.cpp:1392
A zero-copy string field iterator where the field separators are short fixed strings.
Definition: gstringfield.h:53
Network classes.
Definition: gdef.h:1243
std::string hostname()
Returns the hostname.