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