Mercurial > pidgin
comparison libpurple/dnssrv.c @ 27213:3e516701dd33
Changes to our DNS SRV record sorting, care of Vijay Vijay Raghunathan
from Meebo.
SRV records have two fields that determine the order in which the results
should be used:
1. Priority (which we call "pref" for some reason). Records with a
lower priority will be used first.
2. Weight. Records with a higher weight are more likely to be used
first, but there is some amount of randomness. We were actually
doing this backwards and using records with lower weight first. And
we weren't randomizing. But now we are.
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Mon, 29 Jun 2009 06:49:59 +0000 |
parents | 862b8208a546 |
children | e400cd35542b |
comparison
equal
deleted
inserted
replaced
27212:862b8208a546 | 27213:3e516701dd33 |
---|---|
92 typedef struct _PurpleSrvInternalQuery { | 92 typedef struct _PurpleSrvInternalQuery { |
93 int type; | 93 int type; |
94 char query[256]; | 94 char query[256]; |
95 } PurpleSrvInternalQuery; | 95 } PurpleSrvInternalQuery; |
96 | 96 |
97 typedef struct _PurpleSrvResponseContainer { | |
98 PurpleSrvResponse *response; | |
99 int sum; | |
100 } PurpleSrvResponseContainer; | |
101 | |
102 /** | |
103 * Sort by priority, then by weight. Strictly numerically--no | |
104 * randomness. Technically we only need to sort by pref and then | |
105 * make sure any records with weight 0 are at the beginning of | |
106 * their group, but it's just as easy to sort by weight. | |
107 */ | |
97 static gint | 108 static gint |
98 responsecompare(gconstpointer ar, gconstpointer br) | 109 responsecompare(gconstpointer ar, gconstpointer br) |
99 { | 110 { |
100 PurpleSrvResponse *a = (PurpleSrvResponse*)ar; | 111 PurpleSrvResponse *a = (PurpleSrvResponse*)ar; |
101 PurpleSrvResponse *b = (PurpleSrvResponse*)br; | 112 PurpleSrvResponse *b = (PurpleSrvResponse*)br; |
108 return 1; | 119 return 1; |
109 } | 120 } |
110 if(a->pref < b->pref) | 121 if(a->pref < b->pref) |
111 return -1; | 122 return -1; |
112 return 1; | 123 return 1; |
124 } | |
125 | |
126 /** | |
127 * Iterate over a list of PurpleSrvResponseContainer making the sum | |
128 * the running total of the sums. Select a random integer in the range | |
129 * (1, sum+1), then find the first element greater than or equal to the | |
130 * number selected. From RFC 2782. | |
131 * | |
132 * @param list The list of PurpleSrvResponseContainer. This function | |
133 * removes a node from this list and returns the new list. | |
134 * @param container_ptr The PurpleSrvResponseContainer that was chosen | |
135 * will be returned here. | |
136 */ | |
137 static GList *select_random_response(GList *list, | |
138 PurpleSrvResponseContainer **container_ptr) | |
139 { | |
140 GList *cur; | |
141 size_t runningtotal; | |
142 int r; | |
143 | |
144 runningtotal = 0; | |
145 cur = list; | |
146 | |
147 while (cur) { | |
148 PurpleSrvResponseContainer *container = cur->data; | |
149 runningtotal += container->response->weight; | |
150 container->sum = runningtotal; | |
151 cur = cur->next; | |
152 } | |
153 | |
154 /* | |
155 * If the running total is greater than 0, pick a number between | |
156 * 1 and the runningtotal inclusive. (This is not precisely what | |
157 * the RFC algorithm describes, but we wish to deal with integers | |
158 * and avoid floats. This is functionally equivalent.) | |
159 * If running total is 0, then choose r = 0. | |
160 */ | |
161 r = runningtotal ? g_random_int_range(1, runningtotal + 1) : 0; | |
162 cur = list; | |
163 while (r > ((PurpleSrvResponseContainer *)cur->data)->sum) { | |
164 cur = cur->next; | |
165 } | |
166 | |
167 /* Set the return parameter and remove cur from the list */ | |
168 *container_ptr = cur->data; | |
169 return g_list_delete_link(list, cur); | |
170 } | |
171 | |
172 /** | |
173 * Reorder a GList of PurpleSrvResponses that have the same priority | |
174 * (aka "pref"). | |
175 */ | |
176 static void srv_reorder(GList *list, int num) | |
177 { | |
178 int i; | |
179 GList *cur; | |
180 GList *container_list = NULL; | |
181 PurpleSrvResponseContainer *container; | |
182 | |
183 if (num < 2) | |
184 /* Nothing to sort */ | |
185 return; | |
186 | |
187 /* First build a list of container structs */ | |
188 for (i = 0, cur = list; i < num; i++, cur = cur->next) { | |
189 container = g_new(PurpleSrvResponseContainer, 1); | |
190 container->response = cur->data; | |
191 container_list = g_list_prepend(container_list, container); | |
192 } | |
193 container_list = g_list_reverse(container_list); | |
194 | |
195 /* | |
196 * Re-order the list that was passed in as a parameter. We leave | |
197 * the list nodes in place, but replace their data pointers. | |
198 */ | |
199 cur = list; | |
200 while (container_list) { | |
201 container_list = select_random_response(container_list, &container); | |
202 cur->data = container->response; | |
203 g_free(container); | |
204 cur = cur->next; | |
205 } | |
206 } | |
207 | |
208 /** | |
209 * Sorts a GList of PurpleSrvResponse's according to the | |
210 * algorithm described in RFC 2782. | |
211 * | |
212 * @param response GList of PurpleSrvResponse's | |
213 * @param The original list, resorted | |
214 */ | |
215 static GList *purple_srv_sort(GList *list) | |
216 { | |
217 GList *cur, *start; | |
218 int count; | |
219 int pref; | |
220 | |
221 if (!list || !list->next) | |
222 /* Nothing to sort */ | |
223 return list; | |
224 | |
225 list = g_list_sort(list, responsecompare); | |
226 | |
227 start = cur = list; | |
228 count = 1; | |
229 while (cur) { | |
230 PurpleSrvResponse *next_response; | |
231 pref = ((PurpleSrvResponse *)cur->data)->pref; | |
232 next_response = cur->next ? cur->next->data : NULL; | |
233 if (!next_response || next_response->pref != pref) { | |
234 /* | |
235 * The 'count' records starting at 'start' all have the same | |
236 * priority. Sort them by weight. | |
237 */ | |
238 srv_reorder(start, count); | |
239 start = cur->next; | |
240 count = 0; | |
241 } | |
242 count++; | |
243 cur = cur->next; | |
244 } | |
245 | |
246 return list; | |
113 } | 247 } |
114 | 248 |
115 #ifndef _WIN32 | 249 #ifndef _WIN32 |
116 | 250 |
117 G_GNUC_NORETURN static void | 251 G_GNUC_NORETURN static void |
189 strcpy(srvres->hostname, name); | 323 strcpy(srvres->hostname, name); |
190 srvres->pref = pref; | 324 srvres->pref = pref; |
191 srvres->port = port; | 325 srvres->port = port; |
192 srvres->weight = weight; | 326 srvres->weight = weight; |
193 | 327 |
194 ret = g_list_insert_sorted(ret, srvres, responsecompare); | 328 ret = g_list_prepend(ret, srvres); |
195 } else if (query.type == T_TXT) { | 329 } else if (query.type == T_TXT) { |
196 txtres = g_new0(PurpleTxtResponse, 1); | 330 txtres = g_new0(PurpleTxtResponse, 1); |
197 txtres->content = g_strndup((gchar*)(++cp), dlen-1); | 331 txtres->content = g_strndup((gchar*)(++cp), dlen-1); |
198 ret = g_list_append(ret, txtres); | 332 ret = g_list_append(ret, txtres); |
199 cp += dlen - 1; | 333 cp += dlen - 1; |
202 } | 336 } |
203 } | 337 } |
204 | 338 |
205 end: | 339 end: |
206 size = g_list_length(ret); | 340 size = g_list_length(ret); |
341 | |
342 if (query.type == T_SRV) | |
343 ret = purple_srv_sort(ret); | |
344 | |
207 /* TODO: Check return value */ | 345 /* TODO: Check return value */ |
208 write(out, &(query.type), sizeof(query.type)); | 346 write(out, &(query.type), sizeof(query.type)); |
209 write(out, &size, sizeof(size)); | 347 write(out, &size, sizeof(size)); |
210 while (ret != NULL) | 348 while (ret != NULL) |
211 { | 349 { |
395 srvres->hostname[255] = '\0'; | 533 srvres->hostname[255] = '\0'; |
396 srvres->pref = srv_data->wPriority; | 534 srvres->pref = srv_data->wPriority; |
397 srvres->port = srv_data->wPort; | 535 srvres->port = srv_data->wPort; |
398 srvres->weight = srv_data->wWeight; | 536 srvres->weight = srv_data->wWeight; |
399 | 537 |
400 lst = g_slist_insert_sorted(lst, srvres, responsecompare); | 538 lst = g_slist_prepend(lst, srvres); |
401 } | 539 } |
402 | 540 |
403 MyDnsRecordListFree(dr, DnsFreeRecordList); | 541 MyDnsRecordListFree(dr, DnsFreeRecordList); |
404 query_data->results = lst; | 542 query_data->results = purple_srv_sort(lst); |
405 } else if (type == DNS_TYPE_TXT) { | 543 } else if (type == DNS_TYPE_TXT) { |
406 PDNS_RECORD dr_tmp; | 544 PDNS_RECORD dr_tmp; |
407 GSList *lst = NULL; | 545 GSList *lst = NULL; |
408 DNS_TXT_DATA *txt_data; | 546 DNS_TXT_DATA *txt_data; |
409 PurpleTxtResponse *txtres; | 547 PurpleTxtResponse *txtres; |