1 /*
2 * Copyright (c) 2023 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(conn_mgr_conn, CONFIG_NET_CONNECTION_MANAGER_LOG_LEVEL);
9
10 #include <zephyr/net/net_if.h>
11 #include <zephyr/sys/iterable_sections.h>
12 #include <zephyr/net/conn_mgr_monitor.h>
13 #include <zephyr/net/conn_mgr_connectivity.h>
14 #include <zephyr/net/conn_mgr_connectivity_impl.h>
15 #include "conn_mgr_private.h"
16
conn_mgr_if_connect(struct net_if * iface)17 int conn_mgr_if_connect(struct net_if *iface)
18 {
19 struct conn_mgr_conn_binding *binding;
20 struct conn_mgr_conn_api *api;
21 int status;
22
23 LOG_DBG("iface %p connect", iface);
24
25 binding = conn_mgr_if_get_binding(iface);
26 if (!binding) {
27 return -ENOTSUP;
28 }
29
30 api = binding->impl->api;
31 if (!api->connect) {
32 return -ENOTSUP;
33 }
34
35 conn_mgr_binding_lock(binding);
36
37 if (!net_if_is_admin_up(iface)) {
38 status = net_if_up(iface);
39 if (status) {
40 goto out;
41 }
42 }
43
44 conn_mgr_if_set_flag(iface, CONN_MGR_IF_DISCONNECTING, false);
45
46 status = api->connect(binding);
47
48 out:
49 conn_mgr_binding_unlock(binding);
50
51 return status;
52 }
53
conn_mgr_if_disconnect(struct net_if * iface)54 int conn_mgr_if_disconnect(struct net_if *iface)
55 {
56 struct conn_mgr_conn_binding *binding;
57 struct conn_mgr_conn_api *api;
58 int status = 0;
59
60 LOG_DBG("iface %p disconnect", iface);
61
62 binding = conn_mgr_if_get_binding(iface);
63 if (!binding) {
64 return -ENOTSUP;
65 }
66
67 api = binding->impl->api;
68 if (!api->disconnect) {
69 return -ENOTSUP;
70 }
71
72 conn_mgr_binding_lock(binding);
73
74 if (!net_if_is_admin_up(iface)) {
75 goto out;
76 }
77
78 conn_mgr_if_set_flag(iface, CONN_MGR_IF_DISCONNECTING, true);
79
80 status = api->disconnect(binding);
81
82 out:
83 conn_mgr_binding_unlock(binding);
84
85 return status;
86 }
87
conn_mgr_if_is_bound(struct net_if * iface)88 bool conn_mgr_if_is_bound(struct net_if *iface)
89 {
90 struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface);
91
92 return binding != NULL;
93 }
94
conn_mgr_if_get_opt(struct net_if * iface,int optname,void * optval,size_t * optlen)95 int conn_mgr_if_get_opt(struct net_if *iface, int optname, void *optval, size_t *optlen)
96 {
97 struct conn_mgr_conn_binding *binding;
98 struct conn_mgr_conn_api *api;
99 int status;
100
101 if (!optlen) {
102 return -EINVAL;
103 }
104
105 if (!optval) {
106 *optlen = 0;
107 return -EINVAL;
108 }
109
110 binding = conn_mgr_if_get_binding(iface);
111 if (!binding) {
112 *optlen = 0;
113 return -ENOTSUP;
114 }
115
116 api = binding->impl->api;
117 if (!api->get_opt) {
118 *optlen = 0;
119 return -ENOTSUP;
120 }
121
122 conn_mgr_binding_lock(binding);
123
124 status = api->get_opt(binding, optname, optval, optlen);
125
126 conn_mgr_binding_unlock(binding);
127
128 return status;
129 }
130
conn_mgr_if_set_opt(struct net_if * iface,int optname,const void * optval,size_t optlen)131 int conn_mgr_if_set_opt(struct net_if *iface, int optname, const void *optval, size_t optlen)
132 {
133 struct conn_mgr_conn_binding *binding;
134 struct conn_mgr_conn_api *api;
135 int status;
136
137 if (!optval) {
138 return -EINVAL;
139 }
140
141 binding = conn_mgr_if_get_binding(iface);
142 if (!binding) {
143 return -ENOTSUP;
144 }
145
146 api = binding->impl->api;
147 if (!api->set_opt) {
148 return -ENOTSUP;
149 }
150
151 conn_mgr_binding_lock(binding);
152
153 status = api->set_opt(binding, optname, optval, optlen);
154
155 conn_mgr_binding_unlock(binding);
156
157 return status;
158 }
159
conn_mgr_if_set_flag(struct net_if * iface,enum conn_mgr_if_flag flag,bool value)160 int conn_mgr_if_set_flag(struct net_if *iface, enum conn_mgr_if_flag flag, bool value)
161 {
162 struct conn_mgr_conn_binding *binding;
163
164 if (flag >= CONN_MGR_NUM_IF_FLAGS) {
165 return -EINVAL;
166 }
167
168 binding = conn_mgr_if_get_binding(iface);
169 if (!binding) {
170 return -ENOTSUP;
171 }
172
173 conn_mgr_binding_set_flag(binding, flag, value);
174
175 return 0;
176 }
177
conn_mgr_if_get_flag(struct net_if * iface,enum conn_mgr_if_flag flag)178 bool conn_mgr_if_get_flag(struct net_if *iface, enum conn_mgr_if_flag flag)
179 {
180 struct conn_mgr_conn_binding *binding;
181
182 if (flag >= CONN_MGR_NUM_IF_FLAGS) {
183 return false;
184 }
185
186 binding = conn_mgr_if_get_binding(iface);
187 if (!binding) {
188 return false;
189 }
190
191 return conn_mgr_binding_get_flag(binding, flag);
192 }
193
conn_mgr_if_get_timeout(struct net_if * iface)194 int conn_mgr_if_get_timeout(struct net_if *iface)
195 {
196 struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface);
197 int value;
198
199 if (!binding) {
200 return false;
201 }
202
203 conn_mgr_binding_lock(binding);
204
205 value = binding->timeout;
206
207 conn_mgr_binding_unlock(binding);
208
209 return value;
210 }
211
conn_mgr_if_set_timeout(struct net_if * iface,int timeout)212 int conn_mgr_if_set_timeout(struct net_if *iface, int timeout)
213 {
214 struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface);
215
216 if (!binding) {
217 return -ENOTSUP;
218 }
219
220 conn_mgr_binding_lock(binding);
221
222 binding->timeout = timeout;
223
224 conn_mgr_binding_unlock(binding);
225
226 return 0;
227 }
228
229 /* Automated behavior handling */
230
231 /**
232 * @brief Perform automated behaviors in response to ifaces going admin-up.
233 *
234 * @param iface - The iface which became admin-up.
235 */
conn_mgr_conn_handle_iface_admin_up(struct net_if * iface)236 static void conn_mgr_conn_handle_iface_admin_up(struct net_if *iface)
237 {
238 int err;
239
240 /* Ignore ifaces that don't have connectivity implementations */
241 if (!conn_mgr_if_is_bound(iface)) {
242 return;
243 }
244
245 /* Ignore ifaces for which auto-connect is disabled */
246 if (conn_mgr_if_get_flag(iface, CONN_MGR_IF_NO_AUTO_CONNECT)) {
247 return;
248 }
249
250 /* Otherwise, automatically instruct the iface to connect */
251 err = conn_mgr_if_connect(iface);
252 if (err < 0) {
253 NET_ERR("iface auto-connect failed: %d", err);
254 }
255 }
256
257 /**
258 * @brief Take the provided iface admin-down.
259 *
260 * Called automatically by conn_mgr when an iface has lost connection and will not attempt to
261 * regain it.
262 *
263 * @param iface - The iface to take admin-down
264 */
conn_mgr_conn_if_auto_admin_down(struct net_if * iface)265 static void conn_mgr_conn_if_auto_admin_down(struct net_if *iface)
266 {
267 /* NOTE: This will be double-fired for ifaces that are both non-persistent
268 * and are being directly requested to disconnect, since both of these conditions
269 * separately trigger conn_mgr_conn_if_auto_admin_down.
270 *
271 * This is fine, because net_if_down is idempotent, but if you are adding other
272 * behaviors to this function, bear it in mind.
273 */
274
275 /* Ignore ifaces that don't have connectivity implementations */
276 if (!conn_mgr_if_is_bound(iface)) {
277 return;
278 }
279
280 /* Take the iface admin-down if AUTO_DOWN is enabled */
281 if (IS_ENABLED(CONFIG_NET_CONNECTION_MANAGER_AUTO_IF_DOWN) &&
282 !conn_mgr_if_get_flag(iface, CONN_MGR_IF_NO_AUTO_DOWN)) {
283 net_if_down(iface);
284 }
285 }
286
287 /**
288 * @brief Perform automated behaviors in response to any iface that loses oper-up state.
289 *
290 * This is how conn_mgr_conn automatically takes such ifaces admin-down if they are not persistent.
291 *
292 * @param iface - The iface which lost oper-up state.
293 */
conn_mgr_conn_handle_iface_down(struct net_if * iface)294 static void conn_mgr_conn_handle_iface_down(struct net_if *iface)
295 {
296 /* Ignore ifaces that don't have connectivity implementations */
297 if (!conn_mgr_if_is_bound(iface)) {
298 return;
299 }
300
301 /* If the iface is persistent, we expect it to try to reconnect, unless
302 * disconnect was explicitly initiated by the application.
303 */
304 if (conn_mgr_if_get_flag(iface, CONN_MGR_IF_PERSISTENT) &&
305 !conn_mgr_if_get_flag(iface, CONN_MGR_IF_DISCONNECTING)) {
306 return;
307 }
308
309 conn_mgr_if_set_flag(iface, CONN_MGR_IF_DISCONNECTING, false);
310
311 /* Otherwise, we do not expect the iface to reconnect, and we should call
312 * conn_mgr_conn_if_auto_admin_down
313 */
314 conn_mgr_conn_if_auto_admin_down(iface);
315 }
316
317 static struct net_mgmt_event_callback conn_mgr_conn_iface_cb;
conn_mgr_conn_iface_handler(struct net_mgmt_event_callback * cb,uint64_t mgmt_event,struct net_if * iface)318 static void conn_mgr_conn_iface_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event,
319 struct net_if *iface)
320 {
321 if ((mgmt_event & CONN_MGR_CONN_IFACE_EVENTS_MASK) != mgmt_event) {
322 return;
323 }
324
325 switch (mgmt_event) {
326 case NET_EVENT_IF_DOWN:
327 conn_mgr_conn_handle_iface_down(iface);
328 break;
329 case NET_EVENT_IF_ADMIN_UP:
330 conn_mgr_conn_handle_iface_admin_up(iface);
331 break;
332 }
333 }
334
335 static struct net_mgmt_event_callback conn_mgr_conn_self_cb;
conn_mgr_conn_self_handler(struct net_mgmt_event_callback * cb,uint64_t mgmt_event,struct net_if * iface)336 static void conn_mgr_conn_self_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event,
337 struct net_if *iface)
338 {
339 if ((mgmt_event & CONN_MGR_CONN_SELF_EVENTS_MASK) != mgmt_event) {
340 return;
341 }
342
343 switch (NET_MGMT_GET_COMMAND(mgmt_event)) {
344 case NET_EVENT_CONN_CMD_IF_FATAL_ERROR:
345 if (cb->info) {
346 NET_ERR("Fatal connectivity error on iface %d (%p). Reason: %d.",
347 net_if_get_by_iface(iface), iface, *((int *)cb->info)
348 );
349 } else {
350 NET_ERR("Unknown fatal connectivity error on iface %d (%p).",
351 net_if_get_by_iface(iface), iface
352 );
353 }
354 __fallthrough;
355 case NET_EVENT_CONN_CMD_IF_TIMEOUT:
356 /* If a timeout or fatal error occurs, we do not expect the iface to try to
357 * reconnect, so call conn_mgr_conn_if_auto_admin_down.
358 */
359 conn_mgr_conn_if_auto_admin_down(iface);
360 break;
361 }
362
363 }
364
conn_mgr_conn_init(void)365 void conn_mgr_conn_init(void)
366 {
367 /* Initialize connectivity bindings. */
368 STRUCT_SECTION_FOREACH(conn_mgr_conn_binding, binding) {
369 if (!(binding->impl->api)) {
370 LOG_ERR("Connectivity implementation has NULL API, and will be treated as "
371 "non-existent.");
372 } else if (binding->impl->api->init) {
373 conn_mgr_binding_lock(binding);
374
375 /* Set initial default values for binding state */
376
377 binding->timeout = CONN_MGR_IF_NO_TIMEOUT;
378
379 /* Call binding initializer */
380
381 binding->impl->api->init(binding);
382
383 conn_mgr_binding_unlock(binding);
384 }
385 }
386
387 /* Set up event listeners for automated behaviors */
388 net_mgmt_init_event_callback(&conn_mgr_conn_iface_cb, conn_mgr_conn_iface_handler,
389 CONN_MGR_CONN_IFACE_EVENTS_MASK);
390 net_mgmt_add_event_callback(&conn_mgr_conn_iface_cb);
391
392 net_mgmt_init_event_callback(&conn_mgr_conn_self_cb, conn_mgr_conn_self_handler,
393 CONN_MGR_CONN_SELF_EVENTS_MASK);
394 net_mgmt_add_event_callback(&conn_mgr_conn_self_cb);
395
396 /* Trigger any initial automated behaviors for ifaces */
397 STRUCT_SECTION_FOREACH(conn_mgr_conn_binding, binding) {
398 if (binding->impl->api) {
399 /* We need to fire conn_mgr_conn_handle_iface_admin_up for any
400 * (connectivity-enabled) ifaces that went admin-up before we registered
401 * the event callback that typically handles this.
402 */
403 if (net_if_is_admin_up(binding->iface)) {
404 conn_mgr_conn_handle_iface_admin_up(binding->iface);
405 }
406 }
407 }
408 }
409
410 enum conn_mgr_conn_all_if_oper {
411 ALL_IF_UP,
412 ALL_IF_DOWN,
413 ALL_IF_CONNECT,
414 ALL_IF_DISCONNECT
415 };
416
417 struct conn_mgr_conn_all_if_ctx {
418 bool skip_ignored;
419 enum conn_mgr_conn_all_if_oper operation;
420 int status;
421 };
422
423 /* Per-iface callback for conn_mgr_conn_all_if_up */
conn_mgr_conn_all_if_cb(struct net_if * iface,void * user_data)424 static void conn_mgr_conn_all_if_cb(struct net_if *iface, void *user_data)
425 {
426 int status = 0;
427 struct conn_mgr_conn_all_if_ctx *context = (struct conn_mgr_conn_all_if_ctx *)user_data;
428
429 /* Skip ignored ifaces if so desired */
430 if (context->skip_ignored && conn_mgr_is_iface_ignored(iface)) {
431 return;
432 }
433
434 /* Perform the requested operation */
435 switch (context->operation) {
436 case ALL_IF_UP:
437 /* Do not take iface admin up if it already is. */
438 if (net_if_is_admin_up(iface)) {
439 return;
440 }
441
442 status = net_if_up(iface);
443 break;
444 case ALL_IF_DOWN:
445 /* Do not take iface admin down if it already is. */
446 if (!net_if_is_admin_up(iface)) {
447 return;
448 }
449
450 status = net_if_down(iface);
451 break;
452 case ALL_IF_CONNECT:
453 /* Connect operation only supported if iface is bound */
454 if (!conn_mgr_if_is_bound(iface)) {
455 return;
456 }
457
458 status = conn_mgr_if_connect(iface);
459 break;
460 case ALL_IF_DISCONNECT:
461 /* Disconnect operation only supported if iface is bound */
462 if (!conn_mgr_if_is_bound(iface)) {
463 return;
464 }
465
466 status = conn_mgr_if_disconnect(iface);
467 break;
468 }
469
470 if (status == 0) {
471 return;
472 }
473
474 if (context->status == 0) {
475 context->status = status;
476 }
477
478 NET_ERR("%s failed for iface %d (%p). Error: %d",
479 context->operation == ALL_IF_UP ? "net_if_up" :
480 context->operation == ALL_IF_DOWN ? "net_if_down" :
481 context->operation == ALL_IF_CONNECT ? "conn_mgr_if_connect" :
482 context->operation == ALL_IF_DISCONNECT ? "conn_mgr_if_disconnect" :
483 "invalid",
484 net_if_get_by_iface(iface), iface, status
485 );
486 }
487
conn_mgr_all_if_up(bool skip_ignored)488 int conn_mgr_all_if_up(bool skip_ignored)
489 {
490 struct conn_mgr_conn_all_if_ctx context = {
491 .operation = ALL_IF_UP,
492 .skip_ignored = skip_ignored,
493 .status = 0
494 };
495
496 net_if_foreach(conn_mgr_conn_all_if_cb, &context);
497
498 return context.status;
499 }
500
conn_mgr_all_if_down(bool skip_ignored)501 int conn_mgr_all_if_down(bool skip_ignored)
502 {
503 struct conn_mgr_conn_all_if_ctx context = {
504 .operation = ALL_IF_DOWN,
505 .skip_ignored = skip_ignored,
506 .status = 0
507 };
508
509 net_if_foreach(conn_mgr_conn_all_if_cb, &context);
510
511 return context.status;
512 }
513
conn_mgr_all_if_connect(bool skip_ignored)514 int conn_mgr_all_if_connect(bool skip_ignored)
515 {
516 /* First, take all ifaces up.
517 * All bound ifaces will do this automatically when connect is called, but non-bound ifaces
518 * won't, so we must request it explicitly.
519 */
520 struct conn_mgr_conn_all_if_ctx context = {
521 .operation = ALL_IF_UP,
522 .skip_ignored = skip_ignored,
523 .status = 0
524 };
525
526 net_if_foreach(conn_mgr_conn_all_if_cb, &context);
527
528 /* Now connect all ifaces.
529 * We are delibarately not resetting context.status between these two calls so that
530 * the first nonzero status code encountered between the two of them is what is returned.
531 */
532 context.operation = ALL_IF_CONNECT;
533 net_if_foreach(conn_mgr_conn_all_if_cb, &context);
534
535 return context.status;
536 }
537
conn_mgr_all_if_disconnect(bool skip_ignored)538 int conn_mgr_all_if_disconnect(bool skip_ignored)
539 {
540 struct conn_mgr_conn_all_if_ctx context = {
541 .operation = ALL_IF_DISCONNECT,
542 .skip_ignored = skip_ignored,
543 .status = 0
544 };
545
546 net_if_foreach(conn_mgr_conn_all_if_cb, &context);
547
548 return context.status;
549 }
550