mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-14 05:16:18 +01:00
c08ef6c165
If the default route is via a device rather than a particular IP address, then neither RTA_DST nor RTA_GATEWAY will be present in the RTM_NEWROUTE message, and so GNetworkMonitorNetlink would ignore it, and then think there was no default route. (This could happen with certain kinds of VPNs, if they were set to route all traffic through the VPN.) Fix this by recognizing routes that specify RTA_OIF ("output interface") instead of RTA_GATEWAY. https://bugzilla.gnome.org/show_bug.cgi?id=701609
466 lines
14 KiB
C
466 lines
14 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
|
*
|
|
* Copyright 2011 Red Hat, Inc.
|
|
*
|
|
* 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 <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gnetworkmonitornetlink.h"
|
|
#include "gcredentials.h"
|
|
#include "ginetaddressmask.h"
|
|
#include "ginitable.h"
|
|
#include "giomodule-priv.h"
|
|
#include "glibintl.h"
|
|
#include "glib/gstdio.h"
|
|
#include "gnetworkingprivate.h"
|
|
#include "gnetworkmonitor.h"
|
|
#include "gsocket.h"
|
|
#include "gunixcredentialsmessage.h"
|
|
|
|
/* must come at the end to pick system includes from
|
|
* gnetworkingprivate.h */
|
|
#include <linux/netlink.h>
|
|
#include <linux/rtnetlink.h>
|
|
|
|
static void g_network_monitor_netlink_iface_init (GNetworkMonitorInterface *iface);
|
|
static void g_network_monitor_netlink_initable_iface_init (GInitableIface *iface);
|
|
|
|
struct _GNetworkMonitorNetlinkPrivate
|
|
{
|
|
GSocket *sock;
|
|
GSource *source, *dump_source;
|
|
|
|
GPtrArray *dump_networks;
|
|
};
|
|
|
|
static gboolean read_netlink_messages (GSocket *socket,
|
|
GIOCondition condition,
|
|
gpointer user_data);
|
|
static gboolean request_dump (GNetworkMonitorNetlink *nl,
|
|
GError **error);
|
|
|
|
#define g_network_monitor_netlink_get_type _g_network_monitor_netlink_get_type
|
|
G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorNetlink, g_network_monitor_netlink, G_TYPE_NETWORK_MONITOR_BASE,
|
|
G_ADD_PRIVATE (GNetworkMonitorNetlink)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR,
|
|
g_network_monitor_netlink_iface_init)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
|
|
g_network_monitor_netlink_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,
|
|
"netlink",
|
|
20))
|
|
|
|
static void
|
|
g_network_monitor_netlink_init (GNetworkMonitorNetlink *nl)
|
|
{
|
|
nl->priv = g_network_monitor_netlink_get_instance_private (nl);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
g_network_monitor_netlink_initable_init (GInitable *initable,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GNetworkMonitorNetlink *nl = G_NETWORK_MONITOR_NETLINK (initable);
|
|
gint sockfd;
|
|
struct sockaddr_nl snl;
|
|
|
|
/* We create the socket the old-school way because sockaddr_netlink
|
|
* can't be represented as a GSocketAddress
|
|
*/
|
|
sockfd = g_socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE, NULL);
|
|
if (sockfd == -1)
|
|
{
|
|
int errsv = errno;
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
|
_("Could not create network monitor: %s"),
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
snl.nl_family = AF_NETLINK;
|
|
snl.nl_pid = snl.nl_pad = 0;
|
|
snl.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
|
|
if (bind (sockfd, (struct sockaddr *)&snl, sizeof (snl)) != 0)
|
|
{
|
|
int errsv = errno;
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
|
_("Could not create network monitor: %s"),
|
|
g_strerror (errno));
|
|
(void) g_close (sockfd, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
nl->priv->sock = g_socket_new_from_fd (sockfd, error);
|
|
if (error)
|
|
{
|
|
g_prefix_error (error, "%s", _("Could not create network monitor: "));
|
|
(void) g_close (sockfd, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_socket_set_option (nl->priv->sock, SOL_SOCKET, SO_PASSCRED,
|
|
TRUE, NULL))
|
|
{
|
|
int errsv = errno;
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
|
_("Could not create network monitor: %s"),
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Request the current state */
|
|
if (!request_dump (nl, error))
|
|
return FALSE;
|
|
|
|
/* And read responses; since we haven't yet marked the socket
|
|
* non-blocking, each call will block until a message is received.
|
|
*/
|
|
while (nl->priv->dump_networks)
|
|
{
|
|
if (!read_netlink_messages (NULL, G_IO_IN, nl))
|
|
break;
|
|
}
|
|
|
|
g_socket_set_blocking (nl->priv->sock, FALSE);
|
|
nl->priv->source = g_socket_create_source (nl->priv->sock, G_IO_IN, NULL);
|
|
g_source_set_callback (nl->priv->source,
|
|
(GSourceFunc) read_netlink_messages, nl, NULL);
|
|
g_source_attach (nl->priv->source,
|
|
g_main_context_get_thread_default ());
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
request_dump (GNetworkMonitorNetlink *nl,
|
|
GError **error)
|
|
{
|
|
struct nlmsghdr *n;
|
|
struct rtgenmsg *gen;
|
|
gchar buf[NLMSG_SPACE (sizeof (*gen))];
|
|
|
|
memset (buf, 0, sizeof (buf));
|
|
n = (struct nlmsghdr*) buf;
|
|
n->nlmsg_len = NLMSG_LENGTH (sizeof (*gen));
|
|
n->nlmsg_type = RTM_GETROUTE;
|
|
n->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
|
|
n->nlmsg_pid = 0;
|
|
gen = NLMSG_DATA (n);
|
|
gen->rtgen_family = AF_UNSPEC;
|
|
|
|
if (g_socket_send (nl->priv->sock, buf, sizeof (buf),
|
|
NULL, error) < 0)
|
|
{
|
|
g_prefix_error (error, "%s", _("Could not get network status: "));
|
|
return FALSE;
|
|
}
|
|
|
|
nl->priv->dump_networks = g_ptr_array_new_with_free_func (g_object_unref);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
timeout_request_dump (gpointer user_data)
|
|
{
|
|
GNetworkMonitorNetlink *nl = user_data;
|
|
|
|
g_source_destroy (nl->priv->dump_source);
|
|
g_source_unref (nl->priv->dump_source);
|
|
nl->priv->dump_source = NULL;
|
|
|
|
request_dump (nl, NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
queue_request_dump (GNetworkMonitorNetlink *nl)
|
|
{
|
|
if (nl->priv->dump_networks)
|
|
return;
|
|
|
|
if (nl->priv->dump_source)
|
|
{
|
|
g_source_destroy (nl->priv->dump_source);
|
|
g_source_unref (nl->priv->dump_source);
|
|
}
|
|
|
|
nl->priv->dump_source = g_timeout_source_new (1000);
|
|
g_source_set_callback (nl->priv->dump_source,
|
|
(GSourceFunc) timeout_request_dump, nl, NULL);
|
|
g_source_attach (nl->priv->dump_source,
|
|
g_main_context_get_thread_default ());
|
|
}
|
|
|
|
static void
|
|
add_network (GNetworkMonitorNetlink *nl,
|
|
GSocketFamily family,
|
|
gint dest_len,
|
|
guint8 *dest)
|
|
{
|
|
GInetAddress *dest_addr;
|
|
GInetAddressMask *network;
|
|
|
|
if (dest)
|
|
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, dest_len, NULL);
|
|
g_object_unref (dest_addr);
|
|
g_return_if_fail (network != NULL);
|
|
|
|
if (nl->priv->dump_networks)
|
|
g_ptr_array_add (nl->priv->dump_networks, network);
|
|
else
|
|
{
|
|
g_network_monitor_base_add_network (G_NETWORK_MONITOR_BASE (nl), network);
|
|
g_object_unref (network);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_network (GNetworkMonitorNetlink *nl,
|
|
GSocketFamily family,
|
|
gint dest_len,
|
|
guint8 *dest)
|
|
{
|
|
GInetAddress *dest_addr;
|
|
GInetAddressMask *network;
|
|
|
|
if (dest)
|
|
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, dest_len, NULL);
|
|
g_object_unref (dest_addr);
|
|
g_return_if_fail (network != NULL);
|
|
|
|
if (nl->priv->dump_networks)
|
|
{
|
|
GInetAddressMask **dump_networks = (GInetAddressMask **)nl->priv->dump_networks->pdata;
|
|
int i;
|
|
|
|
for (i = 0; i < nl->priv->dump_networks->len; i++)
|
|
{
|
|
if (g_inet_address_mask_equal (network, dump_networks[i]))
|
|
g_ptr_array_remove_index_fast (nl->priv->dump_networks, i--);
|
|
}
|
|
g_object_unref (network);
|
|
}
|
|
else
|
|
{
|
|
g_network_monitor_base_remove_network (G_NETWORK_MONITOR_BASE (nl), network);
|
|
g_object_unref (network);
|
|
}
|
|
}
|
|
|
|
static void
|
|
finish_dump (GNetworkMonitorNetlink *nl)
|
|
{
|
|
g_network_monitor_base_set_networks (G_NETWORK_MONITOR_BASE (nl),
|
|
(GInetAddressMask **)nl->priv->dump_networks->pdata,
|
|
nl->priv->dump_networks->len);
|
|
g_ptr_array_free (nl->priv->dump_networks, TRUE);
|
|
nl->priv->dump_networks = NULL;
|
|
}
|
|
|
|
static gboolean
|
|
read_netlink_messages (GSocket *socket,
|
|
GIOCondition condition,
|
|
gpointer user_data)
|
|
{
|
|
GNetworkMonitorNetlink *nl = user_data;
|
|
GInputVector iv;
|
|
gssize len;
|
|
GSocketControlMessage **cmsgs = NULL;
|
|
gint num_cmsgs = 0, i, flags;
|
|
GError *error = NULL;
|
|
GCredentials *creds;
|
|
uid_t sender;
|
|
struct nlmsghdr *msg;
|
|
struct rtmsg *rtmsg;
|
|
struct rtattr *attr;
|
|
gsize attrlen;
|
|
guint8 *dest, *gateway, *oif;
|
|
gboolean retval = TRUE;
|
|
|
|
iv.buffer = NULL;
|
|
iv.size = 0;
|
|
|
|
flags = MSG_PEEK | MSG_TRUNC;
|
|
len = g_socket_receive_message (nl->priv->sock, NULL, &iv, 1,
|
|
NULL, NULL, &flags, NULL, &error);
|
|
if (len < 0)
|
|
{
|
|
g_warning ("Error on netlink socket: %s", error->message);
|
|
g_error_free (error);
|
|
if (nl->priv->dump_networks)
|
|
finish_dump (nl);
|
|
return FALSE;
|
|
}
|
|
|
|
iv.buffer = g_malloc (len);
|
|
iv.size = len;
|
|
len = g_socket_receive_message (nl->priv->sock, NULL, &iv, 1,
|
|
&cmsgs, &num_cmsgs, NULL, NULL, &error);
|
|
if (len < 0)
|
|
{
|
|
g_warning ("Error on netlink socket: %s", error->message);
|
|
g_error_free (error);
|
|
if (nl->priv->dump_networks)
|
|
finish_dump (nl);
|
|
return FALSE;
|
|
}
|
|
|
|
if (num_cmsgs != 1 || !G_IS_UNIX_CREDENTIALS_MESSAGE (cmsgs[0]))
|
|
goto done;
|
|
|
|
creds = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (cmsgs[0]));
|
|
sender = g_credentials_get_unix_user (creds, NULL);
|
|
if (sender != 0)
|
|
goto done;
|
|
|
|
msg = (struct nlmsghdr *) iv.buffer;
|
|
for (; len > 0; msg = NLMSG_NEXT (msg, len))
|
|
{
|
|
if (!NLMSG_OK (msg, (size_t) len))
|
|
{
|
|
g_warning ("netlink message was truncated; shouldn't happen...");
|
|
retval = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
switch (msg->nlmsg_type)
|
|
{
|
|
case RTM_NEWROUTE:
|
|
case RTM_DELROUTE:
|
|
rtmsg = NLMSG_DATA (msg);
|
|
|
|
if (rtmsg->rtm_family != AF_INET && rtmsg->rtm_family != AF_INET6)
|
|
continue;
|
|
if (rtmsg->rtm_type == RTN_UNREACHABLE)
|
|
continue;
|
|
|
|
attrlen = NLMSG_PAYLOAD (msg, sizeof (struct rtmsg));
|
|
attr = RTM_RTA (rtmsg);
|
|
dest = gateway = oif = NULL;
|
|
while (RTA_OK (attr, attrlen))
|
|
{
|
|
if (attr->rta_type == RTA_DST)
|
|
dest = RTA_DATA (attr);
|
|
else if (attr->rta_type == RTA_GATEWAY)
|
|
gateway = RTA_DATA (attr);
|
|
else if (attr->rta_type == RTA_OIF)
|
|
oif = RTA_DATA (attr);
|
|
attr = RTA_NEXT (attr, attrlen);
|
|
}
|
|
|
|
if (dest || gateway || oif)
|
|
{
|
|
if (msg->nlmsg_type == RTM_NEWROUTE)
|
|
add_network (nl, rtmsg->rtm_family, rtmsg->rtm_dst_len, dest);
|
|
else
|
|
remove_network (nl, rtmsg->rtm_family, rtmsg->rtm_dst_len, dest);
|
|
queue_request_dump (nl);
|
|
}
|
|
break;
|
|
|
|
case NLMSG_DONE:
|
|
finish_dump (nl);
|
|
goto done;
|
|
|
|
case NLMSG_ERROR:
|
|
{
|
|
struct nlmsgerr *e = NLMSG_DATA (msg);
|
|
|
|
g_warning ("netlink error: %s", g_strerror (-e->error));
|
|
}
|
|
retval = FALSE;
|
|
goto done;
|
|
|
|
default:
|
|
g_warning ("unexpected netlink message %d", msg->nlmsg_type);
|
|
retval = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
for (i = 0; i < num_cmsgs; i++)
|
|
g_object_unref (cmsgs[i]);
|
|
g_free (cmsgs);
|
|
|
|
g_free (iv.buffer);
|
|
|
|
if (!retval && nl->priv->dump_networks)
|
|
finish_dump (nl);
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
g_network_monitor_netlink_finalize (GObject *object)
|
|
{
|
|
GNetworkMonitorNetlink *nl = G_NETWORK_MONITOR_NETLINK (object);
|
|
|
|
if (nl->priv->sock)
|
|
{
|
|
g_socket_close (nl->priv->sock, NULL);
|
|
g_object_unref (nl->priv->sock);
|
|
}
|
|
|
|
if (nl->priv->source)
|
|
{
|
|
g_source_destroy (nl->priv->source);
|
|
g_source_unref (nl->priv->source);
|
|
}
|
|
|
|
if (nl->priv->dump_source)
|
|
{
|
|
g_source_destroy (nl->priv->dump_source);
|
|
g_source_unref (nl->priv->dump_source);
|
|
}
|
|
|
|
G_OBJECT_CLASS (g_network_monitor_netlink_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
g_network_monitor_netlink_class_init (GNetworkMonitorNetlinkClass *nl_class)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (nl_class);
|
|
|
|
gobject_class->finalize = g_network_monitor_netlink_finalize;
|
|
}
|
|
|
|
static void
|
|
g_network_monitor_netlink_iface_init (GNetworkMonitorInterface *monitor_iface)
|
|
{
|
|
}
|
|
|
|
static void
|
|
g_network_monitor_netlink_initable_iface_init (GInitableIface *iface)
|
|
{
|
|
iface->init = g_network_monitor_netlink_initable_init;
|
|
}
|