f215e39d41674be7bca8b3c52f55ce310ed9252a
[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::Debug );
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_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
197     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 61 );
198 }
199
200
201 BOOST_AUTO_TEST_CASE( cache_retrieve_ip2 )
202 {
203     HostAddressVec ips = Cache->get_ips("host2_3.test");
204     BOOST_CHECK_EQUAL( ips.size(), 2 );
205     HostAddress ip = ips[0];
206     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.2" );
207     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 92 );
208     ip = ips[1];
209     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.3" );
210     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 93 );
211 }
212
213 BOOST_AUTO_TEST_CASE( cache_retrieve_cname )
214 {
215     HostAddressVec ips = Cache->get_ips("cname.test");
216     BOOST_CHECK( ips.empty() );
217
218     Cname cname = Cache->get_cname("cname.test");
219     BOOST_CHECK_EQUAL( cname.Host, "host1.test" );
220     BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 35 );
221 }
222
223 BOOST_AUTO_TEST_CASE( cache_retrieve_recursive1 )
224 {
225     // should get IP from host1 but ttl from cname since is smaller
226     HostAddressVec ips = Cache->get_ips_recursive("cname.test");
227     BOOST_CHECK_EQUAL( ips.size(), 1 );
228     HostAddress ip = ips.front();
229     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
230     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 35 );
231 }
232
233 BOOST_AUTO_TEST_CASE( cache_retrieve_recursive2 )
234 {
235     // should get IP from host1 but ttl from cname2 since is smaller
236     HostAddressVec ips = Cache->get_ips_recursive("cname3.test");
237     BOOST_CHECK_EQUAL( ips.size(), 1 );
238     HostAddress ip = ips.front();
239     BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" );
240     BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 33 );
241 }
242
243 void cname_skip_test(const uint32_t ttl1, const uint32_t ttl2,
244                      const uint32_t ttl3, const uint32_t ttl4,
245                      const DnsCacheItem &cache,
246                      const std::string &correct_host_after_skip)
247 {
248     {   // create cname chain:
249         // skip_chain_first -(ttl1)-> skip_chain_second -(ttl2)->
250         //   skip_chain_third -(ttl3)-> skip_chain_fourth -(ttl4)-> IPs
251         cache->update( "skip_chain_first.test",
252                  Cname("skip_chain_second.test", ttl1) );
253         cache->update( "skip_chain_second.test",
254                  Cname("skip_chain_third.test", ttl2) );
255         cache->update( "skip_chain_third.test",
256                  Cname("skip_chain_fourth.test", ttl3) );
257         HostAddressVec ips;
258         ips.push_back( HostAddress( address::from_string("192.168.42.4"), ttl4) );
259         cache->update("skip_chain_fourth.test", ips);
260     }
261
262     // normal recursive call should give nothing since one cname is outdated
263     bool check_up_to_date = true;
264     HostAddressVec ips = cache->get_ips_recursive("skip_chain_first.test",
265                                                   check_up_to_date);
266     bool one_is_out_of_date = (ttl1 < 5) || (ttl2 < 5)
267                            || (ttl3 < 5) || (ttl4 < 5);
268     BOOST_CHECK_EQUAL( ips.empty(), one_is_out_of_date );
269
270     // now find host to resolve after the outdated one
271     std::string first_outdated = cache->get_first_outdated_cname(
272                                                     "skip_chain_first.test", 5);
273     BOOST_CHECK_EQUAL( first_outdated, correct_host_after_skip );
274 }
275
276 BOOST_AUTO_TEST_CASE( cache_skip_tests )
277 {
278     // build a cname chain where first one is out of date
279     cname_skip_test(0, 120, 120, 60, Cache, "skip_chain_second.test");
280
281     // build a cname chain where second one is out of date
282     cname_skip_test(120, 0, 120, 60, Cache, "skip_chain_third.test");
283
284     // build a cname chain where third one is out of date
285     cname_skip_test(120, 120, 0, 120, Cache, "skip_chain_fourth.test");
286
287     // build a cname chain where just IPs are out of date
288     cname_skip_test(120, 120, 120, 0, Cache, "");
289
290     // build a cname chain where all are out of date
291     cname_skip_test(0, 0, 0, 0, Cache, "skip_chain_second.test");
292
293     // build a cname chain where all is up to date
294     cname_skip_test(120, 120, 120, 120, Cache, "");
295
296     // test case with very short cname list that is up-to-date
297     BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("cname.test", 5), "" );
298
299     // test case where there is no cname at all
300     BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("host1.test", 5), "" );
301 }
302
303
304 BOOST_AUTO_TEST_SUITE_END()    // of TestDnsCache
305
306
307 // -----------------------------------------------------------------------------
308 // test resolver
309 // -----------------------------------------------------------------------------
310
311 BOOST_FIXTURE_TEST_SUITE( TestDnsResolver, TestFixture )
312
313 BOOST_AUTO_TEST_CASE( create_resolver_v4 )
314 {
315     // create resolver
316     std::string hostname = "www.intra2net.com";
317     ResolverItem resolver = Master->get_resolver_for(hostname,
318                                                      PingProtocol_ICMP);
319     BOOST_CHECK_EQUAL( resolver->get_hostname(), hostname );
320     BOOST_CHECK( !resolver->is_resolving() );
321
322     // cancel should have no effect
323     resolver->cancel_resolve();
324     BOOST_CHECK( !resolver->is_resolving() );
325
326     // should not have any ips since cache did not load anything
327     BOOST_CHECK_EQUAL( resolver->get_resolved_ip_count(), 0 );
328     BOOST_CHECK( !resolver->have_up_to_date_ip() );
329     std::string no_ip = "0.0.0.0";
330     BOOST_CHECK_EQUAL( resolver->get_next_ip().get_ip().to_string(), no_ip );
331 }
332
333
334 void resolve_callback(IoServiceItem io_serv, ResolverItem resolver,
335                       const bool was_success, const int cname_count)
336 {
337     resolver->cancel_resolve();
338     io_serv->stop();
339     BOOST_TEST_MESSAGE( "Stopped io_service" );
340 }
341
342
343 BOOST_AUTO_TEST_CASE( do_resolve )
344 {
345     // create resolver
346     std::string hostname = "www.intra2net.com";
347     ResolverItem resolver = Master->get_resolver_for(hostname,
348                                                      PingProtocol_ICMP);
349
350     // set callback
351     callback_type callback = boost::bind( resolve_callback, IoService, resolver,
352                                           _1, _2 );
353     // start resolving
354     resolver->async_resolve(callback);
355     IoService->run();
356     // this will block until io service is stopped in resolve_callback
357
358     // check for result
359     BOOST_CHECK( resolver->have_up_to_date_ip() );
360 }
361
362 BOOST_AUTO_TEST_SUITE_END()   // of TestDnsResolver
363
364 BOOST_AUTO_TEST_SUITE_END()