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