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