CoAP with DTLS on nRF9151

Dear Nordic team,

I am struggling to set up CoAP with DTLS for a Thingsboard CoAP backend. The connection works just fine when using plain UDP, but when I set up a socket with DTLS I run into an error, where the CoAP client request responds with error 127. I think this means that the CoAP client library identifies the socket as already being connected, but I am not sure why. To break this down some more, I've returned to the official "coap_client" sample and adjusted it to communicate with the Thingsboard Cloud backend. I have confirmed that the sample works using plain UDP. The GET request with the specified path (NOTE: access token is redacted below) works flawlessly. This would be the expected log:

[00:02:10.370,086] <inf> coap_client_sample: CoAP response: code: 0x45, payload: {"shared":{"sdlConfigTs":6}}

From there I modified the sample to provision the required certificate (validity checked with CoAP client on my host machine), use port 5684 and then adjust socket type and options. The modified sample is:

/*
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <stdio.h>
#include <string.h>

#if defined(CONFIG_POSIX_API)
#include <zephyr/posix/arpa/inet.h>
#include <zephyr/posix/netdb.h>
#include <zephyr/posix/sys/socket.h>
#include <zephyr/posix/poll.h>
#else
#include <zephyr/net/socket.h>
#endif /* CONFIG_POSIX_API */

#if CONFIG_MODEM_KEY_MGMT
#include <modem/modem_key_mgmt.h>
#endif

#include <zephyr/kernel.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/net/coap.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/conn_mgr_connectivity.h>
#include <zephyr/net/conn_mgr_monitor.h>
#include <zephyr/random/random.h>
#include <zephyr/net/coap_client.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
#include <zephyr/net/tls_credentials.h>

LOG_MODULE_REGISTER(coap_client_sample, CONFIG_COAP_CLIENT_SAMPLE_LOG_LEVEL);

#define COAP_HOSTNAME "coap.eu.thingsboard.cloud"
#define COAP_PORT 5684
#define COMODO_SEC_TAG 43


static const char comodo_cert[] = {
#include "tb-cloud-root-ca.pem.inc"
	IF_ENABLED(CONFIG_TLS_CREDENTIALS, (0x00))
};

BUILD_ASSERT(sizeof(comodo_cert) < KB(4), "Comodo certificate too large");

/**
 * @brief Provisions a single CA certificate to a specific security tag.
 * @param[in] sec_tag The security tag that the modem will use for the encrypted socket (choose
 * different tags for different DNS addresses).
 * @param[in] cert_buf The root certificate to provision.
 * @param[in] cert_len Length of the certificate.
 * @returns 0 or a negative errno on error
 */
static int32_t tls_cert_provision_single(sec_tag_t sec_tag, const char* cert_buf, size_t cert_len)
{
	int32_t err;

	printk("Processing certificate provisioning for sec tag: %d\n", sec_tag);

#if CONFIG_MODEM_KEY_MGMT
	bool exists;
	int32_t mismatch;

	err = modem_key_mgmt_exists(sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, &exists);
	if (err) {
		printk("Failed to check for certificate on tag %d, err %d\n", sec_tag, err);
		return err;
	}

	if (exists) {
		mismatch = modem_key_mgmt_cmp(
		    sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, cert_buf, cert_len);
		if (!mismatch) {
			printk(
			    "Certificate on tag %d matches expected content. Skipping.\n", sec_tag);
			return 0;
		}

		printk("Certificate mismatch detected on tag %d. Overwriting...\n", sec_tag);
		err = modem_key_mgmt_delete(sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN);
		if (err) {
			printk(
			    "Failed to delete old certificate on tag %d, err %d\n", sec_tag, err);
		}
	}

	printk("Writing certificate to modem storage (tag %d)...\n", sec_tag);
	err = modem_key_mgmt_write(sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, cert_buf, cert_len);
	if (err) {
		printk("Failed to write certificate to tag %d, err %d\n", sec_tag, err);
		return err;
	}
#else /* CONFIG_MODEM_KEY_MGMT */
	err = tls_credential_add(sec_tag, TLS_CREDENTIAL_CA_CERTIFICATE, cert_buf, cert_len);
	if (err == -EEXIST) {
		printk("CA certificate already exists on application core, sec tag: %d\n", sec_tag);
	} else if (err < 0) {
		printk("Failed to register CA certificate to app core: %d\n", err);
		return err;
	}
#endif /* !CONFIG_MODEM_KEY_MGMT */

	printk("Successfully provisioned tag %d\n", sec_tag);
	return 0;
}

/**
 * @brief Provision all certificates to the modem
 */
int32_t tls_cert_provision_all(void)
{
	int32_t err = tls_cert_provision_single(COMODO_SEC_TAG, comodo_cert, sizeof(comodo_cert));
	if (err) {
		printk("Failed to provision Comodo certificate chain\n");
		return err;
	}

	printk("All certificates provisioned successfully.\n");
	return 0;
}


/* Macros used to subscribe to specific Zephyr NET management events. */
#define L4_EVENT_MASK (NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED)
#define CONN_LAYER_EVENT_MASK (NET_EVENT_CONN_IF_FATAL_ERROR)

/* Macro called upon a fatal error, reboots the device. */
#define FATAL_ERROR()					\
	LOG_ERR("Fatal error! Rebooting the device.");	\
	LOG_PANIC();					\
	IF_ENABLED(CONFIG_REBOOT, (sys_reboot(0)))

/* Zephyr NET management event callback structures. */
static struct net_mgmt_event_callback l4_cb;
static struct net_mgmt_event_callback conn_cb;

/* Variable used to indicate if network is connected. */
static bool is_connected;

/* Mutex and conditional variable used to signal network connectivity. */
K_MUTEX_DEFINE(network_connected_lock);
K_CONDVAR_DEFINE(network_connected);

static int server_resolve(struct sockaddr_storage *server)
{
	int err;
	struct addrinfo *result;
	struct addrinfo hints = {
		.ai_family = AF_INET,
		.ai_socktype = SOCK_DGRAM
	};
	char ipv4_addr[NET_IPV4_ADDR_LEN];

	err = getaddrinfo(COAP_HOSTNAME, NULL, &hints, &result);
	if (err) {
		LOG_ERR("getaddrinfo, error: %d", err);
		return err;
	}

	if (result == NULL) {
		LOG_ERR("Address not found");
		return -ENOENT;
	}

	/* IPv4 Address. */
	struct sockaddr_in *server4 = ((struct sockaddr_in *)server);

	server4->sin_addr.s_addr = ((struct sockaddr_in *)result->ai_addr)->sin_addr.s_addr;
	server4->sin_family = AF_INET;
	server4->sin_port = htons(COAP_PORT);

	inet_ntop(AF_INET, &server4->sin_addr.s_addr, ipv4_addr, sizeof(ipv4_addr));

	LOG_INF("IPv4 Address found %s", ipv4_addr);

	/* Free the address. */
	freeaddrinfo(result);

	return 0;
}

static void wait_for_network(void)
{
	k_mutex_lock(&network_connected_lock, K_FOREVER);

	if (!is_connected) {
		LOG_INF("Waiting for network connectivity");
		k_condvar_wait(&network_connected, &network_connected_lock, K_FOREVER);
	}

	k_mutex_unlock(&network_connected_lock);
}

static void response_cb(const struct coap_client_response_data *data, void *user_data)
{
	if (data->result_code >= 0) {
		LOG_INF("CoAP response: code: 0x%x, payload: %s",
			data->result_code, data->payload);
	} else {
		LOG_INF("Response received with error code: %d", data->result_code);
	}
}

static int periodic_coap_request_loop(void)
{
	int err, sock;
	struct sockaddr_storage server = { 0 };
	struct coap_client coap_client = { 0 };
	struct coap_client_request req = {
		.method = COAP_METHOD_GET,
		.confirmable = true,
		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
		.payload = NULL,
		.cb = response_cb,
		.len = 0,
		.path = "/api/v1/ACCESS_TOKEN_REDACTED/attributes?sharedKeys=sdlConfigTs",
	};

	err = server_resolve(&server);
	if (err) {
		LOG_ERR("Failed to resolve server name");
		return err;
	}

	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_DTLS_1_2);
	// sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock < 0) {
		LOG_ERR("Failed to create CoAP socket: %d.", -errno);
		return -errno;
	}

	/* Require peer (server) certificate verification */
	enum {
		NONE = 0,
		OPTIONAL = 1,
		REQUIRED = 2,
	};
	int verify = REQUIRED;

	err = zsock_setsockopt(sock, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));
	if (err) {
		LOG_ERR("Failed to setup peer verification, errno %d", errno);
		return -errno;
	}

	/* Set the server hostname for SNI / certificate validation */
	err = zsock_setsockopt(sock, SOL_TLS, TLS_HOSTNAME, COAP_HOSTNAME, strlen(COAP_HOSTNAME));
	if (err) {
		LOG_ERR("Failed to setup TLS hostname (%s), errno %d", COAP_HOSTNAME, errno);
		return -errno;
	}

	/* Attach the provisioned CA certificate via its security tag */
	sec_tag_t sec_tag_list[] = { COMODO_SEC_TAG };

	err = zsock_setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_list,
	    sizeof(sec_tag_t) * ARRAY_SIZE(sec_tag_list));
	if (err) {
		LOG_ERR("Failed to setup socket security tag, errno %d", errno);
		return -errno;
	}

	LOG_INF("Initializing CoAP client");

	err = coap_client_init(&coap_client, NULL);
	if (err) {
		LOG_ERR("Failed to initialize CoAP client: %d", err);
		return err;
	}

	while (true) {
		wait_for_network();

		/* Send request */
		err = coap_client_req(&coap_client, sock, (struct sockaddr *)&server, &req, NULL);
		if (err) {
			LOG_ERR("Failed to send request: %d", err);
			return err;
		}

		LOG_INF("CoAP GET request sent sent to %s, resource: %s",
			CONFIG_COAP_SAMPLE_SERVER_HOSTNAME, CONFIG_COAP_SAMPLE_RESOURCE);

		k_sleep(K_SECONDS(CONFIG_COAP_SAMPLE_REQUEST_INTERVAL_SECONDS));
	}
}

static void l4_event_handler(struct net_mgmt_event_callback *cb,
			     uint64_t event,
			     struct net_if *iface)
{
	switch (event) {
	case NET_EVENT_L4_CONNECTED:
		LOG_INF("Network connectivity established");
		k_mutex_lock(&network_connected_lock, K_FOREVER);
		is_connected = true;
		k_condvar_signal(&network_connected);
		k_mutex_unlock(&network_connected_lock);
		break;
	case NET_EVENT_L4_DISCONNECTED:
		LOG_INF("Network connectivity lost");
		k_mutex_lock(&network_connected_lock, K_FOREVER);
		is_connected = false;
		k_mutex_unlock(&network_connected_lock);
		break;
	default:
		/* Don't care */
		return;
	}
}
static void connectivity_event_handler(struct net_mgmt_event_callback *cb,
						uint64_t event,
						struct net_if *iface)
{
	if (event == NET_EVENT_CONN_IF_FATAL_ERROR) {
		LOG_ERR("NET_EVENT_CONN_IF_FATAL_ERROR");
		FATAL_ERROR();
		return;
	}
}

int main(void)
{
	int err;

	LOG_INF("The CoAP client sample started");

	/* Setup handler for Zephyr NET Connection Manager events and Connectivity layer. */
	net_mgmt_init_event_callback(&l4_cb, l4_event_handler, L4_EVENT_MASK);
	net_mgmt_add_event_callback(&l4_cb);

	net_mgmt_init_event_callback(&conn_cb, connectivity_event_handler, CONN_LAYER_EVENT_MASK);
	net_mgmt_add_event_callback(&conn_cb);

	/* Bring all network interfaces up.
	 * Wi-Fi or LTE depending on the board that the sample was built for.
	 */
	LOG_INF("Bringing network interface up and connecting to the network");

	err = conn_mgr_all_if_up(true);
	if (err) {
		LOG_ERR("conn_mgr_all_if_up, error: %d", err);
		FATAL_ERROR();
		return err;
	}

	err = conn_mgr_all_if_connect(true);
	if (err) {
		LOG_ERR("conn_mgr_all_if_connect, error: %d", err);
		FATAL_ERROR();
		return err;
	}

	/* Resend connection status if the sample is built for NATIVE_SIM.
	 * This is necessary because the network interface is automatically brought up
	 * at SYS_INIT() before main() is called.
	 * This means that NET_EVENT_L4_CONNECTED fires before the
	 * appropriate handler l4_event_handler() is registered.
	 */
	if (IS_ENABLED(CONFIG_BOARD_NATIVE_SIM)) {
		conn_mgr_mon_resend_status();
	}

	wait_for_network();

	err = periodic_coap_request_loop();
	if (err) {
		LOG_ERR("periodic_coap_request_loop, error: %d", err);
		FATAL_ERROR();
		return err;
	}

	return 0;
}

This yields the following logs.

*** Booting nRF Connect SDK v3.4.0-99553055607b ***
*** Using Zephyr OS v4.4.0-bf801e4e3d19 ***
[00:00:00.254,058] <inf> coap_client_sample: The CoAP client sample started
[00:00:00.254,089] <inf> coap_client_sample: Bringing network interface up and connecting to the network
[00:00:00.586,791] <inf> coap_client_sample: Waiting for network connectivity
[00:00:04.264,648] <inf> coap_client_sample: Network connectivity established
[00:00:04.364,440] <inf> coap_client_sample: IPv4 Address found 3.127.76.36
[00:00:04.364,807] <inf> coap_client_sample: Initializing CoAP client
[00:00:04.366,149] <err> coap_client_sample: Failed to send request: -127
[00:00:04.366,180] <err> coap_client_sample: periodic_coap_request_loop, error: -127
[00:00:04.366,180] <err> coap_client_sample: Fatal error! Rebooting the device.

I have also tried manually connecting the socket and then passing NULL as the address pointer for the request:

/*
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <stdio.h>
#include <string.h>

#if defined(CONFIG_POSIX_API)
#include <zephyr/posix/arpa/inet.h>
#include <zephyr/posix/netdb.h>
#include <zephyr/posix/sys/socket.h>
#include <zephyr/posix/poll.h>
#else
#include <zephyr/net/socket.h>
#endif /* CONFIG_POSIX_API */

#if CONFIG_MODEM_KEY_MGMT
#include <modem/modem_key_mgmt.h>
#endif

#include <zephyr/kernel.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/net/coap.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/conn_mgr_connectivity.h>
#include <zephyr/net/conn_mgr_monitor.h>
#include <zephyr/random/random.h>
#include <zephyr/net/coap_client.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
#include <zephyr/net/tls_credentials.h>

LOG_MODULE_REGISTER(coap_client_sample, CONFIG_COAP_CLIENT_SAMPLE_LOG_LEVEL);

#define COAP_HOSTNAME "coap.eu.thingsboard.cloud"
#define COAP_PORT 5684
#define COMODO_SEC_TAG 43


static const char comodo_cert[] = {
#include "tb-cloud-root-ca.pem.inc"
	IF_ENABLED(CONFIG_TLS_CREDENTIALS, (0x00))
};

BUILD_ASSERT(sizeof(comodo_cert) < KB(4), "Comodo certificate too large");

/**
 * @brief Provisions a single CA certificate to a specific security tag.
 * @param[in] sec_tag The security tag that the modem will use for the encrypted socket (choose
 * different tags for different DNS addresses).
 * @param[in] cert_buf The root certificate to provision.
 * @param[in] cert_len Length of the certificate.
 * @returns 0 or a negative errno on error
 */
static int32_t tls_cert_provision_single(sec_tag_t sec_tag, const char* cert_buf, size_t cert_len)
{
	int32_t err;

	printk("Processing certificate provisioning for sec tag: %d\n", sec_tag);

#if CONFIG_MODEM_KEY_MGMT
	bool exists;
	int32_t mismatch;

	err = modem_key_mgmt_exists(sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, &exists);
	if (err) {
		printk("Failed to check for certificate on tag %d, err %d\n", sec_tag, err);
		return err;
	}

	if (exists) {
		mismatch = modem_key_mgmt_cmp(
		    sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, cert_buf, cert_len);
		if (!mismatch) {
			printk(
			    "Certificate on tag %d matches expected content. Skipping.\n", sec_tag);
			return 0;
		}

		printk("Certificate mismatch detected on tag %d. Overwriting...\n", sec_tag);
		err = modem_key_mgmt_delete(sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN);
		if (err) {
			printk(
			    "Failed to delete old certificate on tag %d, err %d\n", sec_tag, err);
		}
	}

	printk("Writing certificate to modem storage (tag %d)...\n", sec_tag);
	err = modem_key_mgmt_write(sec_tag, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, cert_buf, cert_len);
	if (err) {
		printk("Failed to write certificate to tag %d, err %d\n", sec_tag, err);
		return err;
	}
#else /* CONFIG_MODEM_KEY_MGMT */
	err = tls_credential_add(sec_tag, TLS_CREDENTIAL_CA_CERTIFICATE, cert_buf, cert_len);
	if (err == -EEXIST) {
		printk("CA certificate already exists on application core, sec tag: %d\n", sec_tag);
	} else if (err < 0) {
		printk("Failed to register CA certificate to app core: %d\n", err);
		return err;
	}
#endif /* !CONFIG_MODEM_KEY_MGMT */

	printk("Successfully provisioned tag %d\n", sec_tag);
	return 0;
}

/**
 * @brief Provision all certificates to the modem
 */
int32_t tls_cert_provision_all(void)
{
	int32_t err = tls_cert_provision_single(COMODO_SEC_TAG, comodo_cert, sizeof(comodo_cert));
	if (err) {
		printk("Failed to provision Comodo certificate chain\n");
		return err;
	}

	printk("All certificates provisioned successfully.\n");
	return 0;
}


/* Macros used to subscribe to specific Zephyr NET management events. */
#define L4_EVENT_MASK (NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED)
#define CONN_LAYER_EVENT_MASK (NET_EVENT_CONN_IF_FATAL_ERROR)

/* Macro called upon a fatal error, reboots the device. */
#define FATAL_ERROR()					\
	LOG_ERR("Fatal error! Rebooting the device.");	\
	LOG_PANIC();					\
	IF_ENABLED(CONFIG_REBOOT, (sys_reboot(0)))

/* Zephyr NET management event callback structures. */
static struct net_mgmt_event_callback l4_cb;
static struct net_mgmt_event_callback conn_cb;

/* Variable used to indicate if network is connected. */
static bool is_connected;

/* Mutex and conditional variable used to signal network connectivity. */
K_MUTEX_DEFINE(network_connected_lock);
K_CONDVAR_DEFINE(network_connected);

static int server_resolve(struct sockaddr_storage *server)
{
	int err;
	struct addrinfo *result;
	struct addrinfo hints = {
		.ai_family = AF_INET,
		.ai_socktype = SOCK_DGRAM
	};
	char ipv4_addr[NET_IPV4_ADDR_LEN];

	err = getaddrinfo(COAP_HOSTNAME, NULL, &hints, &result);
	if (err) {
		LOG_ERR("getaddrinfo, error: %d", err);
		return err;
	}

	if (result == NULL) {
		LOG_ERR("Address not found");
		return -ENOENT;
	}

	/* IPv4 Address. */
	struct sockaddr_in *server4 = ((struct sockaddr_in *)server);

	server4->sin_addr.s_addr = ((struct sockaddr_in *)result->ai_addr)->sin_addr.s_addr;
	server4->sin_family = AF_INET;
	server4->sin_port = htons(COAP_PORT);

	inet_ntop(AF_INET, &server4->sin_addr.s_addr, ipv4_addr, sizeof(ipv4_addr));

	LOG_INF("IPv4 Address found %s", ipv4_addr);

	/* Free the address. */
	freeaddrinfo(result);

	return 0;
}

static void wait_for_network(void)
{
	k_mutex_lock(&network_connected_lock, K_FOREVER);

	if (!is_connected) {
		LOG_INF("Waiting for network connectivity");
		k_condvar_wait(&network_connected, &network_connected_lock, K_FOREVER);
	}

	k_mutex_unlock(&network_connected_lock);
}

static void response_cb(const struct coap_client_response_data *data, void *user_data)
{
	if (data->result_code >= 0) {
		LOG_INF("CoAP response: code: 0x%x, payload: %s",
			data->result_code, data->payload);
	} else {
		LOG_INF("Response received with error code: %d", data->result_code);
	}
}

static int periodic_coap_request_loop(void)
{
	int err, sock;
	struct sockaddr_storage server = { 0 };
	struct coap_client coap_client = { 0 };
	struct coap_client_request req = {
		.method = COAP_METHOD_GET,
		.confirmable = true,
		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
		.payload = NULL,
		.cb = response_cb,
		.len = 0,
		.path = "/api/v1/ACCESS_TOKEN_REDACTED/attributes?sharedKeys=sdlConfigTs",
	};

	err = server_resolve(&server);
	if (err) {
		LOG_ERR("Failed to resolve server name");
		return err;
	}

	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_DTLS_1_2);
	// sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock < 0) {
		LOG_ERR("Failed to create CoAP socket: %d.", -errno);
		return -errno;
	}

	/* Require peer (server) certificate verification */
	enum {
		NONE = 0,
		OPTIONAL = 1,
		REQUIRED = 2,
	};
	int verify = REQUIRED;

	err = zsock_setsockopt(sock, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));
	if (err) {
		LOG_ERR("Failed to setup peer verification, errno %d", errno);
		return -errno;
	}

	/* Set the server hostname for SNI / certificate validation */
	err = zsock_setsockopt(sock, SOL_TLS, TLS_HOSTNAME, COAP_HOSTNAME, strlen(COAP_HOSTNAME));
	if (err) {
		LOG_ERR("Failed to setup TLS hostname (%s), errno %d", COAP_HOSTNAME, errno);
		return -errno;
	}

	/* Attach the provisioned CA certificate via its security tag */
	sec_tag_t sec_tag_list[] = { COMODO_SEC_TAG };

	err = zsock_setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_list,
	    sizeof(sec_tag_t) * ARRAY_SIZE(sec_tag_list));
	if (err) {
		LOG_ERR("Failed to setup socket security tag, errno %d", errno);
		return -errno;
	}

	socklen_t addr_len = (((struct sockaddr *)&server)->sa_family == AF_INET) ? sizeof(struct sockaddr_in)
								 : sizeof(struct sockaddr_in6);

	err = connect(sock, (struct sockaddr *)&server, addr_len);
	if (err < 0) {
		LOG_ERR("DTLS transport connect failed, errno %d", errno);
		return -errno;
	}

	LOG_INF("Initializing CoAP client");

	err = coap_client_init(&coap_client, NULL);
	if (err) {
		LOG_ERR("Failed to initialize CoAP client: %d", err);
		return err;
	}

	while (true) {
		wait_for_network();

		/* Send request */
		err = coap_client_req(&coap_client, sock, NULL, &req, NULL);
		if (err) {
			LOG_ERR("Failed to send request: %d", err);
			return err;
		}

		LOG_INF("CoAP GET request sent sent to %s, resource: %s",
			CONFIG_COAP_SAMPLE_SERVER_HOSTNAME, CONFIG_COAP_SAMPLE_RESOURCE);

		k_sleep(K_SECONDS(CONFIG_COAP_SAMPLE_REQUEST_INTERVAL_SECONDS));
	}
}

static void l4_event_handler(struct net_mgmt_event_callback *cb,
			     uint64_t event,
			     struct net_if *iface)
{
	switch (event) {
	case NET_EVENT_L4_CONNECTED:
		LOG_INF("Network connectivity established");
		k_mutex_lock(&network_connected_lock, K_FOREVER);
		is_connected = true;
		k_condvar_signal(&network_connected);
		k_mutex_unlock(&network_connected_lock);
		break;
	case NET_EVENT_L4_DISCONNECTED:
		LOG_INF("Network connectivity lost");
		k_mutex_lock(&network_connected_lock, K_FOREVER);
		is_connected = false;
		k_mutex_unlock(&network_connected_lock);
		break;
	default:
		/* Don't care */
		return;
	}
}
static void connectivity_event_handler(struct net_mgmt_event_callback *cb,
						uint64_t event,
						struct net_if *iface)
{
	if (event == NET_EVENT_CONN_IF_FATAL_ERROR) {
		LOG_ERR("NET_EVENT_CONN_IF_FATAL_ERROR");
		FATAL_ERROR();
		return;
	}
}

int main(void)
{
	int err;

	LOG_INF("The CoAP client sample started");

	/* Setup handler for Zephyr NET Connection Manager events and Connectivity layer. */
	net_mgmt_init_event_callback(&l4_cb, l4_event_handler, L4_EVENT_MASK);
	net_mgmt_add_event_callback(&l4_cb);

	net_mgmt_init_event_callback(&conn_cb, connectivity_event_handler, CONN_LAYER_EVENT_MASK);
	net_mgmt_add_event_callback(&conn_cb);

	/* Bring all network interfaces up.
	 * Wi-Fi or LTE depending on the board that the sample was built for.
	 */
	LOG_INF("Bringing network interface up and connecting to the network");

	err = conn_mgr_all_if_up(true);
	if (err) {
		LOG_ERR("conn_mgr_all_if_up, error: %d", err);
		FATAL_ERROR();
		return err;
	}

	err = conn_mgr_all_if_connect(true);
	if (err) {
		LOG_ERR("conn_mgr_all_if_connect, error: %d", err);
		FATAL_ERROR();
		return err;
	}

	/* Resend connection status if the sample is built for NATIVE_SIM.
	 * This is necessary because the network interface is automatically brought up
	 * at SYS_INIT() before main() is called.
	 * This means that NET_EVENT_L4_CONNECTED fires before the
	 * appropriate handler l4_event_handler() is registered.
	 */
	if (IS_ENABLED(CONFIG_BOARD_NATIVE_SIM)) {
		conn_mgr_mon_resend_status();
	}

	wait_for_network();

	err = periodic_coap_request_loop();
	if (err) {
		LOG_ERR("periodic_coap_request_loop, error: %d", err);
		FATAL_ERROR();
		return err;
	}

	return 0;
}

That produces a different application log:

*** Booting nRF Connect SDK v3.4.0-99553055607b ***
*** Using Zephyr OS v4.4.0-bf801e4e3d19 ***
[00:00:00.385,070] <inf> coap_client_sample: The CoAP client sample started
[00:00:00.385,101] <inf> coap_client_sample: Bringing network interface up and connecting to the network
[00:00:00.720,092] <inf> coap_client_sample: Waiting for network connectivity
[00:00:05.107,818] <inf> coap_client_sample: Network connectivity established
[00:00:05.241,302] <inf> coap_client_sample: IPv4 Address found 52.58.111.18
[00:00:07.060,394] <inf> coap_client_sample: Initializing CoAP client
[00:00:07.063,079] <inf> coap_client_sample: CoAP GET request sent sent to californium.eclipseprojects.io, resource: obs
[00:00:07.161,773] <inf> coap_client_sample: Response received with error code: -106
[00:00:10.102,813] <inf> coap_client_sample: Response received with error code: -106

Is the following definition if the 106 error the one being bubbled up into the callback?

#define EAFNOSUPPORT 106	/* Address family not supported by protocol family */

This leaves me very confused. I also tried to rule out certificate issues by setting the following.

/* Require peer (server) certificate verification */
	enum {
		NONE = 0,
		OPTIONAL = 1,
		REQUIRED = 2,
	};
	int verify = NONE;

	err = zsock_setsockopt(sock, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));
	if (err) {
		LOG_ERR("Failed to setup peer verification, errno %d", errno);
		return -errno;
	}

This yields the same results.

Is the CoAP client library compatible with DTLS and is the intended use to let the library connect the socket or should the socket be manually connected? 

Help is greatly appreciated!

Best,

Tom

EDIT: I was able to confirm that the request can be successfully carried out when using the low-level CoAP methods. Therefore, I can rule out certificate or DTLS handshake issues. I am probably misunderstanding something about the high-level CoAP client library...

Parents
  • Hi Tom,

    Thanks for the detailed CoAP debug log, it matches what we see here.

    On nRF9151 with your approach (DTLS, connect(), NULL address to coap_client_req()), we see the same behaviour on NCS v3.4.0 the request and response complete, then sending the empty ACK fails (Error sending a CoAP ACK-message, -106 in the callback). The same application on NCS v3.4.1 works without that error.

    Your tests with non-confirmable requests fit this, that path does not hit the same ACK handling. This does not indicate that confirmable CoAP is unsupported on the modem, I think it points to a CoAP client issue on v3.4.0 when acknowledging a confirmable response on a connected DTLS socket. So recommendation is to move to NCS v3.4.1. Do let me know if it runs without error on it.

Reply
  • Hi Tom,

    Thanks for the detailed CoAP debug log, it matches what we see here.

    On nRF9151 with your approach (DTLS, connect(), NULL address to coap_client_req()), we see the same behaviour on NCS v3.4.0 the request and response complete, then sending the empty ACK fails (Error sending a CoAP ACK-message, -106 in the callback). The same application on NCS v3.4.1 works without that error.

    Your tests with non-confirmable requests fit this, that path does not hit the same ACK handling. This does not indicate that confirmable CoAP is unsupported on the modem, I think it points to a CoAP client issue on v3.4.0 when acknowledging a confirmable response on a connected DTLS socket. So recommendation is to move to NCS v3.4.1. Do let me know if it runs without error on it.

Children
  • Hey Syed, 

    thanks for the response. I just updated to NCS v3.4.1 and now confirmable requests work! Thanks!

    For the record and for the AI: CoAP with DTLS works when the socket is created, socket options for DTLS are applied and the socket is then connected manually. When using the CoAP client API, it is important to then pass NULL as the "addr" argument in the "coap_client_req()" method. This works on NCS v3.4.1.

    Furthermore I have two additional questions:

    1. Our application will probably have to renew the socket regularly and I am not yet sure if we will be able to use CID for session resumptions. For general TLS session resumption with HTTPS the following seems to work:

    int32_t cache_enabled = 1;
    err = setsockopt(
        fd, NRF_SOL_SECURE, NRF_SO_SEC_SESSION_CACHE, &cache_enabled, sizeof(cache_enabled));
    if (err) {
    	printk("Failed to enable TLS session cache, err %d\n", errno);
    	return err;
    }

    Would this also apply to DTLS session resumption in the CoAP case?

    2. I was wondering how PSM and eDRX settings affect the inner workings of the CoAP client. We do not plan to use observe functionality. Would settings like

    # PSM
    CONFIG_LTE_LC_PSM_MODULE=y
    CONFIG_LTE_PSM_REQ=y
    # PSM Periodic TAU (2 hours)
    CONFIG_LTE_PSM_REQ_RPTAU="00100010"
    # PSM Active Time (22 seconds)
    CONFIG_LTE_PSM_REQ_RAT="00001011"
    # eDRX
    CONFIG_LTE_LC_EDRX_MODULE=y
    CONFIG_LTE_EDRX_REQ=y
    # eDRX Cycle: "0000" = 5.12 s, "0010" = 20.48 s
    CONFIG_LTE_EDRX_REQ_VALUE_LTE_M="0010"
    CONFIG_LTE_EDRX_REQ_VALUE_NBIOT="0010"
    # Paging Time Window (PTW): "0011" = 5.12 s for LTE-M / 10.24 s for NB-IoT
    CONFIG_LTE_PTW_VALUE_LTE_M="0011"
    CONFIG_LTE_PTW_VALUE_NBIOT="0011"

    cause conflicts with the timeout and retry behavior of the CoAP client if the modem successfully negotiates them?

    Thanks again and best,

    Tom

  • > Our application will probably have to renew the socket regularly 

    Why?

    > I am not yet sure if we will be able to use CID for session resumptions.

    CID is not related to session resumption. CID rather obsoletes session resumption in the most cases.

    The nasty point in Nordic's DTLS modem implementation is, that the DTLS state is bound to the socket. If that socket closes, the state is usually gone. I don't know, If there is a way to preserve it, but Nordic will know it. 

  • Hi Tom,

    Good to know it worked.

    I tested your pattern, TLS_SESSION_CACHE on a new DTLS socket, connect(), then coap_client_req close and repeat. However TLS_DTLS_HANDSHAKE_STATUS was full on two connects, so I did not see session resumption on my test server (not ThingsBoard). The cache option applies to DTLS like HTTPS, but I think resumption depends on the server side so I would suggest you to try on your ThingsBoard.

    I am checking details about your second question and will get back to you by tomorrow. Thanks

  • If you provide an ip-capture/wireshark, we will see, what happens exactly.

  • Hey Achim,

    thanks for your responses, this and the previous one. 

    > Our application will probably have to renew the socket regularly 

    Why?

    It might well be that my understanding of UDP sockets is not correct: Our application will have situations in which the socket will only be used every few hours, potentially even once a day. My assumption so far was that we cannot leave the socket open and inactive for long periods, because the cellular provider might close the session. Though, now that I am thinking about it, this might come down to how PSM and eDRX settings affect the session state. Do you have any insights and advice on how long the socket could remain open but inactive?

    > I am not yet sure if we will be able to use CID for session resumptions.

    CID is not related to session resumption. CID rather obsoletes session resumption in the most cases.

    My wording is probably not correct, because this is the first time that I work with CoAP in-depth. My understanding is that the CoAP server and especially the load balancer need to be CID aware when multiple server instances are involved. I haven't yet checked whether this is possible for us. Maybe you know this better: What happens to CIDs if for example load balancer or server instances are restarted or scaled down?

    Best,

    Tom

Related