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;