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