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 = ARRAY_SIZE(other_pubkeys),
},
},
};
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 with the server address
#endif
int main(void)
{
// Add credentials
// [...]
// initialize client
char rcv[128];
sock_udp_t udp_sock;
local.port = 12345;
remote.port = DTLS_DEFAULT_PORT;
remote.netif = gnrc_netif_iter(NULL)->pid; // only if gnrc_netif_highlander() returns true
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,
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), 0);
if (res >= 0) {
printf("Sent %d bytes\n", res);
res = sock_dtls_recv(&dtls_sock, &session, rcv, sizeof(rcv),
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_highlander() returns true
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(). We can set the timeout to 0 if we want the function returns immediately after starting the handshake. In that case, we will need to call sock_dtls_recv() to receive and process all the handshake packets. If the handshake is successful and the session is created, we send packets to it using sock_dtls_send(). As we already know the session exists, we can set the timeout to 0 and listen to the reply with sock_dtls_recv().

Alternatively, set the timeout to of sock_dtls_send() to the duration we want to wait for the handshake process. We can also set the timeout to SOCK_NO_TIMEOUT to block indefinitely until handshake is complete. After handshake completes, the packet will be sent.

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,
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), 0);
if (res >= 0) {
printf("Sent %d bytes: %*.s\n", res, res, data);
res = sock_dtls_recv(&dtls_sock, &session, rcv, sizeof(rcv),
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;

Modules

 SOCK DTLS compile configuration
 

Files

file  dtls.h
 DTLS sock definitions.
 

Macros

#define DTLS_HANDSHAKE_BUFSIZE   (1 << CONFIG_DTLS_HANDSHAKE_BUFSIZE_EXP)
 Size buffer used in handshake to hold credentials.
 
#define SOCK_DTLS_HANDSHAKE   (EXDEV)
 Return value for a successful handshake.
 

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...
 
sock_udp_tsock_dtls_get_udp_sock (sock_dtls_t *sock)
 Get underlying UDP sock. More...
 
int sock_dtls_session_init (sock_dtls_t *sock, const sock_udp_ep_t *ep, sock_dtls_session_t *remote)
 Initialize session handshake. 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)
 Receive handshake messages and application data from remote peer. More...
 
ssize_t sock_dtls_recv_buf (sock_dtls_t *sock, sock_dtls_session_t *remote, void **data, void **buf_ctx, uint32_t timeout)
 Decrypts and provides stack-internal buffer space containing 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, uint32_t timeout)
 Encrypts and sends a message to a remote peer. More...
 
void sock_dtls_close (sock_dtls_t *sock)
 Closes a DTLS sock. More...
 
static int sock_dtls_session_create (sock_dtls_t *sock, const sock_udp_ep_t *ep, sock_dtls_session_t *remote, unsigned timeout)
 Creates a new DTLS session. 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 553 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 529 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 541 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 sock_udp_create() 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_get_udp_sock()

sock_udp_t* sock_dtls_get_udp_sock ( sock_dtls_t sock)

Get underlying UDP sock.

Precondition
sock != NULL.
Parameters
[in]sockDTLS sock to get UDP sock from.
Returns
The underlying UDP sock.

◆ 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 
)

Receive handshake messages and application data from 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
-SOCK_DTLS_HANDSHAKE when new handshake is completed
-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_recv_buf()

ssize_t sock_dtls_recv_buf ( sock_dtls_t sock,
sock_dtls_session_t remote,
void **  data,
void **  buf_ctx,
uint32_t  timeout 
)

Decrypts and provides stack-internal buffer space containing 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 to a stack-internal buffer space containing the received data.
[in,out]buf_ctxStack-internal buffer context. If it points to a NULL pointer, the stack returns a new buffer space for a new packet. If it does not point to a NULL pointer, an existing context is assumed to get a next segment in a buffer.
[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.
Warning
This feature is experimental! This function is quite new, not implemented for all stacks yet, and may be subject to sudden API changes. Do not use in production if this is unacceptable.
Note
Function may block if data is not available and timeout != 0
Function blocks if no packet is currently waiting.
Returns
The number of bytes received on success. May not be the complete payload. Continue calling with the returned buf_ctx to get more buffers until result is 0 or an error.
0, if no received data is available, but everything is in order. If buf_ctx was provided, it was released.
-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).
-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,
uint32_t  timeout 
)

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
[in]timeoutHandshake timeout in microseconds. If timeout > 0, will start a new handshake if no session exists yet. The function will block until handshake completed or timed out. May be SOCK_NO_TIMEOUT to block indefinitely until handshake complete.
Note
When blocking, we will need an extra thread to call sock_dtls_recv() function to handle the incoming handshake messages.
Returns
The number of bytes sent on success
-ENOTCONN, if timeout == 0 and no existing session exists with remote
-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.
-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.
-ETIMEDOUT, 0 < timeout < SOCK_NO_TIMEOUT and timed out.

◆ sock_dtls_session_create()

static int sock_dtls_session_create ( sock_dtls_t sock,
const sock_udp_ep_t ep,
sock_dtls_session_t remote,
unsigned  timeout 
)
inlinestatic

Creates a new DTLS session.

Initiates a handshake with a DTLS server at ep and wait until it completes or timed out.

Deprecated:
Will not be available after the 2020.10 release. Please use sock_dtls_session_init() and sock_dtls_recv() instead.
Parameters
[in]sockDLTS sock to use
[in]epRemote endpoint of the session
[out]remoteThe created session, cannot be NULL
[in]timeoutTimeout to wait for handshake to finish. Returns immediately if 0. May be SOCK_NO_TIMEOUT to wait indefinitely until handshake complete.
Returns
0 on success
-ENOMEM, if no memory to allocate for new peer
-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).

Definition at line 781 of file dtls.h.

◆ 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

◆ sock_dtls_session_init()

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

Initialize session handshake.

Sends a ClientHello message to initialize the handshake. Call sock_dtls_recv() to finish the handshake.

Parameters
[in]sockDTLS sock to use
[in]epRemote endpoint to start a handshake with
[out]remoteResulting session
Returns
1, if new handshake is started
0, if there is an existing session
-ENOMEM, not enough memory to allocate for new peer
-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).