blob: 4def24df2e36c2e5573161e2ada49c1a238b4a08 [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "BinderNetlink"
// #define LOG_NDEBUG 0
#include <android-base/stringprintf.h>
#include <binder/BinderNetlink.h>
#include <errno.h>
#include <linux/android/binderfs.h>
#include <log/log.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/mngt.h>
#include <netlink/socket.h>
#include <string.h>
#include <unistd.h>
#if __has_include(<linux/android/binder_netlink.h>)
#include <linux/android/binder_netlink.h>
#else
// Copy of binder netlink header until include/uapi/linux/*.h are up to date
#ifndef _UAPI_LINUX_ANDROID_BINDER_NETLINK_H
#define _UAPI_LINUX_ANDROID_BINDER_NETLINK_H
#define BINDER_FAMILY_NAME "binder"
#define BINDER_FAMILY_VERSION 1
enum {
BINDER_A_REPORT_ERROR = 1,
BINDER_A_REPORT_CONTEXT,
BINDER_A_REPORT_FROM_PID,
BINDER_A_REPORT_FROM_TID,
BINDER_A_REPORT_TO_PID,
BINDER_A_REPORT_TO_TID,
BINDER_A_REPORT_IS_REPLY,
BINDER_A_REPORT_FLAGS,
BINDER_A_REPORT_CODE,
BINDER_A_REPORT_DATA_SIZE,
__BINDER_A_REPORT_MAX,
BINDER_A_REPORT_MAX = (__BINDER_A_REPORT_MAX - 1)
};
enum {
BINDER_CMD_REPORT = 1,
__BINDER_CMD_MAX,
BINDER_CMD_MAX = (__BINDER_CMD_MAX - 1)
};
#define BINDER_MCGRP_REPORT "report"
#endif /* _UAPI_LINUX_ANDROID_BINDER_NETLINK_H */
#endif // __has_include(<linux/android/binder_netlink.h>)
namespace android {
namespace bindernetlink {
// Control verbose logging.
static constexpr bool DEBUG = false;
// The number of context bytes to copy with strncpy().
static constexpr size_t COPY_LEN =
std::min(sizeof(Report::context) - 1, static_cast<size_t>(BINDERFS_MAX_NAME));
std::string Report::toString() const {
return android::base::StringPrintf("%s %u 0x%08x %u:%u -> %u:%u %u 0x08%x %u %u", context,
error, error, fromPid, fromTid, toPid, toTid, isReply, flags,
code, dataSize);
}
BinderNetlink::BinderNetlink()
: mMcSock(nullptr, nl_socket_free), mId(0), mGroup(0), mStatistics({0, 0}) {}
int BinderNetlink::open() {
int ret;
std::unique_ptr<nl_sock, decltype(&nl_socket_free)> mcsock(nl_socket_alloc(), nl_socket_free);
if (!mcsock.get()) {
ALOGE("Failed to allocate binder netlink event socket");
return -1;
}
ret = genl_connect(mcsock.get());
if (ret < 0) {
ALOGE("Failed to open binder netlink event socket: %s", nl_geterror(ret));
return -1;
}
nl_socket_disable_seq_check(mcsock.get());
ret = genl_ctrl_resolve(mcsock.get(), BINDER_FAMILY_NAME);
if (ret < 0) {
ALOGW("Failed to get binder netlink family id: %s", nl_geterror(ret));
// errno is not set by the above failure, so set it now.
if (ret == -NLE_OBJ_NOTFOUND) errno = EOPNOTSUPP;
return -1;
}
mId = ret;
ret = genl_ctrl_resolve_grp(mcsock.get(), BINDER_FAMILY_NAME, BINDER_MCGRP_REPORT);
if (ret < 0) {
ALOGW("Failed to get binder netlink multicast group: %s", nl_geterror(ret));
return -1;
}
mGroup = ret;
ret = nl_socket_add_membership(mcsock.get(), mGroup);
if (ret < 0) {
ALOGW("Failed to join multicast group: %s", nl_geterror(ret));
return -1;
}
// Configure the callback. Messages will appear in mReport.
ret = nl_socket_modify_cb(mcsock.get(), NL_CB_VALID, NL_CB_CUSTOM, callback, this);
if (ret < 0) {
ALOGW("Failed to set multicast callback: %s", nl_geterror(ret));
return -1;
}
mMcSock = std::move(mcsock);
ALOGD("Binder Netlink id=%d group=%d", mId, mGroup);
return 0;
}
void BinderNetlink::close() {
mMcSock.reset();
}
int BinderNetlink::getReport(struct Report* report) {
if (!mMcSock) {
ALOGE("Called on a closed socket");
errno = EBADF;
return -1;
}
if (!report) {
ALOGE("Valid Report required");
errno = EFAULT;
return -1;
}
// Only one thread is allowed to fetch a report at a time.
std::lock_guard<std::mutex> _lock(mReportMutex);
if (int ret = nl_recvmsgs_default(mMcSock.get()); ret < 0) {
ALOGW("Failed to receive messages: %s", nl_geterror(ret));
return -1;
}
*report = mReport;
return 0;
}
int BinderNetlink::setTimeout(int seconds) {
if (seconds < 0) seconds = 0;
struct timeval tv = {.tv_sec = seconds, .tv_usec = 0};
int fd = nl_socket_get_fd(mMcSock.get());
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
ALOGW("Failed so set timeout to %d: %s", seconds, strerror(errno));
return -1;
}
return 0;
}
Statistics BinderNetlink::getStatistics() {
// Serialize with respect to readReport(), which updates statistics.
std::lock_guard<std::mutex> _lock(mStatisticsMutex);
return mStatistics;
}
int BinderNetlink::callback(struct nl_msg* msg, void* arg) {
if (!arg) {
ALOGW("Invalid netlink callback arg");
return NL_SKIP;
}
return static_cast<BinderNetlink*>(arg)->readReport(msg);
}
int BinderNetlink::readReport(struct nl_msg* msg) {
struct nlmsghdr* nlh = nlmsg_hdr(msg);
struct genlmsghdr* glh = static_cast<struct genlmsghdr*>(nlmsg_data(nlh));
nlattr* nla = genlmsg_attrdata(glh, 0);
int rem = genlmsg_attrlen(glh, 0);
ALOGV_IF(DEBUG, "msg=%p arg=%p", msg, arg);
ALOGV_IF(DEBUG, "nlh: pid=%d seq=%d, type=%d, len=%d", nlh->nlmsg_pid, nlh->nlmsg_seq,
nlh->nlmsg_type, nlh->nlmsg_len);
ALOGV_IF(DEBUG, "glh: cmd=%d, version=%d", glh->cmd, glh->version);
ALOGV_IF(DEBUG, "nla: type=%d len=%d / %d", nla->nla_type, nla->nla_len, rem);
// Serialize with respect to getStatistics(), which reads statistics.
std::lock_guard<std::mutex> _lock(mStatisticsMutex);
switch (glh->cmd) {
case BINDER_CMD_REPORT: {
// Zero the report buffer.
mReport = {{0, 0}};
nla_for_each_attr(nla, nla, rem, rem) {
const int type = nla_type(nla);
const uint32_t val = nla_get_u32(nla);
switch (type) {
case BINDER_A_REPORT_CONTEXT:
// Copy the context from the message. The destination has been zeroed
// and the copy length is at least one byte less than the destination,
// so the result is always null terminated.
strncpy(mReport.context, nla_get_string(nla), COPY_LEN);
break;
case BINDER_A_REPORT_IS_REPLY:
mReport.isReply = true;
break;
case BINDER_A_REPORT_ERROR:
mReport.error = val;
break;
case BINDER_A_REPORT_FROM_PID:
mReport.fromPid = val;
break;
case BINDER_A_REPORT_FROM_TID:
mReport.fromTid = val;
break;
case BINDER_A_REPORT_TO_PID:
mReport.toPid = val;
break;
case BINDER_A_REPORT_TO_TID:
mReport.toTid = val;
break;
case BINDER_A_REPORT_FLAGS:
mReport.flags = val;
break;
case BINDER_A_REPORT_CODE:
mReport.code = val;
break;
case BINDER_A_REPORT_DATA_SIZE:
mReport.dataSize = val;
break;
default:
ALOGW("Unknown report attribute: %d", type);
mStatistics.mUnknownAttribute++;
break;
}
}
break;
}
default:
ALOGV("Unknown report cmd: %d", glh->cmd);
mStatistics.mUnknownCommand++;
return NL_SKIP;
}
return NL_OK;
}
} // namespace bindernetlink
} // namespace android