created arg recursion_count to async_resolve and many other to avoid infinite loops
[pingcheck] / test / test_dns.cpp
CommitLineData
26b0f687
CH
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on 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>
8d26221d
CH
29#include <boost/bind.hpp>
30
31#include <logfunc.hpp>
26b0f687
CH
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"
8d26221d
CH
38#include "dns/cname.h"
39
40using boost::asio::ip::address;
26b0f687 41
cd71d095
CH
42// constants for master
43address name_server = address::from_string("127.0.0.1");
44const int resolved_ip_ttl_threshold = 5; // this should be greater than...
45const uint32_t min_time_between_resolves = 3; // ... this!
46const int max_address_resolution_attempts = 2;
47const int max_recursion_count = 10;
48const std::string cache_file = DnsCache::DoNotUseCacheFile;
49
26b0f687 50//------------------------------------------------------------------------------
8d26221d 51// Global Test fixture (created once for test suite)
26b0f687
CH
52//------------------------------------------------------------------------------
53
8d26221d
CH
54// rely on boost that there will only be one instance
55struct GlobalFixture;
56GlobalFixture *global_fixture;
26b0f687 57
8d26221d
CH
58
59struct GlobalFixture
26b0f687 60{
8d26221d
CH
61
62 GlobalFixture()
63 : IoService()
64 , Cache()
65 , Master()
26b0f687 66 {
8d26221d
CH
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 );
4389b86d 71 I2n::Logger::set_log_level( I2n::Logger::LogLevel::Info );
8d26221d
CH
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(
f833126b 81 new DnsCache(IoService, cache_file, min_time_between_resolves) );
8d26221d
CH
82 cache_temp.swap( Cache );
83 cache_temp.reset();
84 fill_cache();
85
86 // create master
87 DnsMaster::create_master(IoService,
26b0f687
CH
88 name_server,
89 resolved_ip_ttl_threshold,
90 max_address_resolution_attempts,
cd71d095 91 max_recursion_count,
8d26221d
CH
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;
26b0f687 98 }
8d26221d
CH
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
00c81aa0
CH
127 {
128 // cname.test --> host1.test
129 Cache->update("cname.test", Cname("host1.test", 35) );
8d26221d 130
00c81aa0
CH
131 // cname2.test --> cname.test --> host1.test
132 Cache->update("cname2.test", Cname("cname.test", 33) );
9d1b2726 133
00c81aa0
CH
134 // cname3.test --> cname2.test --> cname.test --> host1.test
135 Cache->update("cname3.test", Cname("cname2.test", 37) );
136 }
137
f833126b 138 { // for cache_outdated_test
00c81aa0
CH
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 }
8d26221d 155
f833126b
CH
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
cd71d095
CH
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
8d26221d
CH
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
186BOOST_GLOBAL_FIXTURE( GlobalFixture )
187
188
189// using this as suite-level fixture makes variable Master accessible in all
190// test cases
191struct 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};
26b0f687
CH
211
212//------------------------------------------------------------------------------
213// test suite
214//------------------------------------------------------------------------------
215
8d26221d 216BOOST_FIXTURE_TEST_SUITE( TestDns, TestFixture )
26b0f687
CH
217
218BOOST_AUTO_TEST_CASE( create_master )
219{
8d26221d
CH
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 );
26b0f687
CH
225}
226
8d26221d
CH
227//------------------------------------------------------------------------------
228// test Cache
229//------------------------------------------------------------------------------
230BOOST_FIXTURE_TEST_SUITE( TestDnsCache, TestFixture )
231
232BOOST_AUTO_TEST_CASE( cache_retrieve_ip1 )
26b0f687 233{
8d26221d 234 HostAddressVec ips = Cache->get_ips("host1.test");
f833126b 235 BOOST_REQUIRE_EQUAL( ips.size(), 1 );
8d26221d 236 HostAddress ip = ips.front();
4389b86d 237 BOOST_CHECK( ip.is_valid() );
8d26221d
CH
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
243BOOST_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];
4389b86d 248 BOOST_CHECK( ip.is_valid() );
8d26221d
CH
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];
4389b86d 252 BOOST_CHECK( ip.is_valid() );
8d26221d
CH
253 BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.3" );
254 BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 93 );
255}
26b0f687 256
8d26221d
CH
257BOOST_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
267BOOST_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");
f833126b 271 BOOST_REQUIRE_EQUAL( ips.size(), 1 );
8d26221d 272 HostAddress ip = ips.front();
4389b86d 273 BOOST_CHECK( ip.is_valid() );
8d26221d
CH
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
278BOOST_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");
f833126b 282 BOOST_REQUIRE_EQUAL( ips.size(), 1 );
8d26221d 283 HostAddress ip = ips.front();
4389b86d 284 BOOST_CHECK( ip.is_valid() );
8d26221d
CH
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
9d1b2726
CH
289void 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)
8d26221d 293{
9d1b2726
CH
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) );
8d26221d 303 HostAddressVec ips;
00c81aa0
CH
304 ips.push_back( HostAddress( address::from_string("192.168.42.100"),
305 ttl4 ) );
9d1b2726 306 cache->update("skip_chain_fourth.test", ips);
8d26221d
CH
307 }
308
9d1b2726 309 // normal recursive call should give nothing since one cname is outdated
8d26221d 310 bool check_up_to_date = true;
9d1b2726 311 HostAddressVec ips = cache->get_ips_recursive("skip_chain_first.test",
8d26221d 312 check_up_to_date);
9d1b2726
CH
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 );
8d26221d 316
9d1b2726
CH
317 // now find host to resolve after the outdated one
318 std::string first_outdated = cache->get_first_outdated_cname(
8d26221d 319 "skip_chain_first.test", 5);
9d1b2726
CH
320 BOOST_CHECK_EQUAL( first_outdated, correct_host_after_skip );
321}
322
323BOOST_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), "" );
8d26221d
CH
348}
349
4389b86d
CH
350BOOST_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() );
f833126b
CH
355 DnsCache loaded_cache( IoService,
356 file_name.str(),
357 min_time_between_resolves );
4389b86d 358 HostAddressVec ips = loaded_cache.get_ips("abc.xyz");
f833126b 359 BOOST_REQUIRE_EQUAL( ips.size(), 1 );
4389b86d
CH
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
00c81aa0
CH
373BOOST_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
f833126b
CH
400BOOST_AUTO_TEST_CASE( cache_ttl_below_thresh_test )
401{
f833126b
CH
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 );
00c81aa0 406
f833126b
CH
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}
8d26221d 410
cd71d095
CH
411BOOST_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
8d26221d
CH
419BOOST_AUTO_TEST_SUITE_END() // of TestDnsCache
420
421
422// -----------------------------------------------------------------------------
423// test resolver
424// -----------------------------------------------------------------------------
425
426BOOST_FIXTURE_TEST_SUITE( TestDnsResolver, TestFixture )
427
8d26221d
CH
428BOOST_AUTO_TEST_CASE( create_resolver_v4 )
429{
26b0f687
CH
430 // create resolver
431 std::string hostname = "www.intra2net.com";
8d26221d
CH
432 ResolverItem resolver = Master->get_resolver_for(hostname,
433 PingProtocol_ICMP);
26b0f687
CH
434 BOOST_CHECK_EQUAL( resolver->get_hostname(), hostname );
435 BOOST_CHECK( !resolver->is_resolving() );
4389b86d 436 BOOST_CHECK( !resolver->is_waiting_to_resolve() );
26b0f687
CH
437
438 // cancel should have no effect
439 resolver->cancel_resolve();
440 BOOST_CHECK( !resolver->is_resolving() );
4389b86d 441 BOOST_CHECK( !resolver->is_waiting_to_resolve() );
26b0f687
CH
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
8d26221d 450
cd71d095 451struct ResolveCallback
8d26221d 452{
cd71d095
CH
453public:
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;
8d26221d 472
cd71d095
CH
473 Resolver->cancel_resolve();
474 IoService->stop();
475
476 LastCallWasSuccess = was_success;
477 LastCallRecursionCount = recursion_count;
478 }
479};
8d26221d
CH
480
481BOOST_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);
4389b86d
CH
487 BOOST_CHECK( !resolver->is_resolving() );
488 BOOST_CHECK( !resolver->is_waiting_to_resolve() );
8d26221d
CH
489
490 // set callback
cd71d095
CH
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,
8d26221d 494 _1, _2 );
cd71d095 495
8d26221d
CH
496 // start resolving
497 resolver->async_resolve(callback);
4389b86d 498 BOOST_CHECK( resolver->is_resolving() );
8d26221d
CH
499 IoService->run();
500 // this will block until io service is stopped in resolve_callback
cd71d095
CH
501 BOOST_TEST_MESSAGE( "Back to synchronous operation" );
502 IoService->reset();
8d26221d
CH
503
504 // check for result
cd71d095
CH
505 BOOST_CHECK_EQUAL( callback_obj->CallCount, 1 );
506 BOOST_CHECK( callback_obj->LastCallWasSuccess );
507 BOOST_CHECK_EQUAL( callback_obj->LastCallRecursionCount, 0 );
8d26221d 508 BOOST_CHECK( resolver->have_up_to_date_ip() );
cd71d095
CH
509
510 delete callback_obj;
8d26221d
CH
511}
512
513BOOST_AUTO_TEST_SUITE_END() // of TestDnsResolver
514
26b0f687 515BOOST_AUTO_TEST_SUITE_END()