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!