11e7e9ddab2b1c64d46dd41fee7cdb6a3be94dca
[pingcheck] / test / test_dns.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20
21 #define BOOST_TEST_MAIN
22 #define BOOST_TEST_DYN_LINK
23
24 #include <algorithm>
25
26 #include <boost/test/unit_test.hpp>
27 #include <boost/asio/io_service.hpp>
28 #include <boost/asio/ip/address.hpp>
29 #include <boost/bind.hpp>
30
31 #include <logfunc.hpp>
32
33 #include "host/pingprotocol.h"
34 #include "dns/hostaddress.h"
35 #include "dns/dnsmaster.h"
36 #include "dns/dnscache.h"
37 #include "dns/resolverbase.h"
38 #include "dns/cname.h"
39
40 using boost::asio::ip::address;
41
42 // constants for master
43 address name_server = address::from_string("127.0.0.1");
44 const int resolved_ip_ttl_threshold = 5;      // this should be greater than...
45 const uint32_t min_time_between_resolves = 3; // ... this!
46 const int max_address_resolution_attempts = 2;
47 const int max_recursion_count = 10;
48 const std::string cache_file = DnsCache::DoNotUseCacheFile;
49
50 //------------------------------------------------------------------------------
51 // Global Test fixture (created once for test suite)
52 //------------------------------------------------------------------------------
53
54 // rely on boost that there will only be one instance
55 struct GlobalFixture;
56 GlobalFixture *global_fixture;
57
58
59 struct GlobalFixture
60 {
61
62     GlobalFixture()
63         : IoService()
64         , Cache()
65         , Master()
66     {
67         BOOST_TEST_MESSAGE("Create global fixture");
68
69         // setup logging so we can see output from out code
70         I2n::Logger::enable_stderr_log( true );
71         I2n::Logger::set_log_level( I2n::Logger::LogLevel::Info );
72         I2n::Logger::GlobalLogger.info() << "Logging enabled for DnsTest";
73
74         // IoService
75         IoServiceItem io_service_temp( new boost::asio::io_service() );
76         io_service_temp.swap( IoService );
77         io_service_temp.reset();
78
79         // DNS Cache
80         DnsCacheItem cache_temp = DnsCacheItem(
81                new DnsCache(IoService, cache_file, min_time_between_resolves) );
82         cache_temp.swap( Cache );
83         cache_temp.reset();
84         fill_cache();
85
86         // create master
87         DnsMaster::create_master(IoService,
88                                  name_server,
89                                  resolved_ip_ttl_threshold,
90                                  max_address_resolution_attempts,
91                                  max_recursion_count,
92                                  Cache);
93         Master = DnsMaster::get_instance();
94
95         // remember this instance, so we can later access all these variables
96         if (global_fixture == 0)
97             global_fixture = this;
98     }
99
100     ~GlobalFixture()
101     {
102         BOOST_TEST_MESSAGE("Destructing global fixture");
103         IoService->stop();
104         IoService.reset();
105         Master.reset();
106     }
107
108     void fill_cache()
109     {
110         BOOST_TEST_MESSAGE( "Filling cache..." );
111         {
112             HostAddress ip(address::from_string("192.168.42.1"), 61);
113             HostAddressVec ips;
114             ips.push_back(ip);
115             Cache->update("host1.test", ips);
116         }
117
118         {
119             HostAddress ip1(address::from_string("192.168.42.2"), 92);
120             HostAddress ip2(address::from_string("192.168.42.3"), 93);
121             HostAddressVec ips;
122             ips.push_back(ip1);
123             ips.push_back(ip2);
124             Cache->update("host2_3.test", ips);
125         }
126
127         {
128             // cname.test --> host1.test
129             Cache->update("cname.test", Cname("host1.test", 35) );
130
131             // cname2.test --> cname.test --> host1.test
132             Cache->update("cname2.test", Cname("cname.test", 33) );
133
134             // cname3.test --> cname2.test --> cname.test --> host1.test
135             Cache->update("cname3.test", Cname("cname2.test", 37) );
136         }
137
138         {   // for cache_outdated_test
139             HostAddressVec ips;
140             ips.push_back( HostAddress( address::from_string("192.168.42.4"),
141                                         0 ) );
142             ips.push_back( HostAddress( address::from_string("192.168.42.5"),
143                                         resolved_ip_ttl_threshold-1 ) );
144             ips.push_back( HostAddress( address::from_string("192.168.42.6"),
145                                         resolved_ip_ttl_threshold ) );
146             ips.push_back( HostAddress( address::from_string("192.168.42.7"),
147                                         resolved_ip_ttl_threshold+1 ) );
148             Cache->update("host_outdated.test", ips);
149
150             Cache->update( "cname_outdated.test",
151                       Cname("host_outdated.test", resolved_ip_ttl_threshold) );
152             Cache->update( "cname_up_to_date.test",
153                       Cname("host_outdated.test", resolved_ip_ttl_threshold+1));
154         }
155
156         {   // for cache_ttl_below_thresh_test
157             // TTLs < min_time_between_resolves should be corrected
158             HostAddressVec ips;
159             ips.push_back( HostAddress( address::from_string("192.128.42.8"),
160                                         1 ) );
161             Cache->update("host_ttl_below_thresh.test", ips);
162
163             Cache->update( "cname_ttl_below_thresh.test",
164                       Cname("host_ttl_below_thresh.test", 2) );
165         }
166
167         {   // create a CNAME loop  cname_loop1.test --> cname_loop2.test
168             //                  --> cname_loop3.test --> cname_loop1.test
169             Cache->update( "cname_loop1.test", Cname("cname_loop2.test", 60) );
170             Cache->update( "cname_loop2.test", Cname("cname_loop3.test", 60) );
171             Cache->update( "cname_loop3.test", Cname("cname_loop1.test", 60) );
172         }
173
174         BOOST_TEST_MESSAGE( "Done filling cache." );
175     }
176
177     // these variables will not be available in test cases:
178     IoServiceItem IoService;
179     DnsCacheItem Cache;
180     DnsMasterItem Master;
181 };
182
183 // this causes above fixture to be created only once before tests start and
184 // destructed after tests end; however, variables are not accessible in test
185 // cases
186 BOOST_GLOBAL_FIXTURE( GlobalFixture )
187
188
189 // using this as suite-level fixture makes variable Master accessible in all
190 //   test cases
191 struct TestFixture
192 {
193     IoServiceItem IoService;
194     DnsCacheItem Cache;
195     DnsMasterItem Master;
196
197     TestFixture()
198         : IoService()
199         , Cache()
200         , Master()
201     {
202         BOOST_TEST_MESSAGE("Create test-level fixture");
203         GlobalFixture *global = global_fixture;
204         IoService = global->IoService;
205         Cache = global->Cache;
206         Master = global->Master;
207     }
208
209     virtual ~TestFixture() {}
210 };
211
212 //------------------------------------------------------------------------------
213 // test suite
214 //------------------------------------------------------------------------------
215
216 BOOST_FIXTURE_TEST_SUITE( TestDns, TestFixture )
217
218 BOOST_AUTO_TEST_CASE( create_master )
219 {
220     // simple checks
221     BOOST_CHECK_EQUAL( Master->get_resolved_ip_ttl_threshold(),
222                                    resolved_ip_ttl_threshold );
223     BOOST_CHECK_EQUAL( Master->get_max_address_resolution_attempts(),
224                                    max_address_resolution_attempts );
225 }
226
227 //------------------------------------------------------------------------------
228 // test Cache
229 //------------------------------------------------------------------------------
230 BOOST_FIXTURE_TEST_SUITE( TestDnsCache, TestFixture )
231
232 BOOST_AUTO_TEST_CASE( cache_retrieve_ip1 )
233 {
234     HostAddressVec ips = Cache->get_ips("host1.test");
235     BOOST_REQUIRE_EQUAL( ips.size(), 1 );
236     HostAddress ip = ips.front();
237     BOOST_CHECK( ip.is_valid() );
238     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
239     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 61 );
240 }
241
242
243 BOOST_AUTO_TEST_CASE( cache_retrieve_ip2 )
244 {
245     HostAddressVec ips = Cache->get_ips("host2_3.test");
246     BOOST_CHECK_EQUAL( ips.size(), 2 );
247     HostAddress ip = ips[0];
248     BOOST_CHECK( ip.is_valid() );
249     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.2" );
250     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 92 );
251     ip = ips[1];
252     BOOST_CHECK( ip.is_valid() );
253     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.3" );
254     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 93 );
255 }
256
257 BOOST_AUTO_TEST_CASE( cache_retrieve_cname )
258 {
259     HostAddressVec ips = Cache->get_ips("cname.test");
260     BOOST_CHECK( ips.empty() );
261
262     Cname cname = Cache->get_cname("cname.test");
263     BOOST_CHECK_EQUAL( cname.Host, "host1.test" );
264     BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 35 );
265 }
266
267 BOOST_AUTO_TEST_CASE( cache_retrieve_recursive1 )
268 {
269     // should get IP from host1 but ttl from cname since is smaller
270     HostAddressVec ips = Cache->get_ips_recursive("cname.test");
271     BOOST_REQUIRE_EQUAL( ips.size(), 1 );
272     HostAddress ip = ips.front();
273     BOOST_CHECK( ip.is_valid() );
274     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
275     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 35 );
276 }
277
278 BOOST_AUTO_TEST_CASE( cache_retrieve_recursive2 )
279 {
280     // should get IP from host1 but ttl from cname2 since is smaller
281     HostAddressVec ips = Cache->get_ips_recursive("cname3.test");
282     BOOST_REQUIRE_EQUAL( ips.size(), 1 );
283     HostAddress ip = ips.front();
284     BOOST_CHECK( ip.is_valid() );
285     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
286     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 33 );
287 }
288
289 void cname_skip_test(const uint32_t ttl1, const uint32_t ttl2,
290                      const uint32_t ttl3, const uint32_t ttl4,
291                      const DnsCacheItem &cache,
292                      const std::string &correct_host_after_skip)
293 {
294     {   // create cname chain:
295         // skip_chain_first -(ttl1)-> skip_chain_second -(ttl2)->
296         //   skip_chain_third -(ttl3)-> skip_chain_fourth -(ttl4)-> IPs
297         cache->update( "skip_chain_first.test",
298                  Cname("skip_chain_second.test", ttl1) );
299         cache->update( "skip_chain_second.test",
300                  Cname("skip_chain_third.test", ttl2) );
301         cache->update( "skip_chain_third.test",
302                  Cname("skip_chain_fourth.test", ttl3) );
303         HostAddressVec ips;
304         ips.push_back( HostAddress( address::from_string("192.168.42.100"),
305                                     ttl4 ) );
306         cache->update("skip_chain_fourth.test", ips);
307     }
308
309     // normal recursive call should give nothing since one cname is outdated
310     bool check_up_to_date = true;
311     HostAddressVec ips = cache->get_ips_recursive("skip_chain_first.test",
312                                                   check_up_to_date);
313     bool one_is_out_of_date = (ttl1 < 5) || (ttl2 < 5)
314                            || (ttl3 < 5) || (ttl4 < 5);
315     BOOST_CHECK_EQUAL( ips.empty(), one_is_out_of_date );
316
317     // now find host to resolve after the outdated one
318     std::string first_outdated = cache->get_first_outdated_cname(
319                                                     "skip_chain_first.test", 5);
320     BOOST_CHECK_EQUAL( first_outdated, correct_host_after_skip );
321 }
322
323 BOOST_AUTO_TEST_CASE( cache_skip_tests )
324 {
325     // build a cname chain where first one is out of date
326     cname_skip_test(0, 120, 120, 60, Cache, "skip_chain_second.test");
327
328     // build a cname chain where second one is out of date
329     cname_skip_test(120, 0, 120, 60, Cache, "skip_chain_third.test");
330
331     // build a cname chain where third one is out of date
332     cname_skip_test(120, 120, 0, 120, Cache, "skip_chain_fourth.test");
333
334     // build a cname chain where just IPs are out of date
335     cname_skip_test(120, 120, 120, 0, Cache, "");
336
337     // build a cname chain where all are out of date
338     cname_skip_test(0, 0, 0, 0, Cache, "skip_chain_second.test");
339
340     // build a cname chain where all is up to date
341     cname_skip_test(120, 120, 120, 120, Cache, "");
342
343     // test case with very short cname list that is up-to-date
344     BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("cname.test", 5), "" );
345
346     // test case where there is no cname at all
347     BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("host1.test", 5), "" );
348 }
349
350 BOOST_AUTO_TEST_CASE( cache_load_test )
351 {
352     std::stringstream file_name;
353     file_name << DATA_DIR_STRING << "/" << "dns_cache_example.xml";
354     BOOST_TEST_MESSAGE( "loading cache from file " << file_name.str() );
355     DnsCache loaded_cache( IoService,
356                            file_name.str(),
357                            min_time_between_resolves );
358     HostAddressVec ips = loaded_cache.get_ips("abc.xyz");
359     BOOST_REQUIRE_EQUAL( ips.size(), 1 );
360     HostAddress ip = ips.front();
361     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "11.22.33.44" );
362     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 567 );
363     BOOST_CHECK_EQUAL( ip.get_ttl().get_updated_value(), 0 );
364
365     Cname cname = loaded_cache.get_cname("cname1.xyz");
366     BOOST_CHECK_EQUAL( cname.Host, "abc.xyz" );
367     BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 27 );
368     BOOST_CHECK_EQUAL( cname.Ttl.get_updated_value(), 0 );
369
370     // not testing Ttl set time since is private
371 }
372
373 BOOST_AUTO_TEST_CASE( cache_outdated_test )
374 {
375     bool check_up_to_date = false;
376     HostAddressVec ips = Cache->get_ips("host_outdated.test", check_up_to_date);
377     BOOST_CHECK_EQUAL( ips.size(), 4 );
378     ips = Cache->get_ips_recursive("host_outdated.test", check_up_to_date);
379     BOOST_CHECK_EQUAL( ips.size(), 4 );
380     Cname cname = Cache->get_cname("cname_outdated.test", check_up_to_date);
381     BOOST_CHECK( !cname.Host.empty() );
382     ips = Cache->get_ips_recursive("cname_outdated.test", check_up_to_date);
383     BOOST_CHECK_EQUAL( ips.size(), 4 );
384     ips = Cache->get_ips_recursive("cname_up_to_date.test", check_up_to_date);
385     BOOST_CHECK_EQUAL( ips.size(), 4 );
386
387     check_up_to_date = true;
388     ips = Cache->get_ips( "host_outdated.test", check_up_to_date );
389     BOOST_CHECK_EQUAL( ips.size(), 1 );
390     ips = Cache->get_ips_recursive("host_outdated.test", check_up_to_date);
391     BOOST_CHECK_EQUAL( ips.size(), 1 );
392     cname = Cache->get_cname("cname_outdated.test", check_up_to_date);
393     BOOST_CHECK( cname.Host.empty() );
394     ips = Cache->get_ips_recursive("cname_outdated.test", check_up_to_date);
395     BOOST_CHECK_EQUAL( ips.size(), 0 );
396     ips = Cache->get_ips_recursive("cname_up_to_date.test", check_up_to_date);
397     BOOST_CHECK_EQUAL( ips.size(), 1 );
398 }
399
400 BOOST_AUTO_TEST_CASE( cache_ttl_below_thresh_test )
401 {
402     HostAddressVec ips = Cache->get_ips("host_ttl_below_thresh.test", false);
403     BOOST_REQUIRE_EQUAL( ips.size(), 1 );
404     HostAddress ip = ips.front();
405     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), min_time_between_resolves );
406
407     Cname cname = Cache->get_cname("cname_ttl_below_thresh.test", false);
408     BOOST_CHECK_EQUAL( cname.Ttl.get_value(), min_time_between_resolves );
409 }
410
411 BOOST_AUTO_TEST_CASE( cache_retrieve_loop )
412 {
413     BOOST_CHECK_EQUAL( Cache->get_ips_recursive("cname_loop1.test").size(), 0 );
414     BOOST_CHECK_EQUAL( Cache->get_ips_recursive("cname_loop2.test").size(), 0 );
415     BOOST_CHECK_EQUAL( Cache->get_ips_recursive("cname_loop3.test").size(), 0 );
416 }
417
418
419 BOOST_AUTO_TEST_SUITE_END()    // of TestDnsCache
420
421
422 // -----------------------------------------------------------------------------
423 // test resolver
424 // -----------------------------------------------------------------------------
425
426 BOOST_FIXTURE_TEST_SUITE( TestDnsResolver, TestFixture )
427
428 BOOST_AUTO_TEST_CASE( create_resolver_v4 )
429 {
430     // create resolver
431     std::string hostname = "www.intra2net.com";
432     ResolverItem resolver = Master->get_resolver_for(hostname,
433                                                      PingProtocol_ICMP);
434     BOOST_CHECK_EQUAL( resolver->get_hostname(), hostname );
435     BOOST_CHECK( !resolver->is_resolving() );
436     BOOST_CHECK( !resolver->is_waiting_to_resolve() );
437
438     // cancel should have no effect
439     resolver->cancel_resolve();
440     BOOST_CHECK( !resolver->is_resolving() );
441     BOOST_CHECK( !resolver->is_waiting_to_resolve() );
442
443     // should not have any ips since cache did not load anything
444     BOOST_CHECK_EQUAL( resolver->get_resolved_ip_count(), 0 );
445     BOOST_CHECK( !resolver->have_up_to_date_ip() );
446     std::string no_ip = "0.0.0.0";
447     BOOST_CHECK_EQUAL( resolver->get_next_ip().get_ip().to_string(), no_ip );
448 }
449
450
451 struct ResolveCallback
452 {
453 public:
454     IoServiceItem IoService;
455     ResolverItem Resolver;
456     int CallCount;
457     bool LastCallWasSuccess;
458     int LastCallRecursionCount;
459
460     ResolveCallback(IoServiceItem &io_serv, ResolverItem &resolver)
461         : IoService(io_serv)
462         , Resolver(resolver)
463         , CallCount(0)
464         , LastCallWasSuccess(true)
465         , LastCallRecursionCount(-10)
466     {}
467
468     void call(const bool was_success, const int recursion_count)
469     {
470         BOOST_TEST_MESSAGE( "Callback called" );
471         ++CallCount;
472
473         Resolver->cancel_resolve();
474         IoService->stop();
475
476         LastCallWasSuccess = was_success;
477         LastCallRecursionCount = recursion_count;
478     }
479 };
480
481 BOOST_AUTO_TEST_CASE( do_resolve )
482 {
483     // create resolver
484     std::string hostname = "www.intra2net.com";
485     ResolverItem resolver = Master->get_resolver_for(hostname,
486                                                      PingProtocol_ICMP);
487     BOOST_CHECK( !resolver->is_resolving() );
488     BOOST_CHECK( !resolver->is_waiting_to_resolve() );
489
490     // set callback
491     ResolveCallback *callback_obj = new ResolveCallback(IoService, resolver);
492     // have to manually delete this obj in the end!
493     callback_type callback = boost::bind( &ResolveCallback::call, callback_obj,
494                                           _1, _2 );
495
496     // start resolving
497     resolver->async_resolve(callback);
498     BOOST_CHECK( resolver->is_resolving() );
499     IoService->run();
500     // this will block until io service is stopped in resolve_callback
501     BOOST_TEST_MESSAGE( "Back to synchronous operation" );
502     IoService->reset();
503
504     // check for result
505     BOOST_CHECK_EQUAL( callback_obj->CallCount, 1 );
506     BOOST_CHECK( callback_obj->LastCallWasSuccess );
507     BOOST_CHECK_EQUAL( callback_obj->LastCallRecursionCount, 0 );
508     BOOST_CHECK( resolver->have_up_to_date_ip() );
509
510     delete callback_obj;
511 }
512
513 BOOST_AUTO_TEST_SUITE_END()   // of TestDnsResolver
514
515 BOOST_AUTO_TEST_SUITE_END()