tinydtls  0.8.1
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups
DTLS Usage

DTLS Server Example

This section shows how to use the DTLS library functions to setup a simple secure UDP echo server. The application is responsible for the entire network communication and thus will look like a usual UDP server with socket creation and binding and a typical select-loop as shown below. The minimum configuration required for DTLS is the creation of the dtls_context_t using dtls_new_context(), and a callback for sending data. Received packets are read by the application and passed to dtls_handle_message() as shown in The Read Callback. For any useful communication to happen, read and write call backs and a key management function should be registered as well.

dtls_context_t *the_context = NULL;
int fd, result;
static dtls_handler_t cb = {
.write = send_to_peer,
.read = read_from_peer,
.event = NULL,
.get_psk_key = get_psk_key
};
fd = socket(...);
if (fd < 0 || bind(fd, ...) < 0)
exit(-1);
the_context = dtls_new_context(&fd);
dtls_set_handler(the_context, &cb);
while (1) {
...initialize fd_set rfds and timeout ...
result = select(fd+1, &rfds, NULL, 0, NULL);
if (FD_ISSET(fd, &rfds))
dtls_handle_read(the_context);
}
dtls_free_context(the_context);

The Read Callback

The DTLS library expects received raw data to be passed to dtls_handle_message(). The application is responsible for filling a session_t structure with the address data of the remote peer as illustrated by the following example:

int dtls_handle_read(struct dtls_context_t *ctx) {
int *fd;
session_t session;
static uint8 buf[DTLS_MAX_BUF];
int len;
fd = dtls_get_app_data(ctx);
assert(fd);
session.size = sizeof(session.addr);
len = recvfrom(*fd, buf, sizeof(buf), 0, &session.addr.sa, &session.size);
return len < 0 ? len : dtls_handle_message(ctx, &session, buf, len);
}

Once a new DTLS session was established and DTLS ApplicationData has been received, the DTLS server invokes the read callback with the MAC-verified cleartext data as its argument. A read callback for a simple echo server could look like this:

int read_from_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) {
return dtls_write(ctx, session, data, len);
}

The Send Callback

The callback function send_to_peer() is called whenever data must be sent over the network. Here, the sendto() system call is used to transmit data within the given session. The socket descriptor required by sendto() has been registered as application data when the DTLS context was created with dtls_new_context(). Note that it is on the application to buffer the data when it cannot be sent at the time this callback is invoked. The following example thus is incomplete as it would have to deal with EAGAIN somehow.

int send_to_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) {
int fd = *(int *)dtls_get_app_data(ctx);
return sendto(fd, data, len, MSG_DONTWAIT, &session->addr.sa, session->size);
}

The Key Storage

When a new DTLS session is created, the library must ask the application for keying material. To do so, it invokes the registered call-back function get_psk_info() with the current context and session information as parameter. When the call-back function is invoked with the parameter type set to DTLS_PSK_IDENTITY, the result parameter result must be filled with the psk_identity_hint in case of a server, or the actual psk_identity in case of a client. When type is DTLS_PSK_KEY, the result parameter must be filled with a key for the given identity id. The function must return the number of bytes written to result which must not exceed result_length. In case of an error, the function must return a negative value that corresponds to a valid error code defined in alert.h.

int get_psk_info(struct dtls_context_t *ctx UNUSED_PARAM,
const session_t *session UNUSED_PARAM,
const unsigned char *id, size_t id_len,
unsigned char *result, size_t result_length) {
switch (type) {
if (result_length < psk_id_length) {
dtls_warn("cannot set psk_identity -- buffer too small\n");
}
memcpy(result, psk_id, psk_id_length);
return psk_id_length;
if (id_len != psk_id_length || memcmp(psk_id, id, id_len) != 0) {
dtls_warn("PSK for unknown id requested, exiting\n");
} else if (result_length < psk_key_length) {
dtls_warn("cannot set psk -- buffer too small\n");
}
memcpy(result, psk_key, psk_key_length);
return psk_key_length;
default:
dtls_warn("unsupported request type: %d\n", type);
}
}

The Event Notifier

Applications that want to be notified whenever the status of a DTLS session has changed can register an event handling function with the field event in the dtls_handler_t structure (see DTLS Server Example). The call-back function is called for alert messages and internal state changes. For alert messages, the argument level will be set to a value greater than zero, and code will indicate the notification code. For internal events, level is 0, and code a value greater than 255.

Internal events are DTLS_EVENT_CONNECTED, DTLS_EVENT_CONNECT, and DTLS_EVENT_RENEGOTIATE.

int handle_event(struct dtls_context_t *ctx, session_t *session,
dtls_alert_level_t level, unsigned short code) {
... do something with event ...
return 0;
}

DTLS Client Example

A DTLS client is constructed like a server but needs to actively setup a new session by calling dtls_connect() at some point. As this function usually returns before the new DTLS channel is established, the application must register an event handler and wait for DTLS_EVENT_CONNECT before it can send data over the DTLS channel.