blob: 1f9409c1932d7d3b60d75280a05652951a6f947d [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This code is derived from the 'iw' source code. The copyright and license
// of that code is as follows:
//
// Copyright (c) 2007, 2008 Johannes Berg
// Copyright (c) 2007 Andy Lutomirski
// Copyright (c) 2007 Mike Kershaw
// Copyright (c) 2008-2009 Luis R. Rodriguez
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "shill/nl80211_socket.h"
#include <ctype.h>
#include <errno.h>
#include <net/if.h>
#include <netlink/attr.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <iomanip>
#include <sstream>
#include <string>
#include <base/logging.h>
#include "shill/kernel_bound_nlmessage.h"
#include "shill/logging.h"
#include "shill/netlink_socket.h"
#include "shill/scope_logger.h"
#include "shill/user_bound_nlmessage.h"
using std::string;
namespace shill {
const char Nl80211Socket::kSocketFamilyName[] = "nl80211";
bool Nl80211Socket::Init() {
if (!NetlinkSocket::Init()) {
LOG(ERROR) << "NetlinkSocket didn't initialize.";
return false;
}
nl80211_id_ = genl_ctrl_resolve(GetNlSock(), "nlctrl");
if (nl80211_id_ < 0) {
LOG(ERROR) << "nl80211 not found.";
return false;
}
LOG(INFO) << "Nl80211Socket initialized successfully";
return true;
}
bool Nl80211Socket::AddGroupMembership(const string &group_name) {
int id = GetMulticastGroupId(group_name);
if (id < 0) {
LOG(ERROR) << "No Id for group " << group_name;
return false;
} else {
int result = nl_socket_add_membership(GetNlSock(), id);
if (result != 0) {
LOG(ERROR) << "Failed call to 'nl_socket_add_membership': " << result;
return false;
}
}
LOG(INFO) << " Group " << group_name << " added successfully";
return true;
}
int Nl80211Socket::OnAck(struct nl_msg *unused_msg, void *arg) {
if (arg) {
int *ret = reinterpret_cast<int *>(arg);
*ret = 0;
}
return NL_STOP; // Stop parsing and discard remainder of buffer.
}
int Nl80211Socket::OnError(struct sockaddr_nl *unused_nla, struct nlmsgerr *err,
void *arg) {
int *ret = reinterpret_cast<int *>(arg);
if (err) {
if (ret)
*ret = err->error;
LOG(ERROR) << "Error(" << err->error << ") " << strerror(err->error);
} else {
if (ret)
*ret = -1;
LOG(ERROR) << "Error(<unknown>)";
}
return NL_STOP; // Stop parsing and discard remainder of buffer.
}
int Nl80211Socket::OnFamilyResponse(struct nl_msg *msg, void *arg) {
if (!msg) {
LOG(ERROR) << "NULL |msg| parameter";
return NL_SKIP; // Skip current message, continue parsing buffer.
}
if (!arg) {
LOG(ERROR) << "NULL |arg| parameter";
return NL_SKIP; // Skip current message, continue parsing buffer.
}
struct HandlerArgs *grp = reinterpret_cast<struct HandlerArgs *>(arg);
struct nlattr *tb[CTRL_ATTR_MAX + 1];
struct genlmsghdr *gnlh = reinterpret_cast<struct genlmsghdr *>(
nlmsg_data(nlmsg_hdr(msg)));
struct nlattr *mcgrp = NULL;
int rem_mcgrp;
nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[CTRL_ATTR_MCAST_GROUPS])
return NL_SKIP; // Skip current message, continue parsing buffer.
// nla_for_each_nested(...)
mcgrp = reinterpret_cast<nlattr *>(nla_data(tb[CTRL_ATTR_MCAST_GROUPS]));
rem_mcgrp = nla_len(tb[CTRL_ATTR_MCAST_GROUPS]);
bool found_one = false;
for (; nla_ok(mcgrp, rem_mcgrp); mcgrp = nla_next(mcgrp, &(rem_mcgrp))) {
struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
reinterpret_cast<nlattr *>(nla_data(mcgrp)), nla_len(mcgrp), NULL);
if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]) {
LOG(ERROR) << "No group name in 'group' message";
continue;
} else if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) {
LOG(ERROR) << "No group id in 'group' message";
continue;
}
if (strncmp(reinterpret_cast<char *>(
nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])),
grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) {
continue;
}
found_one = true;
grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
SLOG(WiFi, 6) << "GROUP '"
<< reinterpret_cast<char *>(
nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))
<< "' has ID "
<< grp->id;
break;
}
if (!found_one) {
LOG(ERROR) << "NO GROUP matched '"
<< grp->group
<< "', the one for which we were looking";
}
return NL_SKIP; // Skip current message, continue parsing buffer.
}
int Nl80211Socket::GetMulticastGroupId(const string &group) {
// Allocate and build the message.
KernelBoundNlMessage message;
if (!message.Init()) {
LOG(ERROR) << "Couldn't initialize message";
return -1;
}
if (!message.AddNetlinkHeader(NL_AUTO_PID, NL_AUTO_SEQ, GetFamilyId(), 0, 0,
CTRL_CMD_GETFAMILY, 0))
return -1;
int result = message.AddAttribute(CTRL_ATTR_FAMILY_NAME,
GetSocketFamilyName().length() + 1,
GetSocketFamilyName().c_str());
if (result < 0) {
LOG(ERROR) << "nla_put return error: " << result;
return -1;
}
if (!message.Send(this)) {
return -1;
}
// Wait for the response.
NetlinkSocket::Callback netlink_callback;
struct HandlerArgs grp(group.c_str(), -ENOENT);
int status = 1;
if (netlink_callback.Init()) {
if (!netlink_callback.ErrHandler(NL_CB_CUSTOM, OnError, &status)) {
return -1;
}
if (!netlink_callback.SetHandler(NL_CB_ACK, NL_CB_CUSTOM, OnAck, &status)) {
return -1;
}
if (!netlink_callback.SetHandler(NL_CB_VALID, NL_CB_CUSTOM,
OnFamilyResponse, &grp)) {
return -1;
}
} else {
LOG(ERROR) << "Couldn't initialize callback";
return -1;
}
while (status > 0) { // 'status' is set by the NL_CB_ACK handler.
if (!GetMessagesUsingCallback(&netlink_callback)) {
return -1;
}
}
if (status != 0) {
return -1;
}
return grp.id;
}
} // namespace shill