/* 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 . */ #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. Don’t 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]); }