arc: Move platform2/arc/network/ to platform2/patchpanel

Next step in the arc-networkd -> patchpanel rename, this patch moves the
location of the code.

BUG=b:151879931
TEST=units,flashed image to atlas
TEST=tasts arc.PlayStore, crostini.LaunchTerminal.download

Change-Id: I1b5cf8d670e1631d46f6449b725395157bf88dde
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2115863
Tested-by: Garrick Evans <garrick@chromium.org>
Commit-Queue: Garrick Evans <garrick@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
Reviewed-by: Eric Caruso <ejcaruso@chromium.org>
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
diff --git a/patchpanel/multicast_forwarder.cc b/patchpanel/multicast_forwarder.cc
new file mode 100644
index 0000000..59587a4
--- /dev/null
+++ b/patchpanel/multicast_forwarder.cc
@@ -0,0 +1,515 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "patchpanel/multicast_forwarder.h"
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/ip.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <utility>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+
+#include "patchpanel/dns/dns_protocol.h"
+#include "patchpanel/dns/dns_response.h"
+#include "patchpanel/net_util.h"
+#include "patchpanel/socket.h"
+
+namespace {
+
+const int kBufSize = 1536;
+
+// Returns the IPv4 address assigned to the interface on which the given socket
+// is bound. Or returns INADDR_ANY if the interface has no IPv4 address.
+struct in_addr GetInterfaceIp(int fd, const std::string& ifname) {
+  if (ifname.empty()) {
+    LOG(WARNING) << "Empty interface name";
+    return {0};
+  }
+
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ);
+  if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
+    // Ignore EADDRNOTAVAIL: IPv4 was not provisioned.
+    if (errno != EADDRNOTAVAIL) {
+      PLOG(ERROR) << "SIOCGIFADDR failed for " << ifname;
+    }
+    return {0};
+  }
+
+  struct sockaddr_in* if_addr =
+      reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_addr);
+  return if_addr->sin_addr;
+}
+
+// Fills sockaddr_storage values.
+void SetSockaddr(struct sockaddr_storage* saddr_storage,
+                 sa_family_t sa_family,
+                 uint16_t port,
+                 char* addr) {
+  struct sockaddr* saddr = reinterpret_cast<sockaddr*>(saddr_storage);
+  if (sa_family == AF_INET) {
+    struct sockaddr_in* saddr4 = reinterpret_cast<struct sockaddr_in*>(saddr);
+    saddr4->sin_family = AF_INET;
+    saddr4->sin_port = htons(port);
+    if (addr)
+      memcpy(&saddr4->sin_addr, addr, sizeof(struct in_addr));
+    return;
+  }
+  if (sa_family == AF_INET6) {
+    struct sockaddr_in6* saddr6 = reinterpret_cast<sockaddr_in6*>(saddr);
+    saddr6->sin6_family = AF_INET6;
+    saddr6->sin6_port = htons(port);
+    if (addr)
+      memcpy(&saddr6->sin6_addr, addr, sizeof(struct in6_addr));
+    return;
+  }
+  LOG(ERROR) << "Invalid socket family " << sa_family;
+}
+
+}  // namespace
+
+namespace patchpanel {
+
+MulticastForwarder::Socket::Socket(
+    base::ScopedFD fd,
+    sa_family_t sa_family,
+    const base::Callback<void(int, sa_family_t)>& callback)
+    : fd(std::move(fd)) {
+  watcher = base::FileDescriptorWatcher::WatchReadable(
+      Socket::fd.get(),
+      base::BindRepeating(callback, Socket::fd.get(), sa_family));
+}
+
+MulticastForwarder::MulticastForwarder(const std::string& lan_ifname,
+                                       uint32_t mcast_addr,
+                                       const std::string& mcast_addr6,
+                                       uint16_t port)
+    : lan_ifname_(lan_ifname), port_(port) {
+  mcast_addr_.s_addr = mcast_addr;
+  CHECK(inet_pton(AF_INET6, mcast_addr6.c_str(), mcast_addr6_.s6_addr));
+
+  base::ScopedFD lan_fd(Bind(AF_INET, lan_ifname_));
+  if (!lan_fd.is_valid()) {
+    LOG(WARNING) << "Could not bind socket on " << lan_ifname_ << " for "
+                 << mcast_addr_ << ":" << port_;
+  }
+
+  base::ScopedFD lan_fd6(Bind(AF_INET6, lan_ifname_));
+  if (!lan_fd6.is_valid()) {
+    LOG(WARNING) << "Could not bind socket on " << lan_ifname_ << " for "
+                 << mcast_addr6_ << ":" << port_;
+  }
+
+  lan_socket_.emplace(
+      AF_INET, new Socket(std::move(lan_fd), AF_INET,
+                          base::BindRepeating(
+                              &MulticastForwarder::OnFileCanReadWithoutBlocking,
+                              base::Unretained(this))));
+
+  lan_socket_.emplace(
+      AF_INET6,
+      new Socket(
+          std::move(lan_fd6), AF_INET6,
+          base::BindRepeating(&MulticastForwarder::OnFileCanReadWithoutBlocking,
+                              base::Unretained(this))));
+}
+
+base::ScopedFD MulticastForwarder::Bind(sa_family_t sa_family,
+                                        const std::string& ifname) {
+  char mcast_addr[INET6_ADDRSTRLEN];
+  inet_ntop(sa_family,
+            sa_family == AF_INET ? reinterpret_cast<const void*>(&mcast_addr_)
+                                 : reinterpret_cast<const void*>(&mcast_addr6_),
+            mcast_addr, INET6_ADDRSTRLEN);
+
+  base::ScopedFD fd(socket(sa_family, SOCK_DGRAM, 0));
+  if (!fd.is_valid()) {
+    PLOG(ERROR) << "socket() failed for multicast forwarder on " << ifname
+                << " for " << mcast_addr << ":" << port_;
+    return base::ScopedFD();
+  }
+
+  // The socket needs to be bound to INADDR_ANY rather than a specific
+  // interface, or it will not receive multicast traffic.  Therefore
+  // we use SO_BINDTODEVICE to force TX from this interface, and
+  // specify the interface address in IP_ADD_MEMBERSHIP to control RX.
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ);
+  if (setsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr))) {
+    PLOG(ERROR) << "setsockopt(SOL_SOCKET) failed for multicast forwarder on "
+                << ifname << " for " << mcast_addr << ":" << port_;
+    return base::ScopedFD();
+  }
+
+  int ifindex = if_nametoindex(ifname.c_str());
+  if (ifindex == 0) {
+    PLOG(ERROR)
+        << "Could not obtain interface index for multicast forwarder on "
+        << ifname << " for " << mcast_addr << ":" << port_;
+    return base::ScopedFD();
+  }
+
+  int level, optname;
+  if (sa_family == AF_INET) {
+    struct ip_mreqn mreqn;
+    memset(&mreqn, 0, sizeof(mreqn));
+    mreqn.imr_multiaddr = mcast_addr_;
+    mreqn.imr_address.s_addr = htonl(INADDR_ANY);
+    mreqn.imr_ifindex = ifindex;
+    if (setsockopt(fd.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn,
+                   sizeof(mreqn)) < 0) {
+      PLOG(ERROR)
+          << "Can't add multicast membership for multicast forwarder on "
+          << ifname << " for " << mcast_addr_ << ":" << port_;
+      return base::ScopedFD();
+    }
+
+    level = IPPROTO_IP;
+    optname = IP_MULTICAST_LOOP;
+  } else if (sa_family == AF_INET6) {
+    struct ipv6_mreq mreqn;
+    memset(&mreqn, 0, sizeof(mreqn));
+    mreqn.ipv6mr_multiaddr = mcast_addr6_;
+    mreqn.ipv6mr_interface = ifindex;
+    if (setsockopt(fd.get(), IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreqn,
+                   sizeof(mreqn)) < 0) {
+      PLOG(ERROR)
+          << "Can't add multicast membership for multicast forwarder on "
+          << ifname << " for " << mcast_addr6_ << ":" << port_;
+      return base::ScopedFD();
+    }
+
+    level = IPPROTO_IPV6;
+    optname = IPV6_MULTICAST_LOOP;
+  } else {
+    return base::ScopedFD();
+  }
+
+  int off = 0;
+  if (setsockopt(fd.get(), level, optname, &off, sizeof(off))) {
+    PLOG(ERROR)
+        << "setsockopt(IP_MULTICAST_LOOP) failed for multicast forwarder on "
+        << ifname << " for " << mcast_addr << ":" << port_;
+    return base::ScopedFD();
+  }
+
+  int on = 1;
+  if (setsockopt(fd.get(), SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
+    PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed for multicast forwarder on "
+                << ifname << " for " << mcast_addr << ":" << port_;
+    return base::ScopedFD();
+  }
+
+  struct sockaddr_storage bind_addr = {0};
+  SetSockaddr(&bind_addr, sa_family, port_, nullptr);
+
+  if (bind(fd.get(), (const struct sockaddr*)&bind_addr,
+           sizeof(struct sockaddr_storage)) < 0) {
+    PLOG(ERROR) << "bind(" << port_ << ") failed for multicast forwarder on "
+                << ifname << " for " << mcast_addr << ":" << port_;
+    return base::ScopedFD();
+  }
+
+  return fd;
+}
+
+bool MulticastForwarder::AddGuest(const std::string& int_ifname) {
+  if (int_sockets_.find(std::make_pair(AF_INET, int_ifname)) !=
+          int_sockets_.end() ||
+      int_sockets_.find(std::make_pair(AF_INET6, int_ifname)) !=
+          int_sockets_.end()) {
+    LOG(WARNING) << "Forwarding is already started between " << lan_ifname_
+                 << " and " << int_ifname;
+    return false;
+  }
+
+  bool success = false;
+
+  // Set up IPv4 multicast forwarder.
+  base::ScopedFD int_fd4(Bind(AF_INET, int_ifname));
+  if (int_fd4.is_valid()) {
+    int_fds_.emplace(std::make_pair(AF_INET, int_fd4.get()));
+
+    std::unique_ptr<Socket> int_socket4 = std::make_unique<Socket>(
+        std::move(int_fd4), AF_INET,
+        base::BindRepeating(&MulticastForwarder::OnFileCanReadWithoutBlocking,
+                            base::Unretained(this)));
+
+    int_sockets_.emplace(std::make_pair(AF_INET, int_ifname),
+                         std::move(int_socket4));
+
+    success = true;
+    LOG(INFO) << "Started IPv4 forwarding between " << lan_ifname_ << " and "
+              << int_ifname << " for " << mcast_addr_ << ":" << port_;
+  } else {
+    LOG(WARNING) << "Could not bind socket on " << int_ifname << " for "
+                 << mcast_addr_ << ":" << port_;
+  }
+
+  // Set up IPv6 multicast forwarder.
+  base::ScopedFD int_fd6(Bind(AF_INET6, int_ifname));
+  if (int_fd6.is_valid()) {
+    int_fds_.emplace(std::make_pair(AF_INET6, int_fd6.get()));
+
+    std::unique_ptr<Socket> int_socket6 = std::make_unique<Socket>(
+        std::move(int_fd6), AF_INET6,
+        base::BindRepeating(&MulticastForwarder::OnFileCanReadWithoutBlocking,
+                            base::Unretained(this)));
+
+    int_sockets_.emplace(std::make_pair(AF_INET6, int_ifname),
+                         std::move(int_socket6));
+
+    success = true;
+    LOG(INFO) << "Started IPv6 forwarding between " << lan_ifname_ << " and "
+              << int_ifname << " for " << mcast_addr6_ << ":" << port_;
+  } else {
+    LOG(WARNING) << "Could not bind socket on " << int_ifname << " for "
+                 << mcast_addr6_ << ":" << port_;
+  }
+
+  return success;
+}
+
+void MulticastForwarder::RemoveGuest(const std::string& int_ifname) {
+  const auto& socket4 = int_sockets_.find(std::make_pair(AF_INET, int_ifname));
+  if (socket4 != int_sockets_.end()) {
+    int_fds_.erase(std::make_pair(AF_INET, socket4->second->fd.get()));
+    int_sockets_.erase(socket4);
+  } else {
+    LOG(WARNING) << "IPv4 forwarding is not started between " << lan_ifname_
+                 << " and " << int_ifname;
+  }
+
+  const auto& socket6 = int_sockets_.find(std::make_pair(AF_INET6, int_ifname));
+  if (socket6 != int_sockets_.end()) {
+    int_fds_.erase(std::make_pair(AF_INET6, socket6->second->fd.get()));
+    int_sockets_.erase(socket6);
+  } else {
+    LOG(WARNING) << "IPv6 forwarding is not started between " << lan_ifname_
+                 << " and " << int_ifname;
+  }
+}
+
+void MulticastForwarder::OnFileCanReadWithoutBlocking(int fd,
+                                                      sa_family_t sa_family) {
+  CHECK(sa_family == AF_INET || sa_family == AF_INET6);
+
+  char data[kBufSize];
+
+  struct sockaddr_storage fromaddr_storage = {0};
+  struct sockaddr* fromaddr =
+      reinterpret_cast<struct sockaddr*>(&fromaddr_storage);
+
+  socklen_t addrlen = sizeof(struct sockaddr_storage);
+
+  ssize_t len = recvfrom(fd, data, kBufSize, 0, fromaddr, &addrlen);
+  if (len < 0) {
+    PLOG(WARNING) << "recvfrom failed";
+    return;
+  }
+
+  socklen_t expectlen = sa_family == AF_INET ? sizeof(struct sockaddr_in)
+                                             : sizeof(struct sockaddr_in6);
+  if (addrlen != expectlen) {
+    LOG(WARNING) << "recvfrom failed: unexpected src addr length " << addrlen;
+    return;
+  }
+
+  struct sockaddr_storage dst_storage = {0};
+  struct sockaddr* dst = reinterpret_cast<struct sockaddr*>(&dst_storage);
+  uint16_t src_port;
+
+  if (sa_family == AF_INET) {
+    const struct sockaddr_in* addr4 =
+        reinterpret_cast<const struct sockaddr_in*>(fromaddr);
+    src_port = ntohs(addr4->sin_port);
+  } else if (sa_family == AF_INET6) {
+    const struct sockaddr_in6* addr6 =
+        reinterpret_cast<const struct sockaddr_in6*>(fromaddr);
+    src_port = ntohs(addr6->sin6_port);
+  }
+  SetSockaddr(&dst_storage, sa_family, port_,
+              sa_family == AF_INET ? reinterpret_cast<char*>(&mcast_addr_)
+                                   : reinterpret_cast<char*>(&mcast_addr6_));
+
+  // Forward ingress traffic to all guests.
+  const auto& lan_socket = lan_socket_.find(sa_family);
+  if ((lan_socket != lan_socket_.end() && fd == lan_socket->second->fd.get())) {
+    SendToGuests(data, len, dst, addrlen);
+    return;
+  }
+
+  const auto& int_fd = int_fds_.find(std::make_pair(sa_family, fd));
+  if (int_fd == int_fds_.end() || lan_socket == lan_socket_.end())
+    return;
+
+  // Forward egress traffic from one guest to all other guests.
+  // No IP translation is required as other guests can route to each other
+  // behind the SNAT setup.
+  SendToGuests(data, len, dst, addrlen, fd);
+
+  // On mDNS, sending to physical network requires translating any IPv4
+  // address specific to the guest and not visible to the physical network.
+  if (sa_family == AF_INET && port_ == kMdnsPort) {
+    // TODO(b/132574450) The replacement address should instead be specified
+    // as an input argument, based on the properties of the network
+    // currently connected on |lan_ifname_|.
+    const struct in_addr lan_ip =
+        GetInterfaceIp(lan_socket->second->fd.get(), lan_ifname_);
+    if (lan_ip.s_addr == htonl(INADDR_ANY)) {
+      // When the physical interface has no IPv4 address, IPv4 is not
+      // provisioned and there is no point in trying to forward traffic in
+      // either direction.
+      return;
+    }
+    TranslateMdnsIp(
+        lan_ip, reinterpret_cast<const struct sockaddr_in*>(fromaddr)->sin_addr,
+        data, len);
+  }
+
+  // Forward egress traffic from one guest to outside network.
+  SendTo(src_port, data, len, dst, addrlen);
+}
+
+bool MulticastForwarder::SendTo(uint16_t src_port,
+                                const void* data,
+                                ssize_t len,
+                                const struct sockaddr* dst,
+                                socklen_t dst_len) {
+  if (src_port == port_) {
+    int lan_fd = lan_socket_.find(dst->sa_family)->second->fd.get();
+    if (sendto(lan_fd, data, len, 0, dst, dst_len) < 0) {
+      PLOG(WARNING) << "sendto failed";
+      return false;
+    }
+    return true;
+  }
+
+  patchpanel::Socket temp_socket(dst->sa_family, SOCK_DGRAM);
+
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, lan_ifname_.c_str(), IFNAMSIZ);
+  if (setsockopt(temp_socket.fd(), SOL_SOCKET, SO_BINDTODEVICE, &ifr,
+                 sizeof(ifr))) {
+    PLOG(ERROR) << "setsockopt(SOL_SOCKET) failed";
+    return false;
+  }
+
+  int level, optname;
+  struct sockaddr_storage bind_addr_storage = {0};
+  struct sockaddr* bind_addr = reinterpret_cast<sockaddr*>(&bind_addr_storage);
+  if (dst->sa_family == AF_INET) {
+    level = IPPROTO_IP;
+    optname = IP_MULTICAST_LOOP;
+  } else if (dst->sa_family == AF_INET6) {
+    level = IPPROTO_IPV6;
+    optname = IPV6_MULTICAST_LOOP;
+  } else {
+    return false;
+  }
+  SetSockaddr(&bind_addr_storage, dst->sa_family, src_port, nullptr);
+
+  int flag = 0;
+  if (setsockopt(temp_socket.fd(), level, optname, &flag, sizeof(flag))) {
+    PLOG(ERROR) << "setsockopt(IP_MULTICAST_LOOP) failed";
+    return false;
+  }
+
+  flag = 1;
+  if (setsockopt(temp_socket.fd(), SOL_SOCKET, SO_REUSEADDR, &flag,
+                 sizeof(flag))) {
+    PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
+    return false;
+  }
+
+  if (!temp_socket.Bind(bind_addr, sizeof(struct sockaddr_storage)))
+    return false;
+
+  return temp_socket.SendTo(data, len, dst, dst_len);
+}
+
+bool MulticastForwarder::SendToGuests(const void* data,
+                                      ssize_t len,
+                                      const struct sockaddr* dst,
+                                      socklen_t dst_len,
+                                      int ignore_fd) {
+  bool success = true;
+  for (const auto& socket : int_sockets_) {
+    if (socket.first.first != dst->sa_family)
+      continue;
+    int fd = socket.second->fd.get();
+    if (fd == ignore_fd)
+      continue;
+
+    // Use already created multicast fd.
+    if (sendto(fd, data, len, 0, dst, dst_len) < 0) {
+      PLOG(WARNING) << "sendto failed to " << socket.first.second;
+      success = false;
+    }
+  }
+  return success;
+}
+
+// static
+void MulticastForwarder::TranslateMdnsIp(const struct in_addr& lan_ip,
+                                         const struct in_addr& guest_ip,
+                                         char* data,
+                                         ssize_t len) {
+  if (guest_ip.s_addr == htonl(INADDR_ANY)) {
+    return;
+  }
+
+  // Make sure this is a valid, successful DNS response from the Android
+  // host.
+  if (len > net::dns_protocol::kMaxUDPSize || len <= 0) {
+    return;
+  }
+
+  net::DnsResponse resp;
+  memcpy(resp.io_buffer()->data(), data, len);
+  if (!resp.InitParseWithoutQuery(len) ||
+      !(resp.flags() & net::dns_protocol::kFlagResponse) ||
+      resp.rcode() != net::dns_protocol::kRcodeNOERROR) {
+    return;
+  }
+
+  // Check all A records for the internal IP, and replace it with |lan_ip|
+  // if it is found.
+  net::DnsRecordParser parser = resp.Parser();
+  while (!parser.AtEnd()) {
+    const size_t ipv4_addr_len = sizeof(lan_ip.s_addr);
+
+    net::DnsResourceRecord record;
+    if (!parser.ReadRecord(&record)) {
+      break;
+    }
+    if (record.type == net::dns_protocol::kTypeA &&
+        record.rdata.size() == ipv4_addr_len) {
+      struct in_addr rr_ip;
+      memcpy(&rr_ip, record.rdata.data(), ipv4_addr_len);
+      if (guest_ip.s_addr == rr_ip.s_addr) {
+        // HACK: This is able to calculate the (variable) offset of the IPv4
+        // address inside the resource record by assuming that the
+        // StringPiece returns a pointer inside the io_buffer.  It works
+        // today, but future libchrome changes might break it.
+        size_t ip_offset = record.rdata.data() - resp.io_buffer()->data();
+        CHECK(ip_offset <= len - ipv4_addr_len);
+        memcpy(&data[ip_offset], &lan_ip.s_addr, ipv4_addr_len);
+      }
+    }
+  }
+}
+
+}  // namespace patchpanel