osmo-upf/src/osmo-upf/upf_vty.c

446 lines
13 KiB
C

/* OsmoUpf interface to quagga VTY */
/*
* (C) 2021-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 <osmocom/core/sockaddr_str.h>
#include <osmocom/core/socket.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/upf_gtp.h>
#include <osmocom/upf/up_endpoint.h>
#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 = {
PFCP_NODE,
"%s(config-pfcp)# ",
1,
};
#define pfcp_vty (g_upf->pfcp.vty_cfg)
#define tunend_vty (g_upf->gtp.vty_cfg)
DEFUN(cfg_pfcp, cfg_pfcp_cmd,
"pfcp",
"Enter the PFCP configuration node\n")
{
vty->node = PFCP_NODE;
return CMD_SUCCESS;
}
static int config_write_pfcp(struct vty *vty)
{
vty_out(vty, "pfcp%s", VTY_NEWLINE);
if (strcmp(UPF_PFCP_LISTEN_DEFAULT, pfcp_vty.local_addr))
vty_out(vty, " local-addr %s%s", pfcp_vty.local_addr, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd,
"local-addr IP_ADDR",
"Set the local IP address to bind on for PFCP\n"
"IP address\n")
{
osmo_talloc_replace_string(g_upf, &pfcp_vty.local_addr, argv[0]);
return CMD_SUCCESS;
}
static struct cmd_node cfg_tunend_node = {
TUNEND_NODE,
"%s(config-tunend)# ",
1,
};
#define TUNEND_NODE_STR "Enter the 'tunend' node to configure Linux GTP kernel module usage\n"
DEFUN(cfg_tunend, cfg_tunend_cmd, "tunend", TUNEND_NODE_STR)
{
vty->node = TUNEND_NODE;
return CMD_SUCCESS;
}
/* legacy compat: "tunend" was originally named "gtp" */
DEFUN_CMD_ELEMENT(cfg_tunend, cfg_gtp_cmd, "gtp", TUNEND_NODE_STR, CMD_ATTR_HIDDEN, 0);
static int config_write_tunend(struct vty *vty)
{
struct tunend_vty_cfg_dev *d;
vty_out(vty, "tunend%s", VTY_NEWLINE);
if (g_upf->gtp.mockup)
vty_out(vty, " mockup%s", VTY_NEWLINE);
llist_for_each_entry(d, &tunend_vty.devs, entry) {
if (d->create) {
vty_out(vty, " dev create %s", d->dev_name);
if (d->local_addr)
vty_out(vty, " %s", d->local_addr);
vty_out(vty, "%s", VTY_NEWLINE);
} else {
vty_out(vty, " dev use %s%s", d->dev_name, VTY_NEWLINE);
}
}
return CMD_SUCCESS;
}
#define DEV_STR "Configure the GTP device to use for encaps/decaps.\n"
DEFUN(cfg_tunend_mockup, cfg_tunend_mockup_cmd,
"mockup",
"don't actually send commands to the GTP kernel module, just return success\n")
{
g_upf->gtp.mockup = true;
return CMD_SUCCESS;
}
DEFUN(cfg_tunend_no_mockup, cfg_tunend_no_mockup_cmd,
"no mockup",
NO_STR
"operate GTP kernel module normally\n")
{
g_upf->gtp.mockup = false;
return CMD_SUCCESS;
}
DEFUN(cfg_tunend_dev_create, cfg_tunend_dev_create_cmd,
"dev create DEVNAME [LISTEN_ADDR]",
DEV_STR
"Add GTP device, creating a new Linux kernel GTP device. Will listen on GTPv1 port "
OSMO_STRINGIFY_VAL(PORT_GTP1_U)
" and GTPv0 port " OSMO_STRINGIFY_VAL(PORT_GTP0_U) " on the specified interface, or on ANY if LISTEN_ADDR is"
" omitted.\n"
"device name, e.g. 'apn0'\n"
"IPv4 or IPv6 address to listen on, omit for ANY\n")
{
struct tunend_vty_cfg_dev *d = talloc_zero(g_upf, struct tunend_vty_cfg_dev);
d->create = true;
d->dev_name = talloc_strdup(d, argv[0]);
if (argc > 1)
d->local_addr = talloc_strdup(d, argv[1]);
llist_add(&d->entry, &tunend_vty.devs);
vty_out(vty, "Added GTP device %s (create new)%s", d->dev_name, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_tunend_dev_use, cfg_tunend_dev_use_cmd,
"dev use DEVNAME",
DEV_STR
"Add GTP device, using an existing Linux kernel GTP device, e.g. created by 'gtp-link'\n"
"device name, e.g. 'apn0'\n")
{
struct tunend_vty_cfg_dev *d = talloc_zero(g_upf, struct tunend_vty_cfg_dev);
d->create = false;
d->dev_name = talloc_strdup(d, argv[0]);
llist_add(&d->entry, &tunend_vty.devs);
vty_out(vty, "Added GTP device %s (use existing)%s", d->dev_name, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_tunend_dev_del, cfg_tunend_dev_del_cmd,
"dev delete DEVNAME",
DEV_STR
"Remove a GTP device from the configuration, and delete the Linux kernel GTP device if it was created here.\n"
"device name, e.g. 'apn0'\n")
{
const char *dev_name = argv[0];
struct tunend_vty_cfg_dev *d;
struct upf_gtp_dev *dev;
/* remove from VTY cfg */
llist_for_each_entry(d, &tunend_vty.devs, entry) {
if (strcmp(d->dev_name, dev_name))
continue;
llist_del(&d->entry);
break;
}
/* close device (and possibly delete from system, via talloc destructor) */
dev = upf_gtp_dev_find_by_name(dev_name);
if (dev)
talloc_free(dev);
return CMD_SUCCESS;
}
static struct cmd_node cfg_tunmap_node = {
TUNMAP_NODE,
"%s(config-tunmap)# ",
1,
};
#define TUNMAP_NODE_STR "Enter the 'tunmap' node to configure nftables usage\n"
DEFUN(cfg_tunmap, cfg_tunmap_cmd, "tunmap", TUNMAP_NODE_STR)
{
vty->node = TUNMAP_NODE;
return CMD_SUCCESS;
}
/* legacy compat: "tunmap" was originally named "nft" */
DEFUN_CMD_ELEMENT(cfg_tunmap, cfg_nft_cmd, "nft", TUNMAP_NODE_STR, CMD_ATTR_HIDDEN, 0);
static int config_write_tunmap(struct vty *vty)
{
vty_out(vty, "tunmap%s", VTY_NEWLINE);
if (g_upf->nft.mockup)
vty_out(vty, " mockup%s", VTY_NEWLINE);
if (g_upf->nft.table_name && strcmp(g_upf->nft.table_name, "osmo-upf"))
vty_out(vty, " table-name %s%s", g_upf->nft.table_name, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_tunmap_mockup, cfg_tunmap_mockup_cmd,
"mockup",
"don't actually send rulesets to nftables, just return success\n")
{
g_upf->nft.mockup = true;
return CMD_SUCCESS;
}
DEFUN(cfg_tunmap_no_mockup, cfg_tunmap_no_mockup_cmd,
"no mockup",
NO_STR
"operate nftables rulesets normally\n")
{
g_upf->nft.mockup = false;
return CMD_SUCCESS;
}
DEFUN(cfg_tunmap_table_name, cfg_tunmap_table_name_cmd,
"table-name TABLE_NAME",
"Set the nft inet table name to create and place GTP tunnel forwarding chains in"
" (as in 'nft add table inet foo'). If multiple instances of osmo-upf are running on the same system, each"
" osmo-upf must have its own table name. Otherwise the names of created forwarding chains will collide."
" The default table name is \"osmo-upf\".\n"
"nft inet table name\n")
{
osmo_talloc_replace_string(g_upf, &g_upf->nft.table_name, argv[0]);
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
"List all sessions' PDR and FAR status\n")
{
struct up_peer *peer;
int active_count = 0;
int inactive_count = 0;
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
struct up_session *session;
int bkt;
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
struct pdr *pdr;
llist_for_each_entry(pdr, &session->pdrs, entry) {
if (!pdr->active) {
vty_out(vty, "%s: inactive: %s%s%s%s",
session->fi->id, pdr_to_str_c(OTC_SELECT, pdr),
pdr->inactive_reason ? ": " : "",
pdr->inactive_reason ? : "",
VTY_NEWLINE);
inactive_count++;
} else {
vty_out(vty, "%s: active: %s%s",
session->fi->id, pdr_to_str_c(OTC_SELECT, pdr),
VTY_NEWLINE);
active_count++;
}
}
}
}
vty_out(vty, "(%d of %d active)%s", active_count, active_count + inactive_count, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(show_gtp, show_gtp_cmd,
"show gtp",
SHOW_STR
"Active GTP tunnels, both tunend and tunmap\n")
{
struct up_peer *peer;
int count = 0;
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
struct up_session *session;
int bkt;
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
struct up_gtp_action *a;
llist_for_each_entry(a, &session->active_gtp_actions, entry) {
vty_out(vty, "%s%s", up_gtp_action_to_str_c(OTC_SELECT, a), VTY_NEWLINE);
count++;
}
}
}
vty_out(vty, "(%d active)%s", count, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(show_session, show_session_cmd,
"show session",
SHOW_STR
"PFCP Session status\n")
{
struct up_peer *peer;
int inactive_count = 0;
int active_count = 0;
int fully_active_count = 0;
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
struct up_session *session;
int bkt;
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
vty_out(vty, "%s %s%s",
up_session_to_str_c(OTC_SELECT, session),
up_session_gtp_status(session), VTY_NEWLINE);
if (up_session_is_active(session)) {
if (up_session_is_fully_active(session, NULL, NULL))
fully_active_count++;
else
active_count++;
} else {
inactive_count++;
}
}
}
vty_out(vty, "(%d fully-active + %d partially active + %d inactive)%s",
fully_active_count, active_count, inactive_count, VTY_NEWLINE);
return CMD_SUCCESS;
}
void upf_vty_init()
{
OSMO_ASSERT(g_upf != NULL);
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);
install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd);
install_node(&cfg_tunend_node, config_write_tunend);
install_element(CONFIG_NODE, &cfg_tunend_cmd);
install_element(CONFIG_NODE, &cfg_gtp_cmd);
install_element(TUNEND_NODE, &cfg_tunend_mockup_cmd);
install_element(TUNEND_NODE, &cfg_tunend_no_mockup_cmd);
install_element(TUNEND_NODE, &cfg_tunend_dev_create_cmd);
install_element(TUNEND_NODE, &cfg_tunend_dev_use_cmd);
install_element(TUNEND_NODE, &cfg_tunend_dev_del_cmd);
install_node(&cfg_tunmap_node, config_write_tunmap);
install_element(CONFIG_NODE, &cfg_tunmap_cmd);
install_element(CONFIG_NODE, &cfg_nft_cmd);
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);
}