/* -*- 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 . */ #include "config.h" #include #include "glibintl.h" #include "gsrvtarget.h" #include #include /** * 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; }