glib/gio/gnetworkmonitorbase.c
2023-12-20 16:14:57 +00:00

587 lines
18 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2011 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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 "gnetworkmonitorbase.h"
#include "ginetaddress.h"
#include "ginetaddressmask.h"
#include "ginetsocketaddress.h"
#include "ginitable.h"
#include "gioerror.h"
#include "giomodule-priv.h"
#include "gnetworkmonitor.h"
#include "gsocketaddressenumerator.h"
#include "gsocketconnectable.h"
#include "gtask.h"
#include "glibintl.h"
static void g_network_monitor_base_iface_init (GNetworkMonitorInterface *iface);
static void g_network_monitor_base_initable_iface_init (GInitableIface *iface);
enum
{
PROP_0,
PROP_NETWORK_AVAILABLE,
PROP_NETWORK_METERED,
PROP_CONNECTIVITY
};
struct _GNetworkMonitorBasePrivate
{
GHashTable *networks /* (element-type GInetAddressMask) (owned) */;
gboolean have_ipv4_default_route;
gboolean have_ipv6_default_route;
gboolean is_available;
GMainContext *context;
GSource *network_changed_source;
gboolean initializing;
};
static guint network_changed_signal = 0;
static void queue_network_changed (GNetworkMonitorBase *monitor);
static guint inet_address_mask_hash (gconstpointer key);
static gboolean inet_address_mask_equal (gconstpointer a,
gconstpointer b);
G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorBase, g_network_monitor_base, G_TYPE_OBJECT,
G_ADD_PRIVATE (GNetworkMonitorBase)
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
g_network_monitor_base_initable_iface_init)
G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR,
g_network_monitor_base_iface_init)
_g_io_modules_ensure_extension_points_registered ();
g_io_extension_point_implement (G_NETWORK_MONITOR_EXTENSION_POINT_NAME,
g_define_type_id,
"base",
0))
static void
g_network_monitor_base_init (GNetworkMonitorBase *monitor)
{
monitor->priv = g_network_monitor_base_get_instance_private (monitor);
monitor->priv->networks = g_hash_table_new_full (inet_address_mask_hash,
inet_address_mask_equal,
g_object_unref, NULL);
monitor->priv->context = g_main_context_get_thread_default ();
if (monitor->priv->context)
g_main_context_ref (monitor->priv->context);
monitor->priv->initializing = TRUE;
}
static void
g_network_monitor_base_constructed (GObject *object)
{
GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
if (G_OBJECT_TYPE (monitor) == G_TYPE_NETWORK_MONITOR_BASE)
{
GInetAddressMask *mask;
/* We're the dumb base class, not a smarter subclass. So just
* assume that the network is available.
*/
mask = g_inet_address_mask_new_from_string ("0.0.0.0/0", NULL);
g_network_monitor_base_add_network (monitor, mask);
g_object_unref (mask);
mask = g_inet_address_mask_new_from_string ("::/0", NULL);
if (mask)
{
/* On some environments (for example Windows without IPv6 support
* enabled) the string "::/0" can't be processed and causes
* g_inet_address_mask_new_from_string to return NULL */
g_network_monitor_base_add_network (monitor, mask);
g_object_unref (mask);
}
}
}
static void
g_network_monitor_base_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
switch (prop_id)
{
case PROP_NETWORK_AVAILABLE:
g_value_set_boolean (value, monitor->priv->is_available);
break;
case PROP_NETWORK_METERED:
/* Default to FALSE in the unknown case. */
g_value_set_boolean (value, FALSE);
break;
case PROP_CONNECTIVITY:
g_value_set_enum (value,
monitor->priv->is_available ?
G_NETWORK_CONNECTIVITY_FULL :
G_NETWORK_CONNECTIVITY_LOCAL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
g_network_monitor_base_finalize (GObject *object)
{
GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
g_hash_table_unref (monitor->priv->networks);
if (monitor->priv->network_changed_source)
{
g_source_destroy (monitor->priv->network_changed_source);
g_source_unref (monitor->priv->network_changed_source);
}
if (monitor->priv->context)
g_main_context_unref (monitor->priv->context);
G_OBJECT_CLASS (g_network_monitor_base_parent_class)->finalize (object);
}
static void
g_network_monitor_base_class_init (GNetworkMonitorBaseClass *monitor_class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (monitor_class);
gobject_class->constructed = g_network_monitor_base_constructed;
gobject_class->get_property = g_network_monitor_base_get_property;
gobject_class->finalize = g_network_monitor_base_finalize;
g_object_class_override_property (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
}
static gboolean
g_network_monitor_base_can_reach_sockaddr (GNetworkMonitorBase *base,
GSocketAddress *sockaddr)
{
GInetAddress *iaddr;
GHashTableIter iter;
gpointer key;
if (!G_IS_INET_SOCKET_ADDRESS (sockaddr))
return FALSE;
iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sockaddr));
g_hash_table_iter_init (&iter, base->priv->networks);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GInetAddressMask *mask = key;
if (g_inet_address_mask_matches (mask, iaddr))
return TRUE;
}
return FALSE;
}
static gboolean
g_network_monitor_base_can_reach (GNetworkMonitor *monitor,
GSocketConnectable *connectable,
GCancellable *cancellable,
GError **error)
{
GNetworkMonitorBase *base = G_NETWORK_MONITOR_BASE (monitor);
GSocketAddressEnumerator *enumerator;
GSocketAddress *addr;
if (g_hash_table_size (base->priv->networks) == 0)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
_("Network unreachable"));
return FALSE;
}
enumerator = g_socket_connectable_proxy_enumerate (connectable);
addr = g_socket_address_enumerator_next (enumerator, cancellable, error);
if (!addr)
{
/* Either the user cancelled, or DNS resolution failed */
g_object_unref (enumerator);
return FALSE;
}
if (base->priv->have_ipv4_default_route &&
base->priv->have_ipv6_default_route)
{
g_object_unref (enumerator);
g_object_unref (addr);
return TRUE;
}
while (addr)
{
if (g_network_monitor_base_can_reach_sockaddr (base, addr))
{
g_object_unref (addr);
g_object_unref (enumerator);
return TRUE;
}
g_object_unref (addr);
addr = g_socket_address_enumerator_next (enumerator, cancellable, error);
}
g_object_unref (enumerator);
if (error && !*error)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
_("Host unreachable"));
}
return FALSE;
}
static void
can_reach_async_got_address (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GSocketAddressEnumerator *enumerator = G_SOCKET_ADDRESS_ENUMERATOR (object);
GTask *task = user_data;
GNetworkMonitorBase *base = g_task_get_source_object (task);
GSocketAddress *addr;
GError *error = NULL;
addr = g_socket_address_enumerator_next_finish (enumerator, result, &error);
if (!addr)
{
if (error)
{
/* Either the user cancelled, or DNS resolution failed */
g_task_return_error (task, error);
g_object_unref (task);
return;
}
else
{
/* Resolved all addresses, none matched */
g_task_return_new_error_literal (task, G_IO_ERROR,
G_IO_ERROR_HOST_UNREACHABLE,
_("Host unreachable"));
g_object_unref (task);
return;
}
}
if (g_network_monitor_base_can_reach_sockaddr (base, addr))
{
g_object_unref (addr);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
g_object_unref (addr);
g_socket_address_enumerator_next_async (enumerator,
g_task_get_cancellable (task),
can_reach_async_got_address, task);
}
static void
g_network_monitor_base_can_reach_async (GNetworkMonitor *monitor,
GSocketConnectable *connectable,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GSocketAddressEnumerator *enumerator;
task = g_task_new (monitor, cancellable, callback, user_data);
g_task_set_source_tag (task, g_network_monitor_base_can_reach_async);
if (g_hash_table_size (G_NETWORK_MONITOR_BASE (monitor)->priv->networks) == 0)
{
g_task_return_new_error_literal (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
_("Network unreachable"));
g_object_unref (task);
return;
}
enumerator = g_socket_connectable_proxy_enumerate (connectable);
g_socket_address_enumerator_next_async (enumerator, cancellable,
can_reach_async_got_address, task);
g_object_unref (enumerator);
}
static gboolean
g_network_monitor_base_can_reach_finish (GNetworkMonitor *monitor,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, monitor), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
g_network_monitor_base_iface_init (GNetworkMonitorInterface *monitor_iface)
{
monitor_iface->can_reach = g_network_monitor_base_can_reach;
monitor_iface->can_reach_async = g_network_monitor_base_can_reach_async;
monitor_iface->can_reach_finish = g_network_monitor_base_can_reach_finish;
network_changed_signal = g_signal_lookup ("network-changed", G_TYPE_NETWORK_MONITOR);
}
static gboolean
g_network_monitor_base_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
GNetworkMonitorBase *base = G_NETWORK_MONITOR_BASE (initable);
base->priv->initializing = FALSE;
return TRUE;
}
static void
g_network_monitor_base_initable_iface_init (GInitableIface *iface)
{
iface->init = g_network_monitor_base_initable_init;
}
static guint
inet_address_mask_hash (gconstpointer key)
{
GInetAddressMask *mask = G_INET_ADDRESS_MASK (key);
guint addr_hash;
guint mask_length = g_inet_address_mask_get_length (mask);
GInetAddress *addr = g_inet_address_mask_get_address (mask);
const guint8 *bytes = g_inet_address_to_bytes (addr);
gsize bytes_length = g_inet_address_get_native_size (addr);
union
{
const guint8 *bytes;
guint32 *hash32;
guint64 *hash64;
} integerifier;
/* If we can fit the entire address into the hash key, do it. Dont worry
* about endianness; the address should always be in network endianness. */
if (bytes_length == sizeof (guint32))
{
integerifier.bytes = bytes;
addr_hash = *integerifier.hash32;
}
else if (bytes_length == sizeof (guint64))
{
integerifier.bytes = bytes;
addr_hash = *integerifier.hash64;
}
else
{
gsize i;
/* Otherwise, fall back to adding the bytes together. We do this, rather
* than XORing them, as routes often have repeated tuples which would
* cancel out under XOR. */
addr_hash = 0;
for (i = 0; i < bytes_length; i++)
addr_hash += bytes[i];
}
return addr_hash + mask_length;;
}
static gboolean
inet_address_mask_equal (gconstpointer a,
gconstpointer b)
{
GInetAddressMask *mask_a = G_INET_ADDRESS_MASK (a);
GInetAddressMask *mask_b = G_INET_ADDRESS_MASK (b);
return g_inet_address_mask_equal (mask_a, mask_b);
}
static gboolean
emit_network_changed (gpointer user_data)
{
GNetworkMonitorBase *monitor = user_data;
gboolean is_available;
if (g_source_is_destroyed (g_main_current_source ()))
return FALSE;
g_object_ref (monitor);
is_available = (monitor->priv->have_ipv4_default_route ||
monitor->priv->have_ipv6_default_route);
if (monitor->priv->is_available != is_available)
{
monitor->priv->is_available = is_available;
g_object_notify (G_OBJECT (monitor), "network-available");
}
g_signal_emit (monitor, network_changed_signal, 0, is_available);
g_source_unref (monitor->priv->network_changed_source);
monitor->priv->network_changed_source = NULL;
g_object_unref (monitor);
return FALSE;
}
static void
queue_network_changed (GNetworkMonitorBase *monitor)
{
if (!monitor->priv->network_changed_source &&
!monitor->priv->initializing)
{
GSource *source;
source = g_idle_source_new ();
/* Use G_PRIORITY_HIGH_IDLE priority so that multiple
* network-change-related notifications coming in at
* G_PRIORITY_DEFAULT will get coalesced into one signal
* emission.
*/
g_source_set_priority (source, G_PRIORITY_HIGH_IDLE);
g_source_set_callback (source, emit_network_changed, monitor, NULL);
g_source_set_static_name (source, "[gio] emit_network_changed");
g_source_attach (source, monitor->priv->context);
monitor->priv->network_changed_source = source;
}
/* Normally we wait to update is_available until we emit the signal,
* to keep things consistent. But when we're first creating the
* object, we want it to be correct right away.
*/
if (monitor->priv->initializing)
{
monitor->priv->is_available = (monitor->priv->have_ipv4_default_route ||
monitor->priv->have_ipv6_default_route);
}
}
/**
* g_network_monitor_base_add_network:
* @monitor: the #GNetworkMonitorBase
* @network: (transfer none): a #GInetAddressMask
*
* Adds @network to @monitor's list of available networks.
*
* Since: 2.32
*/
void
g_network_monitor_base_add_network (GNetworkMonitorBase *monitor,
GInetAddressMask *network)
{
if (!g_hash_table_add (monitor->priv->networks, g_object_ref (network)))
return;
if (g_inet_address_mask_get_length (network) == 0)
{
switch (g_inet_address_mask_get_family (network))
{
case G_SOCKET_FAMILY_IPV4:
monitor->priv->have_ipv4_default_route = TRUE;
break;
case G_SOCKET_FAMILY_IPV6:
monitor->priv->have_ipv6_default_route = TRUE;
break;
default:
break;
}
}
/* Don't emit network-changed when multicast-link-local routing
* changes. This rather arbitrary decision is mostly because it
* seems to change quite often...
*/
if (g_inet_address_get_is_mc_link_local (g_inet_address_mask_get_address (network)))
return;
queue_network_changed (monitor);
}
/**
* g_network_monitor_base_remove_network:
* @monitor: the #GNetworkMonitorBase
* @network: a #GInetAddressMask
*
* Removes @network from @monitor's list of available networks.
*
* Since: 2.32
*/
void
g_network_monitor_base_remove_network (GNetworkMonitorBase *monitor,
GInetAddressMask *network)
{
if (!g_hash_table_remove (monitor->priv->networks, network))
return;
if (g_inet_address_mask_get_length (network) == 0)
{
switch (g_inet_address_mask_get_family (network))
{
case G_SOCKET_FAMILY_IPV4:
monitor->priv->have_ipv4_default_route = FALSE;
break;
case G_SOCKET_FAMILY_IPV6:
monitor->priv->have_ipv6_default_route = FALSE;
break;
default:
break;
}
}
queue_network_changed (monitor);
}
/**
* g_network_monitor_base_set_networks:
* @monitor: the #GNetworkMonitorBase
* @networks: (array length=length): an array of #GInetAddressMask
* @length: length of @networks
*
* Drops @monitor's current list of available networks and replaces
* it with @networks.
*/
void
g_network_monitor_base_set_networks (GNetworkMonitorBase *monitor,
GInetAddressMask **networks,
gint length)
{
int i;
g_hash_table_remove_all (monitor->priv->networks);
monitor->priv->have_ipv4_default_route = FALSE;
monitor->priv->have_ipv6_default_route = FALSE;
for (i = 0; i < length; i++)
g_network_monitor_base_add_network (monitor, networks[i]);
}