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
52struct _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
66G_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 */
85GSrvTarget *
86g_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 (str: 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 */
111GSrvTarget *
112g_srv_target_copy (GSrvTarget *target)
113{
114 return g_srv_target_new (hostname: target->hostname, port: target->port,
115 priority: target->priority, weight: target->weight);
116}
117
118/**
119 * g_srv_target_free:
120 * @target: a #GSrvTarget
121 *
122 * Frees @target
123 *
124 * Since: 2.22
125 */
126void
127g_srv_target_free (GSrvTarget *target)
128{
129 g_free (mem: 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 */
146const gchar *
147g_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 */
162guint16
163g_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 */
180guint16
181g_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 */
198guint16
199g_srv_target_get_weight (GSrvTarget *target)
200{
201 return target->weight;
202}
203
204static gint
205compare_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 */
232GList *
233g_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 (s1: target->hostname, s2: "."))
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 (list: 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 (list: targets, compare_func: 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 (begin: 0, end: 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 (list: targets, llink: 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

source code of gtk/subprojects/glib/gio/gsrvtarget.c