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