1 /*
2  * Copyright (c) 2024, Nordic Semiconductor
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/net/tls_credentials.h>
11 #include <zephyr/net/http/server.h>
12 #include <zephyr/net/http/service.h>
13 #include <zephyr/net/net_ip.h>
14 #include <zephyr/net/socket.h>
15 #include <zephyr/net/websocket.h>
16 #include <zephyr/net/net_mgmt.h>
17 #include <zephyr/net/net_stats.h>
18 #include <zephyr/init.h>
19 
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_DECLARE(net_http_server_sample, LOG_LEVEL_DBG);
22 
23 #if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) || defined(CONFIG_COVERAGE_GCOV)
24 #define STACK_SIZE 4096
25 #else
26 #define STACK_SIZE 2048
27 #endif
28 
29 #if defined(CONFIG_NET_TC_THREAD_COOPERATIVE)
30 #define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1)
31 #else
32 #define THREAD_PRIORITY K_PRIO_PREEMPT(8)
33 #endif
34 
35 #if defined(CONFIG_USERSPACE)
36 #include <zephyr/app_memory/app_memdomain.h>
37 extern struct k_mem_partition app_partition;
38 extern struct k_mem_domain app_domain;
39 #define APP_BMEM K_APP_BMEM(app_partition)
40 #define APP_DMEM K_APP_DMEM(app_partition)
41 #else
42 #define APP_BMEM
43 #define APP_DMEM
44 #endif
45 
46 #define MAX_CLIENT_QUEUE CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS
47 #define RECV_BUFFER_SIZE 1280
48 
49 struct ws_netstats_ctx {
50 	int sock;
51 	struct k_work_delayable work;
52 };
53 
54 K_THREAD_STACK_ARRAY_DEFINE(ws_handler_stack,
55 			    CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS,
56 			    STACK_SIZE);
57 static struct k_thread ws_handler_thread[CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS];
58 static APP_BMEM bool ws_handler_in_use[CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS];
59 
60 static struct ws_netstats_ctx netstats_ctx[CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS];
61 
62 static struct data {
63 	int sock;
64 	uint32_t counter;
65 	uint32_t bytes_received;
66 	struct pollfd fds[1];
67 	char recv_buffer[RECV_BUFFER_SIZE];
68 } config[CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS] = {
69 	[0 ... (CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS - 1)] = {
70 		.sock = -1,
71 		.fds[0].fd = -1,
72 	}
73 };
74 
get_free_echo_slot(struct data * cfg)75 static int get_free_echo_slot(struct data *cfg)
76 {
77 	for (int i = 0; i < CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS; i++) {
78 		if (cfg[i].sock < 0) {
79 			return i;
80 		}
81 	}
82 
83 	return -1;
84 }
85 
get_free_netstats_slot(void)86 static int get_free_netstats_slot(void)
87 {
88 	for (int i = 0; i < CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS; i++) {
89 		if (netstats_ctx[i].sock < 0) {
90 			return i;
91 		}
92 	}
93 
94 	return -1;
95 }
96 
sendall(int sock,const void * buf,size_t len)97 static ssize_t sendall(int sock, const void *buf, size_t len)
98 {
99 	while (len) {
100 		ssize_t out_len = send(sock, buf, len, 0);
101 
102 		if (out_len < 0) {
103 			return out_len;
104 		}
105 		buf = (const char *)buf + out_len;
106 		len -= out_len;
107 	}
108 
109 	return 0;
110 }
111 
ws_echo_handler(void * ptr1,void * ptr2,void * ptr3)112 static void ws_echo_handler(void *ptr1, void *ptr2, void *ptr3)
113 {
114 	int slot = POINTER_TO_INT(ptr1);
115 	struct data *cfg = ptr2;
116 	bool *in_use = ptr3;
117 	int offset = 0;
118 	int received;
119 	int client;
120 	int ret;
121 
122 	client = cfg->sock;
123 
124 	cfg->fds[0].fd = client;
125 	cfg->fds[0].events = POLLIN;
126 
127 	/* In this example, we start to receive data from the websocket
128 	 * and send it back to the client. Note that we could either use
129 	 * the BSD socket interface if we do not care about Websocket
130 	 * specific packets, or we could use the websocket_{send/recv}_msg()
131 	 * function to send websocket specific data.
132 	 */
133 	while (true) {
134 		if (poll(cfg->fds, 1, -1) < 0) {
135 			LOG_ERR("Error in poll:%d", errno);
136 			continue;
137 		}
138 
139 		if (cfg->fds[0].fd < 0) {
140 			continue;
141 		}
142 
143 		if (cfg->fds[0].revents & ZSOCK_POLLHUP) {
144 			LOG_DBG("Client #%d has disconnected", client);
145 			break;
146 		}
147 
148 		received = recv(client,
149 				cfg->recv_buffer + offset,
150 				sizeof(cfg->recv_buffer) - offset,
151 				0);
152 
153 		if (received == 0) {
154 			/* Connection closed */
155 			LOG_INF("[%d] Connection closed", slot);
156 			break;
157 		} else if (received < 0) {
158 			/* Socket error */
159 			LOG_ERR("[%d] Connection error %d", slot, errno);
160 			break;
161 		}
162 
163 		cfg->bytes_received += received;
164 		offset += received;
165 
166 #if !defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
167 		/* To prevent fragmentation of the response, reply only if
168 		 * buffer is full or there is no more data to read
169 		 */
170 		if (offset == sizeof(cfg->recv_buffer) ||
171 		    (recv(client, cfg->recv_buffer + offset,
172 			  sizeof(cfg->recv_buffer) - offset,
173 			  MSG_PEEK | MSG_DONTWAIT) < 0 &&
174 		     (errno == EAGAIN || errno == EWOULDBLOCK))) {
175 #endif
176 			ret = sendall(client, cfg->recv_buffer, offset);
177 			if (ret < 0) {
178 				LOG_ERR("[%d] Failed to send data, closing socket",
179 					slot);
180 				break;
181 			}
182 
183 			LOG_DBG("[%d] Received and replied with %d bytes",
184 				slot, offset);
185 
186 			if (++cfg->counter % 1000 == 0U) {
187 				LOG_INF("[%d] Sent %u packets", slot, cfg->counter);
188 			}
189 
190 			offset = 0;
191 #if !defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
192 		}
193 #endif
194 	}
195 
196 	*in_use = false;
197 
198 	(void)websocket_unregister(client);
199 
200 	cfg->sock = -1;
201 }
202 
netstats_collect(char * buf,size_t maxlen)203 static int netstats_collect(char *buf, size_t maxlen)
204 {
205 	int ret;
206 	struct net_stats data;
207 	uint32_t bytes_recv = 0;
208 	uint32_t bytes_sent = 0;
209 	uint32_t ipv6_recv = 0;
210 	uint32_t ipv6_sent = 0;
211 	uint32_t ipv4_recv = 0;
212 	uint32_t ipv4_sent = 0;
213 	uint32_t tcp_recv = 0;
214 	uint32_t tcp_sent = 0;
215 
216 	net_mgmt(NET_REQUEST_STATS_GET_ALL, NULL, &data, sizeof(data));
217 
218 	const char *net_stats_json_template = "{"
219 					      "\"bytes_recv\":%u,"
220 					      "\"bytes_sent\":%u,"
221 					      "\"ipv6_pkt_recv\":%u,"
222 					      "\"ipv6_pkt_sent\":%u,"
223 					      "\"ipv4_pkt_recv\":%u,"
224 					      "\"ipv4_pkt_sent\":%u,"
225 					      "\"tcp_bytes_recv\":%u,"
226 					      "\"tcp_bytes_sent\":%u"
227 					      "}";
228 
229 	bytes_recv = data.bytes.received;
230 	bytes_sent = data.bytes.sent;
231 #if defined(CONFIG_NET_STATISTICS_IPV6)
232 	ipv6_recv = data.ipv6.recv;
233 	ipv6_sent = data.ipv6.sent;
234 #endif
235 #if defined(CONFIG_NET_STATISTICS_IPV4)
236 	ipv4_recv = data.ipv4.recv;
237 	ipv4_sent = data.ipv4.sent;
238 #endif
239 #if defined(CONFIG_NET_STATISTICS_TCP)
240 	tcp_recv = data.tcp.bytes.received;
241 	tcp_sent = data.tcp.bytes.sent;
242 #endif
243 
244 	ret = snprintf(buf, maxlen, net_stats_json_template, bytes_recv, bytes_sent, ipv6_recv,
245 		       ipv6_sent, ipv4_recv, ipv4_sent, tcp_recv, tcp_sent);
246 	if (ret >= maxlen) {
247 		LOG_ERR("Net stats do not fit in buffer");
248 		return -ENOSPC;
249 	}
250 
251 	return ret;
252 }
253 
netstats_handler(struct k_work * work)254 static void netstats_handler(struct k_work *work)
255 {
256 	int ret;
257 	static char tx_buf[256];
258 	struct k_work_delayable *dwork = k_work_delayable_from_work(work);
259 	struct ws_netstats_ctx *ctx = CONTAINER_OF(dwork, struct ws_netstats_ctx, work);
260 
261 	ret = netstats_collect(tx_buf, sizeof(tx_buf));
262 	if (ret < 0) {
263 		LOG_ERR("Unable to collect network statistics, err %d", ret);
264 		goto unregister;
265 	}
266 
267 	ret = websocket_send_msg(ctx->sock, tx_buf, ret, WEBSOCKET_OPCODE_DATA_TEXT, false, true,
268 				 SYS_FOREVER_MS);
269 	if (ret < 0) {
270 		LOG_INF("Couldn't send websocket msg (%d), closing connection", ret);
271 		goto unregister;
272 	}
273 
274 	ret = k_work_reschedule(&ctx->work, K_MSEC(CONFIG_NET_SAMPLE_WEBSOCKET_STATS_INTERVAL));
275 	if (ret < 0) {
276 		LOG_ERR("Failed to schedule netstats work, err %d", ret);
277 		goto unregister;
278 	}
279 
280 	return;
281 
282 unregister:
283 	(void)websocket_unregister(ctx->sock);
284 	ctx->sock = -1;
285 }
286 
ws_netstats_init(void)287 int ws_netstats_init(void)
288 {
289 	for (int i = 0; i < CONFIG_NET_SAMPLE_NUM_WEBSOCKET_HANDLERS; i++) {
290 		netstats_ctx[i].sock = -1;
291 		k_work_init_delayable(&netstats_ctx[i].work, netstats_handler);
292 	}
293 
294 	return 0;
295 }
296 SYS_INIT(ws_netstats_init, APPLICATION, 0);
297 
ws_echo_setup(int ws_socket,void * user_data)298 int ws_echo_setup(int ws_socket, void *user_data)
299 {
300 	int slot;
301 
302 	slot = get_free_echo_slot(config);
303 	if (slot < 0) {
304 		LOG_ERR("Cannot accept more connections");
305 		/* The caller will close the connection in this case */
306 		return -ENOENT;
307 	}
308 
309 	config[slot].sock = ws_socket;
310 
311 	LOG_INF("[%d] Accepted a Websocket connection", slot);
312 
313 	k_thread_create(&ws_handler_thread[slot],
314 			ws_handler_stack[slot],
315 			K_THREAD_STACK_SIZEOF(ws_handler_stack[slot]),
316 			ws_echo_handler,
317 			INT_TO_POINTER(slot), &config[slot], &ws_handler_in_use[slot],
318 			THREAD_PRIORITY,
319 			IS_ENABLED(CONFIG_USERSPACE) ? K_USER |
320 						       K_INHERIT_PERMS : 0,
321 			K_NO_WAIT);
322 
323 	if (IS_ENABLED(CONFIG_THREAD_NAME)) {
324 #define MAX_NAME_LEN sizeof("ws[xx]")
325 		char name[MAX_NAME_LEN];
326 
327 		snprintk(name, sizeof(name), "ws[%d]", slot);
328 		k_thread_name_set(&ws_handler_thread[slot], name);
329 	}
330 
331 	return 0;
332 }
333 
ws_netstats_setup(int ws_socket,void * user_data)334 int ws_netstats_setup(int ws_socket, void *user_data)
335 {
336 	int ret;
337 	int slot;
338 
339 	slot = get_free_netstats_slot();
340 	if (slot < 0) {
341 		LOG_ERR("Cannot accept more netstats websocket connections");
342 		return -ENOENT;
343 	}
344 
345 	netstats_ctx[slot].sock = ws_socket;
346 
347 	ret = k_work_reschedule(&netstats_ctx[slot].work, K_NO_WAIT);
348 	if (ret < 0) {
349 		LOG_ERR("Failed to schedule netstats work, err %d", ret);
350 		return ret;
351 	}
352 
353 	LOG_INF("Accepted websocket connection for net stats");
354 	return 0;
355 }
356