Skip to content
Eduard Grasa edited this page Aug 29, 2018 · 37 revisions

Table of contents

  1. Introduction
  2. API Walkthrough
    • 2.1 Server-side operations
    • 2.2 Client-side operations
  3. API Specification
    • 3.1 rina_open
    • 3.2 rina_register
    • 3.3 rina_unregister
    • 3.4 rina_register_wait
    • 3.5 rina_flow_accept
    • 3.6 rina_flow_respond
    • 3.7 rina_flow_alloc
    • 3.8 rina_flow_alloc_wait
    • 3.9 rina_flow_spec_default
  4. Example applications

1. Introduction

Since existing network applications are written using the socket API, it comes natural to design a C RINA API which closely resembles the socket API, provided that the differences in the naming and addressing scheme and the QoS support are taken into account. The socket API is currently defined by the POSIX.1-2008 standard; for this reason the API presented in this section will be referred to as a POSIX-like API for RINA. The advantages of a C POSIX-like API include the following:

  • POSIX standards are universally accepted and widely known, so that it would be easy for developers to catch up with the RINA API and write applications.
  • It would be easy to port existing network applications to RINA, starting from the definition of a simple mapping between socket API calls and RINA API calls.
  • File descriptors are used as universal handlers to interact with the API; this makes it possible to reuse standard system calls (e.g. read, write, close, ...), and synchronisation mechanism (e.g. select, poll, ...).
  • The C language is widely used for mission-critical network applications, and a C API can also be used directly by C++ programs.

This C POSIX-like API was originally designed for the rlite RINA implementation.

2. API Walkthrough

A convenient way to introduce the API is to show how a simple application would use the client-side and server-side API calls. This also eases the comparison with sockets, where a similar walkthrough is often presented. Note that in this context the term client simply refers to the initiator of the flow allocation procedure, while the term server refers to the other peer. The discussion here, in other words, does not imply that the client/server paradigm must be applied; the walkthrough is more general, being valid also for other distributed application paradigms (e.g. peer-to-peer).

dd

The workflow presented in this subsection, depicted in figure 1, refers to the case of blocking operation, that is the API calls may block waiting for asynchronous events; moreover, for the sake of exposition, we assume that the operations do not fail. Non-blocking operations and errors are however covered by the API specification (section 2.2) and the examples (section 2.4).

2.1 Server-side operations

The first operation needed by the server, (1) in figure 1, is rina_open, which takes no arguments and returns a listening file descriptor (an integer, as usual) to be used for subsequent server-side calls. This file descriptor is the handler for an instance of a RINA control device which acts as a receiver for incoming flow allocation requests.

At (2), the server calls rina_register to register a name with the RINA control device, specifying the associated listening file descriptor (lfd), the name of the DIF to register to (dif) and the name to be registered (appl). The DIF argument is optional and advisory: the API implementation may choose to ignore it, and use some namespace management strategy to decide into which DIF the name should be registered.

After a successful registration, the server can receive flow allocation requests, by calling rina_flow_accept on the listening file descriptor (3). Since the listening file descriptor was not put in non-blocking mode, this call will block until a flow request arrives. When this happens, the function returns a new file descriptor (cfd), the name of the remote application (src) and the QoS granted to the flow. The returned file descriptor is an handler for an instance of a RINA I/O device, to be used for data I/O.

At this point (4), the flow allocation is complete, and the server can exchange SDUs with the client, using the write and read blocking calls or working in non-blocking mode (possibly mutliplexing with other I/O devices, sockets, etc.) by means of poll or select. This I/O phase is completely analogous to the I/O exchange that happens with TCP or UDP sockets, only the QoS may be different.

Once the I/O session ends, the server can close the flow, triggering flow deallocation, using the close system call (5). The server can then decide whether to terminate or accept another flow allocation request (3).

2.2 Client-side operations

Client operation is straightforward; the client calls rina_flow_alloc (1) to issue a flow allocation request, passing as arguments the name of the DIF that is asked to support the flow (dif), the name of the client (src, i.e. the source application name), the name of the destination application (dst, i.e. the server name) and the required QoS parameters for the flow (qos). The call will block until the flow allocation completes successfully, returning an file descriptor (fd) to be used for data I/O.

At this point the client can exchange SDUs with the server (2), using the I/O file descriptor either in blocking or not blocking mode, similarly to what is possible to do with sockets. When the I/O session terminates, the client can deallocate the flow with the close system call.

3. API Specification

The API is defined at the following file: https://github.com/IRATI/stack/blob/master/librina/wrap/rina-api/include/rina/api.h

Some general considerations:

  • Names (e.g. for applications and DIFs) are specified using C strings. Application names are composed of four components: process name, process instance, entity name and entity instance. Only process name is mandatory. The API mandates a standard separator to allow application developers to provide these 4 pieces of information using a single string. This separator is the '|' character, which cannot be used as part of the process name, instance, entity name or instance. Some examples of valid app names:

    • traffic.generator
    • traffic.generator|23
    • traffic.generator||perf
    • traffic.generator|23|perf
    • traffic.generator|23|perf|12
  • The API functions typically return 0 or a positive value on success. On error, -1 is returned with the errno variable set accordingly to the specific error.

3.1 rina_open

 int rina_open(void)

This function opens a RINA control device that can be used to register/unregister names, and manage incoming flow allocation requests. On success, it returns a file descriptor that can be later passed to rina_register(), rina_unregister(), rina_flow_accept(), and rina_flow_respond().

On error -1 is returned with errno set properly. Applications typically call this function as a first step to implement server-side functionalities.

3.2 rina_register

int rina_register(int fd,
                  const char *dif,
                  const char *appl,
                  int flags)

This function registers the application name appl to a DIF in the system. After a successful registration, flow allocation requests can be received on fd by means of rina_flow_accept(). If dif is not NULL, the system may register the application to dif. However, the dif argument is only advisory and the implementation is free to ignore it. If dif is NULL, the system autonomously decide to which DIF appl will be registered to.

If RINA_F_NOWAIT is not specified in flags, this function will block the caller until the operation completes, and 0 is returned on success.

If RINA_F_NOWAIT is specified in flags, the function returns a file descriptor (different from fd) which can be used to wait for the operation to complete (e.g. using POLLIN with poll() or select()). In this case the operation can be completed by a subsequent call to rina_register_wait().

On error -1 is returned, with the errno code properly set.

3.3 rina_unregister

int rina_unregister(int fd,
                    const char *dif,
                    const char *appl,
                    int flags)

This function unregisters the application name appl from the DIF where it was registered to. The dif argument must match the one passed to rina_register(). After a successful unregistration, flow allocation requests can no longer be received on fd. The meaning of the RINA_F_NOWAIT flag is the same as in rina_register(), allowing non-blocking unregistration, to be later completed by calling rina_register_wait().

Returns 0 on success, -1 on error, with the errno code properly set.

3.4 rina_register_wait

int rina_register_wait(int fd,
                       int wfd)

This function is called to wait for the completion of a (un)registration procedure previously initiated with a call to rina_register() or rina_unregister() on fd which had the RINA_F_NOWAIT flag set. The wfd file descriptor must match the one that was returned by rina_[un]register().

It returns 0 on success, -1 error, with the errno code properly set.

3.5 rina_flow_accept

int rina_flow_accept(int fd,
                     char **remote_appl,
                     struct rina_flow_spec *spec,
                     unsigned int flags)

This function is called to accept an incoming flow request arrived on fd. If flags does not contain RINA_F_NORESP, it also sends a positive response to the requesting application; otherwise, the response (positive or negative) can be sent by a subsequent call to the rina_flow_respond(). On success, the char* pointed by remote_appl, if not NULL, is assigned the name of the requesting application. The memory for the requestor name is allocated by the callee and must be freed by the caller. Moreover, if spec is not NULL, the referenced data structure is filled with the QoS specification specified by the requesting application.

If flags does not contain RINA_F_NORESP, on success this function returns a file descriptor that can be subsequently used with standard I/O system calls (write(), read(), select(), ...) to exchange SDUs on the flow and synchronize. If flags does contain RINA_F_NORESP, on success a positive number is returned as an handle to be passed to a subsequent call to rina_flow_respond().

This code

cfd = rina_flow_accept(fd, &x, flags & ˜RINA_F_NORESP)

is functionally equivalent to this code:

h = rina_flow_accept(sfd, &x, flags | RINA_F_NORESP);
cfd = rina_flow_respond(sfd, h, 0 /* positive response */);

On error -1 is returned, with the errno code properly set.

3.6 rina_flow_accept

int rina_flow_respond(int fd, int handle, int response)

This function is called to emit a verdict on the flow allocation request identified by handle, that was previously received on fd by calling rina_flow_accept() with the RINA_F_NORESP flag set. A zero response indicates a positive response, which completes the flow allocation procedure. A non-zero response indicates that the flow allocation request is denied. In both cases response is sent to the requesting application to inform it about the verdict. When the response is positive, on success this function returns a file descriptor that can be subsequently used with standard I/O system calls (write(), read(), select(), ...) to exchange SDUs on the flow and synchronize.

When the response is negative, 0 is returned on success. In any case, -1 is returned on error, with the errno code properly set.

3.7 rina_flow_alloc

int rina_flow_alloc(const char *dif,
                    const char *local_appl,
                    const char *remote_appl,
                    const struct rina_flow_spec *flowspec,
                    unsigned int flags);

This function is called to issue a flow allocation request towards the destination application called remote_appl, using local_appl as a source application name. If flowspec is not NULL, it specifies the QoS parameters to be used for the flow, if the flow allocation request is successful. If flowspec is NULL, an implementation-specific default QoS will be assumed (which typically corresponds to a best-effort QoS). If dif is not NULL the system may look for remote_appl in a DIF called dif. However, the dif argument is only advisory and the system is free to ignore it and take an autonomous decision.

If flags specifies RINA_F_NOWAIT, a call to this function does not wait until the completion of the flow allocation procedure; on success, it just returns a control file descriptor that can be subsequently fed to rina_flow_alloc_wait() to wait for completion and obtain the flow I/O file descriptor. Moreover, the control file descriptor can be used with poll(), select() and similar.

If flags does not specify RINA_F_NOWAIT, a call to this function waits until the flow allocation procedure is complete. On success, it returns a file descriptor that can be subsequently used with standard I/O system calls (write(), read(), select(), ...) to exchange SDUs on the flow and synchronize.

In any case, -1 is returned on error, with the errno code properly set.

3.8 rina_flow_alloc_wait

int rina_flow_alloc_wait(int wfd)

This function waits for the completion of a flow allocation procedure previosuly initiated with a call to rina_flow_alloc() with the RINA_F_NOWAIT flag set. The wfd file descriptor must match the one returned by rina_flow_alloc(). On success, it returns a file descriptor that can be subsequently used with standard I/O system calls (write(), read(), select(), ...) to exchange SDUs on the flow and synchronize.

On error -1 is returned, with the errno code properly set.

3.9 rina_flow_spec_default

struct rina_flow_spec {
    uint64_t max_sdu\_gap; /* in SDUs */
    uint64_t avg_bandwidth; /* in bits per second */
    uint32_t max_delay; /* in microseconds */
    uint16_t max_loss; /* percentage */
    uint32_t max_jitter; /* in microseconds */
    uint8_t in_order\_delivery; /* boolean */
    uint8_t msg_boundaries; /* boolean */
};

void rina_flow_spec_default(struct rina_flow_spec *spec)

This function fills in the provided spec with an implementation-specific default QoS, which should correspond to a best-effort QoS. The fields of the rina_flow_spec data structure specify the QoS of a RINA flow as follows: * max_sdu_gap specifies the maximum number of consecutive SDUs that can be lost without violating the QoS. Specifying -1 means that there is no maximum, and so the flow is unreliable; 0 means that no SDU can be lost and so the flow is reliable.

  • avg_bandwidth specifies the maximum bandwidth that should be guaranteed on this flow, in bits per second. * max_delay specifies the maximum one-way latency that can be experienced by SDUs of this flow without violating the QoS, expressed in microseconds.
  • max_loss specifies the maximum percentage of SDUs that can be lost on this flow without violating the QoS.
  • max_jitter specifies the maximum jitter that can be experienced by SDUs on this flow without violating the QoS.
  • in_order_delivery, if true requires that the SDUs are delivered in order on this flow (no SDU reordering is allowed).
  • msg_boundaries: if true, the flow is stream-oriented, like TCP; a stream-oriented flow does not preserve message boundaries, and therefore write() and read() system calls are used to exchange a stream of bytes, and the granularity of the exchange is the byte. If false, the flow is message-oriented, like UDP, and does preserve message boundaries. The I/O system calls are used to exchanges messages (SDUs), and the granularity of the exchange is the message.

4. Example applications

Two example applications written to this API are available at the rina-tools folder: https://github.com/IRATI/stack/tree/master/rina-tools/src/rlite

Clone this wiki locally