| Home · Search · Print · Help  
 Loadable modulesThis page documents loadable modules interface for Web Polygraph.
This functionality is supported starting with Polygraph version 2.8.0. Table of Contents
1. Introduction2. Availability
 3. Enabling a module
 4. Writing a module
 4.1 Session watchdog modules
 4.2 Header filter modules
 4.3 Non C++ modules
 5. Compiling and linking
 5.1 Linking polyclt
 5.2 Compiling a module
 
 1. Introduction
	Loadable modules are pieces of object code that can be injected into a
	running program. The program source code is not modified and does not need
	to be recompiled. Once the module is loaded, it can manipulate host
	program data and call program functions. Usually, the host program
	delegates some optional processing to loadable modules so that users can
	modify and enhance the functionality of the program without modifying or
	even understanding much of the original source code. Polygraph uses loadable modules for two primary purposes: 
		Allow users to supply custom actions when simulated
		user sessions are started or terminated. This is useful for
		testing session-based authentication mechanisms such as RADIUS
		authentication.Allow users to modify HTTP headers sent by polyclt. A
		module can add custom HTTP header fields or even alter
		Polygraph-supplied headers, all without changing Polygraph source
		code. Server-side support can be added on-demand. The original motivation for adding module interfaces was to let
	Polygraph users to support proprietary and closed authentication
	protocols, such as Microsoft NTLM, without the need to expose the
	authentication source code or modify Polygraph. Other uses are certainly
	possible, and Polygraph development team would be happy to talk to those
	who need to extend the interface. Polygraph distribution does not include useful modules. If you need to
	fiddle with HTTP headers emitted by Polygraph, you have to write your own
	module to do that. 2. Availability
	Loadable modules are supported starting with Polygraph version 2.8.  At
	the time of writing, that version has not been released. Loadable modules may work on any platform that supports
	dlopen(3) family of function calls that provide interface to the
	dynamic linker. FreeBSD, Linux, and many other OSes support
	dlopen(3). Similar functionality is available on Microsoft
	Windows, but it is not supported in Polygraph (yet?). Polygraph's ./configure script will warn you if it is unable
	to detect libraries and header files necessary to support dynamic loadable
	modules. 3. Enabling a module
	If you already have a module, it is easy to add it to polyclt
	using the --loadable_modules option: polyclt ... --loadable_modules /tmp/myfilter.so ...
...
000.01| loading dynamic module: /tmp/myfilter.so
000.01| dynamic modules loaded: 1
...
000.02| registered client-side data filters: 1
            MyFilter-0.1:      adds HTTP Foo-Bar header
 The polyclt output above shows that Polygraph first loads the
	specified module (using /tmp/myfilter.so, a file with the module
	code) and then reports all registered client-side filters. Given the above
	output, we can assume that /tmp/myfilter.so contained a filter
	called "MyFilter" that adds a "Foo-Bar" header to polyclt HTTP
	headers. It is best to use absolute filenames with --loadable_modules
	because the dynamic linker often does not check current directories and
	because the actual current directory may differ at the time
	polyclt is started from, say, a shell script. 4. Writing a module
	To write a friendly module, you need to know the interface that
	Polygraph is using to communicate with loaded modules. There are two such
	interfaces: a session watchdog interface and a header filter interface.
	We describe both below. The descriptions assume that C++ language
	is used to write the modules. We end this section with an explanation
	on how to add a non-C++ module/code to Web Polygraph. 4.1 Session watchdog modulesAll watchdog modules must inherit from the SessionWatch class
	declared in runtime/SessionWatch.h. They must implement all pure
	virtual methods of that class. When polyclt loads a session watchdog module, the module
	initialization proceeds as if the module was a part of a program being
	started. As a part of the initialization process, the module should
	register itself by calling the TheSessionWatchRegistry().add()
	method declared in client/SessionWatchRegistry.h. The only
	parameter to the method is a pointer to the watchdog object being
	registered. See an example below on how this auto-registration can be
	implemented in C++. When polyclt is ready to start a new user session (and before
	the first request is submitted within that session), polyclt
	notifies every registered session watchdog by calling its
	noteStart(client) method. Polygraph will also call session
	watchdogs every heartbeat interval and when the session ends.  PGL Session
	type is documented elsewhere. The watchdogs are called one-by-one, in registration order. There is no
	way to terminate the calling process prematurely.  A watchdog
	may do nothing or may, for example, form and send a RADIUS packet
	on the network. Once all watchdogs are called, polyclt 
	proceeds as usual. Polygraph supplies a pointer to the Client class that
	represents the HTTP user or robot initiating the session.  A watchdog may
	obtain details about the session using public Client methods.
	For example, robot IP address and configured credentials are accessible.
	The watchdog may also access Polygraph's global structures and functions,
	just like any other piece of Polygraph code. Below is an example of a simple session watchdog that announces session
	states on the console along with robot IP address and credentials. See the
	src/client/SessionAnnouncer.cc file in the Polygraph distribution
	for a copy of this code. #include "base/polygraph.h"
#include "runtime/LogComment.h"
#include "client/SessionWatchRegistry.h"
#include "client/Client.h"
class SessionAnnouncer: public SessionWatch<Client> {
    public:
virtual String id() const;
virtual void describe(ostream &os) const;
virtual void noteStart(const Client *client);
virtual void noteHeartbeat(const Client *client);
virtual void noteEnd(const Client *client);
    protected:
void announce(const Client *client, const String &state) const;
};
static bool registered = registered ||
    TheSessionWatchRegistry().add(new SessionAnnouncer);
String SessionAnnouncer::id() const {
    return "SessionAnnouncer-1.0";
}
void SessionAnnouncer::describe(ostream &os) const {
    os << "announces client-side sessions on the console";
}
void SessionAnnouncer::noteStart(const Client *client) {
    announce(client, "starts session");
    Comment(5) << "robot credentials: " << client->credentials() << endc;
}
void SessionAnnouncer::noteHeartbeat(const Client *client) {
    announce(client, "continues session");
}
void SessionAnnouncer::noteEnd(const Client *client) {
    announce(client, "ends session");
}
void SessionAnnouncer::announce(const Client *client, const String &action) const {
    Comment(5) << "robot " << client->id() << " @ " << client->host() << 
": " << action << endc;
}
 4.2 Header filter modulesAll loadable filters must inherit from the
	CltDataFilterRegistry::Filter class declared in
	client/CltDataFilterRegistry.h. They must implement all pure
	virtual methods of that class. When polyclt loads a filter module, the module initialization
	proceeds as if the module was a part of a program being started. As a part
	of the initialization process, the module should register itself by
	calling the TheCltDataFilterRegistry().add() method declared in
	client/CltDataFilterRegistry.h. The only parameter to the method
	is a pointer to the filter object being registered. See examples below on how
	this auto-registration can be implemented in C++. When polyclt is done with stuffing HTTP request headers
	(including request line but excluding terminating CRLF), it passes the
	buffer with the headers through all registered filters using their
	apply() method. The filters are applied one-by-one, in registration
	order. There is no way to terminate the filtering process prematurely.  A
	filter may modify the buffer in any way or may chose to do nothing,
	depending on the headers being passed. Once all filters are applied,
	polyclt appends terminating CRLF and eventually sends the
	headers to the wire. Polygraph supplies a pointer to the CltXact class that
	represents the current HTTP transaction building the headers. A filter may
	obtain details about the transaction using CltXact methods.
	The filter may access Polygraph's global structures and functions,
	just like any other piece of Polygraph code. Below is an example of a simple filter. A more complex example of a
	filter that adds HTTP Basic authentication headers is available in
	Polygraph distribution as src/client/HttpBasicAuthenticator.cc. #include "base/polygraph.h"
#include <iostream>
#include "runtime/IOBuf.h"
#include "client/CltDataFilterRegistry.h"
class MyFilter: public CltDataFilterRegistry::Filter {
    public:
virtual String id() const { return "MyFilter-0.1"; }
virtual void describe(ostream &os) const;
virtual void apply(CltDataFilterRegistry::Producer &p, IOBuf &buf);
};
static bool registered = registered ||
    TheCltDataFilterRegistry().add(new MyFilter);
void MyFilter::describe(ostream &os) const {
    os << "adds HTTP Foo-Bar header";
}
void MyFilter::apply(CltDataFilterRegistry::Producer &, IOBuf &buf) {
    static const String header = "Foo-Bar: XYZ\r\n";
    buf.append(header.data(), header.len());
}
 Also see an NTLM authentication filter below. 4.3 Non C++ modulesIf you already have code written in C or any programming language
		other than C++, you can still use the module interface as long as you
		can call your routines from C++. Here is how one might wrap existing C
		code that implements NTLM authentication via C
		AddNtlmAuthHeaders() function. #include "base/polygraph.h"
#include <iostream>
#include "runtime/LogComment.h"
#include "runtime/IOBuf.h"
#include "client/CltDataFilterRegistry.h"
#include "client/UserCred.h"
#include "client/CltXact.h"
// this is the profile of a C routine we will be calling;
// normally, it would be in some .h header we would include above,
// surrounded by extern "C" {}
extern "C" int AddNtlmAuthHeaders(
    const char *credentials, const char *domain,
    const char *hdrsRep, int hdrsRepSize,
    char *hdrsReq, int *hdrsReqSize);
// a C++ wrapper implementing Polygraph's filter interface
class NtmlFilter: public CltDataFilterRegistry::Filter {
    public:
        virtual String id() const { return "NtmlFilter-3.6d"; }
        virtual void describe(ostream &os) const;
        virtual void apply(CltDataFilterRegistry::Producer &p, IOBuf &buf);
};
static bool registered = registered ||
    TheCltDataFilterRegistry().add(new NtmlFilter);
void NtmlFilter::describe(ostream &os) const {
    os << "adds NTLM authentication headers via C routine";
}
void NtmlFilter::apply(CltDataFilterRegistry::Producer &p, IOBuf &buf) {
    // append authentication headers only of user credentials are
    // set; the latter implies that we received '407 Auth Required'
    const UserCred &credentials = p->credentials();
    if (!credentials.image())
        return;
    // must have NTLM challange info in 407 response that caused this xaction
    const CltXact *cause = p->cause();
    if (!cause)
        return; // XXX: report error here
    // Polygraph should have saved response header for us
    const IOBuf &repHeader = cause->savedRepHeader();
    if (repHeader.contSize() <= 0)
        return; // XXX: report error here
    int exchangeSize = buf.spaceSize();
    const int err = AddNtlmAuthHeaders(
        credentials.image().cstr(), "hardcoded-domain",
        repHeader.content(), repHeader.contSize(),
        buf.space(), &exchangeSize); // in: space size; out: content size
    if (err) {
        Comment << "error: AddNtlmAuthHeaders() error code: " << err << endc;
        return;
    }
    // let I/O buffer know that some content got appended
    if (Should(0 <= exchangeSize && exchangeSize <= buf.spaceSize()))
        buf.appended(exchangeSize);
}
 Similarly, modules in any programming language can be used as long
		as you can call your routines from C++. 5. Compiling and linking
	To use a loadable module, one must both 
		link polyclt executable in a special way andcompile module code in a special way. This section talks about each step. Unless noted otherwise, the
	discussion is FreeBSD-specific. If there are no instructions for your
	operating system below, you will need to experiment; please send us
	commands for environments not covered here so that others can enjoy the
	fruits of your labor. 5.1 Linking polycltFor loadable modules to work, polyclt executable must have
	symbol tables with all code symbols used by a module, even if some of
	those symbols are not used by core polyclt code. GNU C++ compiler
	from the GCC suite has a special option to build complete symbol tables.
	That option is called -rdynamic. Documentation for
	-rdynamic is scarce, but it appears that the compiler converts
	the option into appropriate options for the linker, depending on the build
	platform. Some platforms require no special linker options because they
	build complete dynamic symbol tables by default. Some GNU linkers use the
	--whole-archive option. Polygraph's ./configure script configures Makefiles to use
	-rdynamic when linking polyclt executable, provided you
	are using a GCC compiler, and that the compiler appears to produce
	executables when this option is used (i.e., the compiler does not quit
	with an error when the option is supplied). If the configuration does not do the right thing in your environment,
	you will need to supply correct linking options before configuring
	Polygraph (use LDFLAGS environment variable for that) or manually
	re-link polyclt with the right set of options. Note that
	providing custom LDFLAGS will affect linking of all
	Polygraph executables. 5.2 Compiling a moduleThe compiler options required to compile a loadable module are OS- and
	compiler-specific. Essentially, you need to compile the module as a shared
	library. For example, the following command compiles a loadable module
	called MyModule with module object code already compiled as
	MyModule.o. c++ -shared -export-dynamic -Wl,-soname,MyModule.so -o MyModule.so \
    -I. -I.. -I../.. MyModule.o
 The command works on FreeBSD and Linux with GCC from within the
	src/client directory of Polygraph source distribution. The command
	produces MyModule.so file to be used as a loadable module. 
 Home · Search · Print · Help  
 |