diff options
author | Jakub Hrozek <jhrozek@redhat.com> | 2010-04-15 15:39:55 +0200 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-04-30 07:51:18 -0400 |
commit | fbae85bcb4b3940024f8e3c127fac9da3671302d (patch) | |
tree | 69a7597ff9f81e90d50d52a135f25efdfeb0e186 | |
parent | bd290f62727b8903d889705a9d129ee6c9d62bc9 (diff) | |
download | sssd-fbae85bcb4b3940024f8e3c127fac9da3671302d.tar.gz sssd-fbae85bcb4b3940024f8e3c127fac9da3671302d.tar.xz sssd-fbae85bcb4b3940024f8e3c127fac9da3671302d.zip |
Sort SRV replies according to RFC 2782
RFC 2782 defines a way to sort replies to a SRV query. In short, the
algorithm sorts all replies by priority and then does a weight-based
selection for every priority level.
For details, please see the sections "Usage rules" for overview of the
algorithm and section "The 'Weight' field" for description on the weight
selection.
-rw-r--r-- | src/resolv/async_resolv.c | 244 | ||||
-rw-r--r-- | src/resolv/async_resolv.h | 4 | ||||
-rw-r--r-- | src/tests/resolv-tests.c | 88 |
3 files changed, 336 insertions, 0 deletions
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c index e3bedc60a..fe3b3f6bd 100644 --- a/src/resolv/async_resolv.c +++ b/src/resolv/async_resolv.c @@ -1073,3 +1073,247 @@ ares_gettxt_wakeup(struct tevent_req *subreq) } #endif + +static struct ares_srv_reply *split_reply_list(struct ares_srv_reply *list) +{ + struct ares_srv_reply *single_step, *double_step, *prev; + + if (!list) { + return NULL; + } + + prev = list; + single_step = list->next; + double_step = single_step->next; + + while (double_step && double_step->next) { + prev = single_step; + single_step = single_step->next; + double_step = double_step->next->next; + } + + prev->next = NULL; + return single_step; +} + +static struct ares_srv_reply *merge_reply_list(struct ares_srv_reply *left, + struct ares_srv_reply *right) +{ + struct ares_srv_reply *l, *r; + struct ares_srv_reply *res, *res_start; + + if (!left) + return right; + if (!right) + return left; + + if (left->priority < right->priority) { + res_start = left; + l = left->next; + r = right; + } else { + res_start = right; + l = left; + r = right->next; + } + + res = res_start; + + while(l && r) { + if (l->priority < r->priority) { + res->next = l; + res = l; + l = l->next; + } else { + res->next = r; + res = r; + r = r->next; + } + } + + res->next = l ? l : r; + + return res_start; +} + +/** + * sort linked list of struct ares_srv_reply by priority using merge sort. + * + * Merge sort is ideal for sorting linked lists as there is no problem + * with absence of random access into the list. The complexity is O(n log n) + * + * For reference, see Robert Sedgewick's "Algorithms in C", Addison-Wesley, + * ISBN 0-201-51425 + */ +static struct ares_srv_reply *reply_priority_sort(struct ares_srv_reply *list) +{ + struct ares_srv_reply *half; + + if (!list || !list->next) + return list; + + half = split_reply_list(list); + list = merge_reply_list(reply_priority_sort(list), + reply_priority_sort(half)); + + return list; +} + +static int reply_weight_rearrange(TALLOC_CTX *mem_ctx, + int len, + struct ares_srv_reply **start, + struct ares_srv_reply **end) +{ + int i; + int total, selected; + int *totals; + struct ares_srv_reply *r, *prev, *tmp; + struct ares_srv_reply *new_start = NULL; + struct ares_srv_reply *new_end = NULL; + + if (len <= 1) { + return EOK; + } + + totals = talloc_array(mem_ctx, int, len); + if (!totals) { + return ENOMEM; + } + + srand(time(NULL) * getpid()); + + /* promote all servers with weight==0 to the top */ + r = *(start); + prev = NULL; + while (r != NULL) { + if (r->weight == 0) { + /* remove from the old list */ + if (prev) { + prev->next = r->next; + } else { + *start = r->next; + } + + /* add to the head of the new list */ + tmp = r; + r = r->next; + + tmp->next = *start; + *start = tmp; + } else { + prev = r; + r = r->next; + } + } + *end = prev ? prev : *start; + + while (*start != NULL) { + /* Commpute the sum of the weights of those RRs, and with each RR + * associate the running sum in the selected order. + */ + total = 0; + memset(totals, -1, sizeof(int) * len); + for (i = 0, r = *start; r != NULL; r=r->next, ++i) { + totals[i] = r->weight + total; + total = totals[i]; + } + + /* choose a uniform random number between 0 and the sum computed + * (inclusive), and select the RR whose running sum value is the + * first in the selected order which is greater than or equal to + * the random number selected. + */ + selected = (int)((total + 1) * (rand()/(RAND_MAX + 1.0))); + for (i = 0, r = *start, prev = NULL; r != NULL; r=r->next, ++i) { + if (totals[i] >= selected) + break; + + prev = r; + } + + if (r == NULL || totals[i] == -1) { + DEBUG(1, ("Bug: did not select any server!\n")); + return EIO; + } + + /* remove r from the old list */ + if (prev) { + prev->next = r->next; + } else { + *start = r->next; + } + + /* add r to the end of the new list */ + if (!new_start) { + new_start = r; + new_end = r; + } else { + new_end->next = r; + new_end = r; + } + } + new_end->next = NULL; + + /* return the rearranged list */ + *start = new_start; + *end = new_end; + talloc_free(totals); + return EOK; +} + +int +resolv_sort_srv_reply(TALLOC_CTX *mem_ctx, struct ares_srv_reply **reply) +{ + int ret; + struct ares_srv_reply *pri_start, *pri_end, *next, *prev_end; + int len; + + /* RFC 2782 says: If there is precisely one SRV RR, and its Target is "." + * (the root domain), abort. + */ + if (*reply && !(*reply)->next && strcmp((*reply)->host, ".") == 0) { + DEBUG(1, ("DNS returned only the root domain, aborting\n")); + return EIO; + } + + /* sort the list by priority */ + *reply = reply_priority_sort(*reply); + + pri_start = *reply; + prev_end = NULL; + + while (pri_start) { + pri_end = pri_start; + + /* Find nodes with the same priority */ + len = 1; + while (pri_end->next && pri_end->priority == pri_end->next->priority) { + pri_end = pri_end->next; + len++; + } + + /* rearrange each priority level according to the weight field */ + next = pri_end->next; + pri_end->next = NULL; + ret = reply_weight_rearrange(mem_ctx, len, &pri_start, &pri_end); + if (ret) { + DEBUG(1, ("Error rearranging priority level [%d]: %s\n", + ret, strerror(ret))); + return ret; + } + + /* Hook the level back into the list */ + if (prev_end) { + prev_end->next = pri_start; + } else { + *reply = pri_start; + } + pri_end->next = next; + + /* Move on to the next level */ + prev_end = pri_end; + pri_start = next; + } + + return EOK; +} diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h index 5f87f12e3..b6a13d7fa 100644 --- a/src/resolv/async_resolv.h +++ b/src/resolv/async_resolv.h @@ -88,6 +88,10 @@ int resolv_getsrv_recv(TALLOC_CTX *mem_ctx, int *timeouts, struct ares_srv_reply **reply_list); +/* This is an implementation of section "Usage rules" of RFC 2782 */ +int +resolv_sort_srv_reply(TALLOC_CTX *mem_ctx, struct ares_srv_reply **reply); + /** Get TXT record **/ struct tevent_req *resolv_gettxt_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, diff --git a/src/tests/resolv-tests.c b/src/tests/resolv-tests.c index 7f38192d9..b8c535cf0 100644 --- a/src/tests/resolv-tests.c +++ b/src/tests/resolv-tests.c @@ -541,6 +541,93 @@ static void resolv_free_req(struct tevent_context *ev, talloc_free(req); } +START_TEST(test_resolv_sort_srv_reply) +{ + int ret; + struct ares_srv_reply *replies = NULL; + struct ares_srv_reply *r, *prev = NULL; + struct resolv_test_ctx *test_ctx; + int num_replies = 3; + int i; + + ret = setup_resolv_test(&test_ctx); + if (ret != EOK) { + fail("Could not set up test"); + return; + } + + check_leaks_push(test_ctx); + + /* prepare linked list with reversed values */ + for (i = 0; i<num_replies; i++) { + r = talloc_zero(test_ctx, struct ares_srv_reply); + fail_if(r == NULL); + r->priority = num_replies-i; + r->weight = i; + + if (!replies) { + replies = r; + prev = r; + } else { + prev->next = r; + prev = prev->next; + } + } + + /* do the sort */ + ret = resolv_sort_srv_reply(test_ctx, &replies); + fail_if(ret != EOK); + + /* check if the list is sorted */ + prev = NULL; + for (i = 1, r = replies; r; r=r->next, i++) { + talloc_zfree(prev); + prev = r; + fail_unless(r->priority == i); + } + talloc_zfree(prev); + + /* check if the list is complete */ + fail_unless(i-1 == num_replies); + + /* test if the weighting algorithm runs..not much do + * deterministically test here since it is based on + * random weight-selection */ + replies = NULL; + for (i = 0; i<num_replies; i++) { + r = talloc_zero(test_ctx, struct ares_srv_reply); + fail_if(r == NULL); + r->priority = i % 2 + 1; + r->weight = i; + + if (!replies) { + replies = r; + prev = r; + } else { + prev->next = r; + prev = prev->next; + } + } + + /* do the sort */ + ret = resolv_sort_srv_reply(test_ctx, &replies); + fail_if(ret != EOK); + + /* clean up */ + prev = NULL; + for (i = 1, r = replies; r; r=r->next, i++) { + talloc_zfree(prev); + prev = r; + } + talloc_zfree(prev); + + + /* check for leaks */ + check_leaks_pop(test_ctx); + talloc_zfree(test_ctx); +} +END_TEST + START_TEST(test_resolv_free_req) { int ret = EOK; @@ -602,6 +689,7 @@ Suite *create_resolv_suite(void) /* Do some testing */ tcase_add_test(tc_resolv, test_copy_hostent); tcase_add_test(tc_resolv, test_resolv_ip_addr); + tcase_add_test(tc_resolv, test_resolv_sort_srv_reply); if (use_net_test) { tcase_add_test(tc_resolv, test_resolv_internet); tcase_add_test(tc_resolv, test_resolv_negative); |