38 enum class Result { error , fatal , mx , cname , ip } ;
56 const std::vector<GNet::Address> & nameservers ) :
59 m_message_id(
GStore::MessageId::none()) ,
62 m_nameservers(nameservers) ,
63 m_timer(*this,&
MxLookup::onTimeout,es)
65 if( m_nameservers.empty() )
71 bool ipv4 = std::find_if( m_nameservers.begin() , m_nameservers.end() ,
72 [](
const GNet::Address &a_){return a_.is4();} ) != m_nameservers.end() ;
76 m_socket4->addReadHandler( *
this , m_es ) ;
77 G_DEBUG(
"GFilters::MxLookup::ctor: ipv4 udp socket: " << (*m_socket4).getLocalAddress().displayString() ) ;
80 bool ipv6 = std::find_if( m_nameservers.begin() , m_nameservers.end() ,
81 [](
const GNet::Address &a_){return a_.is6();} ) != m_nameservers.end() ;
85 m_socket6->addReadHandler( *
this , m_es ) ;
86 G_DEBUG(
"GFilters::MxLookup::ctor: ipv6 udp socket: " << (*m_socket6).getLocalAddress().displayString() ) ;
92 if( !m_socket4 && !m_socket6 )
94 fail(
"no nameserver" ) ;
96 else if( forward_to.empty() )
98 fail(
"invalid empty doman" ) ;
102 m_message_id = message_id ;
103 m_port = port ? port : 25U ;
106 m_question = forward_to ;
107 sendMxQuestion( m_ns_index , m_question ) ;
112void GFilters::MxLookup::readEvent()
114 G_DEBUG(
"GFilters::MxLookup::readEvent" ) ;
115 std::vector<char> buffer( 4096U ) ;
116 ssize_t nread = m_socket4->read( &buffer[0] , buffer.size() ) ;
118 process( &buffer[0] ,
static_cast<std::size_t
>(nread) ) ;
119 else if( (nread=m_socket6->read( &buffer[0] , buffer.size() )) > 0 )
120 process( &buffer[0] ,
static_cast<std::size_t
>(nread) ) ;
122 fail(
"dns socket error" ) ;
125void GFilters::MxLookup::process(
const char * p , std::size_t n )
127 G_DEBUG(
"GFilters::MxLookup::process: dns message size " << n ) ;
128 using namespace MxLookupImp ;
130 if( response.valid() && response.QR() && response.ID() && response.ID() < (m_nameservers.size()+1U) )
132 std::size_t ns_index =
static_cast<std::size_t
>(response.ID()) - 1U ;
133 auto pair = parse( response , m_nameservers.at(ns_index) , m_port ) ;
134 if( pair.first == Result::error && (m_ns_failures+1U) < m_nameservers.size() )
135 disable( ns_index , pair.second ) ;
136 else if( pair.first == Result::error || pair.first == Result::fatal )
137 fail( pair.second ) ;
138 else if( pair.first == Result::mx )
139 sendHostQuestion( ns_index , pair.second ) ;
140 else if( pair.first == Result::cname )
141 sendMxQuestion( ns_index , pair.second ) ;
142 else if( pair.first == Result::ip )
143 succeed( pair.second ) ;
147void GFilters::MxLookup::disable( std::size_t ns_index ,
const std::string & reason )
149 G_LOG_MORE(
"GFilters::MxLookup::disable: mx: nameserver "
150 <<
"[" << m_nameservers.at(ns_index).displayString() <<
"] disabled (" << reason <<
")" ) ;
155std::pair<GFilters::MxLookupImp::Result,std::string> GFilters::MxLookupImp::parse(
const GNet::DnsMessage & response ,
158 G_ASSERT( port != 0U ) ;
160 if( response.
RCODE() == 3 && response.
AA() )
162 return { Result::fatal ,
"rcode nxdomain" + from } ;
164 if( response.
RCODE() != 0 )
168 else if( response.
ANCOUNT() == 0U )
170 return { Result::error ,
"no answer section" + from } ;
175 std::string cname_result ;
176 std::string mx_result ;
177 unsigned int mx_pr = 0U ;
180 unsigned int offset = response.
QDCOUNT() ;
181 for(
unsigned int i = 0 ; i < response.
ANCOUNT() ; i++ )
183 auto rr = response.
rr( i + offset ) ;
186 unsigned int pr = rr.
rdata().
word( 0U ) ;
187 std::string name = rr.rdata().dname( 2U ) ;
188 G_LOG_MORE(
"GFilters::MxLookupImp::parse: mx: answer: "
189 <<
"mx [" << name <<
"](priority " << pr <<
")" << from ) ;
190 if( !name.empty() && ( mx_result.empty() || pr < mx_pr ) )
196 else if( rr.isa(
"CNAME") )
198 std::string cname = rr.rdata().dname( 0U ) ;
199 G_LOG_MORE(
"GFilters::MxLookupImp::parse: mx: answer: "
200 <<
"cname [" << cname <<
"]" << from ) ;
201 cname_result = cname ;
206 G_LOG_MORE_IF( a.
port() ,
"GFilters::MxLookupImp::parse: mx: answer: "
208 if( address.
port() == 0U && a.
port() != 0U )
213 if( !cname_result.empty() )
214 return { Result::cname , cname_result } ;
215 else if( address.
port() != 0U )
217 else if( !mx_result.empty() )
218 return { Result::mx , mx_result } ;
220 return { Result::error ,
"invalid response" + from } ;
224void GFilters::MxLookup::sendMxQuestion( std::size_t ns_index ,
const std::string & mx_question )
228 G_LOG_MORE(
"GFilters::MxLookup::sendMxQuestion: mx: question: mx [" << mx_question <<
"] "
229 <<
"to " << m_nameservers[ns_index].hostPartString()
230 << (m_nameservers[ns_index].port()==53U?
"":(
" port "+
G::Str::fromUInt(m_nameservers[ns_index].port()))) ) ;
231 unsigned int id =
static_cast<unsigned int>(ns_index) + 1U ;
233 socket(ns_index).writeto( request.p() , request.n() , m_nameservers[ns_index] ) ;
237void GFilters::MxLookup::sendHostQuestion( std::size_t ns_index ,
const std::string & host_question )
241 G_LOG_MORE(
"GFilters::MxLookup::sendHostQuestion: mx: question: host-ip [" << host_question <<
"] "
242 <<
"to " << m_nameservers[ns_index].hostPartString() ) ;
243 unsigned int id =
static_cast<unsigned int>(ns_index) + 1U ;
245 socket(ns_index).writeto( request.p() , request.n() , m_nameservers[ns_index] ) ;
252 m_timer.cancelTimer() ;
255void GFilters::MxLookup::dropReadHandlers()
258 m_socket4->dropReadHandler() ;
260 m_socket6->dropReadHandler() ;
263void GFilters::MxLookup::fail(
const std::string & error )
265 m_error =
"mx: " + error ;
267 m_timer.startTimer( 0U ) ;
270void GFilters::MxLookup::onTimeout()
272 if( !m_error.empty() )
275 m_done_signal.emit( m_message_id ,
"" , m_error ) ;
280 if( m_ns_index == m_nameservers.size() )
283 sendMxQuestion( m_ns_index , m_question ) ;
288void GFilters::MxLookup::startTimer()
290 bool last = (m_ns_index+1U) == m_nameservers.size() ;
291 G::TimeInterval timeout = last ? m_config.restart_timeout : m_config.ns_timeout ;
292 m_timer.startTimer( timeout ) ;
295void GFilters::MxLookup::succeed(
const std::string & result )
298 m_done_signal.emit( m_message_id , result ,
"" ) ;
303 return m_nameservers.at(ns_index).is4() ? *m_socket4 : *m_socket6 ;
308 return m_done_signal ;
311GFilters::MxLookup::Config::Config()
MxLookup(GNet::ExceptionSink, Config={})
Constructor.
G::Slot::Signal< GStore::MessageId, std::string, std::string > & doneSignal() noexcept
Returns a reference to the completion signal.
static bool enabled()
Returns true if implemented.
void start(const GStore::MessageId &, const std::string &question_domain, unsigned int port)
Starts the lookup.
void cancel()
Cancels the lookup so the doneSignal() is not emitted.
The GNet::Address class encapsulates a TCP/UDP transport address.
static Address loopback(Family, unsigned int port=0U)
Returns a loopback address.
static Address defaultAddress()
Returns a default address, being the IPv4 wildcard address with a zero port number.
std::string displayString(bool with_scope_id=false) const
Returns a printable string that represents the transport address.
unsigned int port() const
Returns port part of the address.
std::string hostPartString() const
Returns a printable string that represents the network address.
const sockaddr * address() const
Returns the sockaddr address.
A derivation of GNet::Socket for a datagram socket.
unsigned int word(unsigned int offset) const
Calls rdataWord().
const DnsMessageRRData & rdata() const
Provides access to the message RDATA.
Represents a DNS query message.
A DNS message parser, with static factory functions for message composition.
unsigned int ANCOUNT() const
Returns the header ANCOUNT field, ie.
unsigned int RCODE() const
Returns the header RCODE.
unsigned int QDCOUNT() const
Returns the header QDCOUNT field, ie.
bool AA() const
Returns the header AA flag (authorative).
RR rr(unsigned int n) const
Returns the n'th record as a RR record.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
A somewhat opaque identifer for a GStore::MessageStore message id.
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
An interval between two G::SystemTime values or two G::TimerTime values.
A class template like a simplified c++17 std::optional.
A configuration structure for GFilters::MxLookup.
A configuration structure for GNet::DatagramSocket.