Squashed commit of the following:

commit 860a83abce314a6b6622ee5ce00947585a9b9abb
Author: Andreas Huber <andih@google.com>
Date:   Thu Mar 1 10:00:19 2018 -0800

    Fix out-of-bounds memory read reported by ASAN.

    Change-Id: I39caec18a35e36864c258338b1a654ddad07b555

commit 246bd8ed19a817fe06d55bdf543ae8abb6a80327
Author: Andreas Huber <andih@google.com>
Date:   Fri Feb 23 11:26:36 2018 -0800

    Fix wifi_relay makefile.

    Change-Id: I7fa9bc13bd74a8001422c0f21dd97c0b46165e0d

commit 3499b50e2bda379fe5e66e38915577d0b6be9247
Author: Andreas Huber <andih@google.com>
Date:   Fri Feb 23 10:55:34 2018 -0800

    std::shared_ptr<vsoc::wifi::WifiExchangeView> => vsoc::wifi::WifiExchangeView*

    Change-Id: I0da7210dac64990020a2c09ffae3f27be489f17e

commit 15a656d5d98cffe6f829287fe2608b58992cbd03
Author: Andreas Huber <andih@google.com>
Date:   Thu Feb 22 14:20:37 2018 -0800

    Common guest/host wifi_relay binary, host wifi_relay launched by

    launch_cvd.

    Change-Id: Ifa5e7bad84df605213519bd76a655b13a55d535d

commit a0d702f8a2de28e98c62361bc52c229d597781a4
Author: Andreas Huber <andih@google.com>
Date:   Thu Feb 22 11:45:46 2018 -0800

    Now using the new CMD_SUBSCRIBE method on the host.

    Change-Id: Ib4800b853d37d2f027ed95b1787789420b5562b8

commit 94c9311b10439964fdf205b61356f1ee646b24ae
Author: Andreas Huber <andih@google.com>
Date:   Tue Feb 20 13:31:38 2018 -0800

    No more hardcoded MAC addresses.

    Change-Id: I10f0d6b5c61225e25a731feb4271fa7032a2c5aa

commit 1701d4ccda395540d88e0e137f1dc204b7636c8a
Author: Andreas Huber <andih@google.com>
Date:   Tue Feb 20 11:03:28 2018 -0800

    Added copyright headers, removed debugging code, switched to glog.

    Change-Id: Ic98e607a8988983441db73617dc9a8599bac2eba

commit b70200a808cd903b0b6a028156684747a7a4e50e
Author: Andreas Huber <andih@google.com>
Date:   Tue Feb 20 09:17:40 2018 -0800

    Removed inject, attempt at cleaning up after disconnected clients.

    Change-Id: I4782d348e7079e8aa12d2789f3fb4bbb1aefd4a4

commit 13fccaa33195832a471d2171afb95bc19fb67446
Author: Andreas Huber <andih@google.com>
Date:   Fri Feb 16 14:16:59 2018 -0800

    Now things appear to be working more stably.

    Change-Id: Ie3bac0bca89c9d8f893825ff0156ab5109c65847

commit d11e2856d93525ac0755b532307a39ec4897fdda
Author: Andreas Huber <andih@google.com>
Date:   Thu Feb 15 16:34:30 2018 -0800

    separate "inject" into wifi_relay_guest and wifi_relay_host

    Change-Id: I255fb9e4f44c31a32f364cbdfa55a0b73b8191e1

commit 7efbd60cb37ab608d25d3a43e7aa6576a8e3c9a6
Author: Andreas Huber <andih@google.com>
Date:   Wed Feb 14 11:46:03 2018 -0800

    initial attempt to support wifi through mac80211_hwsim

    Change-Id: I1ac1440dde40605023f873c4c88401e15100bacc

BUG: 62789500
Test: Later
Change-Id: I53a4c0658a5491a99f34fcc7066f17e0961b1fec
diff --git a/common/commands/Android.bp b/common/commands/Android.bp
index 70311b6..b674475 100644
--- a/common/commands/Android.bp
+++ b/common/commands/Android.bp
@@ -15,4 +15,5 @@
 
 subdirs = [
     "wificlient",
+    "wifi_relay",
 ]
diff --git a/common/commands/wifi_relay/Android.bp b/common/commands/wifi_relay/Android.bp
new file mode 100644
index 0000000..ab85c07
--- /dev/null
+++ b/common/commands/wifi_relay/Android.bp
@@ -0,0 +1,51 @@
+//
+// 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.
+//
+cc_binary {
+    name: "wifi_relay",
+    srcs: [
+        "wifi_relay.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "vsoc_lib",
+        "libcuttlefish_fs",
+        "cuttlefish_auto_resources",
+        "liblog",
+        "libnl",
+    ],
+    static_libs: [
+        "libgflags",
+        "libcuttlefish_wifi_relay",
+    ],
+    header_libs: [
+        "cuttlefish_glog",
+    ],
+    target: {
+        linux_glibc: {
+            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/wifi_relay/wifi_relay.cpp b/common/commands/wifi_relay/wifi_relay.cpp
new file mode 100644
index 0000000..1af95d9
--- /dev/null
+++ b/common/commands/wifi_relay/wifi_relay.cpp
@@ -0,0 +1,339 @@
+/*
+ * 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/libs/wifi_relay/mac80211_hwsim_driver.h"
+#include "common/libs/wifi/nl_client.h"
+
+#if defined(CUTTLEFISH_HOST)
+#include "host/libs/config/host_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>
+
+DEFINE_string(
+        guest_mac_address,
+        "00:43:56:44:80:02",
+        "MAC address of the wifi interface to be created on the guest.");
+
+DEFINE_string(
+        host_mac_address,
+        "42:00:00:00:00:00",
+        "MAC address of the wifi interface running on the host.");
+
+#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) {
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+  Mac80211HwSim::MacAddress guestMAC, hostMAC;
+  if (!Mac80211HwSim::ParseMACAddress(FLAGS_guest_mac_address, &guestMAC)
+          || !Mac80211HwSim::ParseMACAddress(FLAGS_host_mac_address, &hostMAC)) {
+      exit(1);
+  }
+
+#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::unique_ptr<std::thread> nlThread(
+          new 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());
+                  }
+              }
+          }));
+
+  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;
+}
diff --git a/common/commands/wifi_relay/wifi_relay.h b/common/commands/wifi_relay/wifi_relay.h
new file mode 100644
index 0000000..98dce3a
--- /dev/null
+++ b/common/commands/wifi_relay/wifi_relay.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "common/libs/wifi_relay/mac80211_hwsim.h"
+
+#include <errno.h>
+#include <memory>
+
+class WifiRelay {
+ public:
+  WifiRelay(
+          const Mac80211HwSim::MacAddress &localMAC,
+          const Mac80211HwSim::MacAddress &remoteMAC);
+
+  WifiRelay(const WifiRelay &) = delete;
+  WifiRelay &operator=(const WifiRelay &) = delete;
+
+  virtual ~WifiRelay() = default;
+
+  int initCheck() const;
+
+  void run();
+
+  int mac80211Family() const;
+  int nl80211Family() const;
+
+ private:
+  int init_check_ = -ENODEV;
+
+  std::unique_ptr<Mac80211HwSim> mMac80211HwSim;
+};
+
diff --git a/common/libs/Android.bp b/common/libs/Android.bp
index 648d657..b23571f 100644
--- a/common/libs/Android.bp
+++ b/common/libs/Android.bp
@@ -21,4 +21,5 @@
     "threads",
     "time",
     "wifi",
+    "wifi_relay",
 ]
diff --git a/common/libs/wifi_relay/Android.bp b/common/libs/wifi_relay/Android.bp
new file mode 100644
index 0000000..8680364
--- /dev/null
+++ b/common/libs/wifi_relay/Android.bp
@@ -0,0 +1,24 @@
+cc_library_static {
+    name: "libcuttlefish_wifi_relay",
+
+    srcs: [
+        "mac80211_hwsim.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libnl",
+        "vsoc_lib",
+    ],
+    header_libs: [
+        "cuttlefish_glog",
+    ],
+    target: {
+        linux_glibc: {
+            static_libs: [
+                "libcuttlefish_host_config",
+                "libgflags",
+            ],
+        }
+    },
+    defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"]
+}
diff --git a/common/libs/wifi_relay/mac80211_hwsim.cpp b/common/libs/wifi_relay/mac80211_hwsim.cpp
new file mode 100644
index 0000000..c08fcc9
--- /dev/null
+++ b/common/libs/wifi_relay/mac80211_hwsim.cpp
@@ -0,0 +1,442 @@
+/*
+ * 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/libs/wifi_relay/mac80211_hwsim.h"
+
+#include "common/libs/wifi_relay/mac80211_hwsim_driver.h"
+
+#include <glog/logging.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+#include <signal.h>
+#include <string>
+
+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
+
+Mac80211HwSim::Remote::Remote(
+        Mac80211HwSim *parent,
+        vsoc::wifi::WifiExchangeView *wifiExchange)
+    : mParent(parent),
+      mWifiExchange(wifiExchange) {
+    mWifiWorker = mWifiExchange->StartWorker();
+
+    mThread.reset(new 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;
+          }
+
+          // LOG(INFO) << "GUEST->HOST packet of size " << res;
+
+          struct nlmsghdr *hdr = reinterpret_cast<struct nlmsghdr *>(buf.get());
+
+          int len = res;
+          while (nlmsg_ok(hdr, len)) {
+            mParent->injectMessage(hdr);
+
+            hdr = nlmsg_next(hdr, &len);
+          }
+        }}));
+}
+
+Mac80211HwSim::Remote::~Remote() {
+    mDone = true;
+    mWifiExchange->InterruptSelf();
+
+    mThread->join();
+    mThread.reset();
+}
+
+intptr_t Mac80211HwSim::Remote::send(const void *data, size_t 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::dumpMessage(nlmsghdr *msg) const {
+    genlmsghdr *hdr = genlmsg_hdr(msg);
+
+    LOG(VERBOSE) << "message cmd = " << (int)hdr->cmd;
+
+    nlattr *attrs[__HWSIM_ATTR_MAX + 1];
+    int res = genlmsg_parse(
+            msg,
+            0 /* hdrlen */,
+            attrs,
+            __HWSIM_ATTR_MAX,
+            nullptr /* policy */);
+
+    if (res < 0) {
+        LOG(ERROR) << "genlmsg_parse failed.";
+        return;
+    }
+
+    // for HWSIM_CMD_FRAME, the following attributes are present:
+    // HWSIM_ATTR_ADDR_TRANSMITTER, HWSIM_ATTR_FRAME, HWSIM_ATTR_FLAGS,
+    // HWSIM_ATTR_TX_INFO, HWSIM_ATTR_COOKIE, HWSIM_ATTR_FREQ
+
+    for (size_t i = 0; i < __HWSIM_ATTR_MAX; ++i) {
+        if (attrs[i]) {
+            LOG(VERBOSE) << "Got attribute " << i;
+        }
+    }
+}
+
+void Mac80211HwSim::injectMessage(nlmsghdr *msg) {
+#ifdef CUTTLEFISH_HOST
+    LOG(VERBOSE) << "------------------- Guest -> Host -----------------------";
+#else
+    LOG(VERBOSE) << "------------------- Host -> Guest -----------------------";
+#endif
+    dumpMessage(msg);
+
+    // Do NOT check nlmsg_type against mMac80211Family, these are dynamically
+    // assigned and may not necessarily match across machines!
+
+    genlmsghdr *hdr = genlmsg_hdr(msg);
+    if (hdr->cmd != HWSIM_CMD_FRAME) {
+        LOG(VERBOSE) << "injectMessage: not cmd HWSIM_CMD_FRAME.";
+        return;
+    }
+
+    nlattr *attrs[__HWSIM_ATTR_MAX + 1];
+    int res = genlmsg_parse(
+            msg,
+            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) {
+        injectFrame(nla_data(attr), nla_len(attr));
+    } else {
+        LOG(ERROR) << "injectMessage: no HWSIM_ATTR_FRAME.";
+    }
+}
+
+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!";
+    dumpMessage(nlmsg_hdr(msg.get()));
+
+    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
+
+    dumpMessage(msg.get());
+
+#if !defined(CUTTLEFISH_HOST)
+    ackFrame(msg.get());
+#endif
+
+    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(msg.get(), msg->nlmsg_len);
+    }
+}
+
+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);
+    }
+}
+
+// static
+bool Mac80211HwSim::ParseMACAddress(const std::string &s, MacAddress *mac) {
+    mac->resize(ETH_ALEN);
+
+    char dummy;
+    if (sscanf(s.c_str(),
+               "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx%c",
+               &(*mac)[0],
+               &(*mac)[1],
+               &(*mac)[2],
+               &(*mac)[3],
+               &(*mac)[4],
+               &(*mac)[5],
+               &dummy) != 6) {
+        LOG(ERROR) << "Failed to parse MAC address: " << s;
+        return false;
+    }
+
+    return true;
+}
diff --git a/common/libs/wifi_relay/mac80211_hwsim.h b/common/libs/wifi_relay/mac80211_hwsim.h
new file mode 100644
index 0000000..43f94fe
--- /dev/null
+++ b/common/libs/wifi_relay/mac80211_hwsim.h
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "common/vsoc/lib/wifi_exchange_view.h"
+
+#include <errno.h>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <netlink/netlink.h>
+#include <vector>
+
+struct Mac80211HwSim {
+    using MacAddress = std::vector<uint8_t>;
+
+    static constexpr size_t kMessageSizeMax = 128 * 1024;
+
+    explicit Mac80211HwSim(const MacAddress &mac);
+    Mac80211HwSim(const Mac80211HwSim &) = delete;
+    Mac80211HwSim &operator=(const Mac80211HwSim &) = delete;
+
+    virtual ~Mac80211HwSim() = default;
+
+    int initCheck() const;
+
+    int socketFd() const;
+
+    void handlePacket();
+
+    int mac80211Family() const { return mMac80211Family; }
+    int nl80211Family() const { return mNl80211Family; }
+
+    int addRemote(
+            const MacAddress &mac,
+            vsoc::wifi::WifiExchangeView *wifiExchange);
+
+    void removeRemote(const MacAddress &mac);
+
+    static bool ParseMACAddress(const std::string &s, MacAddress *mac);
+
+private:
+    struct Remote {
+        explicit Remote(
+            Mac80211HwSim *parent,
+            vsoc::wifi::WifiExchangeView *wifiExchange);
+
+        Remote(const Remote &) = delete;
+        Remote &operator=(const Remote &) = delete;
+
+        virtual ~Remote();
+
+        intptr_t send(const void *data, size_t size);
+
+    private:
+        Mac80211HwSim *mParent;
+        vsoc::wifi::WifiExchangeView *mWifiExchange;
+        std::unique_ptr<vsoc::RegionWorker> mWifiWorker;
+
+        volatile bool mDone = false;
+        std::unique_ptr<std::thread> mThread;
+    };
+
+    int mInitCheck = -ENODEV;
+    MacAddress mMAC;
+    std::unique_ptr<nl_sock, void (*)(nl_sock *)> mSock;
+    int mMac80211Family = 0;
+    int mNl80211Family = 0;
+
+    std::mutex mRemotesLock;
+    std::map<MacAddress, std::unique_ptr<Remote>> mRemotes;
+
+    void injectMessage(nlmsghdr *hdr);
+    void injectFrame(const void *data, size_t size);
+    void ackFrame(nlmsghdr *msg);
+    void dumpMessage(nlmsghdr *msg) const;
+    int registerOrSubscribe(const MacAddress &mac);
+};
diff --git a/common/libs/wifi_relay/mac80211_hwsim_driver.h b/common/libs/wifi_relay/mac80211_hwsim_driver.h
new file mode 100644
index 0000000..c472254
--- /dev/null
+++ b/common/libs/wifi_relay/mac80211_hwsim_driver.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+enum hwsim_cmd {
+    HWSIM_CMD_UNSPEC,
+    HWSIM_CMD_REGISTER,
+    HWSIM_CMD_FRAME,
+    HWSIM_CMD_TX_INFO_FRAME,
+    HWSIM_CMD_NEW_RADIO,
+    HWSIM_CMD_DEL_RADIO,
+    HWSIM_CMD_GET_RADIO,
+    HWSIM_CMD_SUBSCRIBE,
+    __HWSIM_CMD_MAX
+};
+
+enum hwsim_attr {
+    /* 0 */ HWSIM_ATTR_UNSPEC,
+    /* 1 */ HWSIM_ATTR_ADDR_RECEIVER,
+    /* 2 */ HWSIM_ATTR_ADDR_TRANSMITTER,
+    /* 3 */ HWSIM_ATTR_FRAME,
+    /* 4 */ HWSIM_ATTR_FLAGS,
+    /* 5 */ HWSIM_ATTR_RX_RATE,
+    /* 6 */ HWSIM_ATTR_SIGNAL,
+    /* 7 */ HWSIM_ATTR_TX_INFO,
+    /* 8 */ HWSIM_ATTR_COOKIE,
+    /* 9 */ HWSIM_ATTR_CHANNELS,
+    /* 10 */ HWSIM_ATTR_RADIO_ID,
+    /* 11 */ HWSIM_ATTR_REG_HINT_ALPHA2,
+    /* 12 */ HWSIM_ATTR_REG_CUSTOM_REG,
+    /* 13 */ HWSIM_ATTR_REG_STRICT_REG,
+    /* 14 */ HWSIM_ATTR_SUPPORT_P2P_DEVICE,
+    /* 15 */ HWSIM_ATTR_USE_CHANCTX,
+    /* 16 */ HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
+    /* 17 */ HWSIM_ATTR_RADIO_NAME,
+    /* 18 */ HWSIM_ATTR_NO_VIF,
+    /* 19 */ HWSIM_ATTR_FREQ,
+    __HWSIM_ATTR_MAX
+};
+
+enum hwsim_tx_control_flags {
+    HWSIM_TX_CTL_REQ_TX_STATUS  = 1,
+    HWSIM_TX_CTL_NO_ACK         = 2,
+    HWSIM_TX_STAT_ACK           = 4,
+};
diff --git a/host/commands/launch/main.cc b/host/commands/launch/main.cc
index 053c391..eacd5da 100644
--- a/host/commands/launch/main.cc
+++ b/host/commands/launch/main.cc
@@ -124,6 +124,12 @@
               "host-side socket_forward_proxy process will bias the port "
               "numbers.");
 
+DEFINE_bool(start_wifi_relay, true, "Whether to start the wifi_relay process.");
+DEFINE_string(wifi_relay_binary,
+              StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) +
+                  "/bin/wifi_relay",
+              "Location of the wifi_relay binary.");
+
 DECLARE_string(uuid);
 
 namespace {
@@ -488,5 +494,20 @@
                                  port_options.c_str(), NULL};
     subprocess(vnc_command, NULL, false);
   }
+
+  if (FLAGS_start_wifi_relay) {
+    // Launch the wifi relay, don't wait for it to complete
+    std::string domainArg = "--domain=" + vsoc::GetDomain();
+
+    const char* relay_command[] = {
+        "/usr/bin/sudo",
+        FLAGS_wifi_relay_binary.c_str(),
+        domainArg.c_str(),
+        NULL
+    };
+
+    subprocess(relay_command, NULL /* envp */, false /* wait_for_child */);
+  }
+
   pause();
 }