From 3572241df5221bb5d612f30a1866dfeca91c82df Mon Sep 17 00:00:00 2001 From: Neels Janosch Hofmeyr Date: Mon, 21 Nov 2022 01:51:03 +0100 Subject: [PATCH] tunmap: choose local GTP addr by Network Instance IEs Add 'netinst' config section to osmo-upf.cfg, to define Network Instance name to local IP address mappings. For the tunmap use case (forwarding GTP tunnels), heed the Network Instance IEs in PFCP session creation and return IP addresses in F-TEIDs accordingly. Related: SYS#6192 Related: I37bebc7d6ef75c3e6ae05e81b83a1b5895839a64 (osmo-ttcn3-hacks) Change-Id: I15ee046a1c37b83b8a83527a67a6215a30106d81 --- include/osmocom/upf/Makefile.am | 1 + include/osmocom/upf/netinst.h | 44 ++++++++++++ include/osmocom/upf/upf.h | 2 + src/osmo-upf/Makefile.am | 1 + src/osmo-upf/netinst.c | 124 ++++++++++++++++++++++++++++++++ src/osmo-upf/up_session.c | 99 +++++++++++++++++++++---- src/osmo-upf/upf.c | 1 + src/osmo-upf/upf_vty.c | 71 ++++++++++++++++++ tests/netinst.vty | 80 +++++++++++++++++++++ 9 files changed, 411 insertions(+), 12 deletions(-) create mode 100644 include/osmocom/upf/netinst.h create mode 100644 src/osmo-upf/netinst.c create mode 100644 tests/netinst.vty diff --git a/include/osmocom/upf/Makefile.am b/include/osmocom/upf/Makefile.am index 127e03e..f418cb1 100644 --- a/include/osmocom/upf/Makefile.am +++ b/include/osmocom/upf/Makefile.am @@ -1,4 +1,5 @@ noinst_HEADERS = \ + netinst.h \ up_endpoint.h \ up_peer.h \ up_session.h \ diff --git a/include/osmocom/upf/netinst.h b/include/osmocom/upf/netinst.h new file mode 100644 index 0000000..fb659b6 --- /dev/null +++ b/include/osmocom/upf/netinst.h @@ -0,0 +1,44 @@ +/* + * (C) 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 . + * + */ + +#pragma once + +#include +#include + +struct vty; + +struct network_instance { + struct llist_head entry; + + char *name; + struct osmo_sockaddr_str addr; +}; + +const struct network_instance *netinst_add(void *ctx, struct llist_head *list, const char *name, const char *addr, + const char **errmsg); +const struct network_instance *netinst_find(struct llist_head *list, const char *name); +const struct network_instance *netinst_first(struct llist_head *list); +int netinst_clear(struct llist_head *list); + +int netinst_vty_write(struct vty *vty, struct llist_head *list, const char *indent, const char *name_or_null); diff --git a/include/osmocom/upf/upf.h b/include/osmocom/upf/upf.h index 1597e2c..735a724 100644 --- a/include/osmocom/upf/upf.h +++ b/include/osmocom/upf/upf.h @@ -101,6 +101,8 @@ struct g_upf { int priority; uint32_t next_id_state; } nft; + + struct llist_head netinst; }; extern struct g_upf *g_upf; diff --git a/src/osmo-upf/Makefile.am b/src/osmo-upf/Makefile.am index 1d43ee8..403fb06 100644 --- a/src/osmo-upf/Makefile.am +++ b/src/osmo-upf/Makefile.am @@ -30,6 +30,7 @@ bin_PROGRAMS = \ $(NULL) osmo_upf_SOURCES = \ + netinst.c \ osmo_upf_main.c \ up_endpoint.c \ up_gtp_action.c \ diff --git a/src/osmo-upf/netinst.c b/src/osmo-upf/netinst.c new file mode 100644 index 0000000..8cead8e --- /dev/null +++ b/src/osmo-upf/netinst.c @@ -0,0 +1,124 @@ +/* + * (C) 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 + +/* Add a new netinst entry to the given list. + * \param ctx talloc allocate new entry from ctx. + * \param list append to this list. + * \param name The Network Instance name as given in PFCP Network Instance IEs. + * \param addr IP address string of local interface to associate with the Network Instance. + * \param errmsg On error, an error description is returned in this out-argument. + * \return new network_instance entry, or NULL on error. + */ +const struct network_instance *netinst_add(void *ctx, struct llist_head *list, const char *name, const char *addr, + const char **errmsg) +{ + struct network_instance *netinst; + if (errmsg) + *errmsg = NULL; + + if (!name || !*name) { + if (errmsg) + *errmsg = "Network Instance name must not be empty"; + return NULL; + } + + if (netinst_find(list, name)) { + if (errmsg) + *errmsg = "Network Instance entry with this name already exists"; + return NULL; + } + + netinst = talloc(ctx, struct network_instance); + *netinst = (struct network_instance){ + .name = talloc_strdup(netinst, name), + }; + if (osmo_sockaddr_str_from_str(&netinst->addr, addr, 0)) { + if (errmsg) + *errmsg = "Network Instance address is not a valid IP address string"; + talloc_free(netinst); + return NULL; + } + + llist_add_tail(&netinst->entry, list); + + return netinst; +} + +const struct network_instance *netinst_find(struct llist_head *list, const char *name) +{ + const struct network_instance *netinst; + + if (!name) + return NULL; + + llist_for_each_entry(netinst, list, entry) + if (!strcmp(netinst->name, name)) + return netinst; + + return NULL; +} + +const struct network_instance *netinst_first(struct llist_head *list) +{ + return llist_first_entry_or_null(list, struct network_instance, entry); +} + +/* Clear the list of Network Instance entries, return the nr of entries that were removed. */ +int netinst_clear(struct llist_head *list) +{ + int count = 0; + while (1) { + struct network_instance *netinst = llist_first_entry_or_null(list, struct network_instance, entry); + if (!netinst) + break; + llist_del(&netinst->entry); + talloc_free(netinst); + count++; + } + return count; +} + +/* Write one or all netinst entries to the VTY output. + * If name_or_null is NULL, print all entries. Else, print only the entry matching that name. + * Return number of printed entries. */ +int netinst_vty_write(struct vty *vty, struct llist_head *list, const char *indent, const char *name_or_null) +{ + const struct network_instance *netinst; + int count = 0; + + llist_for_each_entry(netinst, list, entry) { + if (name_or_null && strcmp(netinst->name, name_or_null)) + continue; + vty_out(vty, "%sadd %s %s%s", indent, netinst->name, netinst->addr.ip, VTY_NEWLINE); + count++; + } + return count; +} diff --git a/src/osmo-upf/up_session.c b/src/osmo-upf/up_session.c index 76dc857..36d20f2 100644 --- a/src/osmo-upf/up_session.c +++ b/src/osmo-upf/up_session.c @@ -34,6 +34,7 @@ #include #include #include +#include static enum osmo_pfcp_cause up_session_setup_gtp(struct up_session *session); @@ -114,12 +115,75 @@ struct chosen_f_teid *chosen_f_teid_find(struct llist_head *list, uint8_t choose return NULL; } +/* Find local interface's IP address by Network Instance name. Return 0 on success, or an OSMO_PFCP_CAUSE_* value on + * failure. */ +static int up_session_choose_local_ip(struct up_session *session, struct osmo_pfcp_ip_addrs *local_addr, + const char *netinst_name) +{ + const struct network_instance *netinst; + struct osmo_sockaddr osa = {}; + + if (llist_empty(&g_upf->netinst)) { + /* No network instances are configured in osmo-upf.cfg. Instead use the local address configured for + * PFCP, assuming that in a simplistic setup the host has only one interface. It is unlikely to be + * useful for a production environment where the entire point is to hand packet data from one interface + * to another, and where PFCP most probably happens on an entirely different interface, but may make + * things simpler for lab testing. */ + if (osmo_pfcp_ip_addrs_set(local_addr, + osmo_pfcp_endpoint_get_local_addr(session->up_peer->up_endpoint->pfcp_ep))) { + LOGPFSML(session->fi, LOGL_ERROR, "Invalid local address in pfcp_endpoint cfg\n"); + return OSMO_PFCP_CAUSE_SYSTEM_FAILURE; + } + LOGPFSML(session->fi, LOGL_NOTICE, + "Cannot look up Network Instance %s: No 'netinst' is configured, setting up GTP on same local" + " interface as PFCP: %s (makes sense only for lab testing)\n", + osmo_quote_str_c(OTC_SELECT, netinst_name, -1), + osmo_pfcp_ip_addrs_to_str_c(OTC_SELECT, local_addr)); + return 0; + } + + if (!netinst_name || !*netinst_name) { + /* Empty or no Network Instance IE in incoming PFCP request. Pick the first network instance; makes + * sense only in a simplistic lab setup where packet data is forwarded to the same interface that it is + * received on, and where no Network Instance is indicated by the CPF. Warn if more than one network + * instance is configured to choose from. */ + if (llist_count(&g_upf->netinst) > 1) + LOGPFSML(session->fi, LOGL_NOTICE, + "Missing Network Instance in incoming request, using the first 'netinst' from cfg\n"); + netinst = netinst_first(&g_upf->netinst); + /* there has to be a first entry, because we handled the empty list above. */ + OSMO_ASSERT(netinst); + } else { + netinst = netinst_find(&g_upf->netinst, netinst_name); + if (!netinst) { + LOGPFSML(session->fi, LOGL_ERROR, "Network Instance from PFCP request not found: %s" + " -- ensure there is a 'netinst' / 'add %s ' entry in your config\n", + osmo_quote_str_c(OTC_SELECT, netinst_name, -1), + osmo_escape_str_c(OTC_SELECT, netinst_name, -1)); + return OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE; + } + } + + /* Convert netinst IP address string first to osmo_sockaddr and then to osmo_pfcp_ip_addrs. */ + if (osmo_sockaddr_str_to_sockaddr(&netinst->addr, &osa.u.sas) + || osmo_pfcp_ip_addrs_set(local_addr, &osa)) { + LOGPFSML(session->fi, LOGL_ERROR, + "Network Instance %s from PFCP request yields no valid IP address: " + OSMO_SOCKADDR_STR_FMT "\n", + osmo_quote_str_c(OTC_SELECT, netinst_name, -1), + OSMO_SOCKADDR_STR_FMT_ARGS(&netinst->addr)); + return OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE; + } + return 0; +} + /* Choose an F-TEID (when the peer has sent CHOOSE = 1). * If the peer also sent a CHOOSE_ID, then remember this F-TEID choice under the given ID, and re-use that choice when * the same ID re-appears. The chosen IDs are saved in session->chosen_f_teids. * Return 0 on success, or an OSMO_PFCP_CAUSE_* value on failure. */ static enum osmo_pfcp_cause up_session_choose_f_teid(struct up_session *session, struct osmo_pfcp_ie_f_teid *dst, - bool choose_id_present, uint8_t choose_id) + bool choose_id_present, uint8_t choose_id, + const char *netinst_name) { struct up_endpoint *up_ep = session->up_peer->up_endpoint; struct chosen_f_teid *chosen = NULL; @@ -130,23 +194,26 @@ static enum osmo_pfcp_cause up_session_choose_f_teid(struct up_session *session, /* Re-use a previous F-TEID */ *dst = chosen->f_teid; } else { - /* Choose a new F-TEID */ + int rc; + *dst = (struct osmo_pfcp_ie_f_teid){ - .fixed = { - .teid = up_endpoint_next_teid(up_ep), - }, + .choose_flag = false, }; + + /* Determine local IP address from Network Instance value received in PFCP request */ + rc = up_session_choose_local_ip(session, &dst->fixed.ip_addr, netinst_name); + if (rc) + return rc; + + /* Choose a new TEID */ + dst->fixed.teid = up_endpoint_next_teid(up_ep); if (dst->fixed.teid == 0) { LOGPFSML(session->fi, LOGL_ERROR, "Failed to allocate an unused TEID\n"); return OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION; } - LOGPFSML(session->fi, LOGL_INFO, "Allocated new local TEID 0x%x\n", dst->fixed.teid); + LOGPFSML(session->fi, LOGL_INFO, "Allocated new local F-TEID %s\n", + osmo_pfcp_ie_f_teid_to_str_c(OTC_SELECT, dst)); - if (osmo_pfcp_ip_addrs_set(&dst->fixed.ip_addr, - osmo_pfcp_endpoint_get_local_addr(up_ep->pfcp_ep))) { - LOGPFSML(session->fi, LOGL_ERROR, "Invalid local address in pfcp_endpoint cfg\n"); - return OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION; - } /* Save this choice */ if (choose_id_present) { chosen = talloc(session, struct chosen_f_teid); @@ -270,6 +337,10 @@ int pdr_to_str_buf(char *buf, size_t buflen, const struct pdr *pdr) OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &pdr->desc.pdi.ue_ip_address.ip_addr.v6); } } + if (pdr->desc.pdi.network_inst_present) { + OSMO_STRBUF_PRINTF(sb, " netinst:"); + OSMO_STRBUF_APPEND(sb, osmo_quote_str_buf3, pdr->desc.pdi.network_inst.str, -1); + } if (pdr->local_f_teid) { OSMO_STRBUF_PRINTF(sb, " "); OSMO_STRBUF_APPEND(sb, osmo_pfcp_ie_f_teid_to_str_buf, pdr->local_f_teid); @@ -370,9 +441,13 @@ static struct pdr *pdr_create(struct up_session *session, if (pdr->desc.pdi.local_f_teid.choose_flag) { /* CHOOSE = 1: we need to pick our own local F-TEID */ struct osmo_pfcp_ie_f_teid local_f_teid; + const char *netinst_name = NULL; + if (pdr->desc.pdi.network_inst_present) + netinst_name = pdr->desc.pdi.network_inst.str; *cause = up_session_choose_f_teid(session, &local_f_teid, pdr->desc.pdi.local_f_teid.choose.choose_id_present, - pdr->desc.pdi.local_f_teid.choose.choose_id); + pdr->desc.pdi.local_f_teid.choose.choose_id, + netinst_name); if (*cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { *offending_ie = OSMO_PFCP_IEI_F_TEID; *offending_ie_present = true; diff --git a/src/osmo-upf/upf.c b/src/osmo-upf/upf.c index 3b8bfeb..0a84799 100644 --- a/src/osmo-upf/upf.c +++ b/src/osmo-upf/upf.c @@ -61,6 +61,7 @@ void g_upf_alloc(void *ctx) INIT_LLIST_HEAD(&g_upf->gtp.vty_cfg.devs); INIT_LLIST_HEAD(&g_upf->gtp.devs); + INIT_LLIST_HEAD(&g_upf->netinst); } int upf_pfcp_listen() diff --git a/src/osmo-upf/upf_vty.c b/src/osmo-upf/upf_vty.c index 45f6dec..aff7590 100644 --- a/src/osmo-upf/upf_vty.c +++ b/src/osmo-upf/upf_vty.c @@ -37,11 +37,13 @@ #include #include #include +#include enum upf_vty_node { PFCP_NODE = _LAST_OSMOVTY_NODE + 1, TUNEND_NODE, TUNMAP_NODE, + NETINST_NODE, }; static struct cmd_node cfg_pfcp_node = { @@ -252,6 +254,67 @@ DEFUN(cfg_tunmap_table_name, cfg_tunmap_table_name_cmd, return CMD_SUCCESS; } +static struct cmd_node cfg_netinst_node = { + NETINST_NODE, + "%s(config-netinst)# ", + 1, +}; + +DEFUN(cfg_netinst, cfg_netinst_cmd, + "netinst", + "Enter the Network Instance configuration node\n") +{ + vty->node = NETINST_NODE; + return CMD_SUCCESS; +} + +static int config_write_netinst(struct vty *vty) +{ + vty_out(vty, "netinst%s", VTY_NEWLINE); + netinst_vty_write(vty, &g_upf->netinst, " ", NULL); + return CMD_SUCCESS; +} + +DEFUN(cfg_netinst_add, cfg_netinst_add_cmd, + "add NAME ADDR", + "add Network Instance: associate a PFCP Network Instance name with a local IP address\n" + "Network Instance name as received in PFCP Network Instance IE\n" + "IP address of a local interface\n") +{ + const char *errmsg; + if (!netinst_add(g_upf, &g_upf->netinst, argv[0], argv[1], &errmsg)) { + vty_out(vty, "%% Error: netinst: cannot add %s %s: %s%s", argv[0], argv[1], + errmsg ? : "(unknown error)", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(show_netinst, show_netinst_cmd, + "show netinst [NAME]", + SHOW_STR "List configured Network Instance entries\n" + "Show the Network Instance with this name (show all when omitted)\n") +{ + const char *name_or_null = argc > 0 ? argv[0] : NULL; + + if (!netinst_vty_write(vty, &g_upf->netinst, " ", name_or_null)) { + if (name_or_null) + vty_out(vty, "%% No such Network Instance entry%s", VTY_NEWLINE); + else + vty_out(vty, "%% No Network Instance entries configured%s", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +DEFUN(cfg_netinst_clear, cfg_netinst_clear_cmd, + "clear", + "Remove all Network Instance entries\n") +{ + int count = netinst_clear(&g_upf->netinst); + vty_out(vty, "netinst entries removed: %d%s", count, VTY_NEWLINE); + return CMD_SUCCESS; +} + DEFUN(show_pdr, show_pdr_cmd, "show pdr", SHOW_STR @@ -348,6 +411,7 @@ void upf_vty_init() install_element_ve(&show_pdr_cmd); install_element_ve(&show_gtp_cmd); install_element_ve(&show_session_cmd); + install_element_ve(&show_netinst_cmd); install_node(&cfg_pfcp_node, config_write_pfcp); install_element(CONFIG_NODE, &cfg_pfcp_cmd); @@ -371,4 +435,11 @@ void upf_vty_init() install_element(TUNMAP_NODE, &cfg_tunmap_mockup_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_no_mockup_cmd); install_element(TUNMAP_NODE, &cfg_tunmap_table_name_cmd); + + install_node(&cfg_netinst_node, config_write_netinst); + install_element(CONFIG_NODE, &cfg_netinst_cmd); + + install_element(NETINST_NODE, &cfg_netinst_clear_cmd); + install_element(NETINST_NODE, &cfg_netinst_add_cmd); + install_element(NETINST_NODE, &show_netinst_cmd); } diff --git a/tests/netinst.vty b/tests/netinst.vty new file mode 100644 index 0000000..7b3c726 --- /dev/null +++ b/tests/netinst.vty @@ -0,0 +1,80 @@ +OsmoUPF> show ? +... + netinst List configured Network Instance entries +... +OsmoUPF> show netinst? + netinst List configured Network Instance entries +OsmoUPF> show netinst ? + [NAME] Show the Network Instance with this name (show all when omitted) + +OsmoUPF> show netinst +% No Network Instance entries configured +OsmoUPF> show netinst foo +% No such Network Instance entry + +OsmoUPF> enable + +OsmoUPF# show netinst +% No Network Instance entries configured + +OsmoUPF# configure terminal +OsmoUPF(config)# netinst + +OsmoUPF(config-netinst)# list +... + clear + add NAME ADDR + show netinst [NAME] + +OsmoUPF(config-netinst)# clear? + clear Remove all Network Instance entries +OsmoUPF(config-netinst)# clear ? + + +OsmoUPF(config-netinst)# add? + add add Network Instance: associate a PFCP Network Instance name with a local IP address +OsmoUPF(config-netinst)# add ? + NAME Network Instance name as received in PFCP Network Instance IE +OsmoUPF(config-netinst)# add foo ? + ADDR IP address of a local interface + +OsmoUPF(config-netinst)# add foo bar +% Error: netinst: cannot add foo bar: Network Instance address is not a valid IP address string +OsmoUPF(config-netinst)# add foo 1.2.3.4 +OsmoUPF(config-netinst)# add foo 2.3.4.5 +% Error: netinst: cannot add foo 2.3.4.5: Network Instance entry with this name already exists +OsmoUPF(config-netinst)# add bar 2.3.4.5 +OsmoUPF(config-netinst)# show netinst + add foo 1.2.3.4 + add bar 2.3.4.5 +OsmoUPF(config-netinst)# add baz 1:2:3:4::0 +OsmoUPF(config-netinst)# show netinst + add foo 1.2.3.4 + add bar 2.3.4.5 + add baz 1:2:3:4::0 +OsmoUPF(config-netinst)# show netinst foo + add foo 1.2.3.4 +OsmoUPF(config-netinst)# show netinst bar + add bar 2.3.4.5 +OsmoUPF(config-netinst)# show netinst baz + add baz 1:2:3:4::0 + +OsmoUPF(config-netinst)# show running-config +... +netinst + add foo 1.2.3.4 + add bar 2.3.4.5 + add baz 1:2:3:4::0 +... + +OsmoUPF(config-netinst)# clear +netinst entries removed: 3 +OsmoUPF(config-netinst)# show netinst +% No Network Instance entries configured +OsmoUPF(config-netinst)# clear +netinst entries removed: 0 + +OsmoUPF(config-netinst)# show netinst? + netinst List configured Network Instance entries +OsmoUPF(config-netinst)# show netinst ? + [NAME] Show the Network Instance with this name (show all when omitted)