Suricatta daemon mode

Introduction

Suricatta is – like mongoose – a daemon mode of SWUpdate, hence the name suricatta (engl. meerkat) as it belongs to the mongoose family.

Suricatta regularly polls a remote server for updates, downloads, and installs them. Thereafter, it reboots the system and reports the update status to the server, based on an update state variable currently stored in bootloader’s environment ensuring persistent storage across reboots. Some U-Boot script logics or U-Boot’s bootcount feature may be utilized to alter this update state variable, e.g., by setting it to reflect failure in case booting the newly flashed root file system has failed and a switchback had to be performed.

Suricatta is designed to be extensible in terms of the servers supported as described in Section Supporting different Servers. Currently, support for the hawkBit server is implemented via the hawkBit Direct Device Integration API.

Running suricatta

After having configured and compiled SWUpdate with enabled suricatta support,

./swupdate --help

lists the mandatory and optional arguments to be provided to suricatta when using hawkBit as server. As an example,

./swupdate -l 5 -u '-t default -u http://10.0.0.2:8080 -i 25'

runs SWUpdate in suricatta daemon mode with log-level TRACE, polling a hawkBit instance at http://10.0.0.2:8080 with tenant default and device ID 25.

Note that on startup when having installed an update, suricatta tries to report the update status to its upstream server, e.g., hawkBit, prior to entering the main loop awaiting further updates. If this initial report fails, e.g., because of a not (yet) configured network or a currently unavailable hawkBit server, SWUpdate may exit with an according error code. This behavior allows to, for example, try several upstream servers sequentially. If suricatta should keep retrying until the update status is reported to its upstream server irrespective of the error conditions, this has to be realized externally in terms of restarting SWUpdate on exit.

After an update has been performed, an agent listening on the progress interface may execute post-update actions, e.g., a reboot, on receiving DONE. Additionally, a post-update command specified in the configuration file or given by the -p command line option can be executed.

Note that at least a restart of SWUpdate has to be performed as post-update action since only then suricatta tries to report the update status to its upstream server. Otherwise, succinct update actions announced by the upstream server are skipped with an according message until a restart of SWUpdate has happened in order to not install the same update again.

Supporting different Servers

Support for servers other than hawkBit can be realized by implementing the “interfaces” described in include/channel.h and include/suricatta/server.h. The former abstracts a particular connection to the server, e.g., HTTP-based in case of hawkBit, while the latter implements the logics to poll and install updates. See corelib/channel_curl.c/include/channel_curl.h and suricatta/server_hawkbit.{c,h} for an example implementation targeted towards hawkBit.

include/channel.h describes the functionality a channel has to implement:

typedef struct channel channel_t;
struct channel {
    ...
};

channel_t *channel_new(void);

which sets up and returns a channel_t struct with pointers to functions for opening, closing, fetching, and sending data over the channel.

include/suricatta/server.h describes the functionality a server has to implement:

server_op_res_t server_has_pending_action(int *action_id);
server_op_res_t server_install_update(void);
server_op_res_t server_send_target_data(void);
unsigned int server_get_polling_interval(void);
server_op_res_t server_start(const char *cfgfname, int argc, char *argv[]);
server_op_res_t server_stop(void);
server_op_res_t server_ipc(int fd);

The type server_op_res_t is defined in include/suricatta/suricatta.h. It represents the valid function return codes for a server’s implementation.

In addition to implementing the particular channel and server, the suricatta/Config.in file has to be adapted to include a new option so that the new implementation becomes selectable in SWUpdate’s configuration. In the simplest case, adding an option like the following one for hawkBit into the menu "Server" section is sufficient.

config SURICATTA_HAWKBIT
    bool "hawkBit support"
    depends on HAVE_LIBCURL
    depends on HAVE_JSON_C
    select JSON
    select CURL
    help
      Support for hawkBit server.
      https://projects.eclipse.org/projects/iot.hawkbit

Having included the new server implementation into the configuration, edit suricatta/Makefile to specify the implementation’s linkage into the SWUpdate binary, e.g., for the hawkBit example implementation, the following lines add server_hawkbit.o to the resulting SWUpdate binary if SURICATTA_HAWKBIT was selected while configuring SWUpdate.

ifneq ($(CONFIG_SURICATTA_HAWKBIT),)
lib-$(CONFIG_SURICATTA) += server_hawkbit.o
endif

Support for general purpose HTTP server

This is a very simple backend that uses standard HTTP response codes to signal if an update is available. There are closed source backends implementing this interface, but because the interface is very simple interface, this server type is also suitable for implementing an own backend server.

The API consists of a GET with Query parameters to inform the server about the installed version. The query string has the format:

http(s)://<base URL>?param1=val1&param2=value2...

As examples for parameters, the device can send its serial number, MAC address and the running version of the software. It is duty of the backend to interpret this - SWUpdate just takes them from the “identity” section of the configuration file and encodes the URL.

The server answers with the following return codes:

HTTP Code Text Description
302 Found A new software is available at URL in the Location header
400 Bad Request Some query parameters are missing or in wrong format
403 Forbidden Client certificate not valid
404 Not found No update is available for this device
503 Unavailable An update is available but server can’t handle another update process now.

Server’s answer can contain the following headers:

Header’s name Codes Description
Retry-after 503 Contains a number which tells the device how long to wait until ask the next time for updates. (Seconds)
Content-MD5 302 Contains the checksum of the update file which is available under the url of location header
Location 302 URL where the update file can be downloaded.

The device can send logging data to the server. Any information is transmitted in a HTTP PUT request with the data as plain string in the message body. The Content-Type Header need to be set to text/plain.

The URL for the logging can be set as separate URL in the configuration file or via –logurl command line parameter:

The device sends data in a CSV format (Comma Separated Values). The format is:

value1,value2,...

The format can be specified in the configuration file. A format For each event can be set. The supported events are:

Event Description
check dummy. It could send an event each time the server is polled.
started A new software is found and SWUpdate starts to install it
success A new software was successfully installed
fail Failure by installing the new software

The general server has an own section inside the configuration file. As example:

gservice =
{
        url             = ....;
        logurl          = ;
        logevent : (
                {event = "check"; format="#2,date,fw,hw,sp"},
                {event = "started"; format="#12,date,fw,hw,sp"},
                {event = "success"; format="#13,date,fw,hw,sp"},
                {event = "fail"; format="#14,date,fw,hw,sp"}
        );
}

date is a special field and it is interpreted as localtime in RFC 2822 format. Each Comma Separated field is looked up inside the identify section in the configuration file, and if a match is found the substitution occurs. In case of no match, the field is sent as it is. For example, if the identify section has the following values:

identify : (
        { name = "sp"; value = "333"; },
        { name = "hw"; value = "ipse"; },
        { name = "fw"; value = "1.0"; }
);

with the events set as above, the formatted text in case of “success” will be:

Formatted log: #13,Mon, 17 Sep 2018 10:55:18 CEST,1.0,ipse,333