diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..0b8f2ee5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "websocketpp"] + path = websocketpp + url = https://github.com/zaphoyd/websocketpp.git diff --git a/.waf-tools/websocket.py b/.waf-tools/websocket.py new file mode 100644 index 00000000..2c500c76 --- /dev/null +++ b/.waf-tools/websocket.py @@ -0,0 +1,77 @@ +# encoding: utf-8 + +from waflib import Options, Logs, Errors +from waflib.Configure import conf + +import re + +def addWebsocketOptions(self, opt): + opt.add_option('--without-websocket', action='store_false', default=True, + dest='with_websocket', + help='Disable WebSocket face support') +setattr(Options.OptionsContext, "addWebsocketOptions", addWebsocketOptions) + +@conf +def checkWebsocket(self, **kw): + if not self.options.with_websocket: + return + + isMandatory = kw.get('mandatory', True) + + self.start_msg('Checking for Websocket includes') + + try: + websocketDir = self.path.find_dir('websocketpp/websocketpp') + if not websocketDir: + raise Errors.WafError('Not found') + + versionFile = websocketDir.find_node('version.hpp') + if not websocketDir: + raise Errors.WafError('Corrupted: Websocket version file not found') + + try: + txt = versionFile.read() + except (OSError, IOError): + raise Errors.WafError('Corrupted: cannot read Websocket version file') + + # Looking for the following: + # static int const major_version = 0; + # static int const minor_version = 3; + # static int const patch_version = 0; + + version = [None, None, None] + + majorVersion = re.compile('^static int const major_version = (\\d+);$', re.M) + version[0] = majorVersion.search(txt) + + minorVersion = re.compile('^static int const minor_version = (\\d+);$', re.M) + version[1] = minorVersion.search(txt) + + patchVersion = re.compile('^static int const patch_version = (\\d+);$', re.M) + version[2] = patchVersion.search(txt) + + if not version[0] or not version[1] or not version[2]: + raise Errors.WafError('Corrupted: cannot detect websocket version') + + self.env['WEBSOCKET_VERSION'] = [i.group(1) for i in version] + + # todo: version checking, if necessary + + self.end_msg('.'.join(self.env['WEBSOCKET_VERSION'])) + + self.env['HAVE_WEBSOCKET'] = True + self.define('HAVE_WEBSOCKET', 1) + + except Errors.WafError as error: + if isMandatory: + self.end_msg(str(error), color='RED') + Logs.warn('If you are using git NFD repository, checkout websocketpp submodule: ') + Logs.warn(' git submodule init && git submodule update') + Logs.warn('Otherwise, manually download and extract websocketpp library:') + Logs.warn(' mkdir websocketpp') + Logs.warn(' curl -L -O https://github.com/zaphoyd/websocketpp/archive/0.3.0-alpha4.tar.gz') + Logs.warn(' tar zxf 0.3.0-alpha4.tar.gz -C websocketpp/ --strip 1') + Logs.warn('Alternatively, Websocket support can be disabled with --without-websocket') + self.fatal("The configuration failed") + else: + self.end_msg(str(error)) diff --git a/core/face-uri.cpp b/core/face-uri.cpp index 1b2092c6..2969e20c 100644 --- a/core/face-uri.cpp +++ b/core/face-uri.cpp @@ -112,6 +112,14 @@ FaceUri::FaceUri(const boost::asio::ip::udp::endpoint& endpoint) m_port = boost::lexical_cast(endpoint.port()); } +FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme) + : m_scheme(scheme) +{ + m_isV6 = endpoint.address().is_v6(); + m_host = endpoint.address().to_string(); + m_port = boost::lexical_cast(endpoint.port()); +} + #ifdef HAVE_UNIX_SOCKETS FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint) : m_isV6(false) diff --git a/core/face-uri.hpp b/core/face-uri.hpp index 487f8f8e..94474c77 100644 --- a/core/face-uri.hpp +++ b/core/face-uri.hpp @@ -79,6 +79,9 @@ public: // scheme-specific construction explicit FaceUri(const boost::asio::ip::udp::endpoint& endpoint); + /// construct tcp canonical FaceUri with customized scheme + FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme); + #ifdef HAVE_UNIX_SOCKETS /// construct unix canonical FaceUri explicit diff --git a/daemon/face/websocket-channel.cpp b/daemon/face/websocket-channel.cpp new file mode 100644 index 00000000..db7fdc0e --- /dev/null +++ b/daemon/face/websocket-channel.cpp @@ -0,0 +1,127 @@ +/* -*- 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 + * + * 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 . + **/ + +#include "websocket-channel.hpp" +#include "core/face-uri.hpp" + +namespace nfd { + +NFD_LOG_INIT("WebSocketChannel"); + +using namespace boost::asio; + +WebSocketChannel::WebSocketChannel(const websocket::Endpoint& localEndpoint) + : m_localEndpoint(localEndpoint) + , m_isListening(false) +{ + // Setup WebSocket server + m_server.clear_access_channels(websocketpp::log::alevel::all); + m_server.clear_error_channels(websocketpp::log::alevel::all); + + m_server.set_message_handler(bind(&WebSocketChannel::handleMessage, this, _1, _2)); + m_server.set_open_handler(bind(&WebSocketChannel::handleOpen, this, _1)); + m_server.set_close_handler(bind(&WebSocketChannel::handleClose, this, _1)); + m_server.init_asio(&getGlobalIoService()); + + this->setUri(FaceUri(localEndpoint, "ws")); +} + +WebSocketChannel::~WebSocketChannel() +{ +} + +void +WebSocketChannel::handleMessage(websocketpp::connection_hdl hdl, + websocket::Server::message_ptr msg) +{ + ChannelFaceMap::iterator it = m_channelFaces.find(hdl); + if (it != m_channelFaces.end()) + { + it->second->handleReceive(msg->get_payload()); + } +} + +void +WebSocketChannel::handleOpen(websocketpp::connection_hdl hdl) +{ + std::string remote; + try + { + remote = "wsclient://" + m_server.get_con_from_hdl(hdl)->get_remote_endpoint(); + } + catch (websocketpp::lib::error_code ec) + { + NFD_LOG_DEBUG("handleOpen: cannot get remote uri"); + websocketpp::lib::error_code ecode; + m_server.close(hdl, websocketpp::close::status::normal, "closed by channel", ecode); + } + shared_ptr face = make_shared(FaceUri(remote), this->getUri(), + hdl, boost::ref(m_server)); + m_onFaceCreatedCallback(face); + m_channelFaces[hdl] = face; +} + +void +WebSocketChannel::handleClose(websocketpp::connection_hdl hdl) +{ + ChannelFaceMap::iterator it = m_channelFaces.find(hdl); + if (it != m_channelFaces.end()) + { + NFD_LOG_DEBUG("handleClose: remove client"); + m_channelFaces.erase(it); + } +} + + +void +WebSocketChannel::listen(const FaceCreatedCallback& onFaceCreated) +{ + if (m_isListening) + { + throw Error("Listen already called on this channel"); + } + m_isListening = true; + + m_onFaceCreatedCallback = onFaceCreated; + + try + { + m_server.listen(m_localEndpoint); + } + catch (websocketpp::lib::error_code ec) + { + throw Error("Failed to listen on local endpoint"); + } + + m_server.start_accept(); +} + +size_t +WebSocketChannel::size() const +{ + return m_channelFaces.size(); +} + +} // namespace nfd diff --git a/daemon/face/websocket-channel.hpp b/daemon/face/websocket-channel.hpp new file mode 100644 index 00000000..05d8f998 --- /dev/null +++ b/daemon/face/websocket-channel.hpp @@ -0,0 +1,122 @@ +/* -*- 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 + * + * 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 . + **/ + +#ifndef NFD_DAEMON_FACE_WEBSOCKET_CHANNEL_HPP +#define NFD_DAEMON_FACE_WEBSOCKET_CHANNEL_HPP + +#include "channel.hpp" +#include "core/global-io.hpp" +#include "core/scheduler.hpp" +#include "websocket-face.hpp" + +namespace nfd { + +/** + * \brief Class implementing WebSocket-based channel to create faces + * + * + */ +class WebSocketChannel : public Channel +{ +public: + /** + * \brief Exception of WebSocketChannel + */ + class Error : public std::runtime_error + { + public: + explicit + Error(const std::string& what) + : runtime_error(what) + { + } + }; + + /** + * \brief Create WebSocket channel for the local endpoint + * + * To enable creation of faces upon incoming connections, + * one needs to explicitly call WebSocketChannel::listen method. + * The created socket is bound to the localEndpoint. + * + * \throw WebSocketChannel::Error if bind on the socket fails + */ + explicit + WebSocketChannel(const websocket::Endpoint& localEndpoint); + + virtual + ~WebSocketChannel(); + + /** + * \brief Enable listening on the local endpoint, accept connections, + * and create faces when remote host makes a connection + * \param onFaceCreated Callback to notify successful creation of the face + * + * \throws WebSocketChannel::Error if called multiple times + */ + void + listen(const FaceCreatedCallback& onFaceCreated); + + /** + * \brief Get number of faces in the channel + */ + size_t + size() const; + +private: + void + handleMessage(websocketpp::connection_hdl hdl, websocket::Server::message_ptr msg); + + void + handleOpen(websocketpp::connection_hdl hdl); + + void + handleClose(websocketpp::connection_hdl hdl); + +private: + websocket::Endpoint m_localEndpoint; + + websocket::Server m_server; + + /** + * Callbacks for face creation. + * New communications are detected using async_receive_from. + * Its handler has a fixed signature. No space for the face callback + */ + FaceCreatedCallback m_onFaceCreatedCallback; + + typedef std::map< websocketpp::connection_hdl, shared_ptr > ChannelFaceMap; + ChannelFaceMap m_channelFaces; + + /** + * \brief If true, it means the function listen has already been called + */ + bool m_isListening; + +}; + +} // namespace nfd + +#endif // NFD_DAEMON_FACE_WEBSOCKET_CHANNEL_HPP diff --git a/daemon/face/websocket-face.cpp b/daemon/face/websocket-face.cpp new file mode 100644 index 00000000..4712cfcd --- /dev/null +++ b/daemon/face/websocket-face.cpp @@ -0,0 +1,97 @@ +/* -*- 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 + * + * 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 . + **/ + +#include "websocket-face.hpp" + +namespace nfd { + +NFD_LOG_INIT("WebSocketFace"); + +WebSocketFace::WebSocketFace(const FaceUri& remoteUri, const FaceUri& localUri, + websocketpp::connection_hdl hdl, + websocket::Server& server) + : Face(remoteUri, localUri) + , m_handle(hdl) + , m_server(server) + , m_closed(false) +{ +} + + +void +WebSocketFace::sendInterest(const Interest& interest) +{ + this->onSendInterest(interest); + const Block& payload = interest.wireEncode(); + m_server.send(m_handle, payload.wire(), payload.size(), websocketpp::frame::opcode::binary); +} + +void +WebSocketFace::sendData(const Data& data) +{ + this->onSendData(data); + const Block& payload = data.wireEncode(); + m_server.send(m_handle, payload.wire(), payload.size(), websocketpp::frame::opcode::binary); +} + +void +WebSocketFace::close() +{ + if (m_closed == false) + { + m_closed = true; + websocketpp::lib::error_code ecode; + m_server.close(m_handle, websocketpp::close::status::normal, "closed by nfd", ecode); + } +} + +void +WebSocketFace::handleReceive(const std::string& msg) +{ + // Copy message into Face internal buffer + BOOST_ASSERT(msg.size() <= MAX_NDN_PACKET_SIZE); + + // Try to parse message data + bool isOk = true; + Block element; + isOk = Block::fromBuffer(reinterpret_cast(msg.c_str()), msg.size(), element); + if (!isOk) + { + NFD_LOG_TRACE("[id:" << this->getId() + << "] Received invalid NDN packet of length [" + << msg.size() << "]"); + return; + } + + if (!this->decodeAndDispatchInput(element)) + { + NFD_LOG_WARN("[id:" << this->getId() + << "] Received unrecognized block of type [" + << element.type() << "]"); + // ignore unknown packet and proceed + } +} + +} // namespace nfd diff --git a/daemon/face/websocket-face.hpp b/daemon/face/websocket-face.hpp new file mode 100644 index 00000000..3f2a932b --- /dev/null +++ b/daemon/face/websocket-face.hpp @@ -0,0 +1,77 @@ +/* -*- 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 + * + * 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 . + **/ + +#ifndef NFD_DAEMON_FACE_WEBSOCKET_FACE_HPP +#define NFD_DAEMON_FACE_WEBSOCKET_FACE_HPP + +#include "face.hpp" +#include "core/logger.hpp" + +#ifndef HAVE_WEBSOCKET +#error "Cannot include this file when WebSocket support is not enabled" +#endif // HAVE_WEBSOCKET + +#include "websocketpp.hpp" + +namespace nfd { + +namespace websocket { +typedef boost::asio::ip::tcp::endpoint Endpoint; +typedef websocketpp::server Server; +} // namespace websocket + + +/** + * \brief Implementation of Face abstraction that uses WebSocket + * as underlying transport mechanism + */ +class WebSocketFace : public Face +{ +public: + WebSocketFace(const FaceUri& remoteUri, const FaceUri& localUri, + websocketpp::connection_hdl hdl, websocket::Server& server); + + // from Face + virtual void + sendInterest(const Interest& interest); + + virtual void + sendData(const Data& data); + + virtual void + close(); + + void + handleReceive(const std::string& msg); + +private: + websocketpp::connection_hdl m_handle; + websocket::Server& m_server; + bool m_closed; +}; + +} // namespace nfd + +#endif // NFD_DAEMON_FACE_WEBSOCKET_FACE_HPP diff --git a/daemon/face/websocket-factory.cpp b/daemon/face/websocket-factory.cpp new file mode 100644 index 00000000..224a75ba --- /dev/null +++ b/daemon/face/websocket-factory.cpp @@ -0,0 +1,84 @@ +/* -*- 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 + * + * 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 . + **/ + +#include "websocket-factory.hpp" + +namespace nfd { + +using namespace boost::asio; + +NFD_LOG_INIT("WebSocketFactory"); + +WebSocketFactory::WebSocketFactory(const std::string& defaultPort) + : m_defaultPort(defaultPort) +{ +} + +shared_ptr +WebSocketFactory::createChannel(const websocket::Endpoint& endpoint) +{ + shared_ptr channel = findChannel(endpoint); + if (static_cast(channel)) + return channel; + + channel = make_shared(boost::cref(endpoint)); + m_channels[endpoint] = channel; + + return channel; +} + +shared_ptr +WebSocketFactory::createChannel(const std::string& localIPAddress, + uint16_t localPort) +{ + boost::system::error_code ec; + ip::address address = ip::address::from_string(localIPAddress, ec); + if (ec) + { + throw Error("Invalid address format: " + localIPAddress); + } + websocket::Endpoint endpoint(address, localPort); + return createChannel(endpoint); +} + +shared_ptr +WebSocketFactory::findChannel(const websocket::Endpoint& localEndpoint) +{ + ChannelMap::iterator i = m_channels.find(localEndpoint); + if (i != m_channels.end()) + return i->second; + else + return shared_ptr(); +} + +void +WebSocketFactory::createFace(const FaceUri& uri, + const FaceCreatedCallback& onCreated, + const FaceConnectFailedCallback& onConnectFailed) +{ + throw Error("WebSocketFactory does not support 'createFace' operation"); +} + +} // namespace nfd diff --git a/daemon/face/websocket-factory.hpp b/daemon/face/websocket-factory.hpp new file mode 100644 index 00000000..b241fcc3 --- /dev/null +++ b/daemon/face/websocket-factory.hpp @@ -0,0 +1,108 @@ +/* -*- 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 + * + * 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 . + **/ + +#ifndef NFD_DAEMON_FACE_WEBSOCKET_FACTORY_HPP +#define NFD_DAEMON_FACE_WEBSOCKET_FACTORY_HPP + +#include "protocol-factory.hpp" +#include "websocket-channel.hpp" + + +namespace nfd { + +class WebSocketFactory : public ProtocolFactory +{ +public: + /** + * \brief Exception of WebSocketFactory + */ + class Error : public ProtocolFactory::Error + { + public: + explicit + Error(const std::string& what) + : ProtocolFactory::Error(what) + { + } + }; + + explicit + WebSocketFactory(const std::string& defaultPort); + + /** + * \brief Create WebSocket-based channel using websocket::Endpoint + * + * websocket::Endpoint is really an alias for boost::asio::ip::tcp::endpoint. + * + * If this method called twice with the same endpoint, only one channel + * will be created. The second call will just retrieve the existing + * channel. + * + * \returns always a valid pointer to a WebSocketChannel object, an exception + * is thrown if it cannot be created. + * + * \throws WebSocketFactory::Error + * + */ + shared_ptr + createChannel(const websocket::Endpoint& localEndpoint); + + /** + * \brief Create WebSocket-based channel using specified ip address and port number + * + * \throws WebSocketFactory::Error + */ + shared_ptr + createChannel(const std::string& localIPAddress, + uint16_t localPort); + + // from Factory + virtual void + createFace(const FaceUri& uri, + const FaceCreatedCallback& onCreated, + const FaceConnectFailedCallback& onConnectFailed); + +private: + + /** + * \brief Look up WebSocketChannel using specified local endpoint + * + * \returns shared pointer to the existing WebSocketChannel object + * or empty shared pointer when such channel does not exist + * + * \throws never + */ + shared_ptr + findChannel(const websocket::Endpoint& localEndpoint); + + typedef std::map< websocket::Endpoint, shared_ptr > ChannelMap; + ChannelMap m_channels; + + std::string m_defaultPort; +}; + +} // namespace nfd + +#endif // NFD_DAEMON_FACE_WEBSOCKET_FACTORY_HPP diff --git a/daemon/face/websocketpp.hpp b/daemon/face/websocketpp.hpp new file mode 100644 index 00000000..1e934b6a --- /dev/null +++ b/daemon/face/websocketpp.hpp @@ -0,0 +1,36 @@ +/* -*- 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 + * + * 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 . + */ + +#ifndef NFD_DAEMON_FACE_WEBSOCKETPP_HPP +#define NFD_DAEMON_FACE_WEBSOCKETPP_HPP + +// suppress websocketpp warnings +#pragma GCC system_header +#pragma clang system_header + +#include "websocketpp/config/asio_no_tls.hpp" +#include "websocketpp/server.hpp" + +#endif // NFD_DAEMON_FACE_WEBSOCKETPP_HPP diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp index c5146372..e2c28483 100644 --- a/daemon/mgmt/face-manager.cpp +++ b/daemon/mgmt/face-manager.cpp @@ -41,6 +41,10 @@ #include "face/ethernet-factory.hpp" #endif // HAVE_LIBPCAP +#ifdef HAVE_WEBSOCKET +#include "face/websocket-factory.hpp" +#endif // HAVE_WEBSOCKET + #include namespace nfd { @@ -142,6 +146,7 @@ FaceManager::onConfig(const ConfigSection& configSection, bool hasSeenTcp = false; bool hasSeenUdp = false; bool hasSeenEther = false; + bool hasSeenWebSocket = false; const std::list > nicList(listNetworkInterfaces()); @@ -181,6 +186,14 @@ FaceManager::onConfig(const ConfigSection& configSection, processSectionEther(item->second, isDryRun, nicList); } + else if (item->first == "websocket") + { + if (hasSeenWebSocket) + throw Error("Duplicate \"websocket\" section"); + hasSeenWebSocket = true; + + processSectionWebSocket(item->second, isDryRun); + } else { throw Error("Unrecognized option \"" + item->first + "\""); @@ -621,6 +634,107 @@ FaceManager::processSectionEther(const ConfigSection& configSection, #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 (ConfigSection::const_iterator i = configSection.begin(); + i != configSection.end(); + ++i) + { + if (i->first == "port") + { + port = i->second.get_value(); + try + { + uint16_t portNo = boost::lexical_cast(port); + NFD_LOG_TRACE("WebSocket port set to " << portNo); + } + catch (const std::bad_cast& error) + { + throw ConfigFile::Error("Invalid value for option " + + i->first + "\" in \"websocket\" section"); + } + } + else if (i->first == "listen") + { + needToListen = parseYesNo(i, i->first, "websocket"); + } + else if (i->first == "enable_v4") + { + enableV4 = parseYesNo(i, i->first, "websocket"); + } + else if (i->first == "enable_v6") + { + enableV6 = parseYesNo(i, i->first, "websocket"); + } + else + { + throw ConfigFile::Error("Unrecognized option \"" + + i->first + "\" in \"websocket\" section"); + } + } + + if (!enableV4 && !enableV6) + { + throw 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) + { + throw ConfigFile::Error("NFD does not allow pure IPv6 WebSocket channel."); + } + + if (!isDryRun) + { + shared_ptr factory = make_shared(boost::cref(port)); + m_factories.insert(std::make_pair("websocket", factory)); + uint16_t portNo = boost::lexical_cast(port); + + if (enableV6 && enableV4) + { + shared_ptr ip46Channel = factory->createChannel("::", portNo); + if (needToListen) + { + ip46Channel->listen(bind(&FaceTable::add, &m_faceTable, _1)); + } + + m_factories.insert(std::make_pair("websocket46", factory)); + } + else if (enableV4) + { + shared_ptr ipv4Channel = factory->createChannel("0.0.0.0", portNo); + if (needToListen) + { + ipv4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1)); + } + + m_factories.insert(std::make_pair("websocket4", factory)); + } + } +#else + throw ConfigFile::Error("NFD was compiled without WebSocket, " + "cannot process \"websocket\" section"); +#endif // HAVE_WEBSOCKET +} + void FaceManager::onFaceRequest(const Interest& request) diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp index b5a7890c..649ed4c9 100644 --- a/daemon/mgmt/face-manager.hpp +++ b/daemon/mgmt/face-manager.hpp @@ -144,6 +144,9 @@ private: bool isDryRun, const std::list >& nicList); + void + processSectionWebSocket(const ConfigSection& configSection, bool isDryRun); + /** \brief parse a config option that can be either "yes" or "no" * \throw ConfigFile::Error value is neither "yes" nor "no" * \return true if "yes", false if "no" diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in index 9a081e42..960c59c4 100644 --- a/nfd.conf.sample.in +++ b/nfd.conf.sample.in @@ -115,6 +115,16 @@ face_system @IF_HAVE_LIBPCAP@ mcast yes ; set to 'no' to disable Ethernet multicast, default 'yes' @IF_HAVE_LIBPCAP@ mcast_group 01:00:5E:00:17:AA ; Ethernet multicast group @IF_HAVE_LIBPCAP@} + + ; The websocket section contains settings of WebSocket faces and channels. + + @IF_HAVE_WEBSOCKET@websocket + @IF_HAVE_WEBSOCKET@{ + @IF_HAVE_WEBSOCKET@ listen yes ; set to 'no' to disable WebSocket listener, default 'yes' + @IF_HAVE_WEBSOCKET@ port 9696 ; WebSocket listener port number + @IF_HAVE_WEBSOCKET@ enable_v4 yes ; set to 'no' to disable listening on IPv4 socket, default 'yes' + @IF_HAVE_WEBSOCKET@ enable_v6 yes ; set to 'no' to disable listening on IPv6 socket, default 'yes' + @IF_HAVE_WEBSOCKET@} } ; The authorizations section grants privileges to authorized keys. diff --git a/tests/wscript b/tests/wscript index 8b9c6fc7..be93c0ac 100644 --- a/tests/wscript +++ b/tests/wscript @@ -52,7 +52,8 @@ def build(bld): features='cxx cxxprogram', source=bld.path.ant_glob(['daemon/**/*.cpp'], excl=['daemon/face/ethernet.cpp', - 'daemon/face/unix-*.cpp']), + 'daemon/face/unix-*.cpp', + 'daemon/face/websocket-*.cpp']), use='daemon-objects unit-tests-main', includes='.', install_path=None, @@ -64,6 +65,9 @@ def build(bld): if bld.env['HAVE_UNIX_SOCKETS']: unit_tests_nfd.source += bld.path.ant_glob('daemon/face/unix-*.cpp') + if bld.env['HAVE_WEBSOCKET']: + unit_tests_nfd.source += bld.path.ant_glob('daemon/face/websocket-*.cpp') + unit_tests_rib = bld.program( target='../unit-tests-rib', features='cxx cxxprogram', diff --git a/websocketpp b/websocketpp new file mode 160000 index 00000000..e7ce0382 --- /dev/null +++ b/websocketpp @@ -0,0 +1 @@ +Subproject commit e7ce038207e2c1727a490421afbbe8e41b242318 diff --git a/wscript b/wscript index 4e8622bb..b3f1480d 100644 --- a/wscript +++ b/wscript @@ -32,13 +32,14 @@ from waflib import Logs, Utils, Context def options(opt): opt.load(['compiler_cxx', 'gnu_dirs']) - opt.load(['boost', 'unix-socket', 'dependency-checker', + opt.load(['boost', 'unix-socket', 'dependency-checker', 'websocket', 'default-compiler-flags', 'coverage', 'doxygen', 'sphinx_build'], tooldir=['.waf-tools']) nfdopt = opt.add_option_group('NFD Options') opt.addUnixOptions(nfdopt) + opt.addWebsocketOptions(nfdopt) opt.addDependencyOptions(nfdopt, 'libpcap') nfdopt.add_option('--without-libpcap', action='store_true', default=False, dest='without_libpcap', @@ -55,7 +56,7 @@ def options(opt): def configure(conf): conf.load(['compiler_cxx', 'gnu_dirs', 'default-compiler-flags', - 'boost', 'dependency-checker', + 'boost', 'dependency-checker', 'websocket', 'doxygen', 'sphinx_build']) conf.check_cfg(package='libndn-cxx', args=['--cflags', '--libs'], @@ -79,6 +80,7 @@ def configure(conf): return conf.load('unix-socket') + conf.checkWebsocket(mandatory=True) conf.checkDependency(name='librt', lib='rt', mandatory=False) conf.checkDependency(name='libresolv', lib='resolv', mandatory=False) @@ -130,9 +132,10 @@ def build(bld): source=bld.path.ant_glob(['daemon/**/*.cpp'], excl=['daemon/face/ethernet-*.cpp', 'daemon/face/unix-*.cpp', + 'daemon/face/websocket-*.cpp', 'daemon/main.cpp']), use='core-objects', - includes='daemon', + includes='daemon websocketpp', export_includes='daemon', ) @@ -143,6 +146,9 @@ def build(bld): if bld.env['HAVE_UNIX_SOCKETS']: nfd_objects.source += bld.path.ant_glob('daemon/face/unix-*.cpp') + if bld.env['HAVE_WEBSOCKET']: + nfd_objects.source += bld.path.ant_glob('daemon/face/websocket-*.cpp') + bld(target='bin/nfd', features='cxx cxxprogram', source='daemon/main.cpp', @@ -177,7 +183,8 @@ def build(bld): source='nfd.conf.sample.in', target='nfd.conf.sample', install_path="${SYSCONFDIR}/ndn", - IF_HAVE_LIBPCAP="" if bld.env['HAVE_LIBPCAP'] else "; ") + IF_HAVE_LIBPCAP="" if bld.env['HAVE_LIBPCAP'] else "; ", + IF_HAVE_WEBSOCKET="" if bld.env['HAVE_WEBSOCKET'] else "; ") bld(features='subst', source='tools/nfd-status-http-server.py',