mirror of https://gerrit.osmocom.org/libosmocore
963 lines
28 KiB
C
963 lines
28 KiB
C
|
|
/* network device (interface) functions.
|
|
* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
|
|
*
|
|
* All Rights Reserved
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
/*! \addtogroup netdev
|
|
* @{
|
|
* network device (interface) convenience functions
|
|
*
|
|
* \file netdev.c
|
|
*
|
|
* Example lifecycle use of the API:
|
|
*
|
|
* struct osmo_sockaddr_str osa_str = {};
|
|
* struct osmo_sockaddr osa = {};
|
|
*
|
|
* // Allocate object:
|
|
* struct osmo_netdev *netdev = osmo_netdev_alloc(parent_talloc_ctx, name);
|
|
* OSMO_ASSERT(netdev);
|
|
*
|
|
* // Configure object (before registration):
|
|
* rc = osmo_netdev_set_netns_name(netdev, "some_netns_name_or_null");
|
|
* rc = osmo_netdev_set_ifindex(netdev, if_nametoindex("eth0"));
|
|
*
|
|
* // Register object:
|
|
* rc = osmo_netdev_register(netdev);
|
|
* // The network interface is now being monitored and the network interface
|
|
* // can be operated (see below)
|
|
*
|
|
* // Add a local IPv4 address:
|
|
* rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1");
|
|
* rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
|
|
* rc = osmo_netdev_add_addr(netdev, &osa, 24);
|
|
*
|
|
* // Bring network interface up:
|
|
* rc = osmo_netdev_ifupdown(netdev, true);
|
|
*
|
|
* // Add default route (0.0.0.0/0):
|
|
* rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0");
|
|
* rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
|
|
* rc = osmo_netdev_add_route(netdev, &osa, 0, NULL);
|
|
*
|
|
* // Unregister (can be freed directly too):
|
|
* rc = osmo_netdev_unregister(netdev);
|
|
* // Free the object:
|
|
* osmo_netdev_free(netdev);
|
|
*/
|
|
|
|
#if (!EMBEDDED)
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <ifaddrs.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <net/if.h>
|
|
#include <net/route.h>
|
|
|
|
#if defined(__linux__)
|
|
#include <linux/if_link.h>
|
|
#include <linux/rtnetlink.h>
|
|
#else
|
|
#error "Unknown platform!"
|
|
#endif
|
|
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/core/select.h>
|
|
#include <osmocom/core/linuxlist.h>
|
|
#include <osmocom/core/logging.h>
|
|
#include <osmocom/core/socket.h>
|
|
#include <osmocom/core/netns.h>
|
|
#include <osmocom/core/netdev.h>
|
|
|
|
#if ENABLE_LIBMNL
|
|
#include <osmocom/core/mnl.h>
|
|
#endif
|
|
|
|
#define IFINDEX_UNUSED 0
|
|
|
|
#define LOGNETDEV(netdev, lvl, fmt, args ...) \
|
|
LOGP(DLGLOBAL, lvl, "NETDEV(%s,if=%s/%u,ns=%s): " fmt, \
|
|
(netdev)->name, osmo_netdev_get_dev_name(netdev) ? : "", \
|
|
(netdev)->ifindex, (netdev)->netns_name ? : "", ## args)
|
|
|
|
static struct llist_head g_netdev_netns_ctx_list = LLIST_HEAD_INIT(g_netdev_netns_ctx_list);
|
|
static struct llist_head g_netdev_list = LLIST_HEAD_INIT(g_netdev_list);
|
|
|
|
/* One per netns, shared by all osmo_netdev in a given netns: */
|
|
struct netdev_netns_ctx {
|
|
struct llist_head entry; /* entry in g_netdev_netns_ctx_list */
|
|
unsigned int refcount; /* Number of osmo_netdev currently registered on this netns */
|
|
const char *netns_name; /* default netns has empty string "" (never NULL!) */
|
|
int netns_fd; /* FD to the netns with name "netns_name" above */
|
|
#if ENABLE_LIBMNL
|
|
struct osmo_mnl *omnl;
|
|
#endif
|
|
};
|
|
|
|
static struct netdev_netns_ctx *netdev_netns_ctx_alloc(void *ctx, const char *netns_name)
|
|
{
|
|
struct netdev_netns_ctx *netns_ctx;
|
|
OSMO_ASSERT(netns_name);
|
|
|
|
netns_ctx = talloc_zero(ctx, struct netdev_netns_ctx);
|
|
if (!netns_ctx)
|
|
return NULL;
|
|
|
|
netns_ctx->netns_name = talloc_strdup(netns_ctx, netns_name);
|
|
netns_ctx->netns_fd = -1;
|
|
|
|
llist_add_tail(&netns_ctx->entry, &g_netdev_netns_ctx_list);
|
|
return netns_ctx;
|
|
|
|
}
|
|
|
|
static void netdev_netns_ctx_free(struct netdev_netns_ctx *netns_ctx)
|
|
{
|
|
if (!netns_ctx)
|
|
return;
|
|
|
|
llist_del(&netns_ctx->entry);
|
|
|
|
#if ENABLE_LIBMNL
|
|
if (netns_ctx->omnl) {
|
|
osmo_mnl_destroy(netns_ctx->omnl);
|
|
netns_ctx->omnl = NULL;
|
|
}
|
|
#endif
|
|
|
|
if (netns_ctx->netns_fd != -1) {
|
|
close(netns_ctx->netns_fd);
|
|
netns_ctx->netns_fd = -1;
|
|
}
|
|
talloc_free(netns_ctx);
|
|
}
|
|
|
|
#if ENABLE_LIBMNL
|
|
static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data);
|
|
#endif
|
|
|
|
static int netdev_netns_ctx_init(struct netdev_netns_ctx *netns_ctx)
|
|
{
|
|
struct osmo_netns_switch_state switch_state;
|
|
int rc;
|
|
|
|
if (netns_ctx->netns_name[0] != '\0') {
|
|
LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Switch to netns '%s'\n", netns_ctx->netns_name);
|
|
netns_ctx->netns_fd = osmo_netns_open_fd(netns_ctx->netns_name);
|
|
if (netns_ctx->netns_fd < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n",
|
|
netns_ctx->netns_name, strerror(errno), errno);
|
|
return netns_ctx->netns_fd;
|
|
}
|
|
|
|
/* temporarily switch to specified namespace to create netlink socket */
|
|
rc = osmo_netns_switch_enter(netns_ctx->netns_fd, &switch_state);
|
|
if (rc < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n",
|
|
netns_ctx->netns_name, strerror(errno), errno);
|
|
/* netns_ctx->netns_fd will be freed by future call to netdev_netns_ctx_free() */
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
#if ENABLE_LIBMNL
|
|
netns_ctx->omnl = osmo_mnl_init(NULL, NETLINK_ROUTE, RTMGRP_LINK, netdev_netns_ctx_mnl_cb, netns_ctx);
|
|
rc = (netns_ctx->omnl ? 0 : -EFAULT);
|
|
#else
|
|
rc = 0;
|
|
#endif
|
|
|
|
/* switch back to default namespace */
|
|
if (netns_ctx->netns_name[0] != '\0') {
|
|
int rc2 = osmo_netns_switch_exit(&switch_state);
|
|
if (rc2 < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch back from netns '%s': %s\n",
|
|
netns_ctx->netns_name, strerror(errno));
|
|
return rc2;
|
|
}
|
|
LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Back from netns '%s'\n",
|
|
netns_ctx->netns_name);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static struct netdev_netns_ctx *netdev_netns_ctx_find_by_netns_name(const char *netns_name)
|
|
{
|
|
struct netdev_netns_ctx *netns_ctx;
|
|
|
|
llist_for_each_entry(netns_ctx, &g_netdev_netns_ctx_list, entry) {
|
|
if (strcmp(netns_ctx->netns_name, netns_name))
|
|
continue;
|
|
return netns_ctx;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct netdev_netns_ctx *netdev_netns_ctx_get(const char *netns_name)
|
|
{
|
|
struct netdev_netns_ctx *netns_ctx;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(netns_name);
|
|
netns_ctx = netdev_netns_ctx_find_by_netns_name(netns_name);
|
|
if (!netns_ctx) {
|
|
netns_ctx = netdev_netns_ctx_alloc(NULL, netns_name);
|
|
if (!netns_ctx)
|
|
return NULL;
|
|
rc = netdev_netns_ctx_init(netns_ctx);
|
|
if (rc < 0) {
|
|
netdev_netns_ctx_free(netns_ctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
netns_ctx->refcount++;
|
|
return netns_ctx;
|
|
}
|
|
|
|
static void netdev_netns_ctx_put(struct netdev_netns_ctx *netns_ctx)
|
|
{
|
|
OSMO_ASSERT(netns_ctx);
|
|
netns_ctx->refcount--;
|
|
|
|
if (netns_ctx->refcount == 0)
|
|
netdev_netns_ctx_free(netns_ctx);
|
|
}
|
|
|
|
struct osmo_netdev {
|
|
/* entry in g_netdev_list */
|
|
struct llist_head entry;
|
|
|
|
/* Pointer to struct shared (refcounted) by all osmo_netdev in the same netns: */
|
|
struct netdev_netns_ctx *netns_ctx;
|
|
|
|
/* Name used to identify the osmo_netdev */
|
|
char *name;
|
|
|
|
/* ifindex of the network interface (address space is per netns) */
|
|
unsigned int ifindex;
|
|
|
|
/* Network interface name. Can change over lifetime of the interface. */
|
|
char *dev_name;
|
|
|
|
/* netns name where the netdev interface is created (NULL = default netns) */
|
|
char *netns_name;
|
|
|
|
/* API user private data */
|
|
void *priv_data;
|
|
|
|
/* Whether the netdev is in operation (managing the netdev interface) */
|
|
bool registered;
|
|
|
|
/* Called by netdev each time a new up/down state change is detected. Can be NULL. */
|
|
osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb;
|
|
|
|
/* Called by netdev each time the registered network interface is renamed by the system. Can be NULL. */
|
|
osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb;
|
|
|
|
/* Called by netdev each time the configured MTU changes in registered network interface. Can be NULL. */
|
|
osmo_netdev_mtu_chg_cb_t mtu_chg_cb;
|
|
|
|
/* Whether the netdev interface is UP */
|
|
bool if_up;
|
|
/* Whether we know the interface updown state (aka if if_up holds information)*/
|
|
bool if_up_known;
|
|
|
|
/* The netdev interface MTU size */
|
|
uint32_t if_mtu;
|
|
/* Whether we know the interface MTU size (aka if if_mtu holds information)*/
|
|
bool if_mtu_known;
|
|
};
|
|
|
|
#define NETDEV_NETNS_ENTER(netdev, switch_state, str_prefix) \
|
|
do { \
|
|
if ((netdev)->netns_name) { \
|
|
LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Switch to netns '%s'\n", \
|
|
(netdev)->netns_name); \
|
|
int rc2 = osmo_netns_switch_enter((netdev)->netns_ctx->netns_fd, switch_state); \
|
|
if (rc2 < 0) { \
|
|
LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch to netns '%s': %s (%d)\n", \
|
|
(netdev)->netns_name, strerror(errno), errno); \
|
|
return -EACCES; \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#define NETDEV_NETNS_EXIT(netdev, switch_state, str_prefix) \
|
|
do { \
|
|
if ((netdev)->netns_name) { \
|
|
int rc2 = osmo_netns_switch_exit(switch_state); \
|
|
if (rc2 < 0) { \
|
|
LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch back from netns '%s': %s\n", \
|
|
(netdev)->netns_name, strerror(errno)); \
|
|
return rc2; \
|
|
} \
|
|
LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Back from netns '%s'\n", \
|
|
(netdev)->netns_name); \
|
|
} \
|
|
} while (0)
|
|
|
|
#if ENABLE_LIBMNL
|
|
/* validate the netlink attributes */
|
|
static int netdev_mnl_data_attr_cb(const struct nlattr *attr, void *data)
|
|
{
|
|
const struct nlattr **tb = data;
|
|
int type = mnl_attr_get_type(attr);
|
|
|
|
if (mnl_attr_type_valid(attr, IFLA_MAX) < 0)
|
|
return MNL_CB_OK;
|
|
|
|
switch (type) {
|
|
case IFLA_ADDRESS:
|
|
if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0)
|
|
return MNL_CB_ERROR;
|
|
break;
|
|
case IFLA_MTU:
|
|
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
|
|
return MNL_CB_ERROR;
|
|
break;
|
|
case IFLA_IFNAME:
|
|
if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
|
|
return MNL_CB_ERROR;
|
|
break;
|
|
}
|
|
tb[type] = attr;
|
|
return MNL_CB_OK;
|
|
}
|
|
|
|
static void netdev_mnl_check_mtu_change(struct osmo_netdev *netdev, uint32_t mtu)
|
|
{
|
|
if (netdev->if_mtu_known && netdev->if_mtu == mtu)
|
|
return;
|
|
|
|
LOGNETDEV(netdev, LOGL_NOTICE, "MTU changed: %u\n", mtu);
|
|
if (netdev->mtu_chg_cb)
|
|
netdev->mtu_chg_cb(netdev, mtu);
|
|
|
|
netdev->if_mtu_known = true;
|
|
netdev->if_mtu = mtu;
|
|
}
|
|
|
|
static void netdev_mnl_check_link_state_change(struct osmo_netdev *netdev, bool if_up)
|
|
{
|
|
if (netdev->if_up_known && netdev->if_up == if_up)
|
|
return;
|
|
|
|
LOGNETDEV(netdev, LOGL_NOTICE, "Physical link state changed: %s\n",
|
|
if_up ? "UP" : "DOWN");
|
|
if (netdev->ifupdown_ind_cb)
|
|
netdev->ifupdown_ind_cb(netdev, if_up);
|
|
|
|
netdev->if_up_known = true;
|
|
netdev->if_up = if_up;
|
|
}
|
|
|
|
static int netdev_mnl_cb(struct osmo_netdev *netdev, struct ifinfomsg *ifm, struct nlattr **tb)
|
|
{
|
|
char ifnamebuf[IF_NAMESIZE];
|
|
const char *ifname = NULL;
|
|
bool if_running;
|
|
|
|
if (tb[IFLA_IFNAME]) {
|
|
ifname = mnl_attr_get_str(tb[IFLA_IFNAME]);
|
|
LOGNETDEV(netdev, LOGL_DEBUG, "%s(): ifname=%s\n", __func__, ifname);
|
|
} else {
|
|
/* Try harder to obtain the ifname. This code path should in
|
|
* general not be triggered since usually IFLA_IFNAME is there */
|
|
struct osmo_netns_switch_state switch_state;
|
|
NETDEV_NETNS_ENTER(netdev, &switch_state, "if_indextoname");
|
|
ifname = if_indextoname(ifm->ifi_index, ifnamebuf);
|
|
NETDEV_NETNS_EXIT(netdev, &switch_state, "if_indextoname");
|
|
}
|
|
if (ifname) {
|
|
/* Update dev_name if it changed: */
|
|
if (strcmp(netdev->dev_name, ifname) != 0) {
|
|
if (netdev->dev_name_chg_cb)
|
|
netdev->dev_name_chg_cb(netdev, ifname);
|
|
osmo_talloc_replace_string(netdev, &netdev->dev_name, ifname);
|
|
}
|
|
}
|
|
|
|
if (tb[IFLA_MTU]) {
|
|
uint32_t mtu = mnl_attr_get_u32(tb[IFLA_MTU]);
|
|
LOGNETDEV(netdev, LOGL_DEBUG, "%s(): mtu=%u\n", __func__, mtu);
|
|
netdev_mnl_check_mtu_change(netdev, mtu);
|
|
}
|
|
if (tb[IFLA_ADDRESS]) {
|
|
uint8_t *hwaddr = mnl_attr_get_payload(tb[IFLA_ADDRESS]);
|
|
uint16_t hwaddr_len = mnl_attr_get_payload_len(tb[IFLA_ADDRESS]);
|
|
LOGNETDEV(netdev, LOGL_DEBUG, "%s(): hwaddress=%s\n",
|
|
__func__, osmo_hexdump(hwaddr, hwaddr_len));
|
|
}
|
|
|
|
if_running = !!(ifm->ifi_flags & IFF_RUNNING);
|
|
LOGNETDEV(netdev, LOGL_DEBUG, "%s(): up=%u running=%u\n",
|
|
__func__, !!(ifm->ifi_flags & IFF_UP), if_running);
|
|
netdev_mnl_check_link_state_change(netdev, if_running);
|
|
|
|
return MNL_CB_OK;
|
|
}
|
|
|
|
static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data)
|
|
{
|
|
struct osmo_mnl *omnl = data;
|
|
struct netdev_netns_ctx *netns_ctx = (struct netdev_netns_ctx *)omnl->priv;
|
|
struct nlattr *tb[IFLA_MAX+1] = {};
|
|
struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh);
|
|
struct osmo_netdev *netdev;
|
|
|
|
OSMO_ASSERT(omnl);
|
|
OSMO_ASSERT(ifm);
|
|
|
|
mnl_attr_parse(nlh, sizeof(*ifm), netdev_mnl_data_attr_cb, tb);
|
|
|
|
LOGP(DLGLOBAL, LOGL_DEBUG,
|
|
"%s(): index=%d type=%d flags=0x%x family=%d\n", __func__,
|
|
ifm->ifi_index, ifm->ifi_type, ifm->ifi_flags, ifm->ifi_family);
|
|
|
|
if (ifm->ifi_index == IFINDEX_UNUSED)
|
|
return MNL_CB_ERROR;
|
|
|
|
/* Find the netdev (if any) using key <netns,ifindex>.
|
|
* Different users of the API may have its own osmo_netdev object
|
|
* tracking potentially same netif, hence we need to iterate the whole list
|
|
* and dispatch to all matches:
|
|
*/
|
|
bool found_any = false;
|
|
llist_for_each_entry(netdev, &g_netdev_list, entry) {
|
|
if (!netdev->registered)
|
|
continue;
|
|
if (netdev->ifindex != ifm->ifi_index)
|
|
continue;
|
|
if (strcmp(netdev->netns_ctx->netns_name, netns_ctx->netns_name))
|
|
continue;
|
|
found_any = true;
|
|
netdev_mnl_cb(netdev, ifm, &tb[0]);
|
|
}
|
|
|
|
if (!found_any) {
|
|
LOGP(DLGLOBAL, LOGL_DEBUG, "%s(): device with ifindex %u on netns %s not registered\n", __func__,
|
|
ifm->ifi_index, netns_ctx->netns_name);
|
|
}
|
|
return MNL_CB_OK;
|
|
}
|
|
|
|
/* Trigger an initial dump of the iface to get link information */
|
|
static int netdev_mnl_request_initial_dump(struct osmo_mnl *omnl, unsigned int if_index)
|
|
{
|
|
char buf[MNL_SOCKET_BUFFER_SIZE];
|
|
struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
|
|
struct ifinfomsg *ifm;
|
|
|
|
nlh->nlmsg_type = RTM_GETLINK;
|
|
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
nlh->nlmsg_seq = time(NULL);
|
|
|
|
ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
|
|
ifm->ifi_family = AF_UNSPEC;
|
|
ifm->ifi_index = if_index;
|
|
|
|
if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netdev_mnl_set_ifupdown(struct osmo_mnl *omnl, unsigned int if_index,
|
|
const char *dev_name, bool up)
|
|
{
|
|
char buf[MNL_SOCKET_BUFFER_SIZE];
|
|
struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
|
|
struct ifinfomsg *ifm;
|
|
unsigned int change = 0;
|
|
unsigned int flags = 0;
|
|
|
|
if (up) {
|
|
change |= IFF_UP;
|
|
flags |= IFF_UP;
|
|
} else {
|
|
change |= IFF_UP;
|
|
flags &= ~IFF_UP;
|
|
}
|
|
|
|
nlh->nlmsg_type = RTM_NEWLINK;
|
|
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
nlh->nlmsg_seq = time(NULL);
|
|
|
|
ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
|
|
ifm->ifi_family = AF_UNSPEC;
|
|
ifm->ifi_change = change;
|
|
ifm->ifi_flags = flags;
|
|
ifm->ifi_index = if_index;
|
|
|
|
if (dev_name)
|
|
mnl_attr_put_str(nlh, IFLA_IFNAME, dev_name);
|
|
|
|
if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netdev_mnl_add_addr(struct osmo_mnl *omnl, unsigned int if_index, const struct osmo_sockaddr *osa, uint8_t prefix)
|
|
{
|
|
char buf[MNL_SOCKET_BUFFER_SIZE];
|
|
struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
|
|
struct ifaddrmsg *ifm;
|
|
|
|
nlh->nlmsg_type = RTM_NEWADDR;
|
|
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
|
|
nlh->nlmsg_seq = time(NULL);
|
|
|
|
ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
|
|
ifm->ifa_family = osa->u.sa.sa_family;
|
|
ifm->ifa_prefixlen = prefix;
|
|
ifm->ifa_flags = IFA_F_PERMANENT;
|
|
ifm->ifa_scope = RT_SCOPE_UNIVERSE;
|
|
ifm->ifa_index = if_index;
|
|
|
|
/*
|
|
* The exact meaning of IFA_LOCAL and IFA_ADDRESS depend
|
|
* on the address family being used and the device type.
|
|
* For broadcast devices (like the interfaces we use),
|
|
* for IPv4 we specify both and they are used interchangeably.
|
|
* For IPv6, only IFA_ADDRESS needs to be set.
|
|
*/
|
|
switch (osa->u.sa.sa_family) {
|
|
case AF_INET:
|
|
mnl_attr_put_u32(nlh, IFA_LOCAL, osa->u.sin.sin_addr.s_addr);
|
|
mnl_attr_put_u32(nlh, IFA_ADDRESS, osa->u.sin.sin_addr.s_addr);
|
|
break;
|
|
case AF_INET6:
|
|
mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), &osa->u.sin6.sin6_addr);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netdev_mnl_add_route(struct osmo_mnl *omnl,
|
|
unsigned int if_index,
|
|
const struct osmo_sockaddr *dst_osa,
|
|
uint8_t dst_prefix,
|
|
const struct osmo_sockaddr *gw_osa)
|
|
{
|
|
char buf[MNL_SOCKET_BUFFER_SIZE];
|
|
struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
|
|
struct rtmsg *rtm;
|
|
|
|
nlh->nlmsg_type = RTM_NEWROUTE;
|
|
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
|
|
nlh->nlmsg_seq = time(NULL);
|
|
|
|
rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(*rtm));
|
|
rtm->rtm_family = dst_osa->u.sa.sa_family;
|
|
rtm->rtm_dst_len = dst_prefix;
|
|
rtm->rtm_src_len = 0;
|
|
rtm->rtm_tos = 0;
|
|
rtm->rtm_protocol = RTPROT_STATIC;
|
|
rtm->rtm_table = RT_TABLE_MAIN;
|
|
rtm->rtm_type = RTN_UNICAST;
|
|
rtm->rtm_scope = gw_osa ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
|
|
rtm->rtm_flags = 0;
|
|
|
|
switch (dst_osa->u.sa.sa_family) {
|
|
case AF_INET:
|
|
mnl_attr_put_u32(nlh, RTA_DST, dst_osa->u.sin.sin_addr.s_addr);
|
|
break;
|
|
case AF_INET6:
|
|
mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_osa->u.sin6.sin6_addr);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
mnl_attr_put_u32(nlh, RTA_OIF, if_index);
|
|
|
|
if (gw_osa) {
|
|
switch (gw_osa->u.sa.sa_family) {
|
|
case AF_INET:
|
|
mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_osa->u.sin.sin_addr.s_addr);
|
|
break;
|
|
case AF_INET6:
|
|
mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_osa->u.sin6.sin6_addr);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
|
|
LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* if ENABLE_LIBMNL */
|
|
|
|
/*! Allocate a new netdev object.
|
|
* \param[in] ctx talloc context to use as a parent when allocating the netdev object
|
|
* \param[in] name A name providen to identify the netdev object
|
|
* \returns newly allocated netdev object on success; NULL on error
|
|
*/
|
|
struct osmo_netdev *osmo_netdev_alloc(void *ctx, const char *name)
|
|
{
|
|
struct osmo_netdev *netdev;
|
|
|
|
netdev = talloc_zero(ctx, struct osmo_netdev);
|
|
if (!netdev)
|
|
return NULL;
|
|
|
|
netdev->name = talloc_strdup(netdev, name);
|
|
|
|
llist_add_tail(&netdev->entry, &g_netdev_list);
|
|
return netdev;
|
|
}
|
|
|
|
/*! Free an allocated netdev object.
|
|
* \param[in] netdev The netdev object to free
|
|
*/
|
|
void osmo_netdev_free(struct osmo_netdev *netdev)
|
|
{
|
|
if (!netdev)
|
|
return;
|
|
if (osmo_netdev_is_registered(netdev))
|
|
osmo_netdev_unregister(netdev);
|
|
llist_del(&netdev->entry);
|
|
talloc_free(netdev);
|
|
}
|
|
|
|
/*! Start managing the network device referenced by the netdev object.
|
|
* \param[in] netdev The netdev object to open
|
|
* \returns 0 on success; negative on error
|
|
*/
|
|
int osmo_netdev_register(struct osmo_netdev *netdev)
|
|
{
|
|
char ifnamebuf[IF_NAMESIZE];
|
|
struct osmo_netns_switch_state switch_state;
|
|
int rc = 0;
|
|
|
|
if (netdev->registered)
|
|
return -EALREADY;
|
|
|
|
netdev->netns_ctx = netdev_netns_ctx_get(netdev->netns_name ? : "");
|
|
if (!netdev->netns_ctx)
|
|
return -EFAULT;
|
|
|
|
NETDEV_NETNS_ENTER(netdev, &switch_state, "register");
|
|
|
|
if (!if_indextoname(netdev->ifindex, ifnamebuf)) {
|
|
rc = -ENODEV;
|
|
goto err_put_exit;
|
|
}
|
|
osmo_talloc_replace_string(netdev, &netdev->dev_name, ifnamebuf);
|
|
|
|
#if ENABLE_LIBMNL
|
|
rc = netdev_mnl_request_initial_dump(netdev->netns_ctx->omnl, netdev->ifindex);
|
|
#endif
|
|
|
|
NETDEV_NETNS_EXIT(netdev, &switch_state, "register");
|
|
|
|
netdev->registered = true;
|
|
return rc;
|
|
|
|
err_put_exit:
|
|
NETDEV_NETNS_EXIT(netdev, &switch_state, "register");
|
|
netdev_netns_ctx_put(netdev->netns_ctx);
|
|
return rc;
|
|
}
|
|
|
|
/*! Unregister the netdev object (stop managing /moniutoring the interface)
|
|
* \param[in] netdev The netdev object to close
|
|
* \returns 0 on success; negative on error
|
|
*/
|
|
int osmo_netdev_unregister(struct osmo_netdev *netdev)
|
|
{
|
|
if (!netdev->registered)
|
|
return -EALREADY;
|
|
|
|
netdev->if_up_known = false;
|
|
netdev->if_mtu_known = false;
|
|
|
|
netdev_netns_ctx_put(netdev->netns_ctx);
|
|
netdev->registered = false;
|
|
return 0;
|
|
}
|
|
|
|
/*! Retrieve whether the netdev object is in "registered" state.
|
|
* \param[in] netdev The netdev object to check
|
|
* \returns true if in state "registered"; false otherwise
|
|
*/
|
|
bool osmo_netdev_is_registered(struct osmo_netdev *netdev)
|
|
{
|
|
return netdev->registered;
|
|
}
|
|
|
|
/*! Set private user data pointer on the netdev object.
|
|
* \param[in] netdev The netdev object where the field is set
|
|
*/
|
|
void osmo_netdev_set_priv_data(struct osmo_netdev *netdev, void *priv_data)
|
|
{
|
|
netdev->priv_data = priv_data;
|
|
}
|
|
|
|
/*! Get private user data pointer from the netdev object.
|
|
* \param[in] netdev The netdev object from where to retrieve the field
|
|
* \returns The current value of the priv_data field.
|
|
*/
|
|
void *osmo_netdev_get_priv_data(struct osmo_netdev *netdev)
|
|
{
|
|
return netdev->priv_data;
|
|
}
|
|
|
|
/*! Set data_ind_cb callback, called when a new packet is received on the network interface.
|
|
* \param[in] netdev The netdev object where the field is set
|
|
* \param[in] data_ind_cb the user provided function to be called when the link status (UP/DOWN) changes
|
|
*/
|
|
void osmo_netdev_set_ifupdown_ind_cb(struct osmo_netdev *netdev, osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb)
|
|
{
|
|
netdev->ifupdown_ind_cb = ifupdown_ind_cb;
|
|
}
|
|
|
|
/*! Set dev_name_chg_cb callback, called when a change in the network name is detected
|
|
* \param[in] netdev The netdev object where the field is set
|
|
* \param[in] dev_name_chg_cb the user provided function to be called when a the interface is renamed
|
|
*/
|
|
void osmo_netdev_set_dev_name_chg_cb(struct osmo_netdev *netdev, osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb)
|
|
{
|
|
netdev->dev_name_chg_cb = dev_name_chg_cb;
|
|
}
|
|
|
|
/*! Set mtu_chg_cb callback, called when a change in the network name is detected
|
|
* \param[in] netdev The netdev object where the field is set
|
|
* \param[in] mtu_chg_cb the user provided function to be called when the configured MTU at the interface changes
|
|
*/
|
|
void osmo_netdev_set_mtu_chg_cb(struct osmo_netdev *netdev, osmo_netdev_mtu_chg_cb_t mtu_chg_cb)
|
|
{
|
|
netdev->mtu_chg_cb = mtu_chg_cb;
|
|
}
|
|
|
|
/*! Get name used to identify the netdev object.
|
|
* \param[in] netdev The netdev object from where to retrieve the field
|
|
* \returns The current value of the name used to identify the netdev object
|
|
*/
|
|
const char *osmo_netdev_get_name(const struct osmo_netdev *netdev)
|
|
{
|
|
return netdev->name;
|
|
}
|
|
|
|
/*! Set (specify) interface index identifying the network interface to manage
|
|
* \param[in] netdev The netdev object where the field is set
|
|
* \param[in] ifindex The interface index identifying the interface
|
|
* \returns 0 on success; negative on error
|
|
*
|
|
* The ifindex, together with the netns_name (see
|
|
* osmo_netdev_netns_name_set()), form together the key identifiers of a
|
|
* network interface to manage.
|
|
* This field is used during osmo_netdev_register() time, and hence must be set
|
|
* before calling that API, and cannot be changed when the netdev object is in
|
|
* "registered" state.
|
|
*/
|
|
int osmo_netdev_set_ifindex(struct osmo_netdev *netdev, unsigned int ifindex)
|
|
{
|
|
if (netdev->registered)
|
|
return -EALREADY;
|
|
netdev->ifindex = ifindex;
|
|
return 0;
|
|
}
|
|
|
|
/*! Get interface index identifying the interface managed by netdev
|
|
* \param[in] netdev The netdev object from where to retrieve the field
|
|
* \returns The current value of the configured netdev interface ifindex to use (0 = unset)
|
|
*/
|
|
unsigned int osmo_netdev_get_ifindex(const struct osmo_netdev *netdev)
|
|
{
|
|
return netdev->ifindex;
|
|
}
|
|
|
|
/*! Set (specify) name of the network namespace where the network interface to manage is located
|
|
* \param[in] netdev The netdev object where the field is set
|
|
* \param[in] netns_name The network namespace where the network interface is located
|
|
* \returns 0 on success; negative on error
|
|
*
|
|
* The netns_name, together with the ifindex (see
|
|
* osmo_netdev_ifindex_set()), form together the key identifiers of a
|
|
* network interface to manage.
|
|
* This field is used during osmo_netdev_register() time, and hence must be set
|
|
* before calling that API, and cannot be changed when the netdev object is in
|
|
* "registered" state.
|
|
* If left as NULL (default), the management will be done in the current network namespace.
|
|
*/
|
|
int osmo_netdev_set_netns_name(struct osmo_netdev *netdev, const char *netns_name)
|
|
{
|
|
if (netdev->registered)
|
|
return -EALREADY;
|
|
osmo_talloc_replace_string(netdev, &netdev->netns_name, netns_name);
|
|
return 0;
|
|
}
|
|
|
|
/*! Get name of network namespace used when opening the netdev interface
|
|
* \param[in] netdev The netdev object from where to retrieve the field
|
|
* \returns The current value of the configured network namespace
|
|
*/
|
|
const char *osmo_netdev_get_netns_name(const struct osmo_netdev *netdev)
|
|
{
|
|
return netdev->netns_name;
|
|
}
|
|
|
|
/*! Get name used to name the network interface created by the netdev object
|
|
* \param[in] netdev The netdev object from where to retrieve the field
|
|
* \returns The interface name (or NULL if unknown)
|
|
*
|
|
* This information is retrieved internally once the netdev object enters the
|
|
* "registered" state. Hence, when not registered NULL can be returned.
|
|
*/
|
|
const char *osmo_netdev_get_dev_name(const struct osmo_netdev *netdev)
|
|
{
|
|
return netdev->dev_name;
|
|
}
|
|
|
|
/*! Bring netdev interface UP or DOWN.
|
|
* \param[in] netdev The netdev object managing the netdev interface
|
|
* \param[in] ifupdown true to set the interface UP, false to set it DOWN
|
|
* \returns 0 on succes; negative on error.
|
|
*/
|
|
int osmo_netdev_ifupdown(struct osmo_netdev *netdev, bool ifupdown)
|
|
{
|
|
struct osmo_netns_switch_state switch_state;
|
|
int rc;
|
|
|
|
if (!netdev->registered)
|
|
return -ENODEV;
|
|
|
|
LOGNETDEV(netdev, LOGL_NOTICE, "Bringing dev %s %s\n",
|
|
netdev->dev_name, ifupdown ? "UP" : "DOWN");
|
|
|
|
NETDEV_NETNS_ENTER(netdev, &switch_state, "ifupdown");
|
|
|
|
#if ENABLE_LIBMNL
|
|
rc = netdev_mnl_set_ifupdown(netdev->netns_ctx->omnl, netdev->ifindex,
|
|
netdev->dev_name, ifupdown);
|
|
#else
|
|
LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
|
|
rc = -ENOTSUP;
|
|
#endif
|
|
|
|
NETDEV_NETNS_EXIT(netdev, &switch_state, "ifupdown");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*! Add IP address to netdev interface
|
|
* \param[in] netdev The netdev object managing the netdev interface
|
|
* \param[in] addr The local address to set on the interface
|
|
* \param[in] prefixlen The network prefix of addr
|
|
* \returns 0 on succes; negative on error.
|
|
*/
|
|
int osmo_netdev_add_addr(struct osmo_netdev *netdev, const struct osmo_sockaddr *addr, uint8_t prefixlen)
|
|
{
|
|
struct osmo_netns_switch_state switch_state;
|
|
char buf[INET6_ADDRSTRLEN];
|
|
int rc;
|
|
|
|
if (!netdev->registered)
|
|
return -ENODEV;
|
|
|
|
LOGNETDEV(netdev, LOGL_NOTICE, "Adding address %s/%u to dev %s\n",
|
|
osmo_sockaddr_ntop(&addr->u.sa, buf), prefixlen, netdev->dev_name);
|
|
|
|
NETDEV_NETNS_ENTER(netdev, &switch_state, "Add address");
|
|
|
|
#if ENABLE_LIBMNL
|
|
rc = netdev_mnl_add_addr(netdev->netns_ctx->omnl, netdev->ifindex, addr, prefixlen);
|
|
#else
|
|
LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
|
|
rc = -ENOTSUP;
|
|
#endif
|
|
|
|
NETDEV_NETNS_EXIT(netdev, &switch_state, "Add address");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*! Add IP route to netdev interface
|
|
* \param[in] netdev The netdev object managing the netdev interface
|
|
* \param[in] dst_addr The destination address of the route
|
|
* \param[in] dst_prefixlen The network prefix of dst_addr
|
|
* \param[in] gw_addr The gateway address. Optional, can be NULL.
|
|
* \returns 0 on succes; negative on error.
|
|
*/
|
|
int osmo_netdev_add_route(struct osmo_netdev *netdev, const struct osmo_sockaddr *dst_addr, uint8_t dst_prefixlen, const struct osmo_sockaddr *gw_addr)
|
|
{
|
|
struct osmo_netns_switch_state switch_state;
|
|
char buf_dst[INET6_ADDRSTRLEN];
|
|
char buf_gw[INET6_ADDRSTRLEN];
|
|
int rc;
|
|
|
|
if (!netdev->registered)
|
|
return -ENODEV;
|
|
|
|
LOGNETDEV(netdev, LOGL_NOTICE, "Adding route %s/%u%s%s dev %s\n",
|
|
osmo_sockaddr_ntop(&dst_addr->u.sa, buf_dst), dst_prefixlen,
|
|
gw_addr ? " via " : "",
|
|
gw_addr ? osmo_sockaddr_ntop(&gw_addr->u.sa, buf_gw) : "",
|
|
netdev->dev_name);
|
|
|
|
NETDEV_NETNS_ENTER(netdev, &switch_state, "Add route");
|
|
|
|
#if ENABLE_LIBMNL
|
|
rc = netdev_mnl_add_route(netdev->netns_ctx->omnl, netdev->ifindex, dst_addr, dst_prefixlen, gw_addr);
|
|
#else
|
|
LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
|
|
rc = -ENOTSUP;
|
|
#endif
|
|
|
|
NETDEV_NETNS_EXIT(netdev, &switch_state, "Add route");
|
|
|
|
return rc;
|
|
}
|
|
|
|
#endif /* (!EMBEDDED) */
|
|
|
|
/*! @} */
|