From f9aacf3952effff897ab42991b5ba9090de5d970 Mon Sep 17 00:00:00 2001 From: Jan-Michael Brummer Date: Tue, 26 Dec 2017 14:06:08 +0100 Subject: [PATCH] GNetworkMonitorWindows: Add IPv4/IPv6 network monitor backend for windows Added a Windows backend to GNetworkMonitor, using NotifyRouteChange2() (available on Vista and later). It marshals the route change callbacks to the thread-specific default main context the GNetworkMonitor was constructed in. https://bugzilla.gnome.org/show_bug.cgi?id=685442 --- gio/Makefile.am | 2 + gio/giomodule.c | 4 + gio/gnetworkmonitorwindows.c | 333 +++++++++++++++++++++++++++++++++++ gio/gnetworkmonitorwindows.h | 53 ++++++ gio/meson.build | 2 + 5 files changed, 394 insertions(+) create mode 100644 gio/gnetworkmonitorwindows.c create mode 100644 gio/gnetworkmonitorwindows.h diff --git a/gio/Makefile.am b/gio/Makefile.am index 9b3d04ee9..c00085214 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -345,6 +345,8 @@ win32_actual_sources = \ gwin32outputstream.c \ gwin32outputstream.h \ gwin32networking.h \ + gnetworkmonitorwindows.c \ + gnetworkmonitorwindows.h \ $(NULL) win32_more_sources_for_vcproj = \ diff --git a/gio/giomodule.c b/gio/giomodule.c index 1adfd937e..1d8043d5b 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -964,6 +964,7 @@ extern GType g_cocoa_notification_backend_get_type (void); #ifdef G_PLATFORM_WIN32 #include +extern GType _g_network_monitor_windows_get_type (void); static HMODULE gio_dll = NULL; @@ -1179,6 +1180,9 @@ _g_io_modules_ensure_loaded (void) #ifdef HAVE_NETLINK g_type_ensure (_g_network_monitor_netlink_get_type ()); g_type_ensure (_g_network_monitor_nm_get_type ()); +#endif +#ifdef G_OS_WIN32 + g_type_ensure (_g_network_monitor_windows_get_type ()); #endif } diff --git a/gio/gnetworkmonitorwindows.c b/gio/gnetworkmonitorwindows.c new file mode 100644 index 000000000..369438b92 --- /dev/null +++ b/gio/gnetworkmonitorwindows.c @@ -0,0 +1,333 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2014-2018 Jan-Michael Brummer + * + * 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 +#include + +#include "gnetworkmonitorwindows.h" +#include "ginetaddress.h" +#include "ginetaddressmask.h" +#include "ginitable.h" +#include "giomodule-priv.h" +#include "glibintl.h" +#include "glib/gstdio.h" +#include "gnetworkingprivate.h" +#include "gsocket.h" +#include "gnetworkmonitor.h" +#include "gioerror.h" + +static void g_network_monitor_windows_iface_init (GNetworkMonitorInterface *iface); +static void g_network_monitor_windows_initable_iface_init (GInitableIface *iface); + +struct _GNetworkMonitorWindowsPrivate +{ + gboolean initialized; + GError *init_error; + GMainContext *main_context; + GSource *route_change_source; + HANDLE handle; +}; + +#define g_network_monitor_windows_get_type _g_network_monitor_windows_get_type +G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorWindows, g_network_monitor_windows, G_TYPE_NETWORK_MONITOR_BASE, + G_ADD_PRIVATE (GNetworkMonitorWindows) + G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR, + g_network_monitor_windows_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_network_monitor_windows_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, + "windows", + 20)) + +static void +g_network_monitor_windows_init (GNetworkMonitorWindows *win) +{ + win->priv = g_network_monitor_windows_get_instance_private (win); +} + +static gboolean +win_network_monitor_get_ip_info (IP_ADDRESS_PREFIX prefix, + GSocketFamily *family, + const guint8 **dest, + gsize *len) +{ + switch (prefix.Prefix.si_family) + { + case AF_UNSPEC: + /* Fall-through: AF_UNSPEC deliveres both IPV4 and IPV6 infos, let`s stick with IPV4 here */ + case AF_INET: + *family = G_SOCKET_FAMILY_IPV4; + *dest = (guint8 *) &prefix.Prefix.Ipv4.sin_addr; + *len = prefix.PrefixLength; + break; + case AF_INET6: + *family = G_SOCKET_FAMILY_IPV6; + *dest = (guint8 *) &prefix.Prefix.Ipv6.sin6_addr; + *len = prefix.PrefixLength; + break; + default: + return FALSE; + } + + return TRUE; +} + +static GInetAddressMask * +get_network_mask (GSocketFamily family, + const guint8 *dest, + gsize len) +{ + GInetAddressMask *network; + GInetAddress *dest_addr; + + if (dest != NULL) + 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, len, NULL); + g_object_unref (dest_addr); + + return network; +} + +static gboolean +win_network_monitor_process_table (GNetworkMonitorWindows *win, + GError **error) +{ + DWORD ret = 0; + GPtrArray *networks; + gsize i; + MIB_IPFORWARD_TABLE2 *routes = NULL; + MIB_IPFORWARD_ROW2 *route; + + ret = GetIpForwardTable2 (AF_UNSPEC, &routes); + if (ret != ERROR_SUCCESS) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "GetIpForwardTable2 () failed: %ld", ret); + + return FALSE; + } + + networks = g_ptr_array_new_full (routes->NumEntries, g_object_unref); + for (i = 0; i < routes->NumEntries; i++) + { + GInetAddressMask *network; + const guint8 *dest; + gsize len; + GSocketFamily family; + + route = routes->Table + i; + + if (!win_network_monitor_get_ip_info (route->DestinationPrefix, &family, &dest, &len)) + continue; + + network = get_network_mask (family, dest, len); + if (network == NULL) + continue; + + g_ptr_array_add (networks, network); + } + + g_network_monitor_base_set_networks (G_NETWORK_MONITOR_BASE (win), + (GInetAddressMask **) networks->pdata, + networks->len); + + return TRUE; +} + +static void +add_network (GNetworkMonitorWindows *win, + GSocketFamily family, + const guint8 *dest, + gsize dest_len) +{ + GInetAddressMask *network; + + network = get_network_mask (family, dest, dest_len); + if (network != NULL) + { + g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (win), network); + g_object_unref (network); + } +} + +static void +remove_network (GNetworkMonitorWindows *win, + GSocketFamily family, + const guint8 *dest, + gsize dest_len) +{ + GInetAddressMask *network; + + network = get_network_mask (family, dest, dest_len); + if (network != NULL) + { + g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (win), network); + g_object_unref (network); + } +} + +typedef struct { + PMIB_IPFORWARD_ROW2 route; + MIB_NOTIFICATION_TYPE type; + GNetworkMonitorWindows *win; +} RouteData; + +static gboolean +win_network_monitor_invoke_route_changed (gpointer user_data) +{ + GSocketFamily family; + RouteData *route_data = user_data; + const guint8 *dest; + gsize len; + + switch (route_data->type) + { + case MibDeleteInstance: + if (!win_network_monitor_get_ip_info (route_data->route->DestinationPrefix, &family, &dest, &len)) + break; + + remove_network (route_data->win, family, dest, len); + break; + case MibAddInstance: + if (!win_network_monitor_get_ip_info (route_data->route->DestinationPrefix, &family, &dest, &len)) + break; + + add_network (route_data->win, family, dest, len); + break; + case MibInitialNotification: + default: + break; + } + + return G_SOURCE_REMOVE; +} + +static VOID WINAPI +win_network_monitor_route_changed_cb (PVOID context, + PMIB_IPFORWARD_ROW2 route, + MIB_NOTIFICATION_TYPE type) +{ + GNetworkMonitorWindows *win = context; + RouteData *route_data; + + route_data = g_new0 (RouteData, 1); + route_data->route = route; + route_data->type = type; + route_data->win = win; + + win->priv->route_change_source = g_idle_source_new (); + g_source_set_priority (win->priv->route_change_source, G_PRIORITY_DEFAULT); + g_source_set_callback (win->priv->route_change_source, + win_network_monitor_invoke_route_changed, + route_data, + g_free); + + g_source_attach (win->priv->route_change_source, win->priv->main_context); +} + +static gboolean +g_network_monitor_windows_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GNetworkMonitorWindows *win = G_NETWORK_MONITOR_WINDOWS (initable); + NTSTATUS status; + gboolean read; + + if (!win->priv->initialized) + { + win->priv->main_context = g_main_context_ref_thread_default (); + + /* Read current IP routing table. */ + read = win_network_monitor_process_table (win, &win->priv->init_error); + if (read) + { + /* Register for IPv4 and IPv6 route updates. */ + status = NotifyRouteChange2 (AF_UNSPEC, (PIPFORWARD_CHANGE_CALLBACK) win_network_monitor_route_changed_cb, win, FALSE, &win->priv->handle); + if (status != NO_ERROR) + g_set_error (&win->priv->init_error, G_IO_ERROR, G_IO_ERROR_FAILED, + "NotifyRouteChange2() error: %ld", status); + } + + win->priv->initialized = TRUE; + } + + /* Forward the results. */ + if (win->priv->init_error != NULL) + { + g_propagate_error (error, g_error_copy (win->priv->init_error)); + return FALSE; + } + + return TRUE; +} + +static void +g_network_monitor_windows_finalize (GObject *object) +{ + GNetworkMonitorWindows *win = G_NETWORK_MONITOR_WINDOWS (object); + + /* Cancel notification event */ + if (win->priv->handle) + CancelMibChangeNotify2 (win->priv->handle); + + g_clear_error (&win->priv->init_error); + + if (win->priv->route_change_source != NULL) + { + g_source_destroy (win->priv->route_change_source); + g_source_unref (win->priv->route_change_source); + } + + g_main_context_unref (win->priv->main_context); + + G_OBJECT_CLASS (g_network_monitor_windows_parent_class)->finalize (object); +} + +static void +g_network_monitor_windows_class_init (GNetworkMonitorWindowsClass *win_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (win_class); + + gobject_class->finalize = g_network_monitor_windows_finalize; +} + +static void +g_network_monitor_windows_iface_init (GNetworkMonitorInterface *monitor_iface) +{ +} + +static void +g_network_monitor_windows_initable_iface_init (GInitableIface *iface) +{ + iface->init = g_network_monitor_windows_initable_init; +} diff --git a/gio/gnetworkmonitorwindows.h b/gio/gnetworkmonitorwindows.h new file mode 100644 index 000000000..ea8185f16 --- /dev/null +++ b/gio/gnetworkmonitorwindows.h @@ -0,0 +1,53 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2014-2018 Jan-Michael Brummer + * + * 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_WINDOWS_H__ +#define __G_NETWORK_MONITOR_WINDOWS_H__ + +#include "gnetworkmonitorbase.h" + +G_BEGIN_DECLS + +#define G_TYPE_NETWORK_MONITOR_WINDOWS (_g_network_monitor_windows_get_type ()) +#define G_NETWORK_MONITOR_WINDOWS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_NETWORK_MONITOR_WINDOWS, GNetworkMonitorWindows)) +#define G_NETWORK_MONITOR_WINDOWS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_NETWORK_MONITOR_WINDOWS, GNetworkMonitorWindowsClass)) +#define G_IS_NETWORK_MONITOR_WINDOWS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_NETWORK_MONITOR_WINDOWS)) +#define G_IS_NETWORK_MONITOR_WINDOWS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_NETWORK_MONITOR_WINDOWS)) +#define G_NETWORK_MONITOR_WINDOWS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_NETWORK_MONITOR_WINDOWS, GNetworkMonitorWindowsClass)) + +typedef struct _GNetworkMonitorWindows GNetworkMonitorWindows; +typedef struct _GNetworkMonitorWindowsClass GNetworkMonitorWindowsClass; +typedef struct _GNetworkMonitorWindowsPrivate GNetworkMonitorWindowsPrivate; + +struct _GNetworkMonitorWindows { + GNetworkMonitorBase parent_instance; + + GNetworkMonitorWindowsPrivate *priv; +}; + +struct _GNetworkMonitorWindowsClass { + GNetworkMonitorBaseClass parent_class; +}; + +GType _g_network_monitor_windows_get_type (void); + +G_END_DECLS + +#endif /* __G_NETWORK_MONITOR_WINDOWS_H__ */ diff --git a/gio/meson.build b/gio/meson.build index 83160734e..6d1e081f8 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -388,6 +388,8 @@ else 'gwin32volumemonitor.c', 'gwin32inputstream.c', 'gwin32outputstream.c', + 'gnetworkmonitorwindows.c', + 'gnetworkmonitorwindows.h', ) gio_win_rc = configure_file(