From: Dan Winship Date: Sun, 12 Jun 2011 19:59:36 +0000 (-0400) Subject: add GNetworkMonitor, for... monitoring the network X-Git-Tag: 2.31.2~125 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=fe5ba0f291c11a22fbfe812d1c8315837fa14177;p=platform%2Fupstream%2Fglib.git add GNetworkMonitor, for... monitoring the network Add GNetworkMonitor and its associated extension point, provide a base implementation that always claims the network is available, and a netlink-based implementation built on top of that that actually tracks the network state. https://bugzilla.gnome.org/show_bug.cgi?id=620932 --- diff --git a/configure.ac b/configure.ac index eeb74e8..82f5499 100644 --- a/configure.ac +++ b/configure.ac @@ -1109,6 +1109,10 @@ if test $glib_native_win32 = no; then fi AC_SUBST(NETWORK_LIBS) +AC_CHECK_HEADER([linux/netlink.h], + [AC_DEFINE(HAVE_NETLINK, 1, [We have AF_NETLINK sockets])]) +AM_CONDITIONAL(HAVE_NETLINK, [test "$ac_cv_header_linux_netlink_h" = "yes"]) + case $host in *-*-solaris* ) AC_DEFINE(_XOPEN_SOURCE_EXTENDED, 1, Needed to get declarations for msg_control and msg_controllen on Solaris) diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml index fd408aa..00ed715 100644 --- a/docs/reference/gio/gio-docs.xml +++ b/docs/reference/gio/gio-docs.xml @@ -104,7 +104,7 @@ - Lowlevel network support + Low-level network support @@ -120,7 +120,7 @@ - Highlevel network functionallity + High-level network functionallity @@ -129,6 +129,7 @@ + TLS (SSL) support diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 33287c0..72b1ffb 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -3460,3 +3460,23 @@ G_DBUS_OBJECT_MANAGER_SERVER_GET_CLASS GDBusObjectManagerServerPrivate + +
+gnetworkmonitor +GNetworkMonitor +GNetworkMonitor +GNetworkMonitorInterface +G_NETWORK_MONITOR_EXTENSION_POINT_NAME +g_network_monitor_get_default +g_network_monitor_get_network_available +g_network_monitor_can_reach +g_network_monitor_can_reach_async +g_network_monitor_can_reach_finish + +g_network_monitor_get_type +G_TYPE_NETWORK_MONITOR +G_NETWORK_MONITOR +G_IS_NETWORK_MONITOR +G_NETWORK_MONITOR_GET_INTERFACE +
+ diff --git a/gio/Makefile.am b/gio/Makefile.am index 249cc7b..7e819a1 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -232,6 +232,13 @@ giounixinclude_HEADERS = \ gunixoutputstream.h \ gunixsocketaddress.h \ $(NULL) + +if HAVE_NETLINK +unix_sources += \ + gnetworkmonitornetlink.c \ + gnetworkmonitornetlink.h \ + $(NULL) +endif endif win32_actual_sources = \ @@ -332,6 +339,9 @@ libgio_2_0_la_SOURCES = \ gnativevolumemonitor.h \ gnetworkaddress.c \ gnetworkingprivate.h \ + gnetworkmonitor.c \ + gnetworkmonitorbase.c \ + gnetworkmonitorbase.h \ gnetworkservice.c \ goutputstream.c \ gpermission.c \ @@ -498,6 +508,7 @@ gio_headers = \ gmountoperation.h \ gnativevolumemonitor.h \ gnetworkaddress.h \ + gnetworkmonitor.h \ gnetworkservice.h \ goutputstream.h \ gpermission.h \ diff --git a/gio/gio.h b/gio/gio.h index ab65b1b..042c938 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -92,6 +92,7 @@ #include #include #include +#include #include #include #include diff --git a/gio/gio.symbols b/gio/gio.symbols index 9ac949f..d861395 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -1564,3 +1564,13 @@ g_dbus_object_manager_server_set_connection g_dbus_object_manager_server_get_type g_dbus_object_manager_server_new g_dbus_object_manager_server_unexport +g_network_monitor_can_reach +g_network_monitor_can_reach_async +g_network_monitor_can_reach_finish +g_network_monitor_get_default +g_network_monitor_get_network_available +g_network_monitor_get_type +g_network_monitor_base_add_network +g_network_monitor_base_get_type +g_network_monitor_base_remove_network +g_network_monitor_base_set_networks diff --git a/gio/giomodule.c b/gio/giomodule.c index eac317a..efd8388 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -771,6 +771,10 @@ extern GType _g_winhttp_vfs_get_type (void); extern GType _g_dummy_proxy_resolver_get_type (void); extern GType _g_dummy_tls_backend_get_type (void); +extern GType g_network_monitor_base_get_type (void); +#ifdef HAVE_NETLINK +extern GType _g_network_monitor_netlink_get_type (void); +#endif #ifdef G_PLATFORM_WIN32 @@ -850,6 +854,9 @@ _g_io_modules_ensure_extension_points_registered (void) ep = g_io_extension_point_register (G_TLS_BACKEND_EXTENSION_POINT_NAME); g_io_extension_point_set_required_type (ep, G_TYPE_TLS_BACKEND); + + ep = g_io_extension_point_register (G_NETWORK_MONITOR_EXTENSION_POINT_NAME); + g_io_extension_point_set_required_type (ep, G_TYPE_NETWORK_MONITOR); } G_UNLOCK (registered_extensions); @@ -921,6 +928,10 @@ _g_io_modules_ensure_loaded (void) _g_socks4_proxy_get_type (); _g_socks5_proxy_get_type (); _g_dummy_tls_backend_get_type (); + g_network_monitor_base_get_type (); +#ifdef HAVE_NETLINK + _g_network_monitor_netlink_get_type (); +#endif } G_UNLOCK (loaded_dirs); diff --git a/gio/giotypes.h b/gio/giotypes.h index 6081322..4077dbb 100644 --- a/gio/giotypes.h +++ b/gio/giotypes.h @@ -123,6 +123,7 @@ typedef struct _GMemoryOutputStream GMemoryOutputStream; typedef struct _GMount GMount; /* Dummy typedef */ typedef struct _GMountOperation GMountOperation; typedef struct _GNetworkAddress GNetworkAddress; +typedef struct _GNetworkMonitor GNetworkMonitor; typedef struct _GNetworkService GNetworkService; typedef struct _GOutputStream GOutputStream; typedef struct _GIOStream GIOStream; diff --git a/gio/gnetworkmonitor.c b/gio/gnetworkmonitor.c new file mode 100644 index 0000000..33e9704 --- /dev/null +++ b/gio/gnetworkmonitor.c @@ -0,0 +1,261 @@ +/* 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 "glib.h" +#include "glibintl.h" + +#include "gnetworkmonitor.h" +#include "ginetaddress.h" +#include "ginetsocketaddress.h" +#include "ginitable.h" +#include "gioenumtypes.h" +#include "giomodule-priv.h" +#include "gsimpleasyncresult.h" + +/** + * SECTION:gnetworkmonitor + * @title: GNetworkMonitor + * @short_description: Network status monitor + * @include: gio/gio.h + */ + +/** + * GNetworkMonitor: + * + * #GNetworkMonitor monitors the status of network connections and + * indicates when a possibly-user-visible change has occurred. + * + * Since: 2.32 + */ + +G_DEFINE_INTERFACE_WITH_CODE (GNetworkMonitor, g_network_monitor, G_TYPE_OBJECT, + g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_INITABLE);) + + +enum { + NETWORK_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/** + * g_network_monitor_get_default: + * + * Gets the default #GNetworkMonitor for the system. + * + * Returns: (transfer none): a #GNetworkMonitor + * + * Since: 2.32 + */ +GNetworkMonitor * +g_network_monitor_get_default (void) +{ + return _g_io_module_get_default (G_NETWORK_MONITOR_EXTENSION_POINT_NAME, + "GIO_USE_NETWORK_MONITOR", + NULL); +} + +/** + * g_network_monitor_get_network_available: + * @monitor: the #GNetworkMonitor + * + * Checks if the network is available. "Available" here means that the + * system has a default route available for at least one of IPv4 or + * IPv6. It does not necessarily imply that the public Internet is + * reachable. See #GNetworkMonitor:network-available for more details. + * + * Return value: whether the network is available + * + * Since: 2.32 + */ +gboolean +g_network_monitor_get_network_available (GNetworkMonitor *monitor) +{ + gboolean available = FALSE; + + g_object_get (G_OBJECT (monitor), "network-available", &available, NULL); + return available; +} + +/** + * g_network_monitor_can_reach: + * @monitor: a #GNetworkMonitor + * @connectable: a #GSocketConnectable + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Attempts to determine whether or not the host pointed to by + * @connectable can be reached, without actually trying to connect to + * it. + * + * This may return %TRUE even when #GNetworkMonitor:network-available + * is %FALSE, if, for example, @monitor can determine that + * @connectable refers to a host on a local network. + * + * If @monitor believes that an attempt to connect to @connectable + * will succeed, it will return %TRUE. Otherwise, it will return + * %FALSE and set @error to an appropriate error (such as + * %G_IO_ERROR_HOST_UNREACHABLE). + * + * Note that although this does not attempt to connect to + * @connectable, it may still block for a brief period of time (eg, + * trying to do multicast DNS on the local network), so if you do not + * want to block, you should use g_network_monitor_can_reach_async(). + * + * Return value: %TRUE if @connectable is reachable, %FALSE if not. + * + * Since: 2.32 + */ +gboolean +g_network_monitor_can_reach (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GError **error) +{ + GNetworkMonitorInterface *iface; + + iface = G_NETWORK_MONITOR_GET_INTERFACE (monitor); + return iface->can_reach (monitor, connectable, cancellable, error); +} + +static void +g_network_monitor_real_can_reach_async (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + GError *error = NULL; + + simple = g_simple_async_result_new (G_OBJECT (monitor), + callback, user_data, + g_network_monitor_real_can_reach_async); + if (g_network_monitor_can_reach (monitor, connectable, cancellable, &error)) + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + else + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +void +g_network_monitor_can_reach_async (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GNetworkMonitorInterface *iface; + + iface = G_NETWORK_MONITOR_GET_INTERFACE (monitor); + iface->can_reach_async (monitor, connectable, cancellable, callback, user_data); +} + +static gboolean +g_network_monitor_real_can_reach_finish (GNetworkMonitor *monitor, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (monitor), g_network_monitor_real_can_reach_async), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + else + return g_simple_async_result_get_op_res_gboolean (simple); +} + +gboolean +g_network_monitor_can_reach_finish (GNetworkMonitor *monitor, + GAsyncResult *result, + GError **error) +{ + GNetworkMonitorInterface *iface; + + iface = G_NETWORK_MONITOR_GET_INTERFACE (monitor); + return iface->can_reach_finish (monitor, result, error); +} + +static void +g_network_monitor_default_init (GNetworkMonitorInterface *iface) +{ + iface->can_reach_async = g_network_monitor_real_can_reach_async; + iface->can_reach_finish = g_network_monitor_real_can_reach_finish; + + /** + * GNetworkMonitor::network-changed: + * @monitor: a #GNetworkMonitor + * @available: the current value of #GNetworkMonitor:network-available + * + * Emitted when the network configuration changes. If @available is + * %TRUE, then some hosts may be reachable that were not reachable + * before, while others that were reachable before may no longer be + * reachable. If @available is %FALSE, then no remote hosts are + * reachable. + * + * Since: 2.32 + */ + signals[NETWORK_CHANGED] = + g_signal_new (I_("network-changed"), + G_TYPE_NETWORK_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GNetworkMonitorInterface, network_changed), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, + G_TYPE_BOOLEAN); + + /** + * GNetworkMonitor:network-available: + * + * Whether the network is considered available. That is, whether the + * system has a default route for at least one of IPv4 or IPv6. + * + * Real-world networks are of course much more complicated than + * this; the machine may be connected to a wifi hotspot that + * requires payment before allowing traffic through, or may be + * connected to a functioning router that has lost its own upstream + * connectivity. Some hosts might only be accessible when a VPN is + * active. Other hosts might only be accessible when the VPN is + * not active. Thus, it is best to use + * g_network_monitor_can_reach() or + * g_network_monitor_can_reach_async() to test for reachability on a + * host-by-host basis. (On the other hand, when the property is + * %FALSE, the application can reasonably expect that no remote + * hosts at all are reachable, and should indicate this to the user + * in its UI.) + * + * See also #GNetworkMonitor::network-changed. + * + * Since: 2.32 + */ + g_object_interface_install_property (iface, + g_param_spec_boolean ("network-available", + P_("Network available"), + P_("Whether the network is available"), + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); +} diff --git a/gio/gnetworkmonitor.h b/gio/gnetworkmonitor.h new file mode 100644 index 0000000..e525293 --- /dev/null +++ b/gio/gnetworkmonitor.h @@ -0,0 +1,89 @@ +/* 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. + */ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __G_NETWORK_MONITOR_H__ +#define __G_NETWORK_MONITOR_H__ + +#include + +G_BEGIN_DECLS + +/** + * G_NETWORK_MONITOR_EXTENSION_POINT_NAME: + * + * Extension point for network status monitoring functionality. + * See Extending GIO. + * + * Since: 2.30 + */ +#define G_NETWORK_MONITOR_EXTENSION_POINT_NAME "gio-network-monitor" + +#define G_TYPE_NETWORK_MONITOR (g_network_monitor_get_type ()) +#define G_NETWORK_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_NETWORK_MONITOR, GNetworkMonitor)) +#define G_IS_NETWORK_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_NETWORK_MONITOR)) +#define G_NETWORK_MONITOR_GET_INTERFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_NETWORK_MONITOR, GNetworkMonitorInterface)) + +typedef struct _GNetworkMonitorInterface GNetworkMonitorInterface; + +struct _GNetworkMonitorInterface { + GTypeInterface g_iface; + + void (*network_changed) (GNetworkMonitor *monitor, + gboolean available); + + gboolean (*can_reach) (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GError **error); + void (*can_reach_async) (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*can_reach_finish) (GNetworkMonitor *monitor, + GAsyncResult *result, + GError **error); +}; + +GType g_network_monitor_get_type (void) G_GNUC_CONST; +GNetworkMonitor *g_network_monitor_get_default (void); + +gboolean g_network_monitor_get_network_available (GNetworkMonitor *monitor); + +gboolean g_network_monitor_can_reach (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GError **error); +void g_network_monitor_can_reach_async (GNetworkMonitor *monitor, + GSocketConnectable *connectable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_network_monitor_can_reach_finish (GNetworkMonitor *monitor, + GAsyncResult *result, + GError **error); + +G_END_DECLS + +#endif /* __G_NETWORK_MONITOR_H__ */ diff --git a/gio/gnetworkmonitorbase.c b/gio/gnetworkmonitorbase.c new file mode 100644 index 0000000..2ac4115 --- /dev/null +++ b/gio/gnetworkmonitorbase.c @@ -0,0 +1,412 @@ +/* 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]); +} diff --git a/gio/gnetworkmonitorbase.h b/gio/gnetworkmonitorbase.h new file mode 100644 index 0000000..47ef336 --- /dev/null +++ b/gio/gnetworkmonitorbase.h @@ -0,0 +1,66 @@ +/* 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. + */ + +#ifndef __G_NETWORK_MONITOR_BASE_H__ +#define __G_NETWORK_MONITOR_BASE_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_NETWORK_MONITOR_BASE (g_network_monitor_base_get_type ()) +#define G_NETWORK_MONITOR_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_NETWORK_MONITOR_BASE, GNetworkMonitorBase)) +#define G_NETWORK_MONITOR_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_NETWORK_MONITOR_BASE, GNetworkMonitorBaseClass)) +#define G_IS_NETWORK_MONITOR_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_NETWORK_MONITOR_BASE)) +#define G_IS_NETWORK_MONITOR_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_NETWORK_MONITOR_BASE)) +#define G_NETWORK_MONITOR_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_NETWORK_MONITOR_BASE, GNetworkMonitorBaseClass)) + +typedef struct _GNetworkMonitorBase GNetworkMonitorBase; +typedef struct _GNetworkMonitorBaseClass GNetworkMonitorBaseClass; +typedef struct _GNetworkMonitorBasePrivate GNetworkMonitorBasePrivate; + +struct _GNetworkMonitorBase { + GObject parent_instance; + + GNetworkMonitorBasePrivate *priv; +}; + +struct _GNetworkMonitorBaseClass { + GObjectClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + gpointer padding[8]; +}; + +GType g_network_monitor_base_get_type (void); + +/*< protected >*/ +void g_network_monitor_base_add_network (GNetworkMonitorBase *monitor, + GInetAddressMask *network); +void g_network_monitor_base_remove_network (GNetworkMonitorBase *monitor, + GInetAddressMask *network); +void g_network_monitor_base_set_networks (GNetworkMonitorBase *monitor, + GInetAddressMask **networks, + gint length); + +G_END_DECLS + +#endif /* __G_NETWORK_MONITOR_BASE_H__ */ diff --git a/gio/gnetworkmonitornetlink.c b/gio/gnetworkmonitornetlink.c new file mode 100644 index 0000000..da60b8a --- /dev/null +++ b/gio/gnetworkmonitornetlink.c @@ -0,0 +1,465 @@ +/* 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 +#include +#include +#include + +#include "gnetworkmonitornetlink.h" +#include "gcredentials.h" +#include "ginetaddressmask.h" +#include "ginitable.h" +#include "giomodule-priv.h" +#include "glibintl.h" +#include "gnetworkingprivate.h" +#include "gnetworkmonitor.h" +#include "gsocket.h" +#include "gunixcredentialsmessage.h" + +static void g_network_monitor_netlink_iface_init (GNetworkMonitorInterface *iface); +static void g_network_monitor_netlink_initable_iface_init (GInitableIface *iface); + +#define g_network_monitor_netlink_get_type _g_network_monitor_netlink_get_type +G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorNetlink, g_network_monitor_netlink, G_TYPE_NETWORK_MONITOR_BASE, + G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR, + g_network_monitor_netlink_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_network_monitor_netlink_initable_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_NETWORK_MONITOR_EXTENSION_POINT_NAME, + g_define_type_id, + "netlink", + 20)) + +struct _GNetworkMonitorNetlinkPrivate +{ + GSocket *sock; + GSource *source, *dump_source; + + GPtrArray *dump_networks; +}; + +static gboolean read_netlink_messages (GSocket *socket, + GIOCondition condition, + gpointer user_data); +static gboolean request_dump (GNetworkMonitorNetlink *nl, + GError **error); + +static void +g_network_monitor_netlink_init (GNetworkMonitorNetlink *nl) +{ + nl->priv = G_TYPE_INSTANCE_GET_PRIVATE (nl, + G_TYPE_NETWORK_MONITOR_NETLINK, + GNetworkMonitorNetlinkPrivate); +} + + +static gboolean +g_network_monitor_netlink_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GNetworkMonitorNetlink *nl = G_NETWORK_MONITOR_NETLINK (initable); + gint sockfd, val; + struct sockaddr_nl snl; + + /* We create the socket the old-school way because sockaddr_netlink + * can't be represented as a GSocketAddress + */ + sockfd = socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sockfd == -1) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + _("Could not create netlink socket: %s"), + g_strerror (errno)); + return FALSE; + } + + snl.nl_family = AF_NETLINK; + snl.nl_pid = snl.nl_pad = 0; + snl.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE; + if (bind (sockfd, (struct sockaddr *)&snl, sizeof (snl)) != 0) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + _("Could not bind netlink socket: %s"), + g_strerror (errno)); + close (sockfd); + return FALSE; + } + + val = 1; + if (setsockopt (sockfd, SOL_SOCKET, SO_PASSCRED, &val, sizeof (val)) != 0) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + _("Could not set options on netlink socket: %s"), + g_strerror (errno)); + close (sockfd); + return FALSE; + } + + nl->priv->sock = g_socket_new_from_fd (sockfd, error); + if (error) + { + g_prefix_error (error, "%s", _("Could not wrap netlink socket: ")); + close (sockfd); + return FALSE; + } + + /* Request the current state */ + if (!request_dump (nl, error)) + return FALSE; + + /* And read responses; since we haven't yet marked the socket + * non-blocking, each call will block until a message is received. + */ + while (nl->priv->dump_networks) + { + if (!read_netlink_messages (NULL, G_IO_IN, nl)) + break; + } + + g_socket_set_blocking (nl->priv->sock, FALSE); + nl->priv->source = g_socket_create_source (nl->priv->sock, G_IO_IN, NULL); + g_source_set_callback (nl->priv->source, + (GSourceFunc) read_netlink_messages, nl, NULL); + g_source_attach (nl->priv->source, + g_main_context_get_thread_default ()); + + return TRUE; +} + +static gboolean +request_dump (GNetworkMonitorNetlink *nl, + GError **error) +{ + struct nlmsghdr *n; + struct rtgenmsg *gen; + gchar buf[NLMSG_SPACE (sizeof (*gen))]; + + memset (buf, 0, sizeof (buf)); + n = (struct nlmsghdr*) buf; + n->nlmsg_len = NLMSG_LENGTH (sizeof (*gen)); + n->nlmsg_type = RTM_GETROUTE; + n->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + n->nlmsg_pid = 0; + gen = NLMSG_DATA (n); + gen->rtgen_family = AF_UNSPEC; + + if (g_socket_send (nl->priv->sock, buf, sizeof (buf), + NULL, error) < 0) + { + g_prefix_error (error, "%s", _("Could not send netlink request: ")); + return FALSE; + } + + nl->priv->dump_networks = g_ptr_array_new_with_free_func (g_object_unref); + return TRUE; +} + +static gboolean +timeout_request_dump (gpointer user_data) +{ + GNetworkMonitorNetlink *nl = user_data; + + g_source_destroy (nl->priv->dump_source); + g_source_unref (nl->priv->dump_source); + nl->priv->dump_source = NULL; + + request_dump (nl, NULL); + + return FALSE; +} + +static void +queue_request_dump (GNetworkMonitorNetlink *nl) +{ + if (nl->priv->dump_networks) + return; + + if (nl->priv->dump_source) + { + g_source_destroy (nl->priv->dump_source); + g_source_unref (nl->priv->dump_source); + } + + nl->priv->dump_source = g_timeout_source_new (1000); + g_source_set_callback (nl->priv->dump_source, + (GSourceFunc) timeout_request_dump, nl, NULL); + g_source_attach (nl->priv->dump_source, + g_main_context_get_thread_default ()); +} + +static void +add_network (GNetworkMonitorNetlink *nl, + GSocketFamily family, + gint dest_len, + guint8 *dest, + guint8 *gateway) +{ + GInetAddress *dest_addr; + GInetAddressMask *network; + + if (dest) + dest_addr = g_inet_address_new_from_bytes (dest, family); + else + dest_addr = g_inet_address_new_any (family); + network = g_inet_address_mask_new (dest_addr, dest_len, NULL); + g_object_unref (dest_addr); + g_return_if_fail (network != NULL); + + if (nl->priv->dump_networks) + g_ptr_array_add (nl->priv->dump_networks, network); + else + { + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (nl), network); + g_object_unref (network); + } +} + +static void +remove_network (GNetworkMonitorNetlink *nl, + GSocketFamily family, + gint dest_len, + guint8 *dest, + guint8 *gateway) +{ + GInetAddress *dest_addr; + GInetAddressMask *network; + + if (dest) + dest_addr = g_inet_address_new_from_bytes (dest, family); + else + dest_addr = g_inet_address_new_any (family); + network = g_inet_address_mask_new (dest_addr, dest_len, NULL); + g_object_unref (dest_addr); + g_return_if_fail (network != NULL); + + if (nl->priv->dump_networks) + { + GInetAddressMask **dump_networks = (GInetAddressMask **)nl->priv->dump_networks->pdata; + int i; + + for (i = 0; i < nl->priv->dump_networks->len; i++) + { + if (g_inet_address_mask_equal (network, dump_networks[i])) + g_ptr_array_remove_index_fast (nl->priv->dump_networks, i--); + } + g_object_unref (network); + } + else + { + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (nl), network); + g_object_unref (network); + } +} + +static void +finish_dump (GNetworkMonitorNetlink *nl) +{ + g_network_monitor_base_set_networks (G_NETWORK_MONITOR_BASE (nl), + (GInetAddressMask **)nl->priv->dump_networks->pdata, + nl->priv->dump_networks->len); + g_ptr_array_free (nl->priv->dump_networks, FALSE); + nl->priv->dump_networks = NULL; +} + +static gboolean +read_netlink_messages (GSocket *socket, + GIOCondition condition, + gpointer user_data) +{ + GNetworkMonitorNetlink *nl = user_data; + GInputVector iv; + gsize len; + GSocketControlMessage **cmsgs = NULL; + gint num_cmsgs = 0, i, flags; + GError *error = NULL; + GCredentials *creds; + uid_t sender; + struct nlmsghdr *msg; + struct rtmsg *rtmsg; + struct rtattr *attr; + gsize attrlen; + guint8 *dest, *gateway; + gboolean retval = TRUE; + + iv.buffer = NULL; + iv.size = 0; + + flags = MSG_PEEK | MSG_TRUNC; + len = g_socket_receive_message (nl->priv->sock, NULL, &iv, 1, + NULL, NULL, &flags, NULL, &error); + if (len < 0) + { + g_warning ("Error on netlink socket: %s", error->message); + g_error_free (error); + if (nl->priv->dump_networks) + finish_dump (nl); + return FALSE; + } + + iv.buffer = g_malloc (len); + iv.size = len; + len = g_socket_receive_message (nl->priv->sock, NULL, &iv, 1, + &cmsgs, &num_cmsgs, NULL, NULL, &error); + if (len < 0) + { + g_warning ("Error on netlink socket: %s", error->message); + g_error_free (error); + if (nl->priv->dump_networks) + finish_dump (nl); + return FALSE; + } + + if (num_cmsgs != 1 || !G_IS_UNIX_CREDENTIALS_MESSAGE (cmsgs[0])) + goto done; + + creds = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (cmsgs[0])); + sender = g_credentials_get_unix_user (creds, NULL); + if (sender != 0) + goto done; + + msg = (struct nlmsghdr *) iv.buffer; + for (; len > 0; msg = NLMSG_NEXT (msg, len)) + { + if (!NLMSG_OK (msg, (size_t) len)) + { + g_warning ("netlink message was truncated; shouldn't happen..."); + retval = FALSE; + goto done; + } + + switch (msg->nlmsg_type) + { + case RTM_NEWROUTE: + case RTM_DELROUTE: + rtmsg = NLMSG_DATA (msg); + + if (rtmsg->rtm_family != AF_INET && rtmsg->rtm_family != AF_INET6) + continue; + if (rtmsg->rtm_type == RTN_UNREACHABLE) + continue; + + attrlen = NLMSG_PAYLOAD (msg, sizeof (struct rtmsg)); + attr = RTM_RTA (rtmsg); + dest = gateway = NULL; + while (RTA_OK (attr, attrlen)) + { + if (attr->rta_type == RTA_DST) + dest = RTA_DATA (attr); + else if (attr->rta_type == RTA_GATEWAY) + gateway = RTA_DATA (attr); + attr = RTA_NEXT (attr, attrlen); + } + + if (dest || gateway) + { + if (msg->nlmsg_type == RTM_NEWROUTE) + add_network (nl, rtmsg->rtm_family, rtmsg->rtm_dst_len, dest, gateway); + else + remove_network (nl, rtmsg->rtm_family, rtmsg->rtm_dst_len, dest, gateway); + queue_request_dump (nl); + } + break; + + case NLMSG_DONE: + finish_dump (nl); + goto done; + + case NLMSG_ERROR: + { + struct nlmsgerr *e = NLMSG_DATA (msg); + + g_warning ("netlink error: %s", g_strerror (-e->error)); + } + retval = FALSE; + goto done; + + default: + g_warning ("unexpected netlink message %d", msg->nlmsg_type); + retval = FALSE; + goto done; + } + } + + done: + for (i = 0; i < num_cmsgs; i++) + g_object_unref (cmsgs[i]); + g_free (cmsgs); + + g_free (iv.buffer); + + if (!retval && nl->priv->dump_networks) + finish_dump (nl); + return retval; +} + +static void +g_network_monitor_netlink_finalize (GObject *object) +{ + GNetworkMonitorNetlink *nl = G_NETWORK_MONITOR_NETLINK (object); + + if (nl->priv->sock) + { + g_socket_close (nl->priv->sock, NULL); + g_object_unref (nl->priv->sock); + } + + if (nl->priv->source) + { + g_source_destroy (nl->priv->source); + g_source_unref (nl->priv->source); + } + + if (nl->priv->dump_source) + { + g_source_destroy (nl->priv->dump_source); + g_source_unref (nl->priv->dump_source); + } + + G_OBJECT_CLASS (g_network_monitor_netlink_parent_class)->finalize (object); +} + +static void +g_network_monitor_netlink_class_init (GNetworkMonitorNetlinkClass *nl_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (nl_class); + + g_type_class_add_private (nl_class, sizeof (GNetworkMonitorNetlinkPrivate)); + + gobject_class->finalize = g_network_monitor_netlink_finalize; +} + +static void +g_network_monitor_netlink_iface_init (GNetworkMonitorInterface *monitor_iface) +{ +} + +static void +g_network_monitor_netlink_initable_iface_init (GInitableIface *iface) +{ + iface->init = g_network_monitor_netlink_initable_init; +} diff --git a/gio/gnetworkmonitornetlink.h b/gio/gnetworkmonitornetlink.h new file mode 100644 index 0000000..f6fa22a --- /dev/null +++ b/gio/gnetworkmonitornetlink.h @@ -0,0 +1,57 @@ +/* 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. + */ + +#ifndef __G_NETWORK_MONITOR_NETLINK_H__ +#define __G_NETWORK_MONITOR_NETLINK_H__ + +#include "gnetworkmonitorbase.h" + +G_BEGIN_DECLS + +#define G_TYPE_NETWORK_MONITOR_NETLINK (_g_network_monitor_netlink_get_type ()) +#define G_NETWORK_MONITOR_NETLINK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_NETWORK_MONITOR_NETLINK, GNetworkMonitorNetlink)) +#define G_NETWORK_MONITOR_NETLINK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_NETWORK_MONITOR_NETLINK, GNetworkMonitorNetlinkClass)) +#define G_IS_NETWORK_MONITOR_NETLINK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_NETWORK_MONITOR_NETLINK)) +#define G_IS_NETWORK_MONITOR_NETLINK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_NETWORK_MONITOR_NETLINK)) +#define G_NETWORK_MONITOR_NETLINK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_NETWORK_MONITOR_NETLINK, GNetworkMonitorNetlinkClass)) + +typedef struct _GNetworkMonitorNetlink GNetworkMonitorNetlink; +typedef struct _GNetworkMonitorNetlinkClass GNetworkMonitorNetlinkClass; +typedef struct _GNetworkMonitorNetlinkPrivate GNetworkMonitorNetlinkPrivate; + +struct _GNetworkMonitorNetlink { + GNetworkMonitorBase parent_instance; + + GNetworkMonitorNetlinkPrivate *priv; +}; + +struct _GNetworkMonitorNetlinkClass { + GNetworkMonitorBaseClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + gpointer padding[8]; +}; + +GType _g_network_monitor_netlink_get_type (void); + +G_END_DECLS + +#endif /* __G_NETWORK_MONITOR_NETLINK_H__ */ diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore index 8fdd74f..e3961af 100644 --- a/gio/tests/.gitignore +++ b/gio/tests/.gitignore @@ -70,6 +70,7 @@ live-g-file memory-input-stream memory-output-stream mimeapps +network-monitor network-address org.gtk.test.enums.xml pollable diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index 2091366..8d23c7a 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -56,6 +56,7 @@ TEST_PROGS += \ tls-interaction \ cancellable \ vfs \ + network-monitor \ $(NULL) if OS_UNIX @@ -492,6 +493,8 @@ cancellable_LDADD = $(progs_ldadd) vfs_LDADD = $(progs_ldadd) +network_monitor_LDADD = $(progs_ldadd) + # ----------------------------------------------------------------------------- if OS_UNIX diff --git a/gio/tests/network-monitor.c b/gio/tests/network-monitor.c new file mode 100644 index 0000000..0ba9ec4 --- /dev/null +++ b/gio/tests/network-monitor.c @@ -0,0 +1,463 @@ +/* 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 "gio.h" + +/* hack */ +#define GIO_COMPILATION +#include "gnetworkmonitorbase.h" + +#include + +/* Test data; the GInetAddresses and GInetAddressMasks get filled in + * in main(). Each address in a TestAddress matches the mask in its + * corresponding TestMask, and none of them match any of the other + * masks. The addresses in unmatched don't match any of the masks. + */ + +typedef struct { + const char *string; + GInetAddress *address; +} TestAddress; + +typedef struct { + const char *mask_string; + GInetAddressMask *mask; + TestAddress *addresses; +} TestMask; + +TestAddress net127addrs[] = { + { "127.0.0.1", NULL }, + { "127.0.0.2", NULL }, + { "127.0.0.255", NULL }, + { "127.0.1.0", NULL }, + { "127.0.255.0", NULL }, + { "127.0.255.0", NULL }, + { "127.255.255.255", NULL }, + { NULL, NULL } +}; +TestMask net127 = { "127.0.0.0/8", NULL, net127addrs }; + +TestAddress net10addrs[] = { + { "10.0.0.1", NULL }, + { "10.0.0.2", NULL }, + { "10.0.0.255", NULL }, + { NULL, NULL } +}; +TestMask net10 = { "10.0.0.0/24", NULL, net10addrs }; + +TestAddress net192addrs[] = { + { "192.168.0.1", NULL }, + { "192.168.0.2", NULL }, + { "192.168.0.255", NULL }, + { "192.168.1.0", NULL }, + { "192.168.15.0", NULL }, + { NULL, NULL } +}; +TestMask net192 = { "192.168.0.0/20", NULL, net192addrs }; + +TestAddress netlocal6addrs[] = { + { "::1", NULL }, + { NULL, NULL } +}; +TestMask netlocal6 = { "::1/128", NULL, netlocal6addrs }; + +TestAddress netfe80addrs[] = { + { "fe80::", NULL }, + { "fe80::1", NULL }, + { "fe80::21b:77ff:fea2:972a", NULL }, + { NULL, NULL } +}; +TestMask netfe80 = { "fe80::/64", NULL, netfe80addrs }; + +TestAddress unmatched[] = { + { "10.0.1.0", NULL }, + { "10.0.255.0", NULL }, + { "10.255.255.255", NULL }, + { "192.168.16.0", NULL }, + { "192.168.255.0", NULL }, + { "192.169.0.0", NULL }, + { "192.255.255.255", NULL }, + { "::2", NULL }, + { "1::1", NULL }, + { "fe80::1:0:0:0:0", NULL }, + { "fe80:8000::0:0:0:0", NULL }, + { NULL, NULL } +}; + +GInetAddressMask *ip4_default, *ip6_default; + +static void +notify_handler (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + gboolean *emitted = user_data; + + *emitted = TRUE; +} + +static void +network_changed_handler (GNetworkMonitor *monitor, + gboolean available, + gpointer user_data) +{ + gboolean *emitted = user_data; + + *emitted = TRUE; +} + +static void +assert_signals (GNetworkMonitor *monitor, + gboolean should_emit_notify, + gboolean should_emit_network_changed, + gboolean expected_network_available) +{ + gboolean emitted_notify = FALSE, emitted_network_changed = FALSE; + guint h1, h2; + + h1 = g_signal_connect (monitor, "notify::network-available", + G_CALLBACK (notify_handler), + &emitted_notify); + h2 = g_signal_connect (monitor, "network-changed", + G_CALLBACK (network_changed_handler), + &emitted_network_changed); + + g_main_context_iteration (NULL, FALSE); + + g_signal_handler_disconnect (monitor, h1); + g_signal_handler_disconnect (monitor, h2); + + g_assert (emitted_notify == should_emit_notify); + g_assert (emitted_network_changed == should_emit_network_changed); + + g_assert (g_network_monitor_get_network_available (monitor) == expected_network_available); +} + +static void +run_tests (GNetworkMonitor *monitor, + TestAddress *addresses, + gboolean should_be_reachable) +{ + GError *error = NULL; + int i; + gboolean reachable; + GSocketAddress *sockaddr; + + for (i = 0; addresses[i].address; i++) + { + sockaddr = g_inet_socket_address_new (addresses[i].address, 0); + reachable = g_network_monitor_can_reach (monitor, + G_SOCKET_CONNECTABLE (sockaddr), + NULL, &error); + g_object_unref (sockaddr); + g_assert_cmpint (reachable, ==, should_be_reachable); + if (should_be_reachable) + g_assert_no_error (error); + else + { + g_assert (error != NULL); + g_clear_error (&error); + } + } +} + +static void +test_default (void) +{ + GNetworkMonitor *monitor; + GError *error = NULL; + + monitor = g_initable_new (G_TYPE_NETWORK_MONITOR_BASE, NULL, &error, NULL); + g_assert_no_error (error); + + /* In the default configuration, all addresses are reachable */ + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, TRUE); + + assert_signals (monitor, FALSE, FALSE, TRUE); +} + +static void +test_remove_default (void) +{ + GNetworkMonitor *monitor; + GError *error = NULL; + + monitor = g_initable_new (G_TYPE_NETWORK_MONITOR_BASE, NULL, &error, NULL); + g_assert_no_error (error); + assert_signals (monitor, FALSE, FALSE, TRUE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + ip4_default); + assert_signals (monitor, FALSE, TRUE, TRUE); + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + ip6_default); + assert_signals (monitor, TRUE, TRUE, FALSE); + + /* Now nothing should be reachable */ + run_tests (monitor, net127.addresses, FALSE); + run_tests (monitor, net10.addresses, FALSE); + run_tests (monitor, net192.addresses, FALSE); + run_tests (monitor, netlocal6.addresses, FALSE); + run_tests (monitor, netfe80.addresses, FALSE); + run_tests (monitor, unmatched, FALSE); +} + +static void +test_add_networks (void) +{ + GNetworkMonitor *monitor; + GError *error = NULL; + + monitor = g_initable_new (G_TYPE_NETWORK_MONITOR_BASE, NULL, &error, NULL); + g_assert_no_error (error); + assert_signals (monitor, FALSE, FALSE, TRUE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + ip4_default); + assert_signals (monitor, FALSE, TRUE, TRUE); + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + ip6_default); + assert_signals (monitor, TRUE, TRUE, FALSE); + + /* Now add the masks one by one */ + + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + net127.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, FALSE); + run_tests (monitor, net192.addresses, FALSE); + run_tests (monitor, netlocal6.addresses, FALSE); + run_tests (monitor, netfe80.addresses, FALSE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + net10.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, FALSE); + run_tests (monitor, netlocal6.addresses, FALSE); + run_tests (monitor, netfe80.addresses, FALSE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + net192.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, FALSE); + run_tests (monitor, netfe80.addresses, FALSE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + netlocal6.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, FALSE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + netfe80.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, FALSE); +} + +static void +test_remove_networks (void) +{ + GNetworkMonitor *monitor; + GError *error = NULL; + + monitor = g_initable_new (G_TYPE_NETWORK_MONITOR_BASE, NULL, &error, NULL); + g_assert_no_error (error); + assert_signals (monitor, FALSE, FALSE, TRUE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + ip4_default); + assert_signals (monitor, FALSE, TRUE, TRUE); + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + ip6_default); + assert_signals (monitor, TRUE, TRUE, FALSE); + + /* First add them */ + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + net127.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + net10.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + net192.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + netlocal6.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (monitor), + netfe80.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + + run_tests (monitor, net127.addresses, TRUE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, FALSE); + + /* Now remove them one by one */ + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + net127.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, FALSE); + run_tests (monitor, net10.addresses, TRUE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + net10.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, FALSE); + run_tests (monitor, net10.addresses, FALSE); + run_tests (monitor, net192.addresses, TRUE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + net192.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, FALSE); + run_tests (monitor, net10.addresses, FALSE); + run_tests (monitor, net192.addresses, FALSE); + run_tests (monitor, netlocal6.addresses, TRUE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + netlocal6.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, FALSE); + run_tests (monitor, net10.addresses, FALSE); + run_tests (monitor, net192.addresses, FALSE); + run_tests (monitor, netlocal6.addresses, FALSE); + run_tests (monitor, netfe80.addresses, TRUE); + run_tests (monitor, unmatched, FALSE); + + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (monitor), + netfe80.mask); + assert_signals (monitor, FALSE, TRUE, FALSE); + run_tests (monitor, net127.addresses, FALSE); + run_tests (monitor, net10.addresses, FALSE); + run_tests (monitor, net192.addresses, FALSE); + run_tests (monitor, netlocal6.addresses, FALSE); + run_tests (monitor, netfe80.addresses, FALSE); + run_tests (monitor, unmatched, FALSE); +} + + +static void +init_test (TestMask *test) +{ + GError *error = NULL; + int i; + + test->mask = g_inet_address_mask_new_from_string (test->mask_string, &error); + g_assert_no_error (error); + + for (i = 0; test->addresses[i].string; i++) + { + test->addresses[i].address = g_inet_address_new_from_string (test->addresses[i].string); + if (strchr (test->addresses[i].string, ':')) + g_assert_cmpint (g_inet_address_get_family (test->addresses[i].address), ==, G_SOCKET_FAMILY_IPV6); + else + g_assert_cmpint (g_inet_address_get_family (test->addresses[i].address), ==, G_SOCKET_FAMILY_IPV4); + } +} + +static void +watch_network_changed (GNetworkMonitor *monitor, + gboolean available, + gpointer user_data) +{ + g_print ("Network is %s\n", available ? "up" : "down"); +} + +static void +do_watch_network (void) +{ + GNetworkMonitor *monitor = g_network_monitor_get_default (); + GMainLoop *loop; + + g_print ("Monitoring via %s\n", g_type_name_from_instance ((GTypeInstance *) monitor)); + + g_signal_connect (monitor, "network-changed", + G_CALLBACK (watch_network_changed), NULL); + watch_network_changed (monitor, g_network_monitor_get_network_available (monitor), NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); +} + +int +main (int argc, char **argv) +{ + g_type_init (); + + if (argc == 2 && !strcmp (argv[1], "--watch")) + { + do_watch_network (); + return 0; + } + + g_test_init (&argc, &argv, NULL); + + init_test (&net127); + init_test (&net10); + init_test (&net192); + init_test (&netlocal6); + init_test (&netfe80); + ip4_default = g_inet_address_mask_new_from_string ("0.0.0.0/0", NULL); + ip6_default = g_inet_address_mask_new_from_string ("::/0", NULL); + + g_test_add_func ("/network-monitor/default", test_default); + g_test_add_func ("/network-monitor/remove_default", test_remove_default); + g_test_add_func ("/network-monitor/add_networks", test_add_networks); + g_test_add_func ("/network-monitor/remove_networks", test_remove_networks); + + return g_test_run (); +}