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
This commit is contained in:
Neels Hofmeyr 2022-11-21 01:51:03 +01:00 committed by neels
parent e68eca0e8f
commit 3572241df5
9 changed files with 411 additions and 12 deletions

View File

@ -1,4 +1,5 @@
noinst_HEADERS = \
netinst.h \
up_endpoint.h \
up_peer.h \
up_session.h \

View File

@ -0,0 +1,44 @@
/*
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved.
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/sockaddr_str.h>
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);

View File

@ -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;

View File

@ -30,6 +30,7 @@ bin_PROGRAMS = \
$(NULL)
osmo_upf_SOURCES = \
netinst.c \
osmo_upf_main.c \
up_endpoint.c \
up_gtp_action.c \

124
src/osmo-upf/netinst.c Normal file
View File

@ -0,0 +1,124 @@
/*
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved.
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include <string.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
#include <osmocom/vty/vty.h>
#include <osmocom/upf/netinst.h>
/* 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;
}

View File

@ -34,6 +34,7 @@
#include <osmocom/upf/up_peer.h>
#include <osmocom/upf/up_session.h>
#include <osmocom/upf/up_gtp_action.h>
#include <osmocom/upf/netinst.h>
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 <ip-addr>' 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;

View File

@ -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()

View File

@ -37,11 +37,13 @@
#include <osmocom/upf/up_peer.h>
#include <osmocom/upf/up_session.h>
#include <osmocom/upf/up_gtp_action.h>
#include <osmocom/upf/netinst.h>
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);
}

80
tests/netinst.vty Normal file
View File

@ -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 ?
<cr>
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)