You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
12 KiB
512 lines
12 KiB
/* |
|
* 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, 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); |
|
}
|
|
|