mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-02 07:36:17 +01:00
466 lines
12 KiB
C
466 lines
12 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 <linux/netlink.h>
|
||
|
#include <linux/rtnetlink.h>
|
||
|
|
||
|
#include "gnetworkmonitornetlink.h"
|
||
|
#include "gcredentials.h"
|
||
|
#include "ginetaddressmask.h"
|
||
|
#include "ginitable.h"
|
||
|
#include "giomodule-priv.h"
|
||
|
#include "glibintl.h"
|
||
|
#include "gnetworkingprivate.h"
|
||
|
#include "gnetworkmonitor.h"
|
||
|
#include "gsocket.h"
|
||
|
#include "gunixcredentialsmessage.h"
|
||
|
|
||
|
static void g_network_monitor_netlink_iface_init (GNetworkMonitorInterface *iface);
|
||
|
static void g_network_monitor_netlink_initable_iface_init (GInitableIface *iface);
|
||
|
|
||
|
#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_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))
|
||
|
|
||
|
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);
|
||
|
|
||
|
static void
|
||
|
g_network_monitor_netlink_init (GNetworkMonitorNetlink *nl)
|
||
|
{
|
||
|
nl->priv = G_TYPE_INSTANCE_GET_PRIVATE (nl,
|
||
|
G_TYPE_NETWORK_MONITOR_NETLINK,
|
||
|
GNetworkMonitorNetlinkPrivate);
|
||
|
}
|
||
|
|
||
|
|
||
|
static gboolean
|
||
|
g_network_monitor_netlink_initable_init (GInitable *initable,
|
||
|
GCancellable *cancellable,
|
||
|
GError **error)
|
||
|
{
|
||
|
GNetworkMonitorNetlink *nl = G_NETWORK_MONITOR_NETLINK (initable);
|
||
|
gint sockfd, val;
|
||
|
struct sockaddr_nl snl;
|
||
|
|
||
|
/* We create the socket the old-school way because sockaddr_netlink
|
||
|
* can't be represented as a GSocketAddress
|
||
|
*/
|
||
|
sockfd = socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||
|
if (sockfd == -1)
|
||
|
{
|
||
|
int errsv = errno;
|
||
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
||
|
_("Could not create netlink socket: %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 bind netlink socket: %s"),
|
||
|
g_strerror (errno));
|
||
|
close (sockfd);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
val = 1;
|
||
|
if (setsockopt (sockfd, SOL_SOCKET, SO_PASSCRED, &val, sizeof (val)) != 0)
|
||
|
{
|
||
|
int errsv = errno;
|
||
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
||
|
_("Could not set options on netlink socket: %s"),
|
||
|
g_strerror (errno));
|
||
|
close (sockfd);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
nl->priv->sock = g_socket_new_from_fd (sockfd, error);
|
||
|
if (error)
|
||
|
{
|
||
|
g_prefix_error (error, "%s", _("Could not wrap netlink socket: "));
|
||
|
close (sockfd);
|
||
|
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 send netlink request: "));
|
||
|
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,
|
||
|
guint8 *gateway)
|
||
|
{
|
||
|
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,
|
||
|
guint8 *gateway)
|
||
|
{
|
||
|
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, FALSE);
|
||
|
nl->priv->dump_networks = NULL;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
read_netlink_messages (GSocket *socket,
|
||
|
GIOCondition condition,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
GNetworkMonitorNetlink *nl = user_data;
|
||
|
GInputVector iv;
|
||
|
gsize 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;
|
||
|
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 = 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);
|
||
|
attr = RTA_NEXT (attr, attrlen);
|
||
|
}
|
||
|
|
||
|
if (dest || gateway)
|
||
|
{
|
||
|
if (msg->nlmsg_type == RTM_NEWROUTE)
|
||
|
add_network (nl, rtmsg->rtm_family, rtmsg->rtm_dst_len, dest, gateway);
|
||
|
else
|
||
|
remove_network (nl, rtmsg->rtm_family, rtmsg->rtm_dst_len, dest, gateway);
|
||
|
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);
|
||
|
|
||
|
g_type_class_add_private (nl_class, sizeof (GNetworkMonitorNetlinkPrivate));
|
||
|
|
||
|
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;
|
||
|
}
|