1 /*
2  * Copyright (c) 2019 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(net_websocket_client_sample, LOG_LEVEL_DBG);
9 
10 #include <zephyr/misc/lorem_ipsum.h>
11 #include <zephyr/net/net_ip.h>
12 #include <zephyr/net/socket.h>
13 #include <zephyr/net/tls_credentials.h>
14 #include <zephyr/net/websocket.h>
15 #include <zephyr/random/random.h>
16 #include <zephyr/shell/shell.h>
17 
18 #include "ca_certificate.h"
19 
20 #define SERVER_PORT 9001
21 
22 #if defined(CONFIG_NET_CONFIG_PEER_IPV6_ADDR)
23 #define SERVER_ADDR6  CONFIG_NET_CONFIG_PEER_IPV6_ADDR
24 #else
25 #define SERVER_ADDR6 ""
26 #endif
27 
28 #if defined(CONFIG_NET_CONFIG_PEER_IPV4_ADDR)
29 #define SERVER_ADDR4  CONFIG_NET_CONFIG_PEER_IPV4_ADDR
30 #else
31 #define SERVER_ADDR4 ""
32 #endif
33 
34 static const char lorem_ipsum[] = LOREM_IPSUM;
35 
36 #define MAX_RECV_BUF_LEN (sizeof(lorem_ipsum) - 1)
37 
38 const int ipsum_len = MAX_RECV_BUF_LEN;
39 
40 static uint8_t recv_buf_ipv4[MAX_RECV_BUF_LEN];
41 static uint8_t recv_buf_ipv6[MAX_RECV_BUF_LEN];
42 
43 /* We need to allocate bigger buffer for the websocket data we receive so that
44  * the websocket header fits into it.
45  */
46 #define EXTRA_BUF_SPACE 30
47 
48 static uint8_t temp_recv_buf_ipv4[MAX_RECV_BUF_LEN + EXTRA_BUF_SPACE];
49 static uint8_t temp_recv_buf_ipv6[MAX_RECV_BUF_LEN + EXTRA_BUF_SPACE];
50 
setup_socket(sa_family_t family,const char * server,int port,int * sock,struct sockaddr * addr,socklen_t addr_len)51 static int setup_socket(sa_family_t family, const char *server, int port,
52 			int *sock, struct sockaddr *addr, socklen_t addr_len)
53 {
54 	const char *family_str = family == AF_INET ? "IPv4" : "IPv6";
55 	int ret = 0;
56 
57 	memset(addr, 0, addr_len);
58 
59 	if (family == AF_INET) {
60 		net_sin(addr)->sin_family = AF_INET;
61 		net_sin(addr)->sin_port = htons(port);
62 		inet_pton(family, server, &net_sin(addr)->sin_addr);
63 	} else {
64 		net_sin6(addr)->sin6_family = AF_INET6;
65 		net_sin6(addr)->sin6_port = htons(port);
66 		inet_pton(family, server, &net_sin6(addr)->sin6_addr);
67 	}
68 
69 	if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
70 		sec_tag_t sec_tag_list[] = {
71 			CA_CERTIFICATE_TAG,
72 		};
73 
74 		*sock = socket(family, SOCK_STREAM, IPPROTO_TLS_1_2);
75 		if (*sock >= 0) {
76 			ret = setsockopt(*sock, SOL_TLS, TLS_SEC_TAG_LIST,
77 					 sec_tag_list, sizeof(sec_tag_list));
78 			if (ret < 0) {
79 				LOG_ERR("Failed to set %s secure option (%d)",
80 					family_str, -errno);
81 				ret = -errno;
82 				goto fail;
83 			}
84 
85 			ret = setsockopt(*sock, SOL_TLS, TLS_HOSTNAME,
86 					 TLS_PEER_HOSTNAME,
87 					 sizeof(TLS_PEER_HOSTNAME));
88 			if (ret < 0) {
89 				LOG_ERR("Failed to set %s TLS_HOSTNAME "
90 					"option (%d)", family_str, -errno);
91 				ret = -errno;
92 				goto fail;
93 			}
94 		}
95 	} else {
96 		*sock = socket(family, SOCK_STREAM, IPPROTO_TCP);
97 	}
98 
99 	if (*sock < 0) {
100 		LOG_ERR("Failed to create %s HTTP socket (%d)", family_str,
101 			-errno);
102 	}
103 
104 	return ret;
105 
106 fail:
107 	if (*sock >= 0) {
108 		close(*sock);
109 		*sock = -1;
110 	}
111 
112 	return ret;
113 }
114 
connect_socket(sa_family_t family,const char * server,int port,int * sock,struct sockaddr * addr,socklen_t addr_len)115 static int connect_socket(sa_family_t family, const char *server, int port,
116 			  int *sock, struct sockaddr *addr, socklen_t addr_len)
117 {
118 	int ret;
119 
120 	ret = setup_socket(family, server, port, sock, addr, addr_len);
121 	if (ret < 0 || *sock < 0) {
122 		return -1;
123 	}
124 
125 	ret = connect(*sock, addr, addr_len);
126 	if (ret < 0) {
127 		LOG_ERR("Cannot connect to %s remote (%d)",
128 			family == AF_INET ? "IPv4" : "IPv6",
129 			-errno);
130 		ret = -errno;
131 	}
132 
133 	return ret;
134 }
135 
connect_cb(int sock,struct http_request * req,void * user_data)136 static int connect_cb(int sock, struct http_request *req, void *user_data)
137 {
138 	LOG_INF("Websocket %d for %s connected.", sock, (char *)user_data);
139 
140 	return 0;
141 }
142 
how_much_to_send(size_t max_len)143 static size_t how_much_to_send(size_t max_len)
144 {
145 	size_t amount;
146 
147 	do {
148 		amount = sys_rand32_get() % max_len;
149 	} while (amount == 0U);
150 
151 	return amount;
152 }
153 
sendall_with_ws_api(int sock,const void * buf,size_t len)154 static ssize_t sendall_with_ws_api(int sock, const void *buf, size_t len)
155 {
156 	return websocket_send_msg(sock, buf, len, WEBSOCKET_OPCODE_DATA_TEXT,
157 				  true, true, SYS_FOREVER_MS);
158 }
159 
sendall_with_bsd_api(int sock,const void * buf,size_t len)160 static ssize_t sendall_with_bsd_api(int sock, const void *buf, size_t len)
161 {
162 	return send(sock, buf, len, 0);
163 }
164 
recv_data_wso_api(int sock,size_t amount,uint8_t * buf,size_t buf_len,const char * proto)165 static void recv_data_wso_api(int sock, size_t amount, uint8_t *buf,
166 			      size_t buf_len, const char *proto)
167 {
168 	uint64_t remaining = ULLONG_MAX;
169 	int total_read;
170 	uint32_t message_type;
171 	int ret, read_pos;
172 
173 	read_pos = 0;
174 	total_read = 0;
175 
176 	while (remaining > 0) {
177 		ret = websocket_recv_msg(sock, buf + read_pos,
178 					 buf_len - read_pos,
179 					 &message_type,
180 					 &remaining,
181 					 0);
182 		if (ret < 0) {
183 			if (ret == -EAGAIN) {
184 				k_sleep(K_MSEC(50));
185 				continue;
186 			}
187 
188 			LOG_DBG("%s connection closed while "
189 				"waiting (%d/%d)", proto, ret, errno);
190 			break;
191 		}
192 
193 		read_pos += ret;
194 		total_read += ret;
195 	}
196 
197 	if (remaining != 0 || total_read != amount ||
198 	    /* Do not check the final \n at the end of the msg */
199 	    memcmp(lorem_ipsum, buf, amount - 1) != 0) {
200 		LOG_ERR("%s data recv failure %zd/%d bytes (remaining %" PRId64 ")",
201 			proto, amount, total_read, remaining);
202 		LOG_HEXDUMP_DBG(buf, total_read, "received ws buf");
203 		LOG_HEXDUMP_DBG(lorem_ipsum, total_read, "sent ws buf");
204 	} else {
205 		LOG_DBG("%s recv %d bytes", proto, total_read);
206 	}
207 }
208 
recv_data_bsd_api(int sock,size_t amount,uint8_t * buf,size_t buf_len,const char * proto)209 static void recv_data_bsd_api(int sock, size_t amount, uint8_t *buf,
210 			      size_t buf_len, const char *proto)
211 {
212 	int remaining;
213 	int ret, read_pos;
214 
215 	remaining = amount;
216 	read_pos = 0;
217 
218 	while (remaining > 0) {
219 		ret = recv(sock, buf + read_pos, buf_len - read_pos, 0);
220 		if (ret <= 0) {
221 			if (errno == EAGAIN || errno == ETIMEDOUT) {
222 				k_sleep(K_MSEC(50));
223 				continue;
224 			}
225 
226 			LOG_DBG("%s connection closed while "
227 				"waiting (%d/%d)", proto, ret, errno);
228 			break;
229 		}
230 
231 		read_pos += ret;
232 		remaining -= ret;
233 	}
234 
235 	if (remaining != 0 ||
236 	    /* Do not check the final \n at the end of the msg */
237 	    memcmp(lorem_ipsum, buf, amount - 1) != 0) {
238 		LOG_ERR("%s data recv failure %zd/%d bytes (remaining %d)",
239 			proto, amount, read_pos, remaining);
240 		LOG_HEXDUMP_DBG(buf, read_pos, "received bsd buf");
241 		LOG_HEXDUMP_DBG(lorem_ipsum, read_pos, "sent bsd buf");
242 	} else {
243 		LOG_DBG("%s recv %d bytes", proto, read_pos);
244 	}
245 }
246 
send_and_wait_msg(int sock,size_t amount,const char * proto,uint8_t * buf,size_t buf_len)247 static bool send_and_wait_msg(int sock, size_t amount, const char *proto,
248 			      uint8_t *buf, size_t buf_len)
249 {
250 	static int count;
251 	int ret;
252 
253 	if (sock < 0) {
254 		return true;
255 	}
256 
257 	/* Terminate the sent data with \n so that we can use the
258 	 *      websocketd --port=9001 cat
259 	 * command in server side.
260 	 */
261 	memcpy(buf, lorem_ipsum, amount);
262 	buf[amount] = '\n';
263 
264 	/* Send every 2nd message using dedicated websocket API and generic
265 	 * BSD socket API. Real applications would not work like this but here
266 	 * we want to test both APIs. We also need to send the \n so add it
267 	 * here to amount variable.
268 	 */
269 	if (count % 2) {
270 		ret = sendall_with_ws_api(sock, buf, amount + 1);
271 	} else {
272 		ret = sendall_with_bsd_api(sock, buf, amount + 1);
273 	}
274 
275 	if (ret <= 0) {
276 		if (ret < 0) {
277 			LOG_ERR("%s failed to send data using %s (%d)", proto,
278 				(count % 2) ? "ws API" : "socket API", ret);
279 		} else {
280 			LOG_DBG("%s connection closed", proto);
281 		}
282 
283 		return false;
284 	} else {
285 		LOG_DBG("%s sent %d bytes", proto, ret);
286 	}
287 
288 	if (count % 2) {
289 		recv_data_wso_api(sock, amount + 1, buf, buf_len, proto);
290 	} else {
291 		recv_data_bsd_api(sock, amount + 1, buf, buf_len, proto);
292 	}
293 
294 	count++;
295 
296 	return true;
297 }
298 
main(void)299 int main(void)
300 {
301 	/* Just an example how to set extra headers */
302 	const char *extra_headers[] = {
303 		"Origin: http://foobar\r\n",
304 		NULL
305 	};
306 	int sock4 = -1, sock6 = -1;
307 	int websock4 = -1, websock6 = -1;
308 	int32_t timeout = 3 * MSEC_PER_SEC;
309 	struct sockaddr_in6 addr6;
310 	struct sockaddr_in addr4;
311 	size_t amount;
312 	int ret;
313 
314 	if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
315 		ret = tls_credential_add(CA_CERTIFICATE_TAG,
316 					 TLS_CREDENTIAL_CA_CERTIFICATE,
317 					 ca_certificate,
318 					 sizeof(ca_certificate));
319 		if (ret < 0) {
320 			LOG_ERR("Failed to register public certificate: %d",
321 				ret);
322 			k_sleep(K_FOREVER);
323 		}
324 	}
325 
326 	if (IS_ENABLED(CONFIG_NET_IPV4)) {
327 		(void)connect_socket(AF_INET, SERVER_ADDR4, SERVER_PORT,
328 				     &sock4, (struct sockaddr *)&addr4,
329 				     sizeof(addr4));
330 	}
331 
332 	if (IS_ENABLED(CONFIG_NET_IPV6)) {
333 		(void)connect_socket(AF_INET6, SERVER_ADDR6, SERVER_PORT,
334 				     &sock6, (struct sockaddr *)&addr6,
335 				     sizeof(addr6));
336 	}
337 
338 	if (sock4 < 0 && sock6 < 0) {
339 		LOG_ERR("Cannot create HTTP connection.");
340 		k_sleep(K_FOREVER);
341 	}
342 
343 	if (sock4 >= 0 && IS_ENABLED(CONFIG_NET_IPV4)) {
344 		struct websocket_request req;
345 
346 		memset(&req, 0, sizeof(req));
347 
348 		req.host = SERVER_ADDR4;
349 		req.url = "/";
350 		req.optional_headers = extra_headers;
351 		req.cb = connect_cb;
352 		req.tmp_buf = temp_recv_buf_ipv4;
353 		req.tmp_buf_len = sizeof(temp_recv_buf_ipv4);
354 
355 		websock4 = websocket_connect(sock4, &req, timeout, "IPv4");
356 		if (websock4 < 0) {
357 			LOG_ERR("Cannot connect to %s:%d", SERVER_ADDR4,
358 				SERVER_PORT);
359 			close(sock4);
360 		}
361 	}
362 
363 	if (sock6 >= 0 && IS_ENABLED(CONFIG_NET_IPV6)) {
364 		struct websocket_request req;
365 
366 		memset(&req, 0, sizeof(req));
367 
368 		req.host = SERVER_ADDR6;
369 		req.url = "/";
370 		req.optional_headers = extra_headers;
371 		req.cb = connect_cb;
372 		req.tmp_buf = temp_recv_buf_ipv6;
373 		req.tmp_buf_len = sizeof(temp_recv_buf_ipv6);
374 
375 		websock6 = websocket_connect(sock6, &req, timeout, "IPv6");
376 		if (websock6 < 0) {
377 			LOG_ERR("Cannot connect to [%s]:%d", SERVER_ADDR6,
378 				SERVER_PORT);
379 			close(sock6);
380 		}
381 	}
382 
383 	if (websock4 < 0 && websock6 < 0) {
384 		LOG_ERR("No IPv4 or IPv6 connectivity");
385 		k_sleep(K_FOREVER);
386 	}
387 
388 	LOG_INF("Websocket IPv4 %d IPv6 %d", websock4, websock6);
389 
390 	while (1) {
391 		amount = how_much_to_send(ipsum_len);
392 
393 		if (websock4 >= 0 &&
394 		    !send_and_wait_msg(websock4, amount, "IPv4",
395 				       recv_buf_ipv4, sizeof(recv_buf_ipv4))) {
396 			break;
397 		}
398 
399 		if (websock6 >= 0 &&
400 		    !send_and_wait_msg(websock6, amount, "IPv6",
401 				       recv_buf_ipv6, sizeof(recv_buf_ipv6))) {
402 			break;
403 		}
404 
405 		k_sleep(K_MSEC(250));
406 	}
407 
408 	if (websock4 >= 0) {
409 		close(websock4);
410 	}
411 
412 	if (sock4 >= 0) {
413 		close(sock4);
414 	}
415 
416 	if (websock6 >= 0) {
417 		close(websock6);
418 	}
419 
420 	if (sock6 >= 0) {
421 		close(sock6);
422 	}
423 
424 	k_sleep(K_FOREVER);
425 	return 0;
426 }
427