40 static constexpr unsigned int default_threshold {1U} ;
44 const std::vector<Address> & m_list ;
45 friend inline std::ostream & operator<<( std::ostream & stream ,
const HostList & list )
47 const char * sep =
"" ;
48 for(
auto p = list.m_list.begin() ; p != list.m_list.end() ; ++p , sep =
" " )
50 stream << sep << (*p).hostPartString() ;
55 template <
typename T,
typename P>
59 for( ; p != end ; ++p )
62 result.push_back( (*p).server() ) ;
70 m_callback(callback) ,
72 m_timer(*this,&
DnsBlock::onTimeout,es) ,
74 m_allow_on_timeout(true) ,
75 m_dns_server(
Address::defaultAddress()) ,
87 configureImp( config ,
nullptr ) ;
89 catch( std::exception & e )
91 throw ConfigError( e.what() ) ;
97 configureImp( config ,
this ) ;
109 throw BadFieldCount() ;
111 if( list[0].empty() || !isDomain(list[0]) )
114 if( list.size() < 4U )
115 throw BadFieldCount() ;
117 Address dns_server = nameServerAddress( list[0] ) ;
119 bool allow_on_timeout = threshold == 0U || isPositive( list[1] ) ;
120 unsigned int timeout_ms = ms( list[1] ) ;
122 list.erase( list.begin() , list.begin()+3U ) ;
129 namespace imp = DnsBlockImp ;
130 std::size_t domains = 0U ;
131 for( std::size_t i = 0U ; i < list.size() && isDomain(list[i]) ; i++ )
134 auto p = std::next( list.begin() , domains ) ;
135 unsigned int threshold = p == list.end() ? imp::default_threshold :
G::Str::toUInt(*p++) ;
136 bool positive_timeout = isPositive( p == list.end() ? imp::default_timeout_ms : *p ) ;
137 unsigned int timeout_ms = ms( p == list.end() ? imp::default_timeout_ms : *p++ ) ;
138 Address dns_server = nameServerAddress( p == list.end() ? std::string() : *p++ ) ;
139 bool allow_on_timeout = positive_timeout || threshold == 0U ;
140 if( p != list.end() )
141 throw ConfigError(
"unused fields" ) ;
143 list.resize( domains ) ;
152 m_servers = servers ;
153 m_threshold =
static_cast<std::size_t
>(threshold) ;
154 m_allow_on_timeout = allow_on_timeout ;
155 m_dns_server = dns_server ;
156 m_timeout = timeout ;
161 std::vector<Address> list = GNet::nameservers() ;
162 return list.empty() ?
Address::loopback( Address::Family::ipv4 , 53U ) : list[0] ;
165GNet::Address GNet::DnsBlock::nameServerAddress(
const std::string & s )
167 return s.empty() ? nameServerAddress() : Address::parse(s,Address::NotLocal()) ;
181 return s.empty() || s[0] !=
'-' ;
186 if( !s.empty() && s[s.size()-1U] ==
's' )
187 return 1000U *
static_cast<unsigned int>( std::abs(
G::Str::toInt(s.substr(0U,s.size()-1U))) ) ;
189 return static_cast<unsigned int>( std::abs(
G::Str::toInt(s)) ) ;
194 G_DEBUG(
"GNet::DnsBlock::start: dns-server=" << m_dns_server.displayString() <<
" "
195 <<
"threshold=" << m_threshold <<
" "
196 <<
"timeout=" << m_timeout <<
"(" << m_allow_on_timeout <<
") "
198 <<
"servers=[" <<
G::Str::join(
",",m_servers) <<
"]" ) ;
200 m_result.reset( m_threshold , address ) ;
205 if( m_servers.empty() || is_local )
207 m_timer.startTimer( 0 ) ;
212 static unsigned int id_generator = 10 ;
213 if( (id_generator+m_servers.size()) > 65535U )
219 m_socket_ptr = std::make_unique<DatagramSocket>( m_dns_server.family() , protocol , datagram_socket_config ) ;
220 m_socket_ptr->addReadHandler( *
this , m_es ) ;
223 std::string prefix = queryString( address ) ;
224 unsigned int id = m_id_base = id_generator ;
225 for( G::StringArray::const_iterator server_p = m_servers.begin() ; server_p != m_servers.end() ;
226 ++server_p ,
id++ , id_generator++ )
232 const char * type = address.
family() == Address::Family::ipv4 ?
"A" :
"AAAA" ;
234 G_DEBUG(
"GNet::DnsBlock::start: sending [" << prefix <<
"."
235 << server <<
"] to [" << m_dns_server.displayString() <<
"]: id " <<
id ) ;
237 ssize_t rc = m_socket_ptr->writeto( message.p() , message.n() , m_dns_server ) ;
238 if( rc < 0 ||
static_cast<std::size_t
>(rc) != message.n() )
239 throw SendError( m_socket_ptr->reason() ) ;
241 m_timer.startTimer( m_timeout ) ;
246 return m_timer.active() ;
249void GNet::DnsBlock::readEvent()
251 static std::vector<char> buffer;
252 buffer.resize( 4096U ) ;
253 ssize_t rc = m_socket_ptr->read( &buffer[0] , buffer.size() ) ;
254 if( rc <= 0 ||
static_cast<std::size_t
>(rc) >= buffer.size() )
255 throw BadDnsResponse() ;
256 buffer.resize(
static_cast<std::size_t
>(rc) ) ;
259 if( !message.valid() || !message.QR() || message.ID() < m_id_base ||
260 message.ID() >= (m_id_base+m_servers.size()) || message.RCODE() > 5 )
262 G_WARNING(
"GNet::DnsBlock::readEvent: invalid dns response: qr=" << message.QR()
263 <<
" rcode=" << message.RCODE() <<
" id=" << message.ID() ) ;
267 m_result.at(message.ID()-m_id_base).set( message.addresses() ) ;
269 std::size_t server_count = m_result.list().size() ;
270 std::size_t responder_count = countResponders( m_result.list() ) ;
271 std::size_t laggard_count = server_count - responder_count ;
272 std::size_t deny_count = countDeniers( m_result.list() ) ;
274 G_ASSERT( laggard_count < server_count ) ;
276 G_DEBUG(
"GNet::DnsBlock::readEvent: id=" << message.ID() <<
" rcode=" << message.RCODE()
277 << (message.ANCOUNT()?
" deny ":
" allow ")
278 <<
"got=" << responder_count <<
"/" << server_count <<
" deny-count=" << deny_count <<
"/" << m_threshold ) ;
280 bool finished = m_timer.active() && (
281 responder_count == server_count ||
282 ( m_threshold && deny_count >= m_threshold ) ||
283 ( m_threshold && (deny_count+laggard_count) < m_threshold ) ) ;
287 m_socket_ptr.reset() ;
288 m_timer.cancelTimer() ;
289 m_result.type() = ( m_threshold && deny_count >= m_threshold ) ?
290 DnsBlockResult::Type::Deny :
291 DnsBlockResult::Type::Allow ;
292 m_callback.onDnsBlockResult( m_result ) ;
296void GNet::DnsBlock::onTimeout()
298 m_socket_ptr.reset() ;
299 m_result.type() = m_result.list().empty() ?
300 ( m_servers.empty() ? DnsBlockResult::Type::Inactive : DnsBlockResult::Type::Local ) :
301 ( m_allow_on_timeout ? DnsBlockResult::Type::TimeoutAllow : DnsBlockResult::Type::TimeoutDeny ) ;
302 m_callback.onDnsBlockResult( m_result ) ;
305std::string GNet::DnsBlock::queryString(
const Address & address )
307 return address.queryString() ;
312 using namespace DnsBlockImp ;
313 if( m_type == Type::Local )
315 G_LOG(
"GNet::DnsBlockResult::log: dnsbl: not checking local address [" << m_address.hostPartString() <<
"]" ) ;
317 else if( m_type != Type::Inactive )
319 for(
const auto & result : m_list )
321 std::ostringstream ss ;
322 ss <<
"address [" << m_address.hostPartString() <<
"] " ;
323 if( result.valid() && result.addresses().empty() )
324 ss <<
"allowed by [" << result.server() <<
"]" ;
325 else if( result.valid() )
326 ss <<
"denied by [" << result.server() <<
"]: " << HostList{result.addresses()} ;
328 ss <<
"not checked by [" << result.server() <<
"]" ;
329 G_LOG(
"GNet::DnsBlockResult::log: dnsbl: " << ss.str() ) ;
336 if( m_type == Type::Deny || m_type == Type::TimeoutDeny || m_type == Type::TimeoutAllow )
338 std::ostringstream ss ;
339 ss <<
"client address [" << m_address.hostPartString() <<
"]" ;
340 if( m_type == Type::Deny || m_type == Type::TimeoutDeny )
342 if( m_type == Type::TimeoutDeny || m_type == Type::TimeoutAllow )
343 ss <<
": timeout: no answer from [" <<
G::Str::join(
"] [",laggards()) <<
"]" ;
346 G_WARNING(
"GNet::DnsBlockResult::log: dnsbl: " << ss.str() ) ;
352 return m_type == Type::Inactive || m_type == Type::Local || m_type == Type::TimeoutAllow || m_type == Type::Allow ;
362std::size_t GNet::DnsBlock::countResponders(
const ResultList & list )
364 return static_cast<std::size_t
>( std::count_if( list.begin() , list.end() ,
368std::size_t GNet::DnsBlock::countDeniers(
const ResultList & list )
370 return static_cast<std::size_t
>( std::count_if( list.begin() , list.end() ,
371 [](
const DnsBlockServerResult & r_){return r_.valid() && !r_.addresses().empty();} ) ) ;
376 return DnsBlockImp::server_names_if( m_list.begin() , m_list.end() ,
382 return DnsBlockImp::server_names_if( m_list.begin() , m_list.end() ,
The GNet::Address class encapsulates a TCP/UDP transport address.
bool isLinkLocal() const
Returns true if this is a link-local address.
static Address loopback(Family, unsigned int port=0U)
Returns a loopback address.
bool isUniqueLocal() const
Returns true if this is a locally administered address.
std::string hostPartString() const
Returns a printable string that represents the network address.
bool isLoopback() const
Returns true if this is a loopback address.
Family family() const noexcept
Returns the address family enumeration.
A callback interface for GNet::DnsBlock.
void warn() const
Emits warnings.
bool allow() const
Returns true if the type is Inactive, Local, TimeoutAllow or Allow.
G::StringArray laggards() const
Returns the list of slow or unresponsive servers.
G::StringArray deniers() const
Returns the list of denying servers.
void log() const
Logs the results.
bool deny() const
Returns true if the type is TimeoutDeny or Deny.
A result structure for one DNSBL server.
Implements DNS blocklisting, as per RFC-5782.
static void checkConfig(const std::string &)
Checks the configure() string, throwing on error.
void start(const Address &)
Starts an asychronous check on the given address.
void configure(const Address &dns_server, unsigned int threshold, bool allow_on_timeout, G::TimeInterval timeout, const G::StringArray &servers)
Configures the object after construction.
DnsBlock(DnsBlockCallback &, ExceptionSink, G::string_view config={})
Constructor.
bool busy() const
Returns true after start() and before the completion callback.
A DNS message parser, with static factory functions for message composition.
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).
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
static string_view tailView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like tail() but returning a view into the input string.
static unsigned int toUInt(string_view s)
Converts string 's' to an unsigned int.
static bool isNumeric(string_view s, bool allow_minus_sign=false) noexcept
Returns true if every character is a decimal digit.
static int toInt(string_view s)
Converts string 's' to an int.
static std::string join(string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
static bool isSimple(string_view s) noexcept
Returns true if every character is alphanumeric or "-" or "_".
static void splitIntoFields(string_view in, StringArray &out, char sep, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
static std::string trimmed(const std::string &s, string_view ws)
Returns a trim()med version of s.
static string_view ws() noexcept
Returns a string of standard whitespace characters.
static bool enabled() noexcept
Returns true if test features are enabled.
An interval between two G::SystemTime values or two G::TimerTime values.
A class like c++17's std::string_view.
std::vector< std::string > StringArray
A std::vector of std::strings.
A configuration structure for GNet::DatagramSocket.