|  | /* | 
|  | * Driver interaction with Linux nl80211/cfg80211 | 
|  | * Copyright (c) 2002-2015, Jouni Malinen <[email protected]> | 
|  | * Copyright (c) 2003-2004, Instant802 Networks, Inc. | 
|  | * Copyright (c) 2005-2006, Devicescape Software, Inc. | 
|  | * Copyright (c) 2007, Johannes Berg <[email protected]> | 
|  | * Copyright (c) 2009-2010, Atheros Communications | 
|  | * Copyright (c) 2017, 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 | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <netlink/genl/family.h> | 
|  | #include <netlink/genl/ctrl.h> | 
|  | #include <linux/pkt_sched.h> | 
|  | #include <unistd.h> | 
|  | #include <log/log.h> | 
|  | #include "cld80211_lib.h" | 
|  |  | 
|  | #undef LOG_TAG | 
|  | #define LOG_TAG "CLD80211" | 
|  | #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(struct cld80211_ctx *ctx) | 
|  | { | 
|  | 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, bool sync_driver) | 
|  | { | 
|  | 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"); | 
|  |  | 
|  | if (sync_driver == true) { | 
|  | 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(struct cld80211_ctx *ctx, const char* mcgroup) | 
|  | { | 
|  | if (!ctx || !mcgroup) { | 
|  | ALOGE("%s: ctx/mcgroup is NULL: %s", getprogname(), __func__); | 
|  | return 0; | 
|  | } | 
|  | int id = get_multicast_id(ctx, mcgroup, true); | 
|  | 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(struct cld80211_ctx *ctx, const char* mcgroup) | 
|  | { | 
|  | if (!ctx || !mcgroup) { | 
|  | ALOGE("%s: ctx/mcgroup is NULL: %s", getprogname(), __func__); | 
|  | return 0; | 
|  | } | 
|  | int id = get_multicast_id(ctx, mcgroup, false); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | struct nl_msg *cld80211_msg_alloc(struct cld80211_ctx *ctx, int cmd, | 
|  | struct nlattr **nla_data, int pid) | 
|  | { | 
|  | struct nl_msg *nlmsg; | 
|  |  | 
|  | 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(struct cld80211_ctx *ctx, struct nl_msg *nlmsg) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | 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(struct cld80211_ctx *ctx, struct nl_msg *nlmsg, | 
|  | int (*valid_handler)(struct nl_msg *, void *), | 
|  | void *valid_data) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | 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(struct cld80211_ctx *ctx, 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; | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } while (1); | 
|  |  | 
|  | nl_cb_put(cb); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | struct cld80211_ctx * cld80211_init() | 
|  | { | 
|  | 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(struct cld80211_ctx *ctx) | 
|  | { | 
|  | if (!ctx || !ctx->sock) { | 
|  | ALOGE("%s: ctx/sock is NULL", getprogname()); | 
|  | return; | 
|  | } | 
|  | nl_socket_free(ctx->sock); | 
|  | cleanup_exit_sockets(ctx); | 
|  | free (ctx); | 
|  | } |