E-MailRelay
gdnsmessage.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 G::string_view domain_sv( domain ) ;
54 for( G::StringFieldView part( domain_sv , sep ) ; part ; ++part )
55 {
56 addLabel( part() ) ;
57 }
58 addLabel( G::string_view() ) ; // zero-length root
59}
60
61void GNet::DnsMessageRequest::addLabel( G::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[0] ;
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 DnsMessage() ;
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 DnsMessage( 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 std::string( 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[0]) ;
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 Question(*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 RR( *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
313GNet::DnsMessageQuestion::DnsMessageQuestion( const DnsMessage & msg , unsigned int offset ) :
314 m_size(0U)
315{
316 m_qname = DnsMessageNameParser::read( msg , offset ) ;
317 unsigned int qname_size = DnsMessageNameParser::size( msg , offset ) ;
318 m_qtype = msg.word( offset + qname_size ) ;
319 m_qclass = msg.word( offset + qname_size + 2U ) ;
320 m_size = qname_size + 2U + 2U ; // QNAME + QTYPE + QCLASS
321}
322
324{
325 return m_size ;
326}
327
329{
330 return m_qname ;
331}
332
334{
335 return m_qtype ; // eg. AAAA
336}
337
338#ifndef G_LIB_SMALL
340{
341 return m_qclass ; // eg. IN
342}
343#endif
344
345// ==
346
347unsigned int GNet::DnsMessageNameParser::size( const DnsMessage & msg , unsigned int offset_in )
348{
349 unsigned int offset = offset_in ;
350 for(;;)
351 {
352 unsigned int n = msg.byte( offset ) ;
353 if( ( n & 0xC0 ) == 0xC0 ) // compression -- see RFC-1035 4.1.4
354 return offset - offset_in + 2U ;
355 else if( ( n & 0xC0 ) != 0 )
356 throw DnsMessage::Error( "unknown label type" ) ; // "reserved for future use"
357 else if( n == 0U )
358 break ;
359 else
360 offset += (n+1U) ;
361 }
362 return offset - offset_in + 1U ;
363}
364
365std::string GNet::DnsMessageNameParser::read( const DnsMessage & msg , unsigned int offset_in )
366{
367 unsigned int offset = offset_in ;
368 std::string result ;
369 for(;;)
370 {
371 unsigned int n = msg.byte( offset ) ;
372 if( ( n & 0xC0U ) == 0xC0U )
373 {
374 unsigned int m = msg.byte(offset+1U) ;
375 offset = (n&0x3FU)*256U + m ;
376 if( offset > offset_in )
377 throw DnsMessage::Error( "invalid label" ) ;
378 }
379 else if( ( n & 0xC0U ) != 0U )
380 {
381 throw DnsMessage::Error( "unknown label type" ) ; // "reserved for future use"
382 }
383 else if( n == 0U )
384 {
385 break ;
386 }
387 else
388 {
389 if( n > 63U )
390 throw DnsMessage::Error( "name overflow" ) ;
391
392 result.append( result.empty()?0U:1U , '.' ) ;
393 result.append( msg.span(offset+1U,offset+n+1U) ) ;
394 offset += (n+1U) ;
395 }
396 }
397 return result ;
398}
399
400// ==
401
402GNet::DnsMessageRR::DnsMessageRR( const DnsMessage & msg , unsigned int offset ) :
403 m_msg(msg) ,
404 m_offset(offset) ,
405 m_size(0U) ,
406 m_type(0U) ,
407 m_class(0U) ,
408 m_rdata_offset(0U) ,
409 m_rdata_size(0U)
410{
411 m_name = DnsMessageNameParser::read( msg , offset ) ; // NAME
413
414 m_type = msg.word( offset ) ; offset += 2U ; // TYPE
415 m_class = msg.word( offset ) ; offset += 2U ; // CLASS
416 offset += 4U ; // TTL
417 m_rdata_size = msg.word( offset ) ; offset += 2U ; // RDLENGTH
418
419 m_rdata_offset = offset ;
420 m_size = offset - m_offset + m_rdata_size ;
421
422 if( m_class != 1U ) // "IN" (internet)
423 throw DnsMessage::Error( "invalid rr class" ) ;
424}
425
426#ifndef G_LIB_SMALL
427unsigned int GNet::DnsMessageRR::type() const
428{
429 return m_type ;
430}
431#endif
432
433#ifndef G_LIB_SMALL
434unsigned int GNet::DnsMessageRR::class_() const
435{
436 return m_class ;
437}
438#endif
439
440bool GNet::DnsMessageRR::isa( G::string_view type_name ) const noexcept
441{
442 return m_type == DnsMessageRecordType::value( type_name , std::nothrow ) ;
443}
444
445unsigned int GNet::DnsMessageRR::size() const
446{
447 return m_size ;
448}
449
450#ifndef G_LIB_SMALL
451std::string GNet::DnsMessageRR::name() const
452{
453 return m_name ;
454}
455#endif
456
457std::string GNet::DnsMessageRR::rdataDname( unsigned int rdata_offset ) const
458{
459 return DnsMessageNameParser::read( m_msg , m_rdata_offset + rdata_offset ) ;
460}
461
462#ifndef G_LIB_SMALL
463std::string GNet::DnsMessageRR::rdataDname( unsigned int * rdata_offset_p ) const
464{
465 std::string dname = DnsMessageNameParser::read( m_msg , m_rdata_offset + *rdata_offset_p ) ;
466 *rdata_offset_p += DnsMessageNameParser::size( m_msg , m_rdata_offset + *rdata_offset_p ) ;
467 return dname ;
468}
469#endif
470
471#ifndef G_LIB_SMALL
472std::string GNet::DnsMessageRR::rdataSpan( unsigned int rdata_begin ) const
473{
474 return rdataSpan( rdata_begin , rdataSize() ) ;
475}
476#endif
477
478std::string GNet::DnsMessageRR::rdataSpan( unsigned int rdata_begin , unsigned int rdata_end ) const
479{
480 return m_msg.span( m_rdata_offset + rdata_begin , m_rdata_offset + rdata_end ) ;
481}
482
483#ifndef G_LIB_SMALL
484unsigned int GNet::DnsMessageRR::rdataOffset() const
485{
486 return m_rdata_offset ;
487}
488#endif
489
490unsigned int GNet::DnsMessageRR::rdataSize() const
491{
492 return m_rdata_size ;
493}
494
495unsigned int GNet::DnsMessageRR::rdataByte( unsigned int i ) const
496{
497 return m_msg.byte( m_rdata_offset + i ) ;
498}
499
500unsigned int GNet::DnsMessageRR::rdataWord( unsigned int i ) const
501{
502 return m_msg.word( m_rdata_offset + i ) ;
503}
504
505GNet::Address GNet::DnsMessageRR::address( unsigned int port , std::nothrow_t ) const
506{
507 bool ok = false ;
508 return addressImp( port , ok ) ;
509}
510
512{
513 bool ok = false ;
514 auto a = addressImp( port , ok ) ;
515 if( !ok )
516 throw DnsMessage::Error( "not an address" ) ;
517 return a ;
518}
519
520GNet::Address GNet::DnsMessageRR::addressImp( unsigned int port , bool & ok ) const
521{
522 std::ostringstream ss ;
523 if( isa("A") && rdataSize() == 4U )
524 {
525 ss << rdataByte(0U) << "." << rdataByte(1U) << "." << rdataByte(2U) << "." << rdataByte(3U) << ":" << port ;
526 }
527 else if( isa("AAAA") && rdataSize() == 16U )
528 {
529 const char * sep = "" ;
530 for( unsigned int i = 0 ; i < 8U ; i++ , sep = ":" )
531 ss << sep << std::hex << rdataWord(i*2U) ;
532 ss << "." << port ;
533 }
534 ok = Address::validString( ss.str() , Address::NotLocal() ) ;
535 return ok ? Address::parse( ss.str() , Address::NotLocal() ) : Address::defaultAddress() ;
536}
537
538// ==
539
540namespace GNet
541{
542 namespace DnsMessageRecordTypeImp
543 {
544 struct Pair /// A std::pair-like structure used in GNet::DnsMessage, needed for gcc 4.2.1
545 {
546 unsigned int first ;
547 const char * second ;
548 } ;
549 constexpr std::array<Pair,23U> map = {{
550 { 1 , "A" } , // a host address
551 { 2 , "NS" } , // an authoritative name server
552 { 3 , "MD" } , // a mail destination (Obsolete - use MX)
553 { 4 , "MF" } , // a mail forwarder (Obsolete - use MX)
554 { 5 , "CNAME" } , // the canonical name for an alias
555 { 6 , "SOA" } , // marks the start of a zone of authority
556 { 7 , "MB" } , // a mailbox domain name (EXPERIMENTAL)
557 { 8 , "MG" } , // a mail group member (EXPERIMENTAL)
558 { 9 , "MR" } , // a mail rename domain name (EXPERIMENTAL)
559 { 10 , "NULL_" } , // a null RR (EXPERIMENTAL)
560 { 11 , "WKS" } , // a well known service description
561 { 12 , "PTR" } , // a domain name pointer
562 { 13 , "HINFO" } , // host information
563 { 14 , "MINFO" } , // mailbox or mail list information
564 { 15 , "MX" } , // mail exchange
565 { 16 , "TXT" } , // text strings
566 { 28 , "AAAA" } , // IPv6 -- RFC-3596
567 { 33 , "SRV" } , // service pointer -- RFC-2782
568 { 41 , "OPT" } , // extended options -- EDNS0 -- RFC-2671
569 { 43 , "DS" } , // delegation signer -- DNSSEC -- RFC-4034
570 { 46 , "RRSIG" } , // resource record signature -- DNSSEC -- RFC-4034
571 { 47 , "NSEC" } , // next secure -- DNSSEC -- RFC-4034
572 { 48 , "DNSKEY" } // dns public key -- DNSSEC -- RFC-4034
573 }} ;
574 }
575}
576
577unsigned int GNet::DnsMessageRecordType::value( G::string_view type_name , std::nothrow_t ) noexcept
578{
579 namespace imp = DnsMessageRecordTypeImp ;
580 for( const auto & item : imp::map )
581 {
582 if( G::Str::match( type_name , item.second ) )
583 return item.first ;
584 }
585 return 0U ;
586}
587
589{
590 unsigned int v = value( type_name , std::nothrow ) ;
591 if( v == 0U )
592 throw DnsMessage::Error( "invalid rr type name" ) ;
593 return v ;
594}
595
596std::string GNet::DnsMessageRecordType::name( unsigned int type_value )
597{
598 namespace imp = DnsMessageRecordTypeImp ;
599 for( const auto & item : imp::map )
600 {
601 if( item.first == type_value )
602 return item.second ;
603 }
604 throw DnsMessage::Error( "invalid rr type value" ) ;
605}
606
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:62
static Address parse(const std::string &display_string)
Factory function for any address family.
Definition: gaddress.cpp:178
static bool validString(const std::string &display_string, std::string *reason=nullptr)
Returns true if the transport-address display string is valid.
Definition: gaddress.cpp:385
const sockaddr * address() const
Returns the sockaddr address.
Definition: gaddress.cpp:419
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:305
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).
std::string name() const
Returns the RR NAME.
bool isa(G::string_view) const noexcept
Returns true if the type() has the given name().
static unsigned int value(G::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:351
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(string_view, string_view) noexcept
Returns true if the two strings are the same.
Definition: gstr.cpp:1395
A zero-copy string field iterator where the field separators are short fixed strings.
Definition: gstringfield.h:53
A class like c++17's std::string_view.
Definition: gstringview.h:51
Network classes.
Definition: gdef.h:1144
std::string hostname()
Returns the hostname.