12 Orber Interceptors
12.1 Using Interceptors
For Inter-ORB communication, e.g., via IIOP, it is possible to intercept requests and replies. To be able to use Interceptors Orber the configuration parameter interceptors must be defined.
Configure Orber to Use Interceptors
The configuration parameter interceptors must be defined, e.g., as command line option:
erl -orber interceptors "{native, ['myInterceptor']}"
It is possible to use more than one interceptor; simply add them to the list and they will be invoked in the same order as they appear in the list.
One can also active and deactivate an interceptor during run-time, but this will only affect currently existing connections. For more information, consult Orber's Reference Manual regarding the operations orber:activate_audit_trail/0/1 and orber:activate_audit_trail/0/1.
Creating Interceptors
Each supplied interceptor must export the following functions:
- new_out_connection/3/5 - one of these operations is called when a client application calls an object residing on remote ORB. If an interceptor exports both versions, arity 3 and 5, which operation that will be invoked is Orber internal.
- new_in_connection/3/5 - one of these operations is invoked when a client side ORB tries to set up a connection to the target ORB. If an interceptor exports both versions, arity 3 and 5, which operation that will be invoked is Orber internal.
- out_request/6 - supplies all request data on the client side ORB.
- out_request_encoded/6 - similar to out_request but the request body is encode.
- in_request_encoded/6 - after a new request arrives at the target ORB the request data is passed to the interceptor in encoded format.
- in_request/6 - prior to invoking the operation on the target object, the interceptor in_request is called.
- out_reply/6 - after the target object replied the out_reply operation is called with the result of the object invocation.
- out_reply_encoded/6 - before sending a reply back to the client side ORB this operation is called with the result in encoded format.
- in_reply_encoded/6 - after the client side ORB receives a reply this function is called with the reply in encoded format.
- in_reply/6 - before delivering the reply to the client this operation is invoked.
- closed_in_connection/1 - when a connection is terminated on the client side this function is called.
- closed_out_connection/1 - if an outgoing connection is terminated this operation will be invoked.
The operations new_out_connection, new_in_connection, closed_in_connection and closed_out_connection operations are only invoked once per connection. The remaining operations are called, as shown below, for every Request/Reply to/from remote CORBA Objects.
Figure 12.1: The Invocation Order of Interceptor Functions.
12.2 Interceptor Example
Assume we want to create a simple access service which purpose is to:
- Only allow incoming request from ORB's residing on a certain set of nodes.
- Restrict the objects any client may invoke operations on.
- Only allow outgoing requests to call a limited set of external ORB's.
- Add a checksum to each binary request/reply body.
To restricts the access we use a protected and named ets-table holding all information. How the ets-table is initiated and maintained is implementation specific, but it contain {Node, ObjectTable, ChecksumModule} where Node is used as ets-key, ObjectTable is a reference to another ets-table in which we store which objects the clients are allowed to invoke operations on and ChecksumModule determines which module we should use to handle the checksums.
new_in_connection(Arg, Host, Port) -> %% Since we only use one interceptor we do not care about the %% input Arg since it is set do undefined by Orber. case ets:lookup(in_access_table, Host) of [] -> %% We may want to log the Host/Port to see if someone tried %% to hack in to our system. exit("Access not granted"); [{Host, ObjTable, ChecksumModule}] -> {ObjTable, ChecksumModule} end.
The returned tuple, i.e., {ObjTable, ChecksumModule}, will be passed as the first argument whenever invoking one of the interceptor functions. Unless the connection attempt did not fail we are now ready for receiving requests from the client side ORB.
When a new request comes in the first interceptor function to be invoked is in_request_encoded. We will remove the checksum from the coded request body in the following way:
in_request_encoded({ObjTable, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) -> NewBin = ChecksumModule:remove_checksum(Bin), {NewBin, Extra}.
If the checksum check fails the ChecksumModule should invoke exit/1. But if the check succeeded we are now ready to check if the client-ORB objects are allowed to invoke operations on the target object. Please note, it is possible to run both checks in in_request_encoded. Please note, the checksum calculation must be relatively fast to ensure a good throughput.
If we want to we can restrict any clients to only use a subset of operations exported by a server:
in_request({ObjTable, ChecksumModule}, ObjKey, Ctx, Op, Params, Extra) -> case ets:lookup(ObjTable, {ObjKey, Op}) of [] -> exit("Client tried to invoke illegal operation"); [SomeData] -> {Params, Extra} end.
At this point Orber are now ready to invoke the operation on the target object. Since we do not care about what the reply is the out_reply function do nothing, i.e.:
out_reply(_, _, _, _, Reply, Extra) -> {Reply, Extra}.
If the client side ORB expects a checksum to be added to the reply we add it by using:
out_reply_encoded({ObjTable, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) -> NewBin = ChecksumModule:add_checksum(Bin), {NewBin, Extra}.
If we manipulate the binary as above the behavior must be Bin == remove_checksum(add_checksum(Bin)).
For outgoing requests the principle is the same. Hence, it is not further described here. The complete interceptor module would look like:
-module(myInterceptor). %% Interceptor functions. -export([new_out_connection/3, \011 new_in_connection/3, \011 closed_in_connection/1, \011 closed_out_connection/1, \011 in_request_encoded/6, \011 in_reply_encoded/6, \011 out_reply_encoded/6, \011 out_request_encoded/6, \011 in_request/6, \011 in_reply/6, \011 out_reply/6, \011 out_request/6]). new_in_connection(Arg, Host, Port) -> %% Since we only use one interceptor we do not care about the %% input Arg since it is set do undefined by Orber. case ets:lookup(in_access_table, Host) of [] -> %% We may want to log the Host/Port to see if someone tried %% to hack in to our system. exit("Access not granted"); [{Host, ObjTable, ChecksumModule}] -> {ObjTable, ChecksumModule} end. new_out_connection(Arg, Host, Port) -> case ets:lookup(out_access_table, Host) of [] -> exit("Access not granted"); [{Host, ObjTable, ChecksumModule}] -> {ObjTable, ChecksumModule} end. in_request_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) -> NewBin = ChecksumModule:remove_checksum(Bin), {NewBin, Extra}. in_request({ObjTable, _}, ObjKey, Ctx, Op, Params, Extra) -> case ets:lookup(ObjTable, {ObjKey, Op}) of [] -> exit("Client tried to invoke illegal operation"); [SomeData] -> {Params, Extra} end. out_reply(_, _, _, _, Reply, Extra) -> {Reply, Extra}. out_reply_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) -> NewBin = ChecksumModule:add_checksum(Bin), {NewBin, Extra}. out_request({ObjTable, _}, ObjKey, Ctx, Op, Params, Extra) -> case ets:lookup(ObjTable, {ObjKey, Op}) of [] -> exit("Client tried to invoke illegal operation"); [SomeData] -> {Params, Extra} end. out_request_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) -> NewBin = ChecksumModule:add_checksum(Bin), {NewBin, Extra}. in_reply_encoded({_, ChecksumModule}, ObjKey, Ctx, Op, Bin, Extra) -> NewBin = ChecksumModule:remove_checksum(Bin), {NewBin, Extra}. in_reply(_, _, _, _, Reply, Extra) -> {Reply, Extra}. closed_in_connection(Arg) -> %% Nothing to clean up. Arg. closed_out_connection(Arg) -> %% Nothing to clean up. Arg.
One can also use interceptors for debugging purposes, e.g., print which objects and operations are invoked with which arguments and the outcome of the operation. In conjunction with the configuration parameter orber_debug_level it is rather easy to find out what went wrong or just to log the traffic.