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