E-MailRelay
gdnsmessage.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 <array>
27#include <vector>
28#include <iomanip>
29#include <sstream>
30#include <stdexcept>
31
32GNet::DnsMessageRequest::DnsMessageRequest( const std::string & type , const std::string & hostname , unsigned int id )
33{
34 // header section
35 G_ASSERT( id < 0xffffU ) ;
36 q( (id>>8U)&0xff ) ; q( id&0xff ) ; // ID=id - arbitrary identifier to link query with response
37 q( 0x01 ) ; // flags - QR=0 (ie. query) and RD=1 (ie. recursion desired)
38 q( 0x00 ) ; // RA=0 (recursion available) and Z=0 (zero bits, but see RFC-2671) and RCODE=0 (response code)
39 q( 0x00 ) ; q( 0x01 ) ; // QDCOUNT=1 (ie. one question section)
40 q( 0x00 ) ; q( 0x00 ) ; // ANCOUNT=0 (ie. no answer sections)
41 q( 0x00 ) ; q( 0x00 ) ; // NSCOUNT=0 (ie. no authority sections)
42 q( 0x00 ) ; q( 0x00 ) ; // ARCOUNT=0 (ie. no additional sections)
43
44 // question section
45 q( hostname , '.' ) ; // QNAME
46 q( 0x00 ) ; q( DnsMessageRecordType::value(type) ) ; // eg. QTYPE=A
47 q( 0x00 ) ; q( 0x01 ) ; // QCLASS=IN(ternet)
48}
49
50void GNet::DnsMessageRequest::q( const std::string & domain , char sep )
51{
52 G::StringArray parts ;
53 G::Str::splitIntoFields( domain , parts , {&sep,1U} ) ;
54 for( const auto & part : parts )
55 {
56 q( part ) ;
57 }
58 q( std::string() ) ;
59}
60
61void GNet::DnsMessageRequest::q( const std::string & data )
62{
63 if( data.size() > 63U ) throw DnsMessage::Error("overflow") ;
64 q( static_cast<unsigned int>(data.size()) ) ;
65 m_data.append( data ) ;
66}
67
68void GNet::DnsMessageRequest::q( int n )
69{
70 q( static_cast<unsigned int>(n) ) ;
71}
72
73void GNet::DnsMessageRequest::q( unsigned int n )
74{
75 m_data.append( 1U , static_cast<char>(n) ) ;
76}
77
78const char * GNet::DnsMessageRequest::p() const
79{
80 return m_data.data() ;
81}
82
83std::size_t GNet::DnsMessageRequest::n() const
84{
85 return m_data.size() ;
86}
87
88// ==
89
90GNet::DnsMessage::DnsMessage()
91= default;
92
93GNet::DnsMessage::DnsMessage( const std::vector<char> & buffer ) :
94 m_buffer(buffer)
95{
96 if( TC() )
97 throw Error( "truncated response" ) ;
98}
99
100GNet::DnsMessage::DnsMessage( const char * p , std::size_t n ) :
101 m_buffer(p,p+n)
102{
103}
104
105const char * GNet::DnsMessage::p() const
106{
107 return &m_buffer[0] ;
108}
109
110std::size_t GNet::DnsMessage::n() const
111{
112 return m_buffer.size() ;
113}
114
116{
117 return DnsMessage() ;
118}
119
120GNet::DnsMessage GNet::DnsMessage::request( const std::string & type , const std::string & hostname , unsigned int id )
121{
122 DnsMessageRequest r( type , hostname , id ) ;
123 return DnsMessage( r.p() , r.n() ) ;
124}
125
126std::vector<GNet::Address> GNet::DnsMessage::addresses() const
127{
128 std::vector<Address> list ;
129 for( unsigned int i = QDCOUNT() ; i < (QDCOUNT()+ANCOUNT()) ; i++ )
130 {
131 list.push_back( rrAddress(i) ) ;
132 }
133 return list ;
134}
135
136unsigned int GNet::DnsMessage::byte( unsigned int i ) const
137{
138 char c = m_buffer.at(i) ;
139 return static_cast<unsigned char>(c) ;
140}
141
142unsigned int GNet::DnsMessage::word( unsigned int i ) const
143{
144 return byte(i) * 256U + byte(i+1U) ;
145}
146
147std::string GNet::DnsMessage::span( unsigned int begin , unsigned int end ) const
148{
149 if( begin >= m_buffer.size() || end > m_buffer.size() || begin > end )
150 throw Error( "span error" ) ;
151 return std::string( m_buffer.begin()+begin , m_buffer.begin()+end ) ;
152}
153
154unsigned int GNet::DnsMessage::ID() const
155{
156 return word( 0U ) ;
157}
158
160{
161 return !!( byte(2U) & 0x80 ) ;
162}
163
164unsigned int GNet::DnsMessage::OPCODE() const
165{
166 return ( byte(2U) & 0x78 ) >> 3U ;
167}
168
170{
171 return !!( byte(2U) & 0x04 ) ;
172}
173
175{
176 return !!( byte(2U) & 0x02 ) ;
177}
178
180{
181 return !!( byte(2U) & 0x01 ) ;
182}
183
185{
186 return !!( byte(3U) & 0x80 ) ;
187}
188
189unsigned int GNet::DnsMessage::Z() const
190{
191 return ( byte(3U) & 0x70 ) >> 4 ;
192}
193
194unsigned int GNet::DnsMessage::RCODE() const
195{
196 return byte(3U) & 0x0f ;
197}
198
199unsigned int GNet::DnsMessage::QDCOUNT() const
200{
201 return word(4U) ;
202}
203
204unsigned int GNet::DnsMessage::ANCOUNT() const
205{
206 return word(6U) ;
207}
208
209unsigned int GNet::DnsMessage::NSCOUNT() const
210{
211 return word(8U) ;
212}
213
214unsigned int GNet::DnsMessage::ARCOUNT() const
215{
216 return word(10U) ;
217}
218
219GNet::DnsMessage GNet::DnsMessage::rejection( const DnsMessage & message , unsigned int rcode )
220{
221 DnsMessage result( message ) ;
222 result.reject( rcode ) ;
223 return result ;
224}
225
226void GNet::DnsMessage::reject( unsigned int rcode )
227{
228 if( m_buffer.size() < 10U )
229 throw std::out_of_range( "dns message buffer too small" ) ;
230
231 unsigned char * buffer = reinterpret_cast<unsigned char*>(&m_buffer[0]) ;
232 buffer[2U] |= 0x80U ; // QR
233 buffer[3U] &= 0xf0U ; buffer[3U] |= ( rcode & 0x0fU ) ; // RCODE
234 buffer[6U] = 0U ; buffer[7U] = 0U ; // ANCOUNT
235 buffer[8U] = 0U ; buffer[9U] = 0U ; // NSCOUNT
236
237 // chop off RRs
238 unsigned int new_size = 12U ; // HEADER size
239 for( unsigned int i = 0U ; i < QDCOUNT() ; i++ )
240 new_size += Question(*this,new_size).size() ;
241 m_buffer.resize( new_size ) ;
242}
243
244GNet::DnsMessageQuestion GNet::DnsMessage::question( unsigned int record_index ) const
245{
246 if( record_index >= QDCOUNT() ) throw Error( "invalid record number" ) ;
247 unsigned int offset = 12U ; // HEADER size
248 for( unsigned int i = 0U ; i < record_index ; i++ )
249 offset += Question(*this,offset).size() ;
250 return Question(*this,offset) ;
251}
252
253GNet::DnsMessageRR GNet::DnsMessage::rr( unsigned int record_index ) const
254{
255 if( record_index < QDCOUNT() ) throw Error( "invalid rr number" ) ;
256 unsigned int offset = 12U ; // HEADER size
257 for( unsigned int i = 0U ; i < record_index ; i++ )
258 {
259 if( i < QDCOUNT() )
260 offset += Question(*this,offset).size() ;
261 else
262 offset += RR(*this,offset).size() ;
263 }
264 return RR( *this , offset ) ;
265}
266
267GNet::Address GNet::DnsMessage::rrAddress( unsigned int record_index ) const
268{
269 return rr(record_index).address() ;
270}
271
272// ==
273
274GNet::DnsMessageQuestion::DnsMessageQuestion( const DnsMessage & msg , unsigned int offset ) :
275 m_size(0U)
276{
277 m_qname = DnsMessageNameParser::read( msg , offset ) ;
278 m_size = DnsMessageNameParser::size( msg , offset ) + 2U + 2U ; // QNAME + QTYPE + QCLASS
279}
280
282{
283 return m_size ;
284}
285
287{
288 return m_qname ;
289}
290
291// ==
292
293unsigned int GNet::DnsMessageNameParser::size( const DnsMessage & msg , unsigned int offset_in )
294{
295 unsigned int offset = offset_in ;
296 for(;;)
297 {
298 unsigned int n = msg.byte( offset ) ;
299 if( ( n & 0xC0 ) == 0xC0 ) // compression -- see RFC-1035 4.1.4
300 return offset - offset_in + 2U ;
301 else if( ( n & 0xC0 ) != 0 )
302 throw GNet::DnsMessage::Error( "unknown label type" ) ; // "reserved for future use"
303 else if( n == 0U )
304 break ;
305 else
306 offset += (n+1U) ;
307 }
308 return offset - offset_in + 1U ;
309}
310
311std::string GNet::DnsMessageNameParser::read( const DnsMessage & msg , unsigned int offset_in )
312{
313 unsigned int offset = offset_in ;
314 std::vector<std::string> name ;
315 for(;;)
316 {
317 unsigned int n = msg.byte( offset ) ;
318 if( ( n & 0xC0 ) == 0xC0 )
319 {
320 unsigned int m = msg.byte(offset+1U) ;
321 offset = (n&0x3F)*256U + m ;
322 }
323 else if( ( n & 0xC0 ) != 0 )
324 {
325 throw GNet::DnsMessage::Error( "unknown label type" ) ; // "reserved for future use"
326 }
327 else if( n == 0U )
328 {
329 break ;
330 }
331 else
332 {
333 if( n > 63U )
334 throw GNet::DnsMessage::Error( "name overflow" ) ;
335
336 name.push_back( msg.span(offset+1U,offset+n+1U) ) ;
337 offset += (n+1U) ;
338 }
339 }
340 return G::Str::join( "." , name ) ;
341}
342
343// ==
344
345GNet::DnsMessageRR::DnsMessageRR( const DnsMessage & msg , unsigned int offset ) :
346 m_msg(msg) ,
347 m_offset(offset) ,
348 m_size(0U) ,
349 m_type(0U) ,
350 m_class(0U) ,
351 m_rdata_offset(0U) ,
352 m_rdata_size(0U)
353{
354 m_name = DnsMessageNameParser::read( msg , offset ) ; // NAME
355 offset += DnsMessageNameParser::size( msg , offset ) ;
356
357 m_type = msg.word( offset ) ; offset += 2U ; // TYPE
358 m_class = msg.word( offset ) ; offset += 2U ; // CLASS
359 offset += 4U ; // TTL
360 m_rdata_size = msg.word( offset ) ; offset += 2U ; // RDLENGTH
361
362 m_rdata_offset = offset ;
363 m_size = offset - m_offset + m_rdata_size ;
364
365 if( m_class != 1U ) // "IN" (internet)
366 throw DnsMessage::Error( "invalid rr class" ) ;
367}
368
369unsigned int GNet::DnsMessageRR::type() const
370{
371 return m_type ;
372}
373
374bool GNet::DnsMessageRR::isa( const std::string & type_name ) const
375{
376 return m_type == DnsMessageRecordType::value(type_name) ;
377}
378
379unsigned int GNet::DnsMessageRR::size() const
380{
381 return m_size ;
382}
383
384std::string GNet::DnsMessageRR::name() const
385{
386 return m_name ;
387}
388
389std::string GNet::DnsMessageRR::rdata_dname( unsigned int rdata_offset ) const
390{
391 return DnsMessageNameParser::read( m_msg , m_rdata_offset + rdata_offset ) ;
392}
393
394std::string GNet::DnsMessageRR::rdata_dname( unsigned int * rdata_offset_p ) const
395{
396 std::string dname = DnsMessageNameParser::read( m_msg , m_rdata_offset + *rdata_offset_p ) ;
397 *rdata_offset_p += DnsMessageNameParser::size( m_msg , m_rdata_offset + *rdata_offset_p ) ;
398 return dname ;
399}
400
401std::string GNet::DnsMessageRR::rdata_span( unsigned int rdata_begin ) const
402{
403 return rdata_span( rdata_begin , rdata_size() ) ;
404}
405
406std::string GNet::DnsMessageRR::rdata_span( unsigned int rdata_begin , unsigned int rdata_end ) const
407{
408 return m_msg.span( m_rdata_offset + rdata_begin , m_rdata_offset + rdata_end ) ;
409}
410
411unsigned int GNet::DnsMessageRR::rdata_offset() const
412{
413 return m_rdata_offset ;
414}
415
416unsigned int GNet::DnsMessageRR::rdata_size() const
417{
418 return m_rdata_size ;
419}
420
421unsigned int GNet::DnsMessageRR::rdata_byte( unsigned int i ) const
422{
423 return m_msg.byte( m_rdata_offset + i ) ;
424}
425
426unsigned int GNet::DnsMessageRR::rdata_word( unsigned int i ) const
427{
428 return m_msg.word( m_rdata_offset + i ) ;
429}
430
432{
433 std::ostringstream ss ;
434 if( isa("A") && rdata_size() == 4U )
435 {
436 ss << rdata_byte(0U) << "." << rdata_byte(1U) << "." << rdata_byte(2U) << "." << rdata_byte(3U) << ":0" ;
437 }
438 else if( isa("AAAA") && rdata_size() == 16U )
439 {
440 const char * sep = "" ;
441 for( unsigned int i = 0 ; i < 8U ; i++ , sep = ":" )
442 ss << sep << std::hex << rdata_word(i*2U) ;
443 ss << ".0" ;
444 }
445 else
446 {
447 throw DnsMessage::Error( "not an address" ) ;
448 }
449 return Address::parse( ss.str() , Address::NotLocal() ) ;
450}
451
452// ==
453
454namespace GNet
455{
456 //| \namespace GNet::DnsMessageRecordTypeImp
457 /// A private implementation namespace for GNet::DnsMessage.
458 namespace DnsMessageRecordTypeImp
459 {
460 struct Pair /// A std::pair-like structure used in GNet::DnsMessage, needed for gcc 4.2.1
461 {
462 unsigned int first ;
463 const char * second ;
464 } ;
465 constexpr std::array<Pair,23U> map = {{
466 { 1 , "A" } , // a host address
467 { 2 , "NS" } , // an authoritative name server
468 { 3 , "MD" } , // a mail destination (Obsolete - use MX)
469 { 4 , "MF" } , // a mail forwarder (Obsolete - use MX)
470 { 5 , "CNAME" } , // the canonical name for an alias
471 { 6 , "SOA" } , // marks the start of a zone of authority
472 { 7 , "MB" } , // a mailbox domain name (EXPERIMENTAL)
473 { 8 , "MG" } , // a mail group member (EXPERIMENTAL)
474 { 9 , "MR" } , // a mail rename domain name (EXPERIMENTAL)
475 { 10 , "NULL_" } , // a null RR (EXPERIMENTAL)
476 { 11 , "WKS" } , // a well known service description
477 { 12 , "PTR" } , // a domain name pointer
478 { 13 , "HINFO" } , // host information
479 { 14 , "MINFO" } , // mailbox or mail list information
480 { 15 , "MX" } , // mail exchange
481 { 16 , "TXT" } , // text strings
482 { 28 , "AAAA" } , // IPv6 -- RFC-3596
483 { 33 , "SRV" } , // service pointer -- RFC-2782
484 { 41 , "OPT" } , // extended options -- EDNS0 -- RFC-2671
485 { 43 , "DS" } , // delegation signer -- DNSSEC -- RFC-4034
486 { 46 , "RRSIG" } , // resource record signature -- DNSSEC -- RFC-4034
487 { 47 , "NSEC" } , // next secure -- DNSSEC -- RFC-4034
488 { 48 , "DNSKEY" } // dns public key -- DNSSEC -- RFC-4034
489 }} ;
490 }
491}
492
493unsigned int GNet::DnsMessageRecordType::value( const std::string & type_name )
494{
495 namespace imp = DnsMessageRecordTypeImp ;
496 for( const auto & item : imp::map )
497 {
498 if( type_name == item.second )
499 return item.first ;
500 }
501 throw DnsMessage::Error( "invalid rr type name" ) ;
502}
503
504std::string GNet::DnsMessageRecordType::name( unsigned int type_value )
505{
506 namespace imp = DnsMessageRecordTypeImp ;
507 for( const auto & item : imp::map )
508 {
509 if( item.first == type_value )
510 return item.second ;
511 }
512 throw DnsMessage::Error( "invalid rr type value" ) ;
513}
514
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:53
static Address parse(const std::string &display_string)
Factory function for any address family.
Definition: gaddress.cpp:217
const sockaddr * address() const
Returns the sockaddr address.
Definition: gaddress.cpp:434
static unsigned int size(const DnsMessage &msg, unsigned int)
Returns the size of the compressed name.
static std::string read(const DnsMessage &msg, unsigned int)
Returns the decompressed name, made up of the labels with dots inbetween.
Represents DNS question record.
Definition: gdnsmessage.h:231
DnsMessageQuestion(const DnsMessage &, unsigned int offset)
Constructor.
std::string qname() const
Returns the subject of the question.
unsigned int size() const
Returns the record size.
Represents DNS response record.
Definition: gdnsmessage.h:181
unsigned int type() const
Returns the type value().
DnsMessageRR(const DnsMessage &, unsigned int offset)
Constructor.
Address address() const
Returns the Address if isa(A) or isa(AAAA).
unsigned int size() const
Returns the size.
bool isa(const std::string &) const
Returns true if the type() has the given name().
std::string name() const
Returns the NAME.
static std::string name(unsigned int type_value)
Returns the type name for the given type value.
static unsigned int value(const std::string &type_name)
Returns the type value for the given type name.
Represents a DNS query message.
Definition: gdnsmessage.h:269
std::size_t n() const
Returns message size.
Definition: gdnsmessage.cpp:83
const char * p() const
Returns a pointer to the message data.
Definition: gdnsmessage.cpp:78
DnsMessageRequest(const std::string &type, const std::string &hostname, unsigned int id=0U)
Constructor.
Definition: gdnsmessage.cpp:32
A DNS message parser, with static factory functions for message composition.
Definition: gdnsmessage.h:52
Address rrAddress(unsigned int n) const
Returns the address in the n'th record treated as a RR record.
unsigned int ANCOUNT() const
Returns the header ANCOUNT field, ie.
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).
unsigned int Z() const
Returns the header Z value (zero).
DnsMessage(const std::vector< char > &buffer)
Constructor.
Definition: gdnsmessage.cpp:93
unsigned int QDCOUNT() const
Returns the header QDCOUNT field, ie.
std::size_t n() const
Returns the raw data size.
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).
const char * p() const
Returns the raw data.
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.
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
static void splitIntoFields(const std::string &in, StringArray &out, string_view ws, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1146
Network classes.
Definition: gdef.h:1115
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
std::string hostname()
Returns the hostname.
Overload discriminator for Address::parse()
Definition: gaddress.h:113
A std::pair-like structure used in GNet::DnsMessage, needed for gcc 4.2.1.