glib/gio/gnetworkmonitornetlink.c
Dan Winship c08ef6c165 gnetworkmonitornetlink: handle default route via device
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
2013-07-12 11:59:50 -04:00

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