shill: LinkMonitor: Add ArpPacket code

Add ArpPacket class to parse and compose ARP packets.

BUG=chromium-os:32600
TEST=New unit tests

Change-Id: Icac65cc16e059f07109fc695ba9046cafa0b042c
Reviewed-on: https://gerrit.chromium.org/gerrit/28096
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/arp_packet.cc b/arp_packet.cc
new file mode 100644
index 0000000..5c3998b
--- /dev/null
+++ b/arp_packet.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2012 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 "shill/arp_packet.h"
+
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include <base/logging.h>
+
+namespace shill {
+
+ArpPacket::ArpPacket()
+    : local_ip_address_(IPAddress::kFamilyUnknown),
+      remote_ip_address_(IPAddress::kFamilyUnknown) {}
+
+ArpPacket::ArpPacket(
+    const IPAddress &local_ip, const IPAddress &remote_ip,
+    const ByteString &local_mac, const ByteString &remote_mac)
+    : local_ip_address_(local_ip),
+      remote_ip_address_(remote_ip),
+      local_mac_address_(local_mac),
+      remote_mac_address_(remote_mac) {}
+
+ArpPacket::~ArpPacket() {}
+
+// Format of an ARP packet (all multi-byte values are big-endian):
+//
+//       Byte 0            Byte 1           Byte 2             Byte 3
+// +-----------------+-----------------+-----------------+-----------------+
+// | Format of hardware address (ether)| Format of Protocol Address (IP)   |
+// +-----------------+-----------------+-----------------------------------+
+// | Hardware Length | Protocol Length |       ARP Protocol OpCode         |
+// +-----------------+-----------------+-----------------------------------+
+//
+// plus a variable length section...
+//
+// +-----------------------------------------------------------------------+
+// | Sender Hardware Address (of length "Hardware Length")...              |
+// +-----------------------------------------------------------------------+
+// | Sender IP Address (of length "Protocol Length")...                    |
+// +-----------------------------------------------------------------------+
+// | Target Hardware Address (of length "Hardware Length")...              |
+// +-----------------------------------------------------------------------+
+// | Target IP Address (of length "Protocol Length")...                    |
+// +-----------------------------------------------------------------------+
+bool ArpPacket::ParseReply(const ByteString &packet) {
+  arphdr header;
+  if (packet.GetLength() < sizeof(header)) {
+    LOG(ERROR) << "Packet size " << packet.GetLength()
+               << " is too short to contain ARP header.";
+    return false;
+  }
+
+  memcpy(&header, packet.GetConstData(), sizeof(header));
+
+  const uint16 hardware_type = ntohs(header.ar_hrd);
+  if (hardware_type != ARPHRD_ETHER) {
+    NOTIMPLEMENTED() << "Packet is of unknown ARPHRD type "
+                     << hardware_type;
+    return false;
+  }
+  const uint16 protocol = ntohs(header.ar_pro);
+  IPAddress::Family family = IPAddress::kFamilyUnknown;
+  if (protocol == ETHERTYPE_IP) {
+    family = IPAddress::kFamilyIPv4;
+  } else if (protocol == ETHERTYPE_IPV6) {
+    family = IPAddress::kFamilyIPv6;
+  } else {
+    NOTIMPLEMENTED() << "Packet has unknown protocol "
+                     << protocol;
+    return false;
+  }
+  if (header.ar_hln != ETH_ALEN) {
+    LOG(ERROR) << "Packet has unexpected hardware address length "
+               << static_cast<int>(header.ar_hln) << "; expected " << ETH_ALEN;
+    return false;
+  }
+  size_t ip_address_length = IPAddress::GetAddressLength(family);
+  if (header.ar_pln != ip_address_length) {
+    LOG(ERROR) << "Packet has unexpected protocol address length "
+               << static_cast<int>(header.ar_hln) << "; expected "
+               << ip_address_length;
+    return false;
+  }
+  const uint16 operation = ntohs(header.ar_op);
+  if (operation != ARPOP_REPLY) {
+    NOTIMPLEMENTED() << "Packet is not an ARP reply but of type "
+                     << operation;
+    return false;
+  }
+  size_t min_packet_size =
+      sizeof(header) + 2 * ip_address_length + 2 * ETH_ALEN;
+  if (packet.GetLength() < min_packet_size) {
+    NOTIMPLEMENTED() << "Packet of size "
+                     << packet.GetLength()
+                     << " is too small to contain entire ARP payload; "
+                     << "expected at least "
+                     << min_packet_size;
+    return false;
+  }
+  local_mac_address_ = packet.GetSubstring(sizeof(header), ETH_ALEN);
+  local_ip_address_ = IPAddress(family, packet.GetSubstring(
+      sizeof(header) + ETH_ALEN, ip_address_length));
+  remote_mac_address_ = packet.GetSubstring(
+      sizeof(header) + ETH_ALEN + ip_address_length, ETH_ALEN);
+  remote_ip_address_ = IPAddress(family, packet.GetSubstring(
+      sizeof(header) + ETH_ALEN * 2 + ip_address_length, ip_address_length));
+  return true;
+}
+
+// Output a payload from local parameters.
+bool ArpPacket::FormatRequest(ByteString *packet) const {
+  if (!local_ip_address_.IsValid() || !remote_ip_address_.IsValid()) {
+    LOG(ERROR) << "Local or remote IP address is not valid.";
+    return false;
+  }
+  if (local_ip_address_.family() != remote_ip_address_.family()) {
+    LOG(ERROR) << "Local and remote IP address families do not match!";
+    return false;
+  }
+  uint16 protocol;
+  IPAddress::Family family = local_ip_address_.family();
+  if (family == IPAddress::kFamilyIPv4) {
+    protocol = ETHERTYPE_IP;
+  } else if (family == IPAddress::kFamilyIPv6) {
+    protocol = ETHERTYPE_IPV6;
+  } else {
+    NOTIMPLEMENTED() << "Address family "
+                     << IPAddress::GetAddressFamilyName(family)
+                     << " is not supported.";
+    return false;
+  }
+  size_t ip_address_length = IPAddress::GetAddressLength(family);
+  CHECK(ip_address_length < kuint8max);
+  if (local_mac_address_.GetLength() != ETH_ALEN ||
+      remote_mac_address_.GetLength() != ETH_ALEN) {
+    LOG(ERROR) << "Local or remote MAC address length is incorrect.";
+    return false;
+  }
+
+  arphdr header;
+  header.ar_hrd = htons(ARPHRD_ETHER);
+  header.ar_pro = htons(protocol);
+  header.ar_hln = ETH_ALEN;
+  header.ar_pln = ip_address_length;
+  header.ar_op = htons(ARPOP_REQUEST);
+
+  *packet = ByteString(reinterpret_cast<const unsigned char *>(&header),
+                       sizeof(header));
+
+  packet->Append(local_mac_address_);
+  packet->Append(local_ip_address_.address());
+  packet->Append(remote_mac_address_);
+  packet->Append(remote_ip_address_.address());
+
+  return true;
+}
+
+}  // namespace shill