37 enum class Result { error , fatal , mx , cname , ip } ;
55 const std::vector<GNet::Address> & nameservers ) :
58 m_message_id(
GStore::MessageId::none()) ,
61 m_nameservers(nameservers) ,
62 m_timer(*this,&
MxLookup::onTimeout,es)
64 if( m_nameservers.empty() )
70 bool ipv4 = std::find_if( m_nameservers.begin() , m_nameservers.end() ,
71 [](
const GNet::Address &a_){return a_.is4();} ) != m_nameservers.end() ;
75 m_socket4->addReadHandler( *
this , m_es ) ;
76 G_DEBUG(
"GFilters::MxLookup::ctor: ipv4 udp socket: " << (*m_socket4).getLocalAddress().displayString() ) ;
79 bool ipv6 = std::find_if( m_nameservers.begin() , m_nameservers.end() ,
80 [](
const GNet::Address &a_){return a_.is6();} ) != m_nameservers.end() ;
84 m_socket6->addReadHandler( *
this , m_es ) ;
85 G_DEBUG(
"GFilters::MxLookup::ctor: ipv6 udp socket: " << (*m_socket6).getLocalAddress().displayString() ) ;
91 if( !m_socket4 && !m_socket6 )
93 fail(
"no nameserver" ) ;
95 else if( forward_to.empty() )
97 fail(
"invalid empty doman" ) ;
101 m_message_id = message_id ;
102 m_port = port ? port : 25U ;
105 m_question = forward_to ;
106 sendMxQuestion( m_ns_index , m_question ) ;
111void GFilters::MxLookup::readEvent()
113 G_DEBUG(
"GFilters::MxLookup::readEvent" ) ;
114 std::vector<char> buffer( 4096U ) ;
115 ssize_t nread = m_socket4->read( buffer.data() , buffer.size() ) ;
117 process( buffer.data() ,
static_cast<std::size_t
>(nread) ) ;
118 else if( (nread=m_socket6->read( buffer.data() , buffer.size() )) > 0 )
119 process( buffer.data() ,
static_cast<std::size_t
>(nread) ) ;
121 fail(
"dns socket error" ) ;
124void GFilters::MxLookup::process(
const char * p , std::size_t n )
126 G_DEBUG(
"GFilters::MxLookup::process: dns message size " << n ) ;
127 using namespace MxLookupImp ;
129 if( response.valid() && response.QR() && response.ID() && response.ID() < (m_nameservers.size()+1U) )
131 std::size_t ns_index =
static_cast<std::size_t
>(response.ID()) - 1U ;
132 auto pair = parse( response , m_nameservers.at(ns_index) , m_port ) ;
133 if( pair.first == Result::error && (m_ns_failures+1U) < m_nameservers.size() )
134 disable( ns_index , pair.second ) ;
135 else if( pair.first == Result::error || pair.first == Result::fatal )
136 fail( pair.second ) ;
137 else if( pair.first == Result::mx )
138 sendHostQuestion( ns_index , pair.second ) ;
139 else if( pair.first == Result::cname )
140 sendMxQuestion( ns_index , pair.second ) ;
141 else if( pair.first == Result::ip )
142 succeed( pair.second ) ;
146void GFilters::MxLookup::disable( std::size_t ns_index ,
const std::string & reason )
148 G_LOG_MORE(
"GFilters::MxLookup::disable: mx: nameserver "
149 <<
"[" << m_nameservers.at(ns_index).displayString() <<
"] disabled (" << reason <<
")" ) ;
154std::pair<GFilters::MxLookupImp::Result,std::string> GFilters::MxLookupImp::parse(
const GNet::DnsMessage & response ,
157 G_ASSERT( port != 0U ) ;
159 if( response.
RCODE() == 3 && response.
AA() )
161 return { Result::fatal ,
"rcode nxdomain" + from } ;
163 if( response.
RCODE() != 0 )
167 else if( response.
ANCOUNT() == 0U )
169 return { Result::error ,
"no answer section" + from } ;
174 std::string cname_result ;
175 std::string mx_result ;
176 unsigned int mx_pr = 0U ;
178 unsigned int offset = response.
QDCOUNT() ;
179 for(
unsigned int i = 0 ; i < response.
ANCOUNT() ; i++ )
181 auto rr = response.
rr( i + offset ) ;
184 unsigned int pr = rr.
rdata().
word( 0U ) ;
185 std::string name = rr.rdata().dname( 2U ) ;
186 G_LOG_MORE(
"GFilters::MxLookupImp::parse: mx: answer: "
187 <<
"mx [" << name <<
"](priority " << pr <<
")" << from ) ;
188 if( !name.empty() && ( mx_result.empty() || pr < mx_pr ) )
194 else if( rr.isa(
"CNAME") )
196 std::string cname = rr.rdata().dname( 0U ) ;
197 G_LOG_MORE(
"GFilters::MxLookupImp::parse: mx: answer: "
198 <<
"cname [" << cname <<
"]" << from ) ;
199 cname_result = cname ;
204 G_LOG_MORE_IF( a.
port() ,
"GFilters::MxLookupImp::parse: mx: answer: "
206 if( address.
port() == 0U && a.
port() != 0U )
211 if( !cname_result.empty() )
212 return { Result::cname , cname_result } ;
213 else if( address.
port() != 0U )
215 else if( !mx_result.empty() )
216 return { Result::mx , mx_result } ;
218 return { Result::error ,
"invalid response" + from } ;
222void GFilters::MxLookup::sendMxQuestion( std::size_t ns_index ,
const std::string & mx_question )
226 G_LOG_MORE(
"GFilters::MxLookup::sendMxQuestion: mx: question: mx [" << mx_question <<
"] "
227 <<
"to " << m_nameservers[ns_index].hostPartString()
228 << (m_nameservers[ns_index].port()==53U?
"":(
" port "+
G::Str::fromUInt(m_nameservers[ns_index].port()))) ) ;
229 unsigned int id =
static_cast<unsigned int>(ns_index) + 1U ;
231 socket(ns_index).writeto( request.p() , request.n() , m_nameservers[ns_index] ) ;
235void GFilters::MxLookup::sendHostQuestion( std::size_t ns_index ,
const std::string & host_question )
239 G_LOG_MORE(
"GFilters::MxLookup::sendHostQuestion: mx: question: host-ip [" << host_question <<
"] "
240 <<
"to " << m_nameservers[ns_index].hostPartString() ) ;
241 unsigned int id =
static_cast<unsigned int>(ns_index) + 1U ;
243 socket(ns_index).writeto( request.p() , request.n() , m_nameservers[ns_index] ) ;
250 m_timer.cancelTimer() ;
253void GFilters::MxLookup::dropReadHandlers()
256 m_socket4->dropReadHandler() ;
258 m_socket6->dropReadHandler() ;
261void GFilters::MxLookup::fail(
const std::string & error )
263 m_error =
"mx: " + error ;
265 m_timer.startTimer( 0U ) ;
268void GFilters::MxLookup::onTimeout()
270 if( !m_error.empty() )
273 m_done_signal.emit( m_message_id ,
"" , m_error ) ;
278 if( m_ns_index == m_nameservers.size() )
281 sendMxQuestion( m_ns_index , m_question ) ;
286void GFilters::MxLookup::startTimer()
288 bool last = (m_ns_index+1U) == m_nameservers.size() ;
289 G::TimeInterval timeout = last ? m_config.restart_timeout : m_config.ns_timeout ;
290 m_timer.startTimer( timeout ) ;
293void GFilters::MxLookup::succeed(
const std::string & result )
296 m_done_signal.emit( m_message_id , result ,
"" ) ;
301 return m_nameservers.at(ns_index).is4() ? *m_socket4 : *m_socket6 ;
306 return m_done_signal ;
309GFilters::MxLookup::Config::Config()
MxLookup(GNet::EventState, 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 lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
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 configuration structure for GFilters::MxLookup.
A configuration structure for GNet::DatagramSocket.