shill: LinkMonitor: Add ArpClient code
Add ArpClient class and start hooking it up to LinkMonitor.
BUG=chromium-os:32600
TEST=Unit tests. Some real-world testing using the test harness
to transmit and receive ARP on a real network.
Change-Id: Ic05d8d7eb921878e3776f35b4be285554ef86456
Reviewed-on: https://gerrit.chromium.org/gerrit/28148
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_client.cc b/arp_client.cc
new file mode 100644
index 0000000..f946240
--- /dev/null
+++ b/arp_client.cc
@@ -0,0 +1,179 @@
+// 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_client.h"
+
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include <base/logging.h>
+
+#include "shill/arp_packet.h"
+#include "shill/byte_string.h"
+#include "shill/sockets.h"
+
+namespace shill {
+
+// ARP opcode is the last uint16 in the ARP header.
+const size_t ArpClient::kArpOpOffset = sizeof(arphdr) - sizeof(uint16);
+
+// The largest packet we expect is one with IPv6 addresses in it.
+const size_t ArpClient::kMaxArpPacketLength =
+ sizeof(arphdr) + sizeof(in6_addr) * 2 + ETH_ALEN * 2;
+
+ArpClient::ArpClient(int interface_index)
+ : interface_index_(interface_index),
+ sockets_(new Sockets()),
+ socket_(-1) {}
+
+ArpClient::~ArpClient() {}
+
+bool ArpClient::Start() {
+ if (!CreateSocket()) {
+ LOG(ERROR) << "Could not open ARP socket.";
+ Stop();
+ return false;
+ }
+ return true;
+}
+
+void ArpClient::Stop() {
+ socket_closer_.reset();
+}
+
+
+bool ArpClient::CreateSocket() {
+ int socket = sockets_->Socket(PF_PACKET, SOCK_DGRAM, htons(ETHERTYPE_ARP));
+ if (socket == -1) {
+ PLOG(ERROR) << "Could not create ARP socket";
+ return false;
+ }
+ socket_ = socket;
+ socket_closer_.reset(new ScopedSocketCloser(sockets_.get(), socket_));
+
+ // Create a packet filter incoming ARP replies.
+ static const sock_filter arp_reply_filter[] = {
+ // If we a packet contains ARPOP_REPLY as the ARP opcode...
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kArpOpOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ARPOP_REPLY, 0, 1),
+ // Return the the packet (up to largest expected packet size).
+ BPF_STMT(BPF_RET | BPF_K, kMaxArpPacketLength),
+ // Otherwise, drop it.
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ };
+
+ sock_fprog pf;
+ pf.filter = const_cast<sock_filter *>(arp_reply_filter);
+ pf.len = arraysize(arp_reply_filter);
+ if (sockets_->AttachFilter(socket_, &pf) != 0) {
+ PLOG(ERROR) << "Could not attach packet filter";
+ return false;
+ }
+
+ if (sockets_->SetNonBlocking(socket_) != 0) {
+ PLOG(ERROR) << "Could not set socket to be non-blocking";
+ return false;
+ }
+
+ sockaddr_ll socket_address;
+ memset(&socket_address, 0, sizeof(socket_address));
+ socket_address.sll_family = AF_PACKET;
+ socket_address.sll_protocol = htons(ETHERTYPE_ARP);
+ socket_address.sll_ifindex = interface_index_;
+
+ if (sockets_->Bind(socket_,
+ reinterpret_cast<struct sockaddr *>(&socket_address),
+ sizeof(socket_address)) != 0) {
+ PLOG(ERROR) << "Could not bind socket to interface";
+ return false;
+ }
+
+ return true;
+}
+
+bool ArpClient::ReceiveReply(ArpPacket *packet, ByteString *sender) const {
+ ByteString payload(kMaxArpPacketLength);
+ sockaddr_ll socket_address;
+ memset(&socket_address, 0, sizeof(socket_address));
+ socklen_t socklen = sizeof(socket_address);
+ int result = sockets_->RecvFrom(
+ socket_,
+ payload.GetData(),
+ payload.GetLength(),
+ 0,
+ reinterpret_cast<struct sockaddr *>(&socket_address),
+ &socklen);
+ if (result < 0) {
+ PLOG(ERROR) << "Socket recvfrom failed";
+ return false;
+ }
+
+ payload.Resize(result);
+ if (!packet->ParseReply(payload)) {
+ LOG(ERROR) << "Failed to parse ARP reply.";
+ return false;
+ }
+
+ // The socket address returned may only be big enough to contain
+ // the hardware address of the sender.
+ CHECK(socklen >=
+ sizeof(socket_address) - sizeof(socket_address.sll_addr) + ETH_ALEN);
+ CHECK(socket_address.sll_halen == ETH_ALEN);
+ *sender = ByteString(
+ reinterpret_cast<const unsigned char *>(&socket_address.sll_addr),
+ socket_address.sll_halen);
+ return true;
+}
+
+bool ArpClient::TransmitRequest(const ArpPacket &packet) const {
+ ByteString payload;
+ if (!packet.FormatRequest(&payload)) {
+ return false;
+ }
+
+ sockaddr_ll socket_address;
+ memset(&socket_address, 0, sizeof(socket_address));
+ socket_address.sll_family = AF_PACKET;
+ socket_address.sll_protocol = htons(ETHERTYPE_ARP);
+ socket_address.sll_hatype = ARPHRD_ETHER;
+ socket_address.sll_halen = ETH_ALEN;
+ socket_address.sll_ifindex = interface_index_;
+
+ ByteString remote_address = packet.remote_mac_address();
+ CHECK(sizeof(socket_address.sll_addr) >= remote_address.GetLength());
+ if (remote_address.IsZero()) {
+ // If the destination MAC address is unspecified, send the packet
+ // to the broadcast (all-ones) address.
+ remote_address.BitwiseInvert();
+ }
+ memcpy(&socket_address.sll_addr, remote_address.GetConstData(),
+ remote_address.GetLength());
+
+ int result = sockets_->SendTo(
+ socket_,
+ payload.GetConstData(),
+ payload.GetLength(),
+ 0,
+ reinterpret_cast<struct sockaddr *>(&socket_address),
+ sizeof(socket_address));
+ const int expected_result = static_cast<int>(payload.GetLength());
+ if (result != expected_result) {
+ if (result < 0) {
+ PLOG(ERROR) << "Socket sendto failed";
+ } else if (result < static_cast<int>(payload.GetLength())) {
+ LOG(ERROR) << "Socket sendto returned "
+ << result
+ << " which is different from expected result "
+ << expected_result;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace shill