/*
* Copyright (C) 2018 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.
*/
#include "common/commands/wifi_relay/mac80211_hwsim.h"
#include "common/commands/wifi_relay/mac80211_hwsim_driver.h"
#include <glog/logging.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <signal.h>
#include <sys/uio.h>
#include <gflags/gflags.h>
DEFINE_string(
pcap, "", "Path to save a pcap file of packets");
static constexpr char kWifiSimFamilyName[] = "MAC80211_HWSIM";
static constexpr char kNl80211FamilyName[] = "nl80211";
static constexpr uint32_t kSignalLevelDefault = -24;
#if !defined(ETH_ALEN)
static constexpr size_t ETH_ALEN = 6;
#endif
namespace {
struct pcap_hdr_t {
uint32_t magic_number; /* magic number */
uint16_t version_major; /* major version number */
uint16_t version_minor; /* minor version number */
int32_t thiszone; /* GMT to local correction */
uint32_t sigfigs; /* accuracy of timestamps */
uint32_t snaplen; /* max length of captured packets, in octets */
uint32_t network; /* data link type */
};
struct pcaprec_hdr_t {
uint32_t ts_sec; /* timestamp seconds */
uint32_t ts_usec; /* timestamp microseconds */
uint32_t incl_len; /* number of octets of packet saved in file */
uint32_t orig_len; /* actual length of packet */
};
const pcap_hdr_t pcap_file_header{
0xa1b2c3d4,
2,
4,
0,
0,
65536,
105 // IEEE802.11 without radiotap
};
void WritePCap(const void* buffer, size_t length) {
if (FLAGS_pcap.empty()) {
return;
}
static int pcap = -1;
if (pcap == -1) {
pcap = open(FLAGS_pcap.c_str(), O_RDWR|O_CREAT|O_TRUNC, 0644);
if (pcap == -1) {
return;
}
(void)write(pcap, &pcap_file_header, sizeof(pcap_file_header));
}
size_t write_length = length;
if (write_length > pcap_file_header.snaplen) {
write_length = pcap_file_header.snaplen;
}
pcaprec_hdr_t hdr;
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
hdr.ts_sec = now.tv_sec;
hdr.ts_usec = now.tv_nsec / 1000;
hdr.incl_len = write_length;
hdr.orig_len = length;
struct iovec iov[2] { {&hdr, sizeof(hdr)},
{ const_cast<void*>(buffer), static_cast<size_t>(write_length)}};
(void)writev(pcap, iov, 2);
}
}
Mac80211HwSim::Remote::Remote(
Mac80211HwSim *parent,
vsoc::wifi::WifiExchangeView *wifiExchange)
: mParent(parent),
mWifiExchange(wifiExchange) {
mWifiWorker = mWifiExchange->StartWorker();
mThread = std::thread([this]{
std::unique_ptr<uint8_t[]> buf(
new uint8_t[Mac80211HwSim::kMessageSizeMax]);
for (;;) {
intptr_t res =
mWifiExchange->Recv(buf.get(), Mac80211HwSim::kMessageSizeMax);
if (res < 0) {
LOG(ERROR) << "WifiExchangeView::Recv failed w/ res " << res;
continue;
}
WritePCap(buf.get(), static_cast<size_t>(res));
// LOG(INFO) << "GUEST->HOST packet of size " << res;
mParent->injectFrame(buf.get(), res);
}});
}
Mac80211HwSim::Remote::~Remote() {
mDone = true;
mWifiExchange->InterruptSelf();
mThread.join();
}
intptr_t Mac80211HwSim::Remote::send(const void *data, size_t size) {
WritePCap(data, size);
return mWifiExchange->Send(data, size);
}
Mac80211HwSim::Mac80211HwSim(const MacAddress &mac)
: mMAC(mac),
mSock(nullptr, nl_socket_free) {
int res;
mSock.reset(nl_socket_alloc());
if (mSock == nullptr) {
goto bail;
}
res = nl_connect(mSock.get(), NETLINK_GENERIC);
if (res < 0) {
LOG(ERROR) << "nl_connect failed (" << nl_geterror(res) << ")";
mInitCheck = res;
goto bail;
}
nl_socket_disable_seq_check(mSock.get());
res = nl_socket_set_buffer_size(
mSock.get(), kMessageSizeMax, kMessageSizeMax);
if (res < 0) {
LOG(ERROR)
<< "nl_socket_set_buffer_size failed ("
<< nl_geterror(res)
<< ")";
mInitCheck = res;
goto bail;
}
mMac80211Family = genl_ctrl_resolve(mSock.get(), kWifiSimFamilyName);
if (mMac80211Family <= 0) {
LOG(ERROR) << "genl_ctrl_resolve failed.";
mInitCheck = -ENODEV;
goto bail;
}
mNl80211Family = genl_ctrl_resolve(mSock.get(), kNl80211FamilyName);
if (mNl80211Family <= 0) {
LOG(ERROR) << "genl_ctrl_resolve failed.";
mInitCheck = -ENODEV;
goto bail;
}
#if !defined(CUTTLEFISH_HOST)
res = registerOrSubscribe(mMAC);
if (res < 0) {
mInitCheck = res;
goto bail;
}
#endif
mInitCheck = 0;
return;
bail:
;
}
int Mac80211HwSim::initCheck() const {
return mInitCheck;
}
int Mac80211HwSim::socketFd() const {
return nl_socket_get_fd(mSock.get());
}
void Mac80211HwSim::ackFrame(nlmsghdr *inMsg) {
nlattr *attrs[__HWSIM_ATTR_MAX + 1];
int res = genlmsg_parse(
inMsg,
0 /* hdrlen */,
attrs,
__HWSIM_ATTR_MAX,
nullptr /* policy */);
if (res < 0) {
LOG(ERROR) << "genlmsg_parse failed.";
return;
}
uint32_t flags = nla_get_u32(attrs[HWSIM_ATTR_FLAGS]);
if (!(flags & HWSIM_TX_CTL_REQ_TX_STATUS)) {
LOG(VERBOSE) << "Frame doesn't require TX_STATUS.";
return;
}
flags |= HWSIM_TX_STAT_ACK;
const uint8_t *xmitterAddr =
static_cast<const uint8_t *>(
nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]));
size_t txRatesLen = nla_len(attrs[HWSIM_ATTR_TX_INFO]);
const struct hwsim_tx_rate *txRates =
static_cast<const struct hwsim_tx_rate *>(
nla_data(attrs[HWSIM_ATTR_TX_INFO]));
uint64_t cookie = nla_get_u64(attrs[HWSIM_ATTR_COOKIE]);
std::unique_ptr<nl_msg, void (*)(nl_msg *)> outMsg(
nlmsg_alloc(), nlmsg_free);
genlmsg_put(
outMsg.get(),
NL_AUTO_PID,
NL_AUTO_SEQ,
mMac80211Family,
0 /* hdrlen */,
NLM_F_REQUEST,
HWSIM_CMD_TX_INFO_FRAME,
0 /* version */);
nla_put(outMsg.get(), HWSIM_ATTR_ADDR_TRANSMITTER, ETH_ALEN, xmitterAddr);
nla_put_u32(outMsg.get(), HWSIM_ATTR_FLAGS, flags);
nla_put_u32(outMsg.get(), HWSIM_ATTR_SIGNAL, kSignalLevelDefault);
nla_put(outMsg.get(), HWSIM_ATTR_TX_INFO, txRatesLen, txRates);
nla_put_u64(outMsg.get(), HWSIM_ATTR_COOKIE, cookie);
res = nl_send_auto_complete(mSock.get(), outMsg.get());
if (res < 0) {
LOG(ERROR) << "Sending TX Info failed. (" << nl_geterror(res) << ")";
} else {
LOG(VERBOSE) << "Sending TX Info SUCCEEDED.";
}
}
void Mac80211HwSim::injectFrame(const void *data, size_t size) {
std::unique_ptr<nl_msg, void (*)(nl_msg *)> msg(nlmsg_alloc(), nlmsg_free);
genlmsg_put(
msg.get(),
NL_AUTO_PID,
NL_AUTO_SEQ,
mMac80211Family,
0 /* hdrlen */,
NLM_F_REQUEST,
HWSIM_CMD_FRAME,
0 /* version */);
CHECK_EQ(mMAC.size(), static_cast<size_t>(ETH_ALEN));
nla_put(msg.get(), HWSIM_ATTR_ADDR_RECEIVER, ETH_ALEN, &mMAC[0]);
nla_put(msg.get(), HWSIM_ATTR_FRAME, size, data);
nla_put_u32(msg.get(), HWSIM_ATTR_RX_RATE, 1);
nla_put_u32(msg.get(), HWSIM_ATTR_SIGNAL, kSignalLevelDefault);
LOG(VERBOSE) << "INJECTING!";
int res = nl_send_auto_complete(mSock.get(), msg.get());
if (res < 0) {
LOG(ERROR) << "Injection failed. (" << nl_geterror(res) << ")";
} else {
LOG(VERBOSE) << "Injection SUCCEEDED.";
}
}
void Mac80211HwSim::handlePacket() {
sockaddr_nl from;
uint8_t *data;
int len = nl_recv(mSock.get(), &from, &data, nullptr /* creds */);
if (len == 0) {
LOG(ERROR) << "nl_recv received EOF.";
return;
} else if (len < 0) {
LOG(ERROR) << "nl_recv failed (" << nl_geterror(len) << ")";
return;
}
std::unique_ptr<nlmsghdr, void (*)(nlmsghdr *)> msg(
reinterpret_cast<nlmsghdr *>(data),
[](nlmsghdr *hdr) { free(hdr); });
if (msg->nlmsg_type != mMac80211Family) {
LOG(VERBOSE)
<< "Received msg of type other than MAC80211: "
<< msg->nlmsg_type;
return;
}
#ifdef CUTTLEFISH_HOST
LOG(VERBOSE) << "------------------- Host -> Guest -----------------------";
#else
LOG(VERBOSE) << "------------------- Guest -> Host -----------------------";
#endif
genlmsghdr *hdr = genlmsg_hdr(msg.get());
if (hdr->cmd != HWSIM_CMD_FRAME) {
LOG(VERBOSE) << "cmd HWSIM_CMD_FRAME.";
return;
}
nlattr *attrs[__HWSIM_ATTR_MAX + 1];
int res = genlmsg_parse(
msg.get(),
0 /* hdrlen */,
attrs,
__HWSIM_ATTR_MAX,
nullptr /* policy */);
if (res < 0) {
LOG(ERROR) << "genlmsg_parse failed.";
return;
}
nlattr *attr = attrs[HWSIM_ATTR_FRAME];
if (!attr) {
LOG(ERROR) << "no HWSIM_ATTR_FRAME.";
return;
}
std::lock_guard<std::mutex> autoLock(mRemotesLock);
for (auto &remoteEntry : mRemotes) {
// TODO(andih): Check which remotes to forward this packet to based
// on the destination address.
remoteEntry.second->send(nla_data(attr), nla_len(attr));
}
#if !defined(CUTTLEFISH_HOST)
ackFrame(msg.get());
#endif
}
int Mac80211HwSim::registerOrSubscribe(const MacAddress &mac) {
std::unique_ptr<nl_msg, void (*)(nl_msg *)> msg(nullptr, nlmsg_free);
msg.reset(nlmsg_alloc());
genlmsg_put(
msg.get(),
NL_AUTO_PID,
NL_AUTO_SEQ,
mMac80211Family,
0,
NLM_F_REQUEST,
#ifdef CUTTLEFISH_HOST
HWSIM_CMD_SUBSCRIBE,
#else
HWSIM_CMD_REGISTER,
#endif
0);
#ifdef CUTTLEFISH_HOST
nla_put(msg.get(), HWSIM_ATTR_ADDR_RECEIVER, ETH_ALEN, &mac[0]);
#else
// HWSIM_CMD_REGISTER is a global command not specific to a MAC.
(void)mac;
#endif
int res = nl_send_auto_complete(mSock.get(), msg.get());
if (res < 0) {
LOG(ERROR)
<< "Registration/subscription failed. (" << nl_geterror(res) << ")";
return res;
}
return 0;
}
int Mac80211HwSim::addRemote(
const MacAddress &mac,
vsoc::wifi::WifiExchangeView *wifiExchange) {
#ifdef CUTTLEFISH_HOST
int res = registerOrSubscribe(mac);
if (res < 0) {
return res;
}
#endif
std::lock_guard<std::mutex> autoLock(mRemotesLock);
std::unique_ptr<Remote> remote(new Remote(this, wifiExchange));
mRemotes.insert(std::make_pair(mac, std::move(remote)));
return 0;
}
void Mac80211HwSim::removeRemote(const MacAddress &mac) {
std::lock_guard<std::mutex> autoLock(mRemotesLock);
auto it = mRemotes.find(mac);
if (it != mRemotes.end()) {
mRemotes.erase(it);
}
}