gsrvtarget.c 7.41 KB
Newer Older
1 2 3 4 5 6 7 8 9
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/* GIO - GLib Input, Output and Streaming Library
 *
 * Copyright (C) 2008 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * 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 13 14 15 16 17
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * 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 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
 */

#include "config.h"
#include <glib.h>
#include "glibintl.h"

#include "gsrvtarget.h"

#include <stdlib.h>
#include <string.h>


/**
 * SECTION:gsrvtarget
 * @short_description: DNS SRV record target
 * @include: gio/gio.h
 *
 * SRV (service) records are used by some network protocols to provide
 * service-specific aliasing and load-balancing. For example, XMPP
 * (Jabber) uses SRV records to locate the XMPP server for a domain;
 * rather than connecting directly to "example.com" or assuming a
 * specific server hostname like "xmpp.example.com", an XMPP client
 * would look up the "xmpp-client" SRV record for "example.com", and
 * then connect to whatever host was pointed to by that record.
 *
44
 * You can use g_resolver_lookup_service() or
45
 * g_resolver_lookup_service_async() to find the #GSrvTargets
46 47 48 49
 * for a given service. However, if you are simply planning to connect
 * to the remote service, you can use #GNetworkService's
 * #GSocketConnectable interface and not need to worry about
 * #GSrvTarget at all.
50
 */
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

struct _GSrvTarget {
  gchar   *hostname;
  guint16  port;

  guint16  priority;
  guint16  weight;
};

/**
 * GSrvTarget:
 *
 * A single target host/port that a network service is running on.
 */

66 67
G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target,
                     g_srv_target_copy, g_srv_target_free)
68 69 70 71 72 73 74 75 76 77

/**
 * g_srv_target_new:
 * @hostname: the host that the service is running on
 * @port: the port that the service is running on
 * @priority: the target's priority
 * @weight: the target's weight
 *
 * Creates a new #GSrvTarget with the given parameters.
 *
78
 * You should not need to use this; normally #GSrvTargets are
79 80
 * created by #GResolver.
 *
81
 * Returns: a new #GSrvTarget.
82 83
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
84
 */
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
GSrvTarget *
g_srv_target_new (const gchar *hostname,
                  guint16      port,
                  guint16      priority,
                  guint16      weight)
{
  GSrvTarget *target = g_slice_new0 (GSrvTarget);

  target->hostname = g_strdup (hostname);
  target->port = port;
  target->priority = priority;
  target->weight = weight;

  return target;
}

/**
 * g_srv_target_copy:
 * @target: a #GSrvTarget
 *
 * Copies @target
 *
107
 * Returns: a copy of @target
108 109
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
110
 */
111 112 113 114 115 116 117 118 119 120 121 122 123 124
GSrvTarget *
g_srv_target_copy (GSrvTarget *target)
{
  return g_srv_target_new (target->hostname, target->port,
                           target->priority, target->weight);
}

/**
 * g_srv_target_free:
 * @target: a #GSrvTarget
 *
 * Frees @target
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
125
 */
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
void
g_srv_target_free (GSrvTarget *target)
{
  g_free (target->hostname);
  g_slice_free (GSrvTarget, target);
}

/**
 * g_srv_target_get_hostname:
 * @target: a #GSrvTarget
 *
 * Gets @target's hostname (in ASCII form; if you are going to present
 * this to the user, you should use g_hostname_is_ascii_encoded() to
 * check if it contains encoded Unicode segments, and use
 * g_hostname_to_unicode() to convert it if it does.)
 *
142
 * Returns: @target's hostname
143 144
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
145
 */
146 147 148 149 150 151 152 153 154 155 156 157
const gchar *
g_srv_target_get_hostname (GSrvTarget *target)
{
  return target->hostname;
}

/**
 * g_srv_target_get_port:
 * @target: a #GSrvTarget
 *
 * Gets @target's port
 *
158
 * Returns: @target's port
159 160
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
161
 */
162 163 164 165 166 167 168 169 170 171 172 173 174 175
guint16
g_srv_target_get_port (GSrvTarget *target)
{
  return target->port;
}

/**
 * g_srv_target_get_priority:
 * @target: a #GSrvTarget
 *
 * Gets @target's priority. You should not need to look at this;
 * #GResolver already sorts the targets according to the algorithm in
 * RFC 2782.
 *
176
 * Returns: @target's priority
177 178
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
179
 */
180 181 182 183 184 185 186 187 188 189 190 191 192 193
guint16
g_srv_target_get_priority (GSrvTarget *target)
{
  return target->priority;
}

/**
 * g_srv_target_get_weight:
 * @target: a #GSrvTarget
 *
 * Gets @target's weight. You should not need to look at this;
 * #GResolver already sorts the targets according to the algorithm in
 * RFC 2782.
 *
194
 * Returns: @target's weight
195 196
 *
 * Since: 2.22
Matthias Clasen's avatar
Matthias Clasen committed
197
 */
198 199 200 201 202 203
guint16
g_srv_target_get_weight (GSrvTarget *target)
{
  return target->weight;
}

Allison Karlitskaya's avatar
Allison Karlitskaya committed
204
static gint
205 206 207 208 209 210 211 212 213 214 215
compare_target (gconstpointer a, gconstpointer b)
{
  GSrvTarget *ta = (GSrvTarget *)a;
  GSrvTarget *tb = (GSrvTarget *)b;

  if (ta->priority == tb->priority)
    {
      /* Arrange targets of the same priority "in any order, except
       * that all those with weight 0 are placed at the beginning of
       * the list"
       */
216
      return ta->weight - tb->weight;
217 218 219 220 221 222
    }
  else
    return ta->priority - tb->priority;
}

/**
223
 * g_srv_target_list_sort: (skip)
224 225 226 227
 * @targets: a #GList of #GSrvTarget
 *
 * Sorts @targets in place according to the algorithm in RFC 2782.
 *
228
 * Returns: (transfer full): the head of the sorted list.
229 230
 *
 * Since: 2.22
231
 */
232 233 234
GList *
g_srv_target_list_sort (GList *targets)
{
235 236
  gint sum, num, val, priority, weight;
  GList *t, *out, *tail;
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
  GSrvTarget *target;

  if (!targets)
    return NULL;

  if (!targets->next)
    {
      target = targets->data;
      if (!strcmp (target->hostname, "."))
        {
          /* 'A Target of "." means that the service is decidedly not
           * available at this domain.'
           */
          g_srv_target_free (target);
          g_list_free (targets);
          return NULL;
        }
    }

256 257 258
  /* Sort input list by priority, and put the 0-weight targets first
   * in each priority group. Initialize output list to %NULL.
   */
259
  targets = g_list_sort (targets, compare_target);
260
  out = tail = NULL;
261

262 263
  /* For each group of targets with the same priority, remove them
   * from @targets and append them to @out in a valid order.
264
   */
265
  while (targets)
266
    {
267 268 269 270
      priority = ((GSrvTarget *)targets->data)->priority;

      /* Count the number of targets at this priority level, and
       * compute the sum of their weights.
271
       */
272 273 274 275 276 277 278 279 280 281 282 283
      sum = num = 0;
      for (t = targets; t; t = t->next)
        {
          target = (GSrvTarget *)t->data;
          if (target->priority != priority)
            break;
          sum += target->weight;
          num++;
        }

      /* While there are still targets at this priority level... */
      while (num)
284
        {
285 286 287 288 289 290
          /* Randomly select from the targets at this priority level,
           * giving precedence to the ones with higher weight,
           * according to the rules from RFC 2782.
           */
          val = g_random_int_range (0, sum + 1);
          for (t = targets; ; t = t->next)
291
            {
292 293
              weight = ((GSrvTarget *)t->data)->weight;
              if (weight >= val)
294 295 296 297
                break;
              val -= weight;
            }

298 299 300 301 302 303 304
          targets = g_list_remove_link (targets, t);

          if (!out)
            out = t;
          else
            tail->next = t;
          tail = t;
305 306

          sum -= weight;
307
          num--;
308 309 310
        }
    }

311
  return out;
312
}