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