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