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;
 | |
| } |