blob: 318134af73a32a8304668d3a0b7cdf025d398b99 [file] [log] [blame]
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +01001
2/* network device (interface) functions.
Vadim Yanitskiy03590fc2023-05-18 17:22:26 +07003 * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +01004 * Author: Pau Espin Pedrol <pespin@sysmocom.de>
5 *
6 * All Rights Reserved
7 *
8 * SPDX-License-Identifier: GPL-2.0+
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 */
21
22#include "config.h"
23
24/*! \addtogroup netdev
25 * @{
26 * network device (interface) convenience functions
27 *
28 * \file netdev.c
29 *
30 * Example lifecycle use of the API:
31 *
32 * struct osmo_sockaddr_str osa_str = {};
33 * struct osmo_sockaddr osa = {};
34 *
35 * // Allocate object:
36 * struct osmo_netdev *netdev = osmo_netdev_alloc(parent_talloc_ctx, name);
37 * OSMO_ASSERT(netdev);
38 *
39 * // Configure object (before registration):
40 * rc = osmo_netdev_set_netns_name(netdev, "some_netns_name_or_null");
41 * rc = osmo_netdev_set_ifindex(netdev, if_nametoindex("eth0"));
42 *
43 * // Register object:
44 * rc = osmo_netdev_register(netdev);
45 * // The network interface is now being monitored and the network interface
46 * // can be operated (see below)
47 *
48 * // Add a local IPv4 address:
49 * rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1");
50 * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
51 * rc = osmo_netdev_add_addr(netdev, &osa, 24);
52 *
53 * // Bring network interface up:
54 * rc = osmo_netdev_ifupdown(netdev, true);
55 *
56 * // Add default route (0.0.0.0/0):
57 * rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0");
58 * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
59 * rc = osmo_netdev_add_route(netdev, &osa, 0, NULL);
60 *
61 * // Unregister (can be freed directly too):
62 * rc = osmo_netdev_unregister(netdev);
63 * // Free the object:
64 * osmo_netdev_free(netdev);
65 */
66
67#if (!EMBEDDED)
68
69#include <stdio.h>
70#include <stdlib.h>
71#include <stdio.h>
72#include <fcntl.h>
73#include <unistd.h>
74#include <errno.h>
75#include <ifaddrs.h>
76#include <sys/types.h>
77#include <sys/time.h>
78#include <sys/ioctl.h>
79#include <sys/socket.h>
80#include <sys/stat.h>
81#include <sys/time.h>
82#include <net/if.h>
83#include <net/route.h>
84
85#if defined(__linux__)
86#include <linux/if_link.h>
87#include <linux/rtnetlink.h>
88#else
89#error "Unknown platform!"
90#endif
91
92#include <osmocom/core/utils.h>
93#include <osmocom/core/select.h>
94#include <osmocom/core/linuxlist.h>
95#include <osmocom/core/logging.h>
96#include <osmocom/core/socket.h>
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +010097#include <osmocom/core/netns.h>
98#include <osmocom/core/netdev.h>
99
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100100#if ENABLE_LIBMNL
101#include <osmocom/core/mnl.h>
102#endif
103
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100104#define IFINDEX_UNUSED 0
105
106#define LOGNETDEV(netdev, lvl, fmt, args ...) \
107 LOGP(DLGLOBAL, lvl, "NETDEV(%s,if=%s/%u,ns=%s): " fmt, \
108 (netdev)->name, osmo_netdev_get_dev_name(netdev) ? : "", \
109 (netdev)->ifindex, (netdev)->netns_name ? : "", ## args)
110
111static struct llist_head g_netdev_netns_ctx_list = LLIST_HEAD_INIT(g_netdev_netns_ctx_list);
112static struct llist_head g_netdev_list = LLIST_HEAD_INIT(g_netdev_list);
113
114/* One per netns, shared by all osmo_netdev in a given netns: */
115struct netdev_netns_ctx {
116 struct llist_head entry; /* entry in g_netdev_netns_ctx_list */
117 unsigned int refcount; /* Number of osmo_netdev currently registered on this netns */
118 const char *netns_name; /* default netns has empty string "" (never NULL!) */
119 int netns_fd; /* FD to the netns with name "netns_name" above */
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100120#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100121 struct osmo_mnl *omnl;
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100122#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100123};
124
125static struct netdev_netns_ctx *netdev_netns_ctx_alloc(void *ctx, const char *netns_name)
126{
127 struct netdev_netns_ctx *netns_ctx;
128 OSMO_ASSERT(netns_name);
129
130 netns_ctx = talloc_zero(ctx, struct netdev_netns_ctx);
131 if (!netns_ctx)
132 return NULL;
133
134 netns_ctx->netns_name = talloc_strdup(netns_ctx, netns_name);
135 netns_ctx->netns_fd = -1;
136
137 llist_add_tail(&netns_ctx->entry, &g_netdev_netns_ctx_list);
138 return netns_ctx;
139
140}
141
142static void netdev_netns_ctx_free(struct netdev_netns_ctx *netns_ctx)
143{
144 if (!netns_ctx)
145 return;
146
147 llist_del(&netns_ctx->entry);
148
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100149#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100150 if (netns_ctx->omnl) {
151 osmo_mnl_destroy(netns_ctx->omnl);
152 netns_ctx->omnl = NULL;
153 }
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100154#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100155
156 if (netns_ctx->netns_fd != -1) {
157 close(netns_ctx->netns_fd);
158 netns_ctx->netns_fd = -1;
159 }
160 talloc_free(netns_ctx);
161}
162
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100163#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100164static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data);
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100165#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100166
167static int netdev_netns_ctx_init(struct netdev_netns_ctx *netns_ctx)
168{
169 struct osmo_netns_switch_state switch_state;
170 int rc;
171
172 if (netns_ctx->netns_name[0] != '\0') {
173 LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Switch to netns '%s'\n", netns_ctx->netns_name);
174 netns_ctx->netns_fd = osmo_netns_open_fd(netns_ctx->netns_name);
175 if (netns_ctx->netns_fd < 0) {
176 LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n",
177 netns_ctx->netns_name, strerror(errno), errno);
178 return netns_ctx->netns_fd;
179 }
180
181 /* temporarily switch to specified namespace to create netlink socket */
182 rc = osmo_netns_switch_enter(netns_ctx->netns_fd, &switch_state);
183 if (rc < 0) {
184 LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n",
185 netns_ctx->netns_name, strerror(errno), errno);
186 /* netns_ctx->netns_fd will be freed by future call to netdev_netns_ctx_free() */
187 return rc;
188 }
189 }
190
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100191#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100192 netns_ctx->omnl = osmo_mnl_init(NULL, NETLINK_ROUTE, RTMGRP_LINK, netdev_netns_ctx_mnl_cb, netns_ctx);
193 rc = (netns_ctx->omnl ? 0 : -EFAULT);
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100194#else
195 rc = 0;
196#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100197
198 /* switch back to default namespace */
199 if (netns_ctx->netns_name[0] != '\0') {
200 int rc2 = osmo_netns_switch_exit(&switch_state);
201 if (rc2 < 0) {
202 LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch back from netns '%s': %s\n",
203 netns_ctx->netns_name, strerror(errno));
204 return rc2;
205 }
206 LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Back from netns '%s'\n",
207 netns_ctx->netns_name);
208 }
209 return rc;
210}
211
212static struct netdev_netns_ctx *netdev_netns_ctx_find_by_netns_name(const char *netns_name)
213{
214 struct netdev_netns_ctx *netns_ctx;
215
216 llist_for_each_entry(netns_ctx, &g_netdev_netns_ctx_list, entry) {
217 if (strcmp(netns_ctx->netns_name, netns_name))
218 continue;
219 return netns_ctx;
220 }
221
222 return NULL;
223}
224
225static struct netdev_netns_ctx *netdev_netns_ctx_get(const char *netns_name)
226{
227 struct netdev_netns_ctx *netns_ctx;
228 int rc;
229
230 OSMO_ASSERT(netns_name);
231 netns_ctx = netdev_netns_ctx_find_by_netns_name(netns_name);
232 if (!netns_ctx) {
233 netns_ctx = netdev_netns_ctx_alloc(NULL, netns_name);
234 if (!netns_ctx)
235 return NULL;
236 rc = netdev_netns_ctx_init(netns_ctx);
237 if (rc < 0) {
238 netdev_netns_ctx_free(netns_ctx);
239 return NULL;
240 }
241 }
242 netns_ctx->refcount++;
243 return netns_ctx;
244}
245
246static void netdev_netns_ctx_put(struct netdev_netns_ctx *netns_ctx)
247{
248 OSMO_ASSERT(netns_ctx);
249 netns_ctx->refcount--;
250
251 if (netns_ctx->refcount == 0)
252 netdev_netns_ctx_free(netns_ctx);
253}
254
255struct osmo_netdev {
256 /* entry in g_netdev_list */
257 struct llist_head entry;
258
259 /* Pointer to struct shared (refcounted) by all osmo_netdev in the same netns: */
260 struct netdev_netns_ctx *netns_ctx;
261
262 /* Name used to identify the osmo_netdev */
263 char *name;
264
265 /* ifindex of the network interface (address space is per netns) */
266 unsigned int ifindex;
267
268 /* Network interface name. Can change over lifetime of the interface. */
269 char *dev_name;
270
271 /* netns name where the netdev interface is created (NULL = default netns) */
272 char *netns_name;
273
274 /* API user private data */
275 void *priv_data;
276
277 /* Whether the netdev is in operation (managing the netdev interface) */
278 bool registered;
279
280 /* Called by netdev each time a new up/down state change is detected. Can be NULL. */
281 osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb;
282
283 /* Called by netdev each time the registered network interface is renamed by the system. Can be NULL. */
284 osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb;
285
286 /* Called by netdev each time the configured MTU changes in registered network interface. Can be NULL. */
287 osmo_netdev_mtu_chg_cb_t mtu_chg_cb;
288
289 /* Whether the netdev interface is UP */
290 bool if_up;
291 /* Whether we know the interface updown state (aka if if_up holds information)*/
292 bool if_up_known;
293
294 /* The netdev interface MTU size */
295 uint32_t if_mtu;
296 /* Whether we know the interface MTU size (aka if if_mtu holds information)*/
297 bool if_mtu_known;
298};
299
300#define NETDEV_NETNS_ENTER(netdev, switch_state, str_prefix) \
301 do { \
302 if ((netdev)->netns_name) { \
303 LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Switch to netns '%s'\n", \
304 (netdev)->netns_name); \
305 int rc2 = osmo_netns_switch_enter((netdev)->netns_ctx->netns_fd, switch_state); \
306 if (rc2 < 0) { \
307 LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch to netns '%s': %s (%d)\n", \
308 (netdev)->netns_name, strerror(errno), errno); \
309 return -EACCES; \
310 } \
311 } \
312 } while (0)
313
314#define NETDEV_NETNS_EXIT(netdev, switch_state, str_prefix) \
315 do { \
316 if ((netdev)->netns_name) { \
317 int rc2 = osmo_netns_switch_exit(switch_state); \
318 if (rc2 < 0) { \
319 LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch back from netns '%s': %s\n", \
320 (netdev)->netns_name, strerror(errno)); \
321 return rc2; \
322 } \
323 LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Back from netns '%s'\n", \
324 (netdev)->netns_name); \
325 } \
326 } while (0)
327
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100328#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100329/* validate the netlink attributes */
330static int netdev_mnl_data_attr_cb(const struct nlattr *attr, void *data)
331{
332 const struct nlattr **tb = data;
333 int type = mnl_attr_get_type(attr);
334
335 if (mnl_attr_type_valid(attr, IFLA_MAX) < 0)
336 return MNL_CB_OK;
337
338 switch (type) {
339 case IFLA_ADDRESS:
340 if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0)
341 return MNL_CB_ERROR;
342 break;
343 case IFLA_MTU:
344 if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
345 return MNL_CB_ERROR;
346 break;
347 case IFLA_IFNAME:
348 if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
349 return MNL_CB_ERROR;
350 break;
351 }
352 tb[type] = attr;
353 return MNL_CB_OK;
354}
355
356static void netdev_mnl_check_mtu_change(struct osmo_netdev *netdev, uint32_t mtu)
357{
358 if (netdev->if_mtu_known && netdev->if_mtu == mtu)
359 return;
360
361 LOGNETDEV(netdev, LOGL_NOTICE, "MTU changed: %u\n", mtu);
362 if (netdev->mtu_chg_cb)
363 netdev->mtu_chg_cb(netdev, mtu);
364
365 netdev->if_mtu_known = true;
366 netdev->if_mtu = mtu;
367}
368
369static void netdev_mnl_check_link_state_change(struct osmo_netdev *netdev, bool if_up)
370{
371 if (netdev->if_up_known && netdev->if_up == if_up)
372 return;
373
374 LOGNETDEV(netdev, LOGL_NOTICE, "Physical link state changed: %s\n",
375 if_up ? "UP" : "DOWN");
376 if (netdev->ifupdown_ind_cb)
377 netdev->ifupdown_ind_cb(netdev, if_up);
378
379 netdev->if_up_known = true;
380 netdev->if_up = if_up;
381}
382
383static int netdev_mnl_cb(struct osmo_netdev *netdev, struct ifinfomsg *ifm, struct nlattr **tb)
384{
385 char ifnamebuf[IF_NAMESIZE];
386 const char *ifname = NULL;
387 bool if_running;
388
389 if (tb[IFLA_IFNAME]) {
390 ifname = mnl_attr_get_str(tb[IFLA_IFNAME]);
391 LOGNETDEV(netdev, LOGL_DEBUG, "%s(): ifname=%s\n", __func__, ifname);
392 } else {
393 /* Try harder to obtain the ifname. This code path should in
394 * general not be triggered since usually IFLA_IFNAME is there */
395 struct osmo_netns_switch_state switch_state;
396 NETDEV_NETNS_ENTER(netdev, &switch_state, "if_indextoname");
397 ifname = if_indextoname(ifm->ifi_index, ifnamebuf);
398 NETDEV_NETNS_EXIT(netdev, &switch_state, "if_indextoname");
399 }
400 if (ifname) {
401 /* Update dev_name if it changed: */
402 if (strcmp(netdev->dev_name, ifname) != 0) {
403 if (netdev->dev_name_chg_cb)
404 netdev->dev_name_chg_cb(netdev, ifname);
405 osmo_talloc_replace_string(netdev, &netdev->dev_name, ifname);
406 }
407 }
408
409 if (tb[IFLA_MTU]) {
410 uint32_t mtu = mnl_attr_get_u32(tb[IFLA_MTU]);
411 LOGNETDEV(netdev, LOGL_DEBUG, "%s(): mtu=%u\n", __func__, mtu);
412 netdev_mnl_check_mtu_change(netdev, mtu);
413 }
414 if (tb[IFLA_ADDRESS]) {
415 uint8_t *hwaddr = mnl_attr_get_payload(tb[IFLA_ADDRESS]);
416 uint16_t hwaddr_len = mnl_attr_get_payload_len(tb[IFLA_ADDRESS]);
417 LOGNETDEV(netdev, LOGL_DEBUG, "%s(): hwaddress=%s\n",
418 __func__, osmo_hexdump(hwaddr, hwaddr_len));
419 }
420
421 if_running = !!(ifm->ifi_flags & IFF_RUNNING);
422 LOGNETDEV(netdev, LOGL_DEBUG, "%s(): up=%u running=%u\n",
423 __func__, !!(ifm->ifi_flags & IFF_UP), if_running);
424 netdev_mnl_check_link_state_change(netdev, if_running);
425
426 return MNL_CB_OK;
427}
428
429static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data)
430{
431 struct osmo_mnl *omnl = data;
432 struct netdev_netns_ctx *netns_ctx = (struct netdev_netns_ctx *)omnl->priv;
433 struct nlattr *tb[IFLA_MAX+1] = {};
434 struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh);
435 struct osmo_netdev *netdev;
436
437 OSMO_ASSERT(omnl);
438 OSMO_ASSERT(ifm);
439
440 mnl_attr_parse(nlh, sizeof(*ifm), netdev_mnl_data_attr_cb, tb);
441
442 LOGP(DLGLOBAL, LOGL_DEBUG,
443 "%s(): index=%d type=%d flags=0x%x family=%d\n", __func__,
444 ifm->ifi_index, ifm->ifi_type, ifm->ifi_flags, ifm->ifi_family);
445
446 if (ifm->ifi_index == IFINDEX_UNUSED)
447 return MNL_CB_ERROR;
448
449 /* Find the netdev (if any) using key <netns,ifindex>.
450 * Different users of the API may have its own osmo_netdev object
451 * tracking potentially same netif, hence we need to iterate the whole list
452 * and dispatch to all matches:
453 */
454 bool found_any = false;
455 llist_for_each_entry(netdev, &g_netdev_list, entry) {
456 if (!netdev->registered)
457 continue;
458 if (netdev->ifindex != ifm->ifi_index)
459 continue;
460 if (strcmp(netdev->netns_ctx->netns_name, netns_ctx->netns_name))
461 continue;
462 found_any = true;
463 netdev_mnl_cb(netdev, ifm, &tb[0]);
464 }
465
466 if (!found_any) {
467 LOGP(DLGLOBAL, LOGL_DEBUG, "%s(): device with ifindex %u on netns %s not registered\n", __func__,
468 ifm->ifi_index, netns_ctx->netns_name);
469 }
470 return MNL_CB_OK;
471}
472
Pau Espin Pedrol5c7eaa62023-01-24 18:41:33 +0100473/* Trigger an initial dump of the iface to get link information */
474static int netdev_mnl_request_initial_dump(struct osmo_mnl *omnl, unsigned int if_index)
475{
476 char buf[MNL_SOCKET_BUFFER_SIZE];
477 struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
478 struct ifinfomsg *ifm;
479
480 nlh->nlmsg_type = RTM_GETLINK;
481 nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
482 nlh->nlmsg_seq = time(NULL);
483
484 ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
485 ifm->ifi_family = AF_UNSPEC;
486 ifm->ifi_index = if_index;
487
488 if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
489 LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
490 return -EIO;
491 }
492
493 return 0;
494}
495
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100496static int netdev_mnl_set_ifupdown(struct osmo_mnl *omnl, unsigned int if_index,
497 const char *dev_name, bool up)
498{
499 char buf[MNL_SOCKET_BUFFER_SIZE];
500 struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
501 struct ifinfomsg *ifm;
502 unsigned int change = 0;
503 unsigned int flags = 0;
504
505 if (up) {
506 change |= IFF_UP;
507 flags |= IFF_UP;
508 } else {
509 change |= IFF_UP;
510 flags &= ~IFF_UP;
511 }
512
513 nlh->nlmsg_type = RTM_NEWLINK;
514 nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
515 nlh->nlmsg_seq = time(NULL);
516
517 ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
518 ifm->ifi_family = AF_UNSPEC;
519 ifm->ifi_change = change;
520 ifm->ifi_flags = flags;
521 ifm->ifi_index = if_index;
522
523 if (dev_name)
524 mnl_attr_put_str(nlh, IFLA_IFNAME, dev_name);
525
526 if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
527 LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
528 return -EIO;
529 }
530
531 return 0;
532}
533
534static int netdev_mnl_add_addr(struct osmo_mnl *omnl, unsigned int if_index, const struct osmo_sockaddr *osa, uint8_t prefix)
535{
536 char buf[MNL_SOCKET_BUFFER_SIZE];
537 struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
538 struct ifaddrmsg *ifm;
539
540 nlh->nlmsg_type = RTM_NEWADDR;
541 nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
542 nlh->nlmsg_seq = time(NULL);
543
544 ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
545 ifm->ifa_family = osa->u.sa.sa_family;
546 ifm->ifa_prefixlen = prefix;
547 ifm->ifa_flags = IFA_F_PERMANENT;
548 ifm->ifa_scope = RT_SCOPE_UNIVERSE;
549 ifm->ifa_index = if_index;
550
551 /*
552 * The exact meaning of IFA_LOCAL and IFA_ADDRESS depend
553 * on the address family being used and the device type.
554 * For broadcast devices (like the interfaces we use),
555 * for IPv4 we specify both and they are used interchangeably.
556 * For IPv6, only IFA_ADDRESS needs to be set.
557 */
558 switch (osa->u.sa.sa_family) {
559 case AF_INET:
560 mnl_attr_put_u32(nlh, IFA_LOCAL, osa->u.sin.sin_addr.s_addr);
561 mnl_attr_put_u32(nlh, IFA_ADDRESS, osa->u.sin.sin_addr.s_addr);
562 break;
563 case AF_INET6:
564 mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), &osa->u.sin6.sin6_addr);
565 break;
566 default:
567 return -EINVAL;
568 }
569
570 if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
571 LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
572 return -EIO;
573 }
574
575 return 0;
576}
577
578static int netdev_mnl_add_route(struct osmo_mnl *omnl,
579 unsigned int if_index,
580 const struct osmo_sockaddr *dst_osa,
581 uint8_t dst_prefix,
582 const struct osmo_sockaddr *gw_osa)
583{
584 char buf[MNL_SOCKET_BUFFER_SIZE];
585 struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
586 struct rtmsg *rtm;
587
588 nlh->nlmsg_type = RTM_NEWROUTE;
589 nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
590 nlh->nlmsg_seq = time(NULL);
591
592 rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(*rtm));
593 rtm->rtm_family = dst_osa->u.sa.sa_family;
594 rtm->rtm_dst_len = dst_prefix;
595 rtm->rtm_src_len = 0;
596 rtm->rtm_tos = 0;
597 rtm->rtm_protocol = RTPROT_STATIC;
598 rtm->rtm_table = RT_TABLE_MAIN;
599 rtm->rtm_type = RTN_UNICAST;
600 rtm->rtm_scope = gw_osa ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
601 rtm->rtm_flags = 0;
602
603 switch (dst_osa->u.sa.sa_family) {
604 case AF_INET:
605 mnl_attr_put_u32(nlh, RTA_DST, dst_osa->u.sin.sin_addr.s_addr);
606 break;
607 case AF_INET6:
608 mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_osa->u.sin6.sin6_addr);
609 break;
610 default:
611 return -EINVAL;
612 }
613
614 mnl_attr_put_u32(nlh, RTA_OIF, if_index);
615
616 if (gw_osa) {
617 switch (gw_osa->u.sa.sa_family) {
618 case AF_INET:
619 mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_osa->u.sin.sin_addr.s_addr);
620 break;
621 case AF_INET6:
622 mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_osa->u.sin6.sin6_addr);
623 break;
624 default:
625 return -EINVAL;
626 }
627 }
628
629 if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
630 LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
631 return -EIO;
632 }
633
634 return 0;
635}
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100636#endif /* if ENABLE_LIBMNL */
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100637
638/*! Allocate a new netdev object.
639 * \param[in] ctx talloc context to use as a parent when allocating the netdev object
640 * \param[in] name A name providen to identify the netdev object
641 * \returns newly allocated netdev object on success; NULL on error
642 */
643struct osmo_netdev *osmo_netdev_alloc(void *ctx, const char *name)
644{
645 struct osmo_netdev *netdev;
646
647 netdev = talloc_zero(ctx, struct osmo_netdev);
648 if (!netdev)
649 return NULL;
650
651 netdev->name = talloc_strdup(netdev, name);
652
653 llist_add_tail(&netdev->entry, &g_netdev_list);
654 return netdev;
655}
656
657/*! Free an allocated netdev object.
658 * \param[in] netdev The netdev object to free
659 */
660void osmo_netdev_free(struct osmo_netdev *netdev)
661{
662 if (!netdev)
663 return;
664 if (osmo_netdev_is_registered(netdev))
665 osmo_netdev_unregister(netdev);
666 llist_del(&netdev->entry);
667 talloc_free(netdev);
668}
669
670/*! Start managing the network device referenced by the netdev object.
671 * \param[in] netdev The netdev object to open
672 * \returns 0 on success; negative on error
673 */
674int osmo_netdev_register(struct osmo_netdev *netdev)
675{
676 char ifnamebuf[IF_NAMESIZE];
677 struct osmo_netns_switch_state switch_state;
678 int rc = 0;
679
680 if (netdev->registered)
681 return -EALREADY;
682
683 netdev->netns_ctx = netdev_netns_ctx_get(netdev->netns_name ? : "");
684 if (!netdev->netns_ctx)
685 return -EFAULT;
686
687 NETDEV_NETNS_ENTER(netdev, &switch_state, "register");
688
689 if (!if_indextoname(netdev->ifindex, ifnamebuf)) {
690 rc = -ENODEV;
691 goto err_put_exit;
692 }
693 osmo_talloc_replace_string(netdev, &netdev->dev_name, ifnamebuf);
694
Pau Espin Pedrol5c7eaa62023-01-24 18:41:33 +0100695#if ENABLE_LIBMNL
696 rc = netdev_mnl_request_initial_dump(netdev->netns_ctx->omnl, netdev->ifindex);
697#endif
698
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100699 NETDEV_NETNS_EXIT(netdev, &switch_state, "register");
700
701 netdev->registered = true;
702 return rc;
703
704err_put_exit:
705 NETDEV_NETNS_EXIT(netdev, &switch_state, "register");
706 netdev_netns_ctx_put(netdev->netns_ctx);
707 return rc;
708}
709
710/*! Unregister the netdev object (stop managing /moniutoring the interface)
711 * \param[in] netdev The netdev object to close
712 * \returns 0 on success; negative on error
713 */
714int osmo_netdev_unregister(struct osmo_netdev *netdev)
715{
716 if (!netdev->registered)
717 return -EALREADY;
718
719 netdev->if_up_known = false;
720 netdev->if_mtu_known = false;
721
722 netdev_netns_ctx_put(netdev->netns_ctx);
723 netdev->registered = false;
724 return 0;
725}
726
727/*! Retrieve whether the netdev object is in "registered" state.
728 * \param[in] netdev The netdev object to check
729 * \returns true if in state "registered"; false otherwise
730 */
731bool osmo_netdev_is_registered(struct osmo_netdev *netdev)
732{
733 return netdev->registered;
734}
735
736/*! Set private user data pointer on the netdev object.
737 * \param[in] netdev The netdev object where the field is set
738 */
739void osmo_netdev_set_priv_data(struct osmo_netdev *netdev, void *priv_data)
740{
741 netdev->priv_data = priv_data;
742}
743
744/*! Get private user data pointer from the netdev object.
745 * \param[in] netdev The netdev object from where to retrieve the field
746 * \returns The current value of the priv_data field.
747 */
748void *osmo_netdev_get_priv_data(struct osmo_netdev *netdev)
749{
750 return netdev->priv_data;
751}
752
753/*! Set data_ind_cb callback, called when a new packet is received on the network interface.
754 * \param[in] netdev The netdev object where the field is set
755 * \param[in] data_ind_cb the user provided function to be called when the link status (UP/DOWN) changes
756 */
757void osmo_netdev_set_ifupdown_ind_cb(struct osmo_netdev *netdev, osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb)
758{
759 netdev->ifupdown_ind_cb = ifupdown_ind_cb;
760}
761
762/*! Set dev_name_chg_cb callback, called when a change in the network name is detected
763 * \param[in] netdev The netdev object where the field is set
764 * \param[in] dev_name_chg_cb the user provided function to be called when a the interface is renamed
765 */
766void osmo_netdev_set_dev_name_chg_cb(struct osmo_netdev *netdev, osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb)
767{
768 netdev->dev_name_chg_cb = dev_name_chg_cb;
769}
770
771/*! Set mtu_chg_cb callback, called when a change in the network name is detected
772 * \param[in] netdev The netdev object where the field is set
773 * \param[in] mtu_chg_cb the user provided function to be called when the configured MTU at the interface changes
774 */
775void osmo_netdev_set_mtu_chg_cb(struct osmo_netdev *netdev, osmo_netdev_mtu_chg_cb_t mtu_chg_cb)
776{
777 netdev->mtu_chg_cb = mtu_chg_cb;
778}
779
780/*! Get name used to identify the netdev object.
781 * \param[in] netdev The netdev object from where to retrieve the field
782 * \returns The current value of the name used to identify the netdev object
783 */
784const char *osmo_netdev_get_name(const struct osmo_netdev *netdev)
785{
786 return netdev->name;
787}
788
789/*! Set (specify) interface index identifying the network interface to manage
790 * \param[in] netdev The netdev object where the field is set
791 * \param[in] ifindex The interface index identifying the interface
792 * \returns 0 on success; negative on error
793 *
794 * The ifindex, together with the netns_name (see
795 * osmo_netdev_netns_name_set()), form together the key identifiers of a
796 * network interface to manage.
797 * This field is used during osmo_netdev_register() time, and hence must be set
798 * before calling that API, and cannot be changed when the netdev object is in
799 * "registered" state.
800 */
801int osmo_netdev_set_ifindex(struct osmo_netdev *netdev, unsigned int ifindex)
802{
803 if (netdev->registered)
804 return -EALREADY;
805 netdev->ifindex = ifindex;
806 return 0;
807}
808
809/*! Get interface index identifying the interface managed by netdev
810 * \param[in] netdev The netdev object from where to retrieve the field
811 * \returns The current value of the configured netdev interface ifindex to use (0 = unset)
812 */
813unsigned int osmo_netdev_get_ifindex(const struct osmo_netdev *netdev)
814{
815 return netdev->ifindex;
816}
817
818/*! Set (specify) name of the network namespace where the network interface to manage is located
819 * \param[in] netdev The netdev object where the field is set
820 * \param[in] netns_name The network namespace where the network interface is located
821 * \returns 0 on success; negative on error
822 *
823 * The netns_name, together with the ifindex (see
824 * osmo_netdev_ifindex_set()), form together the key identifiers of a
825 * network interface to manage.
826 * This field is used during osmo_netdev_register() time, and hence must be set
827 * before calling that API, and cannot be changed when the netdev object is in
828 * "registered" state.
829 * If left as NULL (default), the management will be done in the current network namespace.
830 */
831int osmo_netdev_set_netns_name(struct osmo_netdev *netdev, const char *netns_name)
832{
833 if (netdev->registered)
834 return -EALREADY;
835 osmo_talloc_replace_string(netdev, &netdev->netns_name, netns_name);
836 return 0;
837}
838
839/*! Get name of network namespace used when opening the netdev interface
840 * \param[in] netdev The netdev object from where to retrieve the field
841 * \returns The current value of the configured network namespace
842 */
843const char *osmo_netdev_get_netns_name(const struct osmo_netdev *netdev)
844{
845 return netdev->netns_name;
846}
847
848/*! Get name used to name the network interface created by the netdev object
849 * \param[in] netdev The netdev object from where to retrieve the field
850 * \returns The interface name (or NULL if unknown)
851 *
852 * This information is retrieved internally once the netdev object enters the
853 * "registered" state. Hence, when not registered NULL can be returned.
854 */
855const char *osmo_netdev_get_dev_name(const struct osmo_netdev *netdev)
856{
857 return netdev->dev_name;
858}
859
860/*! Bring netdev interface UP or DOWN.
861 * \param[in] netdev The netdev object managing the netdev interface
862 * \param[in] ifupdown true to set the interface UP, false to set it DOWN
863 * \returns 0 on succes; negative on error.
864 */
865int osmo_netdev_ifupdown(struct osmo_netdev *netdev, bool ifupdown)
866{
867 struct osmo_netns_switch_state switch_state;
868 int rc;
869
870 if (!netdev->registered)
871 return -ENODEV;
872
873 LOGNETDEV(netdev, LOGL_NOTICE, "Bringing dev %s %s\n",
874 netdev->dev_name, ifupdown ? "UP" : "DOWN");
875
876 NETDEV_NETNS_ENTER(netdev, &switch_state, "ifupdown");
877
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100878#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100879 rc = netdev_mnl_set_ifupdown(netdev->netns_ctx->omnl, netdev->ifindex,
880 netdev->dev_name, ifupdown);
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100881#else
882 LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
883 rc = -ENOTSUP;
884#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100885
886 NETDEV_NETNS_EXIT(netdev, &switch_state, "ifupdown");
887
888 return rc;
889}
890
891/*! Add IP address to netdev interface
892 * \param[in] netdev The netdev object managing the netdev interface
893 * \param[in] addr The local address to set on the interface
894 * \param[in] prefixlen The network prefix of addr
895 * \returns 0 on succes; negative on error.
896 */
897int osmo_netdev_add_addr(struct osmo_netdev *netdev, const struct osmo_sockaddr *addr, uint8_t prefixlen)
898{
899 struct osmo_netns_switch_state switch_state;
900 char buf[INET6_ADDRSTRLEN];
901 int rc;
902
903 if (!netdev->registered)
904 return -ENODEV;
905
906 LOGNETDEV(netdev, LOGL_NOTICE, "Adding address %s/%u to dev %s\n",
907 osmo_sockaddr_ntop(&addr->u.sa, buf), prefixlen, netdev->dev_name);
908
909 NETDEV_NETNS_ENTER(netdev, &switch_state, "Add address");
910
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100911#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100912 rc = netdev_mnl_add_addr(netdev->netns_ctx->omnl, netdev->ifindex, addr, prefixlen);
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100913#else
914 LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
915 rc = -ENOTSUP;
916#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100917
918 NETDEV_NETNS_EXIT(netdev, &switch_state, "Add address");
919
920 return rc;
921}
922
923/*! Add IP route to netdev interface
924 * \param[in] netdev The netdev object managing the netdev interface
925 * \param[in] dst_addr The destination address of the route
926 * \param[in] dst_prefixlen The network prefix of dst_addr
927 * \param[in] gw_addr The gateway address. Optional, can be NULL.
928 * \returns 0 on succes; negative on error.
929 */
930int osmo_netdev_add_route(struct osmo_netdev *netdev, const struct osmo_sockaddr *dst_addr, uint8_t dst_prefixlen, const struct osmo_sockaddr *gw_addr)
931{
932 struct osmo_netns_switch_state switch_state;
933 char buf_dst[INET6_ADDRSTRLEN];
934 char buf_gw[INET6_ADDRSTRLEN];
935 int rc;
936
937 if (!netdev->registered)
938 return -ENODEV;
939
940 LOGNETDEV(netdev, LOGL_NOTICE, "Adding route %s/%u%s%s dev %s\n",
941 osmo_sockaddr_ntop(&dst_addr->u.sa, buf_dst), dst_prefixlen,
942 gw_addr ? " via " : "",
943 gw_addr ? osmo_sockaddr_ntop(&gw_addr->u.sa, buf_gw) : "",
944 netdev->dev_name);
945
946 NETDEV_NETNS_ENTER(netdev, &switch_state, "Add route");
947
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100948#if ENABLE_LIBMNL
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100949 rc = netdev_mnl_add_route(netdev->netns_ctx->omnl, netdev->ifindex, dst_addr, dst_prefixlen, gw_addr);
Pau Espin Pedrol7d79fa42023-01-25 16:52:01 +0100950#else
951 LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
952 rc = -ENOTSUP;
953#endif
Pau Espin Pedrol9d0321d2023-01-24 17:33:06 +0100954
955 NETDEV_NETNS_EXIT(netdev, &switch_state, "Add route");
956
957 return rc;
958}
959
960#endif /* (!EMBEDDED) */
961
962/*! @} */