Files
ModemManager/src/mm-netlink.c
T
Dan Williams 0a94b0138d netlink: add RTM_GETLINK support to read MAC address
Since we're also now reading responses we need to check sender
credentials to make sure we're talking to the kernel, and not
some other process spoofing netlink.

Signed-off-by: Dan Williams <dan@ioncontrol.co>
2025-02-18 21:06:19 +00:00

639 lines
18 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details:
*
* Basic netlink support based on the QmiNetPortManagerRmnet from libqmi:
* Copyright (C) 2020 Eric Caruso <ejcaruso@chromium.org>
* Copyright (C) 2020 Andrew Lassalle <andrewlassalle@chromium.org>
*
* Copyright (C) 2021 Aleksander Morgado <aleksander@aleksander.es>
* Copyright (C) 2024 JUCR GmbH
*/
#include <errno.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <config.h>
#include <ModemManager.h>
#include <mm-errors-types.h>
#include "mm-log-object.h"
#include "mm-utils.h"
#include "mm-netlink.h"
struct _MMNetlink {
GObject parent;
/* Netlink socket */
GSocket *socket;
GSource *source;
/* Netlink state */
guint current_sequence_id;
GHashTable *transactions;
};
struct _MMNetlinkClass {
GObjectClass parent_class;
};
static void log_object_iface_init (MMLogObjectInterface *iface);
G_DEFINE_TYPE_EXTENDED (MMNetlink, mm_netlink, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
/*****************************************************************************/
/*
* Netlink message construction functions
*/
typedef GByteArray NetlinkMessage;
typedef struct {
struct nlmsghdr msghdr;
struct ifinfomsg ifreq;
} NetlinkHeader;
static NetlinkHeader *
netlink_message_header (NetlinkMessage *msg)
{
return (NetlinkHeader *) (msg->data);
}
static guint
get_pos_of_next_attr (NetlinkMessage *msg)
{
return NLMSG_ALIGN (msg->len);
}
static void
append_netlink_attribute (NetlinkMessage *msg,
gushort type,
gconstpointer value,
gushort len)
{
guint attr_len;
guint old_len;
guint next_attr_rel_pos;
char *next_attr_abs_pos;
struct rtattr new_attr;
/* Expand the buffer to hold the new attribute */
attr_len = RTA_ALIGN (RTA_LENGTH (len));
old_len = msg->len;
next_attr_rel_pos = get_pos_of_next_attr (msg);
g_byte_array_set_size (msg, next_attr_rel_pos + attr_len);
/* fill new bytes with zero, since some padding is added between attributes. */
memset ((char *) msg->data + old_len, 0, msg->len - old_len);
new_attr.rta_type = type;
new_attr.rta_len = RTA_LENGTH (len);
next_attr_abs_pos = (char *) msg->data + next_attr_rel_pos;
memcpy (next_attr_abs_pos, &new_attr, sizeof (struct rtattr));
if (value)
memcpy (RTA_DATA (next_attr_abs_pos), value, len);
/* Update the total netlink message length */
netlink_message_header (msg)->msghdr.nlmsg_len = msg->len;
}
static void
append_netlink_attribute_uint32 (NetlinkMessage *msg,
gushort type,
guint32 value)
{
append_netlink_attribute (msg, type, &value, sizeof (value));
}
static NetlinkMessage *
netlink_message_new (guint ifindex,
guint16 type)
{
NetlinkMessage *msg;
NetlinkHeader *hdr;
int size = sizeof (NetlinkHeader);
msg = g_byte_array_new ();
g_byte_array_set_size (msg, size);
memset ((char *) msg->data, 0, size);
hdr = netlink_message_header (msg);
hdr->msghdr.nlmsg_len = msg->len;
hdr->msghdr.nlmsg_type = type;
hdr->msghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
hdr->ifreq.ifi_family = AF_UNSPEC;
hdr->ifreq.ifi_index = ifindex;
return msg;
}
static NetlinkMessage *
netlink_message_new_setlink (guint ifindex,
gboolean up,
guint mtu)
{
NetlinkMessage *msg;
NetlinkHeader *hdr;
msg = netlink_message_new (ifindex, RTM_SETLINK);
hdr = netlink_message_header (msg);
hdr->ifreq.ifi_flags = up ? IFF_UP : 0;
hdr->ifreq.ifi_change = IFF_UP;
if (mtu)
append_netlink_attribute_uint32 (msg, IFLA_MTU, mtu);
return msg;
}
static NetlinkMessage *
netlink_message_new_getlink (guint ifindex)
{
return netlink_message_new (ifindex, RTM_GETLINK);
}
static void
netlink_message_free (NetlinkMessage *msg)
{
g_byte_array_unref (msg);
}
/*****************************************************************************/
/* Netlink transactions */
typedef gboolean (*MsgFunc) (GTask *, struct nlmsghdr *, GError **);
typedef struct {
MMNetlink *self;
guint32 sequence_id;
GSource *timeout_source;
GTask *completion_task;
MsgFunc completion_fn;
} Transaction;
static gboolean
transaction_timed_out (Transaction *tr)
{
GTask *task;
guint32 sequence_id;
task = g_steal_pointer (&tr->completion_task);
sequence_id = tr->sequence_id;
g_hash_table_remove (tr->self->transactions,
GUINT_TO_POINTER (tr->sequence_id));
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
"Netlink message with sequence ID %u timed out",
sequence_id);
g_object_unref (task);
return G_SOURCE_REMOVE;
}
static void
transaction_complete_with_error (Transaction *tr,
GError *error)
{
GTask *task;
task = g_steal_pointer (&tr->completion_task);
g_hash_table_remove (tr->self->transactions,
GUINT_TO_POINTER (tr->sequence_id));
g_task_return_error (task, error);
g_object_unref (task);
}
static void
transaction_complete (Transaction *tr,
struct nlmsghdr *hdr,
gint saved_errno)
{
GTask *task;
guint32 sequence_id;
task = g_steal_pointer (&tr->completion_task);
sequence_id = tr->sequence_id;
g_hash_table_remove (tr->self->transactions,
GUINT_TO_POINTER (tr->sequence_id));
if (!saved_errno) {
GError *error = NULL;
if (!tr->completion_fn (task, hdr, &error))
g_task_return_error (task, error);
} else {
g_task_return_new_error (task, G_IO_ERROR, g_io_error_from_errno (saved_errno),
"Netlink message with transaction %u failed",
sequence_id);
}
g_object_unref (task);
}
static void
transaction_free (Transaction *tr)
{
g_assert (tr->completion_task == NULL);
g_source_destroy (tr->timeout_source);
g_source_unref (tr->timeout_source);
g_slice_free (Transaction, tr);
}
static Transaction *
transaction_new (MMNetlink *self,
NetlinkMessage *msg,
guint timeout,
GTask *task,
MsgFunc completion_fn)
{
Transaction *tr;
tr = g_slice_new0 (Transaction);
tr->self = self;
tr->sequence_id = ++self->current_sequence_id;
netlink_message_header (msg)->msghdr.nlmsg_seq = tr->sequence_id;
if (timeout) {
tr->timeout_source = g_timeout_source_new_seconds (timeout);
g_source_set_callback (tr->timeout_source,
(GSourceFunc) transaction_timed_out,
tr,
NULL);
g_source_attach (tr->timeout_source,
g_main_context_get_thread_default ());
}
tr->completion_task = g_object_ref (task);
tr->completion_fn = completion_fn;
g_hash_table_insert (self->transactions,
GUINT_TO_POINTER (tr->sequence_id),
tr);
return tr;
}
/*****************************************************************************/
gboolean
mm_netlink_setlink_finish (MMNetlink *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
setlink_complete (GTask *task, struct nlmsghdr *hdr, GError **error)
{
/* Nothing to do here; errors are handled by transaction_complete() */
g_task_return_boolean (task, TRUE);
return TRUE;
}
void
mm_netlink_setlink (MMNetlink *self,
guint ifindex,
gboolean up,
guint mtu,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
NetlinkMessage *msg;
Transaction *tr;
gssize bytes_sent;
GError *error = NULL;
task = g_task_new (self, cancellable, callback, user_data);
if (!self->socket) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"netlink support not available");
g_object_unref (task);
return;
}
msg = netlink_message_new_setlink (ifindex, up, mtu);
/* The task ownership is transferred to the transaction. */
tr = transaction_new (self, msg, 5, task, setlink_complete);
bytes_sent = g_socket_send (self->socket,
(const gchar *) msg->data,
msg->len,
cancellable,
&error);
netlink_message_free (msg);
if (bytes_sent < 0)
transaction_complete_with_error (tr, error);
g_object_unref (task);
}
/*****************************************************************************/
GByteArray *
mm_netlink_get_hwaddr_finish (MMNetlink *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static gboolean
get_hwaddr_complete (GTask *task, struct nlmsghdr *hdr, GError **error)
{
const struct ifinfomsg *ifi;
GByteArray *hwaddr = NULL;
struct rtattr *rta;
int attr_len;
if (hdr->nlmsg_type != RTM_NEWLINK) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"unexpected GETLINK reply message type %d",
hdr->nlmsg_type);
return FALSE;
}
ifi = NLMSG_DATA (hdr);
if (ifi->ifi_index <= 0) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"unexpected link index %d",
ifi->ifi_index);
return FALSE;
}
attr_len = IFLA_PAYLOAD (hdr);
rta = IFLA_RTA (ifi);
for (; RTA_OK (rta, attr_len); rta = RTA_NEXT (rta, attr_len)) {
switch (rta->rta_type) {
case IFLA_ADDRESS:
#define ETH_ADDR_LEN 6
if (rta->rta_len < ETH_ADDR_LEN) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"invalid hardware address length %d",
rta->rta_len);
return FALSE;
}
hwaddr = g_byte_array_sized_new (ETH_ADDR_LEN);
g_byte_array_append (hwaddr, RTA_DATA (rta), ETH_ADDR_LEN);
break;
default:
break;
}
}
if (!hwaddr) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"no hardware address found");
return FALSE;
}
g_task_return_pointer (task, hwaddr, (GDestroyNotify) g_byte_array_unref);
return TRUE;
}
void
mm_netlink_get_hwaddr (MMNetlink *self,
guint ifindex,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
NetlinkMessage *msg;
Transaction *tr;
gssize bytes_sent;
GError *error = NULL;
task = g_task_new (self, cancellable, callback, user_data);
if (!self->socket) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"netlink support not available");
g_object_unref (task);
return;
}
msg = netlink_message_new_getlink (ifindex);
/* The task ownership is transferred to the transaction. */
tr = transaction_new (self, msg, 5, task, get_hwaddr_complete);
bytes_sent = g_socket_send (self->socket,
(const gchar *) msg->data,
msg->len,
cancellable,
&error);
netlink_message_free (msg);
if (bytes_sent < 0)
transaction_complete_with_error (tr, error);
g_object_unref (task);
}
/*****************************************************************************/
static gboolean
netlink_messages_cb (GSocket *socket,
GIOCondition condition,
MMNetlink *self)
{
g_autoptr(GError) error = NULL;
GInputVector iv;
GSocketAddress *addr = NULL;
struct sockaddr_nl source_sockaddr;
gchar buf[2048];
gssize bytes_received;
guint buffer_len;
struct nlmsghdr *hdr;
if (condition & G_IO_HUP || condition & G_IO_ERR) {
mm_obj_warn (self, "socket connection closed");
return G_SOURCE_REMOVE;
}
iv.buffer = &buf;
iv.size = sizeof (buf);
bytes_received = g_socket_receive_message (self->socket,
&addr,
&iv,
1,
NULL,
NULL,
NULL,
NULL,
&error);
if (bytes_received < 0) {
mm_obj_warn (self, "failed to read netlink message: %s", error->message);
return G_SOURCE_REMOVE;
}
if (!g_socket_address_to_native (addr,
&source_sockaddr,
sizeof (source_sockaddr),
&error)) {
mm_obj_warn (self,
"failed to read netlink message source address: %s",
error->message);
return G_SOURCE_REMOVE;
}
/* Ensure the message is from the kernel */
if (source_sockaddr.nl_pid != 0) {
mm_obj_warn (self,
"ignoring non-kernel netlink message from pid %d",
source_sockaddr.nl_pid);
return G_SOURCE_CONTINUE;
}
buffer_len = (guint) bytes_received;
for (hdr = (struct nlmsghdr *) buf; NLMSG_OK (hdr, buffer_len);
NLMSG_NEXT (hdr, buffer_len)) {
Transaction *tr;
struct nlmsgerr *err;
gint nlerr = 0;
tr = g_hash_table_lookup (self->transactions,
GUINT_TO_POINTER (hdr->nlmsg_seq));
if (!tr)
continue;
switch (hdr->nlmsg_type) {
case NLMSG_ERROR:
err = NLMSG_DATA (buf);
nlerr = err->error;
break;
case RTM_NEWLINK:
case NLMSG_DONE:
break;
default:
continue;
}
transaction_complete (tr, hdr, nlerr);
}
return G_SOURCE_CONTINUE;
}
static gboolean
setup_netlink_socket (MMNetlink *self,
GError **error)
{
gint socket_fd;
socket_fd = socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (socket_fd < 0) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Failed to create netlink socket");
return FALSE;
}
self->socket = g_socket_new_from_fd (socket_fd, error);
if (!self->socket) {
close (socket_fd);
return FALSE;
}
if (!g_socket_set_option (self->socket, SOL_SOCKET, SO_PASSCRED, TRUE, NULL)) {
int errsv = errno;
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
"Could not set netlink socket options: %s",
g_strerror (errsv));
return FALSE;
}
self->source = g_socket_create_source (self->socket,
G_IO_IN | G_IO_ERR | G_IO_HUP,
NULL);
g_source_set_callback (self->source,
(GSourceFunc) netlink_messages_cb,
self,
NULL);
g_source_attach (self->source, NULL);
return TRUE;
}
/*****************************************************************************/
static gchar *
log_object_build_id (MMLogObject *_self)
{
return g_strdup ("netlink");
}
/********************************************************************/
static void
mm_netlink_init (MMNetlink *self)
{
g_autoptr(GError) error = NULL;
if (!setup_netlink_socket (self, &error)) {
mm_obj_warn (self, "couldn't setup netlink socket: %s", error->message);
return;
}
self->current_sequence_id = 0;
self->transactions = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify) transaction_free);
}
static void
dispose (GObject *object)
{
MMNetlink *self = MM_NETLINK (object);
g_assert (g_hash_table_size (self->transactions) == 0);
g_clear_pointer (&self->transactions, g_hash_table_unref);
if (self->source)
g_source_destroy (self->source);
g_clear_pointer (&self->source, g_source_unref);
g_clear_object (&self->socket);
G_OBJECT_CLASS (mm_netlink_parent_class)->dispose (object);
}
static void
log_object_iface_init (MMLogObjectInterface *iface)
{
iface->build_id = log_object_build_id;
}
static void
mm_netlink_class_init (MMNetlinkClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = dispose;
}
MM_DEFINE_SINGLETON_GETTER (MMNetlink, mm_netlink_get, MM_TYPE_NETLINK);
/* ---------------------------------------------------------------------------------------------------- */