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 registered
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