Contents
Part 2. Interaction between entities
The preceding part of the Guide shows how to configure interaction with the entities provided in KasperskyOS Community Edition. To do so, just add several strings to the init.yaml
file and link the client library of the entity (vfs_remote
).
But how do you create a server entity, or in other words, an application that provides functionality to other (client) entities? To do so, utilize IPC transport, auxiliary tools and libraries provided in KasperskyOS Community Edition.
This part of the Guide illustrates the following:
- Mechanism for interaction between entities in KasperskyOS
- Tools and libraries that implement transport
- Step-by-step actions for exchanging IPC messages
To simplify the descriptions, the examples in this part of the Guide are built without the ksm.module
. For this reason, when examples are started, a notification WARNING! Booting an insecure kernel!
is displayed. The third part of the Guide examines a solution security policy, use of security policies, and building ksm.module
.
IPC transport tools
To set up interactions between entities, you do not have to implement the correct packaging and unpacking of messages from scratch. In addition, you do not have to write separate code for creating IPC channels.
To resolve these and other problems associated with IPC transport, KasperskyOS provides a special set of tools:
- NkKosTransport
- EDL, CDL and IDL descriptions
- NK compiler
- Init description and einit tool
- Service Locator
Combined use of these tools is shown in the echo example.
NkKosTransport
NkKosTransport is a convenient wrapper for the Call
, Recv
and Reply
system calls. It lets you work separately with messages' constant parts and arenas.
The nk_transport_call()
, nk_transport_recv()
and nk_transport_reply()
functions are used to call transport.
Example:
/* The nk_transport_recv () function executes the Recv system call.
* The request received from the client is inserted into req (constant part of the response) and
* req_arena (response arena). */
nk_transport_recv(&transport.base, (struct nk_message *)&req, &req_arena);
The NkKosTransport
structure and methods for working with it are declared in the transport-kos.h
file:
For more details about the constant part and the arena, refer to IPC message structure.
For more details about using NkKosTransport, refer to NkKosTransport.
EDL, CDL and IDL descriptions
The EDL, CDL and IDL languages are used to describe interfaces that implement server entities.
For more details, refer to Description of entities, components, and interfaces (EDL, CDL, IDL).
Description files (*.edl
, *.cdl
and *.idl
) are processed by the NK compiler during the build. This results in the creation of the *.edl.h
, *.cdl.h
, and *.idl.h
files, which contain the transport methods and types used on the client and on the server.
NK compiler
Based on the EDL-, CDL- and IDL descriptions, the NK compiler (nk-gen-c
) generates a set of transport methods and types. The transport methods and types are needed for generating, sending, receiving and processing IPC messages.
Most important transport methods:
- Interface methods. When an interface method is called on the client, the server is sent an IPC request for calling the appropriate method.
- Dispatch methods (dispatchers). When receiving a request, the server calls the dispatcher, which in turn calls the implementation of the appropriate method.
Most important transport types:
- Types that define the structure of the constant part of a message. They are sent to interface methods, dispatchers and transport functions (
nk_transport_recv()
,nk_transport_reply()
). - Types of proxy objects. A proxy object is used as an argument in an interface method.
For more details, refer to Generated methods and types.
Init description and einit tool
The einit
tool lets you automate the creation of code of the Einit
initializing entity. This entity is the first to start when KasperskyOS is loaded. Then it starts the other entities and creates channels (connections) between them.
To enable the Einit
entity to create a connection between entities A
and B
during startup, you need to specify the following in the init.yaml
file:
init.yaml
# Start B
- name: B
# Start A
- name: A
connections:
# Create a connection with server entity B.
- target: B
# Name of the new connection: some_connection_to_B
id: some_connection_to_B
For more details, refer to Entity startup.
Service Locator
The Service Locator is a library containing the following functions:
ServiceLocatorConnect()
lets you find out the client IPC handle of the channel with a specified name.ServiceLocatorRegister()
lets you find out the server IPC handle of the channel with a specified name.ServiceLocatorGetRiid()
lets you find out the RIID (Runtime Interface ID) using an interface implementation name.
The values of the IPC handle and RIID are used during NkKosTransport initialization.
To use Service Locator, you need to include the sl_api.h
file in the entity code:
For more details, refer to Service Locator.
Page topecho example
The echo example demonstrates the use of IPC transport.
It shows how to use the main tools that let you implement interaction between entities.
About the echo example
The echo example describes a basic case of interaction between two entities:
- The
Client
entity sends a number (value
) to theServer
entity. - The
Server
entity modifies this number and sends the new number (result
) to theClient
entity. - The
Client
entity prints theresult
to the screen.
To set up this interaction between entities:
- Connect the
Client
andServer
entities by using the init description. - On the server, implement an interface with a single
Ping
method that has one input argument (the original number (value
)) and one output argument (the modified number (result
)).Description of the
Ping
method in the IDL language:Ping(in UInt32 value, out UInt32 result);
- Create static description files in the EDL, CDL and IDL languages. Use the NK compiler to generate files containing transport methods and types (proxy object, dispatchers, etc.).
- In the code of the
Client
entity, initialize all required objects (transport, proxy object, request structure, etc.) and call the interface method. - In the code of the
Server
entity, prepare all the required objects (transport, component dispatcher and entity dispatcher, etc.), accept the request from the client, process it and send a response.
The echo example consists of the following source files:
client/src/client.c
– implementation of theClient
entity.server/src/server.c
– implementation of theServer
entity.resources/Server.edl
,resources/Client.edl
,resources/Ping.cdl
,resources/Ping.idl
– static descriptions.init.yaml
– init description.
Implementation of the Client entity in the echo example
The code of the Client
entity uses the transport types and methods that will be generated during the solution build by the NK compiler based on the IDL description of the Ping
interface.
However, to obtain the descriptions of types and signatures of methods required for implementing the entity, you can use the NK compiler immediately after creating an EDL description of the entity, CDL descriptions of components and IDL descriptions of the interfaces used for interaction. As a result, the required types and signatures of methods will be declared in the generated *.h
files.
In the Client
entity implementation, the following is required:
- Get the client IPC handle of the connection (channel) by using the
ServiceLocatorConnect()
function.Input the name of the IPC
server_connection
predefined in the init.yaml file. - Initialize
NkKosTransport
by passing the obtained IPC handle to theNkKosTransport_Init()
function. - Obtain the ID of the required interface (
RIID
) by using theServiceLocatorGetRiid()
function. - Input the full name of the
Ping
interface implementation. It consists of the names of the component instance (Echo.ping
) and interface (ping
) that were previously defined in Server.edl and Ping.cdl. - Initialize the proxy object by passing the transport and interface ID to the
Ping_proxy_init()
function. - Prepare the request and response structures.
- Call the
Ping_Ping()
interface method by passing the proxy object and the pointers to the request and response.
client.c
/* Files required for transport initialization. */
/* Description of the server interface used by the client entity. */
/* Client entity entry point. */
int main(int argc, const char *argv[])
{
NkKosTransport transport;
struct echo_Ping_proxy proxy;
int i;
fprintf(stderr, "Hello I'm client\n");
/* Get client IPC handle of
* "server_connection". */
Handle handle = ServiceLocatorConnect("server_connection");
assert(handle != INVALID_HANDLE);
/* Initialize IPC transport for interaction with the server entity. */
NkKosTransport_Init(&transport, handle, NK_NULL, 0);
/* Get Runtime Interface ID (RIID) for interface echo.Ping.ping.
* Here ping is the name of the echo.Ping component instance,
* echo.Ping.ping is the name of the Ping interface implementation. */
nk_iid_t riid = ServiceLocatorGetRiid(handle, "echo.Ping.ping");
assert(riid != INVALID_RIID);
/* Initialize proxy object by specifying transport (&transport)
* and ID of the server interface (riid). Each method
* of the proxy object will be implemented by sending a request to the server. */
echo_Ping_proxy_init(&proxy, &transport.base, riid);
/* Request and response structures */
echo_Ping_Ping_req req;
echo_Ping_Ping_res res;
/* Test loop. */
req.value = EXAMPLE_VALUE_TO_SEND;
for (i = 0; i < 10; ++i)
{
/* Call Ping interface method.
* Server will be sent a request for calling Ping interface method
* ping_comp.ping_impl with the value argument. Calling thread is locked
* until a response is received from the server. */
if (echo_Ping_Ping(&proxy.base, &req, NULL, &res, NULL) == rcOk)
{
/* Print "result" value from response
* (result is the output argument of the Ping method). */
fprintf(stderr, "result = %d\n", (int) res.result);
/* Include received "result" value into "value" argument
* to resend to server in next iteration. */
req.value = res.result;
}
else
fprintf(stderr, "Failed to call echo.Ping.Ping()\n");
}
return EXIT_SUCCESS;
}
Implementation of the Server entity in the echo example
The code of the Server
entity uses the transport types and methods that will be generated during the solution build by the NK compiler based on the EDL description of the Server
entity.
However, to obtain the types and signatures of methods required for implementing the entity, you can use the NK compiler immediately after creating an EDL description of the entity, CDL descriptions of components and IDL descriptions of the interfaces used for interaction. As a result, the required types and signatures of methods will be declared in the generated *.h
files.
In the server
entity implementation, the following is required:
- Implement the
Ping()
method.The signature of the
Ping()
method implementation must exactly match the signature of thePing_Ping()
interface method that is declared in the Ping.idl.h file. - Get the server IPC handle of the connection (channel) by using the
ServiceLocatorRegister()
function.Input the name of the IPC
server_connection
predefined in the init.yaml file. - Initialize
NkKosTransport
by passing the obtained IPC handle to theNkKosTransport_Init()
function. - Prepare the request and response structures.
- Initialize the dispatch method (dispatcher) of the
Ping
component by using thePing_component_init()
function. - Initialize the dispatch method (dispatcher) of the
Server
entity by using theServer_entity_init()
function. - Receive a request by calling
nk_transport_recv()
. - Process the received request by calling the
Server_entity_dispatch()
dispatcher.The dispatcher calls the required implementation of the method based on the interface ID (
RIID
) received from the client. - Send the response to the
Client
entity by callingnk_transport_reply()
.
server.c
/* Files required for transport initialization. */
/* Server entity descriptions in EDL. */
/* Type of interface implementing object. */
typedef struct IPingImpl {
struct echo_Ping base; // base interface of object
int step; // Additional parameters
} IPingImpl;
/* Implementation of the Ping method. */
static nk_err_t Ping_impl(struct echo_Ping *self,
const echo_Ping_req *req,
const struct nk_arena* req_arena,
echo_Ping_res* res,
struct nk_arena* res_arena)
{
IPingImpl *impl = (IPingImpl *)self;
/* Increment value in the client request by
* one step and include into result argument that will be
* sent to the client in the server response. */
res->Ping.result = req->Ping.value + impl->step;
return NK_EOK;
}
/* IPing object constructor.
* step is the number by which the input value is increased. */
static struct echo_Ping *CreateIPingImpl(int step)
{
/* Table of IPing interface method implementations. */
static const struct echo_Ping_ops ops = {
.Ping = Ping_impl
};
/* Object implementing the interface. */
static struct IPingImpl impl = {
.base = {&ops}
};
impl.step = step;
return &impl.base;
}
/* Server entry point. */
int main(void)
{
NkKosTransport transport;
ServiceId iid;
/* Get server IPC handle of "server_connection". */
Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);
assert(handle != INVALID_HANDLE);
/* Initialize transport to client. */
NkKosTransport_Init(&transport, handle, NK_NULL, 0);
/* Prepare the structures of the request to the server entity: constant
* part and arena. Because none of the methods of the server entity has
* sequence type arguments, only constant parts are used
* request and response. Arenas are effectively unused. However, the valid
* arenas of the request and response must be passed to
* the server transport methods (nk_transport_recv, nk_transport_reply) and
* the dispatch method server_entity_dispatch. */
echo_Server_entity_req req;
char req_buffer[echo_Server_entity_req_arena_size];
struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer));
/* Prepare response structures: constant part and arena. */
echo_Server_entity_res res;
char res_buffer[echo_Server_entity_res_arena_size];
struct nk_arena res_arena = NK_ARENA_INITIALIZER(res_buffer, res_buffer + sizeof(res_buffer));
/* Initialize ping component dispatcher. 3 is the value of the "step",
* which is the number by which the input value is increased. */
echo_Ping_component component;
echo_Ping_component_init(&component, CreateIPingImpl(3));
/* Initialize server entity dispatcher. */
echo_Server_entity entity;
echo_Server_entity_init(&entity, &component);
fprintf(stderr, "Hello I'm server\n");
/* Dispatch loop implementation. */
do
{
/* Reset request/response buffers. */
nk_req_reset(&req);
nk_arena_reset(&req_arena);
nk_arena_reset(&res_arena);
/* Wait for request from client entity. */
if (nk_transport_recv(&transport.base, &req.base_, &req_arena) != NK_EOK) {
fprintf(stderr, "nk_transport_recv error\n");
} else {
/* Process received request by calling Ping_impl implementation
* of the requested Ping interface method. */
echo_Server_entity_dispatch(&entity, &req.base_, &req_arena, &res.base_, &res_arena);
}
/* Send response. */
if (nk_transport_reply(&transport.base, &res.base_, &res_arena) != NK_EOK) {
fprintf(stderr, "nk_transport_reply error\n");
}
}
while (true);
return EXIT_SUCCESS;
}
Description files in the echo example
Description of the Client entity
The Client
entity does not implement an interface, so all you need to do is declare the entity name in its EDL description:
Client.edl
/* Description of the Client entity. */
entity echo.Client
Server entity description
The description of the Server
entity must contain information stating that it implements the Ping
interface. Using static descriptions, you need to insert the implementation of the Ping
interface into the new component (for example, Ping
) and insert the instance of this component into the Server
entity.
The Server
entity contains an instance of the Ping
component:
Server.edl
/* Description of the Server entity. */
entity echo.Server
/* Server is a named instance of the echo.Ping component. */
components {
Server: echo.Ping
}
The Ping
component contains the implementation of the Ping
interface:
Ping.cdl
/* Description of the Ping component. */
component echo.Ping
/* ping is the Ping interface implementation. */
interfaces {
ping: echo.Ping
}
The Ping
package contains a declaration of the Ping
interface:
Ping.idl
/* Description of the Ping package. */
package echo.Ping
interface {
Ping(in UInt32 value, out UInt32 result);
}
Init description
To enable the Client
entity to call a method of the Server
entity, a connection (IPC channel) must be created between them.
To do so, in the init description indicate that the Client
and Server
entities must be started and connect them:
init.yaml
entities:
- name: echo.Client
connections:
- target: echo.Server
id: server_connection
- name: Server
The Server
entity is indicated as - target
, so it will perform the server entity role, meaning it will accept requests from the Client
entity and respond to them.
The created IPC channel is named server_connection
. You can obtain the handle of this channel by using the Service Locator.
Building and running the echo example
See the Building and running examples section.
The build scheme for the echo example looks as follows: