WIFIRouter coop for Cuttlefish Wifi

Change-Id: I58afda9f9ec80ba91c84f4a9dadefa2df386d100
diff --git a/common/libs/wifi/Android.bp b/common/libs/wifi/Android.bp
index a70531e..f6e68ef 100644
--- a/common/libs/wifi/Android.bp
+++ b/common/libs/wifi/Android.bp
@@ -1,4 +1,4 @@
-cc_library {
+cc_library_static {
     name: "libcuttlefish_wifi",
     host_supported: true,
 
@@ -7,7 +7,7 @@
         "nl_client.cc",
         "cmd.cc",
         "virtual_wifi.cc",
-        "virtual_wifi_manager.cc",
+        "wr_client.cc",
     ],
     static_libs: [
         "libbase",
diff --git a/common/libs/wifi/cmd.cc b/common/libs/wifi/cmd.cc
index 762e7fb..1a153cc 100644
--- a/common/libs/wifi/cmd.cc
+++ b/common/libs/wifi/cmd.cc
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "host/commands/wifid/cmd.h"
+#include "common/libs/wifi/cmd.h"
 
-namespace avd {
+namespace cvd {
 
 Cmd::Cmd() : msg_(nlmsg_alloc()) {}
 
@@ -60,4 +60,4 @@
   return responses_;
 }
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/cmd.h b/common/libs/wifi/cmd.h
index c4bc57b..f5d9da0 100644
--- a/common/libs/wifi/cmd.h
+++ b/common/libs/wifi/cmd.h
@@ -22,7 +22,7 @@
 
 #include <netlink/msg.h>
 
-namespace avd {
+namespace cvd {
 constexpr int kWifiSimVersion = 1;
 
 class Cmd {
@@ -55,4 +55,4 @@
   Cmd& operator=(const Cmd&) = delete;
 };
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/netlink.cc b/common/libs/wifi/netlink.cc
index b1fd7de..143a585 100644
--- a/common/libs/wifi/netlink.cc
+++ b/common/libs/wifi/netlink.cc
@@ -13,19 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "host/commands/wifid/netlink.h"
+#include "common/libs/wifi/netlink.h"
 
 #include <glog/logging.h>
 #include <netlink/genl/ctrl.h>
 #include <netlink/genl/family.h>
 
-namespace avd {
+namespace cvd {
 namespace {
 constexpr char kWifiSimFamilyName[] = "MAC80211_HWSIM";
 constexpr char kNl80211FamilyName[] = "nl80211";
 }  // namespace
 
-Netlink::Netlink() : genl_(NETLINK_GENERIC), rtnl_(NETLINK_ROUTE) {}
+Netlink::Netlink(const std::string& wifirouter_socket)
+    : genl_(NETLINK_GENERIC), rtnl_(NETLINK_ROUTE), wrcl_(wifirouter_socket) {}
 
 bool Netlink::Init() {
   if (!genl_.Init()) {
@@ -38,6 +39,11 @@
     return false;
   }
 
+  if (!wrcl_.Init()) {
+    LOG(ERROR) << "Could not connect to Wifi Router.";
+    return false;
+  }
+
   // Start the thread processing asynchronous netlink responses.
   netlink_thread_.reset(new std::thread([this]() { HandleNetlinkMessages(); }));
 
@@ -68,19 +74,22 @@
   fd_set nlfds;
   int genl_fd = nl_socket_get_fd(GeNL().Sock());
   int rtnl_fd = nl_socket_get_fd(RtNL().Sock());
-  int max_fd = std::max(genl_fd, rtnl_fd) + 1;
+  int wrcl_fd = wrcl_.Sock();
 
+  int max_fd = std::max({genl_fd, rtnl_fd, wrcl_fd}) + 1;
   while (true) {
     FD_ZERO(&nlfds);
     FD_SET(genl_fd, &nlfds);
     FD_SET(rtnl_fd, &nlfds);
+    FD_SET(wrcl_fd, &nlfds);
 
     int res = select(max_fd, &nlfds, nullptr, nullptr, nullptr);
     if (res <= 0) continue;
 
     if (FD_ISSET(genl_fd, &nlfds)) nl_recvmsgs_default(GeNL().Sock());
     if (FD_ISSET(rtnl_fd, &nlfds)) nl_recvmsgs_default(RtNL().Sock());
+    if (FD_ISSET(wrcl_fd, &nlfds)) wrcl_.HandleResponses();
   }
 }
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/netlink.h b/common/libs/wifi/netlink.h
index 7d8ac8c..20b13c1 100644
--- a/common/libs/wifi/netlink.h
+++ b/common/libs/wifi/netlink.h
@@ -19,13 +19,14 @@
 #include <thread>
 
 #include <netlink/genl/genl.h>
-#include "host/commands/wifid/nl_client.h"
+#include "common/libs/wifi/nl_client.h"
+#include "common/libs/wifi/wr_client.h"
 
-namespace avd {
+namespace cvd {
 // Netlink provides access to relevant netlink backends and resources.
 class Netlink {
  public:
-  Netlink();
+  Netlink(const std::string& wifirouter_socket);
   ~Netlink() = default;
 
   // Initialize instance of Netlink Factory.
@@ -37,6 +38,8 @@
   // Getter for NETLINK_ROUTE NlClient instance.
   NlClient& RtNL() { return rtnl_; }
 
+  WRClient& WRCL() { return wrcl_; }
+
   // Access Family ID for MAC80211 (WIFI Simulator).
   int FamilyMAC80211() const { return mac80211_hwsim_family_; }
 
@@ -51,8 +54,10 @@
 
   NlClient genl_;
   NlClient rtnl_;
+  WRClient wrcl_;
 
   int mac80211_hwsim_family_ = 0;
+  int router_family_ = 0;
   int nl80211_family_ = 0;
 
   std::unique_ptr<std::thread> netlink_thread_;
@@ -61,4 +66,4 @@
   Netlink& operator=(const Netlink&) = delete;
 };
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/nl_client.cc b/common/libs/wifi/nl_client.cc
index 436631b..5588e3d 100644
--- a/common/libs/wifi/nl_client.cc
+++ b/common/libs/wifi/nl_client.cc
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "host/commands/wifid/nl_client.h"
+#include "common/libs/wifi/nl_client.h"
 
 #include <glog/logging.h>
 
-namespace avd {
+namespace cvd {
 
 NlClient::NlClient(int nl_type)
     : nl_type_(nl_type),
@@ -92,4 +92,4 @@
 
 nl_sock* NlClient::Sock() const { return sock_.get(); }
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/nl_client.h b/common/libs/wifi/nl_client.h
index 556f404..2b5a57f 100644
--- a/common/libs/wifi/nl_client.h
+++ b/common/libs/wifi/nl_client.h
@@ -22,9 +22,9 @@
 
 #include <netlink/genl/genl.h>
 
-#include "host/commands/wifid/cmd.h"
+#include "common/libs/wifi/cmd.h"
 
-namespace avd {
+namespace cvd {
 
 class NlClient {
  public:
@@ -63,4 +63,4 @@
   NlClient& operator=(const NlClient&) = delete;
 };
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/router.h b/common/libs/wifi/router.h
new file mode 100644
index 0000000..9a5ab93
--- /dev/null
+++ b/common/libs/wifi/router.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+namespace cvd {
+// Commands recognized by WIFIRouter netlink family.
+enum {
+  // WIFIROUTER_CMD_REGISTER is used by client to request notifications for
+  // packets sent from an interface with specific MAC address. Recognized
+  // attributes:
+  // - WIFIROUTER_ATTR_MAC - MAC address (byte array) of interface to receive
+  //   notifications for.
+  WIFIROUTER_CMD_REGISTER,
+
+  // WIFIROUTER_CMD_NOTIFY is issued by the server to notify clients for every
+  // new WIFIROUTER packet the client is registered for. Comes with attributes:
+  // - WIFIROUTER_ATTR_MAC - MAC address of interface that received packet,
+  // - WIFIROUTER_ATTR_PACKET - content of the MAC80211_HWSIM packet.
+  WIFIROUTER_CMD_NOTIFY,
+};
+
+// Attributes recognized by WIFIRouter netlink family.
+enum {
+  // Don't use attribute 0 to avoid parsing malformed message.
+  WIFIROUTER_ATTR_UNSPEC,
+
+  // MAC address representing interface from which the packet originated.
+  WIFIROUTER_ATTR_MAC,
+
+  // MAC80211_HWSIM packet content.
+  WIFIROUTER_ATTR_PACKET,
+
+  // Keep this last.
+  WIFIROUTER_ATTR_MAX
+};
+
+}  // namespace cvd
+
diff --git a/common/libs/wifi/virtual_wifi.cc b/common/libs/wifi/virtual_wifi.cc
index 29f532f..be827bf 100644
--- a/common/libs/wifi/virtual_wifi.cc
+++ b/common/libs/wifi/virtual_wifi.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "host/commands/wifid/virtual_wifi.h"
+#include "common/libs/wifi/virtual_wifi.h"
 
 #include <fstream>
 
@@ -22,10 +22,11 @@
 #include <linux/nl80211.h>
 #include <netlink/genl/ctrl.h>
 
-#include "host/commands/wifid/cmd.h"
-#include "host/commands/wifid/mac80211.h"
+#include "common/libs/wifi/cmd.h"
+#include "common/libs/wifi/mac80211.h"
+#include "common/libs/wifi/router.h"
 
-namespace avd {
+namespace cvd {
 namespace {
 // Create new HWSIM Radio.
 // Returns newly created HWSIM radio number, or negative errno code.
@@ -185,6 +186,34 @@
   LOG(ERROR) << "Unknown or no response from netlink.";
   return -1;
 }
+
+bool RegisterForRouterNotifications(Netlink* nl, uint8_t* mac_addr) {
+  Cmd msg;
+
+  if (!genlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, 0, 0,
+                   NLM_F_REQUEST, WIFIROUTER_CMD_REGISTER, 0) ||
+      nla_put(msg.Msg(), WIFIROUTER_ATTR_MAC, MAX_ADDR_LEN, mac_addr)) {
+    LOG(ERROR) << "Could not create wifirouter register message.";
+    return false;
+  }
+
+  nl->WRCL().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));
+      LOG_IF(ERROR, err->error < 0) << "Failed to register with wifi router: "
+                                    << strerror(err->error);
+      return err->error == 0;
+    }
+  }
+
+  LOG(ERROR) << "Unknown or no response from wifi router.";
+  return -1;
+}
+
 }  // namespace
 
 VirtualWIFI::~VirtualWIFI() {
@@ -198,9 +227,13 @@
 }
 
 bool VirtualWIFI::Init() {
-  if (sscanf(addr_.c_str(), "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
+  // Dummy variable is used with sscanf to determine mac address is well formed
+  // (that is: there's no trailing string content).
+  char dummy;
+
+  if (sscanf(addr_.c_str(), "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx%c",
              &mac_addr_[0], &mac_addr_[1], &mac_addr_[2], &mac_addr_[3],
-             &mac_addr_[4], &mac_addr_[5]) != 6) {
+             &mac_addr_[4], &mac_addr_[5], &dummy) != 6) {
     LOG(ERROR) << "Malformed MAC address: " << addr_;
     return false;
   }
@@ -213,6 +246,7 @@
   // interface properties. Each radio can have more than one WLAN.
 
   // 1. Create new MAC80211 HWSIM radio.
+  LOG(INFO) << "Creating virtual radio: " << phy;
   hwsim_number_ = CreateHWSIM(nl_, phy);
   if (hwsim_number_ <= 0) {
     LOG(ERROR) << "Could not create HWSIM: " << strerror(-hwsim_number_);
@@ -220,6 +254,7 @@
   }
 
   // 2. Acquire the WIPHY radio number created with HWSIM radio.
+  LOG(INFO) << "Querying WIPHY number for: " << phy;
   wiphy_number_ = GetWIPHYIndex(phy);
   if (wiphy_number_ <= 0) {
     LOG(ERROR) << "Could not create WIPHY.";
@@ -227,6 +262,7 @@
   }
 
   // 3. Query interface index.
+  LOG(INFO) << "Querying WIFI number for: " << wiphy_number_;
   iface_number_ = GetWiphyInterface(nl_, wiphy_number_);
   if (iface_number_ <= 0) {
     LOG(ERROR) << "Could not query interface details.";
@@ -234,11 +270,20 @@
   }
 
   // 4. Apply requested interface name.
+  LOG(INFO) << "Updating interface name to: " << name_;
   if (!SetWLANInterface(nl_, iface_number_, name_, &mac_addr_[0])) {
+    LOG(ERROR) << "Could not update wlan interface name.";
+    return false;
+  }
+
+  // 5. Register with wifi router.
+  LOG(INFO) << "Registering for notifications for: " << addr_;
+  if (!RegisterForRouterNotifications(nl_, mac_addr_)) {
+    LOG(ERROR) << "Could not register with wifi router.";
     return false;
   }
 
   return true;
 }
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/virtual_wifi.h b/common/libs/wifi/virtual_wifi.h
index 6791875..fac70e0 100644
--- a/common/libs/wifi/virtual_wifi.h
+++ b/common/libs/wifi/virtual_wifi.h
@@ -21,9 +21,9 @@
 #include <netinet/in.h>
 #include <linux/netdevice.h>
 
-#include "host/commands/wifid/netlink.h"
+#include "common/libs/wifi/netlink.h"
 
-namespace avd {
+namespace cvd {
 
 // VirtualWIFI is an abstraction of an (individual) virtual WLAN device.
 // A virtual WLAN is a composition of the three elements:
@@ -77,4 +77,4 @@
   VirtualWIFI& operator=(const VirtualWIFI&) = delete;
 };
 
-}  // namespace avd
+}  // namespace cvd
diff --git a/common/libs/wifi/virtual_wifi_manager.cc b/common/libs/wifi/virtual_wifi_manager.cc
deleted file mode 100644
index 457a907..0000000
--- a/common/libs/wifi/virtual_wifi_manager.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2017 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 "host/commands/wifid/virtual_wifi_manager.h"
-
-#include <glog/logging.h>
-
-#include "host/commands/wifid/cmd.h"
-#include "host/commands/wifid/mac80211.h"
-
-namespace avd {
-namespace {
-// We don't care about byte ordering as much as we do about having all bytes
-// there. Byte order does not match, we want to use it as a key in our map.
-// Note: we accept const void here, because we will also process data coming
-// from netlink (which is untyped).
-uint64_t MACToKey(const void* macaddr) {
-  auto typed = reinterpret_cast<const uint16_t*>(macaddr);
-  return (1ull * typed[0] << 32) | (typed[1] << 16) | typed[2];
-}
-}  // namespace
-
-// Register for asynchronous notifications from MAC80211.
-// Our callback will receive data for each next frame transmitted over any
-// radio.
-bool VirtualWIFIManager::RegisterForSimulatorNotifications() {
-  Cmd msg;
-
-  if (!genlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, nl_->FamilyMAC80211(),
-                   0, NLM_F_REQUEST, HWSIM_CMD_REGISTER, kWifiSimVersion)) {
-    LOG(ERROR) << "Could not create nlmsg registration request.";
-    return false;
-  }
-
-  nl_->GeNL().Send(&msg);
-
-  for (auto* r : msg.Responses()) {
-    auto hdr = nlmsg_hdr(r);
-    if (hdr->nlmsg_type == NLMSG_ERROR) {
-      nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
-      LOG_IF(ERROR, err->error < 0)
-          << "Could not register for VirtualWIFIManager notifications: "
-          << strerror(err->error);
-      return err->error == 0;
-    }
-  }
-
-  LOG(ERROR) << "No response from netlink.";
-  return false;
-}
-
-bool VirtualWIFIManager::Init() {
-  nl_->GeNL().SetDefaultHandler([this](nl_msg* m) { HandleNlResponse(m); });
-  return RegisterForSimulatorNotifications();
-}
-
-VirtualWIFIManager::~VirtualWIFIManager() {
-  // Reset handler.
-  nl_->GeNL().SetDefaultHandler(std::function<void(nl_msg*)>());
-}
-
-void VirtualWIFIManager::HandleNlResponse(nl_msg* m) {
-  auto hdr = nlmsg_hdr(m);
-  auto gen = static_cast<genlmsghdr*>(nlmsg_data(hdr));
-
-  // Ignore Generic Netlink messages coming from other sources.
-  if (hdr->nlmsg_type != nl_->FamilyMAC80211()) return;
-  // Ignore Generic Netlink messages that don't contain MAC80211 frames.
-  if (gen->cmd != HWSIM_CMD_FRAME) return;
-
-  struct nlattr* attrs[HWSIM_ATTR_MAX + 1];
-  if (genlmsg_parse(hdr, 0, attrs, HWSIM_ATTR_MAX, nullptr)) return;
-
-  // Get virtual wlan key from mac address.
-  auto mac = attrs[HWSIM_ATTR_ADDR_TRANSMITTER];
-  if (!mac) return;
-  auto key = MACToKey(nla_data(mac));
-
-  // Redirect packet to VirtualWIFI, if that's indeed one of ours.
-  // Sadly, we don't have any other way of telling.
-  std::shared_ptr<VirtualWIFI> wifi;
-  {
-    std::lock_guard<std::mutex> lock(radios_mutex_);
-    auto radio = radios_.find(key);
-    if (radio == radios_.end()) return;
-    wifi = radio->second.lock();
-  }
-
-  LOG(INFO) << "Found packet from " << wifi->Name();
-}
-
-// Create new MAC80211_HWSIM radio.
-// This can be called after Init completes.
-std::shared_ptr<VirtualWIFI> VirtualWIFIManager::CreateRadio(
-    const std::string& name, const std::string& address) {
-  std::shared_ptr<VirtualWIFI> wifi(new VirtualWIFI(nl_, name, address));
-
-  if (!wifi->Init()) {
-    wifi.reset();
-    return wifi;
-  }
-
-  std::lock_guard<std::mutex> lock(radios_mutex_);
-  radios_[MACToKey(wifi->MacAddr())] = wifi;
-  return wifi;
-}
-
-}  // namespace avd
diff --git a/common/libs/wifi/virtual_wifi_manager.h b/common/libs/wifi/virtual_wifi_manager.h
deleted file mode 100644
index 99d61d1..0000000
--- a/common/libs/wifi/virtual_wifi_manager.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-#pragma once
-
-#include <map>
-#include <memory>
-#include <mutex>
-
-#include "host/commands/wifid/netlink.h"
-#include "host/commands/wifid/virtual_wifi.h"
-
-namespace avd {
-class VirtualWIFIManager {
- public:
-  VirtualWIFIManager(Netlink* nl) : nl_(nl){};
-  ~VirtualWIFIManager();
-
-  // Initialize VirtualWIFI Manager instance.
-  bool Init();
-
-  // Create new VirtualWIFI instance with the specified name.
-  std::shared_ptr<VirtualWIFI> CreateRadio(const std::string& name,
-                                           const std::string& address);
-
- private:
-  // Enables asynchronous notifications from MAC80211 about recently sent wifi
-  // packets.
-  bool RegisterForSimulatorNotifications();
-
-  // Handle asynchronous netlink frame.
-  // Netlink does not differentiate between frame types so this callback will
-  // receive all Generic Netlink frames that do not have a proper recipient.
-  void HandleNlResponse(nl_msg* m);
-
-  Netlink* const nl_;
-
-  // Map VirtualWIFI's MAC address to VirtualWIFI instance.
-  std::map<uint64_t, std::weak_ptr<VirtualWIFI>> radios_;
-  std::mutex radios_mutex_;
-
-  VirtualWIFIManager(const VirtualWIFIManager&) = delete;
-  VirtualWIFIManager& operator=(const VirtualWIFIManager&) = delete;
-};
-
-}  // namespace avd
diff --git a/common/libs/wifi/wr_client.cc b/common/libs/wifi/wr_client.cc
new file mode 100644
index 0000000..65fd170
--- /dev/null
+++ b/common/libs/wifi/wr_client.cc
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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/libs/wifi/wr_client.h"
+
+#include <glog/logging.h>
+
+namespace cvd {
+namespace {
+const int kMaxSupportedPacketSize = getpagesize();
+}  // namespace
+WRClient::WRClient(const std::string& address) : address_(address) {}
+
+bool WRClient::Init() {
+  // Sadly, we can't use SharedFD, because we need access to raw file
+  // descriptor.
+
+  struct sockaddr_un addr {};
+  addr.sun_family = AF_UNIX;
+  memcpy(addr.sun_path + 1, address_.c_str(), address_.size());
+  socklen_t len = offsetof(struct sockaddr_un, sun_path) + address_.size() + 1;
+  socket_ = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+  if (socket_ < 0) {
+    LOG(ERROR) << "socket() failed: " << strerror(errno);
+    return false;
+  }
+
+  auto res = connect(socket_, reinterpret_cast<sockaddr*>(&addr), len);
+  if (res < 0) {
+    LOG(ERROR) << "Could not connect to wifi router: " << strerror(errno);
+    return false;
+  }
+
+  return true;
+}
+
+void WRClient::Send(Cmd* msg) {
+  std::lock_guard<std::mutex> guard(in_flight_mutex_);
+  // Make sure to execute this while in critical section to ensure we have time
+  // to set up seq number & callback before we receive response.
+  auto hdr = nlmsg_hdr(msg->Msg());
+  int seq = in_flight_last_seq_++;
+  // Do not use 0 for sequence numbers. 0 is reserved for async notifications.
+  if (!in_flight_last_seq_) in_flight_last_seq_ = 1;
+
+  hdr->nlmsg_seq = seq;
+  send(socket_, hdr, hdr->nlmsg_len, MSG_NOSIGNAL);
+  in_flight_[seq] = msg;
+}
+
+// Handle asynchronous messages & responses from netlink.
+void WRClient::HandleResponses() {
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[kMaxSupportedPacketSize]);
+
+  auto size = recv(socket_, buf.get(), kMaxSupportedPacketSize, 0);
+  if (size <= 0) {
+    LOG(FATAL) << "No data from WIFI Router - likely dead: " << strerror(errno);
+    return;
+  }
+
+  auto hdr = reinterpret_cast<nlmsghdr*>(buf.get());
+  if (size != hdr->nlmsg_len) {
+    LOG(FATAL) << "Malformed message from WIFI Router.";
+    return;
+  }
+
+  int seq = hdr->nlmsg_seq;
+  std::unique_ptr<nl_msg, void (*)(nl_msg*)> nlmsg(
+      nlmsg_convert(hdr), [](nl_msg* m) { nlmsg_free(m); });
+
+  // Find & invoke corresponding callback, if any.
+  std::lock_guard<std::mutex> guard(in_flight_mutex_);
+  auto pos = in_flight_.find(seq);
+  if (pos != in_flight_.end()) {
+    if (pos->second->OnResponse(nlmsg.get())) {
+      // Erase command if reports it's done.
+      in_flight_.erase(seq);
+    }
+  } else if (default_handler_) {
+    default_handler_(nlmsg.get());
+  }
+}
+
+void WRClient::SetDefaultHandler(std::function<void(nl_msg*)> cb) {
+  std::lock_guard<std::mutex> guard(in_flight_mutex_);
+  default_handler_ = std::move(cb);
+}
+
+int WRClient::Sock() const { return socket_; }
+
+}  // namespace cvd
diff --git a/common/libs/wifi/wr_client.h b/common/libs/wifi/wr_client.h
new file mode 100644
index 0000000..71a6438
--- /dev/null
+++ b/common/libs/wifi/wr_client.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+
+#include <netlink/msg.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/wifi/cmd.h"
+
+namespace cvd {
+
+class WRClient {
+ public:
+  WRClient(const std::string& socket_address);
+  ~WRClient() = default;
+
+  // Init this client: open socket to wifi router.
+  bool Init();
+
+  // Get wifirouter socket used for sending and receiving messages.
+  int Sock() const;
+
+  // Send message to wifi router.
+  void Send(Cmd* msg);
+
+  // Handle incoming responses from wifi router.
+  void HandleResponses();
+
+  // Set callback receiving all asynchronous messages and responses that do not
+  // have any proper recipient.
+  void SetDefaultHandler(std::function<void(nl_msg*)> cb);
+
+ private:
+  // Receive & dispatch netlink response.
+
+  std::string address_;
+  int socket_ = 0;
+  std::mutex in_flight_mutex_;
+  // Do not use 0 as a sequence number. 0 is reserved for asynchronous
+  // notifications.
+  int in_flight_last_seq_ = 1;
+  std::map<uint32_t, Cmd*> in_flight_;
+  std::function<void(nl_msg*)> default_handler_;
+
+  WRClient(const WRClient&) = delete;
+  WRClient& operator=(const WRClient&) = delete;
+};
+
+}  // namespace cvd