/* GIO - GLib Input, Output and Streaming Library * * Copyright 2023 Leo Zi-You Assini * * 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 #include #include #include #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; }