mgmt refactoring: FaceManager
Change-Id: I292a3e3d702746d6e946a2844e6f88f64cd711a4
Refs: #2107
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
new file mode 100644
index 0000000..2991ee6
--- /dev/null
+++ b/daemon/mgmt/face-manager.cpp
@@ -0,0 +1,917 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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-manager.hpp"
+
+#include "core/network-interface.hpp"
+#include "fw/face-table.hpp"
+#include "face/tcp-factory.hpp"
+#include "face/udp-factory.hpp"
+
+#include <ndn-cxx/management/nfd-face-status.hpp>
+#include <ndn-cxx/management/nfd-channel-status.hpp>
+#include <ndn-cxx/management/nfd-face-event-notification.hpp>
+
+#ifdef HAVE_UNIX_SOCKETS
+#include "face/unix-stream-factory.hpp"
+#endif // HAVE_UNIX_SOCKETS
+
+#ifdef HAVE_LIBPCAP
+#include "face/ethernet-factory.hpp"
+#include "face/ethernet-face.hpp"
+#endif // HAVE_LIBPCAP
+
+#ifdef HAVE_WEBSOCKET
+#include "face/websocket-factory.hpp"
+#endif // HAVE_WEBSOCKET
+
+namespace nfd {
+
+NFD_LOG_INIT("FaceManager");
+
+FaceManager::FaceManager(FaceTable& faceTable,
+ Dispatcher& dispatcher,
+ CommandValidator& validator)
+ : ManagerBase(dispatcher, validator, "faces")
+ , m_faceTable(faceTable)
+{
+ registerCommandHandler<ndn::nfd::FaceCreateCommand>("create",
+ bind(&FaceManager::createFace, this, _2, _3, _4, _5));
+
+ registerCommandHandler<ndn::nfd::FaceDestroyCommand>("destroy",
+ bind(&FaceManager::destroyFace, this, _2, _3, _4, _5));
+
+ registerCommandHandler<ndn::nfd::FaceEnableLocalControlCommand>("enable-local-control",
+ bind(&FaceManager::enableLocalControl, this, _2, _3, _4, _5));
+
+ registerCommandHandler<ndn::nfd::FaceDisableLocalControlCommand>("disable-local-control",
+ bind(&FaceManager::disableLocalControl, this, _2, _3, _4, _5));
+
+ registerStatusDatasetHandler("list", bind(&FaceManager::listFaces, this, _1, _2, _3));
+ registerStatusDatasetHandler("channels", bind(&FaceManager::listChannels, this, _1, _2, _3));
+ registerStatusDatasetHandler("query", bind(&FaceManager::queryFaces, this, _1, _2, _3));
+
+ auto postNotification = registerNotificationStream("events");
+ m_faceAddConn =
+ m_faceTable.onAdd.connect(bind(&FaceManager::afterFaceAdded, this, _1, postNotification));
+ m_faceRemoveConn =
+ m_faceTable.onRemove.connect(bind(&FaceManager::afterFaceRemoved, this, _1, postNotification));
+}
+
+void
+FaceManager::setConfigFile(ConfigFile& configFile)
+{
+ configFile.addSectionHandler("face_system", bind(&FaceManager::processConfig, this, _1, _2, _3));
+}
+
+void
+FaceManager::createFace(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ FaceUri uri;
+ if (!uri.parse(parameters.getUri())) {
+ NFD_LOG_TRACE("failed to parse URI");
+ return done(ControlResponse(400, "Malformed command"));
+ }
+
+ if (!uri.isCanonical()) {
+ NFD_LOG_TRACE("received non-canonical URI");
+ return done(ControlResponse(400, "Non-canonical URI"));
+ }
+
+ FactoryMap::iterator factory = m_factories.find(uri.getScheme());
+ if (factory == m_factories.end()) {
+ return done(ControlResponse(501, "Unsupported protocol"));
+ }
+
+ try {
+ factory->second->createFace(uri,
+ parameters.getFacePersistency(),
+ bind(&FaceManager::afterCreateFaceSuccess,
+ this, parameters, _1, done),
+ bind(&FaceManager::afterCreateFaceFailure,
+ this, _1, done));
+ }
+ catch (const std::runtime_error& error) {
+ std::string errorMessage = "Face creation failed: ";
+ errorMessage += error.what();
+
+ NFD_LOG_ERROR(errorMessage);
+ return done(ControlResponse(500, errorMessage));
+ }
+ catch (const std::logic_error& error) {
+ std::string errorMessage = "Face creation failed: ";
+ errorMessage += error.what();
+
+ NFD_LOG_ERROR(errorMessage);
+ return done(ControlResponse(500, errorMessage));
+ }
+}
+
+void
+FaceManager::destroyFace(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ shared_ptr<Face> target = m_faceTable.get(parameters.getFaceId());
+ if (target) {
+ target->close();
+ }
+
+ done(ControlResponse(200, "OK").setBody(parameters.wireEncode()));
+}
+
+void
+FaceManager::enableLocalControl(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ auto result = extractLocalControlParameters(interest, parameters, done);
+ if (result.isValid) {
+ result.face->setLocalControlHeaderFeature(result.feature, true);
+ return done(ControlResponse(200, "OK").setBody(parameters.wireEncode()));
+ }
+}
+
+void
+FaceManager::disableLocalControl(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ auto result = extractLocalControlParameters(interest, parameters, done);
+ if (result.isValid) {
+ result.face->setLocalControlHeaderFeature(result.feature, false);
+ return done(ControlResponse(200, "OK").setBody(parameters.wireEncode()));
+ }
+}
+
+void
+FaceManager::afterCreateFaceSuccess(ControlParameters& parameters,
+ const shared_ptr<Face>& newFace,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ addCreatedFaceToForwarder(newFace);
+ parameters.setFaceId(newFace->getId());
+ parameters.setUri(newFace->getRemoteUri().toString());
+ parameters.setFacePersistency(newFace->getPersistency());
+
+ done(ControlResponse(200, "OK").setBody(parameters.wireEncode()));
+}
+
+void
+FaceManager::afterCreateFaceFailure(const std::string& reason,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ NFD_LOG_DEBUG("Failed to create face: " << reason);
+
+ done(ControlResponse(408, "Failed to create face: " + reason));
+}
+
+FaceManager::ExtractLocalControlParametersResult
+FaceManager::extractLocalControlParameters(const Interest& request,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done)
+{
+ ExtractLocalControlParametersResult result;
+ result.isValid = false;
+
+ auto face = m_faceTable.get(request.getIncomingFaceId());
+ if (!static_cast<bool>(face)) {
+ NFD_LOG_DEBUG("command result: faceid " << request.getIncomingFaceId() << " not found");
+ done(ControlResponse(410, "Face not found"));
+ return result;
+ }
+
+ if (!face->isLocal()) {
+ NFD_LOG_DEBUG("command result: cannot enable local control on non-local faceid " <<
+ face->getId());
+ done(ControlResponse(412, "Face is non-local"));
+ return result;
+ }
+
+ result.isValid = true;
+ result.face = dynamic_pointer_cast<LocalFace>(face);
+ result.feature = static_cast<LocalControlFeature>(parameters.getLocalControlFeature());
+
+ return result;
+}
+
+void
+FaceManager::listFaces(const Name& topPrefix, const Interest& interest,
+ ndn::mgmt::StatusDatasetContext& context)
+{
+ for (const auto& face : m_faceTable) {
+ context.append(face->getFaceStatus().wireEncode());
+ }
+ context.end();
+}
+
+void
+FaceManager::listChannels(const Name& topPrefix, const Interest& interest,
+ ndn::mgmt::StatusDatasetContext& context)
+{
+ std::set<shared_ptr<ProtocolFactory>> seenFactories;
+
+ for (auto i = m_factories.begin(); i != m_factories.end(); ++i) {
+ const shared_ptr<ProtocolFactory>& factory = i->second;
+
+ if (seenFactories.find(factory) != seenFactories.end()) {
+ continue;
+ }
+ seenFactories.insert(factory);
+
+ std::list<shared_ptr<const Channel>> channels = factory->getChannels();
+
+ for (auto j = channels.begin(); j != channels.end(); ++j) {
+ ndn::nfd::ChannelStatus entry;
+ entry.setLocalUri((*j)->getUri().toString());
+ context.append(entry.wireEncode());
+ }
+ }
+
+ context.end();
+}
+
+void
+FaceManager::queryFaces(const Name& topPrefix, const Interest& interest,
+ ndn::mgmt::StatusDatasetContext& context)
+{
+ ndn::nfd::FaceQueryFilter faceFilter;
+ const Name& query = interest.getName();
+ try {
+ faceFilter.wireDecode(query[-1].blockFromValue());
+ }
+ catch (const tlv::Error&) {
+ NFD_LOG_DEBUG("query result: malformed filter");
+ return context.reject(ControlResponse(400, "malformed filter"));
+ }
+
+ for (const auto& face : m_faceTable) {
+ if (doesMatchFilter(faceFilter, face)) {
+ context.append(face->getFaceStatus().wireEncode());
+ }
+ }
+ context.end();
+}
+
+bool
+FaceManager::doesMatchFilter(const ndn::nfd::FaceQueryFilter& filter, shared_ptr<Face> face)
+{
+ if (filter.hasFaceId() &&
+ filter.getFaceId() != static_cast<uint64_t>(face->getId())) {
+ return false;
+ }
+
+ if (filter.hasUriScheme() &&
+ filter.getUriScheme() != face->getRemoteUri().getScheme() &&
+ filter.getUriScheme() != face->getLocalUri().getScheme()) {
+ return false;
+ }
+
+ if (filter.hasRemoteUri() &&
+ filter.getRemoteUri() != face->getRemoteUri().toString()) {
+ return false;
+ }
+
+ if (filter.hasLocalUri() &&
+ filter.getLocalUri() != face->getLocalUri().toString()) {
+ return false;
+ }
+
+ if (filter.hasFaceScope() &&
+ (filter.getFaceScope() == ndn::nfd::FACE_SCOPE_LOCAL) != face->isLocal()) {
+ return false;
+ }
+
+ if (filter.hasFacePersistency() &&
+ filter.getFacePersistency() != face->getPersistency()) {
+ return false;
+ }
+
+ if (filter.hasLinkType() &&
+ (filter.getLinkType() == ndn::nfd::LINK_TYPE_MULTI_ACCESS) != face->isMultiAccess()) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+FaceManager::afterFaceAdded(shared_ptr<Face> face,
+ const ndn::mgmt::PostNotification& post)
+{
+ ndn::nfd::FaceEventNotification notification;
+ notification.setKind(ndn::nfd::FACE_EVENT_CREATED);
+ face->copyStatusTo(notification);
+
+ post(notification.wireEncode());
+}
+
+void
+FaceManager::afterFaceRemoved(shared_ptr<Face> face,
+ const ndn::mgmt::PostNotification& post)
+{
+ ndn::nfd::FaceEventNotification notification;
+ notification.setKind(ndn::nfd::FACE_EVENT_DESTROYED);
+ face->copyStatusTo(notification);
+
+ post(notification.wireEncode());
+}
+
+void
+FaceManager::processConfig(const ConfigSection& configSection,
+ bool isDryRun,
+ const std::string& filename)
+{
+ bool hasSeenUnix = false;
+ bool hasSeenTcp = false;
+ bool hasSeenUdp = false;
+ bool hasSeenEther = false;
+ bool hasSeenWebSocket = false;
+
+ const std::vector<NetworkInterfaceInfo> nicList(listNetworkInterfaces());
+
+ for (const auto& item : configSection) {
+ if (item.first == "unix") {
+ if (hasSeenUnix) {
+ BOOST_THROW_EXCEPTION(Error("Duplicate \"unix\" section"));
+ }
+ hasSeenUnix = true;
+
+ processSectionUnix(item.second, isDryRun);
+ }
+ else if (item.first == "tcp") {
+ if (hasSeenTcp) {
+ BOOST_THROW_EXCEPTION(Error("Duplicate \"tcp\" section"));
+ }
+ hasSeenTcp = true;
+
+ processSectionTcp(item.second, isDryRun);
+ }
+ else if (item.first == "udp") {
+ if (hasSeenUdp) {
+ BOOST_THROW_EXCEPTION(Error("Duplicate \"udp\" section"));
+ }
+ hasSeenUdp = true;
+
+ processSectionUdp(item.second, isDryRun, nicList);
+ }
+ else if (item.first == "ether") {
+ if (hasSeenEther) {
+ BOOST_THROW_EXCEPTION(Error("Duplicate \"ether\" section"));
+ }
+ hasSeenEther = true;
+
+ processSectionEther(item.second, isDryRun, nicList);
+ }
+ else if (item.first == "websocket") {
+ if (hasSeenWebSocket) {
+ BOOST_THROW_EXCEPTION(Error("Duplicate \"websocket\" section"));
+ }
+ hasSeenWebSocket = true;
+
+ processSectionWebSocket(item.second, isDryRun);
+ }
+ else {
+ BOOST_THROW_EXCEPTION(Error("Unrecognized option \"" + item.first + "\""));
+ }
+ }
+}
+
+void
+FaceManager::processSectionUnix(const ConfigSection& configSection, bool isDryRun)
+{
+ // ; the unix section contains settings of Unix stream faces and channels
+ // unix
+ // {
+ // path /var/run/nfd.sock ; Unix stream listener path
+ // }
+
+#if defined(HAVE_UNIX_SOCKETS)
+
+ std::string path = "/var/run/nfd.sock";
+
+ for (auto i = configSection.begin(); i != configSection.end(); ++i) {
+ if (i->first == "path") {
+ path = i->second.get_value<std::string>();
+ }
+ else {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
+ i->first + "\" in \"unix\" section"));
+ }
+ }
+
+ if (!isDryRun) {
+ if (m_factories.count("unix") > 0) {
+ return;
+ }
+
+ shared_ptr<UnixStreamFactory> factory = make_shared<UnixStreamFactory>();
+ shared_ptr<UnixStreamChannel> unixChannel = factory->createChannel(path);
+
+ // Should acceptFailed callback be used somehow?
+ unixChannel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+ UnixStreamChannel::ConnectFailedCallback());
+
+ m_factories.insert(std::make_pair("unix", factory));
+ }
+#else
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD was compiled without Unix sockets support, "
+ "cannot process \"unix\" section"));
+#endif // HAVE_UNIX_SOCKETS
+}
+
+void
+FaceManager::processSectionTcp(const ConfigSection& configSection, bool isDryRun)
+{
+ // ; the tcp section contains settings of TCP faces and channels
+ // tcp
+ // {
+ // listen yes ; set to 'no' to disable TCP listener, default 'yes'
+ // port 6363 ; TCP listener port number
+ // }
+
+ std::string port = "6363";
+ bool needToListen = true;
+ bool enableV4 = true;
+ bool enableV6 = true;
+
+ for (auto i = configSection.begin(); i != configSection.end(); ++i) {
+ if (i->first == "port") {
+ port = i->second.get_value<std::string>();
+ try {
+ uint16_t portNo = boost::lexical_cast<uint16_t>(port);
+ NFD_LOG_TRACE("TCP port set to " << portNo);
+ }
+ catch (const std::bad_cast& error) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option " +
+ i->first + "\" in \"tcp\" section"));
+ }
+ }
+ else if (i->first == "listen") {
+ needToListen = ConfigFile::parseYesNo(i, i->first, "tcp");
+ }
+ else if (i->first == "enable_v4") {
+ enableV4 = ConfigFile::parseYesNo(i, i->first, "tcp");
+ }
+ else if (i->first == "enable_v6") {
+ enableV6 = ConfigFile::parseYesNo(i, i->first, "tcp");
+ }
+ else {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
+ i->first + "\" in \"tcp\" section"));
+ }
+ }
+
+ if (!enableV4 && !enableV6) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("IPv4 and IPv6 channels have been disabled."
+ " Remove \"tcp\" section to disable TCP channels or"
+ " re-enable at least one channel type."));
+ }
+
+ if (!isDryRun) {
+ if (m_factories.count("tcp") > 0) {
+ return;
+ }
+
+ shared_ptr<TcpFactory> factory = make_shared<TcpFactory>(port);
+ m_factories.insert(std::make_pair("tcp", factory));
+
+ if (enableV4) {
+ shared_ptr<TcpChannel> ipv4Channel = factory->createChannel("0.0.0.0", port);
+ if (needToListen) {
+ // Should acceptFailed callback be used somehow?
+ ipv4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+ TcpChannel::ConnectFailedCallback());
+ }
+
+ m_factories.insert(std::make_pair("tcp4", factory));
+ }
+
+ if (enableV6) {
+ shared_ptr<TcpChannel> ipv6Channel = factory->createChannel("::", port);
+ if (needToListen) {
+ // Should acceptFailed callback be used somehow?
+ ipv6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+ TcpChannel::ConnectFailedCallback());
+ }
+
+ m_factories.insert(std::make_pair("tcp6", factory));
+ }
+ }
+}
+
+void
+FaceManager::processSectionUdp(const ConfigSection& configSection,
+ bool isDryRun,
+ const std::vector<NetworkInterfaceInfo>& nicList)
+{
+ // ; the udp section contains settings of UDP faces and channels
+ // udp
+ // {
+ // port 6363 ; UDP unicast port number
+ // idle_timeout 600 ; idle time (seconds) before closing a UDP unicast face
+ // keep_alive_interval 25 ; interval (seconds) between keep-alive refreshes
+
+ // ; NFD creates one UDP multicast face per NIC
+ // mcast yes ; set to 'no' to disable UDP multicast, default 'yes'
+ // mcast_port 56363 ; UDP multicast port number
+ // mcast_group 224.0.23.170 ; UDP multicast group (IPv4 only)
+ // }
+
+ std::string port = "6363";
+ bool enableV4 = true;
+ bool enableV6 = true;
+ size_t timeout = 600;
+ size_t keepAliveInterval = 25;
+ bool useMcast = true;
+ std::string mcastGroup = "224.0.23.170";
+ std::string mcastPort = "56363";
+
+
+ for (auto i = configSection.begin(); i != configSection.end(); ++i) {
+ if (i->first == "port") {
+ port = i->second.get_value<std::string>();
+ try {
+ uint16_t portNo = boost::lexical_cast<uint16_t>(port);
+ NFD_LOG_TRACE("UDP port set to " << portNo);
+ }
+ catch (const std::bad_cast& error) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option " +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+ else if (i->first == "enable_v4") {
+ enableV4 = ConfigFile::parseYesNo(i, i->first, "udp");
+ }
+ else if (i->first == "enable_v6") {
+ enableV6 = ConfigFile::parseYesNo(i, i->first, "udp");
+ }
+ else if (i->first == "idle_timeout") {
+ try {
+ timeout = i->second.get_value<size_t>();
+ }
+ catch (const std::exception& e) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option \"" +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+ else if (i->first == "keep_alive_interval") {
+ try {
+ keepAliveInterval = i->second.get_value<size_t>();
+
+ /// \todo Make use of keepAliveInterval
+ /// \todo what is keep alive interval used for?
+ (void)(keepAliveInterval);
+ }
+ catch (const std::exception& e) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option \"" +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+ else if (i->first == "mcast") {
+ useMcast = ConfigFile::parseYesNo(i, i->first, "udp");
+ }
+ else if (i->first == "mcast_port") {
+ mcastPort = i->second.get_value<std::string>();
+ try {
+ uint16_t portNo = boost::lexical_cast<uint16_t>(mcastPort);
+ NFD_LOG_TRACE("UDP multicast port set to " << portNo);
+ }
+ catch (const std::bad_cast& error) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option " +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+ else if (i->first == "mcast_group") {
+ using namespace boost::asio::ip;
+ mcastGroup = i->second.get_value<std::string>();
+ try {
+ address mcastGroupTest = address::from_string(mcastGroup);
+ if (!mcastGroupTest.is_v4()) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option \"" +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+ catch(const std::runtime_error& e) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option \"" +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+ else {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
+ i->first + "\" in \"udp\" section"));
+ }
+ }
+
+ if (!enableV4 && !enableV6) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("IPv4 and IPv6 channels have been disabled."
+ " Remove \"udp\" section to disable UDP channels or"
+ " re-enable at least one channel type."));
+ }
+ else if (useMcast && !enableV4) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("IPv4 multicast requested, but IPv4 channels"
+ " have been disabled (conflicting configuration options set)"));
+ }
+
+ if (!isDryRun) {
+ shared_ptr<UdpFactory> factory;
+ bool isReload = false;
+ if (m_factories.count("udp") > 0) {
+ isReload = true;
+ factory = static_pointer_cast<UdpFactory>(m_factories["udp"]);
+ }
+ else {
+ factory = make_shared<UdpFactory>(port);
+ m_factories.insert(std::make_pair("udp", factory));
+ }
+
+ if (!isReload && enableV4) {
+ shared_ptr<UdpChannel> v4Channel =
+ factory->createChannel("0.0.0.0", port, time::seconds(timeout));
+
+ v4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+ UdpChannel::ConnectFailedCallback());
+
+ m_factories.insert(std::make_pair("udp4", factory));
+ }
+
+ if (!isReload && enableV6) {
+ shared_ptr<UdpChannel> v6Channel =
+ factory->createChannel("::", port, time::seconds(timeout));
+
+ v6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+ UdpChannel::ConnectFailedCallback());
+ m_factories.insert(std::make_pair("udp6", factory));
+ }
+
+ if (useMcast && enableV4) {
+ std::vector<NetworkInterfaceInfo> ipv4MulticastInterfaces;
+ for (const auto& nic : nicList) {
+ if (nic.isUp() && nic.isMulticastCapable() && !nic.ipv4Addresses.empty()) {
+ ipv4MulticastInterfaces.push_back(nic);
+ }
+ }
+
+ bool isNicNameNecessary = false;
+#if defined(__linux__)
+ if (ipv4MulticastInterfaces.size() > 1) {
+ // On Linux if we have more than one MulticastUdpFace
+ // we need to specify the name of the interface
+ isNicNameNecessary = true;
+ }
+#endif
+
+ std::list<shared_ptr<MulticastUdpFace> > multicastFacesToRemove;
+ for (auto i = factory->getMulticastFaces().begin();
+ i != factory->getMulticastFaces().end();
+ ++i) {
+ multicastFacesToRemove.push_back(i->second);
+ }
+
+ for (const auto& nic : ipv4MulticastInterfaces) {
+ shared_ptr<MulticastUdpFace> newFace;
+ newFace = factory->createMulticastFace(nic.ipv4Addresses[0].to_string(),
+ mcastGroup,
+ mcastPort,
+ isNicNameNecessary ? nic.name : "");
+ addCreatedFaceToForwarder(newFace);
+ multicastFacesToRemove.remove(newFace);
+ }
+
+ for (auto i = multicastFacesToRemove.begin();
+ i != multicastFacesToRemove.end();
+ ++i) {
+ (*i)->close();
+ }
+ }
+ else {
+ std::list<shared_ptr<MulticastUdpFace>> multicastFacesToRemove;
+ for (auto i = factory->getMulticastFaces().begin();
+ i != factory->getMulticastFaces().end();
+ ++i) {
+ multicastFacesToRemove.push_back(i->second);
+ }
+
+ for (auto i = multicastFacesToRemove.begin();
+ i != multicastFacesToRemove.end();
+ ++i) {
+ (*i)->close();
+ }
+ }
+ }
+}
+
+void
+FaceManager::processSectionEther(const ConfigSection& configSection,
+ bool isDryRun,
+ const std::vector<NetworkInterfaceInfo>& nicList)
+{
+ // ; the ether section contains settings of Ethernet faces and channels
+ // ether
+ // {
+ // ; NFD creates one Ethernet multicast face per NIC
+ // mcast yes ; set to 'no' to disable Ethernet multicast, default 'yes'
+ // mcast_group 01:00:5E:00:17:AA ; Ethernet multicast group
+ // }
+
+#if defined(HAVE_LIBPCAP)
+ bool useMcast = true;
+ ethernet::Address mcastGroup(ethernet::getDefaultMulticastAddress());
+
+ for (auto i = configSection.begin(); i != configSection.end(); ++i) {
+ if (i->first == "mcast") {
+ useMcast = ConfigFile::parseYesNo(i, i->first, "ether");
+ }
+ else if (i->first == "mcast_group") {
+ mcastGroup = ethernet::Address::fromString(i->second.get_value<std::string>());
+ if (mcastGroup.isNull()) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option \"" +
+ i->first + "\" in \"ether\" section"));
+ }
+ }
+ else {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
+ i->first + "\" in \"ether\" section"));
+ }
+ }
+
+ if (!isDryRun) {
+ shared_ptr<EthernetFactory> factory;
+ if (m_factories.count("ether") > 0) {
+ factory = static_pointer_cast<EthernetFactory>(m_factories["ether"]);
+ }
+ else {
+ factory = make_shared<EthernetFactory>();
+ m_factories.insert(std::make_pair("ether", factory));
+ }
+
+ if (useMcast) {
+ std::list<shared_ptr<EthernetFace> > multicastFacesToRemove;
+ for (auto i = factory->getMulticastFaces().begin();
+ i != factory->getMulticastFaces().end();
+ ++i) {
+ multicastFacesToRemove.push_back(i->second);
+ }
+
+ for (const auto& nic : nicList) {
+ if (nic.isUp() && nic.isMulticastCapable()) {
+ try {
+ shared_ptr<EthernetFace> newFace =
+ factory->createMulticastFace(nic, mcastGroup);
+
+ addCreatedFaceToForwarder(newFace);
+ multicastFacesToRemove.remove(newFace);
+ }
+ catch (const EthernetFactory::Error& factoryError) {
+ NFD_LOG_ERROR(factoryError.what() << ", continuing");
+ }
+ catch (const EthernetFace::Error& faceError) {
+ NFD_LOG_ERROR(faceError.what() << ", continuing");
+ }
+ }
+ }
+
+ for (auto i = multicastFacesToRemove.begin();
+ i != multicastFacesToRemove.end();
+ ++i) {
+ (*i)->close();
+ }
+ }
+ else {
+ std::list<shared_ptr<EthernetFace> > multicastFacesToRemove;
+ for (auto i = factory->getMulticastFaces().begin();
+ i != factory->getMulticastFaces().end();
+ ++i) {
+ multicastFacesToRemove.push_back(i->second);
+ }
+
+ for (auto i = multicastFacesToRemove.begin();
+ i != multicastFacesToRemove.end();
+ ++i) {
+ (*i)->close();
+ }
+ }
+ }
+#else
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD was compiled without libpcap, cannot process \"ether\" section"));
+#endif // HAVE_LIBPCAP
+}
+
+void
+FaceManager::processSectionWebSocket(const ConfigSection& configSection, bool isDryRun)
+{
+ // ; the websocket section contains settings of WebSocket faces and channels
+ // websocket
+ // {
+ // listen yes ; set to 'no' to disable WebSocket listener, default 'yes'
+ // port 9696 ; WebSocket listener port number
+ // enable_v4 yes ; set to 'no' to disable listening on IPv4 socket, default 'yes'
+ // enable_v6 yes ; set to 'no' to disable listening on IPv6 socket, default 'yes'
+ // }
+
+#if defined(HAVE_WEBSOCKET)
+
+ std::string port = "9696";
+ bool needToListen = true;
+ bool enableV4 = true;
+ bool enableV6 = true;
+
+ for (auto i = configSection.begin(); i != configSection.end(); ++i) {
+ if (i->first == "port") {
+ port = i->second.get_value<std::string>();
+ try {
+ uint16_t portNo = boost::lexical_cast<uint16_t>(port);
+ NFD_LOG_TRACE("WebSocket port set to " << portNo);
+ }
+ catch (const std::bad_cast& error) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option " +
+ i->first + "\" in \"websocket\" section"));
+ }
+ }
+ else if (i->first == "listen") {
+ needToListen = ConfigFile::parseYesNo(i, i->first, "websocket");
+ }
+ else if (i->first == "enable_v4") {
+ enableV4 = ConfigFile::parseYesNo(i, i->first, "websocket");
+ }
+ else if (i->first == "enable_v6") {
+ enableV6 = ConfigFile::parseYesNo(i, i->first, "websocket");
+ }
+ else {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
+ i->first + "\" in \"websocket\" section"));
+ }
+ }
+
+ if (!enableV4 && !enableV6) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("IPv4 and IPv6 channels have been disabled."
+ " Remove \"websocket\" section to disable WebSocket channels or"
+ " re-enable at least one channel type."));
+ }
+
+ if (!enableV4 && enableV6) {
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD does not allow pure IPv6 WebSocket channel."));
+ }
+
+ if (!isDryRun) {
+ if (m_factories.count("websocket") > 0) {
+ return;
+ }
+
+ shared_ptr<WebSocketFactory> factory = make_shared<WebSocketFactory>(port);
+ m_factories.insert(std::make_pair("websocket", factory));
+
+ if (enableV6 && enableV4) {
+ shared_ptr<WebSocketChannel> ip46Channel = factory->createChannel("::", port);
+ if (needToListen) {
+ ip46Channel->listen(bind(&FaceTable::add, &m_faceTable, _1));
+ }
+
+ m_factories.insert(std::make_pair("websocket46", factory));
+ }
+ else if (enableV4) {
+ shared_ptr<WebSocketChannel> ipv4Channel = factory->createChannel("0.0.0.0", port);
+ if (needToListen) {
+ ipv4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1));
+ }
+
+ m_factories.insert(std::make_pair("websocket4", factory));
+ }
+ }
+#else
+ BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD was compiled without WebSocket, "
+ "cannot process \"websocket\" section"));
+#endif // HAVE_WEBSOCKET
+}
+
+void
+FaceManager::addCreatedFaceToForwarder(shared_ptr<Face> newFace)
+{
+ m_faceTable.add(newFace);
+}
+
+} // namespace
diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp
new file mode 100644
index 0000000..f5283d6
--- /dev/null
+++ b/daemon/mgmt/face-manager.hpp
@@ -0,0 +1,164 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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_MGMT_FACE_MANAGER_HPP
+#define NFD_DAEMON_MGMT_FACE_MANAGER_HPP
+
+#include "manager-base.hpp"
+#include "face/local-face.hpp"
+#include <ndn-cxx/management/nfd-face-query-filter.hpp>
+
+namespace nfd {
+
+class FaceTable;
+class NetworkInterfaceInfo;
+class ProtocolFactory;
+
+/**
+ * @brief implement the Face Management of NFD Management Protocol.
+ * @sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt
+ */
+class FaceManager : public ManagerBase
+{
+public:
+ FaceManager(FaceTable& faceTable,
+ Dispatcher& dispatcher,
+ CommandValidator& validator);
+
+ /**
+ * @brief Subscribe to face_system section for the config file
+ */
+ void
+ setConfigFile(ConfigFile& configFile);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // ControlCommand
+ void
+ createFace(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done);
+
+ void
+ destroyFace(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done);
+
+ void
+ enableLocalControl(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done);
+
+ void
+ disableLocalControl(const Name& topPrefix, const Interest& interest,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // helpers for ControlCommand
+ void
+ afterCreateFaceSuccess(ControlParameters& parameters,
+ const shared_ptr<Face>& newFace,
+ const ndn::mgmt::CommandContinuation& done);
+
+ void
+ afterCreateFaceFailure(const std::string& reason,
+ const ndn::mgmt::CommandContinuation& done);
+
+ struct ExtractLocalControlParametersResult
+ {
+ bool isValid;
+ shared_ptr<LocalFace> face;
+ LocalControlFeature feature;
+ };
+
+ ExtractLocalControlParametersResult
+ extractLocalControlParameters(const Interest& request,
+ const ControlParameters& parameters,
+ const ndn::mgmt::CommandContinuation& done);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // StatusDataset
+ void
+ listFaces(const Name& topPrefix, const Interest& interest,
+ ndn::mgmt::StatusDatasetContext& context);
+
+ void
+ listChannels(const Name& topPrefix, const Interest& interest,
+ ndn::mgmt::StatusDatasetContext& context);
+
+ void
+ queryFaces(const Name& topPrefix, const Interest& interest,
+ ndn::mgmt::StatusDatasetContext& context);
+
+private: // helpers for StatusDataset handler
+ bool
+ doesMatchFilter(const ndn::nfd::FaceQueryFilter& filter, shared_ptr<Face> face);
+
+private: // NotificationStream
+ void
+ afterFaceAdded(shared_ptr<Face> face,
+ const ndn::mgmt::PostNotification& post);
+
+ void
+ afterFaceRemoved(shared_ptr<Face> face,
+ const ndn::mgmt::PostNotification& post);
+
+private: // configuration
+ void
+ processConfig(const ConfigSection& configSection, bool isDryRun, const std::string& filename);
+
+ void
+ processSectionUnix(const ConfigSection& configSection, bool isDryRun);
+
+ void
+ processSectionTcp(const ConfigSection& configSection, bool isDryRun);
+
+ void
+ processSectionUdp(const ConfigSection& configSection,
+ bool isDryRun,
+ const std::vector<NetworkInterfaceInfo>& nicList);
+
+ void
+ processSectionEther(const ConfigSection& configSection,
+ bool isDryRun,
+ const std::vector<NetworkInterfaceInfo>& nicList);
+
+ void
+ processSectionWebSocket(const ConfigSection& configSection, bool isDryRun);
+
+private: // helpers for configuration
+ void
+ addCreatedFaceToForwarder(shared_ptr<Face> newFace);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ typedef std::map<std::string/*protocol*/, shared_ptr<ProtocolFactory>> FactoryMap;
+ FactoryMap m_factories;
+
+private:
+ FaceTable& m_faceTable;
+ signal::ScopedConnection m_faceAddConn;
+ signal::ScopedConnection m_faceRemoveConn;
+};
+
+} // namespace nfd
+
+#endif // NFD_DAEMON_MGMT_FACE_MANAGER_HPP
diff --git a/daemon/nfd.cpp b/daemon/nfd.cpp
index 741e495..14930ca 100644
--- a/daemon/nfd.cpp
+++ b/daemon/nfd.cpp
@@ -34,7 +34,7 @@
#include "face/internal-face.hpp"
#include "face/internal-client-face.hpp"
// #include "mgmt/fib-manager.hpp"
-// #include "mgmt/face-manager.hpp"
+#include "mgmt/face-manager.hpp"
// #include "mgmt/strategy-choice-manager.hpp"
// #include "mgmt/status-server.hpp"
#include "mgmt/general-config-section.hpp"
@@ -142,7 +142,9 @@
// bind(&Forwarder::getFace, m_forwarder.get(), _1),
// m_internalFace, m_keyChain));
- // m_faceManager.reset(new FaceManager(m_forwarder->getFaceTable(), m_internalFace, m_keyChain));
+ m_faceManager.reset(new FaceManager(m_forwarder->getFaceTable(),
+ *m_dispatcher,
+ *m_validator));
// m_strategyChoiceManager.reset(new StrategyChoiceManager(m_forwarder->getStrategyChoice(),
// m_internalFace, m_keyChain));
@@ -161,7 +163,7 @@
m_validator->setConfigFile(config);
- // m_faceManager->setConfigFile(config);
+ m_faceManager->setConfigFile(config);
// parse config file
if (!m_configFile.empty()) {
@@ -202,8 +204,8 @@
tablesConfig.setConfigFile(config);
- // m_faceManager->setConfigFile(config);
m_validator->setConfigFile(config);
+ m_faceManager->setConfigFile(config);
if (!m_configFile.empty()) {
config.parse(m_configFile, false);
@@ -218,7 +220,7 @@
{
// reload only face_system section of the config file to re-initialize multicast faces
ConfigFile config(&ConfigFile::ignoreUnknownSection);
- // m_faceManager->setConfigFile(config);
+ m_faceManager->setConfigFile(config);
if (!m_configFile.empty()) {
config.parse(m_configFile, false);
diff --git a/daemon/nfd.hpp b/daemon/nfd.hpp
index 7dc7148..eb7e012 100644
--- a/daemon/nfd.hpp
+++ b/daemon/nfd.hpp
@@ -114,7 +114,7 @@
unique_ptr<ndn::mgmt::Dispatcher> m_dispatcher;
// unique_ptr<FibManager> m_fibManager;
- // unique_ptr<FaceManager> m_faceManager;
+ unique_ptr<FaceManager> m_faceManager;
// unique_ptr<StrategyChoiceManager> m_strategyChoiceManager;
// unique_ptr<StatusServer> m_statusServer;
diff --git a/tests/daemon/mgmt/face-manager-create-face.t.cpp b/tests/daemon/mgmt/face-manager-create-face.t.cpp
new file mode 100644
index 0000000..ffee5aa
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager-create-face.t.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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 "mgmt/face-manager.hpp"
+#include "fw/forwarder.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#include <boost/property_tree/info_parser.hpp>
+
+namespace nfd {
+namespace tests {
+
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+BOOST_FIXTURE_TEST_SUITE(CreateFace, BaseFixture)
+
+class FaceManagerNode
+{
+public:
+ FaceManagerNode(ndn::KeyChain& keyChain, const std::string& port = "6363")
+ : face(ndn::util::makeDummyClientFace(getGlobalIoService(), {true, true}))
+ , dispatcher(*face, keyChain, ndn::security::SigningInfo())
+ , manager(forwarder.getFaceTable(), dispatcher, validator)
+ {
+ dispatcher.addTopPrefix("/localhost/nfd");
+
+ std::string basicConfig =
+ "face_system\n"
+ "{\n"
+ " tcp\n"
+ " {\n"
+ " port " + port + "\n"
+ " }\n"
+ " udp\n"
+ " {\n"
+ " port " + port + "\n"
+ " }\n"
+ "}\n"
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " certfile any\n"
+ " privileges\n"
+ " {\n"
+ " faces\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "\n";
+ std::istringstream input(basicConfig);
+ nfd::ConfigSection configSection;
+ boost::property_tree::read_info(input, configSection);
+
+ ConfigFile config;
+ manager.setConfigFile(config);
+ validator.setConfigFile(config);
+ config.parse(configSection, false, "dummy-config");
+ }
+
+ void
+ closeFaces()
+ {
+ std::vector<shared_ptr<Face>> facesToClose;
+ std::copy(forwarder.getFaceTable().begin(), forwarder.getFaceTable().end(),
+ std::back_inserter(facesToClose));
+ for (auto face : facesToClose) {
+ face->close();
+ }
+ }
+
+public:
+ Forwarder forwarder;
+ shared_ptr<ndn::util::DummyClientFace> face;
+ ndn::mgmt::Dispatcher dispatcher;
+ CommandValidator validator;
+ FaceManager manager;
+};
+
+class FaceManagerFixture : public UnitTestTimeFixture
+{
+public:
+ FaceManagerFixture()
+ : node1(keyChain, "16363")
+ , node2(keyChain, "26363")
+ {
+ advanceClocks(time::milliseconds(1), 100);
+ }
+
+ ~FaceManagerFixture()
+ {
+ node1.closeFaces();
+ node2.closeFaces();
+ advanceClocks(time::milliseconds(1), 100);
+ }
+
+public:
+ ndn::KeyChain keyChain;
+ FaceManagerNode node1; // used to test FaceManager
+ FaceManagerNode node2; // acts as a remote endpoint
+};
+
+class TcpFaceOnDemand
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("tcp4://127.0.0.1:26363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+ }
+};
+
+class TcpFacePersistent
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("tcp4://127.0.0.1:26363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+ }
+};
+
+class TcpFacePermanent
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("tcp4://127.0.0.1:26363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+ }
+};
+
+class UdpFaceOnDemand
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("udp4://127.0.0.1:26363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+ }
+};
+
+class UdpFaceCannotConnect // face that will cause afterCreateFaceFailure to be invoked
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("udp4://0.0.0.0:16363"); // cannot connect to self
+ }
+};
+
+
+class UdpFacePersistent
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("udp4://127.0.0.1:26363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+ }
+};
+
+class UdpFacePermanent
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("udp4://127.0.0.1:26363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+ }
+};
+
+class Success
+{
+public:
+ ControlResponse
+ getExpected()
+ {
+ return ControlResponse()
+ .setCode(200)
+ .setText("OK");
+ }
+};
+
+template<int CODE>
+class Failure
+{
+public:
+ ControlResponse
+ getExpected()
+ {
+ return ControlResponse()
+ .setCode(CODE)
+ .setText("Error"); // error description should not be checked
+ }
+};
+
+namespace mpl = boost::mpl;
+
+// pairs of CreateCommand and Success status
+typedef mpl::vector<mpl::pair<TcpFaceOnDemand, Failure<500>>,
+ mpl::pair<TcpFacePersistent, Success>,
+ mpl::pair<TcpFacePermanent, Failure<500>>,
+ mpl::pair<UdpFaceOnDemand, Failure<500>>,
+ mpl::pair<UdpFacePersistent, Success>,
+ mpl::pair<UdpFacePermanent, Success>,
+ mpl::pair<UdpFaceCannotConnect, Failure<408>>> Faces;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(NewFace, T, Faces, FaceManagerFixture)
+{
+ typedef typename T::first FaceType;
+ typedef typename T::second CreateResult;
+
+ Name commandName("/localhost/nfd/faces");
+ commandName.append("create");
+ commandName.append(FaceType().getParameters().wireEncode());
+
+ shared_ptr<Interest> command(make_shared<Interest>(commandName));
+ this->keyChain.sign(*command);
+
+ bool hasCallbackFired = false;
+ this->node1.face->onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
+ // std::cout << response << std::endl;
+ if (!command->getName().isPrefixOf(response.getName())) {
+ return;
+ }
+
+ ControlResponse actual(response.getContent().blockFromValue());
+ ControlResponse expected(CreateResult().getExpected());
+ BOOST_CHECK_EQUAL(expected.getCode(), actual.getCode());
+ BOOST_MESSAGE(actual.getText());
+
+ if (actual.getBody().hasWire()) {
+ ControlParameters expectedParams(FaceType().getParameters());
+ ControlParameters actualParams(actual.getBody());
+
+ BOOST_CHECK_EQUAL(expectedParams.getUri(), actualParams.getUri());
+ BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
+ }
+ hasCallbackFired = true;
+ });
+
+ this->node1.face->receive(*command);
+ this->advanceClocks(time::milliseconds(1), 100);
+
+ BOOST_CHECK(hasCallbackFired);
+}
+
+
+typedef mpl::vector<// mpl::pair<mpl::pair<TcpFacePersistent, TcpFacePermanent>, TcpFacePermanent>, // no need to check now
+ // mpl::pair<mpl::pair<TcpFacePermanent, TcpFacePersistent>, TcpFacePermanent>, // no need to check now
+ mpl::pair<mpl::pair<UdpFacePersistent, UdpFacePermanent>, UdpFacePermanent>,
+ mpl::pair<mpl::pair<UdpFacePermanent, UdpFacePersistent>, UdpFacePermanent>> FaceTransitions;
+
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExistingFace, T, FaceTransitions, FaceManagerFixture)
+{
+ typedef typename T::first::first FaceType1;
+ typedef typename T::first::second FaceType2;
+ typedef typename T::second FinalFaceType;
+
+ {
+ // create face
+
+ Name commandName("/localhost/nfd/faces");
+ commandName.append("create");
+ commandName.append(FaceType1().getParameters().wireEncode());
+
+ shared_ptr<Interest> command(make_shared<Interest>(commandName));
+ this->keyChain.sign(*command);
+
+ this->node1.face->receive(*command);
+ this->advanceClocks(time::milliseconds(1), 10);
+ }
+
+ //
+ {
+ // re-create face (= change face persistency)
+
+ Name commandName("/localhost/nfd/faces");
+ commandName.append("create");
+ commandName.append(FaceType2().getParameters().wireEncode());
+
+ shared_ptr<Interest> command(make_shared<Interest>(commandName));
+ this->keyChain.sign(*command);
+
+ bool hasCallbackFired = false;
+ this->node1.face->onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
+ if (!command->getName().isPrefixOf(response.getName())) {
+ return;
+ }
+
+ ControlResponse actual(response.getContent().blockFromValue());
+ BOOST_REQUIRE_EQUAL(actual.getCode(), 200);
+
+ ControlParameters expectedParams(FinalFaceType().getParameters());
+ ControlParameters actualParams(actual.getBody());
+ BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
+
+ hasCallbackFired = true;
+ });
+
+ this->node1.face->receive(*command);
+ this->advanceClocks(time::milliseconds(1), 10);
+
+ BOOST_CHECK(hasCallbackFired);
+ }
+}
+
+
+class UdpFace
+{
+public:
+ ControlParameters
+ getParameters()
+ {
+ return ControlParameters()
+ .setUri("udp4://127.0.0.1:16363")
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+ }
+};
+
+
+// Note that the transitions from on-demand TcpFace are intentionally not tested.
+// On-demand TcpFace has a remote endpoint with a randomized port number. Normal face
+// creation operations will not need to create a face toward a remote port not listened by
+// a channel.
+
+typedef mpl::vector<mpl::pair<UdpFace, UdpFacePersistent>,
+ mpl::pair<UdpFace, UdpFacePermanent>> OnDemandFaceTransitions;
+
+// need a slightly different logic to test transitions from OnDemand state
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExistingFaceOnDemand, T, OnDemandFaceTransitions, FaceManagerFixture)
+{
+ typedef typename T::first OtherNodeFace;
+ typedef typename T::second FaceType;
+
+ {
+ // create on-demand face
+
+ Name commandName("/localhost/nfd/faces");
+ commandName.append("create");
+ commandName.append(OtherNodeFace().getParameters().wireEncode());
+
+ shared_ptr<Interest> command(make_shared<Interest>(commandName));
+ this->keyChain.sign(*command);
+
+ ndn::util::signal::ScopedConnection connection =
+ this->node2.face->onSendData.connect([this, command] (const Data& response) {
+ if (!command->getName().isPrefixOf(response.getName())) {
+ return;
+ }
+
+ ControlResponse controlResponse(response.getContent().blockFromValue());
+ BOOST_REQUIRE_EQUAL(controlResponse.getText(), "OK");
+ BOOST_REQUIRE_EQUAL(controlResponse.getCode(), 200);
+ uint64_t faceId = ControlParameters(controlResponse.getBody()).getFaceId();
+ auto face = this->node2.forwarder.getFace(static_cast<FaceId>(faceId));
+
+ // to force creation of on-demand face
+ auto dummyInterest = make_shared<Interest>("/hello/world");
+ face->sendInterest(*dummyInterest);
+ });
+
+ this->node2.face->receive(*command);
+ this->advanceClocks(time::milliseconds(1), 10);
+ }
+
+ // make sure there is on-demand face
+ bool onDemandFaceFound = false;
+ FaceUri onDemandFaceUri(FaceType().getParameters().getUri());
+ for (auto face : this->node1.forwarder.getFaceTable()) {
+ if (face->getRemoteUri() == onDemandFaceUri) {
+ onDemandFaceFound = true;
+ break;
+ }
+ }
+ BOOST_REQUIRE(onDemandFaceFound);
+
+ //
+ {
+ // re-create face (= change face persistency)
+
+ Name commandName("/localhost/nfd/faces");
+ commandName.append("create");
+ commandName.append(FaceType().getParameters().wireEncode());
+
+ shared_ptr<Interest> command(make_shared<Interest>(commandName));
+ this->keyChain.sign(*command);
+
+ bool hasCallbackFired = false;
+ this->node1.face->onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
+ if (!command->getName().isPrefixOf(response.getName())) {
+ return;
+ }
+
+ ControlResponse actual(response.getContent().blockFromValue());
+ BOOST_REQUIRE_EQUAL(actual.getCode(), 200);
+
+ ControlParameters expectedParams(FaceType().getParameters());
+ ControlParameters actualParams(actual.getBody());
+ BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
+
+ hasCallbackFired = true;
+ });
+
+ this->node1.face->receive(*command);
+ this->advanceClocks(time::milliseconds(1), 10);
+
+ BOOST_CHECK(hasCallbackFired);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // CreateFace
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // tests
+} // nfd
diff --git a/tests/daemon/mgmt/face-manager-process-config.t.cpp b/tests/daemon/mgmt/face-manager-process-config.t.cpp
new file mode 100644
index 0000000..9463ac4
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager-process-config.t.cpp
@@ -0,0 +1,438 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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 "mgmt/face-manager.hpp"
+#include "face/udp-factory.hpp"
+
+#ifdef HAVE_LIBPCAP
+#include "face/ethernet-factory.hpp"
+#endif // HAVE_LIBPCAP
+
+#include "manager-common-fixture.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+class FaceManagerProcessConfigFixture : public ManagerCommonFixture
+{
+public:
+ FaceManagerProcessConfigFixture()
+ : m_manager(m_forwarder.getFaceTable(), m_dispatcher, m_validator)
+ {
+ m_manager.setConfigFile(m_config);
+ }
+
+public:
+ void
+ parseConfig(const std::string& type, bool isDryRun)
+ {
+ m_config.parse(type, isDryRun, "test-config");
+ }
+
+protected:
+ FaceManager m_manager;
+ ConfigFile m_config;
+};
+
+
+BOOST_FIXTURE_TEST_SUITE(ProcessConfig, FaceManagerProcessConfigFixture)
+
+#ifdef HAVE_UNIX_SOCKETS
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUnix)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " unix\n"
+ " {\n"
+ " path /tmp/nfd.sock\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUnixUnknownOption)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " unix\n"
+ " {\n"
+ " hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+#endif // HAVE_UNIX_SOCKETS
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcp)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " tcp\n"
+ " {\n"
+ " listen yes\n"
+ " port 16363\n"
+ " enable_v4 yes\n"
+ " enable_v6 yes\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcpBadListen)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " tcp\n"
+ " {\n"
+ " listen hello\n"
+ " }\n"
+ "}\n";
+
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcpChannelsDisabled)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " tcp\n"
+ " {\n"
+ " port 6363\n"
+ " enable_v4 no\n"
+ " enable_v6 no\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcpUnknownOption)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " tcp\n"
+ " {\n"
+ " hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdp)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " port 6363\n"
+ " enable_v4 yes\n"
+ " enable_v6 yes\n"
+ " idle_timeout 30\n"
+ " keep_alive_interval 25\n"
+ " mcast yes\n"
+ " mcast_port 56363\n"
+ " mcast_group 224.0.23.170\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadIdleTimeout)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " idle_timeout hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadMcast)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " mcast hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadMcastGroup)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " mcast no\n"
+ " mcast_port 50\n"
+ " mcast_group hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadMcastGroupV6)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " mcast no\n"
+ " mcast_port 50\n"
+ " mcast_group ::1\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpChannelsDisabled)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " port 6363\n"
+ " enable_v4 no\n"
+ " enable_v6 no\n"
+ " idle_timeout 30\n"
+ " keep_alive_interval 25\n"
+ " mcast yes\n"
+ " mcast_port 56363\n"
+ " mcast_group 224.0.23.170\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpConflictingMcast)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " port 6363\n"
+ " enable_v4 no\n"
+ " enable_v6 yes\n"
+ " idle_timeout 30\n"
+ " keep_alive_interval 25\n"
+ " mcast yes\n"
+ " mcast_port 56363\n"
+ " mcast_group 224.0.23.170\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpUnknownOption)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpMulticastReinit)
+{
+ const std::string CONFIG_WITH_MCAST =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " mcast yes\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITH_MCAST, false));
+
+ BOOST_REQUIRE(m_manager.m_factories.find("udp") != m_manager.m_factories.end());
+ auto factory = dynamic_pointer_cast<UdpFactory>(m_manager.m_factories.find("udp")->second);
+ BOOST_REQUIRE(factory != nullptr);
+
+ if (factory->getMulticastFaces().size() == 0) {
+ BOOST_TEST_MESSAGE("Destroying multicast faces is not tested because "
+ "no UDP multicast faces are available");
+ return;
+ }
+ BOOST_CHECK_GT(factory->getMulticastFaces().size(), 0);
+
+ const std::string CONFIG_WITHOUT_MCAST =
+ "face_system\n"
+ "{\n"
+ " udp\n"
+ " {\n"
+ " mcast no\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
+ BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
+}
+
+#ifdef HAVE_LIBPCAP
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEther)
+{
+
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " ether\n"
+ " {\n"
+ " mcast yes\n"
+ " mcast_group 01:00:5E:00:17:AA\n"
+ " }\n"
+ "}\n";
+
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherBadMcast)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " ether\n"
+ " {\n"
+ " mcast hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherBadMcastGroup)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " ether\n"
+ " {\n"
+ " mcast yes\n"
+ " mcast_group\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherUnknownOption)
+{
+ const std::string CONFIG =
+ "face_system\n"
+ "{\n"
+ " ether\n"
+ " {\n"
+ " hello\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+ BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherMulticastReinit)
+{
+ const std::string CONFIG_WITH_MCAST =
+ "face_system\n"
+ "{\n"
+ " ether\n"
+ " {\n"
+ " mcast yes\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITH_MCAST, false));
+
+ BOOST_REQUIRE(m_manager.m_factories.find("ether") != m_manager.m_factories.end());
+ auto factory = dynamic_pointer_cast<EthernetFactory>(m_manager.m_factories.find("ether")->second);
+ BOOST_REQUIRE(factory != nullptr);
+
+ if (factory->getMulticastFaces().size() == 0) {
+ BOOST_TEST_MESSAGE("Destroying multicast faces is not tested because "
+ "no Ethernet multicast faces are available");
+ return;
+ }
+ BOOST_CHECK_GT(factory->getMulticastFaces().size(), 0);
+
+ const std::string CONFIG_WITHOUT_MCAST =
+ "face_system\n"
+ "{\n"
+ " ether\n"
+ " {\n"
+ " mcast no\n"
+ " }\n"
+ "}\n";
+ BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
+ BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
+}
+
+#endif // HAVE_LIBPCAP
+
+BOOST_AUTO_TEST_SUITE_END() // ProcessConfig
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/face-manager.t.cpp b/tests/daemon/mgmt/face-manager.t.cpp
new file mode 100644
index 0000000..db24029
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager.t.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, 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 "mgmt/face-manager.hpp"
+#include "manager-common-fixture.hpp"
+#include "../face/dummy-face.hpp"
+#include "face/tcp-factory.hpp"
+#include "face/udp-factory.hpp"
+
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/encoding/tlv.hpp>
+#include <ndn-cxx/management/nfd-channel-status.hpp>
+#include <ndn-cxx/management/nfd-face-event-notification.hpp>
+
+namespace nfd {
+namespace tests {
+
+class FaceManagerFixture : public ManagerCommonFixture
+{
+public:
+ FaceManagerFixture()
+ : m_faceTable(m_forwarder.getFaceTable())
+ , m_manager(m_faceTable, m_dispatcher, m_validator)
+ {
+ setTopPrefixAndPrivilege("/localhost/nfd", "faces");
+ }
+
+public:
+ template<typename Face>
+ shared_ptr<Face>
+ addFace(bool wantRemoveLastNotification = false)
+ {
+ auto face = make_shared<Face>();
+ m_faceTable.add(face);
+ advanceClocks(time::milliseconds(1), 10); // wait for notification posted
+ if (wantRemoveLastNotification) {
+ m_responses.pop_back();
+ }
+ return face;
+ }
+
+protected:
+ FaceTable& m_faceTable;
+ FaceManager m_manager;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Mgmt, FaceManagerFixture)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+BOOST_AUTO_TEST_SUITE(DestroyFace)
+
+BOOST_AUTO_TEST_CASE(Existing)
+{
+ auto addedFace = addFace<DummyFace>(true); // clear notification for creation
+
+ auto parameters = ControlParameters().setFaceId(addedFace->getId());
+ auto command = makeControlCommandRequest("/localhost/nfd/faces/destroy", parameters);
+
+ receiveInterest(command);
+
+ BOOST_REQUIRE_EQUAL(m_responses.size(), 2); // one response and one notification
+ // notification is already tested, so ignore it
+
+ BOOST_CHECK_EQUAL(checkResponse(1, command->getName(), makeResponse(200, "OK", parameters)),
+ CheckResponseResult::OK);
+
+ BOOST_CHECK_EQUAL(addedFace->getId(), -1);
+}
+
+BOOST_AUTO_TEST_CASE(NonExisting)
+{
+ auto parameters = ControlParameters().setFaceId(65535);
+ auto command = makeControlCommandRequest("/localhost/nfd/faces/destroy", parameters);
+
+ receiveInterest(command);
+
+ BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+
+ BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), makeResponse(200, "OK", parameters)),
+ CheckResponseResult::OK);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // DestroyFace
+
+BOOST_AUTO_TEST_CASE(FaceEvents)
+{
+ auto addedFace = addFace<DummyFace>(); // trigger FACE_EVENT_CREATED notification
+ BOOST_CHECK_NE(addedFace->getId(), -1);
+ int64_t faceId = addedFace->getId();
+
+ // check notification
+ {
+ Block payload;
+ ndn::nfd::FaceEventNotification notification;
+ BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+ BOOST_CHECK_NO_THROW(payload = m_responses[0].getContent().blockFromValue());
+ BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+ BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+ BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_CREATED);
+ BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+ BOOST_CHECK_EQUAL(notification.getRemoteUri(), addedFace->getRemoteUri().toString());
+ BOOST_CHECK_EQUAL(notification.getLocalUri(), addedFace->getLocalUri().toString());
+ BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+ BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+ BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+ }
+
+ addedFace->close(); // trigger FaceDestroy FACE_EVENT_DESTROYED
+ advanceClocks(time::milliseconds(1), 10);
+
+ // check notification
+ {
+ Block payload;
+ ndn::nfd::FaceEventNotification notification;
+ BOOST_REQUIRE_EQUAL(m_responses.size(), 2);
+ BOOST_CHECK_NO_THROW(payload = m_responses[1].getContent().blockFromValue());
+ BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+ BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+ BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_DESTROYED);
+ BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+ BOOST_CHECK_EQUAL(notification.getRemoteUri(), addedFace->getRemoteUri().toString());
+ BOOST_CHECK_EQUAL(notification.getLocalUri(), addedFace->getLocalUri().toString());
+ BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+ BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+ BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+ }
+ BOOST_CHECK_EQUAL(addedFace->getId(), -1);
+}
+
+BOOST_AUTO_TEST_CASE(EnableDisableLocalControl)
+{
+ auto nonLocalFace = addFace<DummyFace>(true); // clear notification
+ auto localFace = addFace<DummyLocalFace>(true); // clear notification
+ BOOST_CHECK(localFace->isLocal());
+ BOOST_CHECK(!nonLocalFace->isLocal());
+
+ std::vector<Name> commandNames;
+ auto testLocalControl = [&] (const Name& name, const ControlParameters& params, uint64_t faceId) {
+ auto command = makeControlCommandRequest(name, params,
+ [faceId] (shared_ptr<Interest> interest) {
+ interest->setIncomingFaceId(faceId);
+ });
+ receiveInterest(command);
+ commandNames.push_back(command->getName());
+ };
+
+ auto paramsF = ControlParameters().setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+ auto paramsN = ControlParameters().setLocalControlFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+ // non-existing face: 0~3
+ testLocalControl("/localhost/nfd/faces/enable-local-control", paramsF, FACEID_NULL);
+ testLocalControl("/localhost/nfd/faces/disable-local-control", paramsF, FACEID_NULL);
+ testLocalControl("/localhost/nfd/faces/enable-local-control", paramsN, FACEID_NULL);
+ testLocalControl("/localhost/nfd/faces/disable-local-control", paramsN, FACEID_NULL);
+
+ // non-local face: 4~7
+ testLocalControl("/localhost/nfd/faces/enable-local-control", paramsF, nonLocalFace->getId());
+ testLocalControl("/localhost/nfd/faces/disable-local-control", paramsF, nonLocalFace->getId());
+ testLocalControl("/localhost/nfd/faces/enable-local-control", paramsN, nonLocalFace->getId());
+ testLocalControl("/localhost/nfd/faces/disable-local-control", paramsN, nonLocalFace->getId());
+
+ // enableLocalControl for Incoming FaceId on existing local face:
+ testLocalControl("/localhost/nfd/faces/enable-local-control", paramsF, localFace->getId()); // 8
+ BOOST_CHECK(localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+ BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+ // disableLocalControl for Incoming FaceId on existing local face
+ testLocalControl("/localhost/nfd/faces/disable-local-control", paramsF, localFace->getId()); // 9
+ BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+ BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+ // enableLocalControl for NextHop ID on existing local face
+ testLocalControl("/localhost/nfd/faces/enable-local-control", paramsN, localFace->getId()); // 10
+ BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+ BOOST_CHECK(localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+ // disableLocalControl for NextHop ID on existing local face
+ testLocalControl("/localhost/nfd/faces/disable-local-control", paramsN, localFace->getId()); // 11
+ BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+ BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+ // check responses
+ BOOST_REQUIRE_EQUAL(m_responses.size(), 12);
+ BOOST_CHECK_EQUAL(checkResponse(0, commandNames[0], ControlResponse(410, "Face not found")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(1, commandNames[1], ControlResponse(410, "Face not found")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(2, commandNames[2], ControlResponse(410, "Face not found")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(3, commandNames[3], ControlResponse(410, "Face not found")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(4, commandNames[4], ControlResponse(412, "Face is non-local")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(5, commandNames[5], ControlResponse(412, "Face is non-local")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(6, commandNames[6], ControlResponse(412, "Face is non-local")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(7, commandNames[7], ControlResponse(412, "Face is non-local")),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(8, commandNames[8], makeResponse(200, "OK", paramsF)),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(9, commandNames[9], makeResponse(200, "OK", paramsF)),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(10, commandNames[10], makeResponse(200, "OK", paramsN)),
+ CheckResponseResult::OK);
+ BOOST_CHECK_EQUAL(checkResponse(11, commandNames[11], makeResponse(200, "OK", paramsN)),
+ CheckResponseResult::OK);
+}
+
+class TestFace : public DummyFace
+{
+public:
+ explicit
+ TestFace(const std::string& uri = "test://")
+ : DummyFace(uri, uri)
+ {
+ getMutableCounters().getNInInterests().set(ndn::random::generateWord64());
+ getMutableCounters().getNInDatas().set(ndn::random::generateWord64());
+ getMutableCounters().getNOutInterests().set(ndn::random::generateWord64());
+ getMutableCounters().getNOutDatas().set(ndn::random::generateWord64());
+ getMutableCounters().getNInBytes().set(ndn::random::generateWord64());
+ getMutableCounters().getNOutBytes().set(ndn::random::generateWord64());
+ }
+};
+
+// @todo Refactor when ndn::nfd::FaceStatus implementes operator!= and operator<<
+class FaceStatus : public ndn::nfd::FaceStatus
+{
+public:
+ FaceStatus(const ndn::nfd::FaceStatus& s)
+ : ndn::nfd::FaceStatus(s)
+ {
+ }
+};
+
+bool
+operator!=(const FaceStatus& left, const FaceStatus& right)
+{
+ return left.getRemoteUri() != right.getRemoteUri() ||
+ left.getLocalUri() != right.getLocalUri() ||
+ left.getFaceScope() != right.getFaceScope() ||
+ left.getFacePersistency() != right.getFacePersistency() ||
+ left.getLinkType() != right.getLinkType() ||
+ left.getNInInterests() != right.getNInInterests() ||
+ left.getNInDatas() != right.getNInDatas() ||
+ left.getNOutInterests() != right.getNOutInterests() ||
+ left.getNOutDatas() != right.getNOutDatas() ||
+ left.getNInBytes() != right.getNInBytes() ||
+ left.getNOutBytes() != right.getNOutBytes();
+}
+
+std::ostream&
+operator<<(std::ostream &os, const FaceStatus& status)
+{
+ os << "[" << status.getRemoteUri() << ", "
+ << status.getLocalUri() << ", "
+ << status.getFacePersistency() << ", "
+ << status.getLinkType() << ", "
+ << status.getNInInterests() << ", "
+ << status.getNInDatas() << ", "
+ << status.getNOutInterests() << ", "
+ << status.getNOutDatas() << ", "
+ << status.getNInBytes() << ", "
+ << status.getNOutBytes() << "]";
+ return os;
+}
+
+BOOST_AUTO_TEST_CASE(FaceDataset)
+{
+ size_t nEntries = 303;
+ for (size_t i = 0 ; i < nEntries ; i ++) {
+ addFace<TestFace>(true);
+ }
+
+ receiveInterest(makeInterest("/localhost/nfd/faces/list"));
+
+ Block content;
+ BOOST_CHECK_NO_THROW(content = concatenateResponses());
+ BOOST_CHECK_NO_THROW(content.parse());
+ BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+ std::vector<FaceStatus> expectedStatuses, receivedStatuses;
+ std::set<FaceId> faceIds;
+ for (size_t idx = 0; idx < nEntries; ++idx) {
+ BOOST_TEST_MESSAGE("processing element: " << idx);
+
+ ndn::nfd::FaceStatus decodedStatus;
+ BOOST_REQUIRE_NO_THROW(decodedStatus.wireDecode(content.elements()[idx]));
+ BOOST_REQUIRE(m_faceTable.get(decodedStatus.getFaceId()) != nullptr);
+ faceIds.insert(decodedStatus.getFaceId());
+ receivedStatuses.push_back(decodedStatus);
+ expectedStatuses.push_back(m_faceTable.get(decodedStatus.getFaceId())->getFaceStatus());
+ }
+
+ BOOST_CHECK_EQUAL(faceIds.size(), nEntries);
+ BOOST_CHECK_EQUAL_COLLECTIONS(receivedStatuses.begin(), receivedStatuses.end(),
+ expectedStatuses.begin(), expectedStatuses.end());
+}
+
+BOOST_AUTO_TEST_CASE(FaceQuery)
+{
+ auto face1 = addFace<DummyFace>(true); // dummy://
+ auto face2 = addFace<DummyLocalFace>(true); // dummy://, local
+ auto face3 = addFace<TestFace>(true); // test://
+
+ auto generateQueryName = [] (const ndn::nfd::FaceQueryFilter& filter) {
+ return Name("/localhost/nfd/faces/query").append(filter.wireEncode());
+ };
+
+ auto querySchemeName =
+ generateQueryName(ndn::nfd::FaceQueryFilter().setUriScheme("dummy"));
+ auto queryIdName =
+ generateQueryName(ndn::nfd::FaceQueryFilter().setFaceId(face1->getId()));
+ auto queryScopeName =
+ generateQueryName(ndn::nfd::FaceQueryFilter().setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL));
+ auto invalidQueryName =
+ Name("/localhost/nfd/faces/query").append(ndn::makeStringBlock(tlv::Content, "invalid"));
+
+ receiveInterest(makeInterest(querySchemeName)); // face1 and face2 expected
+ receiveInterest(makeInterest(queryIdName)); // face1 expected
+ receiveInterest(makeInterest(queryScopeName)); // face1 and face3 expected
+ receiveInterest(makeInterest(invalidQueryName)); // nack expected
+
+ BOOST_REQUIRE_EQUAL(m_responses.size(), 4);
+
+ Block content;
+ ndn::nfd::FaceStatus status;
+
+ content = m_responses[0].getContent();
+ BOOST_CHECK_NO_THROW(content.parse());
+ BOOST_CHECK_EQUAL(content.elements().size(), 2); // face1 and face2
+ BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[0]));
+ BOOST_CHECK_EQUAL(face1->getId(), status.getFaceId());
+ BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[1]));
+ BOOST_CHECK_EQUAL(face2->getId(), status.getFaceId());
+
+ content = m_responses[1].getContent();
+ BOOST_CHECK_NO_THROW(content.parse());
+ BOOST_CHECK_EQUAL(content.elements().size(), 1); // face1
+ BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[0]));
+ BOOST_CHECK_EQUAL(face1->getId(), status.getFaceId());
+
+ content = m_responses[2].getContent();
+ BOOST_CHECK_NO_THROW(content.parse());
+ BOOST_CHECK_EQUAL(content.elements().size(), 2); // face1 and face3
+ BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[0]));
+ BOOST_CHECK_EQUAL(face1->getId(), status.getFaceId());
+ BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[1]));
+ BOOST_CHECK_EQUAL(face3->getId(), status.getFaceId());
+
+ ControlResponse expectedResponse(400, "malformed filter"); // nack, 400, malformed filter
+ BOOST_CHECK_EQUAL(checkResponse(3, invalidQueryName, expectedResponse, tlv::ContentType_Nack),
+ CheckResponseResult::OK);
+}
+
+class TestChannel : public Channel
+{
+public:
+ TestChannel(const std::string& uri)
+ {
+ setUri(FaceUri(uri));
+ }
+};
+
+class TestProtocolFactory : public ProtocolFactory
+{
+public:
+ virtual void
+ createFace(const FaceUri& uri,
+ ndn::nfd::FacePersistency persistency,
+ const FaceCreatedCallback& onCreated,
+ const FaceConnectFailedCallback& onConnectFailed) DECL_OVERRIDE
+ {
+ }
+
+ virtual std::list<shared_ptr<const Channel>>
+ getChannels() const DECL_OVERRIDE
+ {
+ return m_channels;
+ }
+
+public:
+ shared_ptr<TestChannel>
+ addChannel(const std::string& channelUri)
+ {
+ auto channel = make_shared<TestChannel>(channelUri);
+ m_channels.push_back(channel);
+ return channel;
+ }
+
+private:
+ std::list<shared_ptr<const Channel> > m_channels;
+};
+
+BOOST_AUTO_TEST_CASE(ChannelDataset)
+{
+ auto factory = make_shared<TestProtocolFactory>();
+ m_manager.m_factories["test"] = factory;
+
+ std::map<std::string, shared_ptr<TestChannel>> addedChannels;
+ size_t nEntries = 404;
+ for (size_t i = 0 ; i < nEntries ; i ++) {
+ auto channel = factory->addChannel("test" + boost::lexical_cast<std::string>(i) + "://");
+ addedChannels[channel->getUri().toString()] = channel;
+ }
+
+ receiveInterest(makeInterest("/localhost/nfd/faces/channels"));
+
+ Block content;
+ BOOST_CHECK_NO_THROW(content = concatenateResponses());
+ BOOST_CHECK_NO_THROW(content.parse());
+ BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+ for (size_t idx = 0; idx < nEntries; ++idx) {
+ BOOST_TEST_MESSAGE("processing element: " << idx);
+
+ ndn::nfd::ChannelStatus decodedStatus;
+ BOOST_CHECK_NO_THROW(decodedStatus.wireDecode(content.elements()[idx]));
+ BOOST_CHECK(addedChannels.find(decodedStatus.getLocalUri()) != addedChannels.end());
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd