mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 15:06:14 +01:00
7cba800a84
This code used to look at the SCM_CREDENTIALS and ignore every message not from uid 0. However, when user namespaces are in use this does not work, as if uid 0 is not mapped you get overflowuid instead. Right now this means we ignore all messages in such user namespaces and glib apps hang on startup. We can't look at pids either, as pid 0 is returned for processes outside your pid namespace. Instead the correct approach is to look at the sending sockaddr and if the port id (nl_pid) is zero, then its from the kernel. Source: http://lists.linuxfoundation.org/pipermail/containers/2015-May/036032.html https://bugzilla.gnome.org/show_bug.cgi?id=750203
478 lines
14 KiB
C
478 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.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;
|
|
gint flags;
|
|
GError *error = NULL;
|
|
GSocketAddress *addr;
|
|
struct nlmsghdr *msg;
|
|
struct rtmsg *rtmsg;
|
|
struct rtattr *attr;
|
|
struct sockaddr_nl source_sockaddr;
|
|
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, &addr, &iv, 1,
|
|
NULL, NULL, 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 (!g_socket_address_to_native (addr, &source_sockaddr, sizeof (source_sockaddr), &error))
|
|
{
|
|
g_warning ("Error on netlink socket: %s", error->message);
|
|
g_error_free (error);
|
|
if (nl->priv->dump_networks)
|
|
finish_dump (nl);
|
|
return FALSE;
|
|
}
|
|
|
|
/* If the sender port id is 0 (not fakeable) then the message is from the kernel */
|
|
if (source_sockaddr.nl_pid != 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)
|
|
{
|
|
/* Unless we're processing the results of a dump, ignore
|
|
* IPv6 link-local multicast routes, which are added and
|
|
* removed all the time for some reason.
|
|
*/
|
|
#define UNALIGNED_IN6_IS_ADDR_MC_LINKLOCAL(a) \
|
|
((a[0] == 0xff) && ((a[1] & 0xf) == 0x2))
|
|
|
|
if (!nl->priv->dump_networks &&
|
|
rtmsg->rtm_family == AF_INET6 &&
|
|
rtmsg->rtm_dst_len != 0 &&
|
|
UNALIGNED_IN6_IS_ADDR_MC_LINKLOCAL (dest))
|
|
continue;
|
|
|
|
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:
|
|
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;
|
|
}
|