mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 08:22:16 +01:00 
			
		
		
		
	
		
			
	
	
		
			454 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			454 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /* GIO - GLib Input, Output and Streaming Library
 | ||
|  |  * | ||
|  |  * Copyright 2023 Leo Zi-You Assini <leoziyou@amazon.it> | ||
|  |  * | ||
|  |  * 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 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 <arpa/inet.h>
 | ||
|  | #include <fcntl.h>
 | ||
|  | #include <net/route.h>
 | ||
|  | #include <sys/sysctl.h>
 | ||
|  | 
 | ||
|  | #include "ginetaddress.h"
 | ||
|  | #include "ginetaddressmask.h"
 | ||
|  | #include "ginitable.h"
 | ||
|  | #include "gio.h"
 | ||
|  | #include "gioerror.h"
 | ||
|  | #include "giomodule-priv.h"
 | ||
|  | #include "gnetworkmonitor.h"
 | ||
|  | #include "gosxnetworkmonitor.h"
 | ||
|  | #include "gstdio.h"
 | ||
|  | 
 | ||
|  | static GInitableIface *initable_parent_iface; | ||
|  | static void g_osx_network_monitor_iface_init (GNetworkMonitorInterface *iface); | ||
|  | static void g_osx_network_monitor_initable_iface_init (GInitableIface *iface); | ||
|  | 
 | ||
|  | typedef struct | ||
|  | { | ||
|  |   GSource source; | ||
|  |   gint fd; | ||
|  |   gpointer tag; | ||
|  | } GOsxNetworkMonitorSource; | ||
|  | 
 | ||
|  | static gboolean | ||
|  | osx_network_monitor_source_dispatch (GSource *source, | ||
|  |                                      GSourceFunc callback, | ||
|  |                                      gpointer user_data) | ||
|  | { | ||
|  |   gboolean ret; | ||
|  | 
 | ||
|  |   g_return_val_if_fail (callback != NULL, FALSE); | ||
|  | 
 | ||
|  |   ret = callback (user_data); | ||
|  | 
 | ||
|  |   return ret; | ||
|  | } | ||
|  | 
 | ||
|  | static GSource * | ||
|  | osx_network_monitor_source_new (gint sockfd) | ||
|  | { | ||
|  |   static GSourceFuncs source_funcs = { | ||
|  |     NULL, /* prepare */ | ||
|  |     NULL, /* check */ | ||
|  |     osx_network_monitor_source_dispatch, | ||
|  |     NULL, /* finalize */ | ||
|  |     NULL, /* closure */ | ||
|  |     NULL  /* marshal */ | ||
|  |   }; | ||
|  | 
 | ||
|  |   GSource *source; | ||
|  |   GOsxNetworkMonitorSource *network_monitor_source; | ||
|  | 
 | ||
|  |   source = g_source_new (&source_funcs, sizeof (GOsxNetworkMonitorSource)); | ||
|  |   network_monitor_source = (GOsxNetworkMonitorSource *) source; | ||
|  | 
 | ||
|  |   network_monitor_source->fd = sockfd; | ||
|  |   network_monitor_source->tag = g_source_add_unix_fd (source, sockfd, G_IO_IN); | ||
|  | 
 | ||
|  |   g_debug ("Created source for fd=%d", sockfd); | ||
|  | 
 | ||
|  |   return source; | ||
|  | } | ||
|  | 
 | ||
|  | struct _GOsxNetworkMonitor | ||
|  | { | ||
|  |   GNetworkMonitorBase parent_instance; | ||
|  | 
 | ||
|  |   gint sockfd; | ||
|  |   char msg_buffer[sizeof (struct rt_msghdr) + sizeof (struct sockaddr) * 8]; | ||
|  |   GSource *route_change_source; /* (owned) (nullable) */ | ||
|  | }; | ||
|  | 
 | ||
|  | G_DEFINE_TYPE_WITH_CODE (GOsxNetworkMonitor, g_osx_network_monitor, G_TYPE_NETWORK_MONITOR_BASE, G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR, g_osx_network_monitor_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_osx_network_monitor_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, | ||
|  |                                                          "osx", | ||
|  |                                                          20)) | ||
|  | 
 | ||
|  | #define ROUNDUP(a, size) (((a) & ((size) -1)) ? (1 + ((a) | ((size) -1))) : (a))
 | ||
|  | 
 | ||
|  | #define NEXT_SA(ap) ap = (struct sockaddr *) ((caddr_t) ap + (ap->sa_len ? ROUNDUP (ap->sa_len, sizeof (u_long)) : sizeof (u_long)))
 | ||
|  | 
 | ||
|  | /* Extract the sockaddrs from @sa into @rti_info according to the mask in @addrs.
 | ||
|  |  * @rti_info must be allocated by the caller and have at least as many elements | ||
|  |  * as there are high bits in @addrs, up to `RTAX_MAX` elements. | ||
|  |  */ | ||
|  | static void | ||
|  | get_rtaddrs (unsigned int addrs_mask, | ||
|  |              const struct sockaddr *sa, | ||
|  |              const struct sockaddr **rti_info) | ||
|  | { | ||
|  |   for (unsigned int i = 0; i < RTAX_MAX; i++) | ||
|  |     { | ||
|  |       if (addrs_mask & (1 << i)) | ||
|  |         { | ||
|  |           rti_info[i] = sa; | ||
|  |           NEXT_SA (sa); | ||
|  |         } | ||
|  |       else | ||
|  |         rti_info[i] = NULL; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /* Returns the position of the last positive bit
 | ||
|  |  * | ||
|  |  * 0.0.0.0 (00000000.00000000.0000000.0000000) => 0 | ||
|  |  * 255.255.255.255 (11111111.11111111.11111111.11111111) => 32 | ||
|  |  * 0.0.0.1 (00000000.00000000.0000000.0000001) => 32 | ||
|  |  * 32.0.0.0 (00100001.00000000.0000000.0000000) => 8 | ||
|  |  * 0.16.0.16 (00000000.00010000.0000000.00010000) => 28 | ||
|  |  */ | ||
|  | static gsize | ||
|  | get_last_bit_position (const guint8 *ip, | ||
|  |                        gsize len_in_bits) | ||
|  | { | ||
|  |   gssize i; | ||
|  |   gssize bytes = (gssize) len_in_bits / 8; | ||
|  |   gulong ip_in_binary = 0; | ||
|  | 
 | ||
|  |   for (i = 0; i < bytes; i++) | ||
|  |     ip_in_binary = (ip_in_binary << 8) | ip[i]; | ||
|  | 
 | ||
|  |   if (ip_in_binary == 0) | ||
|  |     return 0; | ||
|  | 
 | ||
|  |   gsize last_bit_position = len_in_bits - g_bit_nth_lsf (ip_in_binary, -1); | ||
|  | 
 | ||
|  |   return (gsize) last_bit_position; | ||
|  | } | ||
|  | 
 | ||
|  | static GInetAddressMask * | ||
|  | get_network_mask (const struct rt_msghdr *rtm) | ||
|  | { | ||
|  |   GInetAddressMask *network = NULL; | ||
|  |   GInetAddress *dest_addr; | ||
|  |   const struct sockaddr *sa = (struct sockaddr *) (rtm + 1); | ||
|  |   const struct sockaddr *nm; | ||
|  |   const struct sockaddr *rti_info[RTAX_MAX]; | ||
|  |   GSocketFamily family; | ||
|  |   const guint8 *dest; | ||
|  |   gsize len; | ||
|  |   GError *error = NULL; | ||
|  | 
 | ||
|  |   get_rtaddrs (rtm->rtm_addrs, sa, rti_info); | ||
|  | 
 | ||
|  |   sa = rti_info[RTAX_DST]; | ||
|  |   if (sa == NULL) | ||
|  |     return NULL; | ||
|  | 
 | ||
|  |   nm = rti_info[RTAX_NETMASK]; | ||
|  |   if (nm == NULL) | ||
|  |     return NULL; | ||
|  | 
 | ||
|  |   /* Get IP information */ | ||
|  |   switch (sa->sa_family) | ||
|  |     { | ||
|  |     case AF_UNSPEC: | ||
|  |       /* Fall-through: AF_UNSPEC delivers both IPv4 and IPv6 infos, let's stick with IPv4 here */ | ||
|  |     case AF_INET: | ||
|  |       family = G_SOCKET_FAMILY_IPV4; | ||
|  |       /* For IPv4 sin_addr is a guint8 array[4], e.g [255, 255, 255, 255] */ | ||
|  |       dest = (guint8 *) &((struct sockaddr_in *) sa)->sin_addr; | ||
|  |       len = get_last_bit_position (dest, 32); | ||
|  |       break; | ||
|  |     case AF_INET6: | ||
|  |       /* Skip IPv6 here as OSX keeps a default route to a tunneling device even if disconnected */ | ||
|  |       return NULL; | ||
|  |     default: | ||
|  |       return NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |   /* Create dest address */ | ||
|  |   if (dest != NULL) | ||
|  |     dest_addr = g_inet_address_new_from_bytes (dest, family); | ||
|  |   else | ||
|  |     dest_addr = g_inet_address_new_any (family); | ||
|  | 
 | ||
|  |   /* Create and return network mask */ | ||
|  |   network = g_inet_address_mask_new (dest_addr, len, &error); | ||
|  | 
 | ||
|  |   if (network == NULL) | ||
|  |     { | ||
|  |       g_warning ("Unable to create network mask: %s", error->message); | ||
|  |       g_error_free (error); | ||
|  |     } | ||
|  | 
 | ||
|  |   g_object_unref (dest_addr); | ||
|  | 
 | ||
|  |   return g_steal_pointer (&network); | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | osx_network_manager_process_table (GOsxNetworkMonitor *self, | ||
|  |                                    GError **error) | ||
|  | { | ||
|  |   GPtrArray *networks; | ||
|  |   gsize n_networks; | ||
|  |   struct rt_msghdr *rtm; | ||
|  |   gint mib[6]; | ||
|  |   gsize needed; | ||
|  |   gchar *buf; | ||
|  |   gchar *limit; | ||
|  |   gchar *next; | ||
|  | 
 | ||
|  |   /* Create Management Information Base
 | ||
|  |    * System information is stored in a hierarchial tree structure. By specifying each array element the search can be refined. | ||
|  |    * First array element is the top level name which is always prefixed by CTL_ (examples include CTL_VFS for file system information, CTL_HW for user-level information etc...) | ||
|  |    * https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctl.3.html
 | ||
|  |    */ | ||
|  |   mib[0] = CTL_NET;  /* CTL_NET = Network related information */ | ||
|  |   mib[1] = PF_ROUTE; /* PF_ROUTE = Retrieve entire routing table */ | ||
|  | 
 | ||
|  |   mib[2] = 0; /* 0 = protocol number, which is currently always 0 */ | ||
|  |   mib[3] = 0; /* 0 = Retrieve all address families */ | ||
|  |   mib[4] = NET_RT_DUMP; | ||
|  |   mib[5] = 0; | ||
|  | 
 | ||
|  |   /* Request size of buffer */ | ||
|  |   if (sysctl (mib, G_N_ELEMENTS (mib), NULL, &needed, NULL, 0) < 0 || needed == 0) | ||
|  |     { | ||
|  |       int saved_errno = errno; | ||
|  | 
 | ||
|  |       g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), | ||
|  |                            "Could not request buffer size"); | ||
|  | 
 | ||
|  |       return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |   /* Allocate memory */ | ||
|  |   buf = g_malloc0 (needed); | ||
|  | 
 | ||
|  |   /* Request needed bytes in buffer for routing table */ | ||
|  |   if (sysctl (mib, G_N_ELEMENTS (mib), buf, &needed, NULL, 0) < 0) | ||
|  |     { | ||
|  |       int saved_errno = errno; | ||
|  | 
 | ||
|  |       g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), | ||
|  |                            "Could not request buffer"); | ||
|  | 
 | ||
|  |       g_free (buf); | ||
|  | 
 | ||
|  |       return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |   limit = buf + needed; | ||
|  | 
 | ||
|  |   networks = g_ptr_array_new_with_free_func (g_object_unref); | ||
|  |   for (next = buf; next < limit; next += rtm->rtm_msglen) | ||
|  |     { | ||
|  |       GInetAddressMask *network; | ||
|  | 
 | ||
|  |       rtm = (struct rt_msghdr *) next; | ||
|  | 
 | ||
|  |       network = get_network_mask (rtm); | ||
|  |       if (network == NULL) | ||
|  |         continue; | ||
|  | 
 | ||
|  |       g_ptr_array_add (networks, g_steal_pointer (&network)); | ||
|  |     } | ||
|  | 
 | ||
|  |   n_networks = networks->len; | ||
|  |   g_network_monitor_base_set_networks (G_NETWORK_MONITOR_BASE (self), | ||
|  |                                        (GInetAddressMask **) g_ptr_array_steal (networks, &n_networks), | ||
|  |                                        n_networks); | ||
|  | 
 | ||
|  |   g_free (buf); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | clear_network_monitor (GOsxNetworkMonitor *self) | ||
|  | { | ||
|  |   g_debug ("Clearing source for fd=%d", self->sockfd); | ||
|  | 
 | ||
|  |   if (self->route_change_source != NULL) | ||
|  |     { | ||
|  |       g_source_destroy (self->route_change_source); | ||
|  |       g_clear_pointer (&self->route_change_source, g_source_unref); | ||
|  |     } | ||
|  | 
 | ||
|  |   g_clear_fd (&self->sockfd, NULL); | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | osx_network_monitor_callback (gpointer user_data) | ||
|  | { | ||
|  |   GOsxNetworkMonitor *self = user_data; | ||
|  |   gint32 read_msg; | ||
|  |   GInetAddressMask *network; | ||
|  | 
 | ||
|  |   memset (&self->msg_buffer, 0, sizeof (self->msg_buffer)); | ||
|  |   read_msg = read (self->sockfd, self->msg_buffer, sizeof (self->msg_buffer)); | ||
|  | 
 | ||
|  |   /* Skip read if we have no data */ | ||
|  |   if (read_msg == -1 && errno == EAGAIN) | ||
|  |     { | ||
|  |       return G_SOURCE_CONTINUE; | ||
|  |     } | ||
|  | 
 | ||
|  |   if (read_msg <= 0) | ||
|  |     { | ||
|  |       g_warning ("Unable to monitor network change: failed to read from socket"); | ||
|  |       clear_network_monitor (self); | ||
|  |       return G_SOURCE_REMOVE; | ||
|  |     } | ||
|  | 
 | ||
|  |   /* Check if it is a type of interest */ | ||
|  |   switch (((struct rt_msghdr *) self->msg_buffer)->rtm_type) | ||
|  |     { | ||
|  |     case RTM_ADD: | ||
|  |       network = get_network_mask ((struct rt_msghdr *) self->msg_buffer); | ||
|  |       if (network != NULL) | ||
|  |         { | ||
|  |           g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (self), network); | ||
|  |           g_object_unref (network); | ||
|  |         } | ||
|  |       break; | ||
|  |     case RTM_DELETE: | ||
|  |       network = get_network_mask ((struct rt_msghdr *) self->msg_buffer); | ||
|  |       if (network != NULL) | ||
|  |         { | ||
|  |           g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (self), network); | ||
|  |           g_object_unref (network); | ||
|  |         } | ||
|  |       break; | ||
|  |     default: | ||
|  |       break; | ||
|  |     } | ||
|  | 
 | ||
|  |   return G_SOURCE_CONTINUE; | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | g_osx_network_monitor_start_monitoring (GOsxNetworkMonitor *self, | ||
|  |                                         GError **error) | ||
|  | { | ||
|  |   GSource *source; | ||
|  | 
 | ||
|  |   self->sockfd = socket (PF_ROUTE, SOCK_RAW, 0); | ||
|  |   if (self->sockfd == -1) | ||
|  |     { | ||
|  |       int saved_errno = errno; | ||
|  | 
 | ||
|  |       g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), | ||
|  |                            "Failed to create PF_ROUTE socket"); | ||
|  | 
 | ||
|  |       return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |   /* FIXME: Currently it is not possible to set SOCK_NONBLOCK and SOCK_CLOEXEC
 | ||
|  |    * in the socket constructor so workaround is this racy call to fcntl. Should be | ||
|  |    * replaced once the flags are supported. */ | ||
|  |   fcntl (self->sockfd, F_SETFL, fcntl (self->sockfd, F_GETFL, 0) | O_NONBLOCK | O_CLOEXEC); | ||
|  | 
 | ||
|  |   source = osx_network_monitor_source_new (self->sockfd); | ||
|  | 
 | ||
|  |   g_source_set_priority (source, G_PRIORITY_DEFAULT); | ||
|  |   g_source_set_callback (source, | ||
|  |                          osx_network_monitor_callback, | ||
|  |                          self, | ||
|  |                          NULL); | ||
|  |   g_source_attach (source, NULL); | ||
|  | 
 | ||
|  |   self->route_change_source = g_steal_pointer (&source); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | g_osx_network_monitor_init (GOsxNetworkMonitor *self) | ||
|  | { | ||
|  |   self->sockfd = -1; | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | g_osx_network_monitor_initable_init (GInitable *initable, | ||
|  |                                      GCancellable *cancellable, | ||
|  |                                      GError **error) | ||
|  | { | ||
|  |   GOsxNetworkMonitor *self = G_OSX_NETWORK_MONITOR (initable); | ||
|  | 
 | ||
|  |   /* Read current IP routing table. */ | ||
|  |   if (!osx_network_manager_process_table (self, error)) | ||
|  |     { | ||
|  |       return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |   /* Start monitoring */ | ||
|  |   if (!g_osx_network_monitor_start_monitoring (self, error)) | ||
|  |     { | ||
|  |       return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |   return initable_parent_iface->init (initable, cancellable, error); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | g_osx_network_monitor_finalize (GObject *object) | ||
|  | { | ||
|  |   GOsxNetworkMonitor *self = G_OSX_NETWORK_MONITOR (object); | ||
|  | 
 | ||
|  |   clear_network_monitor (self); | ||
|  | 
 | ||
|  |   G_OBJECT_CLASS (g_osx_network_monitor_parent_class)->finalize (object); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | g_osx_network_monitor_class_init (GOsxNetworkMonitorClass *osx_class) | ||
|  | { | ||
|  |   GObjectClass *gobject_class = G_OBJECT_CLASS (osx_class); | ||
|  | 
 | ||
|  |   gobject_class->finalize = g_osx_network_monitor_finalize; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | g_osx_network_monitor_iface_init (GNetworkMonitorInterface *monitor_iface) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | g_osx_network_monitor_initable_iface_init (GInitableIface *iface) | ||
|  | { | ||
|  |   initable_parent_iface = g_type_interface_peek_parent (iface); | ||
|  | 
 | ||
|  |   iface->init = g_osx_network_monitor_initable_init; | ||
|  | } |