/* GIO - GLib Input, Output and Streaming Library * * Copyright 2011 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 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, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. */ #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 "glibintl.h" static void g_network_monitor_base_iface_init (GNetworkMonitorInterface *iface); static void g_network_monitor_base_initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorBase, g_network_monitor_base, G_TYPE_OBJECT, 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)) enum { PROP_0, PROP_NETWORK_AVAILABLE }; struct _GNetworkMonitorBasePrivate { GPtrArray *networks; 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 void g_network_monitor_base_init (GNetworkMonitorBase *monitor) { monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, G_TYPE_NETWORK_MONITOR_BASE, GNetworkMonitorBasePrivate); monitor->priv->networks = g_ptr_array_new_with_free_func (g_object_unref); monitor->priv->context = g_main_context_get_thread_default (); if (monitor->priv->context) g_main_context_ref (monitor->priv->context); monitor->priv->initializing = TRUE; queue_network_changed (monitor); } 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); 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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void g_network_monitor_base_finalize (GObject *object) { GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object); g_ptr_array_free (monitor->priv->networks, TRUE); 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); g_type_class_add_private (monitor_class, sizeof (GNetworkMonitorBasePrivate)); 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"); } static gboolean g_network_monitor_base_can_reach (GNetworkMonitor *monitor, GSocketConnectable *connectable, GCancellable *cancellable, GError **error) { GNetworkMonitorBasePrivate *priv = G_NETWORK_MONITOR_BASE (monitor)->priv; GSocketAddressEnumerator *enumerator; GSocketAddress *addr; if (priv->have_ipv4_default_route && priv->have_ipv6_default_route) return TRUE; if (priv->networks->len == 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; } while (addr) { if (G_IS_INET_SOCKET_ADDRESS (addr)) { GInetAddress *iaddr; int i; iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)); for (i = 0; i < priv->networks->len; i++) { if (g_inet_address_mask_matches (priv->networks->pdata[i], iaddr)) { 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 g_network_monitor_base_iface_init (GNetworkMonitorInterface *monitor_iface) { monitor_iface->can_reach = g_network_monitor_base_can_reach; 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) { return TRUE; } static void g_network_monitor_base_initable_iface_init (GInitableIface *iface) { iface->init = g_network_monitor_base_initable_init; } static gboolean emit_network_changed (gpointer user_data) { GNetworkMonitorBase *monitor = user_data; gboolean is_available; g_object_ref (monitor); if (monitor->priv->initializing) monitor->priv->initializing = FALSE; else { 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) { 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_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: a #GInetAddressMask * * Adds @network to @monitor's list of available networks. */ void g_network_monitor_base_add_network (GNetworkMonitorBase *monitor, GInetAddressMask *network) { int i; for (i = 0; i < monitor->priv->networks->len; i++) { if (g_inet_address_mask_equal (monitor->priv->networks->pdata[i], network)) return; } g_ptr_array_add (monitor->priv->networks, g_object_ref (network)); 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. */ void g_network_monitor_base_remove_network (GNetworkMonitorBase *monitor, GInetAddressMask *network) { int i; for (i = 0; i < monitor->priv->networks->len; i++) { if (g_inet_address_mask_equal (monitor->priv->networks->pdata[i], network)) { g_ptr_array_remove_index_fast (monitor->priv->networks, i); 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); return; } } } /** * 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_ptr_array_set_size (monitor->priv->networks, 0); 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]); }