| /* |
| * Driver interaction with Linux nl80211/cfg80211 |
| * Copyright (c) 2002-2015, Jouni Malinen <j@w1.fi> |
| * Copyright (c) 2003-2004, Instant802 Networks, Inc. |
| * Copyright (c) 2005-2006, Devicescape Software, Inc. |
| * Copyright (c) 2007, Johannes Berg <johannes@sipsolutions.net> |
| * Copyright (c) 2009-2010, Atheros Communications |
| * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE |
| * |
| * Changes from Qualcomm Innovation Center are provided under the following license: |
| * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. |
| * SPDX-License-Identifier: BSD-3-Clause-Clear |
| */ |
| |
| #include <errno.h> |
| #include <netlink/genl/family.h> |
| #include <netlink/genl/ctrl.h> |
| #include <linux/pkt_sched.h> |
| #include <unistd.h> |
| #include "cld80211_lib.h" |
| |
| #ifndef LE_BUILD |
| #include <log/log.h> |
| #undef LOG_TAG |
| #define LOG_TAG "CLD80211" |
| #else |
| #include <stdlib.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| #define ALOGI(fmt, args...) syslog(LOG_INFO, fmt, ## args) |
| #define ALOGE(fmt, args...) syslog(LOG_ERR, fmt, ## args) |
| extern const char *__progname; |
| const char *getprogname() { return (__progname); } |
| #endif |
| |
| #define SOCK_BUF_SIZE (256*1024) |
| |
| struct family_data { |
| const char *group; |
| int id; |
| }; |
| |
| |
| static struct nl_sock * create_nl_socket(int protocol) |
| { |
| struct nl_sock *sock; |
| |
| sock = nl_socket_alloc(); |
| if (sock == NULL) { |
| ALOGE("%s: Failed to create NL socket, err: %d", |
| getprogname(), errno); |
| return NULL; |
| } |
| |
| if (nl_connect(sock, protocol)) { |
| ALOGE("%s: Could not connect sock, err: %d", |
| getprogname(), errno); |
| nl_socket_free(sock); |
| return NULL; |
| } |
| |
| return sock; |
| } |
| |
| |
| static int init_exit_sockets(struct cld80211_ctx *ctx) |
| { |
| ctx->exit_sockets[0] = -1; |
| ctx->exit_sockets[1] = -1; |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->exit_sockets[0]) == -1) { |
| ALOGE("%s: Failed to create exit socket pair", getprogname()); |
| return -1; |
| } |
| ALOGI("%s: initialized exit socket pair", getprogname()); |
| |
| return 0; |
| } |
| |
| |
| static void cleanup_exit_sockets(struct cld80211_ctx *ctx) |
| { |
| if (ctx->exit_sockets[0] >= 0) { |
| close(ctx->exit_sockets[0]); |
| ctx->exit_sockets[0] = -1; |
| } |
| |
| if (ctx->exit_sockets[1] >= 0) { |
| close(ctx->exit_sockets[1]); |
| ctx->exit_sockets[1] = -1; |
| } |
| } |
| |
| |
| void exit_cld80211_recv(void *cldctx) |
| { |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx) { |
| ALOGE("%s: ctx is NULL: %s", getprogname(), __func__); |
| return; |
| } |
| TEMP_FAILURE_RETRY(write(ctx->exit_sockets[0], "E", 1)); |
| ALOGI("%s: Sent msg on exit sock to unblock poll()", getprogname()); |
| } |
| |
| |
| /* Event handlers */ |
| static int response_handler(struct nl_msg *msg, void *arg) |
| { |
| UNUSED(msg); |
| UNUSED(arg); |
| ALOGI("%s: Received nlmsg response: no callback registered;drop it", |
| getprogname()); |
| |
| return NL_SKIP; |
| } |
| |
| |
| static int ack_handler(struct nl_msg *msg, void *arg) |
| { |
| int *err = (int *)arg; |
| *err = 0; |
| UNUSED(msg); |
| return NL_STOP; |
| } |
| |
| |
| static int finish_handler(struct nl_msg *msg, void *arg) |
| { |
| int *ret = (int *)arg; |
| *ret = 0; |
| UNUSED(msg); |
| return NL_SKIP; |
| } |
| |
| |
| static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, |
| void *arg) |
| { |
| int *ret = (int *)arg; |
| *ret = err->error; |
| |
| UNUSED(nla); |
| ALOGE("%s: error_handler received : %d", getprogname(), err->error); |
| return NL_SKIP; |
| } |
| |
| |
| static int no_seq_check(struct nl_msg *msg, void *arg) |
| { |
| UNUSED(msg); |
| UNUSED(arg); |
| return NL_OK; |
| } |
| |
| |
| int cld80211_recv_msg(struct nl_sock *sock, struct nl_cb *cb) |
| { |
| if (!sock || !cb) { |
| ALOGE("%s: %s is NULL", getprogname(), sock?"cb":"sock"); |
| return -EINVAL; |
| } |
| |
| int res = nl_recvmsgs(sock, cb); |
| if(res) |
| ALOGE("%s: Error :%d while reading nl msg , err: %d", |
| getprogname(), res, errno); |
| return res; |
| } |
| |
| |
| static void cld80211_handle_event(int events, struct nl_sock *sock, |
| struct nl_cb *cb) |
| { |
| if (events & POLLERR) { |
| ALOGE("%s: Error reading from socket", getprogname()); |
| cld80211_recv_msg(sock, cb); |
| } else if (events & POLLHUP) { |
| ALOGE("%s: Remote side hung up", getprogname()); |
| } else if (events & POLLIN) { |
| cld80211_recv_msg(sock, cb); |
| } else { |
| ALOGE("%s: Unknown event - %0x", getprogname(), events); |
| } |
| } |
| |
| |
| static int family_handler(struct nl_msg *msg, void *arg) |
| { |
| struct family_data *res = arg; |
| struct nlattr *tb[CTRL_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *mcgrp; |
| int i; |
| |
| nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| if (!tb[CTRL_ATTR_MCAST_GROUPS]) |
| return NL_SKIP; |
| |
| nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], i) { |
| struct nlattr *tb2[CTRL_ATTR_MCAST_GRP_MAX + 1]; |
| nla_parse(tb2, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), |
| nla_len(mcgrp), NULL); |
| |
| if (!tb2[CTRL_ATTR_MCAST_GRP_NAME] || |
| !tb2[CTRL_ATTR_MCAST_GRP_ID] || |
| strncmp(nla_data(tb2[CTRL_ATTR_MCAST_GRP_NAME]), |
| res->group, |
| nla_len(tb2[CTRL_ATTR_MCAST_GRP_NAME])) != 0) |
| continue; |
| res->id = nla_get_u32(tb2[CTRL_ATTR_MCAST_GRP_ID]); |
| break; |
| }; |
| |
| return NL_SKIP; |
| } |
| |
| |
| static int get_multicast_id(struct cld80211_ctx *ctx, const char *group) |
| { |
| struct family_data res = { group, -ENOENT }; |
| struct nl_msg *nlmsg = nlmsg_alloc(); |
| |
| if (!nlmsg) { |
| return -1; |
| } |
| |
| genlmsg_put(nlmsg, 0, 0, ctx->nlctrl_familyid, 0, 0, |
| CTRL_CMD_GETFAMILY, 0); |
| nla_put_string(nlmsg, CTRL_ATTR_FAMILY_NAME, "cld80211"); |
| |
| cld80211_send_recv_msg(ctx, nlmsg, family_handler, &res); |
| ALOGI("%s: nlctrl family id: %d group: %s mcast_id: %d", getprogname(), |
| ctx->nlctrl_familyid, group, res.id); |
| nlmsg_free(nlmsg); |
| return res.id; |
| } |
| |
| |
| int cld80211_add_mcast_group(void *cldctx, const char* mcgroup) |
| { |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !mcgroup) { |
| ALOGE("%s: ctx/mcgroup is NULL: %s", getprogname(), __func__); |
| return 0; |
| } |
| int id = get_multicast_id(ctx, mcgroup); |
| if (id < 0) { |
| ALOGE("%s: Could not find group %s, errno: %d id: %d", |
| getprogname(), mcgroup, errno, id); |
| return id; |
| } |
| |
| int ret = nl_socket_add_membership(ctx->sock, id); |
| if (ret < 0) { |
| ALOGE("%s: Could not add membership to group %s, errno: %d", |
| getprogname(), mcgroup, errno); |
| } |
| |
| return ret; |
| } |
| |
| |
| int cld80211_remove_mcast_group(void *cldctx, const char* mcgroup) |
| { |
| // Drop membership is not a necessary cleanup action so comment it out. |
| #if 0 |
| if (!ctx || !mcgroup) { |
| ALOGE("%s: ctx/mcgroup is NULL: %s", getprogname(), __func__); |
| return 0; |
| } |
| int id = get_multicast_id(ctx, mcgroup); |
| if (id < 0) { |
| ALOGE("%s: Could not find group %s, errno: %d id: %d", |
| getprogname(), mcgroup, errno, id); |
| return id; |
| } |
| |
| int ret = nl_socket_drop_membership(ctx->sock, id); |
| if (ret < 0) { |
| ALOGE("%s: Could not drop membership from group %s, errno: %d," |
| " ret: %d", getprogname(), mcgroup, errno, ret); |
| return ret; |
| } |
| #endif |
| return 0; |
| } |
| |
| |
| struct nl_msg *cld80211_msg_alloc(void *cldctx, int cmd, struct nlattr **nla_data, |
| int pid) |
| { |
| struct nl_msg *nlmsg; |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !nla_data) { |
| ALOGE("%s: ctx is null: %s", getprogname(), __func__); |
| return NULL; |
| } |
| |
| nlmsg = nlmsg_alloc(); |
| if (nlmsg == NULL) { |
| ALOGE("%s: Out of memory", getprogname()); |
| return NULL; |
| } |
| |
| genlmsg_put(nlmsg, pid, /* seq = */ 0, ctx->netlink_familyid, |
| 0, 0, cmd, /* version = */ 0); |
| |
| *nla_data = nla_nest_start(nlmsg, CLD80211_ATTR_VENDOR_DATA); |
| if (!*nla_data) |
| goto cleanup; |
| |
| return nlmsg; |
| |
| cleanup: |
| if (nlmsg) |
| nlmsg_free(nlmsg); |
| return NULL; |
| } |
| |
| |
| int cld80211_send_msg(void *cldctx, struct nl_msg *nlmsg) |
| { |
| int err; |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock || !nlmsg) { |
| ALOGE("%s: Invalid data from client", getprogname()); |
| return -EINVAL; |
| } |
| |
| err = nl_send_auto_complete(ctx->sock, nlmsg); /* send message */ |
| if (err < 0) { |
| ALOGE("%s: failed to send msg: %d", getprogname(), err); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| |
| int cld80211_send_recv_msg(void *cldctx, struct nl_msg *nlmsg, |
| int (*valid_handler)(struct nl_msg *, void *), |
| void *valid_data) |
| { |
| int err; |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock || !nlmsg) { |
| ALOGE("%s: Invalid data from client", getprogname()); |
| return -EINVAL; |
| } |
| |
| struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!cb) |
| return -ENOMEM; |
| |
| err = nl_send_auto_complete(ctx->sock, nlmsg); /* send message */ |
| if (err < 0) |
| goto out; |
| |
| err = 1; |
| |
| nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); |
| nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); |
| nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); |
| nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); |
| |
| if (valid_handler) |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, |
| valid_handler, valid_data); |
| else |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, |
| response_handler, valid_data); |
| |
| while (err > 0) { /* wait for reply */ |
| int res = nl_recvmsgs(ctx->sock, cb); |
| if (res) { |
| ALOGE("%s: cld80211: nl_recvmsgs failed: %d", |
| getprogname(), res); |
| } |
| } |
| out: |
| nl_cb_put(cb); |
| return err; |
| } |
| |
| |
| int cld80211_recv(void *cldctx, int timeout, bool recv_multi_msg, |
| int (*valid_handler)(struct nl_msg *, void *), |
| void *cbctx) |
| { |
| struct pollfd pfd[2]; |
| struct nl_cb *cb; |
| int err; |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock || !valid_handler) { |
| ALOGE("%s: Invalid data from client", getprogname()); |
| return -EINVAL; |
| } |
| |
| cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!cb) |
| return -ENOMEM; |
| |
| memset(&pfd[0], 0, 2*sizeof(struct pollfd)); |
| |
| err = 1; |
| |
| nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); |
| nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); |
| nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); |
| nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, cbctx); |
| |
| pfd[0].fd = nl_socket_get_fd(ctx->sock); |
| pfd[0].events = POLLIN; |
| |
| pfd[1].fd = ctx->exit_sockets[1]; |
| pfd[1].events = POLLIN; |
| |
| do { |
| pfd[0].revents = 0; |
| pfd[1].revents = 0; |
| int result = poll(pfd, 2, timeout); |
| if (result < 0) { |
| ALOGE("%s: Error polling socket", getprogname()); |
| } else if (pfd[0].revents & (POLLIN | POLLHUP | POLLERR)) { |
| cld80211_handle_event(pfd[0].revents, ctx->sock, cb); |
| if (!recv_multi_msg) |
| break; |
| } else { |
| ALOGI("%s: Exiting poll", getprogname()); |
| break; |
| } |
| if (ctx->is_terminating) { |
| ALOGI("Exiting poll as program:%s is terminating", |
| getprogname()); |
| break; |
| } |
| } while (1); |
| |
| nl_cb_put(cb); |
| return 0; |
| } |
| |
| |
| struct cld80211_ctx * cld80211_init(void) |
| { |
| struct cld80211_ctx *ctx; |
| |
| ctx = (struct cld80211_ctx *)malloc(sizeof(struct cld80211_ctx)); |
| if (ctx == NULL) { |
| ALOGE("%s: Failed to alloc cld80211_ctx", getprogname()); |
| return NULL; |
| } |
| memset(ctx, 0, sizeof(struct cld80211_ctx)); |
| |
| ctx->sock = create_nl_socket(NETLINK_GENERIC); |
| if (ctx->sock == NULL) { |
| ALOGE("%s: Failed to create socket port", getprogname()); |
| goto cleanup; |
| } |
| |
| /* Set the socket buffer size */ |
| if (nl_socket_set_buffer_size(ctx->sock, SOCK_BUF_SIZE , 0) < 0) { |
| ALOGE("%s: Could not set nl_socket RX buffer size for sock: %s", |
| getprogname(), strerror(errno)); |
| /* continue anyway with the default (smaller) buffer */ |
| } |
| |
| ctx->netlink_familyid = genl_ctrl_resolve(ctx->sock, "cld80211"); |
| if (ctx->netlink_familyid < 0) { |
| ALOGE("%s: Could not resolve cld80211 familty id", |
| getprogname()); |
| goto cleanup; |
| } |
| |
| ctx->nlctrl_familyid = genl_ctrl_resolve(ctx->sock, "nlctrl"); |
| if (ctx->nlctrl_familyid < 0) { |
| ALOGE("%s: net link family nlctrl is not present: %d err:%d", |
| getprogname(), ctx->nlctrl_familyid, errno); |
| goto cleanup; |
| } |
| |
| |
| if (init_exit_sockets(ctx) != 0) { |
| ALOGE("%s: Failed to initialize exit sockets", getprogname()); |
| goto cleanup; |
| } |
| |
| return ctx; |
| cleanup: |
| if (ctx->sock) { |
| nl_socket_free(ctx->sock); |
| } |
| free (ctx); |
| return NULL; |
| } |
| |
| |
| void cld80211_deinit(void *cldctx) |
| { |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock) { |
| ALOGE("%s: ctx/sock is NULL", getprogname()); |
| return; |
| } |
| nl_socket_free(ctx->sock); |
| cleanup_exit_sockets(ctx); |
| free (ctx); |
| } |
| |
| void cld80211_stop_recv(void *cldctx, bool is_terminating) |
| { |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock) { |
| ALOGE("%s: ctx/sock is NULL", getprogname()); |
| return; |
| } |
| ALOGE("%s: Program is terminating:%d", getprogname(), is_terminating); |
| ctx->is_terminating = is_terminating; |
| } |
| |
| struct nl_sock *cld80211_get_nl_socket_ctx(void *cldctx) |
| { |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock) { |
| ALOGE("%s: ctx/sock is NULL", getprogname()); |
| return NULL; |
| } |
| return ctx->sock; |
| } |
| |
| int *cld80211_get_exit_socket_pair(void *cldctx) |
| { |
| struct cld80211_ctx *ctx = (struct cld80211_ctx *) cldctx; |
| |
| if (!ctx || !ctx->sock) { |
| ALOGE("%s: ctx/sock is NULL", getprogname()); |
| return NULL; |
| } |
| return ctx->exit_sockets; |
| } |