/*
* 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 "wifi_relay.h"
#include "common/commands/wifi_relay/mac80211_hwsim_driver.h"
#include "common/commands/wifi_relay/nl_client.h"
#if defined(CUTTLEFISH_HOST)
#include "host/libs/config/cuttlefish_config.h"
#endif
#include <linux/netdevice.h>
#include <linux/nl80211.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <fstream>
#if !defined(CUTTLEFISH_HOST)
DEFINE_string(
iface_name, "wlan0", "Name of the wifi interface to be created.");
#endif
WifiRelay::WifiRelay(
const Mac80211HwSim::MacAddress &localMAC,
const Mac80211HwSim::MacAddress &remoteMAC)
: mMac80211HwSim(new Mac80211HwSim(localMAC)) {
init_check_ = mMac80211HwSim->initCheck();
if (init_check_ < 0) {
return;
}
init_check_ = mMac80211HwSim->addRemote(
remoteMAC,
#if defined(CUTTLEFISH_HOST)
vsoc::wifi::WifiExchangeView::GetInstance(vsoc::GetDomain().c_str())
#else
vsoc::wifi::WifiExchangeView::GetInstance()
#endif
);
}
int WifiRelay::initCheck() const {
return init_check_;
}
void WifiRelay::run() {
for (;;) {
fd_set rs;
FD_ZERO(&rs);
FD_SET(mMac80211HwSim->socketFd(), &rs);
int maxFd = mMac80211HwSim->socketFd();
int res = select(maxFd + 1, &rs, nullptr, nullptr, nullptr);
if (res <= 0) {
continue;
}
if (FD_ISSET(mMac80211HwSim->socketFd(), &rs)) {
mMac80211HwSim->handlePacket();
}
}
}
int WifiRelay::mac80211Family() const {
return mMac80211HwSim->mac80211Family();
}
int WifiRelay::nl80211Family() const {
return mMac80211HwSim->nl80211Family();
}
int createRadio(cvd::NlClient *nl, int familyMAC80211, const char *phyName) {
cvd::Cmd msg;
genlmsg_put(
msg.Msg(),
NL_AUTO_PID,
NL_AUTO_SEQ,
familyMAC80211,
0,
NLM_F_REQUEST,
HWSIM_CMD_NEW_RADIO,
cvd::kWifiSimVersion);
nla_put_string(msg.Msg(), HWSIM_ATTR_RADIO_NAME, phyName);
nla_put_flag(msg.Msg(), HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE);
nl->Send(&msg);
// Responses() pauses until netlink responds to previously sent message.
for (auto *r : msg.Responses()) {
auto hdr = nlmsg_hdr(r);
if (hdr->nlmsg_type == NLMSG_ERROR) {
nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
return err->error;
}
}
return -1;
}
int getPhyIndex(const std::string &phyName) {
std::ifstream file("/sys/class/ieee80211/" + phyName + "/index");
int number;
file >> number;
return number;
}
int getInterfaceIndex(cvd::NlClient *nl, int familyNL80211, uint32_t phyIndex) {
cvd::Cmd msg;
genlmsg_put(
msg.Msg(),
NL_AUTO_PID,
NL_AUTO_SEQ,
familyNL80211,
0,
NLM_F_REQUEST | NLM_F_DUMP,
NL80211_CMD_GET_INTERFACE,
0);
nl->Send(&msg);
// Responses() pauses until netlink responds to previously sent message.
for (auto *r : msg.Responses()) {
auto hdr = nlmsg_hdr(r);
if (hdr->nlmsg_type == NLMSG_ERROR) {
nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
return err->error;
}
// Last message in entire series.
if (hdr->nlmsg_type == NLMSG_DONE) {
break;
}
// !DONE && !ERROR => content.
// Decode attributes supplied by netlink.
// the genlmsg_parse puts each attribute in a respective slot in an array,
// so we have to preallocate enough space.
struct nlattr* attrs[NL80211_ATTR_MAX + 1];
auto err = genlmsg_parse(hdr, 0, attrs, NL80211_ATTR_MAX, nullptr);
// Return error if response could not be parsed. This is actually quite
// serious.
if (err < 0) {
LOG(ERROR) << "Could not process netlink response: " << strerror(-err);
return err;
}
// Check if we have WIPHY attribute in response -- and if it's the relevant
// one.
auto wiphy = attrs[NL80211_ATTR_WIPHY];
if (wiphy != nullptr && nla_get_u32(wiphy) == phyIndex) {
auto number = attrs[NL80211_ATTR_IFINDEX];
if (number != nullptr) {
return nla_get_u32(number);
}
}
}
return -1;
}
int updateInterface(
cvd::NlClient *nlRoute,
int ifaceIndex,
const std::string &name,
const uint8_t *mac) {
cvd::Cmd msg;
ifinfomsg ifm{};
ifm.ifi_index = ifaceIndex;
nlmsg_put(
msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, RTM_SETLINK, 0, NLM_F_REQUEST);
nlmsg_append(msg.Msg(), &ifm, sizeof(ifm), 0);
nla_put_string(msg.Msg(), IFLA_IFNAME, name.c_str());
std::vector<uint8_t> macCopy(MAX_ADDR_LEN);
memcpy(&macCopy[0], mac, ETH_ALEN);
nla_put(msg.Msg(), IFLA_ADDRESS, MAX_ADDR_LEN, &macCopy[0]);
nlRoute->Send(&msg);
// Responses() pauses until netlink responds to previously sent message.
for (auto *r : msg.Responses()) {
auto hdr = nlmsg_hdr(r);
LOG(VERBOSE) << "got response of type " << hdr->nlmsg_type;
if (hdr->nlmsg_type == NLMSG_ERROR) {
nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
if (err->error < 0) {
LOG(ERROR) << "updateInterface failed w/ " << err->error
<< " (" << strerror(-err->error) << ")";
}
return err->error;
}
}
LOG(VERBOSE) << "No more responses";
return -1;
}
int main(int argc, char **argv) {
::android::base::InitLogging(argv, android::base::StderrLogger);
gflags::ParseCommandLineFlags(&argc, &argv, true);
auto wifi_view = vsoc::wifi::WifiExchangeView::GetInstance(
#if defined(CUTTLEFISH_HOST)
vsoc::GetDomain().c_str()
#endif
);
Mac80211HwSim::MacAddress guestMAC = wifi_view->GetGuestMACAddress();
Mac80211HwSim::MacAddress hostMAC = wifi_view->GetHostMACAddress();
#ifdef CUTTLEFISH_HOST
WifiRelay relay(hostMAC, guestMAC);
#else
WifiRelay relay(guestMAC, hostMAC);
#endif
int res = relay.initCheck();
if (res < 0) {
LOG(ERROR)
<< "WifiRelay::initCheck() returned error "
<< res
<< " ("
<< strerror(-res)
<< ")";
exit(1);
}
#if !defined(CUTTLEFISH_HOST)
cvd::NlClient client(NETLINK_GENERIC);
if (!client.Init()) {
LOG(ERROR) << "Could not open Netlink Generic.";
exit(1);
}
cvd::NlClient nlRoute(NETLINK_ROUTE);
if (!nlRoute.Init()) {
LOG(ERROR) << "Could not open Netlink Route.";
exit(1);
}
std::thread([&client, &nlRoute] {
for (;;) {
fd_set rs;
FD_ZERO(&rs);
int fdGeneric = nl_socket_get_fd(client.Sock());
int fdRoute = nl_socket_get_fd(nlRoute.Sock());
FD_SET(fdGeneric, &rs);
FD_SET(fdRoute, &rs);
int maxFd = std::max(fdGeneric, fdRoute);
int res = select(maxFd + 1, &rs, nullptr, nullptr, nullptr);
if (res == 0) {
continue;
} else if (res < 0) {
continue;
}
if (FD_ISSET(fdGeneric, &rs)) {
nl_recvmsgs_default(client.Sock());
}
if (FD_ISSET(fdRoute, &rs)) {
nl_recvmsgs_default(nlRoute.Sock());
}
}
}).detach();
const std::string phyName = FLAGS_iface_name + "_phy";
if (createRadio(&client, relay.mac80211Family(), phyName.c_str()) < 0) {
LOG(ERROR) << "Could not create radio.";
exit(1);
}
int phyIndex = getPhyIndex(phyName);
CHECK_GE(phyIndex, 0);
LOG(VERBOSE) << "Got PHY index " << phyIndex;
int ifaceIndex = getInterfaceIndex(
&client, relay.nl80211Family(), static_cast<uint32_t>(phyIndex));
CHECK_GE(ifaceIndex, 0);
LOG(VERBOSE) << "Got interface index " << ifaceIndex;
if (updateInterface(
&nlRoute, ifaceIndex, FLAGS_iface_name, &guestMAC[0]) < 0) {
LOG(ERROR) << "Failed to update interface.";
exit(1);
}
#endif // !defined(CUTTLEFISH_HOST)
relay.run();
return 0;
}