E-MailRelay
gdnsblock.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 gdnsblock.cpp
19///
20
21#include "gdef.h"
22#include "gdnsblock.h"
23#include "gdnsmessage.h"
24#include "gresolver.h"
25#include "glocal.h"
26#include "gstr.h"
27#include "gtest.h"
28#include "gassert.h"
29#include "glog.h"
30#include <sstream>
31#include <algorithm>
32#include <cstdlib>
33
34namespace GNet
35{
36 namespace DnsBlockImp /// An implementation namespace for GNet::DnsBlock.
37 {
38 struct Responder /// A functor that tests a GNet::DnsBlockServerResult.
39 {
40 bool operator()( const GNet::DnsBlockServerResult & r ) const
41 {
42 return r.valid() ;
43 }
44 } ;
45 struct Denier /// A functor that tests a GNet::DnsBlockServerResult.
46 {
47 bool operator()( const GNet::DnsBlockServerResult & r ) const
48 {
49 return r.valid() && !r.addresses().empty() ;
50 }
51 } ;
52 struct Laggard /// A functor that tests a GNet::DnsBlockServerResult.
53 {
54 bool operator()( const GNet::DnsBlockServerResult & r ) const
55 {
56 return !r.valid() ;
57 }
58 } ;
59 struct HostList /// A list of addresses used by GNet::DnsBlock.
60 {
61 HostList( const std::vector<GNet::Address> & list ) : m_list(list) {}
62 const std::vector<GNet::Address> & m_list ;
63 } ;
64 std::ostream & operator<<( std::ostream & stream , const HostList & list )
65 {
66 const char * sep = "" ;
67 for( auto p = list.m_list.begin() ; p != list.m_list.end() ; ++p , sep = " " )
68 {
69 stream << sep << (*p).hostPartString() ;
70 }
71 return stream ;
72 }
73 template <typename T, typename P>
74 G::StringArray server_names_if( T p , T end , P pred )
75 {
76 G::StringArray result ;
77 for( ; p != end ; ++p )
78 {
79 if( pred(*p) )
80 result.push_back( (*p).server() ) ;
81 }
82 return result ;
83 }
84 }
85}
86
87GNet::DnsBlock::DnsBlock( DnsBlockCallback & callback , ExceptionSink es , const std::string & config ) :
88 m_callback(callback) ,
89 m_es(es) ,
90 m_timer(*this,&DnsBlock::onTimeout,es) ,
91 m_threshold(1U) ,
92 m_allow_on_timeout(true) ,
93 m_dns_server(Address::defaultAddress()) ,
94 m_timeout(0) ,
95 m_id_base(0U)
96{
97 if( !config.empty() )
98 configure( config ) ;
99}
100
101void GNet::DnsBlock::checkConfig( const std::string & config )
102{
103 try
104 {
105 configureImp( config , nullptr ) ;
106 }
107 catch( std::exception & e )
108 {
109 throw Error( "invalid dnsbl configuration string" , e.what() ) ;
110 }
111}
112
113void GNet::DnsBlock::configure( const std::string & config )
114{
115 configureImp( config , this ) ;
116}
117
118void GNet::DnsBlock::configureImp( const std::string & config , DnsBlock * p )
119{
120 G::StringArray list = G::Str::splitIntoFields( config , "," ) ;
121 if( list.size() < 4U )
122 throw std::runtime_error( "not enough comma-sparated fields" ) ;
123
124 Address dns_server = Address::parse( list.at(0U) , Address::NotLocal() ) ;
125
126 // normally allow on timeout, but deny on timeout if configured value is negative
127 std::size_t threshold = G::Str::toUInt( list.at(2U) ) ;
128 bool allow_on_timeout = G::Str::toInt(list.at(1U)) >= 0 || threshold == 0U ;
129 unsigned int timeout_ms = static_cast<unsigned int>( std::abs(G::Str::toInt(list.at(1U))) ) ;
130
131 list.erase( list.begin() , list.begin()+3U ) ;
132 if( p )
133 p->configure( dns_server , threshold , allow_on_timeout , G::TimeInterval(0U,timeout_ms*1000U) , list ) ;
134}
135
136void GNet::DnsBlock::configure( const Address & dns_server , std::size_t threshold ,
137 bool allow_on_timeout , G::TimeInterval timeout , const G::StringArray & servers )
138{
139 m_servers = servers ;
140 m_threshold = threshold ;
141 m_allow_on_timeout = allow_on_timeout ;
142 m_dns_server = dns_server ;
143 m_timeout = timeout ;
144}
145
146void GNet::DnsBlock::start( const Address & address )
147{
148 G_DEBUG( "GNet::DnsBlock::start: dns-server=" << m_dns_server.displayString() << " "
149 << "threshold=" << m_threshold << " timeout=" << m_timeout << " "
150 << "address=" << address.hostPartString() << " "
151 << " servers=[" << G::Str::join(",",m_servers) << "]" ) ;
152
153 m_result.reset( m_threshold , address ) ;
154
155 // dont block connections from local addresses
156 bool is_local = address.isLoopback() || address.isUniqueLocal() || address.isLinkLocal() ;
157 if( G::Test::enabled("dns-block-allow-local") ) is_local = false ;
158 if( m_servers.empty() || is_local )
159 {
160 m_timer.startTimer( 0 ) ;
161 return ;
162 }
163
164 // re-base the sequence number if necessary
165 static unsigned int id_generator = 10 ;
166 if( (id_generator+m_servers.size()) > 65535U )
167 id_generator = 10 ;
168
169 // create a socket to receive responses
170 m_socket_ptr = std::make_unique<DatagramSocket>( m_dns_server.family() ) ;
171 m_socket_ptr->addReadHandler( *this , m_es ) ;
172
173 // send a DNS query to each configured server
174 std::string prefix = queryString( address ) ; // eg. "1.0.0.127"
175 unsigned int id = m_id_base = id_generator ;
176 for( G::StringArray::const_iterator server_p = m_servers.begin() ; server_p != m_servers.end() ;
177 ++server_p , id++ , id_generator++ )
178 {
179 std::string server = G::Str::trimmed( *server_p , G::Str::ws() ) ;
180
181 m_result.add( DnsBlockServerResult(server) ) ;
182
183 const char * type = address.family() == Address::Family::ipv4 ? "A" : "AAAA" ;
184 DnsMessage message = DnsMessage::request( type , std::string(prefix).append(1U,'.').append(server) , id ) ;
185 G_DEBUG( "GNet::DnsBlock::start: sending [" << prefix << "."
186 << server << "] to [" << m_dns_server.displayString() << "]: id " << id ) ;
187
188 ssize_t rc = m_socket_ptr->writeto( message.p() , message.n() , m_dns_server ) ;
189 if( rc < 0 || static_cast<std::size_t>(rc) != message.n() )
190 throw Error( "socket send failed" , m_socket_ptr->reason() ) ;
191 }
192 m_timer.startTimer( m_timeout ) ;
193}
194
196{
197 return m_timer.active() ;
198}
199
200void GNet::DnsBlock::readEvent()
201{
202 static std::vector<char> buffer;
203 buffer.resize( 4096U ) ; // 512 in RFC-1035 4.2.1
204 ssize_t rc = m_socket_ptr->read( &buffer[0] , buffer.size() ) ;
205 if( rc <= 0 || static_cast<std::size_t>(rc) >= buffer.size() )
206 throw Error( "invalid dns response size" ) ;
207 buffer.resize( static_cast<std::size_t>(rc) ) ;
208
209 DnsMessage message( buffer ) ;
210 if( !message.QR() || message.ID() < m_id_base ||
211 message.ID() >= (m_id_base+m_servers.size()) || message.RCODE() > 5 )
212 {
213 G_WARNING( "GNet::DnsBlock::readEvent: invalid dns response: qr=" << message.QR()
214 << " rcode=" << message.RCODE() << " id=" << message.ID() ) ;
215 return ;
216 }
217
218 m_result.at(message.ID()-m_id_base).set( message.addresses() ) ;
219
220 std::size_t server_count = m_result.list().size() ;
221 std::size_t responder_count = countResponders( m_result.list() ) ;
222 std::size_t laggard_count = server_count - responder_count ; G_ASSERT( laggard_count < server_count ) ;
223 std::size_t deny_count = countDeniers( m_result.list() ) ;
224
225 G_DEBUG( "GNet::DnsBlock::readEvent: id=" << message.ID() << " rcode=" << message.RCODE()
226 << (message.ANCOUNT()?" deny ":" allow ")
227 << "got=" << responder_count << "/" << server_count << " deny-count=" << deny_count << "/" << m_threshold ) ;
228
229 bool finished = m_timer.active() && (
230 responder_count == server_count ||
231 ( m_threshold && deny_count >= m_threshold ) ||
232 ( m_threshold && (deny_count+laggard_count) < m_threshold ) ) ;
233
234 if( finished )
235 {
236 m_socket_ptr.reset() ;
237 m_timer.cancelTimer() ;
238 m_result.type() = ( m_threshold && deny_count >= m_threshold ) ?
239 DnsBlockResult::Type::Deny :
240 DnsBlockResult::Type::Allow ;
241 m_callback.onDnsBlockResult( m_result ) ;
242 }
243}
244
245void GNet::DnsBlock::onTimeout()
246{
247 m_socket_ptr.reset() ;
248 m_result.type() = m_result.list().empty() ?
249 ( m_servers.empty() ? DnsBlockResult::Type::Inactive : DnsBlockResult::Type::Local ) :
250 ( m_allow_on_timeout ? DnsBlockResult::Type::TimeoutAllow : DnsBlockResult::Type::TimeoutDeny ) ;
251 m_callback.onDnsBlockResult( m_result ) ;
252}
253
254std::string GNet::DnsBlock::queryString( const Address & address )
255{
256 return address.queryString() ;
257}
258
260{
261 using namespace DnsBlockImp ;
262 if( m_type == Type::Local )
263 {
264 G_LOG( "GNet::DnsBlockResult::log: dnsbl: not checking local address [" << m_address.hostPartString() << "]" ) ;
265 }
266 else if( m_type != Type::Inactive )
267 {
268 for( const auto & result : m_list )
269 {
270 std::ostringstream ss ;
271 ss << "address [" << m_address.hostPartString() << "] " ;
272 if( result.valid() && result.addresses().empty() )
273 ss << "allowed by [" << result.server() << "]" ;
274 else if( result.valid() )
275 ss << "denied by [" << result.server() << "]: " << HostList(result.addresses()) ;
276 else
277 ss << "not checked by [" << result.server() << "]" ;
278 G_LOG( "GNet::DnsBlockResult::log: dnsbl: " << ss.str() ) ;
279 }
280 }
281}
282
284{
285 if( m_type == Type::Deny || m_type == Type::TimeoutDeny || m_type == Type::TimeoutAllow )
286 {
287 std::ostringstream ss ;
288 ss << "client address [" << m_address.hostPartString() << "]" ;
289 if( m_type == Type::Deny || m_type == Type::TimeoutDeny )
290 ss << " blocked" ;
291 if( m_type == Type::TimeoutDeny || m_type == Type::TimeoutAllow )
292 ss << ": timeout: no answer from [" << G::Str::join("] [",laggards()) << "]" ;
293 else
294 ss << " by [" << G::Str::join("] [",deniers()) << "]" ;
295 G_WARNING( "GNet::DnsBlockResult::log: dnsbl: " << ss.str() ) ;
296 }
297}
298
300{
301 return m_type == Type::Inactive || m_type == Type::Local || m_type == Type::TimeoutAllow || m_type == Type::Allow ;
302}
303
305{
306 return !allow() ;
307}
308
309std::size_t GNet::DnsBlock::countResponders( const ResultList & list )
310{
311 using namespace DnsBlockImp ;
312 return static_cast<std::size_t>(std::count_if(list.begin(),list.end(),Responder())) ;
313}
314
315std::size_t GNet::DnsBlock::countDeniers( const ResultList & list )
316{
317 using namespace DnsBlockImp ;
318 return static_cast<std::size_t>(std::count_if(list.begin(),list.end(),Denier())) ;
319}
320
322{
323 using namespace DnsBlockImp ;
324 return server_names_if( m_list.begin() , m_list.end() , Denier() ) ;
325}
326
328{
329 using namespace DnsBlockImp ;
330 return server_names_if( m_list.begin() , m_list.end() , Laggard() ) ;
331}
332
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:53
Family family() const
Returns the address family enumeration.
Definition: gaddress.cpp:491
bool isLinkLocal() const
Returns true if this is a link-local address.
Definition: gaddress.cpp:306
std::string hostPartString(bool raw=false) const
Returns a string which represents the network address.
Definition: gaddress.cpp:384
bool isUniqueLocal() const
Returns true if this is a locally administered address.
Definition: gaddress.cpp:315
static Address parse(const std::string &display_string)
Factory function for any address family.
Definition: gaddress.cpp:217
bool isLoopback() const
Returns true if this is a loopback address.
Definition: gaddress.cpp:288
A callback interface for GNet::DnsBlock.
Definition: gdnsblock.h:208
void warn() const
Emits warnings.
Definition: gdnsblock.cpp:283
bool allow() const
Returns true if the type is Inactive, Local, TimeoutAllow or Allow.
Definition: gdnsblock.cpp:299
G::StringArray laggards() const
Returns the list of slow or unresponsive servers.
Definition: gdnsblock.cpp:327
G::StringArray deniers() const
Returns the list of denying servers.
Definition: gdnsblock.cpp:321
void log() const
Logs the results.
Definition: gdnsblock.cpp:259
bool deny() const
Returns true if the type is TimeoutDeny or Deny.
Definition: gdnsblock.cpp:304
A result structure for one DNSBL server.
Definition: gdnsblock.h:47
bool valid() const
Returns true if the list() is valid.
Definition: gdnsblock.h:234
const std::vector< Address > & addresses() const
Returns the result list, which is empty if there is no block or not valid().
Definition: gdnsblock.h:246
Implements DNS blocklisting, as per RFC-5782.
Definition: gdnsblock.h:145
static void checkConfig(const std::string &)
Checks the configure() string, throwing on error.
Definition: gdnsblock.cpp:101
DnsBlock(DnsBlockCallback &, ExceptionSink, const std::string &config=std::string())
Constructor.
Definition: gdnsblock.cpp:87
void configure(const Address &dns_server, std::size_t threshold, bool allow_on_timeout, G::TimeInterval timeout, const G::StringArray &servers)
Configures the object after construction.
Definition: gdnsblock.cpp:136
void start(const Address &)
Starts an asychronous check on the given address.
Definition: gdnsblock.cpp:146
bool busy() const
Returns true after start() and before the completion callback.
Definition: gdnsblock.cpp:195
A DNS message parser, with static factory functions for message composition.
Definition: gdnsmessage.h:52
std::size_t n() const
Returns the raw data size.
const char * p() const
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).
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
static string_view ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1255
static void splitIntoFields(const std::string &in, StringArray &out, string_view ws, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1146
static int toInt(const std::string &s)
Converts string 's' to an int.
Definition: gstr.cpp:507
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:604
static std::string trimmed(const std::string &s, string_view ws)
Returns a trim()med version of s.
Definition: gstr.cpp:364
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:289
Network classes.
Definition: gdef.h:1115
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
Overload discriminator for Address::parse()
Definition: gaddress.h:113
A functor that tests a GNet::DnsBlockServerResult.
Definition: gdnsblock.cpp:46
A list of addresses used by GNet::DnsBlock.
Definition: gdnsblock.cpp:60
A functor that tests a GNet::DnsBlockServerResult.
Definition: gdnsblock.cpp:53
A functor that tests a GNet::DnsBlockServerResult.
Definition: gdnsblock.cpp:39