Asterisk - The Open Source Telephony Project  21.4.1
test_dns_query_set.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*** MODULEINFO
20  <depend>TEST_FRAMEWORK</depend>
21  <support_level>core</support_level>
22  ***/
23 
24 #include "asterisk.h"
25 
26 #include <arpa/nameser.h>
27 #include <arpa/inet.h>
28 
29 #include "asterisk/test.h"
30 #include "asterisk/module.h"
31 #include "asterisk/vector.h"
32 #include "asterisk/dns_core.h"
33 #include "asterisk/dns_resolver.h"
34 #include "asterisk/dns_query_set.h"
35 #include "asterisk/dns_internal.h"
36 
38  /*! Boolean indicator if query set has completed */
40  /*! Number of times resolve() method has been called */
41  int resolves;
42  /*! Number of times resolve() method is allowed to be called */
44  /*! Number of times cancel() method has been called */
45  int cancel;
46  /*! Number of times cancel() method is allowed to be called */
48  ast_mutex_t lock;
49  ast_cond_t cond;
50 };
51 
52 static void query_set_data_destructor(void *obj)
53 {
54  struct query_set_data *qsdata = obj;
55 
56  ast_mutex_destroy(&qsdata->lock);
57  ast_cond_destroy(&qsdata->cond);
58 }
59 
60 static struct query_set_data *query_set_data_alloc(void)
61 {
62  struct query_set_data *qsdata;
63 
64  qsdata = ao2_alloc(sizeof(*qsdata), query_set_data_destructor);
65  if (!qsdata) {
66  return NULL;
67  }
68 
69  ast_mutex_init(&qsdata->lock);
70  ast_cond_init(&qsdata->cond, NULL);
71 
72  return qsdata;
73 }
74 
75 #define DNS_ANSWER "Yes sirree"
76 #define DNS_ANSWER_SIZE strlen(DNS_ANSWER)
77 
78 /*!
79  * \brief Thread that performs asynchronous resolution.
80  *
81  * This thread uses the query's user data to determine how to
82  * perform the resolution. If the allowed number of resolutions
83  * has not been reached then this will succeed, otherwise the
84  * query is expected to have been canceled.
85  *
86  * \param dns_query The ast_dns_query that is being performed
87  * \return NULL
88  */
89 static void *resolution_thread(void *dns_query)
90 {
91  struct ast_dns_query *query = dns_query;
92  struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
93  struct query_set_data *qsdata = query_set->user_data;
94 
95  ast_assert(qsdata != NULL);
96 
97  ast_dns_resolver_set_result(query, 0, 0, NOERROR, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE);
99 
100  ao2_ref(query, -1);
101  return NULL;
102 }
103 
104 /*!
105  * \brief Resolver's resolve() method
106  *
107  * \param query The query that is to be resolved
108  * \retval 0 Successfully created thread to perform the resolution
109  * \retval non-zero Failed to create resolution thread
110  */
111 static int query_set_resolve(struct ast_dns_query *query)
112 {
113  struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
114  struct query_set_data *qsdata = query_set->user_data;
115  pthread_t resolver_thread;
116 
117  /* Only the queries which will not be canceled actually start a thread */
118  if (qsdata->resolves++ < qsdata->cancel_allowed) {
119  return 0;
120  }
121 
122  return ast_pthread_create_detached(&resolver_thread, NULL, resolution_thread, ao2_bump(query));
123 }
124 
125 /*!
126  * \brief Resolver's cancel() method
127  *
128  * \param query The query to cancel
129  * \return 0
130  */
131 static int query_set_cancel(struct ast_dns_query *query)
132 {
133  struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
134  struct query_set_data *qsdata;
135  int res = -1;
136 
137  if (!query_set) {
138  return -1;
139  }
140  qsdata = query_set->user_data;
141 
142  if (qsdata->cancel++ < qsdata->cancel_allowed) {
143  res = 0;
144  }
145 
146  return res;
147 }
148 
149 static struct ast_dns_resolver query_set_resolver = {
150  .name = "query_set",
151  .priority = 0,
152  .resolve = query_set_resolve,
153  .cancel = query_set_cancel,
154 };
155 
156 /*!
157  * \brief Callback which is invoked upon query set completion
158  *
159  * \param query_set The query set
160  */
161 static void query_set_callback(const struct ast_dns_query_set *query_set)
162 {
163  struct query_set_data *qsdata = ast_dns_query_set_get_data(query_set);
164 
165  ast_mutex_lock(&qsdata->lock);
166  qsdata->query_set_complete = 1;
167  ast_cond_signal(&qsdata->cond);
168  ast_mutex_unlock(&qsdata->lock);
169 }
170 
171 /*!
172  * \brief Framework for running a query set DNS test
173  *
174  * This function serves as a common way of testing various numbers of queries in a
175  * query set and optional canceling of them.
176  *
177  * \param test The test being run
178  * \param resolve The number of queries that should be allowed to complete resolution
179  * \param cancel The number of queries that should be allowed to be canceled
180  */
181 static enum ast_test_result_state query_set_test(struct ast_test *test, int resolve, int cancel)
182 {
183  int total = resolve + cancel;
184  RAII_VAR(struct ast_dns_query_set *, query_set, NULL, ao2_cleanup);
185  RAII_VAR(struct query_set_data *, qsdata, NULL, ao2_cleanup);
186  enum ast_test_result_state res = AST_TEST_PASS;
187  int idx;
188  struct timespec timeout;
189 
190  if (ast_dns_resolver_register(&query_set_resolver)) {
191  ast_test_status_update(test, "Failed to register query set DNS resolver\n");
192  return AST_TEST_FAIL;
193  }
194 
195  qsdata = query_set_data_alloc();
196  if (!qsdata) {
197  ast_test_status_update(test, "Failed to allocate data necessary for query set test\n");
198  res = AST_TEST_FAIL;
199  goto cleanup;
200  }
201 
202  query_set = ast_dns_query_set_create();
203  if (!query_set) {
204  ast_test_status_update(test, "Failed to create DNS query set\n");
205  res = AST_TEST_FAIL;
206  goto cleanup;
207  }
208 
209  qsdata->resolves_allowed = resolve;
210  qsdata->cancel_allowed = cancel;
211 
212  for (idx = 0; idx < total; ++idx) {
213  if (ast_dns_query_set_add(query_set, "asterisk.org", T_A, C_IN)) {
214  ast_test_status_update(test, "Failed to add query to DNS query set\n");
215  res = AST_TEST_FAIL;
216  goto cleanup;
217  }
218  }
219 
220  if (ast_dns_query_set_num_queries(query_set) != total) {
221  ast_test_status_update(test, "DNS query set does not contain the correct number of queries\n");
222  res = AST_TEST_FAIL;
223  goto cleanup;
224  }
225 
226  ast_dns_query_set_resolve_async(query_set, query_set_callback, qsdata);
227 
228  if (cancel && (cancel == total)) {
229  if (ast_dns_query_set_resolve_cancel(query_set)) {
230  ast_test_status_update(test, "Failed to cancel DNS query set when it should be cancellable\n");
231  res = AST_TEST_FAIL;
232  }
233 
234  if (qsdata->query_set_complete) {
235  ast_test_status_update(test, "Query set callback was invoked despite all queries being cancelled\n");
236  res = AST_TEST_FAIL;
237  }
238 
239  goto cleanup;
240  } else if (cancel) {
241  if (!ast_dns_query_set_resolve_cancel(query_set)) {
242  ast_test_status_update(test, "Successfully cancelled DNS query set when it should not be possible\n");
243  res = AST_TEST_FAIL;
244  goto cleanup;
245  }
246  }
247 
248  timeout = ast_tsnow();
249  timeout.tv_sec += 10;
250 
251  ast_mutex_lock(&qsdata->lock);
252  while (!qsdata->query_set_complete) {
253  if (ast_cond_timedwait(&qsdata->cond, &qsdata->lock, &timeout) == ETIMEDOUT) {
254  break;
255  }
256  }
257  ast_mutex_unlock(&qsdata->lock);
258 
259  if (!qsdata->query_set_complete) {
260  ast_test_status_update(test, "Query set did not complete when it should have\n");
261  res = AST_TEST_FAIL;
262  goto cleanup;
263  }
264 
265  for (idx = 0; idx < ast_dns_query_set_num_queries(query_set); ++idx) {
266  const struct ast_dns_query *query = ast_dns_query_set_get(query_set, idx);
267 
268  if (strcmp(ast_dns_query_get_name(query), "asterisk.org")) {
269  ast_test_status_update(test, "Query did not have expected name\n");
270  res = AST_TEST_FAIL;
271  }
272  if (ast_dns_query_get_rr_type(query) != T_A) {
273  ast_test_status_update(test, "Query did not have expected type\n");
274  res = AST_TEST_FAIL;
275  }
276  if (ast_dns_query_get_rr_class(query) != C_IN) {
277  ast_test_status_update(test, "Query did not have expected class\n");
278  res = AST_TEST_FAIL;
279  }
280  }
281 
282 cleanup:
283  ast_dns_resolver_unregister(&query_set_resolver);
284  return res;
285 }
286 
287 AST_TEST_DEFINE(query_set)
288 {
289  switch (cmd) {
290  case TEST_INIT:
291  info->name = "query_set";
292  info->category = "/main/dns/query_set/";
293  info->summary = "Test nominal asynchronous DNS query set";
294  info->description =
295  "This tests nominal query set in the following ways:\n"
296  "\t* Multiple queries are added to a query set\n"
297  "\t* The mock resolver is configured to respond to all queries\n"
298  "\t* Asynchronous resolution of the query set is started\n"
299  "\t* The mock resolver responds to all queries\n"
300  "\t* We ensure that the query set callback is invoked upon completion";
301  return AST_TEST_NOT_RUN;
302  case TEST_EXECUTE:
303  break;
304  }
305 
306  return query_set_test(test, 4, 0);
307 }
308 
309 AST_TEST_DEFINE(query_set_empty)
310 {
311  switch (cmd) {
312  case TEST_INIT:
313  info->name = "query_set_empty";
314  info->category = "/main/dns/query_set/";
315  info->summary = "Test nominal asynchronous empty DNS query set";
316  info->description =
317  "This tests nominal query set in the following ways:\n"
318  "\t* No queries are added to a query set\n"
319  "\t* Asynchronous resolution of the query set is started\n"
320  "\t* We ensure that the query set callback is invoked upon completion";
321  return AST_TEST_NOT_RUN;
322  case TEST_EXECUTE:
323  break;
324  }
325 
326  return query_set_test(test, 0, 0);
327 }
328 
329 AST_TEST_DEFINE(query_set_nominal_cancel)
330 {
331  switch (cmd) {
332  case TEST_INIT:
333  info->name = "query_set_nominal_cancel";
334  info->category = "/main/dns/query_set/";
335  info->summary = "Test nominal asynchronous DNS query set cancellation";
336  info->description =
337  "This tests nominal query set cancellation in the following ways:\n"
338  "\t* Multiple queries are added to a query set\n"
339  "\t* The mock resolver is configured to NOT respond to any queries\n"
340  "\t* Asynchronous resolution of the query set is started\n"
341  "\t* The query set is canceled and is confirmed to return with success";
342  return AST_TEST_NOT_RUN;
343  case TEST_EXECUTE:
344  break;
345  }
346 
347  return query_set_test(test, 0, 4);
348 }
349 
350 AST_TEST_DEFINE(query_set_off_nominal_cancel)
351 {
352  switch (cmd) {
353  case TEST_INIT:
354  info->name = "query_set_off_nominal_cancel";
355  info->category = "/main/dns/query_set/";
356  info->summary = "Test off-nominal asynchronous DNS query set cancellation";
357  info->description =
358  "This tests nominal query set cancellation in the following ways:\n"
359  "\t* Multiple queries are added to a query set\n"
360  "\t* The mock resolver is configured to respond to half the queries\n"
361  "\t* Asynchronous resolution of the query set is started\n"
362  "\t* The query set is canceled and is confirmed to return failure\n"
363  "\t* The query set callback is confirmed to run, since it could not be fully canceled";
364  return AST_TEST_NOT_RUN;
365  case TEST_EXECUTE:
366  break;
367  }
368 
369  return query_set_test(test, 2, 2);
370 }
371 
372 static int unload_module(void)
373 {
374  AST_TEST_UNREGISTER(query_set);
375  AST_TEST_UNREGISTER(query_set_empty);
376  AST_TEST_UNREGISTER(query_set_nominal_cancel);
377  AST_TEST_UNREGISTER(query_set_off_nominal_cancel);
378 
379  return 0;
380 }
381 
382 static int load_module(void)
383 {
384  AST_TEST_REGISTER(query_set);
385  AST_TEST_REGISTER(query_set_empty);
386  AST_TEST_REGISTER(query_set_nominal_cancel);
387  AST_TEST_REGISTER(query_set_off_nominal_cancel);
388 
390 }
391 
392 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DNS query set tests");
Asterisk main include file. File version handling, generic pbx functions.
const char * ast_dns_query_get_name(const struct ast_dns_query *query)
Get the name queried in a DNS query.
Definition: dns_core.c:57
int ast_dns_query_get_rr_class(const struct ast_dns_query *query)
Get the record resource class of a DNS query.
Definition: dns_core.c:67
void ast_dns_resolver_unregister(struct ast_dns_resolver *resolver)
Unregister a DNS resolver.
Definition: dns_core.c:680
void * user_data
User-specific data.
Definition: dns_internal.h:197
struct ast_dns_query_set * ast_dns_query_set_create(void)
Create a query set to hold queries.
Definition: dns_query_set.c:60
Test Framework API.
const char * name
The name of the resolver implementation.
Definition: dns_resolver.h:34
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition: res_stasis.c:327
void * ast_dns_query_get_data(const struct ast_dns_query *query)
Get the user specific data of a DNS query.
Definition: dns_core.c:72
struct ast_dns_query * ast_dns_query_set_get(const struct ast_dns_query_set *query_set, unsigned int index)
Retrieve a query from a query set.
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
int ast_dns_resolver_register(struct ast_dns_resolver *resolver)
Register a DNS resolver.
Definition: dns_core.c:630
void ast_dns_resolver_completed(struct ast_dns_query *query)
Mark a DNS query as having been completed.
Definition: dns_core.c:599
DNS Query Set API.
DNS resolver implementation.
Definition: dns_resolver.h:32
Internal DNS structure definitions.
A DNS query.
Definition: dns_internal.h:137
int ast_dns_query_set_resolve_cancel(struct ast_dns_query_set *query_set)
Cancel an asynchronous DNS query set resolution.
Vector container support.
int ast_dns_query_get_rr_type(const struct ast_dns_query *query)
Get the record resource type of a DNS query.
Definition: dns_core.c:62
int ast_dns_query_set_add(struct ast_dns_query_set *query_set, const char *name, int rr_type, int rr_class)
Add a query to a query set.
struct timespec ast_tsnow(void)
Returns current timespec. Meant to avoid calling ast_tvnow() just to create a timespec from the timev...
Definition: time.h:186
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
size_t ast_dns_query_set_num_queries(const struct ast_dns_query_set *query_set)
Retrieve the number of queries in a query set.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_dns_resolver_set_result(struct ast_dns_query *query, unsigned int secure, unsigned int bogus, unsigned int rcode, const char *canonical, const char *answer, size_t answer_size)
Set result information for a DNS query.
Definition: dns_core.c:456
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
A set of DNS queries.
Definition: dns_internal.h:185
DNS Resolver API.
void ast_dns_query_set_resolve_async(struct ast_dns_query_set *query_set, ast_dns_query_set_callback callback, void *data)
Asynchronously resolve queries in a query set.
Structure for mutex and tracking information.
Definition: lock.h:135
Core DNS API.
void * ast_dns_query_set_get_data(const struct ast_dns_query_set *query_set)
Retrieve user specific data from a query set.