39 static constexpr std::string_view default_timeout_ms {
"5000",4U} ;
40 static constexpr unsigned int default_threshold {1U} ;
44 const std::vector<Address> & m_list ;
45 friend 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) ,
73 m_dns_server(
Address::defaultAddress())
83 configureImp( config ,
nullptr ) ;
85 catch( std::exception & e )
87 throw ConfigError( e.what() ) ;
93 configureImp( config ,
this ) ;
96void GNet::DnsBlock::configureImp( std::string_view config ,
DnsBlock * dnsblock_p )
105 throw BadFieldCount() ;
107 if( list[0].empty() || !isDomain(list[0]) )
110 if( list.size() < 4U )
111 throw BadFieldCount() ;
113 Address dns_server = nameServerAddress( list[0] ) ;
115 bool allow_on_timeout = threshold == 0U || isPositive( list[1] ) ;
116 unsigned int timeout_ms = ms( list[1] ) ;
118 list.erase( list.begin() , list.begin()+3U ) ;
125 namespace imp = DnsBlockImp ;
126 std::size_t domains = 0U ;
127 for( std::size_t i = 0U ; i < list.size() && isDomain(list[i]) ; i++ )
130 auto p = std::next( list.begin() , domains ) ;
131 unsigned int threshold = p == list.end() ? imp::default_threshold :
G::Str::toUInt(*p++) ;
132 bool positive_timeout = isPositive( p == list.end() ? imp::default_timeout_ms : *p ) ;
133 unsigned int timeout_ms = ms( p == list.end() ? imp::default_timeout_ms : *p++ ) ;
134 Address dns_server = nameServerAddress( p == list.end() ? std::string() : *p++ ) ;
135 bool allow_on_timeout = positive_timeout || threshold == 0U ;
136 if( p != list.end() )
137 throw ConfigError(
"unused fields" ) ;
139 list.resize( domains ) ;
148 m_servers = servers ;
149 m_threshold =
static_cast<std::size_t
>(threshold) ;
150 m_allow_on_timeout = allow_on_timeout ;
151 m_dns_server = dns_server ;
152 m_timeout = timeout ;
157 std::vector<Address> list = GNet::nameservers() ;
158 return list.empty() ?
Address::loopback( Address::Family::ipv4 , 53U ) : list[0] ;
161GNet::Address GNet::DnsBlock::nameServerAddress(
const std::string & s )
163 return s.empty() ? nameServerAddress() : Address::parse(s,Address::NotLocal()) ;
166bool GNet::DnsBlock::isDomain( std::string_view s )
noexcept
175bool GNet::DnsBlock::isPositive( std::string_view s )
noexcept
177 return s.empty() || s[0] !=
'-' ;
180unsigned int GNet::DnsBlock::ms( std::string_view s )
182 if( !s.empty() && s[s.size()-1U] ==
's' )
183 return 1000U *
static_cast<unsigned int>( std::abs(
G::Str::toInt(s.substr(0U,s.size()-1U))) ) ;
185 return static_cast<unsigned int>( std::abs(
G::Str::toInt(s)) ) ;
190 G_DEBUG(
"GNet::DnsBlock::start: dns-server=" << m_dns_server.displayString() <<
" "
191 <<
"threshold=" << m_threshold <<
" "
192 <<
"timeout=" << m_timeout <<
"(" << m_allow_on_timeout <<
") "
194 <<
"servers=[" <<
G::Str::join(
",",m_servers) <<
"]" ) ;
196 m_result.reset( m_threshold , address ) ;
201 if( m_servers.empty() || is_local )
203 m_timer.startTimer( 0 ) ;
208 static unsigned int id_generator = 10 ;
209 if( (id_generator+m_servers.size()) > 65535U )
215 m_socket_ptr = std::make_unique<DatagramSocket>( m_dns_server.family() , protocol , datagram_socket_config ) ;
216 m_socket_ptr->addReadHandler( *
this , m_es ) ;
219 std::string prefix = queryString( address ) ;
220 unsigned int id = m_id_base = id_generator ;
221 for( G::StringArray::const_iterator server_p = m_servers.begin() ; server_p != m_servers.end() ;
222 ++server_p ,
id++ , id_generator++ )
228 const char * type = address.
family() == Address::Family::ipv4 ?
"A" :
"AAAA" ;
230 G_DEBUG(
"GNet::DnsBlock::start: sending [" << prefix <<
"."
231 << server <<
"] to [" << m_dns_server.displayString() <<
"]: id " <<
id ) ;
233 ssize_t rc = m_socket_ptr->writeto( message.
p() , message.
n() , m_dns_server ) ;
234 if( rc < 0 ||
static_cast<std::size_t
>(rc) != message.
n() )
235 throw SendError( m_socket_ptr->reason() ) ;
237 m_timer.startTimer( m_timeout ) ;
242 return m_timer.active() ;
245void GNet::DnsBlock::readEvent()
247 static std::vector<char> buffer;
248 buffer.resize( 4096U ) ;
249 ssize_t rc = m_socket_ptr->read( buffer.data() , buffer.size() ) ;
250 if( rc <= 0 ||
static_cast<std::size_t
>(rc) >= buffer.size() )
251 throw BadDnsResponse() ;
252 buffer.resize(
static_cast<std::size_t
>(rc) ) ;
255 if( !message.valid() || !message.QR() || message.ID() < m_id_base ||
256 message.ID() >= (m_id_base+m_servers.size()) || message.RCODE() > 5 )
258 G_WARNING(
"GNet::DnsBlock::readEvent: invalid dns response: qr=" << message.QR()
259 <<
" rcode=" << message.RCODE() <<
" id=" << message.ID() ) ;
263 m_result.at(message.ID()-m_id_base).set( message.addresses() ) ;
265 std::size_t server_count = m_result.list().size() ;
266 std::size_t responder_count = countResponders( m_result.list() ) ;
267 std::size_t laggard_count = server_count - responder_count ;
268 std::size_t deny_count = countDeniers( m_result.list() ) ;
270 G_ASSERT( laggard_count < server_count ) ;
272 G_DEBUG(
"GNet::DnsBlock::readEvent: id=" << message.ID() <<
" rcode=" << message.RCODE()
273 << (message.ANCOUNT()?
" deny ":
" allow ")
274 <<
"got=" << responder_count <<
"/" << server_count <<
" deny-count=" << deny_count <<
"/" << m_threshold ) ;
276 bool finished = m_timer.active() && (
277 responder_count == server_count ||
278 ( m_threshold && deny_count >= m_threshold ) ||
279 ( m_threshold && (deny_count+laggard_count) < m_threshold ) ) ;
283 m_socket_ptr.reset() ;
284 m_timer.cancelTimer() ;
285 m_result.type() = ( m_threshold && deny_count >= m_threshold ) ?
286 DnsBlockResult::Type::Deny :
287 DnsBlockResult::Type::Allow ;
288 m_callback.onDnsBlockResult( m_result ) ;
292void GNet::DnsBlock::onTimeout()
294 m_socket_ptr.reset() ;
295 m_result.type() = m_result.list().empty() ?
296 ( m_servers.empty() ? DnsBlockResult::Type::Inactive : DnsBlockResult::Type::Local ) :
297 ( m_allow_on_timeout ? DnsBlockResult::Type::TimeoutAllow : DnsBlockResult::Type::TimeoutDeny ) ;
298 m_callback.onDnsBlockResult( m_result ) ;
301std::string GNet::DnsBlock::queryString(
const Address & address )
303 return address.queryString() ;
308 using namespace DnsBlockImp ;
309 if( m_type == Type::Local )
311 G_LOG(
"GNet::DnsBlockResult::log: dnsbl: not checking local address [" << m_address.hostPartString() <<
"]" ) ;
313 else if( m_type != Type::Inactive )
315 for(
const auto & result : m_list )
317 std::ostringstream ss ;
318 ss <<
"address [" << m_address.hostPartString() <<
"] " ;
319 if( result.valid() && result.addresses().empty() )
320 ss <<
"allowed by [" << result.server() <<
"]" ;
321 else if( result.valid() )
322 ss <<
"denied by [" << result.server() <<
"]: " << HostList{result.addresses()} ;
324 ss <<
"not checked by [" << result.server() <<
"]" ;
325 G_LOG(
"GNet::DnsBlockResult::log: dnsbl: " << ss.str() ) ;
332 if( m_type == Type::Deny || m_type == Type::TimeoutDeny || m_type == Type::TimeoutAllow )
334 std::ostringstream ss ;
335 ss <<
"client address [" << m_address.hostPartString() <<
"]" ;
336 if( m_type == Type::Deny || m_type == Type::TimeoutDeny )
338 if( m_type == Type::TimeoutDeny || m_type == Type::TimeoutAllow )
339 ss <<
": timeout: no answer from [" <<
G::Str::join(
"] [",laggards()) <<
"]" ;
342 G_WARNING(
"GNet::DnsBlockResult::log: dnsbl: " << ss.str() ) ;
348 return m_type == Type::Inactive || m_type == Type::Local || m_type == Type::TimeoutAllow || m_type == Type::Allow ;
358std::size_t GNet::DnsBlock::countResponders(
const ResultList & list )
360 return static_cast<std::size_t
>( std::count_if( list.begin() , list.end() ,
364std::size_t GNet::DnsBlock::countDeniers(
const ResultList & list )
366 return static_cast<std::size_t
>( std::count_if( list.begin() , list.end() ,
367 [](
const DnsBlockServerResult & r_){return r_.valid() && !r_.addresses().empty();} ) ) ;
372 return DnsBlockImp::server_names_if( m_list.begin() , m_list.end() ,
378 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 &, EventState, std::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.
const char * p() const noexcept
Returns the raw data.
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::size_t n() const noexcept
Returns the raw data size.
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
static int toInt(std::string_view s)
Converts string 's' to an int.
static std::string join(std::string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
static bool isSimple(std::string_view s) noexcept
Returns true if every character is alphanumeric or "-" or "_".
static void splitIntoFields(std::string_view in, StringArray &out, char sep, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
static bool isNumeric(std::string_view s, bool allow_minus_sign=false) noexcept
Returns true if every character is a decimal digit.
static std::string_view tailView(std::string_view in, std::size_t pos, std::string_view default_={}) noexcept
Like tail() but returning a view into the input string.
static unsigned int toUInt(std::string_view s)
Converts string 's' to an unsigned int.
static std::string_view ws() noexcept
Returns a string of standard whitespace characters.
static std::string trimmed(const std::string &s, std::string_view ws)
Returns a trim()med version of s.
static bool enabled() noexcept
Returns true if test features are enabled.
An interval between two G::SystemTime values or two G::TimerTime values.
std::vector< std::string > StringArray
A std::vector of std::strings.
A configuration structure for GNet::DatagramSocket.