blob: 6b4068ba4111c6a23832e076145239c82f8561ea [file] [log] [blame]
/* -*- 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
* version 2.1 of the License, or (at your option) any later version.
*
* 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
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#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.
*
* You can use g_resolver_lookup_service() or
* g_resolver_lookup_service_async() to find the #GSrvTargets
* 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.
*/
struct _GSrvTarget {
gchar *hostname;
guint16 port;
guint16 priority;
guint16 weight;
};
/**
* GSrvTarget:
*
* A single target host/port that a network service is running on.
*/
G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target,
g_srv_target_copy, g_srv_target_free)
/**
* 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.
*
* You should not need to use this; normally #GSrvTargets are
* created by #GResolver.
*
* Returns: a new #GSrvTarget.
*
* Since: 2.22
*/
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
*
* Returns: a copy of @target
*
* Since: 2.22
*/
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
*/
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.)
*
* Returns: @target's hostname
*
* Since: 2.22
*/
const gchar *
g_srv_target_get_hostname (GSrvTarget *target)
{
return target->hostname;
}
/**
* g_srv_target_get_port:
* @target: a #GSrvTarget
*
* Gets @target's port
*
* Returns: @target's port
*
* Since: 2.22
*/
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.
*
* Returns: @target's priority
*
* Since: 2.22
*/
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.
*
* Returns: @target's weight
*
* Since: 2.22
*/
guint16
g_srv_target_get_weight (GSrvTarget *target)
{
return target->weight;
}
static gint
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"
*/
return ta->weight - tb->weight;
}
else
return ta->priority - tb->priority;
}
/**
* g_srv_target_list_sort: (skip)
* @targets: a #GList of #GSrvTarget
*
* Sorts @targets in place according to the algorithm in RFC 2782.
*
* Returns: (transfer full): the head of the sorted list.
*
* Since: 2.22
*/
GList *
g_srv_target_list_sort (GList *targets)
{
gint sum, num, val, priority, weight;
GList *t, *out, *tail;
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;
}
}
/* Sort input list by priority, and put the 0-weight targets first
* in each priority group. Initialize output list to %NULL.
*/
targets = g_list_sort (targets, compare_target);
out = tail = NULL;
/* For each group of targets with the same priority, remove them
* from @targets and append them to @out in a valid order.
*/
while (targets)
{
priority = ((GSrvTarget *)targets->data)->priority;
/* Count the number of targets at this priority level, and
* compute the sum of their weights.
*/
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)
{
/* 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)
{
weight = ((GSrvTarget *)t->data)->weight;
if (weight >= val)
break;
val -= weight;
}
targets = g_list_remove_link (targets, t);
if (!out)
out = t;
else
tail->next = t;
tail = t;
sum -= weight;
num--;
}
}
return out;
}