Merge request !1268 from 'netlink-getlink'

netlink: add RTM_GETLINK support to read MAC address

https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1268
This commit is contained in:
Dan Williams
2025-02-19 00:42:33 +00:00
4 changed files with 268 additions and 22 deletions
+198 -22
View File
@@ -15,8 +15,10 @@
* 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>
@@ -160,6 +162,12 @@ netlink_message_new_setlink (guint ifindex,
return msg;
}
static NetlinkMessage *
netlink_message_new_getlink (guint ifindex)
{
return netlink_message_new (ifindex, RTM_GETLINK);
}
static void
netlink_message_free (NetlinkMessage *msg)
{
@@ -169,11 +177,14 @@ netlink_message_free (NetlinkMessage *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
@@ -212,8 +223,9 @@ transaction_complete_with_error (Transaction *tr,
}
static void
transaction_complete (Transaction *tr,
gint saved_errno)
transaction_complete (Transaction *tr,
struct nlmsghdr *hdr,
gint saved_errno)
{
GTask *task;
guint32 sequence_id;
@@ -225,7 +237,10 @@ transaction_complete (Transaction *tr,
GUINT_TO_POINTER (tr->sequence_id));
if (!saved_errno) {
g_task_return_boolean (task, TRUE);
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",
@@ -248,7 +263,8 @@ static Transaction *
transaction_new (MMNetlink *self,
NetlinkMessage *msg,
guint timeout,
GTask *task)
GTask *task,
MsgFunc completion_fn)
{
Transaction *tr;
@@ -266,6 +282,7 @@ transaction_new (MMNetlink *self,
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),
@@ -283,6 +300,14 @@ mm_netlink_setlink_finish (MMNetlink *self,
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,
@@ -310,7 +335,110 @@ mm_netlink_setlink (MMNetlink *self,
msg = netlink_message_new_setlink (ifindex, up, mtu);
/* The task ownership is transferred to the transaction. */
tr = transaction_new (self, msg, 5, task);
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,
@@ -328,43 +456,83 @@ mm_netlink_setlink (MMNetlink *self,
/*****************************************************************************/
static gboolean
netlink_message_cb (GSocket *socket,
GIOCondition condition,
MMNetlink *self)
netlink_messages_cb (GSocket *socket,
GIOCondition condition,
MMNetlink *self)
{
g_autoptr(GError) error = NULL;
gchar buf[512];
gssize bytes_received;
guint buffer_len;
struct nlmsghdr *hdr;
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;
}
bytes_received = g_socket_receive (socket, buf, sizeof (buf), NULL, &error);
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, "socket i/o failure: %s", error->message);
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;
if (hdr->nlmsg_type != NLMSG_ERROR)
continue;
gint nlerr = 0;
tr = g_hash_table_lookup (self->transactions,
GUINT_TO_POINTER (hdr->nlmsg_seq));
if (!tr)
continue;
err = NLMSG_DATA (buf);
transaction_complete (tr, err->error);
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;
}
@@ -375,7 +543,7 @@ setup_netlink_socket (MMNetlink *self,
{
gint socket_fd;
socket_fd = socket (AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
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");
@@ -388,11 +556,19 @@ setup_netlink_socket (MMNetlink *self,
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_message_cb,
(GSourceFunc) netlink_messages_cb,
self,
NULL);
g_source_attach (self->source, NULL);
+9
View File
@@ -49,6 +49,15 @@ gboolean mm_netlink_setlink_finish (MMNetlink *self,
GAsyncResult *res,
GError **error);
void mm_netlink_get_hwaddr (MMNetlink *self,
guint ifindex,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GByteArray *mm_netlink_get_hwaddr_finish (MMNetlink *self,
GAsyncResult *res,
GError **error);
G_END_DECLS
#endif /* MM_MODEM_HELPERS_NETLINK_H */
+53
View File
@@ -97,6 +97,59 @@ mm_port_net_link_setup (MMPortNet *self,
/*****************************************************************************/
GByteArray *
mm_port_net_get_hwaddress_finish (MMPortNet *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
netlink_get_hwaddress_ready (MMNetlink *netlink,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
GByteArray *hwaddr;
hwaddr = mm_netlink_get_hwaddr_finish (netlink, res, &error);
if (!hwaddr) {
g_prefix_error (&error, "netlink operation failed: ");
g_task_return_error (task, error);
} else
g_task_return_pointer (task, hwaddr, (GDestroyNotify) g_byte_array_unref);
g_object_unref (task);
}
void
mm_port_net_get_hwaddress (MMPortNet *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
ensure_ifindex (self);
if (!self->priv->ifindex) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"no valid interface index found for %s",
mm_port_get_device (MM_PORT (self)));
g_object_unref (task);
return;
}
mm_netlink_get_hwaddr (mm_netlink_get (), /* singleton */
self->priv->ifindex,
cancellable,
(GAsyncReadyCallback) netlink_get_hwaddress_ready,
task);
}
/*****************************************************************************/
MMPortNet *
mm_port_net_new (const gchar *name)
{
+8
View File
@@ -60,4 +60,12 @@ gboolean mm_port_net_link_setup_finish (MMPortNet *self,
GAsyncResult *res,
GError **error);
void mm_port_net_get_hwaddress (MMPortNet *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GByteArray *mm_port_net_get_hwaddress_finish (MMPortNet *self,
GAsyncResult *res,
GError **error);
#endif /* MM_PORT_NET_H */