Mercurial > pidgin
diff libpurple/dnssrv.c @ 27590:a08e84032814
merge of '2348ff22f0ff3453774b8b25b36238465580c609'
and 'e76f11543c2a4aa05bdf584f087cbe3439029661'
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Sun, 12 Jul 2009 05:43:38 +0000 |
parents | e400cd35542b |
children | 7fbf964c6c6c |
line wrap: on
line diff
--- a/libpurple/dnssrv.c Sun Jul 12 05:42:40 2009 +0000 +++ b/libpurple/dnssrv.c Sun Jul 12 05:43:38 2009 +0000 @@ -66,7 +66,7 @@ #endif struct _PurpleTxtResponse { - char *content; + char *content; }; struct _PurpleSrvQueryData { @@ -82,7 +82,7 @@ GThread *resolver; char *query; char *error_message; - GSList *results; + GList *results; #else int fd_in, fd_out; pid_t pid; @@ -94,6 +94,17 @@ char query[256]; } PurpleSrvInternalQuery; +typedef struct _PurpleSrvResponseContainer { + PurpleSrvResponse *response; + int sum; +} PurpleSrvResponseContainer; + +/** + * Sort by priority, then by weight. Strictly numerically--no + * randomness. Technically we only need to sort by pref and then + * make sure any records with weight 0 are at the beginning of + * their group, but it's just as easy to sort by weight. + */ static gint responsecompare(gconstpointer ar, gconstpointer br) { @@ -112,6 +123,130 @@ return 1; } +/** + * Iterate over a list of PurpleSrvResponseContainer making the sum + * the running total of the sums. Select a random integer in the range + * (1, sum+1), then find the first element greater than or equal to the + * number selected. From RFC 2782. + * + * @param list The list of PurpleSrvResponseContainer. This function + * removes a node from this list and returns the new list. + * @param container_ptr The PurpleSrvResponseContainer that was chosen + * will be returned here. + */ +static GList * +select_random_response(GList *list, PurpleSrvResponseContainer **container_ptr) +{ + GList *cur; + size_t runningtotal; + int r; + + runningtotal = 0; + cur = list; + + while (cur) { + PurpleSrvResponseContainer *container = cur->data; + runningtotal += container->response->weight; + container->sum = runningtotal; + cur = cur->next; + } + + /* + * If the running total is greater than 0, pick a number between + * 1 and the runningtotal inclusive. (This is not precisely what + * the RFC algorithm describes, but we wish to deal with integers + * and avoid floats. This is functionally equivalent.) + * If running total is 0, then choose r = 0. + */ + r = runningtotal ? g_random_int_range(1, runningtotal + 1) : 0; + cur = list; + while (r > ((PurpleSrvResponseContainer *)cur->data)->sum) { + cur = cur->next; + } + + /* Set the return parameter and remove cur from the list */ + *container_ptr = cur->data; + return g_list_delete_link(list, cur); +} + +/** + * Reorder a GList of PurpleSrvResponses that have the same priority + * (aka "pref"). + */ +static void +srv_reorder(GList *list, int num) +{ + int i; + GList *cur, *container_list = NULL; + PurpleSrvResponseContainer *container; + + if (num < 2) + /* Nothing to sort */ + return; + + /* First build a list of container structs */ + for (i = 0, cur = list; i < num; i++, cur = cur->next) { + container = g_new(PurpleSrvResponseContainer, 1); + container->response = cur->data; + container_list = g_list_prepend(container_list, container); + } + container_list = g_list_reverse(container_list); + + /* + * Re-order the list that was passed in as a parameter. We leave + * the list nodes in place, but replace their data pointers. + */ + cur = list; + while (container_list) { + container_list = select_random_response(container_list, &container); + cur->data = container->response; + g_free(container); + cur = cur->next; + } +} + +/** + * Sorts a GList of PurpleSrvResponses according to the + * algorithm described in RFC 2782. + * + * @param response GList of PurpleSrvResponse's + * @param The original list, resorted + */ +static GList * +purple_srv_sort(GList *list) +{ + int pref, count; + GList *cur, *start; + + if (!list || !list->next) { + /* Nothing to sort */ + return list; + } + + list = g_list_sort(list, responsecompare); + + start = cur = list; + count = 1; + while (cur) { + PurpleSrvResponse *next_response; + pref = ((PurpleSrvResponse *)cur->data)->pref; + next_response = cur->next ? cur->next->data : NULL; + if (!next_response || next_response->pref != pref) { + /* + * The 'count' records starting at 'start' all have the same + * priority. Sort them by weight. + */ + srv_reorder(start, count); + start = cur->next; + count = 0; + } + count++; + cur = cur->next; + } + + return list; +} + #ifndef _WIN32 G_GNUC_NORETURN static void @@ -121,11 +256,8 @@ PurpleSrvResponse *srvres; PurpleTxtResponse *txtres; queryans answer; - int size; - int qdcount; - int ancount; - guchar *end; - guchar *cp; + int size, qdcount, ancount; + guchar *end, *cp; gchar name[256]; guint16 type, dlen, pref, weight, port; PurpleSrvInternalQuery query; @@ -191,7 +323,7 @@ srvres->port = port; srvres->weight = weight; - ret = g_list_insert_sorted(ret, srvres, responsecompare); + ret = g_list_prepend(ret, srvres); } else if (query.type == T_TXT) { txtres = g_new0(PurpleTxtResponse, 1); txtres->content = g_strndup((gchar*)(++cp), dlen-1); @@ -204,14 +336,21 @@ end: size = g_list_length(ret); + + if (query.type == T_SRV) + ret = purple_srv_sort(ret); + /* TODO: Check return value */ write(out, &(query.type), sizeof(query.type)); write(out, &size, sizeof(size)); while (ret != NULL) { /* TODO: Check return value */ - if (query.type == T_SRV) write(out, ret->data, sizeof(PurpleSrvResponse)); - if (query.type == T_TXT) write(out, ret->data, sizeof(PurpleTxtResponse)); + if (query.type == T_SRV) + write(out, ret->data, sizeof(PurpleSrvResponse)); + if (query.type == T_TXT) + write(out, ret->data, sizeof(PurpleTxtResponse)); + g_free(ret->data); ret = g_list_remove(ret, ret->data); } @@ -272,7 +411,7 @@ cb(res, size, query_data->extradata); } else if (type == T_TXT) { - GSList *responses = NULL; + GList *responses = NULL; PurpleTxtResponse *res; PurpleTxtCallback cb = query_data->cb.txt; ssize_t red; @@ -285,8 +424,8 @@ "response: %s\n", g_strerror(errno)); size = 0; g_free(res); - g_slist_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); - g_slist_free(responses); + g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); + g_list_free(responses); responses = NULL; break; } @@ -312,36 +451,36 @@ res_main_thread_cb(gpointer data) { PurpleSrvResponse *srvres = NULL; - int size = 0; PurpleSrvQueryData *query_data = data; if(query_data->error_message != NULL) purple_debug_error("dnssrv", query_data->error_message); else { if (query_data->type == DNS_TYPE_SRV) { PurpleSrvResponse *srvres_tmp = NULL; - GSList *lst = query_data->results; - - size = g_slist_length(lst); + GList *lst = query_data->results; + int size = g_list_length(lst); if(query_data->cb.srv && size > 0) srvres_tmp = srvres = g_new0(PurpleSrvResponse, size); while (lst) { + PurpleSrvResponse *lstdata = lst->data; + lst = g_list_delete_link(lst, lst); + if(query_data->cb.srv) - memcpy(srvres_tmp++, lst->data, sizeof(PurpleSrvResponse)); - g_free(lst->data); - lst = g_slist_remove(lst, lst->data); + memcpy(srvres_tmp++, lstdata, sizeof(PurpleSrvResponse)); + g_free(lstdata); } query_data->results = NULL; purple_debug_info("dnssrv", "found %d SRV entries\n", size); - + if(query_data->cb.srv) query_data->cb.srv(srvres, size, query_data->extradata); } else if (query_data->type == DNS_TYPE_TXT) { - GSList *lst = query_data->results; + GList *lst = query_data->results; - purple_debug_info("dnssrv", "found %d TXT entries\n", g_slist_length(lst)); - + purple_debug_info("dnssrv", "found %d TXT entries\n", g_list_length(lst)); + if (query_data->cb.txt) { query_data->results = NULL; query_data->cb.txt(lst, query_data->extradata); @@ -379,10 +518,10 @@ } else { if (type == DNS_TYPE_SRV) { PDNS_RECORD dr_tmp; - GSList *lst = NULL; + GList *lst = NULL; DNS_SRV_DATA *srv_data; PurpleSrvResponse *srvres; - + for (dr_tmp = dr; dr_tmp != NULL; dr_tmp = dr_tmp->pNext) { /* Discard any incorrect entries. I'm not sure if this is necessary */ if (dr_tmp->wType != type || strcmp(dr_tmp->pName, query_data->query) != 0) { @@ -396,15 +535,15 @@ srvres->pref = srv_data->wPriority; srvres->port = srv_data->wPort; srvres->weight = srv_data->wWeight; - - lst = g_slist_insert_sorted(lst, srvres, responsecompare); + + lst = g_list_prepend(lst, srvres); } MyDnsRecordListFree(dr, DnsFreeRecordList); - query_data->results = lst; + query_data->results = purple_srv_sort(lst); } else if (type == DNS_TYPE_TXT) { PDNS_RECORD dr_tmp; - GSList *lst = NULL; + GList *lst = NULL; DNS_TXT_DATA *txt_data; PurpleTxtResponse *txtres; @@ -425,13 +564,13 @@ s = g_string_append(s, txt_data->pStringArray[i]); txtres->content = g_string_free(s, FALSE); - lst = g_slist_append(lst, txtres); + lst = g_list_append(lst, txtres); } MyDnsRecordListFree(dr, DnsFreeRecordList); query_data->results = lst; } else { - + } } @@ -500,10 +639,9 @@ internal_query.type = T_SRV; strncpy(internal_query.query, query, 255); - + if (write(in[1], &internal_query, sizeof(internal_query)) < 0) purple_debug_error("dnssrv", "Could not write to SRV resolver\n"); - query_data = g_new0(PurpleSrvQueryData, 1); query_data->type = T_SRV; @@ -596,10 +734,10 @@ close(out[1]); close(in[0]); - + internal_query.type = T_TXT; strncpy(internal_query.query, query, 255); - + if (write(in[1], &internal_query, sizeof(internal_query)) < 0) purple_debug_error("dnssrv", "Could not write to TXT resolver\n");