Cuttlefish Wifi: Packet switcher

Change-Id: I960a16d0ab13cd2fbfcdfdb148bb696ce379f474
diff --git a/common/commands/wificlient/Android.bp b/common/commands/wificlient/Android.bp
index 067d085..cc2e268 100644
--- a/common/commands/wificlient/Android.bp
+++ b/common/commands/wificlient/Android.bp
@@ -5,7 +5,9 @@
     ],
     shared_libs: [
         "libbase",
-        "libcuttlefish_wifi",
+        "vsoc_lib",
+        "libcuttlefish_fs",
+        "cuttlefish_auto_resources",
         "liblog",
         "libnl",
     ],
@@ -15,5 +17,18 @@
     header_libs: [
         "cuttlefish_glog",
     ],
-    defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"],
+    target: {
+        linux: {
+            static_libs: [
+                "libcuttlefish_wifi",
+                "libcuttlefish_host_config",
+            ],
+        },
+        android: {
+            static_libs: [
+                "libcuttlefish_wifi",
+            ],
+        }
+    },
+    defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"]
 }
diff --git a/common/commands/wificlient/main.cc b/common/commands/wificlient/main.cc
index e060408..1b6736b 100644
--- a/common/commands/wificlient/main.cc
+++ b/common/commands/wificlient/main.cc
@@ -18,6 +18,7 @@
 #include <glog/logging.h>
 
 #include "common/libs/wifi/netlink.h"
+#include "common/libs/wifi/packet_switch.h"
 #include "common/libs/wifi/virtual_wifi.h"
 
 DEFINE_string(router, "cvd-wifirouter", "Path to WIFI Router Unix socket.");
@@ -35,6 +36,12 @@
     exit(1);
   }
 
+  cvd::PacketSwitch pktswitch(nl.get());
+  if (!pktswitch.Init()) {
+    LOG(ERROR) << "Could not initialize packet switch.";
+    exit(1);
+  }
+
   std::unique_ptr<cvd::VirtualWIFI> radio(
       new cvd::VirtualWIFI(nl.get(), FLAGS_iface, FLAGS_macaddr));
   if (!radio->Init()) {
@@ -42,5 +49,7 @@
     exit(1);
   }
 
+  pktswitch.Start();
+
   pause();
 }
diff --git a/common/libs/wifi/Android.bp b/common/libs/wifi/Android.bp
index 0ce06f1..6a6e846 100644
--- a/common/libs/wifi/Android.bp
+++ b/common/libs/wifi/Android.bp
@@ -1,10 +1,11 @@
-cc_library_shared {
+cc_library_static {
     name: "libcuttlefish_wifi",
 
     srcs: [
+        "cmd.cc",
         "netlink.cc",
         "nl_client.cc",
-        "cmd.cc",
+        "packet_switch.cc",
         "virtual_wifi.cc",
         "wr_client.cc",
     ],
@@ -16,5 +17,13 @@
     header_libs: [
         "cuttlefish_glog",
     ],
-    defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"],
+    target: {
+        linux: {
+            static_libs: [
+                "libcuttlefish_host_config",
+                "libgflags",
+            ],
+        }
+    },
+    defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"]
 }
diff --git a/common/libs/wifi/cmd.cc b/common/libs/wifi/cmd.cc
index 1a153cc..2576359 100644
--- a/common/libs/wifi/cmd.cc
+++ b/common/libs/wifi/cmd.cc
@@ -19,6 +19,8 @@
 
 Cmd::Cmd() : msg_(nlmsg_alloc()) {}
 
+Cmd::Cmd(nlmsghdr* h) : msg_(nlmsg_convert(h)) {}
+
 Cmd::~Cmd() {
   for (auto& msg : responses_) {
     nlmsg_free(msg);
diff --git a/common/libs/wifi/cmd.h b/common/libs/wifi/cmd.h
index f5d9da0..f9ff509 100644
--- a/common/libs/wifi/cmd.h
+++ b/common/libs/wifi/cmd.h
@@ -28,6 +28,7 @@
 class Cmd {
  public:
   Cmd();
+  explicit Cmd(nlmsghdr* h);
   ~Cmd();
 
   // Cmd() creates netlink request to be sent to kernel.
diff --git a/common/libs/wifi/packet_switch.cc b/common/libs/wifi/packet_switch.cc
new file mode 100644
index 0000000..5dc58fe
--- /dev/null
+++ b/common/libs/wifi/packet_switch.cc
@@ -0,0 +1,108 @@
+/*
+ * 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/packet_switch.h"
+#include "common/libs/wifi/router.h"
+
+#ifdef CUTTLEFISH_HOST
+#include "host/libs/config/host_config.h"
+#endif
+
+namespace cvd {
+
+PacketSwitch::~PacketSwitch() { Stop(); }
+
+bool PacketSwitch::Init() {
+#ifdef CUTTLEFISH_HOST
+  return shm_wifi_.Open(vsoc::GetDomain().c_str());
+#else
+  return shm_wifi_.Open();
+#endif
+}
+
+void PacketSwitch::Start() {
+  std::lock_guard<std::mutex> l(op_mutex_);
+  if (started_) return;
+  // set started to true immediately; this attribute is referenced by threads to
+  // know whether they should terminate.
+  started_ = true;
+
+  nl_->WRCL().SetDefaultHandler(
+      [this](nl_msg* m) { ProcessPacket(m, false); });
+
+  shm_xchg_.reset(new std::thread([this] {
+      size_t maxlen = getpagesize();
+      std::unique_ptr<uint8_t[]> msg(new uint8_t[maxlen]);
+      while (started_) {
+        // TODO(ender): how to trigger (periodic?) exit from this call?
+        auto len = shm_wifi_.Recv(msg.get(), maxlen);
+        std::unique_ptr<nl_msg, void (*)(nl_msg*)> nlm(
+            nlmsg_convert(reinterpret_cast<nlmsghdr*>(msg.get())), nlmsg_free);
+        ProcessPacket(nlm.get(), true);
+      }
+    }));
+}
+
+void PacketSwitch::Stop() {
+  std::lock_guard<std::mutex> l(op_mutex_);
+  if (!started_) return;
+  started_ = false;
+  nl_->WRCL().SetDefaultHandler(std::function<void(nl_msg*)>());
+
+  shm_xchg_->join();
+  shm_xchg_.reset();
+}
+
+void PacketSwitch::ProcessPacket(nl_msg* m, bool is_incoming) {
+  auto header = nlmsg_hdr(m);
+  auto genhdr = reinterpret_cast<genlmsghdr*>(nlmsg_data(header));
+
+  if (genhdr->cmd == WIFIROUTER_CMD_NOTIFY) {
+    // This attribute is mandatory: it contains MAC80211_HWSIM frame.
+    auto packet =
+        nlmsg_find_attr(header, sizeof(*genhdr), WIFIROUTER_ATTR_PACKET);
+    if (!packet) return;
+
+    // If origin is not local (= not set from local WIFI), then forward it to
+    // local WIFI.
+    if (is_incoming) {
+      // Need to update MAC80211_HWSIM WIFI family before injecting packet.
+      // Different kernels may have different family numbers allocated.
+      auto frame = reinterpret_cast<nlmsghdr*>(nla_data(packet));
+      frame->nlmsg_type = nl_->FamilyMAC80211();
+      frame->nlmsg_pid = 0;
+      frame->nlmsg_seq = 0;
+      frame->nlmsg_flags = NLM_F_REQUEST;
+      Cmd local(frame);
+
+      nl_->GeNL().Send(&local);
+
+      for (auto* r : local.Responses()) {
+        auto hdr = nlmsg_hdr(r);
+        if (hdr->nlmsg_type == NLMSG_ERROR) {
+          nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
+          if (err->error < 0) {
+            LOG(ERROR) << "Could not send WIFI message: "
+                       << strerror(-err->error);
+          }
+        }
+      }
+    } else {
+      shm_wifi_.Send(&header, header->nlmsg_len);
+    }
+  }
+}
+
+}  // namespace cvd
diff --git a/common/libs/wifi/packet_switch.h b/common/libs/wifi/packet_switch.h
new file mode 100644
index 0000000..c20e681
--- /dev/null
+++ b/common/libs/wifi/packet_switch.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <memory>
+#include <set>
+
+#include "common/libs/wifi/netlink.h"
+#include "common/libs/wifi/virtual_wifi.h"
+#include "common/libs/wifi/wr_client.h"
+#include "common/vsoc/lib/wifi_exchange_view.h"
+
+namespace cvd {
+
+class PacketSwitch {
+ public:
+  PacketSwitch(Netlink* nl) : nl_(nl) {}
+  ~PacketSwitch();
+
+  bool Init();
+  void Start();
+  void Stop();
+
+ private:
+  void ProcessPacket(nl_msg* m, bool is_incoming);
+
+  Netlink* nl_;
+
+  std::mutex op_mutex_;
+  // Started is referenced by all threads created by PacketSwitch to determine
+  // whether to carry on working, or terminate.
+  bool started_;
+
+  std::unique_ptr<std::thread> shm_xchg_;
+  vsoc::wifi::WifiExchangeView shm_wifi_;
+
+  PacketSwitch(const PacketSwitch&) = delete;
+  PacketSwitch& operator=(const PacketSwitch&) = delete;
+};
+
+}  // namespace cvd
diff --git a/common/vsoc/lib/wifi_exchange_view.cpp b/common/vsoc/lib/wifi_exchange_view.cpp
index 23e0bc6..301f35f 100644
--- a/common/vsoc/lib/wifi_exchange_view.cpp
+++ b/common/vsoc/lib/wifi_exchange_view.cpp
@@ -20,17 +20,17 @@
 namespace vsoc {
 namespace wifi {
 
-bool WifiExchangeView::Send(const void* buffer, size_t length) {
+intptr_t WifiExchangeView::Send(const void* buffer, intptr_t length) {
 #ifdef CUTTLEFISH_HOST
   return data()->guest_ingress.Write(this, static_cast<const char*>(buffer),
-                                     length) == length;
+                                     length);
 #else
   return data()->guest_egress.Write(this, static_cast<const char*>(buffer),
-                                    length) == length;
+                                    length);
 #endif
 }
 
-intptr_t WifiExchangeView::Recv(void* buffer, size_t max_length) {
+intptr_t WifiExchangeView::Recv(void* buffer, intptr_t max_length) {
 #ifdef CUTTLEFISH_HOST
   return data()->guest_egress.Read(this, static_cast<char*>(buffer),
                                    max_length);
diff --git a/common/vsoc/lib/wifi_exchange_view.h b/common/vsoc/lib/wifi_exchange_view.h
index 83d7e6a..6bdab03 100644
--- a/common/vsoc/lib/wifi_exchange_view.h
+++ b/common/vsoc/lib/wifi_exchange_view.h
@@ -27,11 +27,11 @@
  public:
   // Send netlink packet to peer.
   // returns true, if operation was successful.
-  bool Send(const void* buffer, size_t length);
+  intptr_t Send(const void* buffer, intptr_t length);
 
   // Receive netlink packet from peer.
   // Returns number of bytes read, or negative value, if failed.
-  intptr_t Recv(void* buffer, size_t max_length);
+  intptr_t Recv(void* buffer, intptr_t max_length);
 
   // Set guest MAC address.
   void SetGuestMACAddress(const uint8_t* mac_address);