Blame gio/gsrvtarget.c

Packit ae235b
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
Packit ae235b
Packit ae235b
/* GIO - GLib Input, Output and Streaming Library
Packit ae235b
 *
Packit ae235b
 * Copyright (C) 2008 Red Hat, Inc.
Packit ae235b
 *
Packit ae235b
 * This library is free software; you can redistribute it and/or
Packit ae235b
 * modify it under the terms of the GNU Lesser General Public
Packit ae235b
 * License as published by the Free Software Foundation; either
Packit ae235b
 * version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 * This library is distributed in the hope that it will be useful,
Packit ae235b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 * Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 * You should have received a copy of the GNU Lesser General
Packit ae235b
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
#include <glib.h>
Packit ae235b
#include "glibintl.h"
Packit ae235b
Packit ae235b
#include "gsrvtarget.h"
Packit ae235b
Packit ae235b
#include <stdlib.h>
Packit ae235b
#include <string.h>
Packit ae235b
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * SECTION:gsrvtarget
Packit ae235b
 * @short_description: DNS SRV record target
Packit ae235b
 * @include: gio/gio.h
Packit ae235b
 *
Packit ae235b
 * SRV (service) records are used by some network protocols to provide
Packit ae235b
 * service-specific aliasing and load-balancing. For example, XMPP
Packit ae235b
 * (Jabber) uses SRV records to locate the XMPP server for a domain;
Packit ae235b
 * rather than connecting directly to "example.com" or assuming a
Packit ae235b
 * specific server hostname like "xmpp.example.com", an XMPP client
Packit ae235b
 * would look up the "xmpp-client" SRV record for "example.com", and
Packit ae235b
 * then connect to whatever host was pointed to by that record.
Packit ae235b
 *
Packit ae235b
 * You can use g_resolver_lookup_service() or
Packit ae235b
 * g_resolver_lookup_service_async() to find the #GSrvTargets
Packit ae235b
 * for a given service. However, if you are simply planning to connect
Packit ae235b
 * to the remote service, you can use #GNetworkService's
Packit ae235b
 * #GSocketConnectable interface and not need to worry about
Packit ae235b
 * #GSrvTarget at all.
Packit ae235b
 */
Packit ae235b
Packit ae235b
struct _GSrvTarget {
Packit ae235b
  gchar   *hostname;
Packit ae235b
  guint16  port;
Packit ae235b
Packit ae235b
  guint16  priority;
Packit ae235b
  guint16  weight;
Packit ae235b
};
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * GSrvTarget:
Packit ae235b
 *
Packit ae235b
 * A single target host/port that a network service is running on.
Packit ae235b
 */
Packit ae235b
Packit ae235b
G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target,
Packit ae235b
                     g_srv_target_copy, g_srv_target_free)
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_new:
Packit ae235b
 * @hostname: the host that the service is running on
Packit ae235b
 * @port: the port that the service is running on
Packit ae235b
 * @priority: the target's priority
Packit ae235b
 * @weight: the target's weight
Packit ae235b
 *
Packit ae235b
 * Creates a new #GSrvTarget with the given parameters.
Packit ae235b
 *
Packit ae235b
 * You should not need to use this; normally #GSrvTargets are
Packit ae235b
 * created by #GResolver.
Packit ae235b
 *
Packit ae235b
 * Returns: a new #GSrvTarget.
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
GSrvTarget *
Packit ae235b
g_srv_target_new (const gchar *hostname,
Packit ae235b
                  guint16      port,
Packit ae235b
                  guint16      priority,
Packit ae235b
                  guint16      weight)
Packit ae235b
{
Packit ae235b
  GSrvTarget *target = g_slice_new0 (GSrvTarget);
Packit ae235b
Packit ae235b
  target->hostname = g_strdup (hostname);
Packit ae235b
  target->port = port;
Packit ae235b
  target->priority = priority;
Packit ae235b
  target->weight = weight;
Packit ae235b
Packit ae235b
  return target;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_copy:
Packit ae235b
 * @target: a #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Copies @target
Packit ae235b
 *
Packit ae235b
 * Returns: a copy of @target
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
GSrvTarget *
Packit ae235b
g_srv_target_copy (GSrvTarget *target)
Packit ae235b
{
Packit ae235b
  return g_srv_target_new (target->hostname, target->port,
Packit ae235b
                           target->priority, target->weight);
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_free:
Packit ae235b
 * @target: a #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Frees @target
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
void
Packit ae235b
g_srv_target_free (GSrvTarget *target)
Packit ae235b
{
Packit ae235b
  g_free (target->hostname);
Packit ae235b
  g_slice_free (GSrvTarget, target);
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_get_hostname:
Packit ae235b
 * @target: a #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Gets @target's hostname (in ASCII form; if you are going to present
Packit ae235b
 * this to the user, you should use g_hostname_is_ascii_encoded() to
Packit ae235b
 * check if it contains encoded Unicode segments, and use
Packit ae235b
 * g_hostname_to_unicode() to convert it if it does.)
Packit ae235b
 *
Packit ae235b
 * Returns: @target's hostname
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
const gchar *
Packit ae235b
g_srv_target_get_hostname (GSrvTarget *target)
Packit ae235b
{
Packit ae235b
  return target->hostname;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_get_port:
Packit ae235b
 * @target: a #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Gets @target's port
Packit ae235b
 *
Packit ae235b
 * Returns: @target's port
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
guint16
Packit ae235b
g_srv_target_get_port (GSrvTarget *target)
Packit ae235b
{
Packit ae235b
  return target->port;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_get_priority:
Packit ae235b
 * @target: a #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Gets @target's priority. You should not need to look at this;
Packit ae235b
 * #GResolver already sorts the targets according to the algorithm in
Packit ae235b
 * RFC 2782.
Packit ae235b
 *
Packit ae235b
 * Returns: @target's priority
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
guint16
Packit ae235b
g_srv_target_get_priority (GSrvTarget *target)
Packit ae235b
{
Packit ae235b
  return target->priority;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_get_weight:
Packit ae235b
 * @target: a #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Gets @target's weight. You should not need to look at this;
Packit ae235b
 * #GResolver already sorts the targets according to the algorithm in
Packit ae235b
 * RFC 2782.
Packit ae235b
 *
Packit ae235b
 * Returns: @target's weight
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
guint16
Packit ae235b
g_srv_target_get_weight (GSrvTarget *target)
Packit ae235b
{
Packit ae235b
  return target->weight;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gint
Packit ae235b
compare_target (gconstpointer a, gconstpointer b)
Packit ae235b
{
Packit ae235b
  GSrvTarget *ta = (GSrvTarget *)a;
Packit ae235b
  GSrvTarget *tb = (GSrvTarget *)b;
Packit ae235b
Packit ae235b
  if (ta->priority == tb->priority)
Packit ae235b
    {
Packit ae235b
      /* Arrange targets of the same priority "in any order, except
Packit ae235b
       * that all those with weight 0 are placed at the beginning of
Packit ae235b
       * the list"
Packit ae235b
       */
Packit ae235b
      return ta->weight - tb->weight;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    return ta->priority - tb->priority;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_srv_target_list_sort: (skip)
Packit ae235b
 * @targets: a #GList of #GSrvTarget
Packit ae235b
 *
Packit ae235b
 * Sorts @targets in place according to the algorithm in RFC 2782.
Packit ae235b
 *
Packit ae235b
 * Returns: (transfer full): the head of the sorted list.
Packit ae235b
 *
Packit ae235b
 * Since: 2.22
Packit ae235b
 */
Packit ae235b
GList *
Packit ae235b
g_srv_target_list_sort (GList *targets)
Packit ae235b
{
Packit ae235b
  gint sum, num, val, priority, weight;
Packit ae235b
  GList *t, *out, *tail;
Packit ae235b
  GSrvTarget *target;
Packit ae235b
Packit ae235b
  if (!targets)
Packit ae235b
    return NULL;
Packit ae235b
Packit ae235b
  if (!targets->next)
Packit ae235b
    {
Packit ae235b
      target = targets->data;
Packit ae235b
      if (!strcmp (target->hostname, "."))
Packit ae235b
        {
Packit ae235b
          /* 'A Target of "." means that the service is decidedly not
Packit ae235b
           * available at this domain.'
Packit ae235b
           */
Packit ae235b
          g_srv_target_free (target);
Packit ae235b
          g_list_free (targets);
Packit ae235b
          return NULL;
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  /* Sort input list by priority, and put the 0-weight targets first
Packit ae235b
   * in each priority group. Initialize output list to %NULL.
Packit ae235b
   */
Packit ae235b
  targets = g_list_sort (targets, compare_target);
Packit ae235b
  out = tail = NULL;
Packit ae235b
Packit ae235b
  /* For each group of targets with the same priority, remove them
Packit ae235b
   * from @targets and append them to @out in a valid order.
Packit ae235b
   */
Packit ae235b
  while (targets)
Packit ae235b
    {
Packit ae235b
      priority = ((GSrvTarget *)targets->data)->priority;
Packit ae235b
Packit ae235b
      /* Count the number of targets at this priority level, and
Packit ae235b
       * compute the sum of their weights.
Packit ae235b
       */
Packit ae235b
      sum = num = 0;
Packit ae235b
      for (t = targets; t; t = t->next)
Packit ae235b
        {
Packit ae235b
          target = (GSrvTarget *)t->data;
Packit ae235b
          if (target->priority != priority)
Packit ae235b
            break;
Packit ae235b
          sum += target->weight;
Packit ae235b
          num++;
Packit ae235b
        }
Packit ae235b
Packit ae235b
      /* While there are still targets at this priority level... */
Packit ae235b
      while (num)
Packit ae235b
        {
Packit ae235b
          /* Randomly select from the targets at this priority level,
Packit ae235b
           * giving precedence to the ones with higher weight,
Packit ae235b
           * according to the rules from RFC 2782.
Packit ae235b
           */
Packit ae235b
          val = g_random_int_range (0, sum + 1);
Packit ae235b
          for (t = targets; ; t = t->next)
Packit ae235b
            {
Packit ae235b
              weight = ((GSrvTarget *)t->data)->weight;
Packit ae235b
              if (weight >= val)
Packit ae235b
                break;
Packit ae235b
              val -= weight;
Packit ae235b
            }
Packit ae235b
Packit ae235b
          targets = g_list_remove_link (targets, t);
Packit ae235b
Packit ae235b
          if (!out)
Packit ae235b
            out = t;
Packit ae235b
          else
Packit ae235b
            tail->next = t;
Packit ae235b
          tail = t;
Packit ae235b
Packit ae235b
          sum -= weight;
Packit ae235b
          num--;
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return out;
Packit ae235b
}