// Copyright (C) 2007 // Fraunhofer Institut fuer offene Kommunikationssysteme (FOKUS) // Kompetenzzentrum fuer Satelitenkommunikation (SatCom) // Stefan Bund // // This program 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 2 of the License, or // (at your option) any later version. // // This program 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 this program; if not, write to the // Free Software Foundation, Inc., // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /** \mainpage libPPI : The Packet Processing Infrastructure The PPI provides an infrastructure to create packet oriented network processin applications. A PPI application is built by combining processing modules in a very flexible manner. \image html scenario.png Target Scenario The PPI concept is built around some key concepts \li The PPI is based on processing \ref packets. It does not handle stream oriented channels. \li The PPI is built around reusable \ref modules. Each module is completely independent. \li Each module has an arbitrary number of \ref connectors, inputs and outputs. \li The modules are connected to each other using flexible \ref connections. \li Data flow throughout the network is governed via flexible automatic or manual \ref throttling. \li Modules may register additional external \ref events (file descriptor events or timers). The PPI thereby builds on the facilities provided by the other components of the SENF framework. Modules are divided roughly in to two categories: I/O modules provide packet sources and sinks (network connection, writing packets to disk, generating new packets) whereas processing modules process packets internally. The target scenario above depicts a diffserv capable UDLR/ULE router including performance optimizations for TCP traffic (PEP). This router is built by combining several modules. In this scenario, TAP, ASI Out, Raw Socket and in a limited way Generator are I/O modules whereas PEP, DiffServ, DVB Enc, GRE/UDLR, TCP Filter and Stufferare processing modules. ASI/MPEG and Net are external I/O ports which are integrated via the TAP, ASI Out and Raw Sock modules using external events. \section packets Packets The PPI processes packets and uses the Packet library to handle them. All packets are passed around as generic Packet::ptr's, the PPI does not enforce any packet type restrictions. \section modules Modules A module is represented by a class type. Each module has several components: \li It may have any number of connectors (inputs and outputs) \li Each module declares flow information which details the route packets take within the module. This information does not define how the information is processed, it only tells, where data arriving on some input will be directed at. \li The module might take additional parameters. \li The module might also register additional events. \code class RateStuffer : public senf::ppi::Module { public: ActiveInput payload; ActiveInput stuffing; ActiveOutput output; RateStuffer(unsigned packetsPerSecond) { route(payload, output); route(stuffing, output); registerEvent(&RateStuffer::tick, senf::ppi::IntervalTimer(1000u, packetsPerSecond)); } private: void tick() { if (payload) output(payload()); else output(stuffing()); } }; \endcode This module declares three I/O connectors (see below): payload, stuffing and output. These connectors are defined as public data members so they can be accessed from the outside. This is important as we will see below. On module instantiation, it will declare it's flow information with route (which is inherited from senf::ppi::Module). Then the module registers an interval timer which will fire packetsPerSecond times every 1000 milliseconds. The processing of the module is very simple: Whenever a timer tick arrives a packet is sent. If the payload input is ready (see throttling below), a payload packet is sent, otherwise a stuffing packet is sent. The module will therefore provide a constant stream of packets at a fixed rate on output An example module to generate the stuffing packets could be \code class CopyPacketGenerator : public senf::ppi::Module { public: PassiveOutput output; CopyPacketGenerator(Packet::ptr template) : template_ (template) { noroute(output); output.onRequest(&CopyPacketGenerator::makePacket); } private: Packet::ptr template_; void makePacket() { output(template_.clone()); } }; \endcode This module just produces a copy of a given packet whenever output is requested. \section connectors Connectors Inputs and Outputs can be active and passive. An \e active I/O is activated by the module to send data or to poll for available packets. A \e passive I/O is signaled by the framework to fetch data from the module or to pass data into the module. To send or receive a packet (either actively or after packet reception has been signaled), the module just calls the connector. This allows to generate or process multiple packets in one iteration. However, reading will only succeed, as long as packets are available from the connection. Since a module is free to generate more than a single packet on incoming packet requests, all input connectors incorporate a packet queue. This queue is exposed to the module and allows the module to process packets in batches. \section connections Connections To make use of the modules, they have to be instantiated and connections have to be created between the I/O connectors. It is possible to connect any pair of input/output connectors as long as one of them is active and the other is passive. It is possible to connect two active connectors with each other using a special adaptor module. This Module has a passive input and a passive output. It will queue any incoming packets and automatically handle throttling events (see below). This adaptor is automatically added by the connect method if needed. To complete our simplified example: Lets say we have an ActiveSocketInput and a PassiveUdpOutput module. We can then use our RateStuffer module to build an application which will create a fixed-rate UDP stream: \code RateStuffer rateStuffer (10); senf::Packet::ptr stuffingPacket = senf::Packet::create<...>(...); CopyPacketGenerator generator (stuffingPacket); senf::UDPv4ClientSocketHandle inputSocket (1111); senf::ppi::ActiveSocketInput udpInput (inputSocket); senf::UDPv4ClientSocketHandle outputSocket ("2.3.4.5:2222"); senf::ppi::PassiveSocketOutput udpOutput (outputSocket); senf::ppi::connect(udpInput.output, rateStuffer.payload, dynamicModule() -> qdisc(ThresholdQueueing(10,5)) ); senf::ppi::connect(generator.output, rateStuffer.stuffing); senf::ppi::connect(rateStuffer.output, udpOutput.input); senf::ppi::run(); \endcode First all necessary modules are created. Then the connections between these modules are set up. The buffering on the udpInput <-> rateStuffer adaptor is changed so the queue will begin to throttle only if more than 10 packets are in the queue. The connection will be unthrottled as soon as there are no more than 5 packets left in the queue. This application will read udp-packts coming in on port 1111 and will forward them to port 2222 on host 2.3.4.5 with a fixed rate of 10 packets / second. \section throttling Throttling If a passive connector cannot handle incoming requests, this connector may be \e throttled. Throttling a request will forward a throttle notification to the module connected to that connector. The module then must handle this throttle notification. If automatic throttling is enabled for the module (which is the default), the notification will automatically be forwarded to all dependent connectors as taken from the flow information. For there it will be forwarded to further modules and so on. A throttle notification reaching an I/O module will normally disable the input/output by disabling any external I/O events registered by the module. When the passive connector which originated the notification becomes active again, it creates an unthrottle notification which will be forwarded in the same way. This notification will re-enable any registered I/O events. The above discussion shows, that throttle events are always generated on passive connectors and received on active connectors. To differentiate further, the throttling originating from a passive input is called backward throttling since it is forwarded in the direction \e opposite to the data flow. Backward throttling notifications are sent towards the input modules. On the other hand, the throttling originating from a passive output is called forward throttling since it is forwarded along the \e same direction the data is. Forward throttling notifications are therefore sent towards the output modules. Since throttling a passive input may not disable all further packet delivery immediately, any passive input contains an input queue. In it's default configuration, the queue will send out throttle notifications when it becomes non-empty and unthrottle notifications when it becomes empty again. This automatic behavior may however be disabled. This allows a module to collect incoming packets in it's input queue before processing a bunch of them in one go. \section events Events Modules may register additional events. These external events are very important since the drive the PPI framework. Possible event sources are \li time based events \li file descriptors. Here some example code implementing the ActiveSocketInput Module: \code class ActiveSocketInput : public senf::ppi::Module { static PacketParser defaultParser_; public: ActiveOutput output; typedef senf::ClientSocketHandle< senf::MakeSocketPolicy< senf::ReadablePolicy, senf::DatagramFramingPolicy > > Socket; // I hestitate taking parser by const & since a const & can be bound to // a temporary even though a const & is all we need. The real implementation // will probably make this a template arg. This simplifies the memory management // from the users pov. ActiveSocketInput(Socket socket, DataParser & parser = SocketInput::defaultParser_) : socket_ (socket), parser_ (parser) event_ (registerEvent( &ActiveSocketInput::data, senf::ppi::IOSignaler(socket, senf::ppi::IOSignaler::Read) )) { route(event_, output); } private: Socket socket_; DataParser const & parser_; senf::ppi:IOSignaler::EventBinding event_; void data() { std::string data; socket_.read(data); output(parser_(data)); } }; \endcode First we declare our own socket handle type which allows us to read packets. The constructor then takes two arguments: A compatible socket and a parser object. This parser object gets passed the packet data as read from the socket (an \c std::string) and returns a senf::Packet::ptr. The \c PacketParser is a simple parser which interprets the data as specified by the template argument. We register an IOSignaler event. This event will be signaled whenever the socket is readable. This event is routet to the output. This routing automates throttling for the socket: Whenever the output receives a throttle notifications, the event will be temporarily disabled. Processing arriving packets happens in the \c data() member: This member simple reads a packet from the socket. It passes this packet to the \c parser_ and sends the generated packet out. \implementation Generation of throttle notifications: Backward throttling notifications are automatically generated (if this is not disabled) whenever the input queue is non-empty \e after the event handler has finished processing. Forward throttling notifications are not generated automatically within the connector. However, the Passive-Passive adaptor will generate Forward-throttling notifications whenever the input queue is empty. \note Open Issues \li We need to clearly differentiate between auto-throttling and auto-throttle-forwarding, between a connectors own throttling state and the forwarded state. \li Exception handling \li ActiveInputs also need a queue: This is necessary to allow a PassiveOutput to create more than a single packet from a single 'onRequest' event. This greatly simplifies writing modules which produce multiple output packets for a single input packet. \li We need to clear up the throttled() member semantics: If the connector is throttled, does it return so if there are still packets in the queue? Probably yes. However, it does not forward throttling notifications until instructed by the qdisc. Throttling notifications are also bound to onThrottle/onUnThrottle callbacks. The semantics are then clear: An active connector emitting onThrottle cannot process any further request (for inputs, no data will be available, for outputs the data will be queued in the peer input) */ // Local Variables: // mode: c++ // fill-column: 100 // c-file-style: "senf" // indent-tabs-mode: nil // ispell-local-dictionary: "american" // mode: flyspell // mode: auto-fill // End: