6cb1df5f0672b7bc6619c5100097280e80ea83af
[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         // cname.test --> host1.test
125         Cache->update("cname.test", Cname("host1.test", 35) );
126
127         // cname2.test --> cname.test --> host1.test
128         Cache->update("cname2.test", Cname("cname.test", 33) );
129
130         // cname3.test --> cname2.test --> cname.test --> host1.test
131         Cache->update("cname3.test", Cname("cname2.test", 37) );
132
133         BOOST_TEST_MESSAGE( "Done filling cache." );
134     }
135
136     // these variables will not be available in test cases:
137     IoServiceItem IoService;
138     DnsCacheItem Cache;
139     DnsMasterItem Master;
140 };
141
142 // this causes above fixture to be created only once before tests start and
143 // destructed after tests end; however, variables are not accessible in test
144 // cases
145 BOOST_GLOBAL_FIXTURE( GlobalFixture )
146
147
148 // using this as suite-level fixture makes variable Master accessible in all
149 //   test cases
150 struct TestFixture
151 {
152     IoServiceItem IoService;
153     DnsCacheItem Cache;
154     DnsMasterItem Master;
155
156     TestFixture()
157         : IoService()
158         , Cache()
159         , Master()
160     {
161         BOOST_TEST_MESSAGE("Create test-level fixture");
162         GlobalFixture *global = global_fixture;
163         IoService = global->IoService;
164         Cache = global->Cache;
165         Master = global->Master;
166     }
167
168     virtual ~TestFixture() {}
169 };
170
171 //------------------------------------------------------------------------------
172 // test suite
173 //------------------------------------------------------------------------------
174
175 BOOST_FIXTURE_TEST_SUITE( TestDns, TestFixture )
176
177 BOOST_AUTO_TEST_CASE( create_master )
178 {
179     // simple checks
180     BOOST_CHECK_EQUAL( Master->get_resolved_ip_ttl_threshold(),
181                                    resolved_ip_ttl_threshold );
182     BOOST_CHECK_EQUAL( Master->get_max_address_resolution_attempts(),
183                                    max_address_resolution_attempts );
184 }
185
186 //------------------------------------------------------------------------------
187 // test Cache
188 //------------------------------------------------------------------------------
189 BOOST_FIXTURE_TEST_SUITE( TestDnsCache, TestFixture )
190
191 BOOST_AUTO_TEST_CASE( cache_retrieve_ip1 )
192 {
193     HostAddressVec ips = Cache->get_ips("host1.test");
194     BOOST_CHECK_EQUAL( ips.size(), 1 );
195     HostAddress ip = ips.front();
196     BOOST_CHECK( ip.is_valid() );
197     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
198     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 61 );
199 }
200
201
202 BOOST_AUTO_TEST_CASE( cache_retrieve_ip2 )
203 {
204     HostAddressVec ips = Cache->get_ips("host2_3.test");
205     BOOST_CHECK_EQUAL( ips.size(), 2 );
206     HostAddress ip = ips[0];
207     BOOST_CHECK( ip.is_valid() );
208     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.2" );
209     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 92 );
210     ip = ips[1];
211     BOOST_CHECK( ip.is_valid() );
212     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.3" );
213     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 93 );
214 }
215
216 BOOST_AUTO_TEST_CASE( cache_retrieve_cname )
217 {
218     HostAddressVec ips = Cache->get_ips("cname.test");
219     BOOST_CHECK( ips.empty() );
220
221     Cname cname = Cache->get_cname("cname.test");
222     BOOST_CHECK_EQUAL( cname.Host, "host1.test" );
223     BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 35 );
224 }
225
226 BOOST_AUTO_TEST_CASE( cache_retrieve_recursive1 )
227 {
228     // should get IP from host1 but ttl from cname since is smaller
229     HostAddressVec ips = Cache->get_ips_recursive("cname.test");
230     BOOST_CHECK_EQUAL( ips.size(), 1 );
231     HostAddress ip = ips.front();
232     BOOST_CHECK( ip.is_valid() );
233     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
234     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 35 );
235 }
236
237 BOOST_AUTO_TEST_CASE( cache_retrieve_recursive2 )
238 {
239     // should get IP from host1 but ttl from cname2 since is smaller
240     HostAddressVec ips = Cache->get_ips_recursive("cname3.test");
241     BOOST_CHECK_EQUAL( ips.size(), 1 );
242     HostAddress ip = ips.front();
243     BOOST_CHECK( ip.is_valid() );
244     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
245     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 33 );
246 }
247
248 void cname_skip_test(const uint32_t ttl1, const uint32_t ttl2,
249                      const uint32_t ttl3, const uint32_t ttl4,
250                      const DnsCacheItem &cache,
251                      const std::string &correct_host_after_skip)
252 {
253     {   // create cname chain:
254         // skip_chain_first -(ttl1)-> skip_chain_second -(ttl2)->
255         //   skip_chain_third -(ttl3)-> skip_chain_fourth -(ttl4)-> IPs
256         cache->update( "skip_chain_first.test",
257                  Cname("skip_chain_second.test", ttl1) );
258         cache->update( "skip_chain_second.test",
259                  Cname("skip_chain_third.test", ttl2) );
260         cache->update( "skip_chain_third.test",
261                  Cname("skip_chain_fourth.test", ttl3) );
262         HostAddressVec ips;
263         ips.push_back( HostAddress( address::from_string("192.168.42.4"), ttl4) );
264         cache->update("skip_chain_fourth.test", ips);
265     }
266
267     // normal recursive call should give nothing since one cname is outdated
268     bool check_up_to_date = true;
269     HostAddressVec ips = cache->get_ips_recursive("skip_chain_first.test",
270                                                   check_up_to_date);
271     bool one_is_out_of_date = (ttl1 < 5) || (ttl2 < 5)
272                            || (ttl3 < 5) || (ttl4 < 5);
273     BOOST_CHECK_EQUAL( ips.empty(), one_is_out_of_date );
274
275     // now find host to resolve after the outdated one
276     std::string first_outdated = cache->get_first_outdated_cname(
277                                                     "skip_chain_first.test", 5);
278     BOOST_CHECK_EQUAL( first_outdated, correct_host_after_skip );
279 }
280
281 BOOST_AUTO_TEST_CASE( cache_skip_tests )
282 {
283     // build a cname chain where first one is out of date
284     cname_skip_test(0, 120, 120, 60, Cache, "skip_chain_second.test");
285
286     // build a cname chain where second one is out of date
287     cname_skip_test(120, 0, 120, 60, Cache, "skip_chain_third.test");
288
289     // build a cname chain where third one is out of date
290     cname_skip_test(120, 120, 0, 120, Cache, "skip_chain_fourth.test");
291
292     // build a cname chain where just IPs are out of date
293     cname_skip_test(120, 120, 120, 0, Cache, "");
294
295     // build a cname chain where all are out of date
296     cname_skip_test(0, 0, 0, 0, Cache, "skip_chain_second.test");
297
298     // build a cname chain where all is up to date
299     cname_skip_test(120, 120, 120, 120, Cache, "");
300
301     // test case with very short cname list that is up-to-date
302     BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("cname.test", 5), "" );
303
304     // test case where there is no cname at all
305     BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("host1.test", 5), "" );
306 }
307
308 BOOST_AUTO_TEST_CASE( cache_load_test )
309 {
310     std::stringstream file_name;
311     file_name << DATA_DIR_STRING << "/" << "dns_cache_example.xml";
312     BOOST_TEST_MESSAGE( "loading cache from file " << file_name.str() );
313     DnsCache loaded_cache( IoService, file_name.str() );
314     HostAddressVec ips = loaded_cache.get_ips("abc.xyz");
315     BOOST_CHECK_EQUAL( ips.size(), 1 );
316     HostAddress ip = ips.front();
317     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "11.22.33.44" );
318     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 567 );
319     BOOST_CHECK_EQUAL( ip.get_ttl().get_updated_value(), 0 );
320
321     Cname cname = loaded_cache.get_cname("cname1.xyz");
322     BOOST_CHECK_EQUAL( cname.Host, "abc.xyz" );
323     BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 27 );
324     BOOST_CHECK_EQUAL( cname.Ttl.get_updated_value(), 0 );
325
326     // not testing Ttl set time since is private
327 }
328
329
330 BOOST_AUTO_TEST_SUITE_END()    // of TestDnsCache
331
332
333 // -----------------------------------------------------------------------------
334 // test resolver
335 // -----------------------------------------------------------------------------
336
337 BOOST_FIXTURE_TEST_SUITE( TestDnsResolver, TestFixture )
338
339 BOOST_AUTO_TEST_CASE( create_resolver_v4 )
340 {
341     // create resolver
342     std::string hostname = "www.intra2net.com";
343     ResolverItem resolver = Master->get_resolver_for(hostname,
344                                                      PingProtocol_ICMP);
345     BOOST_CHECK_EQUAL( resolver->get_hostname(), hostname );
346     BOOST_CHECK( !resolver->is_resolving() );
347     BOOST_CHECK( !resolver->is_waiting_to_resolve() );
348
349     // cancel should have no effect
350     resolver->cancel_resolve();
351     BOOST_CHECK( !resolver->is_resolving() );
352     BOOST_CHECK( !resolver->is_waiting_to_resolve() );
353
354     // should not have any ips since cache did not load anything
355     BOOST_CHECK_EQUAL( resolver->get_resolved_ip_count(), 0 );
356     BOOST_CHECK( !resolver->have_up_to_date_ip() );
357     std::string no_ip = "0.0.0.0";
358     BOOST_CHECK_EQUAL( resolver->get_next_ip().get_ip().to_string(), no_ip );
359 }
360
361
362 void resolve_callback(IoServiceItem io_serv, ResolverItem resolver,
363                       const bool was_success, const int cname_count)
364 {
365     resolver->cancel_resolve();
366     io_serv->stop();
367     BOOST_TEST_MESSAGE( "Stopped io_service" );
368 }
369
370
371 BOOST_AUTO_TEST_CASE( do_resolve )
372 {
373     // create resolver
374     std::string hostname = "www.intra2net.com";
375     ResolverItem resolver = Master->get_resolver_for(hostname,
376                                                      PingProtocol_ICMP);
377     BOOST_CHECK( !resolver->is_resolving() );
378     BOOST_CHECK( !resolver->is_waiting_to_resolve() );
379
380     // set callback
381     callback_type callback = boost::bind( resolve_callback, IoService, resolver,
382                                           _1, _2 );
383     // start resolving
384     resolver->async_resolve(callback);
385     BOOST_CHECK( resolver->is_resolving() );
386     IoService->run();
387     // this will block until io service is stopped in resolve_callback
388
389     // check for result
390     BOOST_CHECK( resolver->have_up_to_date_ip() );
391 }
392
393 BOOST_AUTO_TEST_SUITE_END()   // of TestDnsResolver
394
395 BOOST_AUTO_TEST_SUITE_END()