shill: EapListener: New class for listening for EAP
Create an entity that listens for EAP traffic on a given interface
index. It will invoke a callback method when an EAP request is
received.
BUG=chromium:225914
TEST=Unit tests + manual: Used core code base with real sockets and
a veth pair with hostapd running in wired mode on the other end.
Change-Id: If2e1c0100dc65875ef61d5747482cc1bae3cfd36
Reviewed-on: https://gerrit.chromium.org/gerrit/47212
Commit-Queue: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
diff --git a/Makefile b/Makefile
index c0cb3c1..6bc5120 100644
--- a/Makefile
+++ b/Makefile
@@ -234,6 +234,7 @@
dhcpcd_proxy.o \
diagnostics_reporter.o \
dns_client.o \
+ eap_listener.o \
endpoint.o \
ephemeral_profile.o \
error.o \
@@ -349,6 +350,7 @@
dhcp_provider_unittest.o \
diagnostics_reporter_unittest.o \
dns_client_unittest.o \
+ eap_listener_unittest.o \
error_unittest.o \
ethernet_service_unittest.o \
file_reader_unittest.o \
diff --git a/arp_client_unittest.cc b/arp_client_unittest.cc
index e72ce4c..b55fdfe 100644
--- a/arp_client_unittest.cc
+++ b/arp_client_unittest.cc
@@ -22,7 +22,6 @@
using testing::InSequence;
using testing::Invoke;
using testing::Mock;
-using testing::Test;
using testing::Return;
using testing::StrictMock;
using testing::Test;
diff --git a/eap_listener.cc b/eap_listener.cc
new file mode 100644
index 0000000..11a0ca2
--- /dev/null
+++ b/eap_listener.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2013 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/eap_listener.h"
+
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <netinet/in.h>
+
+#include <base/compiler_specific.h>
+#include <base/bind.h>
+
+#include "shill/eap_protocol.h"
+#include "shill/event_dispatcher.h"
+#include "shill/logging.h"
+#include "shill/sockets.h"
+
+namespace shill {
+
+const size_t EapListener::kMaxEapPacketLength =
+ sizeof(eap_protocol::Ieee8021xHdr) + sizeof(eap_protocol::EapHeader);
+
+EapListener::EapListener(EventDispatcher *event_dispatcher,
+ int interface_index)
+ : dispatcher_(event_dispatcher),
+ interface_index_(interface_index),
+ sockets_(new Sockets()),
+ socket_(-1) {}
+
+EapListener::~EapListener() {}
+
+bool EapListener::Start() {
+ if (!CreateSocket()) {
+ LOG(ERROR) << "Could not open EAP listener socket.";
+ Stop();
+ return false;
+ }
+
+ receive_request_handler_.reset(
+ dispatcher_->CreateReadyHandler(
+ socket_,
+ IOHandler::kModeInput,
+ base::Bind(&EapListener::ReceiveRequest, base::Unretained(this))));
+
+ return true;
+}
+
+void EapListener::Stop() {
+ receive_request_handler_.reset();
+ socket_closer_.reset();
+ socket_ = -1;
+}
+
+
+bool EapListener::CreateSocket() {
+ int socket = sockets_->Socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_PAE));
+ if (socket == -1) {
+ PLOG(ERROR) << "Could not create EAP listener socket";
+ return false;
+ }
+ socket_ = socket;
+ socket_closer_.reset(new ScopedSocketCloser(sockets_.get(), socket_));
+
+ 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(ETH_P_PAE);
+ 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;
+}
+
+void EapListener::ReceiveRequest(int fd) {
+ struct ALIGNAS(1) {
+ eap_protocol::Ieee8021xHdr onex_header;
+ eap_protocol::EapHeader eap_header;
+ } payload;
+ sockaddr_ll remote_address;
+ memset(&remote_address, 0, sizeof(remote_address));
+ socklen_t socklen = sizeof(remote_address);
+ int result = sockets_->RecvFrom(
+ socket_, &payload, sizeof(payload), 0,
+ reinterpret_cast<struct sockaddr *>(&remote_address),
+ &socklen);
+ if (result < 0) {
+ PLOG(ERROR) << "Socket recvfrom failed";
+ Stop();
+ return;
+ }
+
+ if (result != sizeof(payload)) {
+ LOG(INFO) << "Short EAP packet received";
+ return;
+ }
+
+ if (payload.onex_header.version != eap_protocol::kIeee8021xEapolVersion2 ||
+ payload.onex_header.type != eap_protocol::kIIeee8021xTypeEapPacket ||
+ payload.eap_header.code != eap_protocol::kEapCodeRequest) {
+ LOG(INFO) << "Packet is not a valid EAP request";
+ return;
+ }
+
+ request_received_callback_.Run();
+}
+
+} // namespace shill
diff --git a/eap_listener.h b/eap_listener.h
new file mode 100644
index 0000000..0e3b9f7
--- /dev/null
+++ b/eap_listener.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2013 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.
+
+#ifndef SHILL_EAP_LISTENER_H_
+#define SHILL_EAP_LISTENER_H_
+
+#include <base/basictypes.h>
+#include <base/callback.h>
+#include <base/memory/scoped_ptr.h>
+
+namespace shill {
+
+class EventDispatcher;
+class IOHandler;
+class ScopedSocketCloser;
+class Sockets;
+
+// Listens for EAP packets on |interface_index| and invokes a
+// callback when a request frame arrives.
+class EapListener {
+ public:
+ typedef base::Callback<void()> EapRequestReceivedCallback;
+
+ explicit EapListener(EventDispatcher *event_dispatcher,
+ int interface_index);
+ virtual ~EapListener();
+
+ // Create a socket for tranmission and reception. Returns true
+ // if successful, false otherwise.
+ virtual bool Start();
+
+ // Destroy the client socket.
+ virtual void Stop();
+
+ // Setter for |request_received_callback_|.
+ virtual void set_request_received_callback(
+ const EapRequestReceivedCallback &callback) {
+ request_received_callback_ = callback;
+ }
+
+ private:
+ friend class EapListenerTest;
+
+ // The largest EAP packet we expect to receive.
+ static const size_t kMaxEapPacketLength;
+
+ // Creates |socket_|. Returns true on succes, false on failure.
+ bool CreateSocket();
+
+ // Retrieves an EAP packet from |socket_|. This is the callback method
+ // configured on |receive_request_handler_|.
+ void ReceiveRequest(int fd);
+
+ // Event dispatcher to use for creating an input handler.
+ EventDispatcher *dispatcher_;
+
+ // The interface index fo the device to monitor.
+ const int interface_index_;
+
+ // Callback handle to invoke when an EAP request is received.
+ EapRequestReceivedCallback request_received_callback_;
+
+ // Sockets instance to perform socket calls on.
+ scoped_ptr<Sockets> sockets_;
+
+ // Receive socket configured to receive PAE (Port Access Entity) packets.
+ int socket_;
+
+ // Scoped socket closer for the receive |socket_|.
+ scoped_ptr<ScopedSocketCloser> socket_closer_;
+
+ // Input handler for |socket_|. Calls ReceiveRequest().
+ scoped_ptr<IOHandler> receive_request_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(EapListener);
+};
+
+} // namespace shill
+
+#endif // SHILL_EAP_LISTENER_H_
diff --git a/eap_listener_unittest.cc b/eap_listener_unittest.cc
new file mode 100644
index 0000000..53eb3eb
--- /dev/null
+++ b/eap_listener_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2013 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/eap_listener.h"
+
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include <base/bind.h>
+#include <gtest/gtest.h>
+
+#include "shill/byte_string.h"
+#include "shill/eap_protocol.h"
+#include "shill/mock_event_dispatcher.h"
+#include "shill/mock_log.h"
+#include "shill/mock_sockets.h"
+
+using testing::_;
+using testing::HasSubstr;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace shill {
+
+class EapListenerTest : public testing::Test {
+ public:
+ EapListenerTest() : listener_(&dispatcher_, kInterfaceIndex) {}
+ virtual ~EapListenerTest() {}
+
+ virtual void SetUp() {
+ sockets_ = new StrictMock<MockSockets>();
+ // Passes ownership.
+ listener_.sockets_.reset(sockets_);
+ listener_.set_request_received_callback(
+ base::Bind(&EapListenerTest::ReceiveCallback, base::Unretained(this)));
+ }
+
+ virtual void TearDown() {
+ if (GetSocket() == kSocketFD) {
+ EXPECT_CALL(*sockets_, Close(kSocketFD));
+ listener_.Stop();
+ }
+ }
+
+ ssize_t SimulateRecvFrom(int sockfd, void *buf, size_t len, int flags,
+ struct sockaddr *src_addr, socklen_t *addrlen);
+
+ MOCK_METHOD0(ReceiveCallback, void());
+
+ protected:
+ static const int kInterfaceIndex;
+ static const int kSocketFD;
+ static const uint8 kEapPacketPayload[];
+
+ bool CreateSocket() { return listener_.CreateSocket(); }
+ int GetInterfaceIndex() { return listener_.interface_index_; }
+ size_t GetMaxEapPacketLength() { return EapListener::kMaxEapPacketLength; }
+ int GetSocket() { return listener_.socket_; }
+ void StartListener() { StartListenerWithFD(kSocketFD); }
+ void ReceiveRequest() { listener_.ReceiveRequest(kSocketFD); }
+ void StartListenerWithFD(int fd);
+
+ MockEventDispatcher dispatcher_;
+ EapListener listener_;
+
+ // Owned by EapListener, and tracked here only for mocks.
+ MockSockets *sockets_;
+
+ // Tests can assign this in order to set the data isreturned in our
+ // mock implementation of Sockets::RecvFrom().
+ ByteString recvfrom_reply_data_;
+};
+
+// static
+const int EapListenerTest::kInterfaceIndex = 123;
+const int EapListenerTest::kSocketFD = 456;
+const uint8 EapListenerTest::kEapPacketPayload[] = {
+ eap_protocol::kIeee8021xEapolVersion2,
+ eap_protocol::kIIeee8021xTypeEapPacket,
+ 0x00, 0x00, // Payload length (should be 5, but unparsed by EapListener).
+ eap_protocol::kEapCodeRequest,
+ 0x00, // Identifier (unparsed).
+ 0x00, 0x00, // Packet length (should be 5, but unparsed by EapListener).
+ 0x01 // Request type: Identity (not parsed by EapListener).
+};
+
+ssize_t EapListenerTest::SimulateRecvFrom(int sockfd, void *buf, size_t len,
+ int flags, struct sockaddr *src_addr,
+ socklen_t *addrlen) {
+ // Mimic behavior of the real recvfrom -- copy no more than requested.
+ int copy_length = std::min(recvfrom_reply_data_.GetLength(), len);
+ memcpy(buf, recvfrom_reply_data_.GetConstData(), copy_length);
+ return copy_length;
+}
+
+MATCHER_P(IsEapLinkAddress, interface_index, "") {
+ const struct sockaddr_ll *socket_address =
+ reinterpret_cast<const struct sockaddr_ll *>(arg);
+ return socket_address->sll_family == AF_PACKET &&
+ socket_address->sll_protocol == htons(ETH_P_PAE) &&
+ socket_address->sll_ifindex == interface_index;
+}
+
+void EapListenerTest::StartListenerWithFD(int fd) {
+ EXPECT_CALL(*sockets_, Socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_PAE)))
+ .WillOnce(Return(fd));
+ EXPECT_CALL(*sockets_, SetNonBlocking(fd)).WillOnce(Return(0));
+ EXPECT_CALL(*sockets_,
+ Bind(fd, IsEapLinkAddress(kInterfaceIndex), sizeof(sockaddr_ll)))
+ .WillOnce(Return(0));
+ EXPECT_CALL(dispatcher_, CreateReadyHandler(fd, IOHandler::kModeInput, _));
+ EXPECT_TRUE(listener_.Start());
+ EXPECT_EQ(fd, listener_.socket_);
+}
+
+TEST_F(EapListenerTest, Constructor) {
+ EXPECT_EQ(kInterfaceIndex, GetInterfaceIndex());
+ EXPECT_EQ(8, GetMaxEapPacketLength());
+ EXPECT_EQ(-1, GetSocket());
+}
+
+TEST_F(EapListenerTest, SocketOpenFail) {
+ ScopedMockLog log;
+ EXPECT_CALL(log,
+ Log(logging::LOG_ERROR, _,
+ HasSubstr("Could not create EAP listener socket"))).Times(1);
+
+ EXPECT_CALL(*sockets_, Socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_PAE)))
+ .WillOnce(Return(-1));
+ EXPECT_FALSE(CreateSocket());
+}
+
+TEST_F(EapListenerTest, SocketNonBlockingFail) {
+ ScopedMockLog log;
+ EXPECT_CALL(log,
+ Log(logging::LOG_ERROR, _,
+ HasSubstr("Could not set socket to be non-blocking"))).Times(1);
+
+ EXPECT_CALL(*sockets_, Socket(_, _, _)).WillOnce(Return(kSocketFD));
+ EXPECT_CALL(*sockets_, SetNonBlocking(kSocketFD)).WillOnce(Return(-1));
+ EXPECT_FALSE(CreateSocket());
+}
+
+TEST_F(EapListenerTest, SocketBindFail) {
+ ScopedMockLog log;
+ EXPECT_CALL(log,
+ Log(logging::LOG_ERROR, _,
+ HasSubstr("Could not bind socket to interface"))).Times(1);
+
+ EXPECT_CALL(*sockets_, Socket(_, _, _)).WillOnce(Return(kSocketFD));
+ EXPECT_CALL(*sockets_, SetNonBlocking(kSocketFD)).WillOnce(Return(0));
+ EXPECT_CALL(*sockets_, Bind(kSocketFD, _, _)).WillOnce(Return(-1));
+ EXPECT_FALSE(CreateSocket());
+}
+
+TEST_F(EapListenerTest, StartSuccess) {
+ StartListener();
+}
+
+TEST_F(EapListenerTest, StartMultipleTimes) {
+ const int kFirstSocketFD = kSocketFD + 1;
+ StartListenerWithFD(kFirstSocketFD);
+ EXPECT_CALL(*sockets_, Close(kFirstSocketFD));
+ StartListener();
+}
+
+TEST_F(EapListenerTest, Stop) {
+ StartListener();
+ EXPECT_EQ(kSocketFD, GetSocket());
+ EXPECT_CALL(*sockets_, Close(kSocketFD));
+ listener_.Stop();
+ EXPECT_EQ(-1, GetSocket());
+}
+
+
+TEST_F(EapListenerTest, ReceiveFail) {
+ StartListener();
+ EXPECT_CALL(*sockets_,
+ RecvFrom(kSocketFD, _, GetMaxEapPacketLength(), 0, _, _))
+ .WillOnce(Return(-1));
+ EXPECT_CALL(*this, ReceiveCallback()).Times(0);
+ EXPECT_CALL(*sockets_, Close(kSocketFD));
+
+ ScopedMockLog log;
+ // RecvFrom returns an error.
+ EXPECT_CALL(log,
+ Log(logging::LOG_ERROR, _,
+ HasSubstr("Socket recvfrom failed"))).Times(1);
+ ReceiveRequest();
+}
+
+TEST_F(EapListenerTest, ReceiveEmpty) {
+ StartListener();
+ EXPECT_CALL(*sockets_,
+ RecvFrom(kSocketFD, _, GetMaxEapPacketLength(), 0, _, _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*this, ReceiveCallback()).Times(0);
+ ReceiveRequest();
+}
+
+TEST_F(EapListenerTest, ReceiveShort) {
+ StartListener();
+ recvfrom_reply_data_ = ByteString(kEapPacketPayload,
+ GetMaxEapPacketLength() - 1);
+ EXPECT_CALL(*sockets_,
+ RecvFrom(kSocketFD, _, GetMaxEapPacketLength(), 0, _, _))
+ .WillOnce(Invoke(this, &EapListenerTest::SimulateRecvFrom));
+ EXPECT_CALL(*this, ReceiveCallback()).Times(0);
+ ScopedMockLog log;
+ EXPECT_CALL(log,
+ Log(logging::LOG_INFO, _,
+ HasSubstr("Short EAP packet received"))).Times(1);
+ ReceiveRequest();
+}
+
+TEST_F(EapListenerTest, ReceiveInvalid) {
+ StartListener();
+ // We're partially initializing this field, just making sure at least one
+ // part of it is incorrect.
+ uint8 bad_payload[sizeof(kEapPacketPayload)] = {
+ eap_protocol::kIeee8021xEapolVersion2 - 1
+ };
+ recvfrom_reply_data_ = ByteString(bad_payload, sizeof(bad_payload));
+ EXPECT_CALL(*sockets_,
+ RecvFrom(kSocketFD, _, GetMaxEapPacketLength(), 0, _, _))
+ .WillOnce(Invoke(this, &EapListenerTest::SimulateRecvFrom));
+ EXPECT_CALL(*this, ReceiveCallback()).Times(0);
+ ScopedMockLog log;
+ EXPECT_CALL(log,
+ Log(logging::LOG_INFO, _,
+ HasSubstr("Packet is not a valid EAP request"))).Times(1);
+ ReceiveRequest();
+}
+
+TEST_F(EapListenerTest, ReceiveSuccess) {
+ StartListener();
+ recvfrom_reply_data_ =
+ ByteString(kEapPacketPayload, sizeof(kEapPacketPayload));
+ EXPECT_CALL(*sockets_,
+ RecvFrom(kSocketFD, _, GetMaxEapPacketLength(), 0, _, _))
+ .WillOnce(Invoke(this, &EapListenerTest::SimulateRecvFrom));
+ EXPECT_CALL(*this, ReceiveCallback()).Times(1);
+ ReceiveRequest();
+}
+
+} // namespace shill
diff --git a/eap_protocol.h b/eap_protocol.h
new file mode 100644
index 0000000..34cf224
--- /dev/null
+++ b/eap_protocol.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2013 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.
+
+#ifndef SHILL_EAP_PROTOCOL_H_
+#define SHILL_EAP_PROTOCOL_H_
+
+#include <base/basictypes.h>
+#include <base/compiler_specific.h>
+
+namespace shill {
+
+namespace eap_protocol {
+
+struct ALIGNAS(1) Ieee8021xHdr {
+ uint8 version;
+ uint8 type;
+ uint16 length;
+};
+
+enum IeeeEapolVersion {
+ kIeee8021xEapolVersion2 = 2
+};
+
+enum IeeeEapolType {
+ kIIeee8021xTypeEapPacket = 0,
+ kIIeee8021xTypeEapolStart = 1,
+ kIIeee8021xTypeEapolLogoff = 2,
+ kIIeee8021xTypeEapolKey = 3,
+ kIIeee8021xTypeEapolEncapsulatedAsfAlert = 4
+};
+
+struct ALIGNAS(1) EapHeader {
+ uint8 code;
+ uint8 identifier;
+ uint16 length; // including code and identifier; network byte order
+};
+
+enum EapCode {
+ kEapCodeRequest = 1,
+ kEapCodeRespnose = 2,
+ kEapCodeSuccess = 3,
+ kEapCodeFailure = 4
+};
+
+} // namespace eap_protocol
+
+} // namespace shill
+
+#endif // SHILL_EAP_PROTOCOL_H_