/* * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH * All Rights Reserved. * * Author: Neels Janosch Hofmeyr * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_GTP_TUN(TUN, LEVEL, FMT, ARGS...) \ LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_tunend_to_str_c(OTC_SELECT, (TUN)), ##ARGS) int upf_gtp_dev_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_dev *dev) { uint16_t v0_port; struct osmo_strbuf sb = { .buf = buf, .len = buflen }; OSMO_STRBUF_PRINTF(sb, "%s", dev->name ? : "null"); if (dev->name && dev->ifidx) OSMO_STRBUF_PRINTF(sb, " [%u]", dev->ifidx); if (dev->sgsn_mode) OSMO_STRBUF_PRINTF(sb, " (SGSN)"); OSMO_STRBUF_PRINTF(sb, " "); OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &dev->gtpv1.local_addr); v0_port = osmo_sockaddr_port(&dev->gtpv0.local_addr.u.sa); if (dev->gtpv0.enabled && v0_port) OSMO_STRBUF_PRINTF(sb, "/%u", v0_port); return sb.chars_needed; } char *upf_gtp_dev_to_str_c(void *ctx, const struct upf_gtp_dev *dev) { OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_dev_to_str_buf, dev) } struct upf_gtp_dev *upf_gtp_dev_find_by_name(const char *name) { struct upf_gtp_dev *dev; llist_for_each_entry(dev, &g_upf->tunend.devs, entry) { if (!strcmp(name, dev->name)) return dev; } return NULL; } struct upf_gtp_dev *upf_gtp_dev_find_by_local_addr(const struct osmo_sockaddr *local_addr) { struct upf_gtp_dev *dev; struct upf_gtp_dev *dev_any = NULL; struct osmo_sockaddr needle = *local_addr; llist_for_each_entry(dev, &g_upf->tunend.devs, entry) { /* To leave the port number out of the cmp, set the needle's port to match */ osmo_sockaddr_set_port(&needle.u.sa, osmo_sockaddr_port(&dev->gtpv1.local_addr.u.sa)); if (!osmo_sockaddr_cmp(&needle, &dev->gtpv1.local_addr)) return dev; if (osmo_sockaddr_is_any(&dev->gtpv1.local_addr) == 1) dev_any = dev; } /* No 1:1 match found, but there is a dev listening on ANY? Return that. * If there is no such dev, return NULL. */ return dev_any; } struct upf_gtp_dev *upf_gtp_dev_first() { return llist_first_entry_or_null(&g_upf->tunend.devs, struct upf_gtp_dev, entry); } /* Tell the kernel to remove the GTP device. Called implicitly by talloc_free() (see upf_gtp_dev_destruct()). */ static int upf_gtp_dev_delete(struct upf_gtp_dev *dev) { int rc; if (!dev->name) return 0; rc = gtp_dev_destroy(dev->name); if (rc < 0) { LOG_GTP_DEV(dev, LOGL_ERROR, "Error while deleting device: %s\n", strerror(errno)); return rc; } LOG_GTP_DEV(dev, LOGL_NOTICE, "Deleted GTP device\n"); dev->name = NULL; return 0; } static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev); /* Allocate state for one GTP device, add to g_upf->tunend.devs and return the created device. If state for the device of * that name already exists, do nothing and return NULL. */ static struct upf_gtp_dev *upf_gtp_dev_alloc(const char *name, const char *local_addr) { struct upf_gtp_dev *dev = upf_gtp_dev_find_by_name(name); struct osmo_sockaddr_str addr_conv; local_addr = local_addr ? : "0.0.0.0"; if (dev) { LOG_GTP_DEV(dev, LOGL_ERROR, "Device already exists. Cannot create %s %s\n", name, local_addr); return NULL; } dev = talloc(g_upf, struct upf_gtp_dev); *dev = (struct upf_gtp_dev){ .name = talloc_strdup(dev, name), .gtpv0.ofd.fd = -1, .gtpv1.ofd.fd = -1, }; INIT_LLIST_HEAD(&dev->tunnels); osmo_sockaddr_str_from_str(&addr_conv, local_addr, PORT_GTP0_U); osmo_sockaddr_str_to_sockaddr(&addr_conv, &dev->gtpv0.local_addr.u.sas); addr_conv.port = PORT_GTP1_U; osmo_sockaddr_str_to_sockaddr(&addr_conv, &dev->gtpv1.local_addr.u.sas); /* Need to add to list before setting up the destructor. A talloc_free() does automagically remove from the * list. */ llist_add(&dev->entry, &g_upf->tunend.devs); talloc_set_destructor(dev, upf_gtp_dev_destruct); return dev; } static int dev_resolve_ifidx(struct upf_gtp_dev *dev) { int rc; dev->ifidx = if_nametoindex(dev->name); if (dev->ifidx == 0) { LOG_GTP_DEV(dev, LOGL_ERROR, "No such device: '%s'\n", dev->name); talloc_free(dev); return -EIO; } /* Let's try something to see if talking to the device works. */ errno = 0; rc = gtp_list_tunnel(g_upf->tunend.genl_id, g_upf->tunend.nl); if (errno) rc = -errno; else if (rc) rc = -EINVAL; if (rc) { LOG_GTP_DEV(dev, LOGL_ERROR, "Failed to open GTP device: %s\n", strerror(-rc)); talloc_free(dev); return rc; } LOG_GTP_DEV(dev, LOGL_NOTICE, "GTP device ready (ifidx=%u)\n", dev->ifidx); return 0; } static int upf_gtp_dev_create(struct upf_gtp_dev *dev, int gtp0_fd, int gtp1_fd) { if (dev->sgsn_mode) return gtp_dev_create_sgsn(-1, dev->name, gtp0_fd, gtp1_fd); return gtp_dev_create(-1, dev->name, gtp0_fd, gtp1_fd); } int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_addr, bool listen_for_gtpv0, bool sgsn_mode) { const struct osmo_sockaddr any = { .u.sin = { .sin_family = AF_INET, .sin_port = 0, .sin_addr = { .s_addr = INADDR_ANY, }, }, }; int rc; struct upf_gtp_dev *dev; if (g_upf->tunend.mockup) { LOGP(DGTP, LOGL_NOTICE, "tunend/mockup active: not opening GTP device '%s'\n", name); return 0; } dev = upf_gtp_dev_alloc(name, local_addr); if (!dev) return -EIO; dev->sgsn_mode = sgsn_mode; rc = upf_gtp_genl_ensure_open(); if (rc) { LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot set up GTP device, failed to open mnl_socket\n"); return rc; } if (listen_for_gtpv0) { rc = osmo_sock_init_osa_ofd(&dev->gtpv0.ofd, SOCK_DGRAM, 0, &dev->gtpv0.local_addr, &any, OSMO_SOCK_F_BIND); if (rc < 0) { LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot bind GTPv0 on %s (rc=%d)\n", osmo_sockaddr_to_str_c(OTC_SELECT, &dev->gtpv0.local_addr), rc); talloc_free(dev); return -EIO; } dev->gtpv0.enabled = true; LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv0 bound\n"); } /* GTPv1 */ rc = osmo_sock_init_osa_ofd(&dev->gtpv1.ofd, SOCK_DGRAM, 0, &dev->gtpv1.local_addr, &any, OSMO_SOCK_F_BIND); if (rc < 0) { LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot bind GTPv1 (rc=%d)\n", rc); talloc_free(dev); return -EIO; } LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv1 bound\n"); if (create_gtp_dev) { int gtp0_fd = listen_for_gtpv0 ? dev->gtpv0.ofd.fd : -1; int gtp1_fd = dev->gtpv1.ofd.fd; rc = upf_gtp_dev_create(dev, gtp0_fd, gtp1_fd); if (rc == -EEXIST && gtp_dev_destroy(dev->name) == 0) { LOG_GTP_DEV(dev, LOGL_ERROR, "deleted GTP device from unclean shutdown\n"); rc = upf_gtp_dev_create(dev, gtp0_fd, gtp1_fd); } if (rc < 0) { LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot create GTP device: rc=%d\n", rc); /* name = NULL: signal to the destructor that it does not need to delete the device */ dev->name = NULL; talloc_free(dev); return -EIO; } LOG_GTP_DEV(dev, LOGL_NOTICE, "created GTP device\n"); dev->created = true; } rc = dev_resolve_ifidx(dev); if (rc) { talloc_free(dev); return -EIO; } upf_gtpu_echo_setup(dev); return 0; } void upf_gtp_devs_close() { struct upf_gtp_dev *dev; while ((dev = llist_first_entry_or_null(&g_upf->tunend.devs, struct upf_gtp_dev, entry))) talloc_free(dev); } void upf_gtp_genl_close() { if (!g_upf->tunend.nl) return; genl_socket_close(g_upf->tunend.nl); g_upf->tunend.nl = NULL; g_upf->tunend.genl_id = -1; LOGP(DGTP, LOGL_NOTICE, "Closed mnl_socket\n"); } /* Open an MNL socket which allows to create and remove GTP devices (requires CAP_NET_ADMIN). */ int upf_gtp_genl_ensure_open() { /* Already open? */ if (g_upf->tunend.nl && g_upf->tunend.genl_id >= 0) return 0; /* sanity / paranoia: if re-opening, make sure the previous socket is closed */ if (g_upf->tunend.nl) upf_gtp_genl_close(); g_upf->tunend.nl = genl_socket_open(); if (!g_upf->tunend.nl) { LOGP(DGTP, LOGL_ERROR, "Cannot open mnl_socket: %s\n", strerror(errno)); return -EIO; } g_upf->tunend.genl_id = genl_lookup_family(g_upf->tunend.nl, "gtp"); if (g_upf->tunend.genl_id < 0) { LOGP(DGTP, LOGL_ERROR, "genl family 'gtp' not found\n"); return -ENOTSUP; } LOGP(DGTP, LOGL_NOTICE, "Opened mnl_socket\n"); return 0; } struct upf_gtp_tunend { struct llist_head entry; struct upf_gtp_dev *dev; struct upf_tunend desc; bool active; }; static int upf_gtp_tunend_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_tunend *tun) { struct osmo_strbuf sb = { .buf = buf, .len = buflen }; /* "tunend{dev=apn0 access(GTP-r=1.2.3.4 TEID:l=0x1234,r=0x5678) core(UE-l=10.9.8.7)}" */ OSMO_STRBUF_PRINTF(sb, "tunend{dev=%s access(GTP-r=", tun->dev->name); OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.access.remote.addr); OSMO_STRBUF_PRINTF(sb, " TEID:l=0x%x,r=0x%x) core(UE-l=", tun->desc.access.local.teid, tun->desc.access.remote.teid); OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.core.ue_local_addr); OSMO_STRBUF_PRINTF(sb, ")}"); return sb.chars_needed; } static char *upf_gtp_tunend_to_str_c(void *ctx, const struct upf_gtp_tunend *tun) { OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_tunend_to_str_buf, tun) } static int upf_gtp_tunend_deactivate(struct upf_gtp_tunend *tun); static int upf_gtp_tunend_destruct(struct upf_gtp_tunend *tun) { if (tun->active) upf_gtp_tunend_deactivate(tun); llist_del(&tun->entry); return 0; } #define tunend_validate(TUNEND) \ do { \ OSMO_ASSERT(osmo_sockaddr_port(&(TUNEND)->access.local.addr.u.sa) == 0); \ OSMO_ASSERT(osmo_sockaddr_port(&(TUNEND)->access.remote.addr.u.sa) == 0); \ OSMO_ASSERT(osmo_sockaddr_port(&(TUNEND)->core.ue_local_addr.u.sa) == 0); \ } while (0) static struct upf_gtp_tunend *upf_gtp_tunend_alloc(struct upf_gtp_dev *dev, const struct upf_tunend *desc) { struct upf_gtp_tunend *tun = talloc(dev, struct upf_gtp_tunend); OSMO_ASSERT(tun); tunend_validate(desc); *tun = (struct upf_gtp_tunend){ .dev = dev, .desc = *desc, }; llist_add(&tun->entry, &dev->tunnels); talloc_set_destructor(tun, upf_gtp_tunend_destruct); return tun; } static struct gtp_tunnel *upf_gtp_tunend_to_gtp_tunnel(struct upf_gtp_tunend *tun) { struct gtp_tunnel *t; if (tun->desc.core.ue_local_addr.u.sas.ss_family != AF_INET || tun->desc.access.remote.addr.u.sas.ss_family != AF_INET) { LOG_GTP_TUN(tun, LOGL_ERROR, "Only capabale of IPv4\n"); return NULL; } t = gtp_tunnel_alloc(); OSMO_ASSERT(t); gtp_tunnel_set_ifidx(t, tun->dev->ifidx); gtp_tunnel_set_version(t, GTP_V1); gtp_tunnel_set_i_tei(t, tun->desc.access.local.teid); gtp_tunnel_set_o_tei(t, tun->desc.access.remote.teid); gtp_tunnel_set_sgsn_ip4(t, &tun->desc.access.remote.addr.u.sin.sin_addr); gtp_tunnel_set_ms_ip4(t, &tun->desc.core.ue_local_addr.u.sin.sin_addr); return t; } int upf_gtp_tunend_activate(struct upf_gtp_tunend *tun) { int rc; struct gtp_tunnel *t; if (tun->active) return -EALREADY; t = upf_gtp_tunend_to_gtp_tunnel(tun); if (!t) return -ENOTSUP; errno = 0; rc = gtp_add_tunnel(g_upf->tunend.genl_id, g_upf->tunend.nl, t); if (errno) { rc = -errno; } else if (rc) { rc = -EINVAL; } else { tun->active = true; } gtp_tunnel_free(t); return rc; } static struct upf_gtp_tunend *upf_gtp_dev_tunend_find(struct upf_gtp_dev *dev, const struct upf_tunend *tunend) { struct upf_gtp_tunend *tun; tunend_validate(tunend); llist_for_each_entry(tun, &dev->tunnels, entry) { if (upf_gtp_tunend_cmp(tunend, &tun->desc)) continue; return tun; } return NULL; } int upf_gtp_dev_tunend_add(struct upf_gtp_dev *dev, const struct upf_tunend *tunend) { struct upf_gtp_tunend *tun; tunend_validate(tunend); tun = upf_gtp_dev_tunend_find(dev, tunend); if (!tun) tun = upf_gtp_tunend_alloc(dev, tunend); if (tun->active) return 0; return upf_gtp_tunend_activate(tun); } int upf_gtp_dev_tunend_del(struct upf_gtp_dev *dev, const struct upf_tunend *tunend) { struct upf_gtp_tunend *tun; int rc; tunend_validate(tunend); tun = upf_gtp_dev_tunend_find(dev, tunend); if (!tun) return 0; if (tun->active) { rc = upf_gtp_tunend_deactivate(tun); if (rc) return rc; } talloc_free(tun); return 0; } static int upf_gtp_tunend_deactivate(struct upf_gtp_tunend *tun) { int rc; struct gtp_tunnel *t; if (!tun->active) { LOG_GTP_TUN(tun, LOGL_ERROR, "Cannot deactivate, not active\n"); return -EINVAL; } t = upf_gtp_tunend_to_gtp_tunnel(tun); if (!t) return -EINVAL; rc = gtp_del_tunnel(g_upf->tunend.genl_id, g_upf->tunend.nl, t); if (rc) LOG_GTP_TUN(tun, LOGL_ERROR, "Failed to delete tunnel\n"); else tun->active = false; gtp_tunnel_free(t); return rc; } static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev) { struct upf_gtp_tunend *t; /* Destruct and clean up all active tunnels before deleting the device */ while ((t = llist_first_entry_or_null(&dev->tunnels, struct upf_gtp_tunend, entry))) talloc_free(t); llist_del(&dev->entry); /* osmo_fd_close() is a noop if ofd.fd == -1 */ osmo_fd_close(&dev->gtpv0.ofd); osmo_fd_close(&dev->gtpv1.ofd); if (dev->created) upf_gtp_dev_delete(dev); return 0; } int upf_gtp_tunend_cmp(const struct upf_tunend *a, const struct upf_tunend *b) { int r; if (a == b) return 0; if (!a) return -1; if (!b) return 1; #define CMP_MEMB(MEMB) OSMO_CMP(a->MEMB, b->MEMB) if ((r = CMP_MEMB(access.local.teid))) return r; if ((r = CMP_MEMB(access.remote.teid))) return r; return osmo_sockaddr_cmp(&a->access.remote.addr, &b->access.remote.addr); }