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