This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

First HTTPS socket connect() success, subsequent connect always hangs

Hello,

I'm having issues connecting over TLS more than a single time.  Using the nrf/samples/nrf9160/https_client works fine.

However, if you modify it to connect more than once, it fails (assuming you do close the socket fd).  I made a modifications to main.c that simply loop everything after the lte_lc_init_and_connect() call.

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

#include <string.h>
#include <zephyr.h>
#include <stdlib.h>
#include <net/socket.h>
#include <net/bsdlib.h>
#include <net/tls_credentials.h>
#include <lte_lc.h>
#include <at_cmd.h>
#include <at_notif.h>
#include <modem_key_mgmt.h>

/* DOCLOSE_SOCKET_FD_AFTER_USE 
 * undefine this to try _without_ closing the socket 
 */
#define DOCLOSE_SOCKET_FD_AFTER_USE

/*
 * undefine this to avoid re provisioning the cert on each
 * reset -- this must be done at least once though.
 */
#define DO_PROVISION_GOOGLE_ROOTCERT


#define HTTPS_PORT 443

#define HTTP_HEAD                                                              \
	"HEAD / HTTP/1.1\r\n"                                                  \
	"Host: www.google.com:443\r\n"                                         \
	"Connection: close\r\n\r\n"

#define HTTP_HEAD_LEN (sizeof(HTTP_HEAD) - 1)

#define HTTP_HDR_END "\r\n\r\n"

#define RECV_BUF_SIZE 2048
#define TLS_SEC_TAG 42

static const char send_buf[] = HTTP_HEAD;
static char recv_buf[RECV_BUF_SIZE];

/* Certificate for `google.com` */
static const char cert[] = {
	#include "../cert/GlobalSign-Root-CA-R2"
};

BUILD_ASSERT_MSG(sizeof(cert) < KB(4), "Certificate too large");


/* Initialize AT communications */
int at_comms_init(void)
{
	int err;

	err = at_cmd_init();
	if (err) {
		printk("Failed to initialize AT commands, err %d\n", err);
		return err;
	}

	err = at_notif_init();
	if (err) {
		printk("Failed to initialize AT notifications, err %d\n", err);
		return err;
	}

	return 0;
}

/* Provision certificate to modem */
int cert_provision(void)
{
	int err;
	bool exists;
	u8_t unused;

	err = modem_key_mgmt_exists(TLS_SEC_TAG,
				    MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN,
				    &exists, &unused);
	if (err) {
		printk("Failed to check for certificates err %d\n", err);
		return err;
	}

	if (exists) {
		/* For the sake of simplicity we delete what is provisioned
		 * with our security tag and reprovision our certificate.
		 */
		err = modem_key_mgmt_delete(TLS_SEC_TAG,
					    MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN);
		if (err) {
			printk("Failed to delete existing certificate, err %d\n",
			       err);
		}
	}

	printk("Provisioning certificate\n");

	/*  Provision certificate to the modem */
	err = modem_key_mgmt_write(TLS_SEC_TAG,
				   MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN,
				   cert, sizeof(cert) - 1);
	if (err) {
		printk("Failed to provision certificate, err %d\n", err);
		return err;
	}

	return 0;
}

/* Setup TLS options on a given socket */
int tls_setup(int fd)
{
	int err;
	int verify;

	/* Security tag that we have provisioned the certificate with */
	const sec_tag_t tls_sec_tag[] = {
		TLS_SEC_TAG,
	};

	/* Set up TLS peer verification */
	enum {
		NONE = 0,
		OPTIONAL = 1,
		REQUIRED = 2,
	};

	verify = REQUIRED;

	err = setsockopt(fd, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));
	if (err) {
		printk("Failed to setup peer verification, err %d\n", errno);
		return err;
	}

	/* Associate the socket with the security tag
	 * we have provisioned the certificate with.
	 */
	err = setsockopt(fd, SOL_TLS, TLS_SEC_TAG_LIST, tls_sec_tag,
			 sizeof(tls_sec_tag));
	if (err) {
		printk("Failed to setup TLS sec tag, err %d\n", errno);
		return err;
	}

	return 0;
}

void setup(void) {


}
void main(void)
{

	int err;
	int fd;
	char *p;
	int bytes;
	size_t off;
	struct addrinfo *res;
	struct addrinfo hints = {
		.ai_family = AF_INET,
		.ai_socktype = SOCK_STREAM,
	};

	printk("HTTPS client sample started\n\r");

	err = bsdlib_init();
	if (err) {
		printk("Failed to initialize bsdlib!");
		return;
	}

	/* Initialize AT comms in order to provision the certificate */
	err = at_comms_init();
	if (err) {
		return;
	}

	/* Provision certificates before connecting to the LTE network */
#ifdef DO_PROVISION_GOOGLE_ROOTCERT
	printk("installing root cert for verif");
	err = cert_provision();
	if (err) {
		return;
	}
#endif

	printk("Waiting for network.. ");
	err = lte_lc_init_and_connect();
	if (err) {
		printk("Failed to connect to the LTE network, err %d\n", err);
		return;
	}
	printk("OK\n");


	/* LOOP HERE, to get the address, connect and send/rcv, then close */
	for(;;) {

	printk("getting google address\n");
	err = getaddrinfo("google.com", NULL, &hints, &res);
	if (err) {
		printk("getaddrinfo() failed, err %d\n", errno);
		return;
	}

	((struct sockaddr_in *)res->ai_addr)->sin_port = htons(HTTPS_PORT);


	fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
	if (fd == -1) {
		printk("Failed to open socket!\n");
		return; 
	}




	/* Setup TLS socket options */
	err = tls_setup(fd);
	if (err) {
		goto clean_up;
	}

	printk("Connecting to %s through socket %i\n", "google.com", fd);
	err = connect(fd, res->ai_addr, sizeof(struct sockaddr_in));
	if (err) {
		printk("connect() failed, err: %d\n", errno);
		goto clean_up;
	}

	printk("Connection established");
	off = 0;
	do {
		bytes = send(fd, &send_buf[off], HTTP_HEAD_LEN - off, 0);
		if (bytes < 0) {
			printk("send() failed, err %d\n", errno);
			goto clean_up;
		}
		off += bytes;
	} while (off < HTTP_HEAD_LEN);

	printk("Sent %d bytes\n", off);

	off = 0;
	do {
		bytes = recv(fd, &recv_buf[off], RECV_BUF_SIZE - off, 0);
		if (bytes < 0) {
			printk("recv() failed, err %d\n", errno);
			goto clean_up;
		}
		off += bytes;
	} while (bytes != 0 /* peer closed connection */);

	printk("Received %d bytes\n", off);

	/* Print HTTP response */
	p = strstr(recv_buf, "\r\n");
	if (p) {
		off = p - recv_buf;
		recv_buf[off + 1] = '\0';
		printk("\n>\t %s\n\n", recv_buf);
	}

	printk("Finished, closing socket.\n");

clean_up:
	freeaddrinfo(res);
#ifdef DOCLOSE_SOCKET_FD_AFTER_USE
	(void)close(fd);
#endif
	k_sleep(4000);
	} /* end infinite loop */
}

If you leave the socket hanging, multiple connections will work but you eventually run out of resources.  Output looks like:

HTTPS client sample started
Waiting for network.. OK
getting google address
Connecting to google.com through socket 2
Connection establishedSent 64 bytes
Received 903 bytes

>	 HTTP/1.1 200 OK

Finished, closing socket.
getting google address
Connecting to google.com through socket 3
Connection establishedSent 64 bytes
Received 903 bytes

>	 HTTP/1.1 200 OK

Finished, closing socket.
getting google address
Connecting to google.com through socket 4
connect() failed, err: 45
getting google address
Connecting to google.com through socket 5
connect() failed, err: 45
getting google address

If you actually clean up and close it, then it hangs forever on second call to connect().  Output stops at:

HTTPS client sample started
Waiting for network.. OK
getting google address
Connecting to google.com through socket 2
Connection establishedSent 64 bytes
Received 903 bytes

>	 HTTP/1.1 200 OK

Finished, closing socket.
getting google address
Connecting to google.com through socket 2
[stuck here forever and ever]

What is missing here to actually be able to connect to a remote host more than once?  Is it some setsockopt() to clear out what was set previously, in tls_setup()?  Something else?

Thanks!

Parents
  • Hi.

    Bsdlib v0.6.1 enabled TLS session caching by default. However, this triggers a bug in the modem when trying to re-connect to a server.

    As a workaround, you can either disable TLS session caching using socket options:

    nrf_sec_session_cache_t session_cache = 0;
    
    nrf_setsockopt(socket, NRF_SOL_SECURE,
    
            NRF_SO_SEC_SESSION_CACHE, &session_cache,
    
            sizeof(session_cache));

    Or, you can go back to the previous version of bsdlib (v0.6.0) by changing the revision checked out by west in the ncs/nrf/west.yml file on line 72 to 3e381d09f1d305e230435f5b6e4c9ef928b6a697, and running west update.

    We are working on fixing the bug.

    Best regards,

    Didrik

Reply
  • Hi.

    Bsdlib v0.6.1 enabled TLS session caching by default. However, this triggers a bug in the modem when trying to re-connect to a server.

    As a workaround, you can either disable TLS session caching using socket options:

    nrf_sec_session_cache_t session_cache = 0;
    
    nrf_setsockopt(socket, NRF_SOL_SECURE,
    
            NRF_SO_SEC_SESSION_CACHE, &session_cache,
    
            sizeof(session_cache));

    Or, you can go back to the previous version of bsdlib (v0.6.0) by changing the revision checked out by west in the ncs/nrf/west.yml file on line 72 to 3e381d09f1d305e230435f5b6e4c9ef928b6a697, and running west update.

    We are working on fixing the bug.

    Best regards,

    Didrik

Children
Related