Skip to content

Instantly share code, notes, and snippets.

@yoursunny
Last active March 9, 2016 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yoursunny/e3c520a590af86a0bb37 to your computer and use it in GitHub Desktop.
Save yoursunny/e3c520a590af86a0bb37 to your computer and use it in GitHub Desktop.
NFD management dispatcher http://redmine.named-data.net/issues/2200
namespace ndn {
namespace mgmt {
// ---- AUTHORIZATION ----
/** \brief a function to be called if authorization is successful
* \param requester a string that indicates the requester, whose semantics is determined by
* the Authorization function; this value is intended for logging only,
* and should not affect how the request is processed
*/
typedef std::function<void(const std::string& requester)> AcceptContinuation;
/** \brief indicate how to reply in case authorization is rejected
*/
enum class RejectReply {
/** \brief do not reply
*/
SILENT,
/** \brief reply with a ControlResponse where StatusCode is 403
*/
STATUS403,
/** \brief reply with an application NACK
*/
NACK
};
/** \brief a function to be called if authorization is rejected
*/
typedef std::function<void(RejectReply act)> RejectContinuation;
/** \brief a function that performs authorization
* \param prefix top-level prefix, eg. "/localhost/nfd";
* This argument can be inspected to allow Interests only under a subset of
* top-level prefixes (eg. allow "/localhost/nfd" only),
* or to use different trust model regarding to the prefix.
* \param interest incoming Interest
* \param params parsed ControlParameters for ControlCommand, otherwise nullptr;
* This is guaranteed to be not-null and have correct type for the command,
* but may not be valid (eg. can have missing fields).
*
* Either accept or reject must be called after authorization completes.
*/
typedef std::function<void(const Name& prefix, const Interest& interest,
const ControlParameters* params,
AcceptContinuation accept, RejectContinuation reject)> Authorization;
/** \return an Authorization that accepts all Interests, with empty string as requester
*/
Authorization
makeAcceptAllAuthorization();
// ---- ControlCommand ----
/** \brief base class for a struct that contains ControlCommand parameters
*/
class ControlParameters
{
public:
virtual void
wireDecode(const Block& wire) = 0;
virtual Block
wireEncode() const = 0;
};
/** \brief a function to validate input ControlParameters
* \param params parsed ControlParameters;
* This is guaranteed to have correct type for the command.
*/
typedef std::function<bool(const ControlParameters& params)> ValidateParameters;
/** \brief ControlCommand response
*/
class ControlResponse
{
// (move from original ndn::nfd::ControlResponse)
};
/** \brief a function to be called after ControlCommandHandler completes
* \param resp the response to be sent to requester
*/
typedef std::function<void(const ControlResponse& resp)> CommandContinuation;
/** \brief a function to handle an authorized ControlCommand
* \param prefix top-level prefix, eg. "/localhost/nfd";
* \param interest incoming Interest
* \param params parsed ControlParameters;
* This is guaranteed to have correct type for the command,
* and is valid (eg. has all required fields).
*/
typedef std::function<void(const Name& prefix, const Interest& interest,
const ControlParameters& params,
CommandContinuation done)> ControlCommandHandler;
// ---- StatusDataset ----
class StatusDatasetContext
{
public:
/** \return prefix of Data packets, with version component but without segment component
*/
const Name&
getPrefix() const;
/** \brief change prefix of Data packets
* \param prefix the prefix; it must start with Interest Name, may contain version component,
* but must not contain segment component
* \throw std::invalid_argument prefix does not start with Interest Name
* \throw std::domain_error append, end, or reject has been invoked
*
* StatusDatasetHandler may change the prefix of Data packets with this method,
* before sending any response.
* The version component is optional, and will be generated from current timestamp when omitted.
*/
void
setPrefix(const Name& prefix);
/** \return expiration duration for this dataset response
*/
const time::milliseconds&
getExpiry() const;
/** \brief set expiration duration
*
* The response will be cached for the specified duration.
* Incoming Interest that matches a cached response will be satisfied with that response,
* without invoking StatusDatasetHandler again.
*/
void
setExpiry(const time::milliseconds& expiry);
/** \brief append a Block to the response
* \throw std::domain_error end or reject has been invoked
*/
void
append(const Block& block);
/** \brief end the response successfully after appending zero or more blocks
* \throw std::domain_error reject has been invoked
*/
void
end();
/** \brief declare the non-existence of a response
* \throw std::domain_error append or end has been invoked
*
* This should be invoked when the incoming Interest is malformed.
* A producer-generated NACK will be returned to requester.
*
* \param content Content of producer-generated NACK
*/
void
reject(const Block& content = Block(tlv::Content));
private:
friend class Dispatcher;
};
// note: no additional public members should be added;
// private members can be added as necessary, which is accessible by dispatcher as a friend
// note: before #2182, setExpiry should be setting the FreshnessPeriod field of all outgoing Data.
/** \brief a function to handle a StatusDataset request
* \param prefix top-level prefix, eg. "/localhost/nfd";
* \param interest incoming Interest; its Name doesn't contain version and segment components
* \param context a collection of functions to respond to the request
*
* This function can generate zero or more blocks and pass them to \p context.append,
* and must call \p context.end upon completion.
* Alternatively, this function may reject the request by calling \p context.reject.
*/
typedef std::function<void(const Name& prefix, const Interest& interest,
StatusDatasetContext& context)> StatusDatasetHandler;
// ---- NotificationStream ----
/** \brief a function to post a notification
*/
typedef std::function<void(const Block& notification)> PostNotification;
// ---- DISPATCHER ----
/** \brief represents a dispatcher on server side of NFD Management protocol
*/
class Dispatcher : noncopyable
{
public:
/** \brief constructor
* \param face the Face on which the dispatcher operates
* \param keyChain a KeyChain to sign Data
* \param signingInfo signing parameters to sign Data with \p keyChain
*/
Dispatcher(Face& face, security::KeyChain& keyChain, const security::SigningInfo& signingInfo = security::SigningInfo());
virtual
~Dispatcher();
/** \brief add a top-level prefix
* \param prefix a top-level prefix, eg. "/localhost/nfd"
* \param wantRegister whether prefix registration should be performed through the Face
* \param signingInfo signing parameters to sign the prefix registration command
* \throw std::out_of_range \p prefix overlaps with an existing top-level prefix
*
* Procedure for adding a top-level prefix:
* 1. if the new top-level prefix overlaps with an existing top-level prefix
* (one top-level prefix is a prefix of another top-level prefix), throw std::domain_error
* 2. if wantRegister is true, invoke face.registerPrefix for the top-level prefix;
* the returned RegisteredPrefixId shall be recorded internally, indexed by the top-level prefix
* 3. foreach relPrefix from ControlCommands and StatusDatasets,
* join the top-level prefix with the relPrefix to obtain the full prefix,
* and invoke non-registering overload of face.setInterestFilter,
* with the InterestHandler set to an appropriate private method to handle incoming Interests
* for the ControlCommand or StatusDataset;
* the returned InterestFilterId shall be recorded internally, indexed by the top-level prefix
*/
void
addTopPrefix(const Name& prefix, bool wantRegister = true, const security::SigningInfo& signingInfo = security::SigningInfo());
// note: SigningInfo taken in constructor shouldn't be used for the prefix registration command,
// because the Face may have a different KeyChain
/** \brief remove a top-level prefix
* \param prefix a top-level prefix, eg. "/localhost/nfd"
*
* Procedure for removing a top-level prefix:
* 1. if the top-level prefix has not been added, abort these steps
* 2. if the top-level prefix has been added with wantRegister,
* invoke face.unregisterPrefix with the RegisteredPrefixId
* 3. foreach InterestFilterId recorded during addTopPrefix,
* invoke face.unsetInterestFilter with the InterestFilterId
*/
void
removeTopPrefix(const Name& prefix)
public: // ControlCommand
/** \brief register a ControlCommand
* \tparam CP subclass of ControlParameters used by this command
* \param relPrefix a prefix for this command, eg. "faces/create";
* relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be non-overlapping
* (no relPrefix is a prefix of another relPrefix)
* \pre no top-level prefix has been added
* \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
* \throw std::domain_error one or more top-level prefix has been added
*
* Procedure for processing a ControlCommand:
* 1. extract the NameComponent containing ControlParameters (the component after relPrefix),
* and parse ControlParameters into type CP; if parsing fails, abort these steps
* 2. perform authorization; if authorization is rejected,
* perform the RejectReply action, and abort these steps
* 3. validate ControlParameters; if validation fails,
* make ControlResponse with StatusCode 400, and go to step 5
* 4. invoke handler, wait until CommandContinuation is called
* 5. encode the ControlResponse into one Data packet
* 6. sign the Data packet
* 7. if the Data packet is too large, abort these steps and log an error
* 8. send the signed Data packet
*/
template<typename CP>
void
addControlCommand(const PartialName& relPrefix,
Authorization authorization,
ValidateParameters validateParams,
ControlCommandHandler handler);
public: // StatusDataset
/** \brief register a StatusDataset or a prefix under which StatusDatasets can be requested
* \param relPrefix a prefix for this dataset, eg. "faces/list";
* relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be non-overlapping
* (no relPrefix is a prefix of another relPrefix)
* \param authorization should set identity to Name() if the dataset is public
* \pre no top-level prefix has been added
* \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
* \throw std::domain_error one or more top-level prefix has been added
*
* Procedure for processing a StatusDataset request:
* 1. if the request Interest contains version or segment components, abort these steps;
* note: the request may contain more components after relPrefix, eg. a query condition
* 2. perform authorization; if authorization is rejected,
* perform the RejectReply action, and abort these steps
* 3. construct StatusDatasetContext
* 4. invoke handler, wait until either StatusDatasetContext::end or
* StatusDatasetContext::reject is called
* 5. if StatusDatasetContext::reject has been called, create a producer-generated NACK,
* sign and send it, and abort these steps
* 6. segment the buffer of blocks accepted from StatusDatasetContext::append
* into one or more segments under the allocated version,
* such that the Data packets will not become too large after signing
* 7. set FinalBlockId on at least the last segment
* 8. sign and send the Data packets
*
* As an optimization, a Data packet may be sent as soon as enough octets have been collected
* through StatusDatasetContenxt::append calls.
*/
void
addStatusDataset(const PartialName& relPrefix,
Authorization authorization,
StatusDatasetHandler handler);
// note: This design pushes all Data packets in the response to the forwarder,
// assuming the ContentStore will admit them.
// This will change in #2182.
public: // NotificationStream
/** \brief register a NotificationStream
* \param relPrefix a prefix for this notification stream, eg. "faces/events";
* relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be non-overlapping
* (no relPrefix is a prefix of another relPrefix)
* \return a function into which notifications can be posted
* \pre no top-level prefix has been added
* \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
* \throw std::domain_error one or more top-level prefix has been added
*
* Procedure for posting a notification:
* 1. if no top-level prefix has been added, or more than one top-level prefixes have been added,
* abort these steps and log an error
* 2. assign the next sequence number to the notification
* 3. place the notification block into one Data packet under the sole top-level prefix
* 4. sign the Data packet
* 5. if the Data packet is too large, abort these steps and log an error
* 6. send the signed Data packet
*/
PostNotification
addNotificationStream(const PartialName& relPrefix);
// note: This NotificationStream only works when there's exactly one top-level prefix.
// This fits today's use cases, but will be improved after #2182.
};
} // namespace mgmt
} // namespace ndn
namespace ndn {
namespace nfd {
class ControlParameters : public ::ndn::mgmt::ControlParameters
{
// (omitted)
};
/** \tparam CC a subclass of ::ndn::nfd::ControlCommand
* \return a ValidateParameters constructed from CC::validateRequest
*/
template<typename CC>
::ndn::mgmt::ValidateParameters
makeRequestParameterValidator();
} // namespace nfd
} // namespace ndn
namespace nfd {
/** \brief a router face that is connected to InternalClientFace
*/
class InternalFace : public nfd::Face
{
};
/** \brief a client face that is connected to NFD InternalFace
*/
class InternalClientFace : public ndn::Face
{
public:
/** \brief constructor a client face and hook to an InternalFace
*/
explicit
InternalClientFace(InternalFace& internalFace);
};
} // namespace nfd
// example for FaceManager
namespace nfd {
class ManagementAuthorization : noncopyable
{
public:
explicit
ManagementAuthorization(ConfigFile section);
void
authorize(const Name& prefix, const Interest& interest,
const ::ndn::mgmt::ControlParameters* params,
::ndn::mgmt::AcceptContinuation accept, ::ndn::mgmt::RejectContinuation reject) const
{
if (/* signature is bad, or certificate is unknown */) {
reject(RejectReply::STATUS403);
return;
}
name::Component module = interest.getName().at(prefix.size());
if (/* certificate has the privilege */) {
accept(/* username of the certificate owner */);
}
else {
reject(RejectReply::STATUS403);
}
}
};
class FaceManager
{
public:
void
dispatcherRegister(::nfd::mgmt::Dispatcher& dispatcher)
{
auto commandAuth = bind(&ManagementAuthorization::authorize, m_auth, _1, _2, _3, _4, _5);
auto acceptAllAuth = ::ndn::mgmt::makeAcceptAllAuthorization();
auto validateCreateParams = ::ndn::nfd::makeRequestParameterValidator<::ndn::nfd::FaceCreateCommand>();
dispatcher.addControlCommand("faces/create", commandAuth, validateCreateParams,
bind(&FaceManager::handleCreateCommand, this, _1, _2, _3, _4));
dispatcher.addStatusDataset("faces/list", acceptAllAuth,
bind(&FaceManager::handleListRequest, this, _1, _2, _3));
dispatcher.addStatusDataset("faces/query", acceptAllAuth,
bind(&FaceManager::handleQueryRequest, this, _1, _2, _3));
this->m_postStatusChangeNotification = dispatcher.addNotificationStream("faces/events");
this->startNotificationStream();
}
void
startNotificationStream()
{
m_faceTable.onAdd += [] (shared_ptr<Face> face) {
FaceEvent evt = {CREATED, face};
m_postStatusChangeNotification(evt.wireEncode());
}
}
private:
void
handleCreateCommand(const Name& prefix, const Interest& interest,
const ::ndn::mgmt::ControlParameters& params,
CommandContinuation done)
{
const ::ndn::nfd::ControlParameters& nfdParams = static_cast<const ::ndn::nfd::ControlParameters&>(params);
createFace(nfdParams.faceUri,
[done] (FaceId faceId) {
ControlResponse resp;
/* populate resp */
done(resp);
},
[done] {
ControlResponse resp;
/* populate resp */
done(resp);
});
}
void
handleListRequest(const Name& prefix, const Interest& interest,
::ndn::mgmt::StatusDatasetContext context)
{
for (const Face& face : m_faceTable) {
context.append(face.getFaceStatus().wireEncode());
}
context.end();
}
void
handleQueryRequest(const Name& prefix, const Interest& interest,
::ndn::mgmt::StatusDatasetContext context)
{
const name::Component& filterComponent = interest.getName().at(prefix.size() + PartialName("faces/query").size());
FaceQueryFilter filter;
try {
filter.wireDecode(filterComponent);
}
catch (tlv::Error&) {
context.reject();
return;
}
for (const Face& face : m_faceTable) {
if (filter.matches(face)) {
context.append(face.getFaceStatus().wireEncode());
}
}
context.end();
}
private:
FaceTable m_faceTable;
ManagementAuthorization m_auth;
KeyChain m_keyChain;
::ndn::mgmt::PostNotification m_postStatusChangeNotification;
};
} // namespace nfd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment