1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ 2 3 /* GIO - GLib Input, Output and Streaming Library 4 * 5 * Copyright (C) 2008 Red Hat, Inc. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General 18 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 #include "config.h" 22 #include <glib.h> 23 #include "glibintl.h" 24 25 #include "gsrvtarget.h" 26 27 #include <stdlib.h> 28 #include <string.h> 29 30 31 /** 32 * SECTION:gsrvtarget 33 * @short_description: DNS SRV record target 34 * @include: gio/gio.h 35 * 36 * SRV (service) records are used by some network protocols to provide 37 * service-specific aliasing and load-balancing. For example, XMPP 38 * (Jabber) uses SRV records to locate the XMPP server for a domain; 39 * rather than connecting directly to "example.com" or assuming a 40 * specific server hostname like "xmpp.example.com", an XMPP client 41 * would look up the "xmpp-client" SRV record for "example.com", and 42 * then connect to whatever host was pointed to by that record. 43 * 44 * You can use g_resolver_lookup_service() or 45 * g_resolver_lookup_service_async() to find the #GSrvTargets 46 * for a given service. However, if you are simply planning to connect 47 * to the remote service, you can use #GNetworkService's 48 * #GSocketConnectable interface and not need to worry about 49 * #GSrvTarget at all. 50 */ 51 52 struct _GSrvTarget { 53 gchar *hostname; 54 guint16 port; 55 56 guint16 priority; 57 guint16 weight; 58 }; 59 60 /** 61 * GSrvTarget: 62 * 63 * A single target host/port that a network service is running on. 64 */ 65 G_DEFINE_BOXED_TYPE(GSrvTarget,g_srv_target,g_srv_target_copy,g_srv_target_free)66 G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target, 67 g_srv_target_copy, g_srv_target_free) 68 69 /** 70 * g_srv_target_new: 71 * @hostname: the host that the service is running on 72 * @port: the port that the service is running on 73 * @priority: the target's priority 74 * @weight: the target's weight 75 * 76 * Creates a new #GSrvTarget with the given parameters. 77 * 78 * You should not need to use this; normally #GSrvTargets are 79 * created by #GResolver. 80 * 81 * Returns: a new #GSrvTarget. 82 * 83 * Since: 2.22 84 */ 85 GSrvTarget * 86 g_srv_target_new (const gchar *hostname, 87 guint16 port, 88 guint16 priority, 89 guint16 weight) 90 { 91 GSrvTarget *target = g_slice_new0 (GSrvTarget); 92 93 target->hostname = g_strdup (hostname); 94 target->port = port; 95 target->priority = priority; 96 target->weight = weight; 97 98 return target; 99 } 100 101 /** 102 * g_srv_target_copy: 103 * @target: a #GSrvTarget 104 * 105 * Copies @target 106 * 107 * Returns: a copy of @target 108 * 109 * Since: 2.22 110 */ 111 GSrvTarget * g_srv_target_copy(GSrvTarget * target)112 g_srv_target_copy (GSrvTarget *target) 113 { 114 return g_srv_target_new (target->hostname, target->port, 115 target->priority, target->weight); 116 } 117 118 /** 119 * g_srv_target_free: 120 * @target: a #GSrvTarget 121 * 122 * Frees @target 123 * 124 * Since: 2.22 125 */ 126 void g_srv_target_free(GSrvTarget * target)127 g_srv_target_free (GSrvTarget *target) 128 { 129 g_free (target->hostname); 130 g_slice_free (GSrvTarget, target); 131 } 132 133 /** 134 * g_srv_target_get_hostname: 135 * @target: a #GSrvTarget 136 * 137 * Gets @target's hostname (in ASCII form; if you are going to present 138 * this to the user, you should use g_hostname_is_ascii_encoded() to 139 * check if it contains encoded Unicode segments, and use 140 * g_hostname_to_unicode() to convert it if it does.) 141 * 142 * Returns: @target's hostname 143 * 144 * Since: 2.22 145 */ 146 const gchar * g_srv_target_get_hostname(GSrvTarget * target)147 g_srv_target_get_hostname (GSrvTarget *target) 148 { 149 return target->hostname; 150 } 151 152 /** 153 * g_srv_target_get_port: 154 * @target: a #GSrvTarget 155 * 156 * Gets @target's port 157 * 158 * Returns: @target's port 159 * 160 * Since: 2.22 161 */ 162 guint16 g_srv_target_get_port(GSrvTarget * target)163 g_srv_target_get_port (GSrvTarget *target) 164 { 165 return target->port; 166 } 167 168 /** 169 * g_srv_target_get_priority: 170 * @target: a #GSrvTarget 171 * 172 * Gets @target's priority. You should not need to look at this; 173 * #GResolver already sorts the targets according to the algorithm in 174 * RFC 2782. 175 * 176 * Returns: @target's priority 177 * 178 * Since: 2.22 179 */ 180 guint16 g_srv_target_get_priority(GSrvTarget * target)181 g_srv_target_get_priority (GSrvTarget *target) 182 { 183 return target->priority; 184 } 185 186 /** 187 * g_srv_target_get_weight: 188 * @target: a #GSrvTarget 189 * 190 * Gets @target's weight. You should not need to look at this; 191 * #GResolver already sorts the targets according to the algorithm in 192 * RFC 2782. 193 * 194 * Returns: @target's weight 195 * 196 * Since: 2.22 197 */ 198 guint16 g_srv_target_get_weight(GSrvTarget * target)199 g_srv_target_get_weight (GSrvTarget *target) 200 { 201 return target->weight; 202 } 203 204 static gint compare_target(gconstpointer a,gconstpointer b)205 compare_target (gconstpointer a, gconstpointer b) 206 { 207 GSrvTarget *ta = (GSrvTarget *)a; 208 GSrvTarget *tb = (GSrvTarget *)b; 209 210 if (ta->priority == tb->priority) 211 { 212 /* Arrange targets of the same priority "in any order, except 213 * that all those with weight 0 are placed at the beginning of 214 * the list" 215 */ 216 return ta->weight - tb->weight; 217 } 218 else 219 return ta->priority - tb->priority; 220 } 221 222 /** 223 * g_srv_target_list_sort: (skip) 224 * @targets: a #GList of #GSrvTarget 225 * 226 * Sorts @targets in place according to the algorithm in RFC 2782. 227 * 228 * Returns: (transfer full): the head of the sorted list. 229 * 230 * Since: 2.22 231 */ 232 GList * g_srv_target_list_sort(GList * targets)233 g_srv_target_list_sort (GList *targets) 234 { 235 gint sum, num, val, priority, weight; 236 GList *t, *out, *tail; 237 GSrvTarget *target; 238 239 if (!targets) 240 return NULL; 241 242 if (!targets->next) 243 { 244 target = targets->data; 245 if (!strcmp (target->hostname, ".")) 246 { 247 /* 'A Target of "." means that the service is decidedly not 248 * available at this domain.' 249 */ 250 g_srv_target_free (target); 251 g_list_free (targets); 252 return NULL; 253 } 254 } 255 256 /* Sort input list by priority, and put the 0-weight targets first 257 * in each priority group. Initialize output list to %NULL. 258 */ 259 targets = g_list_sort (targets, compare_target); 260 out = tail = NULL; 261 262 /* For each group of targets with the same priority, remove them 263 * from @targets and append them to @out in a valid order. 264 */ 265 while (targets) 266 { 267 priority = ((GSrvTarget *)targets->data)->priority; 268 269 /* Count the number of targets at this priority level, and 270 * compute the sum of their weights. 271 */ 272 sum = num = 0; 273 for (t = targets; t; t = t->next) 274 { 275 target = (GSrvTarget *)t->data; 276 if (target->priority != priority) 277 break; 278 sum += target->weight; 279 num++; 280 } 281 282 /* While there are still targets at this priority level... */ 283 while (num) 284 { 285 /* Randomly select from the targets at this priority level, 286 * giving precedence to the ones with higher weight, 287 * according to the rules from RFC 2782. 288 */ 289 val = g_random_int_range (0, sum + 1); 290 for (t = targets; ; t = t->next) 291 { 292 weight = ((GSrvTarget *)t->data)->weight; 293 if (weight >= val) 294 break; 295 val -= weight; 296 } 297 298 targets = g_list_remove_link (targets, t); 299 300 if (!out) 301 out = t; 302 else 303 tail->next = t; 304 tail = t; 305 306 sum -= weight; 307 num--; 308 } 309 } 310 311 return out; 312 } 313