DTLS sock API

Sock submodule for DTLS. More...

Detailed Description

Sock submodule for DTLS.

DTLS sock acts as a wrapper for the underlying DTLS module to provide encryption for applications using the UDP sock API.

How To Use

Summary

Makefile Includes

First, we need to include a module that implements this API in our applications Makefile. For example the module that implements this API for tinydtls is called `tinydtls_sock_dtls'.

The corresponding pkg providing the DTLS implementation will be automatically included so there is no need to use USEPKG to add the pkg manually.

Each DTLS implementation may have its own configuration options and caveat. This can be found at DTLS.

Adding credentials

Before using this API, either as a server or a client, we first need to add the credentials to be used for the encryption using credman. Note that credman does not copy the credentials given into the system, it only has information about the credentials and where it is located at. So it is your responsibility to make sure that the credential is valid during the lifetime of your application.

#include <stdio.h>
#include "net/credman.h"
#define SOCK_DTLS_SERVER_TAG (10)
#define SOCK_DTLS_CLIENT_TAG (20)
static char *psk_key = "secretPSK";
static char *psk_id = "secretID";
static const unsigned char server_ecdsa_priv_key[] = {...};
static const unsigned char server_ecdsa_pub_key_x[] = {...};
static const unsigned char server_ecdsa_pub_key_y[] = {...};
static const unsigned char client_pubkey_x[] = {...};
static const unsigned char client_pubkey_y[] = {...};
static ecdsa_public_key_t other_pubkeys[] = {
{ .x = client_pubkey_x, .y = client_pubkey_y },
};
int main(void)
{
credman_credential_t psk_credential = {
.tag = SOCK_DTLS_SERVER_TAG,
.params = {
.psk = {
.key = { .s = psk_key, .len = sizeof(psk_key), },
.id = { .s = psk_id, .len = sizeof(psk_id), },
},
},
};
if (credman_add(&psk_credential) < 0) {
puts("Error cannot add credential");
}
credman_credential_t ecc_credential = {
.tag = SOCK_DTLS_SERVER_TAG,
.params = {
.ecdsa = {
.private_key = server_ecdsa_priv_key,
.public_key = {
.x = server_ecdsa_pub_key_x,
.y = server_ecdsa_pub_key_y,
},
.client_keys = other_pubkeys,
.client_keys_size = sizeof(other_pubkeys) / sizeof(other_pubkeys[0]),
},
},
};
if (credman_add(&ecc_credential) < 0) {
puts("Error cannot add credential");
}
// start server/client
// [...]
}

Above we see an example of how to register a PSK and an ECC credential.

First, we need to include the header file for the API.

#include "net/credman.h"
int main(void)
{
credman_credential_t psk_credential = {
.tag = SOCK_DTLS_SERVER_TAG,
.params = {
.psk = {
.key = { .s = psk_key, .len = sizeof(psk_key), },
.id = { .s = psk_id, .len = sizeof(psk_id), },
},
},
};
[...]
}

We tell credman which credential to add by filling in the credentials information in a struct credman_credential_t. For PSK credentials, we use enum CREDMAN_TYPE_PSK for the type.

Next, we must assign a tag for the credential. Tags are unsigned integer value that is used to identify which DTLS sock has access to which credential. Each DTLS sock will also be assigned a tag. As a result, a sock can only use credentials that have the same tag as its assigned tag.

if (credman_add(&psk_credential) < 0) {
puts("Error cannot add credential");
}

After credential information is filled, we can add it to the credential pool using credman_add().

For adding credentials of other types, you can follow the steps above except credman_credential_t::type and credman_credential_t::params depend on the type of credential used.

Server Operation

After credentials are added, we can start the server.

#include <stdio.h>
#include "net/sock/dtls.h"
#define SOCK_DTLS_SERVER_TAG (10)
int main(void)
{
// Add credentials
// [...]
// initialize server
sock_udp_t udp_sock;
local.port = 20220;
if (sock_udp_create(&udp_sock, &local, NULL, 0) < 0) {
puts("Error creating UDP sock");
return -1;
}
sock_dtls_t dtls_sock;
if (sock_dtls_create(&dtls_sock, &udp_sock,
SOCK_DTLS_SERVER_TAG,
puts("Error creating DTLS sock");
return -1;
}
while (1) {
int res;
char buf[128];
res = sock_dtls_recv(&dtls_sock, &session, buf, sizeof(buf),
if (res > 0) {
printf("Received %d bytes\n", res);
if (sock_dtls_send(&dtls_sock, &session, buf, res) < 0) {
puts("Error sending reply");
}
}
}
return 0;
}

This is an example of a DTLS echo server.

DTLS sock uses an initialized UDP sock to send and receive encrypted packets. Therefore, the listening port for the server also needs to be set here.

sock_udp_t udp_sock;
local.port = 20220;
if (sock_udp_create(&udp_sock, &local, NULL, 0) < 0) {
puts("Error creating UDP sock");
return -1;
}

Using the initialized UDP sock, we can then create our DTLS sock. We use SOCK_DTLS_SERVER_TAG, which is defined as 10 in our application code beforehand, as our tag. Using SOCK_DTLS_1_2 and SOCK_DTLS_SERVER, we set our DTLS endpoint to use DTLS version 1.2 and act as a DTLS server.

Note that some DTLS implementation do not support earlier versions of DTLS. In this case, sock_dtls_create() will return an error. A list of supported DTLS version for each DTLS implementation can be found at this page. In case of error, the program is stopped.

#define SOCK_DTLS_SERVER_TAG (10)
[...]
sock_dtls_t dtls_sock;
if (sock_dtls_create(&dtls_sock, &udp_sock,
SOCK_DTLS_SERVER_TAG,
puts("Error creating DTLS sock");
return -1;
}

Now we can listen to incoming packets using sock_dtls_recv(). The application waits indefinitely for new packets. If we want to timeout this wait period we could alternatively set the timeout parameter of the function to a value != SOCK_NO_TIMEOUT. If an error occurs we just ignore it and continue looping. We can reply to an incoming message using its session.

while (1) {
int res;
char buf[128];
res = sock_dtls_recv(&dtls_sock, &session, buf, sizeof(buf),
if (res > 0) {
printf("Received %d bytes -- echo!\n", res);
if (sock_dtls_send(&dtls_sock, &session, buf, res) < 0) {
puts("Error sending reply");
}
}
}
return 0;

Client Operation

#include "net/sock/udp.h"
#include "net/sock/dtls.h"
#include "net/ipv6/addr.h"
#include "net/credman.h"
#define SOCK_DTLS_CLIENT_TAG (20)
#ifndef SERVER_ADDR
#define SERVER_ADDR "fe80::aa:bb:cc:dd" // replace this with the server address
#endif
int main(void)
{
// Add credentials
// [...]
// initialize client
char rcv[128];
sock_udp_t udp_sock;
local.port = 12345;
sock_udp_ep_t remote;
remote.port = DTLS_DEFAULT_PORT;
remote.netif = gnrc_netif_iter(NULL)->pid; // only if GNRC_NETIF_NUMOF == 1
sock_dtls_t dtls_sock;
if (!ipv6_addr_from_str((ipv6_addr_t *)remote.addr.ipv6, SERVER_ADDR)) {
puts("Error parsing destination address");
return -1;
}
if (sock_udp_create(&udp_sock, &local, NULL, 0) < 0) {
puts("Error creating UDP sock");
return -1;
}
if (sock_dtls_create(&dtls_sock, &udp_sock,
SOCK_DTLS_CLIENT_TAG,
puts("Error creating DTLS sock");
sock_udp_close(&udp_sock);
return -1;
}
if (sock_dtls_session_create(&dtls_sock, &remote, &session) < 0) {
puts("Error creating session");
sock_dtls_close(&dtls_sock);
sock_udp_close(&udp_sock);
return -1;
}
const char data[] = "HELLO";
int res = sock_dtls_send(&dtls_sock, &session, data, sizeof(data));
if (res >= 0) {
printf("Sent %d bytes\n", res);
res = sock_dtls_recv(&dtls_sock, &session, rcv, sizeof(rcv), SOCK_NO_TIMEOUT);
if (res > 0) {
printf("Received %d bytes\n", res);
}
}
else {
puts("Error sending data");
}
sock_dtls_session_destroy(&dtls_sock, &session);
sock_dtls_close(&dtls_sock);
sock_udp_close(&udp_sock);
return 0;
}

This is an example of a DTLS echo client.

Like the server, we must first create the UDP sock.

sock_udp_t udp_sock;
local.port = 12345;
sock_udp_create(&udp_sock, &local, NULL, 0);

After that, we set the address of the remote endpoint and its listening port, which is DTLS_DEFAULT_PORT (20220).

remote.port = DTLS_DEFAULT_PORT;
remote.netif = gnrc_netif_iter(NULL)->pid; // only if GNRC_NETIF_NUMOF == 1
if (!ipv6_addr_from_str((ipv6_addr_t *)remote.addr.ipv6, SERVER_ADDR)) {
puts("Error parsing destination address");
return -1;
}

After the UDP sock is created, we can proceed with creating the DTLS sock. Before sending the packet, we must first create a session with the remote endpoint using sock_dtls_session_create(). If the handshake is successful and the session is created, we send packets to it using sock_dtls_send(). If the packet is successfully sent, we listen for the response with sock_dtls_recv().

sock_dtls_create() and sock_dtls_close() only manages the DTLS layer. That means we still have to clean up the created UDP sock from before by calling sock_udp_close() on our UDP sock in case of error or we reached the end of the application.

char rcv[128];
sock_dtls_t dtls_sock;
[...]
if (sock_dtls_create(&dtls_sock, &udp_sock,
SOCK_DTLS_CLIENT_TAG,
puts("Error creating DTLS sock");
sock_udp_close(&udp_sock);
return -1;
}
if (sock_dtls_session_create(&dtls_sock, &remote, &session) < 0) {
puts("Error creating session");
sock_dtls_close(&dtls_sock);
sock_udp_close(&udp_sock);
return -1;
}
const char data[] = "HELLO";
int res = sock_dtls_send(&dtls_sock, &session, data, sizeof(data));
if (res >= 0) {
printf("Sent %d bytes: %*.s\n", res, res, data);
res = sock_dtls_recv(&dtls_sock, &session, rcv, sizeof(rcv), SOCK_NO_TIMEOUT);
if (res > 0) {
printf("Received %d bytes: %*.s\n", res, res, rcv);
}
}
else {
puts("Error sending data");
}
sock_dtls_session_destroy(&dtls_sock, &session);
sock_dtls_close(&dtls_sock);
sock_udp_close(&udp_sock);
return 0;

Files

file  dtls.h
 DTLS sock definitions.
 

Typedefs

typedef struct sock_dtls sock_dtls_t
 Type for a DTLS sock object. More...
 
typedef struct sock_dtls_session sock_dtls_session_t
 Information about a created session.
 

Functions

void sock_dtls_init (void)
 Called exactly once during auto_init. More...
 
int sock_dtls_create (sock_dtls_t *sock, sock_udp_t *udp_sock, credman_tag_t tag, unsigned version, unsigned role)
 Creates a new DTLS sock object. More...
 
int sock_dtls_session_create (sock_dtls_t *sock, const sock_udp_ep_t *ep, sock_dtls_session_t *remote)
 Creates a new DTLS session. More...
 
void sock_dtls_session_destroy (sock_dtls_t *sock, sock_dtls_session_t *remote)
 Destroys an existing DTLS session. More...
 
ssize_t sock_dtls_recv (sock_dtls_t *sock, sock_dtls_session_t *remote, void *data, size_t maxlen, uint32_t timeout)
 Decrypts and reads a message from a remote peer. More...
 
ssize_t sock_dtls_send (sock_dtls_t *sock, sock_dtls_session_t *remote, const void *data, size_t len)
 Encrypts and sends a message to a remote peer. More...
 
void sock_dtls_close (sock_dtls_t *sock)
 Closes a DTLS sock. More...
 
enum  { SOCK_DTLS_1_0 = 1, SOCK_DTLS_1_2 = 2, SOCK_DTLS_1_3 = 3 }
 DTLS version number. More...
 
enum  { SOCK_DTLS_CLIENT = 1, SOCK_DTLS_SERVER = 2 }
 DTLS role. More...
 

Typedef Documentation

◆ sock_dtls_t

typedef struct sock_dtls sock_dtls_t

Type for a DTLS sock object.

Note
API implementors: struct sock_dtls needs to be defined by an implementation-specific sock_dtls_types.h.

Definition at line 501 of file dtls.h.

Enumeration Type Documentation

◆ anonymous enum

anonymous enum

DTLS version number.

Enumerator
SOCK_DTLS_1_0 

DTLS version 1.0.

SOCK_DTLS_1_2 

DTLS version 1.2.

SOCK_DTLS_1_3 

DTLS version 1.3.

Definition at line 477 of file dtls.h.

◆ anonymous enum

anonymous enum

DTLS role.

Enumerator
SOCK_DTLS_CLIENT 

Endpoint client role.

SOCK_DTLS_SERVER 

Endpoint server role.

Definition at line 489 of file dtls.h.

Function Documentation

◆ sock_dtls_close()

void sock_dtls_close ( sock_dtls_t sock)

Closes a DTLS sock.

Releases any memory allocated by sock_dtls_create(). This function does NOT close the UDP sock used by the DTLS sock. After the call to this function, user will have to call sock_udp_close() to close the UDP sock.

Precondition
(sock != NULL)
Parameters
sockDTLS sock to close

◆ sock_dtls_create()

int sock_dtls_create ( sock_dtls_t sock,
sock_udp_t udp_sock,
credman_tag_t  tag,
unsigned  version,
unsigned  role 
)

Creates a new DTLS sock object.

Takes an initialized UDP sock and uses it for the transport. Memory allocation functions required by the underlying DTLS stack can be called in this function.

See also
(D)TLS Credential Manager.
Parameters
[out]sockThe resulting DTLS sock object
[in]udp_sockExisting UDP sock initialized with to be used underneath.
[in]tagCredential tag of sock. The sock will only use credentials with the same tag given here.
[in]versionDTLS version to use.
[in]roleRole of the endpoint.
Returns
0 on success.
-1 on error

◆ sock_dtls_init()

void sock_dtls_init ( void  )

Called exactly once during auto_init.

Calls the initialization function required by the DTLS stack used.

◆ sock_dtls_recv()

ssize_t sock_dtls_recv ( sock_dtls_t sock,
sock_dtls_session_t remote,
void *  data,
size_t  maxlen,
uint32_t  timeout 
)

Decrypts and reads a message from a remote peer.

Parameters
[in]sockDTLS sock to use.
[out]remoteRemote DTLS session of the received data. Cannot be NULL.
[out]dataPointer where the received data should be stored.
[in]maxlenMaximum space available at data.
[in]timeoutReceive timeout in microseconds. If 0 and no data is available, the function returns immediately. May be SOCK_NO_TIMEOUT to wait until data is available.
Note
Function may block if data is not available and timeout != 0
Returns
The number of bytes received on success
-EADDRNOTAVAIL, if the local endpoint of sock is not set.
-EAGAIN, if timeout is 0 and no data is available.
-EINVAL, if remote is invalid or sock is not properly initialized (or closed while sock_dtls_recv() blocks).
-ENOBUFS, if buffer space is not large enough to store received data.
-ENOMEM, if no memory was available to receive data.
-ETIMEDOUT, if timeout expired.

◆ sock_dtls_send()

ssize_t sock_dtls_send ( sock_dtls_t sock,
sock_dtls_session_t remote,
const void *  data,
size_t  len 
)

Encrypts and sends a message to a remote peer.

Parameters
[in]sockDTLS sock to use
[in]remoteDTLS session to use. A new session will be created if no session exist between client and server.
[in]dataPointer where the data to be send are stored
[in]lenLength of data to be send
Note
Function may block until a session is created if there is no existing session with remote.
Returns
The number of bytes sent on success
-EADDRINUSE, if sock_dtls_t::udp_sock has no local end-point.
-EAFNOSUPPORT, if remote->ep != NULL and sock_dtls_session_t::ep::family of remote is != AF_UNSPEC and not supported.
-EHOSTUNREACH, if sock_dtls_session_t::ep of remote is not reachable.
-EINVAL, if sock_udp_ep_t::addr of remote->ep is an invalid address.
-EINVAL, if sock_udp_ep_t::port of remote->ep is 0.
-ENOMEM, if no memory was available to send data.

◆ sock_dtls_session_create()

int sock_dtls_session_create ( sock_dtls_t sock,
const sock_udp_ep_t ep,
sock_dtls_session_t remote 
)

Creates a new DTLS session.

Initializes handshake process with a DTLS server at ep.

Parameters
[in]sockDLTS sock to use
[in]epRemote endpoint of the session
[out]remoteThe created session, cannot be NULL
Returns
0 on success
-EAGAIN, if DTLS_HANDSHAKE_TIMEOUT is 0 and no data is available.
-EADDRNOTAVAIL, if the local endpoint of sock is not set.
-EINVAL, if remote is invalid or sock is not properly initialized (or closed while sock_udp_recv() blocks).
-ENOBUFS, if buffer space is not large enough to store received credentials.
-ETIMEDOUT, if timed out when trying to create session.

◆ sock_dtls_session_destroy()

void sock_dtls_session_destroy ( sock_dtls_t sock,
sock_dtls_session_t remote 
)

Destroys an existing DTLS session.

Precondition
(sock != NULL) && (ep != NULL)
Parameters
[in]socksock_dtls_t, which the session is created on
[in]remoteRemote session to destroy