/* * 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; }