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...

  • Hi,

    Thanks for reaching out.

    I am trying out on my end and will get back to you soon. Although not very important, can you share the modem firmware version you are using?

    Best Regards,
    Syed Maysum

  • Hi Syed,

    thanks for trying out the sample! I'm using modem firmware v2.0.4. 

    One more thing that might cause the 106 errors inside the callback, is the path length of the request. Because of the API structure and the access token, the path is 62 characters long in this particular case. I'm not sure if this might be an issue, but just for the sake of completeness, here are the CoAP settings I use:

    CONFIG_COAP=y
    CONFIG_COAP_CLIENT=y
    CONFIG_COAP_CLIENT_THREAD_PRIORITY=10
    CONFIG_COAP_CLIENT_MESSAGE_SIZE=512
    CONFIG_COAP_CLIENT_MESSAGE_HEADER_SIZE=128
    CONFIG_COAP_CLIENT_MAX_PATH_LENGTH=128

    Best,

    Tom

  • I've found time to trouble-shoot this more and I think the CoAP library debug logs might reveal where the problem lies. With the request set to confirmable, the log seems to show that the request is successfully transmitted, the response successfully received, but the acknowledgement cannot be returned:

    [00:00:07.596,099] <inf> coaps: IPv4 Address found 3.127.76.36
    [00:00:07.596,405] <inf> coaps: sa_family = 1
    [00:00:10.778,137] <dbg> net_coap: coap_client_req: Request is_observe 0
    [00:00:10.778,198] <dbg> net_coap: send_request: Send CoAP Request:
                                       REDACTED_TO_HIDE_ACCESS_TOKEN 
    [00:00:10.779,632] <inf> main_app: waiting
    [00:00:11.101,684] <dbg> net_coap: receive: Receive CoAP Response:
                                       48 45 0c 6e 2b 8a 67 c9  42 04 52 63 c1 32 ff 7b |HE.n+.g. B.Rc.2.{
                                       22 73 68 61 72 65 64 22  3a 7b 22 73 64 6c 43 6f |"shared" :{"sdlCo
                                       6e 66 69 67 54 73 22 3a  36 7d 7d                |nfigTs": 6}}     
    [00:00:11.101,715] <dbg> net_coap: recv_response: Received 43 bytes
    [00:00:11.101,745] <dbg> net_coap: send_request: Send CoAP Request:
                                       60 00 0c 6e                                      |`..n             
    [00:00:11.101,776] <err> net_coap: Error sending a CoAP ACK-message
    [00:00:11.101,806] <inf> main_app: Response received with error code: -106
    [00:00:11.101,837] <inf> main_app: sleeping
    [00:00:11.101,898] <err> net_coap: Error handling response
    [00:00:13.669,830] <dbg> net_coap: receive: Receive CoAP Response:
                                       48 45 0c 6e 2b 8a 67 c9  42 04 52 63 c1 32 ff 7b |HE.n+.g. B.Rc.2.{
                                       22 73 68 61 72 65 64 22  3a 7b 22 73 64 6c 43 6f |"shared" :{"sdlCo
                                       6e 66 69 67 54 73 22 3a  36 7d 7d                |nfigTs": 6}}     
    [00:00:13.669,860] <dbg> net_coap: recv_response: Received 43 bytes
    [00:00:13.669,921] <dbg> net_coap: send_request: Send CoAP Request:
                                       60 00 0c 6e                                      |`..n             
    [00:00:13.669,921] <err> net_coap: Error sending a CoAP ACK-message
    [00:00:13.669,952] <inf> main_app: Response received with error code: -106
    [00:00:13.669,952] <err> net_coap: Error handling response

    When using a non-confirmable request, no errors occur. I've confirmed this with GET and POST requests. Data is read from and written to Thingsboard successfully.

    After having a look at the library's code where this

    static int send_ack(int sock_fd, const struct net_sockaddr *addr, net_socklen_t addrlen,
    		    const struct coap_packet *req, uint8_t response_code)
    {
    	int ret;
    	struct coap_packet ack;
    	uint8_t ack_buf[COAP_FIXED_HEADER_SIZE + COAP_TOKEN_MAX_LEN];
    
    	ret = coap_ack_init(&ack, req, ack_buf, sizeof(ack_buf), response_code);
    	if (ret < 0) {
    		LOG_ERR("Failed to initialize CoAP ACK-message");
    		return ret;
    	}
    
    	ret = send_request(sock_fd, ack.data, ack.offset, 0, addr, addrlen);
    	if (ret < 0) {
    		LOG_ERR("Error sending a CoAP ACK-message");
    		return ret;
    	}
    
    	return 0;
    }

    seems to produce the error message, because this

    static int send_request(int sock, const void *buf, size_t len, int flags,
    			const struct net_sockaddr *dest_addr, net_socklen_t addrlen)
    {
    	int ret;
    
    	LOG_HEXDUMP_DBG(buf, len, "Send CoAP Request:");
    	if (addrlen == 0) {
    		ret = zsock_sendto(sock, buf, len, flags, NULL, 0);
    	} else {
    		ret = zsock_sendto(sock, buf, len, flags, dest_addr, addrlen);
    	}
    
    	return ret >= 0 ? ret : -errno;
    }

    throws the error when trying to write the ACK ("60 00 0c 6e") to the socket, I am wondering if the modem rejects that specific message for some reason. I think that this is not a general socket issue, because I can reuse the socket for multiple following requests successfully. It just seems to reject only the ACK.

    Do you have an idea why this happens? Are confirmable packets not supported via the modem?

  • -106 => #define EAFNOSUPPORT 106    /* Address family not supported by protocol family */

    I would check the "dest_addr", "addrlen" parameters.

    > I am wondering if the modem rejects that specific message for some reason.

    I never recognized that in all the years (sending a couple 10.000 ACKs).

    > When using a non-confirmable request, no errors occur. I've confirmed this with GET and POST requests. Data is read from and written to Thingsboard successfully.

    That's more a indirect effect. ThingsBoard uses a "Separate Response", a "Piggybacked Response" would work without such an "ACK for Response".

    Also not sure, if you want to use observe/notify and therefore the Responses (Notification) may be sent as CON (from time to time to check the interest). In my experience, NATs makes observe/notify pretty frequently useless. We discussed that years ago in the IETF core list.

    In the end, if your client is designed for "very low energy consumption", it will frequently sleep for long. Depending on your network setup, that may already cause ip-address changes and usually makes observe/notify not longer working.

Related