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