face: EthernetChannel
Change-Id: I8d9be571a2b0abe0cfad09756ea6c691e0679450
Refs: #4011
diff --git a/.jenkins.d/20-tests.sh b/.jenkins.d/20-tests.sh
index 1e39e10..b5d512a 100755
--- a/.jenkins.d/20-tests.sh
+++ b/.jenkins.d/20-tests.sh
@@ -49,6 +49,6 @@
# Then use sudo to run those tests that need superuser powers
sudo -E ./build/unit-tests-core -t TestPrivilegeHelper $(ut_log_args core-privilege)
-sudo -E ./build/unit-tests-daemon -t Face/*EthernetTransport $(ut_log_args daemon-ethernet)
+sudo -E ./build/unit-tests-daemon -t Face/*Ethernet* $(ut_log_args daemon-ethernet)
sudo -E ./build/unit-tests-daemon -t Face/*Factory/ProcessConfig $(ut_log_args daemon-face-config)
sudo -E ./build/unit-tests-daemon -t Mgmt/TestGeneralConfigSection/UserAndGroupConfig,NoUserConfig $(ut_log_args daemon-user-config)
diff --git a/core/network-interface.hpp b/core/network-interface.hpp
index 0034052..32caaed 100644
--- a/core/network-interface.hpp
+++ b/core/network-interface.hpp
@@ -1,12 +1,12 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
- * Copyright (c) 2014, Regents of the University of California,
- * Arizona Board of Regents,
- * Colorado State University,
- * University Pierre & Marie Curie, Sorbonne University,
- * Washington University in St. Louis,
- * Beijing Institute of Technology,
- * The University of Memphis
+ * Copyright (c) 2014-2017, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
*
* This file is part of NFD (Named Data Networking Forwarding Daemon).
* See AUTHORS.md for complete list of NFD authors and contributors.
@@ -34,7 +34,9 @@
namespace nfd {
-namespace ethernet = ndn::util::ethernet;
+namespace ethernet {
+using namespace ndn::util::ethernet;
+} // namespace ethernet
/** \brief contains information about a network interface
*/
diff --git a/daemon/face/ethernet-channel.cpp b/daemon/face/ethernet-channel.cpp
new file mode 100644
index 0000000..d0676cc
--- /dev/null
+++ b/daemon/face/ethernet-channel.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ethernet-channel.hpp"
+#include "ethernet-protocol.hpp"
+#include "generic-link-service.hpp"
+#include "unicast-ethernet-transport.hpp"
+#include "core/global-io.hpp"
+
+#include <boost/range/adaptor/map.hpp>
+#include <pcap/pcap.h>
+
+namespace nfd {
+namespace face {
+
+NFD_LOG_INIT("EthernetChannel");
+
+EthernetChannel::EthernetChannel(const NetworkInterfaceInfo& localEndpoint,
+ time::nanoseconds idleTimeout)
+ : m_localEndpoint(localEndpoint)
+ , m_isListening(false)
+ , m_socket(getGlobalIoService())
+ , m_pcap(m_localEndpoint.name)
+ , m_idleFaceTimeout(idleTimeout)
+#ifdef _DEBUG
+ , m_nDropped(0)
+#endif
+{
+ setUri(FaceUri::fromDev(m_localEndpoint.name));
+ NFD_LOG_CHAN_INFO("Creating channel");
+}
+
+void
+EthernetChannel::connect(const ethernet::Address& remoteEndpoint,
+ ndn::nfd::FacePersistency persistency,
+ const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onConnectFailed)
+{
+ shared_ptr<Face> face;
+ try {
+ face = createFace(remoteEndpoint, persistency).second;
+ }
+ catch (const boost::system::system_error& e) {
+ NFD_LOG_CHAN_DEBUG("Face creation for " << remoteEndpoint << " failed: " << e.what());
+ if (onConnectFailed)
+ onConnectFailed(504, std::string("Face creation failed: ") + e.what());
+ return;
+ }
+
+ // Need to invoke the callback regardless of whether or not we had already
+ // created the face so that control responses and such can be sent
+ onFaceCreated(face);
+}
+
+void
+EthernetChannel::listen(const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onFaceCreationFailed)
+{
+ if (isListening()) {
+ NFD_LOG_CHAN_WARN("Already listening");
+ return;
+ }
+ m_isListening = true;
+
+ try {
+ m_pcap.activate(DLT_EN10MB);
+ m_socket.assign(m_pcap.getFd());
+ }
+ catch (const PcapHelper::Error& e) {
+ BOOST_THROW_EXCEPTION(Error(e.what()));
+ }
+ updateFilter();
+
+ asyncRead(onFaceCreated, onFaceCreationFailed);
+ NFD_LOG_CHAN_DEBUG("Started listening");
+}
+
+void
+EthernetChannel::asyncRead(const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onReceiveFailed)
+{
+ m_socket.async_read_some(boost::asio::null_buffers(),
+ bind(&EthernetChannel::handleRead, this,
+ boost::asio::placeholders::error,
+ onFaceCreated, onReceiveFailed));
+}
+
+void
+EthernetChannel::handleRead(const boost::system::error_code& error,
+ const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onReceiveFailed)
+{
+ if (error) {
+ if (error != boost::asio::error::operation_aborted) {
+ NFD_LOG_CHAN_DEBUG("Receive failed: " << error.message());
+ if (onReceiveFailed)
+ onReceiveFailed(500, "Receive failed: " + error.message());
+ }
+ return;
+ }
+
+ const uint8_t* pkt;
+ size_t len;
+ std::string err;
+ std::tie(pkt, len, err) = m_pcap.readNextPacket();
+
+ if (pkt == nullptr) {
+ NFD_LOG_CHAN_WARN("Read error: " << err);
+ }
+ else {
+ const ether_header* eh;
+ std::tie(eh, err) = ethernet::checkFrameHeader(pkt, len, m_localEndpoint.etherAddress,
+ m_localEndpoint.etherAddress);
+ if (eh == nullptr) {
+ NFD_LOG_CHAN_DEBUG(err);
+ }
+ else {
+ ethernet::Address sender(eh->ether_shost);
+ pkt += ethernet::HDR_LEN;
+ len -= ethernet::HDR_LEN;
+ processIncomingPacket(pkt, len, sender, onFaceCreated, onReceiveFailed);
+ }
+ }
+
+#ifdef _DEBUG
+ size_t nDropped = m_pcap.getNDropped();
+ if (nDropped - m_nDropped > 0)
+ NFD_LOG_CHAN_DEBUG("Detected " << nDropped - m_nDropped << " dropped frame(s)");
+ m_nDropped = nDropped;
+#endif
+
+ asyncRead(onFaceCreated, onReceiveFailed);
+}
+
+void
+EthernetChannel::processIncomingPacket(const uint8_t* packet, size_t length,
+ const ethernet::Address& sender,
+ const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onReceiveFailed)
+{
+ NFD_LOG_CHAN_TRACE("New peer " << sender);
+
+ bool isCreated = false;
+ shared_ptr<Face> face;
+ try {
+ std::tie(isCreated, face) = createFace(sender, ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+ }
+ catch (const EthernetTransport::Error& e) {
+ NFD_LOG_CHAN_DEBUG("Face creation for " << sender << " failed: " << e.what());
+ if (onReceiveFailed)
+ onReceiveFailed(504, std::string("Face creation failed: ") + e.what());
+ return;
+ }
+
+ if (isCreated)
+ onFaceCreated(face);
+ else
+ NFD_LOG_CHAN_DEBUG("Received frame for existing face");
+
+ // dispatch the packet to the face for processing
+ auto* transport = static_cast<UnicastEthernetTransport*>(face->getTransport());
+ transport->receivePayload(packet, length, sender);
+}
+
+std::pair<bool, shared_ptr<Face>>
+EthernetChannel::createFace(const ethernet::Address& remoteEndpoint,
+ ndn::nfd::FacePersistency persistency)
+{
+ auto it = m_channelFaces.find(remoteEndpoint);
+ if (it != m_channelFaces.end()) {
+ // we already have a face for this endpoint, so reuse it
+ NFD_LOG_CHAN_TRACE("Reusing existing face for " << remoteEndpoint);
+ return {false, it->second};
+ }
+
+ // else, create a new face
+ auto linkService = make_unique<GenericLinkService>();
+ auto transport = make_unique<UnicastEthernetTransport>(m_localEndpoint, remoteEndpoint,
+ persistency, m_idleFaceTimeout);
+ auto face = make_shared<Face>(std::move(linkService), std::move(transport));
+
+ m_channelFaces[remoteEndpoint] = face;
+ connectFaceClosedSignal(*face, [this, remoteEndpoint] {
+ m_channelFaces.erase(remoteEndpoint);
+ updateFilter();
+ });
+ updateFilter();
+
+ return {true, face};
+}
+
+void
+EthernetChannel::updateFilter()
+{
+ if (!isListening())
+ return;
+
+ std::string filter = "(ether proto " + to_string(ethernet::ETHERTYPE_NDN) +
+ ") && (ether dst " + m_localEndpoint.etherAddress.toString() + ")";
+ for (const auto& addr : m_channelFaces | boost::adaptors::map_keys) {
+ filter += " && (not ether src " + addr.toString() + ")";
+ }
+ // "not vlan" must appear last in the filter expression, or the
+ // rest of the filter won't work as intended, see pcap-filter(7)
+ filter += " && (not vlan)";
+
+ NFD_LOG_CHAN_TRACE("Updating filter: " << filter);
+ m_pcap.setPacketFilter(filter.data());
+}
+
+} // namespace face
+} // namespace nfd
diff --git a/daemon/face/ethernet-channel.hpp b/daemon/face/ethernet-channel.hpp
new file mode 100644
index 0000000..cb4fbe9
--- /dev/null
+++ b/daemon/face/ethernet-channel.hpp
@@ -0,0 +1,146 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_FACE_ETHERNET_CHANNEL_HPP
+#define NFD_DAEMON_FACE_ETHERNET_CHANNEL_HPP
+
+#include "channel.hpp"
+#include "pcap-helper.hpp"
+#include "core/network-interface.hpp"
+
+namespace nfd {
+namespace face {
+
+/**
+ * \brief Class implementing Ethernet-based channel to create faces
+ */
+class EthernetChannel : public Channel
+{
+public:
+ /**
+ * \brief EthernetChannel-related error
+ */
+ class Error : public std::runtime_error
+ {
+ public:
+ explicit
+ Error(const std::string& what)
+ : std::runtime_error(what)
+ {
+ }
+ };
+
+ /**
+ * \brief Create an Ethernet channel on the given \p localEndpoint (network interface)
+ *
+ * To enable creation of faces upon incoming connections,
+ * one needs to explicitly call EthernetChannel::listen method.
+ */
+ EthernetChannel(const NetworkInterfaceInfo& localEndpoint,
+ time::nanoseconds idleTimeout);
+
+ bool
+ isListening() const override
+ {
+ return m_isListening;
+ }
+
+ size_t
+ size() const override
+ {
+ return m_channelFaces.size();
+ }
+
+ /**
+ * \brief Create a unicast Ethernet face toward \p remoteEndpoint
+ *
+ * \param remoteEndpoint The remote Ethernet endpoint
+ * \param persistency Persistency of the newly created face
+ * \param onFaceCreated Callback to notify successful creation of the face
+ * \param onConnectFailed Callback to notify errors
+ */
+ void
+ connect(const ethernet::Address& remoteEndpoint,
+ ndn::nfd::FacePersistency persistency,
+ const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onConnectFailed);
+
+ /**
+ * \brief Start listening
+ *
+ * Enable listening on the local endpoint, waiting for incoming frames,
+ * and creating a face when a frame is received from a new remote host.
+ *
+ * Faces created in this way will have on-demand persistency.
+ *
+ * \param onFaceCreated Callback to notify successful creation of a face
+ * \param onFaceCreationFailed Callback to notify errors
+ * \throw Error
+ */
+ void
+ listen(const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onFaceCreationFailed);
+
+private:
+ void
+ asyncRead(const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onReceiveFailed);
+
+ void
+ handleRead(const boost::system::error_code& error,
+ const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onReceiveFailed);
+
+ void
+ processIncomingPacket(const uint8_t* packet, size_t length,
+ const ethernet::Address& sender,
+ const FaceCreatedCallback& onFaceCreated,
+ const FaceCreationFailedCallback& onReceiveFailed);
+
+ std::pair<bool, shared_ptr<Face>>
+ createFace(const ethernet::Address& remoteEndpoint,
+ ndn::nfd::FacePersistency persistency);
+
+ void
+ updateFilter();
+
+private:
+ const NetworkInterfaceInfo m_localEndpoint;
+ bool m_isListening;
+ boost::asio::posix::stream_descriptor m_socket;
+ PcapHelper m_pcap;
+ std::map<ethernet::Address, shared_ptr<Face>> m_channelFaces;
+ const time::nanoseconds m_idleFaceTimeout; ///< Timeout for automatic closure of idle on-demand faces
+
+#ifdef _DEBUG
+ /// number of frames dropped by the kernel, as reported by libpcap
+ size_t m_nDropped;
+#endif
+};
+
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_DAEMON_FACE_ETHERNET_CHANNEL_HPP
diff --git a/daemon/face/ethernet-protocol.cpp b/daemon/face/ethernet-protocol.cpp
new file mode 100644
index 0000000..2ec0bb7
--- /dev/null
+++ b/daemon/face/ethernet-protocol.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ethernet-protocol.hpp"
+
+#include <arpa/inet.h> // for ntohs()
+
+namespace nfd {
+namespace ethernet {
+
+std::pair<const ether_header*, std::string>
+checkFrameHeader(const uint8_t* packet, size_t length,
+ const Address& localAddr, const Address& destAddr)
+{
+ if (length < HDR_LEN + MIN_DATA_LEN)
+ return {nullptr, "Received frame too short: " + to_string(length) + " bytes"};
+
+ const ether_header* eh = reinterpret_cast<const ether_header*>(packet);
+
+ // in some cases VLAN-tagged frames may survive the BPF filter,
+ // make sure we do not process those frames (see #3348)
+ if (ntohs(eh->ether_type) != ETHERTYPE_NDN)
+ return {nullptr, "Received frame with wrong ethertype: " + to_string(ntohs(eh->ether_type))};
+
+#ifdef _DEBUG
+ Address shost(eh->ether_shost);
+ if (shost == localAddr)
+ return {nullptr, "Received frame sent by this host"};
+
+ Address dhost(eh->ether_dhost);
+ if (dhost != destAddr)
+ return {nullptr, "Received frame addressed to another host or multicast group: " + dhost.toString()};
+#endif
+
+ return {eh, ""};
+}
+
+} // namespace ethernet
+} // namespace nfd
diff --git a/daemon/face/ethernet-protocol.hpp b/daemon/face/ethernet-protocol.hpp
new file mode 100644
index 0000000..4eca7cf
--- /dev/null
+++ b/daemon/face/ethernet-protocol.hpp
@@ -0,0 +1,43 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_FACE_ETHERNET_PROTOCOL_HPP
+#define NFD_DAEMON_FACE_ETHERNET_PROTOCOL_HPP
+
+#include "core/network-interface.hpp"
+
+#include <net/ethernet.h>
+
+namespace nfd {
+namespace ethernet {
+
+std::pair<const ether_header*, std::string>
+checkFrameHeader(const uint8_t* packet, size_t length,
+ const Address& localAddr, const Address& destAddr);
+
+} // namespace ethernet
+} // namespace nfd
+
+#endif // NFD_DAEMON_FACE_ETHERNET_PROTOCOL_HPP
diff --git a/daemon/face/ethernet-transport.cpp b/daemon/face/ethernet-transport.cpp
index 0773e85..9f6d465 100644
--- a/daemon/face/ethernet-transport.cpp
+++ b/daemon/face/ethernet-transport.cpp
@@ -24,16 +24,16 @@
*/
#include "ethernet-transport.hpp"
+#include "ethernet-protocol.hpp"
#include "core/global-io.hpp"
#include <pcap/pcap.h>
-#include <cerrno> // for errno
-#include <cstring> // for memcpy(), strerror(), strncpy()
-#include <arpa/inet.h> // for htons() and ntohs()
-#include <net/ethernet.h> // for struct ether_header
-#include <net/if.h> // for struct ifreq
-#include <sys/ioctl.h> // for ioctl()
+#include <cerrno> // for errno
+#include <cstring> // for memcpy(), strerror(), strncpy()
+#include <arpa/inet.h> // for htons()
+#include <net/if.h> // for struct ifreq
+#include <sys/ioctl.h> // for ioctl()
namespace nfd {
namespace face {
@@ -62,10 +62,7 @@
// do this after assigning m_socket because getInterfaceMtu uses it
this->setMtu(getInterfaceMtu());
- m_socket.async_read_some(boost::asio::null_buffers(),
- bind(&EthernetTransport::handleRead, this,
- boost::asio::placeholders::error,
- boost::asio::placeholders::bytes_transferred));
+ asyncRead();
}
void
@@ -114,19 +111,27 @@
buffer.prependByteArray(m_srcAddress.data(), m_srcAddress.size());
buffer.prependByteArray(m_destAddress.data(), m_destAddress.size());
- // send the packet
+ // send the frame
int sent = pcap_inject(m_pcap, buffer.buf(), buffer.size());
if (sent < 0)
NFD_LOG_FACE_ERROR("pcap_inject failed: " << m_pcap.getLastError());
else if (static_cast<size_t>(sent) < buffer.size())
- NFD_LOG_FACE_ERROR("Failed to send the full frame: bufsize=" << buffer.size() << " sent=" << sent);
+ NFD_LOG_FACE_ERROR("Failed to send the full frame: size=" << buffer.size() << " sent=" << sent);
else
// print block size because we don't want to count the padding in buffer
NFD_LOG_FACE_TRACE("Successfully sent: " << block.size() << " bytes");
}
void
-EthernetTransport::handleRead(const boost::system::error_code& error, size_t)
+EthernetTransport::asyncRead()
+{
+ m_socket.async_read_some(boost::asio::null_buffers(),
+ bind(&EthernetTransport::handleRead, this,
+ boost::asio::placeholders::error));
+}
+
+void
+EthernetTransport::handleRead(const boost::system::error_code& error)
{
if (error)
return processErrorCode(error);
@@ -136,63 +141,54 @@
std::string err;
std::tie(pkt, len, err) = m_pcap.readNextPacket();
- if (pkt == nullptr)
+ if (pkt == nullptr) {
NFD_LOG_FACE_ERROR("Read error: " << err);
- else
- processIncomingPacket(pkt, len);
+ }
+ else {
+ const ether_header* eh;
+ std::tie(eh, err) = ethernet::checkFrameHeader(pkt, len, m_srcAddress,
+ m_destAddress.isMulticast() ? m_destAddress : m_srcAddress);
+ if (eh == nullptr) {
+ NFD_LOG_FACE_DEBUG(err);
+ }
+ else {
+ ethernet::Address sender(eh->ether_shost);
+ pkt += ethernet::HDR_LEN;
+ len -= ethernet::HDR_LEN;
+ receivePayload(pkt, len, sender);
+ }
+ }
#ifdef _DEBUG
size_t nDropped = m_pcap.getNDropped();
if (nDropped - m_nDropped > 0)
- NFD_LOG_FACE_DEBUG("Detected " << nDropped - m_nDropped << " dropped packet(s)");
+ NFD_LOG_FACE_DEBUG("Detected " << nDropped - m_nDropped << " dropped frame(s)");
m_nDropped = nDropped;
#endif
- m_socket.async_read_some(boost::asio::null_buffers(),
- bind(&EthernetTransport::handleRead, this,
- boost::asio::placeholders::error,
- boost::asio::placeholders::bytes_transferred));
+ asyncRead();
}
void
-EthernetTransport::processIncomingPacket(const uint8_t* packet, size_t length)
+EthernetTransport::receivePayload(const uint8_t* payload, size_t length,
+ const ethernet::Address& sender)
{
- if (length < ethernet::HDR_LEN + ethernet::MIN_DATA_LEN) {
- NFD_LOG_FACE_WARN("Received frame is too short (" << length << " bytes)");
- return;
- }
-
- const ether_header* eh = reinterpret_cast<const ether_header*>(packet);
- const ethernet::Address sourceAddress(eh->ether_shost);
-
- // in some cases VLAN-tagged frames may survive the BPF filter,
- // make sure we do not process those frames (see #3348)
- if (ntohs(eh->ether_type) != ethernet::ETHERTYPE_NDN)
- return;
-
- // check that our BPF filter is working correctly
- BOOST_ASSERT_MSG(ethernet::Address(eh->ether_dhost) == m_destAddress,
- "Received frame addressed to another host or multicast group");
- BOOST_ASSERT_MSG(sourceAddress != m_srcAddress,
- "Received frame sent by this host");
-
- packet += ethernet::HDR_LEN;
- length -= ethernet::HDR_LEN;
-
bool isOk = false;
Block element;
- std::tie(isOk, element) = Block::fromBuffer(packet, length);
+ std::tie(isOk, element) = Block::fromBuffer(payload, length);
if (!isOk) {
- NFD_LOG_FACE_WARN("Received invalid packet from " << sourceAddress.toString());
+ NFD_LOG_FACE_WARN("Received invalid packet from " << sender);
return;
}
- NFD_LOG_FACE_TRACE("Received: " << element.size() << " bytes from " << sourceAddress.toString());
+ NFD_LOG_FACE_TRACE("Received: " << element.size() << " bytes from " << sender);
Transport::Packet tp(std::move(element));
static_assert(sizeof(tp.remoteEndpoint) >= ethernet::ADDR_LEN,
"Transport::Packet::remoteEndpoint is too small");
- std::memcpy(&tp.remoteEndpoint, sourceAddress.data(), sourceAddress.size());
+ if (m_destAddress.isMulticast()) {
+ std::memcpy(&tp.remoteEndpoint, sender.data(), sender.size());
+ }
this->receive(std::move(tp));
}
diff --git a/daemon/face/ethernet-transport.hpp b/daemon/face/ethernet-transport.hpp
index 57a7edc..575ea72 100644
--- a/daemon/face/ethernet-transport.hpp
+++ b/daemon/face/ethernet-transport.hpp
@@ -49,6 +49,16 @@
}
};
+ /**
+ * @brief Processes the payload of an incoming frame
+ * @param payload Pointer to the first byte of data after the Ethernet header
+ * @param length Payload length
+ * @param sender Sender address
+ */
+ void
+ receivePayload(const uint8_t* payload, size_t length,
+ const ethernet::Address& sender);
+
protected:
EthernetTransport(const NetworkInterfaceInfo& localEndpoint,
const ethernet::Address& remoteEndpoint);
@@ -66,19 +76,11 @@
void
sendPacket(const ndn::Block& block);
- /**
- * @brief async_read_some() callback
- */
void
- handleRead(const boost::system::error_code& error, size_t nBytesRead);
+ asyncRead();
- /**
- * @brief Processes an incoming packet as captured by libpcap
- * @param packet Pointer to the received packet, including the link-layer header
- * @param length Packet length
- */
void
- processIncomingPacket(const uint8_t* packet, size_t length);
+ handleRead(const boost::system::error_code& error);
/**
* @brief Handles errors encountered by Boost.Asio on the receive path
@@ -101,7 +103,7 @@
private:
#ifdef _DEBUG
- /// number of packets dropped by the kernel, as reported by libpcap
+ /// number of frames dropped by the kernel, as reported by libpcap
size_t m_nDropped;
#endif
};
diff --git a/daemon/face/udp-channel.cpp b/daemon/face/udp-channel.cpp
index dfbfefc..3ab9e1f 100644
--- a/daemon/face/udp-channel.cpp
+++ b/daemon/face/udp-channel.cpp
@@ -36,10 +36,10 @@
namespace ip = boost::asio::ip;
UdpChannel::UdpChannel(const udp::Endpoint& localEndpoint,
- const time::seconds& timeout)
+ time::nanoseconds idleTimeout)
: m_localEndpoint(localEndpoint)
, m_socket(getGlobalIoService())
- , m_idleFaceTimeout(timeout)
+ , m_idleFaceTimeout(idleTimeout)
{
setUri(FaceUri(m_localEndpoint));
NFD_LOG_CHAN_INFO("Creating channel");
@@ -129,6 +129,8 @@
if (isCreated)
onFaceCreated(face);
+ else
+ NFD_LOG_CHAN_DEBUG("Received datagram for existing face");
// dispatch the datagram to the face for processing
auto* transport = static_cast<UnicastUdpTransport*>(face->getTransport());
diff --git a/daemon/face/udp-channel.hpp b/daemon/face/udp-channel.hpp
index 403afac..b239157 100644
--- a/daemon/face/udp-channel.hpp
+++ b/daemon/face/udp-channel.hpp
@@ -41,17 +41,14 @@
{
public:
/**
- * \brief Create UDP channel for the local endpoint
+ * \brief Create a UDP channel on the given \p localEndpoint
*
* To enable creation of faces upon incoming connections,
* one needs to explicitly call UdpChannel::listen method.
- * The created socket is bound to the localEndpoint.
- * reuse_address option is set
- *
- * \throw UdpChannel::Error if bind on the socket fails
+ * The created socket is bound to \p localEndpoint.
*/
UdpChannel(const udp::Endpoint& localEndpoint,
- const time::seconds& timeout);
+ time::nanoseconds idleTimeout);
bool
isListening() const override
@@ -66,9 +63,12 @@
}
/**
- * \brief Create a face by establishing connection to remote endpoint
+ * \brief Create a unicast UDP face toward \p remoteEndpoint
*
- * \throw UdpChannel::Error if bind or connect on the socket fail
+ * \param remoteEndpoint The remote UDP endpoint
+ * \param persistency Persistency of the newly created face
+ * \param onFaceCreated Callback to notify successful creation of the face
+ * \param onConnectFailed Callback to notify errors
*/
void
connect(const udp::Endpoint& remoteEndpoint,
@@ -77,14 +77,15 @@
const FaceCreationFailedCallback& onConnectFailed);
/**
- * \brief Enable listening on the local endpoint, accept connections,
- * and create faces when remote host makes a connection
+ * \brief Start listening
+ *
+ * Enable listening on the local endpoint, waiting for incoming datagrams,
+ * and creating a face when a datagram is received from a new remote host.
+ *
+ * Faces created in this way will have on-demand persistency.
*
* \param onFaceCreated Callback to notify successful creation of a face
* \param onFaceCreationFailed Callback to notify errors
- *
- * Once a face is created, if it doesn't send/receive anything for
- * a period of time equal to timeout, it will be destroyed
*/
void
listen(const FaceCreatedCallback& onFaceCreated,
@@ -96,8 +97,8 @@
const FaceCreationFailedCallback& onReceiveFailed);
/**
- * \brief The channel has received a new packet from a remote
- * endpoint that is not associated with any UdpFace yet
+ * \brief The channel has received a packet from a remote
+ * endpoint not associated with any UDP face yet
*/
void
handleNewPeer(const boost::system::error_code& error,
@@ -113,9 +114,9 @@
const udp::Endpoint m_localEndpoint;
udp::Endpoint m_remoteEndpoint; ///< The latest peer that started communicating with us
boost::asio::ip::udp::socket m_socket; ///< Socket used to "accept" new peers
- std::map<udp::Endpoint, shared_ptr<Face>> m_channelFaces;
std::array<uint8_t, ndn::MAX_NDN_PACKET_SIZE> m_receiveBuffer;
- time::seconds m_idleFaceTimeout; ///< Timeout for automatic closure of idle on-demand faces
+ std::map<udp::Endpoint, shared_ptr<Face>> m_channelFaces;
+ const time::nanoseconds m_idleFaceTimeout; ///< Timeout for automatic closure of idle on-demand faces
};
} // namespace face
diff --git a/daemon/face/unix-stream-channel.hpp b/daemon/face/unix-stream-channel.hpp
index 310030b..e396d8e 100644
--- a/daemon/face/unix-stream-channel.hpp
+++ b/daemon/face/unix-stream-channel.hpp
@@ -82,13 +82,19 @@
}
/**
- * \brief Enable listening on the local endpoint, accept connections,
- * and create faces when a connection is made
+ * \brief Start listening
+ *
+ * Enable listening on the Unix socket, waiting for incoming connections,
+ * and creating a face when a connection is made.
+ *
+ * Faces created in this way will have on-demand persistency.
+ *
* \param onFaceCreated Callback to notify successful creation of the face
* \param onAcceptFailed Callback to notify when channel fails (accept call
* returns an error)
* \param backlog The maximum length of the queue of pending incoming
* connections
+ * \throw Error
*/
void
listen(const FaceCreatedCallback& onFaceCreated,
diff --git a/tests/daemon/face/ethernet-channel.t.cpp b/tests/daemon/face/ethernet-channel.t.cpp
new file mode 100644
index 0000000..238e193
--- /dev/null
+++ b/tests/daemon/face/ethernet-channel.t.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "face/ethernet-channel.hpp"
+
+#include "ethernet-fixture.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+class EthernetChannelFixture : public EthernetFixture
+{
+protected:
+ unique_ptr<EthernetChannel>
+ makeChannel()
+ {
+ BOOST_ASSERT(netifs.size() > 0);
+ return make_unique<EthernetChannel>(netifs.front(), time::seconds(2));
+ }
+};
+
+BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestEthernetChannel, EthernetChannelFixture)
+
+BOOST_AUTO_TEST_CASE(Uri)
+{
+ SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+
+ auto channel = makeChannel();
+ BOOST_CHECK_EQUAL(channel->getUri(), FaceUri::fromDev(netifs.front().name));
+}
+
+BOOST_AUTO_TEST_CASE(Listen)
+{
+ SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+
+ auto channel = makeChannel();
+ BOOST_CHECK_EQUAL(channel->isListening(), false);
+
+ channel->listen(nullptr, nullptr);
+ BOOST_CHECK_EQUAL(channel->isListening(), true);
+
+ // listen() is idempotent
+ BOOST_CHECK_NO_THROW(channel->listen(nullptr, nullptr));
+ BOOST_CHECK_EQUAL(channel->isListening(), true);
+}
+
+BOOST_AUTO_TEST_CASE(FaceClosure)
+{
+ SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+
+ auto channel = makeChannel();
+ BOOST_CHECK_EQUAL(channel->size(), 0);
+
+ shared_ptr<nfd::Face> face;
+ channel->connect({0x00, 0x00, 0x5e, 0x00, 0x53, 0x5e},
+ ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+ [&face] (const shared_ptr<nfd::Face>& newFace) {
+ BOOST_REQUIRE(newFace != nullptr);
+ face = newFace;
+ },
+ [] (uint32_t status, const std::string& reason) {
+ BOOST_FAIL("No error expected, but got: [" << status << ": " << reason << "]");
+ });
+ BOOST_CHECK_EQUAL(channel->size(), 1);
+ BOOST_REQUIRE(face != nullptr);
+
+ face->close();
+ g_io.poll();
+ BOOST_CHECK_EQUAL(channel->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestEthernetChannel
+BOOST_AUTO_TEST_SUITE_END() // Face
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
diff --git a/tests/daemon/face/ethernet-factory.t.cpp b/tests/daemon/face/ethernet-factory.t.cpp
index b8b07d1..5e7c6d2 100644
--- a/tests/daemon/face/ethernet-factory.t.cpp
+++ b/tests/daemon/face/ethernet-factory.t.cpp
@@ -153,7 +153,7 @@
{
ether
{
- mcast_group 01:00:00:00:00:01
+ mcast_group 01:00:5e:90:10:01
}
}
)CONFIG";
@@ -162,7 +162,7 @@
{
ether
{
- mcast_group 01:00:00:00:00:02
+ mcast_group 01:00:5e:90:10:02
}
}
)CONFIG";
@@ -171,14 +171,14 @@
auto etherMcastFaces = this->listEtherMcastFaces();
BOOST_REQUIRE_EQUAL(etherMcastFaces.size(), netifs.size());
BOOST_CHECK_EQUAL(etherMcastFaces.front()->getRemoteUri(),
- FaceUri(ethernet::Address(0x01, 0x00, 0x00, 0x00, 0x00, 0x01)));
+ FaceUri(ethernet::Address{0x01, 0x00, 0x5e, 0x90, 0x10, 0x01}));
parseConfig(CONFIG2, false);
g_io.poll();
etherMcastFaces = this->listEtherMcastFaces();
BOOST_REQUIRE_EQUAL(etherMcastFaces.size(), netifs.size());
BOOST_CHECK_EQUAL(etherMcastFaces.front()->getRemoteUri(),
- FaceUri(ethernet::Address(0x01, 0x00, 0x00, 0x00, 0x00, 0x02)));
+ FaceUri(ethernet::Address{0x01, 0x00, 0x5e, 0x90, 0x10, 0x02}));
}
BOOST_AUTO_TEST_CASE(Whitelist)
@@ -286,7 +286,7 @@
ether
{
mcast yes
- mcast_group 02:00:00:00:00:01
+ mcast_group 00:00:5e:00:53:5e
}
}
)CONFIG";
diff --git a/tests/daemon/face/ethernet-fixture.hpp b/tests/daemon/face/ethernet-fixture.hpp
index 029bb6e..5e3b612 100644
--- a/tests/daemon/face/ethernet-fixture.hpp
+++ b/tests/daemon/face/ethernet-fixture.hpp
@@ -56,7 +56,7 @@
}
void
- initializeUnicast(ethernet::Address remoteAddr = {0x0A, 0x01, 0x23, 0x45, 0x67, 0x89},
+ initializeUnicast(ethernet::Address remoteAddr = {0x00, 0x00, 0x5e, 0x00, 0x53, 0x5e},
ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT)
{
BOOST_ASSERT(netifs.size() > 0);
@@ -67,7 +67,7 @@
}
void
- initializeMulticast(ethernet::Address mcastGroup = ethernet::getDefaultMulticastAddress(),
+ initializeMulticast(ethernet::Address mcastGroup = {0x01, 0x00, 0x5e, 0x90, 0x10, 0x5e},
ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS)
{
BOOST_ASSERT(netifs.size() > 0);