Cannot loop send() when trying to send large data in https_client sample on nRF9160

Hi everyone,

I want to send large data with https protocol.

I edited the https_client sample split the data and looped send() for sending the large data.

But when I did send() once, I got error 128. Did the socket close by itself?

After I run send() once, I run close() -> tls_setup() ->connect() again. Then I can do send() again.

This is very time consuming, in a single loop, reconnecting takes 2 or 3 seconds in my environment. So I need a different solution.

Is it possible to loop send() continuously with the socket open? In the past, there have been similar case with the http protocol, but it seems that closing the socket with every send() is the solution.

https://devzone.nordicsemi.com/f/nordic-q-a/46086/nrf9160-dk-http-post-to-my-webserver

Is this still not improving? If I can send quickly even if I close the socket, it may be a solution.

HW:nRF9160DK

FW:modem v1.3.1

SDK v1.9.1

My LTE connection is LTE-M.

Best Regards,

Yukio Oyama

Parents
  • Hello

    Looking at the https client sample, it looks like the code is supposed to support sending multiple packets before closing the socket.

    I assume the changes you've made is to limit how many bytes you're allowed to send in each packet? Can you show me the exact changes you've made to the sample?

    Best regards,

    Einar

  • Hello Einar-san,

    This is the minimum set with errors. (Some APN information was hidden.)

    To find the cause, I searched for the smallest change where the error occurred. When sending twice (not loop), an error will occur on the second sending.

    This may be due to server behavior rather than device, but I'm not familiar with either.

    If a socket has been a timeout from open or access, can it be controlled by a parameter in the code?

    4263.prj.conf

    /*
     * Copyright (c) 2020 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <string.h>
    #include <zephyr.h>
    #include <stdlib.h>
    #include <net/socket.h>
    #include <modem/nrf_modem_lib.h>
    #include <net/tls_credentials.h>
    #include <modem/lte_lc.h>
    #include <modem/modem_key_mgmt.h>
    
    #define _HTTPS_POST_      1	// Send https to the test server.(1:enable)
    #define _SIM_SOFTBANK_EN_ 1 // Use softbank sim.(1:enable)
    #define _SECOND_SEND_     1 // Second transmission.(1:enable)
    
    #define HTTPS_PORT 443
    
    #if _HTTPS_POST_
    #define HTTPS_HOSTNAME "httpbin.org"
    #else
    #define HTTPS_HOSTNAME "example.com"
    #endif
    
    #if _HTTPS_POST_
    #define HTTP_HEAD                      \
    	"POST /post HTTP/1.1\r\n"              \
    	"Host: " HTTPS_HOSTNAME ":443\r\n" \
    	"Content-Type: text/html\r\n"	   \
    	"Content-Length: 5\r\n"            \
    	"\r\nhello\r\n"
    #else
    #define HTTP_HEAD                                                              \
    	"HEAD / HTTP/1.1\r\n"                                                  \
    	"Host: " HTTPS_HOSTNAME ":443\r\n"                                     \
    	"Connection: close\r\n\r\n"
    #endif
    
    
    #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 `example.com` */
    static const char cert[] = {
    #if _HTTPS_POST_
    	#include "../cert/startfield_class2.pem"	// "httpbin.org"
    #else
    	#include "../cert/DigiCertGlobalRootCA.pem"
    #endif
    };
    
    BUILD_ASSERT(sizeof(cert) < KB(4), "Certificate too large");
    
    #if _SIM_SOFTBANK_EN_
    #define CMD_CGDCONT "AT+CGDCONT=0,\"IPV4V6\",\"APN_name\""
    #define CMD_CGAUTH  "AT+CGAUTH=0,1,\"username\",\"password\""
    #define AT_MAX_CMD_LEN          2048
    static uint8_t at_buf[AT_MAX_CMD_LEN];
    #endif
    
    /* Provision certificate to modem */
    int cert_provision(void)
    {
    	int err;
    	bool exists;
    	int mismatch;
    
    	/* It may be sufficient for you application to check whether the correct
    	 * certificate is provisioned with a given tag directly using modem_key_mgmt_cmp().
    	 * Here, for the sake of the completeness, we check that a certificate exists
    	 * before comparing it with what we expect it to be.
    	 */
    	err = modem_key_mgmt_exists(TLS_SEC_TAG, MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN, &exists);
    	if (err) {
    		printk("Failed to check for certificates err %d\n", err);
    		return err;
    	}
    
    	if (exists) {
    		mismatch = modem_key_mgmt_cmp(TLS_SEC_TAG,
    					      MODEM_KEY_MGMT_CRED_TYPE_CA_CHAIN,
    					      cert, strlen(cert));
    		if (!mismatch) {
    			printk("Certificate match\n");
    			return 0;
    		}
    
    		printk("Certificate mismatch\n");
    		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,
    	};
    
    #if defined(CONFIG_SAMPLE_TFM_MBEDTLS)
    	err = tls_credential_add(tls_sec_tag[0], TLS_CREDENTIAL_CA_CERTIFICATE, cert, sizeof(cert));
    	if (err) {
    		return err;
    	}
    #endif
    
    	/* Set up TLS peer verification */
    	enum {
    		NONE = 0,
    		OPTIONAL = 1,
    		REQUIRED = 2,
    	};
    
    //	verify = REQUIRED;
    	verify = NONE;
    
    	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;
    	}
    
    	err = setsockopt(fd, SOL_TLS, TLS_HOSTNAME, HTTPS_HOSTNAME, sizeof(HTTPS_HOSTNAME) - 1);
    	if (err) {
    		printk("Failed to setup TLS hostname, err %d\n", errno);
    		return err;
    	}
    	return 0;
    }
    
    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");
    
    #if _SIM_SOFTBANK_EN_
    	err = nrf_modem_at_cmd(recv_buf, sizeof(recv_buf), "%s", CMD_CGDCONT);
    	printk("AT+CGDCONT err:%x, %s\n\r",err, recv_buf);
    	err = nrf_modem_at_cmd(recv_buf, sizeof(recv_buf), "%s", CMD_CGAUTH);
    	printk("AT+CGDCONT err:%x, %s\n\r",err, recv_buf);
    #endif	//_SIM_SOFTBANK_EN_
    
    #if !defined(CONFIG_SAMPLE_TFM_MBEDTLS)
    	/* Provision certificates before connecting to the LTE network */
    	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");
    
    	err = getaddrinfo(HTTPS_HOSTNAME, NULL, &hints, &res);
    	if (err) {
    		printk("getaddrinfo() failed, err %d\n", errno);
    		return;
    	}
    
    	((struct sockaddr_in *)res->ai_addr)->sin_port = htons(HTTPS_PORT);
    
    	if (IS_ENABLED(CONFIG_SAMPLE_TFM_MBEDTLS)) {
    		fd = socket(AF_INET, SOCK_STREAM | SOCK_NATIVE_TLS, IPPROTO_TLS_1_2);
    	} else {
    		fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
    	}
    	if (fd == -1) {
    		printk("Failed to open socket!\n");
    		goto clean_up;
    	}
    
    	/* Setup TLS socket options */
    	err = tls_setup(fd);
    	if (err) {
    		goto clean_up;
    	}
    
    	printk("Connecting to %s\n", HTTPS_HOSTNAME);
    	err = connect(fd, res->ai_addr, sizeof(struct sockaddr_in));
    	if (err) {
    		printk("connect() failed, err: %d\n", errno);
    		goto clean_up;
    	}
    
    	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);
    	}
    
    #if _SECOND_SEND_
    	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);
    
    	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);
    	}
    
    
    #endif
    
    	printk("Finished, closing socket.\n");
    
    clean_up:
    	freeaddrinfo(res);
    	(void)close(fd);
    
    	lte_lc_power_off();
    }
    

    In my experiment, send_buff [] is fixed and small in size. Finally, in my project I want to split the image file every 2KB (or less) and upload it by looping the send() .

    Best Regards,

    Yukio Oyama

  • Hello again

    In your code I see that you repeat the whole send-recv procedure when you send the second time. I would not be surprised if the connection is closed server side after handling your recv-request.

    In order to send more while the connection is open, I would instead try to send all your data in the send loop, as you can see from the way the sample code is set up that you are allowed to call send() several times before calling recv(). Does that make sense?

    Best regards,

    Einar

  • Hello Einar-san,

    I wanted to check if the transmitted data was correct every time when I sent the data. But if Recv() causes the socket to close, I would consider changing the way the data is sent.

    I noticed something strange when I was considering this issue.

    I tried increasing the buffer size to send large data. Then, I was able to send 4KB of data if I sent it only once. Of course, I couldn't loop it. However, if I close the socket each time, the 4KB transmission will be reproduced.

    The data size limit for TLS is 2KB.Is it possible to send 4KB only once?

    And does aggressive use of it cause problems in the future?

    Repeating Send() without Recv() will now be confirmed. I will report the results later.

    Best Regards,

    Yukio Oyama

  • Hi

    The size of packets it is possible to send is ultimately limited by available RAM in the modem. This might be more than 2KB depending on your application, but I would still recommend staying within the documented data size limits to guarantee your application will work as you expect.

    Looping send() before calling recv() should work for any buffer size you are able to send though.

    -Einar

  • Hi Einar-san,

    I repeated send() before recv(). However, error 95 occurred when the total data size exceeded around 4KB.

    One transmission size -> Number of repetitions until an error occurs (total size)

    429byte ->11times(all:4719byte)

    685byte ->8times(all:5480byte)

    1201byte ->4times(all:4804byte)

    It seems to cause errors when it exceeds around 4.7KB.

    Is the modem storing data without sending it every send()? Or, does it cause an error if the code repeats send() faster than the modem send the data?

    In the former case, is the only solution is to close the socket?

    In the latter case, is there a way to check the status of the modem before to repeat send()?

    Or is there any other cause and solution?

    The solution doesn't stick to repeating Send (). I want to send large data quickly.

    Best Regards,

    Yukio Oyama

Reply
  • Hi Einar-san,

    I repeated send() before recv(). However, error 95 occurred when the total data size exceeded around 4KB.

    One transmission size -> Number of repetitions until an error occurs (total size)

    429byte ->11times(all:4719byte)

    685byte ->8times(all:5480byte)

    1201byte ->4times(all:4804byte)

    It seems to cause errors when it exceeds around 4.7KB.

    Is the modem storing data without sending it every send()? Or, does it cause an error if the code repeats send() faster than the modem send the data?

    In the former case, is the only solution is to close the socket?

    In the latter case, is there a way to check the status of the modem before to repeat send()?

    Or is there any other cause and solution?

    The solution doesn't stick to repeating Send (). I want to send large data quickly.

    Best Regards,

    Yukio Oyama

Children
Related